Skip to content

Conversation

@tegnike
Copy link
Owner

@tegnike tegnike commented Aug 26, 2024

Summary by CodeRabbit

  • 新機能
    • PDFファイルをスライドに変換する新しい機能を追加。
    • ユーザーインターフェースにアシスタントテキストの表示を制御するオプションを追加。
    • 言語に応じたローカリゼーションの強化(英語、日本語、韓国語、中国語)。
  • バグ修正
    • スライドコンバージョンの際、エラーメッセージが適切に表示されるよう修正。
  • ドキュメント
    • APIエンドポイントの追加に関するドキュメントを更新。

@vercel
Copy link

vercel bot commented Aug 26, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
aituber-kit ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 26, 2024 11:08pm

@coderabbitai
Copy link

coderabbitai bot commented Aug 26, 2024

Walkthrough

この変更は、ユーザーインターフェースのローカライズを強化し、PDFスライドの変換機能を追加するためのもので、複数の言語に対応した翻訳ファイルの更新、APIエンドポイントの作成、状態管理の改善を含んでいます。また、関連する依存関係の追加や、コンポーネント内の状態変数の変更も行われています。

Changes

ファイル 変更概要
.gitignore /public/slides/ディレクトリ内の全ファイルを無視するルールを追加し、/public/slides/demo/サブディレクトリは除外。
locales/en/translation.json 新しいキーの追加と既存キーの名称変更(ShowSettingsButtonShowControlPanel)。PDF変換機能に関連する新しいキーを追加。
locales/ja/translation.json 英語ファイルと同様に、日本語の翻訳エントリを追加。
locales/ko/translation.json 韓国語の翻訳エントリを追加および変更。
locales/zh/translation.json 中国語の翻訳エントリを追加および変更。
package.json 新しい依存関係(canvas, formidable, pdfjs-dist)の追加と、@types/nodeのバージョン更新。
src/components/menu.tsx showSettingsButtonshowControlPanelにリネームし、新しい状態変数showAssistantTextを追加。
src/components/settings/advancedSettings.tsx showSettingsButtonshowControlPanelにリネームし、新しい状態変数showAssistantTextを追加。
src/components/settings/slide.tsx 新しい状態変数updateKeyを追加し、スライドフォルダの取得を更新。
src/components/settings/slideConvert.tsx PDFファイルのアップロードと変換を行う新しいコンポーネントSlideConvertを追加。
src/features/stores/menu.ts MenuStateインターフェースのshowSettingsButtonshowControlPanelに変更。
src/features/stores/settings.ts Characterインターフェースに新しいプロパティshowAssistantTextを追加。
src/pages/api/convertSlide.ts PDFスライドを画像に変換するAPIエンドポイントを追加。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SlideConvert
    participant API

    User->>SlideConvert: PDFファイルをアップロード
    SlideConvert->>API: ファイルとパラメータを送信
    API->>API: PDFを画像に変換
    API->>SlideConvert: 変換結果を返す
    SlideConvert->>User: 成功またはエラーメッセージを表示
Loading

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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

Commits

Files that changed from the base of the PR and between ad4d7da and 144ac08.

Files ignored due to path filters (1)
  • package-lock.json is 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: プロパティ名の変更を確認しました。

showSettingsButtonshowControlPanel に変更されました。新しいプロパティ名は、意図された機能に一致しています。

コードの変更を承認します。


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: 状態変数の変更

showSettingsButtonshowControlPanel に置き換えられ、新しい状態変数 showAssistantText が追加されています。

状態変数の変更は問題ありません。


39-53: アシスタントテキストの表示切替

アシスタントテキストの表示切替が正しく実装されています。

アシスタントテキストの表示切替は問題ありません。


55-69: キャラクター名の表示切替

キャラクター名の表示切替が正しく実装されています。

キャラクター名の表示切替は問題ありません。


77-86: 英語から日本語への切替

英語から日本語への切替が正しく実装されています。

英語から日本語への切替は問題ありません。


90-107: コントロールパネルの表示切替

コントロールパネルの表示切替が正しく実装されています。

コントロールパネルの表示切替は問題ありません。

locales/zh/translation.json (2)

106-106: キーの変更

ShowSettingsButtonShowControlPanel に置き換えられています。

キーの変更は問題ありません。


107-120: 新しい翻訳キーの追加

PDF変換機能に関連する新しい翻訳キーが追加されています。

新しい翻訳キーの追加は問題ありません。

src/features/stores/settings.ts (3)

59-59: プロパティの追加が適切です

Character インターフェースに showAssistantText プロパティを追加することで、アシスタントテキストの表示を制御できるようになりました。この変更は正しく実装されています。


127-127: デフォルト値の追加が適切です

settingsStoreshowAssistantText: 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 変数は正しく追加され、レンダリングロジックに統合されています。

Comment on lines +35 to +56
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'))
}
Copy link

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.

Suggested change
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'))
}

Comment on lines +65 to +101
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
}
Copy link

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.

Suggested change
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('スライドラインの作成に失敗しました')
}
}

Comment on lines +31 to +63
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
}
Copy link

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.

Suggested change
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の変換に失敗しました')
}
}

Comment on lines +103 to +183
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![bg](${imgBase64})\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が変換されました' })
})
Copy link

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![bg](${imgBase64})\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.

Suggested change
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![bg](${imgBase64})\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![bg](${imgBase64})\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が変換されました' })
})
}

@tegnike tegnike merged commit ee1d65f into main Aug 26, 2024
terisuke pushed a commit to terisuke/aituber-kit that referenced this pull request Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants