-
-
Notifications
You must be signed in to change notification settings - Fork 143
本番リリース #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Feature/convert pdf
Feature/convert pdf
[fix] Linterエラーの修正
PDFをスライドモード用データに変換する機能を追加
UIコンポーネントの表示切替調整
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Walkthroughこの変更は、ユーザーインターフェースのローカライズを強化し、PDFスライドの変換機能を追加するためのもので、複数の言語に対応した翻訳ファイルの更新、APIエンドポイントの作成、状態管理の改善を含んでいます。また、関連する依存関係の追加や、コンポーネント内の状態変数の変更も行われています。 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant SlideConvert
participant API
User->>SlideConvert: PDFファイルをアップロード
SlideConvert->>API: ファイルとパラメータを送信
API->>API: PDFを画像に変換
API->>SlideConvert: 変換結果を返す
SlideConvert->>User: 成功またはエラーメッセージを表示
TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (invoked as PR comments)
Additionally, you can add CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
Files selected for processing (13)
- .gitignore (1 hunks)
- locales/en/translation.json (1 hunks)
- locales/ja/translation.json (1 hunks)
- locales/ko/translation.json (1 hunks)
- locales/zh/translation.json (1 hunks)
- package.json (2 hunks)
- src/components/menu.tsx (2 hunks)
- src/components/settings/advancedSettings.tsx (2 hunks)
- src/components/settings/slide.tsx (5 hunks)
- src/components/settings/slideConvert.tsx (1 hunks)
- src/features/stores/menu.ts (1 hunks)
- src/features/stores/settings.ts (3 hunks)
- src/pages/api/convertSlide.ts (1 hunks)
Files skipped from review due to trivial changes (1)
- .gitignore
Additional comments not posted (68)
src/features/stores/menu.ts (2)
5-5: プロパティ名の変更を確認しました。
showSettingsButtonがshowControlPanelに変更されました。新しいプロパティ名は、意図された機能に一致しています。コードの変更を承認します。
13-13: 初期値の変更を確認しました。
showControlPanelの初期値がtrueに設定されました。これは、更新されたMenuStateインターフェースと一致しています。コードの変更を承認します。
package.json (5)
30-30: 新しい依存関係canvasの追加を確認しました。
canvasパッケージが追加されました。これは、アプリケーションのグラフィックス処理機能を強化するために必要です。コードの変更を承認します。
31-31: 新しい依存関係formidableの追加を確認しました。
formidableパッケージが追加されました。これは、フォームデータの処理機能を強化するために必要です。コードの変更を承認します。
36-36: 新しい依存関係pdfjs-distの追加を確認しました。
pdfjs-distパッケージが追加されました。これは、PDFドキュメントの処理機能を強化するために必要です。コードの変更を承認します。
47-47: 新しい依存関係@types/formidableの追加を確認しました。
@types/formidableパッケージが追加されました。これは、formidableパッケージのTypeScriptサポートを提供するために必要です。コードの変更を承認します。
48-48: 依存関係@types/nodeの更新を確認しました。
@types/nodeパッケージが22.3.0から^22.1.0に更新されました。これは、Node.jsの型定義の互換性を確保するために必要です。コードの変更を承認します。
src/components/settings/slide.tsx (7)
7-7: 新しいコンポーネントSlideConvertのインポートを確認しました。
SlideConvertコンポーネントがインポートされました。これは、スライド変換機能を提供するために必要です。コードの変更を承認します。
20-20: 新しい状態変数updateKeyの追加を確認しました。
updateKey状態変数が追加されました。これは、コンポーネントの更新をトリガーするために必要です。コードの変更を承認します。
28-28:useEffectフックの変更を確認しました。
useEffectフックがupdateKeyに依存するように変更されました。これは、updateKeyの変更時にフォルダリストを再取得するために必要です。コードの変更を承認します。
37-39: 新しい関数handleFolderUpdateの追加を確認しました。
handleFolderUpdate関数が追加されました。これは、updateKeyをインクリメントして更新をトリガーするために必要です。コードの変更を承認します。
57-58:handleFolderChange関数の変更を確認しました。
handleFolderChange関数がisPlaying状態をリセットし、currentSlideをゼロに設定するように変更されました。これは、新しいフォルダが選択されたときにスライドの再生をリセットするために必要です。コードの変更を承認します。
87-87:select要素へのkey属性の追加を確認しました。
select要素にkey属性が追加されました。これは、updateKeyの変更時にselect要素を再レンダリングするために必要です。コードの変更を承認します。
95-97:SlideConvertコンポーネントの条件付きレンダリングを確認しました。
SlideConvertコンポーネントがselectAIServiceの値に基づいて条件付きでレンダリングされるようになりました。これは、適切なAIサービスが選択された場合にのみスライド変換機能を提供するために必要です。コードの変更を承認します。
src/components/settings/slideConvert.tsx (7)
1-3: インポートの整理必要なモジュールとフックが正しくインポートされています。
インポートの整理は問題ありません。
6-8: プロパティの定義
SlideConvertPropsインターフェースが正しく定義されています。プロパティの定義は問題ありません。
10-19: 状態変数の初期化状態変数が正しく初期化されています。
状態変数の初期化は問題ありません。
20-26: ファイル変更ハンドラーファイル変更ハンドラーが正しく実装されています。
ファイル変更ハンドラーの実装は問題ありません。
28-33: フォーム送信ハンドラーのバリデーションフォーム送信ハンドラーで必要なフィールドがチェックされています。
バリデーションの実装は問題ありません。
59-117: コンポーネントのレンダリングコンポーネントのレンダリングが正しく実装されています。
コンポーネントのレンダリングは問題ありません。
120-120: エクスポートの確認
SlideConvertコンポーネントが正しくエクスポートされています。エクスポートの実装は問題ありません。
src/components/settings/advancedSettings.tsx (5)
14-15: 状態変数の変更
showSettingsButtonがshowControlPanelに置き換えられ、新しい状態変数showAssistantTextが追加されています。状態変数の変更は問題ありません。
39-53: アシスタントテキストの表示切替アシスタントテキストの表示切替が正しく実装されています。
アシスタントテキストの表示切替は問題ありません。
55-69: キャラクター名の表示切替キャラクター名の表示切替が正しく実装されています。
キャラクター名の表示切替は問題ありません。
77-86: 英語から日本語への切替英語から日本語への切替が正しく実装されています。
英語から日本語への切替は問題ありません。
90-107: コントロールパネルの表示切替コントロールパネルの表示切替が正しく実装されています。
コントロールパネルの表示切替は問題ありません。
locales/zh/translation.json (2)
106-106: キーの変更
ShowSettingsButtonがShowControlPanelに置き換えられています。キーの変更は問題ありません。
107-120: 新しい翻訳キーの追加PDF変換機能に関連する新しい翻訳キーが追加されています。
新しい翻訳キーの追加は問題ありません。
src/features/stores/settings.ts (3)
59-59: プロパティの追加が適切です
CharacterインターフェースにshowAssistantTextプロパティを追加することで、アシスタントテキストの表示を制御できるようになりました。この変更は正しく実装されています。
127-127: デフォルト値の追加が適切です
settingsStoreにshowAssistantText: trueを追加することで、デフォルトでアシスタントテキストが表示されるようになりました。この変更は正しく実装されています。
173-173: 状態管理の更新が適切です
settingsStoreの状態取得にshowAssistantTextを追加することで、新しいプロパティが正しく処理されるようになりました。この変更は正しく実装されています。locales/ja/translation.json (12)
104-104: 翻訳の追加が適切です
"ShowAssistantText": "回答欄を表示する"の翻訳が正しく追加されています。
107-107: 翻訳の追加が適切です
"ShowControlPanel": "操作パネルを表示する"の翻訳が正しく追加されています。
108-108: 翻訳の追加が適切です
"ShowControlPanelInfo": "設定画面は Cmd + . (Mac) / Ctrl + . (Windows) で表示することができます。"の翻訳が正しく追加されています。
112-112: 翻訳の追加が適切です
"PdfConvertLabel": "PDFスライド変換"の翻訳が正しく追加されています。
113-113: 翻訳の追加が適切です
"PdfConvertDescription": "PDFをスライドモード用のデータに変換します。選択しているAIサービスがOpenAIの場合のみ利用可能です。"の翻訳が正しく追加されています。
114-114: 翻訳の追加が適切です
"PdfConvertFileUpload": "PDFファイルを選択"の翻訳が正しく追加されています。
115-115: 翻訳の追加が適切です
"PdfConvertFolderName": "保存フォルダ名"の翻訳が正しく追加されています。
116-116: 翻訳の追加が適切です
"PdfConvertModelSelect": "モデルを選択"の翻訳が正しく追加されています。
117-117: 翻訳の追加が適切です
"PdfConvertButton": "PDFをスライドに変換"の翻訳が正しく追加されています。
118-118: 翻訳の追加が適切です
"PdfConvertLoading": "変換中..."の翻訳が正しく追加されています。
119-119: 翻訳の追加が適切です
"PdfConvertSuccess": "変換が完了しました"の翻訳が正しく追加されています。
120-121: 翻訳の追加が適切です
"PdfConvertError": "変換に失敗しました"および"PdfConvertSubmitError": "PDFファイル、フォルダ名、APIキーが設定されているか確認をしてください"の翻訳が正しく追加されています。locales/ko/translation.json (10)
103-103: 번역 추가가 적절합니다
"ShowAssistantText": "답변란 표시"번역이 올바르게 추가되었습니다。
106-106: 번역 추가가 적절합니다
"ShowControlPanel": "설정 버튼 표시"번역이 올바르게 추가되었습니다。
107-107: 번역 추가가 적절합니다
"ShowControlPanelInfo": "설정 화면은 Cmd + . (Mac) / Ctrl + . (Windows)를 눌러 표시할 수 있습니다。"번역이 올바르게 추가되었습니다。
111-111: 번역 추가가 적절합니다
"PdfConvertLabel": "PDF 슬라이드 변환"번역이 올바르게 추가되었습니다。
112-112: 번역 추가가 적절합니다
"PdfConvertDescription": "PDF를 슬라이드 모드 데이터로 변환합니다. 선택한 AI 서비스가 OpenAI인 경우에만 사용 가능합니다。"번역이 올바르게 추가되었습니다。
113-113: 번역 추가가 적절합니다
"PdfConvertFileUpload": "PDF 파일 선택"번역이 올바르게 추가되었습니다。
114-114: 번역 추가가 적절합니다
"PdfConvertFolderName": "저장 폴더 이름"번역이 올바르게 추가되었습니다。
115-115: 번역 추가가 적절합니다
"PdfConvertModelSelect": "모델 선택"번역이 올바르게 추가되었습니다。
116-116: 번역 추가가 적절합니다
"PdfConvertButton": "PDF를 슬라이드로 변환"번역이 올바르게 추가되었습니다。
117-117: 번역 추가가 적절합니다
"PdfConvertLoading": "변환 중..."번역이 올바르게 추가되었습니다。src/pages/api/convertSlide.ts (1)
186-204: 問題ありません
getLanguage関数は正しく実装されています。locales/en/translation.json (12)
103-103: 問題ありません
ShowAssistantTextキーは正しく追加されています。
106-106: 問題ありません
ShowControlPanelキーは正しく追加されています。
111-111: 問題ありません
PdfConvertLabelキーは正しく追加されています。
112-112: 問題ありません
PdfConvertDescriptionキーは正しく追加されています。
113-113: 問題ありません
PdfConvertFileUploadキーは正しく追加されています。
114-114: 問題ありません
PdfConvertFolderNameキーは正しく追加されています。
115-115: 問題ありません
PdfConvertModelSelectキーは正しく追加されています。
116-116: 問題ありません
PdfConvertButtonキーは正しく追加されています。
117-117: 問題ありません
PdfConvertLoadingキーは正しく追加されています。
118-118: 問題ありません
PdfConvertSuccessキーは正しく追加されています。
119-119: 問題ありません
PdfConvertErrorキーは正しく追加されています。
120-120: 問題ありません
PdfConvertSubmitErrorキーは正しく追加されています。src/components/menu.tsx (2)
25-25: 問題ありません
showControlPanel変数は正しくリネームされ、レンダリングロジックに統合されています。
27-27: 問題ありません
showAssistantText変数は正しく追加され、レンダリングロジックに統合されています。
| setIsLoading(true) | ||
|
|
||
| const formData = new FormData() | ||
| formData.append('file', file) | ||
| formData.append('folderName', folderName) | ||
| formData.append('apiKey', apiKey) | ||
| formData.append('model', model) | ||
| formData.append('selectLanguage', selectLanguage) | ||
|
|
||
| const response = await fetch('/api/convertSlide', { | ||
| method: 'POST', | ||
| body: formData, | ||
| }) | ||
| setIsLoading(false) | ||
|
|
||
| // フォルダ更新関数を呼び出す | ||
| if (response.ok) { | ||
| onFolderUpdate() | ||
| alert(t('PdfConvertSuccess')) | ||
| } else { | ||
| alert(t('PdfConvertError')) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
フォーム送信ハンドラーの実装
フォーム送信ハンドラーが正しく実装されています。ただし、エラーハンドリングを強化するために、try-catch ブロックを追加することをお勧めします。
エラーハンドリングを強化するために、次のように try-catch ブロックを追加してください。
setIsLoading(true)
+ try {
const formData = new FormData()
formData.append('file', file)
formData.append('folderName', folderName)
formData.append('apiKey', apiKey)
formData.append('model', model)
formData.append('selectLanguage', selectLanguage)
const response = await fetch('/api/convertSlide', {
method: 'POST',
body: formData,
})
setIsLoading(false)
// フォルダ更新関数を呼び出す
if (response.ok) {
onFolderUpdate()
alert(t('PdfConvertSuccess'))
} else {
alert(t('PdfConvertError'))
}
+ } catch (error) {
+ setIsLoading(false)
+ alert(t('PdfConvertError'))
+ }Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setIsLoading(true) | |
| const formData = new FormData() | |
| formData.append('file', file) | |
| formData.append('folderName', folderName) | |
| formData.append('apiKey', apiKey) | |
| formData.append('model', model) | |
| formData.append('selectLanguage', selectLanguage) | |
| const response = await fetch('/api/convertSlide', { | |
| method: 'POST', | |
| body: formData, | |
| }) | |
| setIsLoading(false) | |
| // フォルダ更新関数を呼び出す | |
| if (response.ok) { | |
| onFolderUpdate() | |
| alert(t('PdfConvertSuccess')) | |
| } else { | |
| alert(t('PdfConvertError')) | |
| } | |
| setIsLoading(true) | |
| try { | |
| const formData = new FormData() | |
| formData.append('file', file) | |
| formData.append('folderName', folderName) | |
| formData.append('apiKey', apiKey) | |
| formData.append('model', model) | |
| formData.append('selectLanguage', selectLanguage) | |
| const response = await fetch('/api/convertSlide', { | |
| method: 'POST', | |
| body: formData, | |
| }) | |
| setIsLoading(false) | |
| // フォルダ更新関数を呼び出す | |
| if (response.ok) { | |
| onFolderUpdate() | |
| alert(t('PdfConvertSuccess')) | |
| } else { | |
| alert(t('PdfConvertError')) | |
| } | |
| } catch (error) { | |
| setIsLoading(false) | |
| alert(t('PdfConvertError')) | |
| } |
| async function createSlideLine( | ||
| imageBase64: string, | ||
| apiKey: string, | ||
| model: string, | ||
| selectLanguage: string, | ||
| previousResult: string | null | ||
| ) { | ||
| const client = new OpenAI({ apiKey }) | ||
| const additionalPrompt = previousResult | ||
| ? `Previous slide content: ${previousResult}` | ||
| : 'This is the first slide.' | ||
|
|
||
| const response = await client.chat.completions.create({ | ||
| model: `${model}`, | ||
| messages: [ | ||
| { | ||
| role: 'system', | ||
| content: `${systemPrompt.replace('{{language}}', selectLanguage)}\n${additionalPrompt}`, | ||
| }, | ||
| { | ||
| role: 'user', | ||
| content: [ | ||
| { | ||
| type: 'image_url', | ||
| image_url: { | ||
| url: `${imageBase64}`, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| response_format: { type: 'json_object' }, | ||
| }) | ||
|
|
||
| const result = JSON.parse(response.choices[0].message?.content || '{}') | ||
| return result | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
エラーハンドリングを追加してください
createSlideLine 関数はOpenAIのAPIを使用してスライドのスクリプトを生成しますが、エラーハンドリングが欠けています。以下のようにエラーハンドリングを追加することをお勧めします。
async function createSlideLine(
imageBase64: string,
apiKey: string,
model: string,
selectLanguage: string,
previousResult: string | null
) {
try {
const client = new OpenAI({ apiKey })
const additionalPrompt = previousResult
? `Previous slide content: ${previousResult}`
: 'This is the first slide.'
const response = await client.chat.completions.create({
model: `${model}`,
messages: [
{
role: 'system',
content: `${systemPrompt.replace('{{language}}', selectLanguage)}\n${additionalPrompt}`,
},
{
role: 'user',
content: [
{
type: 'image_url',
image_url: {
url: `${imageBase64}`,
},
},
],
},
],
response_format: { type: 'json_object' },
})
const result = JSON.parse(response.choices[0].message?.content || '{}')
return result
} catch (error) {
console.error('Error creating slide line:', error)
throw new Error('スライドラインの作成に失敗しました')
}
}Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async function createSlideLine( | |
| imageBase64: string, | |
| apiKey: string, | |
| model: string, | |
| selectLanguage: string, | |
| previousResult: string | null | |
| ) { | |
| const client = new OpenAI({ apiKey }) | |
| const additionalPrompt = previousResult | |
| ? `Previous slide content: ${previousResult}` | |
| : 'This is the first slide.' | |
| const response = await client.chat.completions.create({ | |
| model: `${model}`, | |
| messages: [ | |
| { | |
| role: 'system', | |
| content: `${systemPrompt.replace('{{language}}', selectLanguage)}\n${additionalPrompt}`, | |
| }, | |
| { | |
| role: 'user', | |
| content: [ | |
| { | |
| type: 'image_url', | |
| image_url: { | |
| url: `${imageBase64}`, | |
| }, | |
| }, | |
| ], | |
| }, | |
| ], | |
| response_format: { type: 'json_object' }, | |
| }) | |
| const result = JSON.parse(response.choices[0].message?.content || '{}') | |
| return result | |
| } | |
| async function createSlideLine( | |
| imageBase64: string, | |
| apiKey: string, | |
| model: string, | |
| selectLanguage: string, | |
| previousResult: string | null | |
| ) { | |
| try { | |
| const client = new OpenAI({ apiKey }) | |
| const additionalPrompt = previousResult | |
| ? `Previous slide content: ${previousResult}` | |
| : 'This is the first slide.' | |
| const response = await client.chat.completions.create({ | |
| model: `${model}`, | |
| messages: [ | |
| { | |
| role: 'system', | |
| content: `${systemPrompt.replace('{{language}}', selectLanguage)}\n${additionalPrompt}`, | |
| }, | |
| { | |
| role: 'user', | |
| content: [ | |
| { | |
| type: 'image_url', | |
| image_url: { | |
| url: `${imageBase64}`, | |
| }, | |
| }, | |
| ], | |
| }, | |
| ], | |
| response_format: { type: 'json_object' }, | |
| }) | |
| const result = JSON.parse(response.choices[0].message?.content || '{}') | |
| return result | |
| } catch (error) { | |
| console.error('Error creating slide line:', error) | |
| throw new Error('スライドラインの作成に失敗しました') | |
| } | |
| } |
| async function convertPdfToImages(pdfBuffer: Buffer): Promise<string[]> { | ||
| // PDFファイルを読み込む | ||
| const pdfData = new Uint8Array(pdfBuffer) | ||
| const pdf = await pdfjsLib.getDocument({ | ||
| data: pdfData, | ||
| standardFontDataUrl: './node_modules/pdfjs-dist/standard_fonts/', | ||
| }).promise | ||
| const pageCount = pdf.numPages | ||
| const images: string[] = [] | ||
|
|
||
| for (let i = 1; i <= pageCount; i++) { | ||
| const page = await pdf.getPage(i) | ||
| const viewport = page.getViewport({ scale: 1.0 }) | ||
|
|
||
| // Canvasを作成 | ||
| const canvas = createCanvas(viewport.width, viewport.height) | ||
| const context = canvas.getContext('2d') | ||
|
|
||
| // ページをレンダリング | ||
| const renderContext = { | ||
| canvasContext: context as any, | ||
| viewport: viewport, | ||
| } | ||
| await page.render(renderContext).promise | ||
|
|
||
| // CanvasをBase64フォーマットに変換 | ||
| const imgBuffer = canvas.toBuffer('image/png') | ||
| const base64Image = imgBuffer.toString('base64') | ||
| images.push(`data:image/png;base64,${base64Image}`) | ||
| } | ||
|
|
||
| return images | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
エラーハンドリングを追加してください
convertPdfToImages 関数はPDFバッファをBase64エンコードされた画像の配列に変換しますが、エラーハンドリングが欠けています。以下のようにエラーハンドリングを追加することをお勧めします。
async function convertPdfToImages(pdfBuffer: Buffer): Promise<string[]> {
try {
const pdfData = new Uint8Array(pdfBuffer)
const pdf = await pdfjsLib.getDocument({
data: pdfData,
standardFontDataUrl: './node_modules/pdfjs-dist/standard_fonts/',
}).promise
const pageCount = pdf.numPages
const images: string[] = []
for (let i = 1; i <= pageCount; i++) {
const page = await pdf.getPage(i)
const viewport = page.getViewport({ scale: 1.0 })
const canvas = createCanvas(viewport.width, viewport.height)
const context = canvas.getContext('2d')
const renderContext = {
canvasContext: context as any,
viewport: viewport,
}
await page.render(renderContext).promise
const imgBuffer = canvas.toBuffer('image/png')
const base64Image = imgBuffer.toString('base64')
images.push(`data:image/png;base64,${base64Image}`)
}
return images
} catch (error) {
console.error('Error converting PDF to images:', error)
throw new Error('PDFの変換に失敗しました')
}
}Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async function convertPdfToImages(pdfBuffer: Buffer): Promise<string[]> { | |
| // PDFファイルを読み込む | |
| const pdfData = new Uint8Array(pdfBuffer) | |
| const pdf = await pdfjsLib.getDocument({ | |
| data: pdfData, | |
| standardFontDataUrl: './node_modules/pdfjs-dist/standard_fonts/', | |
| }).promise | |
| const pageCount = pdf.numPages | |
| const images: string[] = [] | |
| for (let i = 1; i <= pageCount; i++) { | |
| const page = await pdf.getPage(i) | |
| const viewport = page.getViewport({ scale: 1.0 }) | |
| // Canvasを作成 | |
| const canvas = createCanvas(viewport.width, viewport.height) | |
| const context = canvas.getContext('2d') | |
| // ページをレンダリング | |
| const renderContext = { | |
| canvasContext: context as any, | |
| viewport: viewport, | |
| } | |
| await page.render(renderContext).promise | |
| // CanvasをBase64フォーマットに変換 | |
| const imgBuffer = canvas.toBuffer('image/png') | |
| const base64Image = imgBuffer.toString('base64') | |
| images.push(`data:image/png;base64,${base64Image}`) | |
| } | |
| return images | |
| } | |
| async function convertPdfToImages(pdfBuffer: Buffer): Promise<string[]> { | |
| try { | |
| const pdfData = new Uint8Array(pdfBuffer) | |
| const pdf = await pdfjsLib.getDocument({ | |
| data: pdfData, | |
| standardFontDataUrl: './node_modules/pdfjs-dist/standard_fonts/', | |
| }).promise | |
| const pageCount = pdf.numPages | |
| const images: string[] = [] | |
| for (let i = 1; i <= pageCount; i++) { | |
| const page = await pdf.getPage(i) | |
| const viewport = page.getViewport({ scale: 1.0 }) | |
| const canvas = createCanvas(viewport.width, viewport.height) | |
| const context = canvas.getContext('2d') | |
| const renderContext = { | |
| canvasContext: context as any, | |
| viewport: viewport, | |
| } | |
| await page.render(renderContext).promise | |
| const imgBuffer = canvas.toBuffer('image/png') | |
| const base64Image = imgBuffer.toString('base64') | |
| images.push(`data:image/png;base64,${base64Image}`) | |
| } | |
| return images | |
| } catch (error) { | |
| console.error('Error converting PDF to images:', error) | |
| throw new Error('PDFの変換に失敗しました') | |
| } | |
| } |
| async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
| const form = formidable({ multiples: true }) | ||
|
|
||
| form.parse(req, async (err, fields, files) => { | ||
| if (err) { | ||
| res.status(500).send('Form parse error') | ||
| return | ||
| } | ||
|
|
||
| const file = Array.isArray(files.file) ? files.file[0] : files.file | ||
| const folderName = Array.isArray(fields.folderName) | ||
| ? fields.folderName[0] | ||
| : fields.folderName | ||
| const apiKey = Array.isArray(fields.apiKey) | ||
| ? fields.apiKey[0] | ||
| : fields.apiKey | ||
| const model = Array.isArray(fields.model) ? fields.model[0] : fields.model | ||
| const selectLanguage = Array.isArray(fields.selectLanguage) | ||
| ? fields.selectLanguage[0] | ||
| : fields.selectLanguage | ||
|
|
||
| if (!file) { | ||
| res.status(400).send('No file uploaded') | ||
| return | ||
| } | ||
|
|
||
| const pdfBuffer = fs.readFileSync(file.filepath) | ||
| const images = await convertPdfToImages(pdfBuffer) | ||
| const slideDir = path.join( | ||
| process.cwd(), | ||
| 'public', | ||
| 'slides', | ||
| folderName || 'defaultFolder' | ||
| ) | ||
| const markdownPath = path.join(slideDir, 'slides.md') | ||
| const jsonPath = path.join(slideDir, 'scripts.json') | ||
|
|
||
| // 生成されたスライドデータを保存するディレクトリを作成 | ||
| if (!fs.existsSync(slideDir)) { | ||
| fs.mkdirSync(slideDir, { recursive: true }) | ||
| } | ||
|
|
||
| const scriptList: unknown[] = [] | ||
| let markdownContent = '---\nmarp: true' // Markdownの初期コンテンツ | ||
| let previousResult: string | null = null | ||
|
|
||
| console.log('start convert') | ||
|
|
||
| const language = getLanguage(selectLanguage) | ||
|
|
||
| for (let i = 0; i < images.length; i++) { | ||
| const imgBase64 = images[i] | ||
| if (apiKey && model) { | ||
| const slideLine = await createSlideLine( | ||
| imgBase64, | ||
| apiKey, | ||
| model, | ||
| language, | ||
| previousResult | ||
| ) | ||
| slideLine.page = i // ページ番号を追加 | ||
| scriptList.push(slideLine) | ||
| console.log(`=== slideLine ${i} ===`) | ||
| console.log(slideLine.line) | ||
| previousResult = slideLine.line | ||
| } else { | ||
| throw new Error('API Key and Model must not be undefined') | ||
| } | ||
|
|
||
| // Markdownコンテンツの形成 | ||
| markdownContent += `\n---\n\n` | ||
| } | ||
|
|
||
| console.log('end convert') | ||
|
|
||
| // MarkdownファイルとJSONファイルを保存 | ||
| fs.writeFileSync(markdownPath, markdownContent) | ||
| fs.writeFileSync(jsonPath, JSON.stringify(scriptList, null, 2)) | ||
|
|
||
| res.status(200).json({ message: 'PDFが変換されました' }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ファイル操作のエラーハンドリングを追加してください
handler 関数はAPIリクエストを処理するメイン関数ですが、ファイルの読み書き操作にエラーハンドリングが欠けています。以下のようにエラーハンドリングを追加することをお勧めします。
form.parse(req, async (err, fields, files) => {
if (err) {
res.status(500).send('Form parse error')
return
}
const file = Array.isArray(files.file) ? files.file[0] : files.file
const folderName = Array.isArray(fields.folderName)
? fields.folderName[0]
: fields.folderName
const apiKey = Array.isArray(fields.apiKey)
? fields.apiKey[0]
: fields.apiKey
const model = Array.isArray(fields.model) ? fields.model[0] : fields.model
const selectLanguage = Array.isArray(fields.selectLanguage)
? fields.selectLanguage[0]
: fields.selectLanguage
if (!file) {
res.status(400).send('No file uploaded')
return
}
let pdfBuffer
try {
pdfBuffer = fs.readFileSync(file.filepath)
} catch (error) {
console.error('Error reading PDF file:', error)
res.status(500).send('Error reading PDF file')
return
}
const images = await convertPdfToImages(pdfBuffer)
const slideDir = path.join(
process.cwd(),
'public',
'slides',
folderName || 'defaultFolder'
)
const markdownPath = path.join(slideDir, 'slides.md')
const jsonPath = path.join(slideDir, 'scripts.json')
if (!fs.existsSync(slideDir)) {
try {
fs.mkdirSync(slideDir, { recursive: true })
} catch (error) {
console.error('Error creating slide directory:', error)
res.status(500).send('Error creating slide directory')
return
}
}
const scriptList: unknown[] = []
let markdownContent = '---\nmarp: true'
let previousResult: string | null = null
console.log('start convert')
const language = getLanguage(selectLanguage)
for (let i = 0; i < images.length; i++) {
const imgBase64 = images[i]
if (apiKey && model) {
const slideLine = await createSlideLine(
imgBase64,
apiKey,
model,
language,
previousResult
)
slideLine.page = i
scriptList.push(slideLine)
console.log(`=== slideLine ${i} ===`)
console.log(slideLine.line)
previousResult = slideLine.line
} else {
throw new Error('API Key and Model must not be undefined')
}
markdownContent += `\n---\n\n`
}
console.log('end convert')
try {
fs.writeFileSync(markdownPath, markdownContent)
fs.writeFileSync(jsonPath, JSON.stringify(scriptList, null, 2))
} catch (error) {
console.error('Error writing files:', error)
res.status(500).send('Error writing files')
return
}
res.status(200).json({ message: 'PDFが変換されました' })
})Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async function handler(req: NextApiRequest, res: NextApiResponse) { | |
| const form = formidable({ multiples: true }) | |
| form.parse(req, async (err, fields, files) => { | |
| if (err) { | |
| res.status(500).send('Form parse error') | |
| return | |
| } | |
| const file = Array.isArray(files.file) ? files.file[0] : files.file | |
| const folderName = Array.isArray(fields.folderName) | |
| ? fields.folderName[0] | |
| : fields.folderName | |
| const apiKey = Array.isArray(fields.apiKey) | |
| ? fields.apiKey[0] | |
| : fields.apiKey | |
| const model = Array.isArray(fields.model) ? fields.model[0] : fields.model | |
| const selectLanguage = Array.isArray(fields.selectLanguage) | |
| ? fields.selectLanguage[0] | |
| : fields.selectLanguage | |
| if (!file) { | |
| res.status(400).send('No file uploaded') | |
| return | |
| } | |
| const pdfBuffer = fs.readFileSync(file.filepath) | |
| const images = await convertPdfToImages(pdfBuffer) | |
| const slideDir = path.join( | |
| process.cwd(), | |
| 'public', | |
| 'slides', | |
| folderName || 'defaultFolder' | |
| ) | |
| const markdownPath = path.join(slideDir, 'slides.md') | |
| const jsonPath = path.join(slideDir, 'scripts.json') | |
| // 生成されたスライドデータを保存するディレクトリを作成 | |
| if (!fs.existsSync(slideDir)) { | |
| fs.mkdirSync(slideDir, { recursive: true }) | |
| } | |
| const scriptList: unknown[] = [] | |
| let markdownContent = '---\nmarp: true' // Markdownの初期コンテンツ | |
| let previousResult: string | null = null | |
| console.log('start convert') | |
| const language = getLanguage(selectLanguage) | |
| for (let i = 0; i < images.length; i++) { | |
| const imgBase64 = images[i] | |
| if (apiKey && model) { | |
| const slideLine = await createSlideLine( | |
| imgBase64, | |
| apiKey, | |
| model, | |
| language, | |
| previousResult | |
| ) | |
| slideLine.page = i // ページ番号を追加 | |
| scriptList.push(slideLine) | |
| console.log(`=== slideLine ${i} ===`) | |
| console.log(slideLine.line) | |
| previousResult = slideLine.line | |
| } else { | |
| throw new Error('API Key and Model must not be undefined') | |
| } | |
| // Markdownコンテンツの形成 | |
| markdownContent += `\n---\n\n` | |
| } | |
| console.log('end convert') | |
| // MarkdownファイルとJSONファイルを保存 | |
| fs.writeFileSync(markdownPath, markdownContent) | |
| fs.writeFileSync(jsonPath, JSON.stringify(scriptList, null, 2)) | |
| res.status(200).json({ message: 'PDFが変換されました' }) | |
| }) | |
| async function handler(req: NextApiRequest, res: NextApiResponse) { | |
| const form = formidable({ multiples: true }) | |
| form.parse(req, async (err, fields, files) => { | |
| if (err) { | |
| res.status(500).send('Form parse error') | |
| return | |
| } | |
| const file = Array.isArray(files.file) ? files.file[0] : files.file | |
| const folderName = Array.isArray(fields.folderName) | |
| ? fields.folderName[0] | |
| : fields.folderName | |
| const apiKey = Array.isArray(fields.apiKey) | |
| ? fields.apiKey[0] | |
| : fields.apiKey | |
| const model = Array.isArray(fields.model) ? fields.model[0] : fields.model | |
| const selectLanguage = Array.isArray(fields.selectLanguage) | |
| ? fields.selectLanguage[0] | |
| : fields.selectLanguage | |
| if (!file) { | |
| res.status(400).send('No file uploaded') | |
| return | |
| } | |
| let pdfBuffer | |
| try { | |
| pdfBuffer = fs.readFileSync(file.filepath) | |
| } catch (error) { | |
| console.error('Error reading PDF file:', error) | |
| res.status(500).send('Error reading PDF file') | |
| return | |
| } | |
| const images = await convertPdfToImages(pdfBuffer) | |
| const slideDir = path.join( | |
| process.cwd(), | |
| 'public', | |
| 'slides', | |
| folderName || 'defaultFolder' | |
| ) | |
| const markdownPath = path.join(slideDir, 'slides.md') | |
| const jsonPath = path.join(slideDir, 'scripts.json') | |
| if (!fs.existsSync(slideDir)) { | |
| try { | |
| fs.mkdirSync(slideDir, { recursive: true }) | |
| } catch (error) { | |
| console.error('Error creating slide directory:', error) | |
| res.status(500).send('Error creating slide directory') | |
| return | |
| } | |
| } | |
| const scriptList: unknown[] = [] | |
| let markdownContent = '---\nmarp: true' | |
| let previousResult: string | null = null | |
| console.log('start convert') | |
| const language = getLanguage(selectLanguage) | |
| for (let i = 0; i < images.length; i++) { | |
| const imgBase64 = images[i] | |
| if (apiKey && model) { | |
| const slideLine = await createSlideLine( | |
| imgBase64, | |
| apiKey, | |
| model, | |
| language, | |
| previousResult | |
| ) | |
| slideLine.page = i | |
| scriptList.push(slideLine) | |
| console.log(`=== slideLine ${i} ===`) | |
| console.log(slideLine.line) | |
| previousResult = slideLine.line | |
| } else { | |
| throw new Error('API Key and Model must not be undefined') | |
| } | |
| markdownContent += `\n---\n\n` | |
| } | |
| console.log('end convert') | |
| try { | |
| fs.writeFileSync(markdownPath, markdownContent) | |
| fs.writeFileSync(jsonPath, JSON.stringify(scriptList, null, 2)) | |
| } catch (error) { | |
| console.error('Error writing files:', error) | |
| res.status(500).send('Error writing files') | |
| return | |
| } | |
| res.status(200).json({ message: 'PDFが変換されました' }) | |
| }) | |
| } |
Summary by CodeRabbit