Skip to content

Conversation

@tegnike
Copy link
Owner

@tegnike tegnike commented Apr 25, 2025

  • 新機能

    • スライド編集ページを追加し、スクリプトや補足情報の編集が画面上からできるようになりました。
    • トースト通知に「ツール」タイプを追加しました。
  • リファクタリング

    • AIの回答を処理するロジックを大幅にリファクタリングしました。
    • 会話履歴の保存ロジックを変更しました。
    • 会話履歴にテキストがリアルタイムに反映されるようになりました。
  • バグ修正

    • Live2Dモデル選択時に、稀に音声が再生されなくなる不具合を修正しました。
    • Live2Dモデル選択時に、画像のドラッグ&ドロップができない不具合が発生していたので修正しました。
    • 停止ボタン押下時に、適切に音声再生やリップシンクが停止していなかった問題を修正しました。
  • テスト

    • 多様な機能の単体テストを多数追加しました(チャット応答、音声合成、メッセージ処理など)。
    • Jest等のテスト環境・モック・ガイドラインを整備し、CI/CDに自動テストを追加しました。
  • ドキュメント

    • スポンサーを追加しました。
  • ライセンス

    • Live2D利用時の追加料金率を変更しました。

Summary by CodeRabbit

  • 新機能

    • スライド編集ページを追加し、スクリプトや補足情報の編集・保存が可能に
    • Live2Dキャンバスへの画像ドラッグ&ドロップ対応
    • スライド選択時に「編集」ボタンを追加、スライドエディタへの遷移が可能に
    • チャットログの保存・復元がより高速かつ安定化(メッセージのアップサート対応)
    • 音声再生・リップシンクの強制停止機能を追加
    • メッセージID生成の標準化と一意管理を導入
    • トースト通知に「ツール」タイプを追加
  • 改善

    • チャット・音声処理の進行中に他操作で即時停止可能に(SpeakQueueの停止制御強化)
    • マルチメッセージ保存API対応で安定性向上
    • チャットメッセージ処理の分割・モジュール化とコードブロック対応強化
    • スライド操作UIのレイアウトとアクセシビリティ改善
    • エラーハンドリングの堅牢化(APIレスポンスやストリーム処理)
    • 各種多言語トースト・UI文言の表現改善と追加
    • バージョン表記を「2.33.0」に更新
  • 多言語対応

    • トースト通知やUI文言を多数の言語で改善・追加(例:「UsingTool」メッセージ、新しいスライド編集関連文言)
  • ドキュメント

    • テストガイドライン、開発プロセス、プロジェクト構造、技術スタック、API管理ルールなど多数のドキュメントを新規追加・更新
    • 環境変数NEXT_PUBLIC_INCLUDE_SYSTEM_MESSAGES_IN_CUSTOM_APIの追加と関連ドキュメント整備
    • ライセンス文書のLive2D追加料金を25%から40%に改定
    • スライド設定・編集に関するユーザー向けガイドを拡充
  • バグ修正

    • スライドAPIでのパスバリデーション強化と不正リクエスト対応
    • チャット・音声・ストリーム処理の例外処理・エラーハンドリングを強化
  • テスト・CI/CD

    • Jestを用いた包括的なユニットテスト・モック実装を大幅追加
    • GitHub Actionsによるテスト自動化ワークフローを導入
    • テストカバレッジ・ウォッチモードのnpmスクリプト追加

ochisamu and others added 30 commits April 9, 2025 22:57
…た。また、APIエンドポイントでのメッセージ処理を配列対応に変更し、既存のメッセージとの統合を行いました。
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
feat(messages): 非同期での英語から日本語への変換処理を改善し、音声合成のバッファ処理を最適化
トーストに現在利用しているツール情報を載せるようにする
…p-fix

Live2Dモデルでドラッグ&ドロップによる画像アップロード機能を追加
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
Co-Authored-By: ニケちゃん / nikechan <o3nike.teg.14@gmail.com>
@vercel
Copy link

vercel bot commented Apr 25, 2025

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 Apr 25, 2025 10:28pm

@coderabbitai
Copy link

coderabbitai bot commented Apr 25, 2025

Walkthrough

この変更は、プロジェクト全体にわたる大規模なアップデートです。主な内容は、包括的なテスト環境の導入、JestとTesting Libraryによるユニットテスト・モックの整備、CI/CDによる自動テストワークフローの追加、チャット・メッセージ処理・音声合成・スライド編集機能の大幅なリファクタリングおよび強化です。また、多言語対応のトーストメッセージやスライド編集用の新規ページ・APIエンドポイントの追加、UIコンポーネントの改良、ユーティリティ関数や型定義の拡張も含まれています。

Changes

ファイル群 変更概要
.cursor/rules/test.mdc, jest.config.js, jest.setup.*, .github/workflows/test.yml, package.json テストガイドライン文書、Jest設定、セットアップスクリプト、GitHub ActionsによるCIテストワークフロー、テスト関連依存パッケージ・スクリプトの追加
src/__mocks__/*, src/__tests__/*, src/__tests__/testUtils.ts Jest用の各種モック実装(canvas, OpenAI, ReadableStream等)、チャット・メッセージ・音声合成・ユーティリティ関数のユニットテスト追加、テスト補助関数の新規追加
src/features/chat/handlers.ts, src/features/chat/difyChat.ts, src/features/chat/vercelAIChat.ts, src/features/chat/openAIAudioChat.ts, src/features/messages/speakCharacter.ts, src/features/messages/speakQueue.ts, src/features/messages/live2dHandler.ts, src/features/messages/messageSelectors.ts, src/features/messages/messages.ts, src/features/stores/home.ts, src/features/stores/toast.ts, src/features/vrmViewer/model.ts, src/features/lipSync/lipSync.ts, src/features/youtube/youtubeComments.ts, src/utils/messageUtils.ts チャット・AI応答・音声合成・メッセージ処理の大規模リファクタリング、upsertMessage・セッション管理・ストップ制御・ID生成・型拡張・エラー処理強化、トーストタイプ追加、ユーティリティ関数新設
src/components/* Live2D画像ドラッグ&ドロップ対応、メッセージ入力・受信・スライド・トースト・コントロールUIの機能追加・プロップ拡張・デザイン調整
src/pages/api/* スライド編集・補足・チャットログ保存APIの新設・拡張、バリデーション・エラーハンドリング強化
src/pages/slide-editor/[slideName].tsx スライド編集用の新規ページ追加(スクリプト・補足編集、ナビゲーション、保存・復元、UI制御含む)
src/hooks/* 音声認識・リアルタイムAPI利用時の発話停止制御の追加
locales/*/translation.json トーストメッセージ等の多言語翻訳追加・修正(各言語で新規キー追加・既存文言の調整)
tailwind.config.js トースト用新規カラー追加

Changes(ファイル詳細)

ファイル/ディレクトリ 変更内容概要
.cursor/rules/test.mdc, jest.config.js, jest.setup.*, .github/workflows/test.yml, package.json テストガイドライン・Jest/Testing Library設定・CIワークフロー・依存追加
src/__mocks__/*, src/__tests__/*, src/__tests__/testUtils.ts モック実装とユニットテストの追加
src/features/chat/handlers.ts チャット・AI応答・発話処理のリファクタリング・upsertMessage対応・コードブロック/感情タグ処理強化
src/features/messages/speakQueue.ts シングルトン化・stopAll/セッション管理・発話停止制御の強化
src/features/messages/speakCharacter.ts 発話処理のキャンセル制御・非同期変換の同期化・エラー処理改善・関数エクスポート
src/features/stores/home.ts upsertMessage追加・チャットログの差分保存・バッチ保存・初期化処理
src/features/messages/live2dHandler.ts, src/features/vrmViewer/model.ts, src/features/lipSync/lipSync.ts 発話停止・リセット・LipSync制御の明示的管理
src/features/chat/difyChat.ts, src/features/chat/vercelAIChat.ts, src/features/chat/openAIAudioChat.ts ストリーミング応答処理・エラー処理・ツール呼び出し通知・型安全性強化
src/features/messages/messageSelectors.ts, src/features/messages/messages.ts, src/utils/messageUtils.ts メッセージID生成・正規化処理・型定義拡張
src/components/* UIコンポーネントの機能追加・プロップ拡張・デザイン調整
src/pages/api/* スライド/チャットログAPIの新設・拡張・バリデーション強化
src/pages/slide-editor/[slideName].tsx スライド編集ページ新設(スクリプト・補足編集、保存/復元、ナビゲーション等)
src/hooks/* 音声認識・発話停止制御の強化
locales/*/translation.json 多言語トースト・UI文言の追加・修正
tailwind.config.js トースト用カラー追加

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MessageInput
    participant SpeakQueue
    participant ChatHandler
    participant AIService
    participant HomeStore
    participant Toast

    User->>MessageInput: メッセージ入力・送信
    MessageInput->>SpeakQueue: stopAll()(発話停止)
    MessageInput->>ChatHandler: handleSendChatFn(text)
    ChatHandler->>HomeStore: upsertMessage({role: 'user', content: text})
    ChatHandler->>AIService: getAIChatResponseStream(messages)
    AIService-->>ChatHandler: ストリーム応答
    ChatHandler->>HomeStore: upsertMessage({role: 'assistant', content: chunk})
    ChatHandler->>SpeakQueue: addTask(音声合成)
    SpeakQueue->>HomeStore: isSpeaking更新
    ChatHandler->>Toast: show(エラー/ツール通知等)
Loading
sequenceDiagram
    participant SlideEditor
    participant API
    participant FileSystem

    SlideEditor->>API: GET /api/getSlideFolders, /api/getSupplement
    API->>FileSystem: slides.md, scripts.json, supplement.txt 読み込み
    API-->>SlideEditor: スクリプト・補足データ返却

    SlideEditor->>User: 編集操作(スクリプト/補足)
    User->>SlideEditor: 保存要求
    SlideEditor->>API: POST /api/updateSlideData
    API->>FileSystem: scripts.json, supplement.txt 上書き保存
    API-->>SlideEditor: 成功/エラー応答
Loading

Possibly related PRs

  • tegnike/aituber-kit#360: src/features/chat/handlers.ts のチャット処理・AI応答ストリーミング・upsertMessage導入等の大規模リファクタリングと密接に関連。
  • tegnike/aituber-kit#337: スライド編集機能・UI・API・ナビゲーション導入に直接関係。
  • tegnike/aituber-kit#368: MessageInput コンポーネントの isSpeaking プロップ追加・マイクボタン制御に関する変更が一致。

🪧 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>, please review it.
    • 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 gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @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 using 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 generate sequence diagram to generate a sequence diagram of the changes in this 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.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

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.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Apr 25, 2025

Deploying aituber-kit with  Cloudflare Pages  Cloudflare Pages

Latest commit: 261504c
Status: ✅  Deploy successful!
Preview URL: https://3d3843ea.aituber-kit-4s1.pages.dev
Branch Preview URL: https://develop.aituber-kit-4s1.pages.dev

View logs

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: 15

🔭 Outside diff range comments (1)
src/features/messages/live2dHandler.ts (1)

166-192: 🛠️ Refactor suggestion

startIdleMotion メソッドの堅牢性向上

インターバル内での viewer の存在チェックと try-catch によるエラーハンドリングの追加により、メソッドの堅牢性が向上しています。ただし、静的メソッド内で this を使用している点について改善が必要です。

  try {
-   viewer.motion(idleMotion)
+   viewer.motion(idleMotion)
  } catch (error) {
    console.error('Idle motion failed:', error)
-   this.stopIdleMotion()
+   Live2DHandler.stopIdleMotion()
  }

静的メソッド内では、this ではなくクラス名 Live2DHandler を使用することで、コードの明確さと保守性が向上します。

🧰 Tools
🪛 Biome (1.9.4)

[error] 188-188: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

🧹 Nitpick comments (31)
src/components/settings/messageReceiver.tsx (1)

50-65: リンクUIが大幅に改善されました。

シンプルなリンクからスタイリングされたボタンへの変更は、以下の点で優れています:

  1. Next.jsのLinkコンポーネントを使用してクライアントサイドナビゲーションを最適化
  2. 新しいタブでの開き方と適切なセキュリティ属性(rel="noopener noreferrer"
  3. Tailwind CSSによる一貫したスタイリング
  4. アイコンの追加による視覚的なわかりやすさ

既存のLinkコンポーネント(src/components/link.tsx)を再利用することも検討できますが、この実装はこの特定のユースケースに最適化されているため、現状の実装も妥当です。

.github/workflows/test.yml (1)

22-29: テスト失敗時の出力改善とカバレッジレポートの追加を検討してください

現在のテスト実行では、失敗時の詳細情報やカバレッジレポートが設定されていません。CI/CDパイプラインの価値を高めるために、これらを追加することを検討してください。

以下のような改善を提案します:

 - name: Run tests
-  run: npm test
+  run: npm test -- --verbose
+
+- name: Generate test coverage report
+  run: npm test -- --coverage
+
+- name: Archive code coverage results
+  uses: actions/upload-artifact@v4
+  with:
+    name: code-coverage-report
+    path: coverage/
src/features/youtube/youtubeComments.ts (1)

96-195: エラーハンドリングの改善を検討してください

現在の実装では、エラーをキャッチしてconsole.errorでログ出力するだけで、エラー状態をUIに反映したり、リトライ機能を提供したりしていません。ユーザーエクスペリエンスを向上させるためにエラーハンドリングを強化することを検討してください。

 } catch (error) {
   console.error('Error fetching comments:', error)
+  // エラー状態を更新
+  homeStore.setState({ 
+    errorState: { 
+      message: 'YouTube接続エラー', 
+      detail: error instanceof Error ? error.message : '不明なエラー',
+      timestamp: new Date().toISOString()
+    } 
+  })
+  // 一定時間後に自動リトライ
+  setTimeout(() => {
+    if (homeStore.getState().errorState?.message === 'YouTube接続エラー') {
+      console.log('YouTubeコメント取得のリトライを実行します')
+      fetchAndProcessComments(handleSendChat)
+    }
+  }, 30000) // 30秒後にリトライ
 }
src/components/Live2DComponent.tsx (1)

161-186: ドラッグアンドドロップ機能の実装

画像ファイルのドラッグアンドドロップ機能の実装は良いユーザビリティ向上です。ただし、以下の改善点を検討してください:

  1. ドラッグ中のビジュアルフィードバックがありません
  2. 複数ファイルドロップ時に最初のファイルだけ処理され、ユーザーへの通知がありません
  3. 非画像ファイルのドロップ時の処理や通知がありません

以下のように改善することを提案します:

 const handleDragOver = (event: DragEvent) => {
   event.preventDefault()
+  // ドラッグ中のビジュアルフィードバック
+  if (event.dataTransfer) {
+    event.dataTransfer.dropEffect = 'copy'
+  }
 }

 const handleDrop = (event: DragEvent) => {
   event.preventDefault()

   const files = event.dataTransfer?.files
   if (!files) {
     return
   }

+  // ファイル数を確認
+  if (files.length > 1) {
+    // トースト通知を表示
+    import('@/features/stores/toast').then(({ default: toastStore }) => {
+      toastStore.setState({
+        message: '最初の画像ファイルのみが処理されます',
+        type: 'info',
+        shown: true,
+      })
+    })
+  }
+
   const file = files[0]
   if (!file) {
     return
   }

   if (file.type.startsWith('image/')) {
     const reader = new FileReader()
     reader.readAsDataURL(file)
     reader.onload = function () {
       const image = reader.result as string
       image !== '' && homeStore.setState({ modalImage: image })
+      // 成功通知
+      import('@/features/stores/toast').then(({ default: toastStore }) => {
+        toastStore.setState({
+          message: '画像が読み込まれました',
+          type: 'success',
+          shown: true,
+        })
+      })
     }
+  } else {
+    // 非画像ファイルの通知
+    import('@/features/stores/toast').then(({ default: toastStore }) => {
+      toastStore.setState({
+        message: '画像ファイルのみがサポートされています',
+        type: 'error',
+        shown: true,
+      })
+    })
   }
 }
src/__tests__/utils/textProcessing.test.ts (1)

1-52: テスト全体のカバレッジと構造について

テストファイル全体が適切に構造化されており、各関数の基本機能と特殊ケースを網羅しています。しかし、以下のようなエッジケースのテストを追加することを検討してもよいでしょう:

  1. 空文字列の処理
  2. null/undefined値のハンドリング
  3. 混合文字列(日本語と英語が混在)の正確な変換

それぞれの関数の主要な機能は現在のテストでカバーされていますが、これらのエッジケースを追加することで堅牢性がさらに向上します。

src/features/chat/vercelAIChat.ts (1)

264-284: ツール呼び出し通知の改善提案

ツール呼び出し通知の実装は素晴らしいですが、以下の点を考慮することで、さらに改善できる可能性があります:

  1. トースト通知の重複防止: 同じツールが頻繁に呼び出される場合、同一のトースト通知が連続して表示される可能性があります。すでにタグにtoolNameを含めていますが、既存のトーストを更新する、または表示頻度を制限する機能を追加することを検討してください。

  2. ツールの状態管理: ツールの開始だけでなく、完了や失敗したときの通知も追加するとユーザー体験が向上するでしょう。

  3. ツールの詳細情報: 特定のツールについて、より詳細な情報をログに記録することを検討してください。これはデバッグや分析に役立ちます。

src/features/lipSync/lipSync.ts (2)

275-285: 停止処理の拡張提案

現在のstopCurrentPlaybackメソッドは基本的な停止機能を提供していますが、以下の拡張を検討することでさらに機能を向上させることができます:

  1. オプションのコールバックパラメータを追加して、停止完了時や停止できなかった場合に呼び出せるようにする
  2. 現在の再生状態(再生中かどうか)を返すisPlayingようなメソッドの追加
  3. フェードアウト効果を適用してから停止する機能(より自然な停止体験のため)

これらの拡張により、より柔軟で洗練された音声制御が可能になるでしょう。


1-286: LipSyncクラス全体について

LipSyncクラスへの変更は、オーディオ再生の管理を大幅に改善しています。現在再生中のソースを明示的に追跡し、適切に停止する機能を追加することで、以下のような利点があります:

  1. リソースの適切な管理(不要になったオーディオソースを適切に解放)
  2. ユーザー体験の向上(新しい音声再生前に古い再生を適切に停止)
  3. 他のコンポーネント(Model、Live2DHandler、speechQueue等)との統合の改善

これらの変更は、PRの概要にある「チャット・メッセージ処理・音声合成・スライド編集機能の大幅なリファクタリングおよび強化」という目標に合致しています。

一つの改善点として、メソッドのJSDocコメントをもう少し詳細にすることで、APIの使用方法と期待される動作がより明確になるでしょう。

src/components/slideControls.tsx (2)

24-27: レイアウトのTailwind CSSへのリファクタリングは改善です

インラインスタイルや個別のマージンクラスからTailwind CSSのフレックスボックスユーティリティへの移行は、コードの保守性と一貫性を向上させています。

ただし、不要な{' '}が25行目に残っているようです。これは削除しても問題ありません。

-      {' '}

11-51: コメントの言語について

コードベース内でコメントが日本語で記述されています。これはプロジェクトの規約に沿っていると思われますが、国際的なチームがある場合は、英語でのコメントも検討することをお勧めします。

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

40-44: デバウンス用タイマーのリークを防ぐためのクリーンアップを検討してください
saveDebounceTimer はモジュールスコープで保持され、アプリが HMR(開発時)や Next.js のクライアント側ナビゲーションで再読み込みされると参照が残ったままになる恐れがあります。
window.addEventListener('beforeunload', …) などで clearTimeout する/subscribe のアンマウントフックで片付ける、あるいは Zustand の onDestroy を使うなど、タイマー解放経路を用意しておくと安全です。


91-97: 新規メッセージ追加時のバリデーションがやや緩いです
content === undefined だけを弾いているため null や空文字列が許可されます。
content == null(null 又は undefined)で弾く
typeof content === 'string' ? content.trim().length > 0 : true のように空文字チェック
を追加するとログの整合性が向上します。


165-186: fetch エラー処理の冗長ログとキャンセル制御

  1. console.error が多発すると本番でノイズになるため、自前ロガーや Sentry 等に統一しても良いでしょう。
  2. ページ離脱時・ネットワーク切断時に未完了リクエストが残ります。AbortController を使ってタイムアウト/キャンセル対応すると UX が向上します。
src/__tests__/features/chat/handlers.test.ts (1)

1-203: テストカバレッジを拡充する余地があります

現在のテストはhandleSendChatFnprocessAIResponseの基本的なシナリオをカバーしていますが、以下のようなケースも追加することでテスト範囲を拡張できます:

  1. スライドモードが有効な場合の処理
  2. 実時間APIモードが有効な場合の処理
  3. さまざまなエラーシナリオでのprocessAIResponseの動作
  4. タイムスタンプが含まれるユーザーメッセージの処理
src/pages/api/updateSlideData.ts (1)

60-67: エラーハンドリングの改善余地

現在のディレクトリ存在チェックでは、具体的なエラー種別(ENOENT)を検証していません。より明確なエラーハンドリングのために、以下のような実装に改善することを検討してください:

try {
  await fs.access(targetDir)
} catch (error: any) {
-  console.error(`Directory not found: ${targetDir}`, error)
+  if (error.code === 'ENOENT') {
+    console.error(`Directory not found: ${targetDir}`)
+    return res
+      .status(404)
+      .json({ message: 'Not Found: Slide directory does not exist.' })
+  }
+  console.error(`Error accessing directory: ${targetDir}`, error)
  return res
    .status(404)
    .json({ message: 'Not Found: Slide directory does not exist.' })
}
jest.setup.canvas.js (1)

50-97: node-canvasモジュールの重複コードを最適化することをご検討ください

node-canvasモジュールのモックが、canvasモジュールとほぼ同一のコードで実装されています。両モジュールでコード重複を避けるため、共通のモック関数を作成することをお勧めします。

+ const createCanvasMock = () => ({
+   createCanvas: jest.fn(() => ({
+     getContext: jest.fn(() => ({
+       measureText: jest.fn(() => ({ width: 0 })),
+       fillText: jest.fn(),
+       fillRect: jest.fn(),
+       drawImage: jest.fn(),
+       putImageData: jest.fn(),
+       getImageData: jest.fn(),
+       createImageData: jest.fn(),
+       setTransform: jest.fn(),
+       save: jest.fn(),
+       restore: jest.fn(),
+       translate: jest.fn(),
+       rotate: jest.fn(),
+       scale: jest.fn(),
+     })),
+     toBuffer: jest.fn(() => Buffer.from([])),
+     toDataURL: jest.fn(() => ''),
+     width: 100,
+     height: 100,
+   })),
+   loadImage: jest.fn(() =>
+     Promise.resolve({
+       width: 100,
+       height: 100,
+     })
+   ),
+   Image: jest.fn(function () {
+     this.src = ''
+     this.onload = null
+     this.width = 0
+     this.height = 0
+   }),
+   registerFont: jest.fn(),
+   parseFont: jest.fn(),
+   createImageData: jest.fn(),
+   ImageData: jest.fn(),
+   PNGStream: jest.fn(),
+   JPEGStream: jest.fn(),
+   PDFStream: jest.fn(),
+ })
+ 
  jest.mock(
    'canvas',
-   () => {
-     return {
-       createCanvas: jest.fn(() => ({
-         getContext: jest.fn(() => ({
-           measureText: jest.fn(() => ({ width: 0 })),
-           fillText: jest.fn(),
-           fillRect: jest.fn(),
-           drawImage: jest.fn(),
-           putImageData: jest.fn(),
-           getImageData: jest.fn(),
-           createImageData: jest.fn(),
-           setTransform: jest.fn(),
-           save: jest.fn(),
-           restore: jest.fn(),
-           translate: jest.fn(),
-           rotate: jest.fn(),
-           scale: jest.fn(),
-         })),
-         toBuffer: jest.fn(() => Buffer.from([])),
-         toDataURL: jest.fn(() => ''),
-         width: 100,
-         height: 100,
-       })),
-       loadImage: jest.fn(() =>
-         Promise.resolve({
-           width: 100,
-           height: 100,
-         })
-       ),
-       Image: jest.fn(function () {
-         this.src = ''
-         this.onload = null
-         this.width = 0
-         this.height = 0
-       }),
-       registerFont: jest.fn(),
-       parseFont: jest.fn(),
-       createImageData: jest.fn(),
-       ImageData: jest.fn(),
-       PNGStream: jest.fn(),
-       JPEGStream: jest.fn(),
-       PDFStream: jest.fn(),
-     }
-   },
+   () => createCanvasMock(),
    { virtual: true }
  )
  
  jest.mock(
    'node-canvas',
-   () => {
-     return {
-       createCanvas: jest.fn(() => ({
-         getContext: jest.fn(() => ({
-           measureText: jest.fn(() => ({ width: 0 })),
-           fillText: jest.fn(),
-           fillRect: jest.fn(),
-           drawImage: jest.fn(),
-           putImageData: jest.fn(),
-           getImageData: jest.fn(),
-           createImageData: jest.fn(),
-           setTransform: jest.fn(),
-           save: jest.fn(),
-           restore: jest.fn(),
-           translate: jest.fn(),
-           rotate: jest.fn(),
-           scale: jest.fn(),
-         })),
-         toBuffer: jest.fn(() => Buffer.from([])),
-         toDataURL: jest.fn(() => ''),
-         width: 100,
-         height: 100,
-       })),
-       loadImage: jest.fn(() =>
-         Promise.resolve({
-           width: 100,
-           height: 100,
-         })
-       ),
-       Image: jest.fn(function () {
-         this.src = ''
-         this.onload = null
-         this.width = 0
-         this.height = 0
-       }),
-       registerFont: jest.fn(),
-       parseFont: jest.fn(),
-       createImageData: jest.fn(),
-       ImageData: jest.fn(),
-       PNGStream: jest.fn(),
-       JPEGStream: jest.fn(),
-       PDFStream: jest.fn(),
-     }
-   },
+   () => createCanvasMock(),
    { virtual: true }
  )
src/__tests__/features/chat/openAIAudioChat.test.ts (1)

250-306: テキストのみのレスポンス処理テストに改善の余地があります

テキストのみのレスポンス処理をテストしていますが、296行目のコメントで mockController.enqueue が呼ばれないことを期待していますが、このテストではストリームの読み取りだけを行っており、実際に mockController.enqueue を呼び出していません。コメントと実装の一貫性を確保するために修正を検討してください。

- expect(mockController.enqueue).not.toHaveBeenCalled() // mockController.enqueue は呼ばれないはず
+ // テキストのみのレスポンスなので、エンキューされるデータはありません
.cursor/rules/test.mdc (1)

161-235: 特殊なモック、CI/CD統合、優先順位の説明が良好です

canvas モジュールや ReadableStream のモック、GitHub Actions による CI/CD 統合、テスト対象の優先順位、今後の課題について詳細に説明されています。これらの情報は、プロジェクトの包括的なテスト戦略を理解するために重要です。

テストカバレッジに関する具体的な目標や最小要件についても追加すると良いでしょう。

テストカバレッジの目標や最小要件(例:「重要なモジュールは80%以上のカバレッジを目指す」など)を追加することで、開発者がテスト実装の目標を理解しやすくなります。

src/pages/slide-editor/[slideName].tsx (3)

8-14: goToSlide の重複定義による責務の混在
pages 直下のページコンポーネントで goToSlide を再定義し export していますが、同名関数が別モジュールに存在する場合に衝突・メンテナンスコスト増大を招きます。
共通ロジックは /features/ などに切り出し、ページ側では import のみにしましょう。


354-378: UI フィードバックは alert でなく Toast を使いましょう
alert は同期ブロッキングで UX が低下します。既に toastStore があるため、成功/失敗をトースト表示へ置き換えると統一感が保てます。


290-310: updateScriptsState 内の条件分岐を簡潔に
findIndexmap で二度走査しています。オプショナルチェーンを使った一発書きや reduce での変換でパフォーマンスと可読性が向上します。
[static_analysis] が指摘している optional chain 化もここに該当します。

🧰 Tools
🪛 Biome (1.9.4)

[error] 290-290: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/__tests__/features/messages/messageSelectors.test.ts (3)

5-8: settingsStore のモックが他のエクスポートを失います

jest.mock() でデフォルトエクスポートをオブジェクトにすげ替えていますが、
settingsStoregetState 以外のプロパティ/メソッドが存在する場合、それらが未定義になりテストが落ちる可能性があります。
jest.requireActual を併用して既存の実装を継承し、getState だけを上書きする形にすると安全です。

 jest.mock('../../../features/stores/settings', () => {
-  return { getState: jest.fn() }
+  const actual = jest.requireActual('../../../features/stores/settings')
+  return { ...actual, getState: jest.fn() }
 })

173-181: mockReturnValue を書き換えた後の後片付けが無い

beforeEach 内で settingsStore.getState を差し替えていますが、
afterEach で元の戻り値にリセットしないと他テストへ影響する恐れがあります。
jest.resetAllMocks() ではなく mockReset() を利用すると個別に安全に戻せます。


184-219: merge 条件のユースケースをテストに追加しましょう

normalizeMessages は「同一 role かつ同一 id」の連続メッセージを結合します。
本テストケースでは id を指定しておらず、結合ロジックを実質テストしていません。
以下のようなケースを追加すると実装保証が高まります。

const messagesWithId = [
  { id: '1', role: 'user', content: 'こんにちは' },
  { id: '1', role: 'user', content: 'お元気?' },
]
expect(messageSelectors.normalizeMessages(messagesWithId)).toHaveLength(1)
locales/en/translation.json (1)

300-304: 新規キーは他言語 JSON も忘れず追加を

"EditSlideScripts""PleaseSelectSlide" は ja/zh など他 locale にも追加しておかないと
i18n で fall-back 文字列が表示されます。

src/pages/api/save-chat-log.ts (2)

65-69: 同時書き込みで競合が発生する恐れ

fs.writeFileSync を直列呼び出しすると、複数リクエストが重なる環境で
最後に書き込んだ内容だけが生き残り、データ欠損する可能性があります。
ロック機構または fs.promises.open + appendFile で排他制御をご検討ください。


123-134: sort().reverse() はタイムスタンプ順を保証しません

ISO 文字列に置換しても、文字列ソートでは log_2024-3-10log_2024-12-01 の順が逆転します。
Intl.Collator か数値化して比較するソートを推奨します。

- .sort()
+ .sort((a,b) => new Date(a.slice(4,-5)).getTime() - new Date(b.slice(4,-5)).getTime())
  .reverse()
src/features/messages/speakQueue.ts (1)

70-75: 高頻度タスク追加時の homeStore.setState が再レンダリングを誘発

タスク追加ごとに isSpeakingtrue に設定していますが、既に true の場合は無駄な再レンダリングが発生します。条件付き更新にするとパフォーマンスが向上します。

-    homeStore.setState({ isSpeaking: true })
+    if (!homeStore.getState().isSpeaking) {
+      homeStore.setState({ isSpeaking: true })
+    }
src/features/messages/speakCharacter.ts (1)

250-253: tokenAtStart を返却するより currentStopToken を再取得した方が明確

tokenAtStart を payload に含めていますが、後段で同値比較するだけなら、addTask 直前に initialToken と再比較する方がデータ構造がシンプルになります。payload からトークンを除外すると責務分離が明快になります。

src/features/chat/handlers.ts (2)

590-595: 未使用の sessionId 変数を削除してください
handleSendChatFn 内で sessionId を生成していますが、このスコープでは一切利用されていません。不要な変数は可読性を下げるだけでなく、後々のリファクタ時に「意味があるのでは?」という誤解を招きやすいため削除を推奨します。

-  const sessionId = generateSessionId()

115-206: 文章抽出・コードブロック判定ロジックが重複しており保守コストが高いです
speakMessageHandlerprocessAIResponse にほぼ同一の

  • コードブロック検出 (CODE_DELIMITER)
  • 感情タグ抽出
  • 文抽出・音声合成トリガ
    の巨大ループが存在します。バグ修正や仕様変更時に二重対応が必要となり、将来的な不整合の温床になります。
  1. 共通の ストリームパーサー (ジェネレータ/クラス) を src/utils/chatParser.ts 等に切り出す
  2. その結果を handleSpeakAndStateUpdate に流し込む
    の2段構成にすればロジック共有・テスト容易化が図れます。

Also applies to: 298-526

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 413f0fb and d40d909.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • public/images/icons/external-link.svg is excluded by !**/*.svg
📒 Files selected for processing (69)
  • .cursor/rules/test.mdc (1 hunks)
  • .github/workflows/test.yml (1 hunks)
  • jest.config.js (1 hunks)
  • jest.setup.canvas.js (1 hunks)
  • jest.setup.js (1 hunks)
  • locales/ar/translation.json (2 hunks)
  • locales/de/translation.json (2 hunks)
  • locales/en/translation.json (2 hunks)
  • locales/es/translation.json (2 hunks)
  • locales/fr/translation.json (2 hunks)
  • locales/hi/translation.json (2 hunks)
  • locales/it/translation.json (2 hunks)
  • locales/ja/translation.json (2 hunks)
  • locales/ko/translation.json (2 hunks)
  • locales/pl/translation.json (2 hunks)
  • locales/pt/translation.json (2 hunks)
  • locales/ru/translation.json (2 hunks)
  • locales/th/translation.json (2 hunks)
  • locales/vi/translation.json (2 hunks)
  • locales/zh/translation.json (2 hunks)
  • package.json (2 hunks)
  • src/__mocks__/canvas.js (1 hunks)
  • src/__mocks__/canvasMock.js (1 hunks)
  • src/__mocks__/node-canvas.js (1 hunks)
  • src/__mocks__/openai.js (1 hunks)
  • src/__mocks__/readableStream.js (1 hunks)
  • src/__tests__/features/chat/aiChatFactory.test.ts (1 hunks)
  • src/__tests__/features/chat/difyChat.test.ts (1 hunks)
  • src/__tests__/features/chat/handlers.test.ts (1 hunks)
  • src/__tests__/features/chat/openAIAudioChat.test.ts (1 hunks)
  • src/__tests__/features/chat/vercelAIChat.test.ts (1 hunks)
  • src/__tests__/features/messages/messageSelectors.test.ts (1 hunks)
  • src/__tests__/features/messages/speakCharacter.test.ts (1 hunks)
  • src/__tests__/testUtils.ts (1 hunks)
  • src/__tests__/utils/textProcessing.test.ts (1 hunks)
  • src/components/Live2DComponent.tsx (3 hunks)
  • src/components/messageInput.tsx (2 hunks)
  • src/components/messageReceiver.tsx (1 hunks)
  • src/components/settings/index.tsx (1 hunks)
  • src/components/settings/messageReceiver.tsx (2 hunks)
  • src/components/settings/slide.tsx (2 hunks)
  • src/components/slideControls.tsx (2 hunks)
  • src/components/toast.tsx (2 hunks)
  • src/features/chat/difyChat.ts (2 hunks)
  • src/features/chat/handlers.ts (9 hunks)
  • src/features/chat/openAIAudioChat.ts (1 hunks)
  • src/features/chat/vercelAIChat.ts (3 hunks)
  • src/features/lipSync/lipSync.ts (4 hunks)
  • src/features/messages/live2dHandler.ts (3 hunks)
  • src/features/messages/messageSelectors.ts (1 hunks)
  • src/features/messages/messages.ts (1 hunks)
  • src/features/messages/speakCharacter.ts (2 hunks)
  • src/features/messages/speakQueue.ts (6 hunks)
  • src/features/stores/home.ts (5 hunks)
  • src/features/stores/toast.ts (1 hunks)
  • src/features/vrmViewer/model.ts (1 hunks)
  • src/features/youtube/youtubeComments.ts (3 hunks)
  • src/hooks/useBrowserSpeechRecognition.ts (2 hunks)
  • src/hooks/useRealtimeVoiceAPI.ts (3 hunks)
  • src/hooks/useVoiceRecognition.ts (1 hunks)
  • src/hooks/useWhisperRecognition.ts (3 hunks)
  • src/pages/api/getSlideFolders.ts (1 hunks)
  • src/pages/api/getSupplement.ts (1 hunks)
  • src/pages/api/save-chat-log.ts (3 hunks)
  • src/pages/api/updateSlideData.ts (1 hunks)
  • src/pages/send-message.tsx (1 hunks)
  • src/pages/slide-editor/[slideName].tsx (1 hunks)
  • src/utils/messageUtils.ts (1 hunks)
  • tailwind.config.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (19)
src/components/messageReceiver.tsx (1)
src/features/chat/handlers.ts (1)
  • processAIResponse (269-583)
src/hooks/useRealtimeVoiceAPI.ts (1)
src/features/messages/speakQueue.ts (1)
  • SpeakQueue (14-203)
src/hooks/useBrowserSpeechRecognition.ts (1)
src/features/messages/speakQueue.ts (1)
  • SpeakQueue (14-203)
src/hooks/useVoiceRecognition.ts (1)
src/features/messages/speakQueue.ts (1)
  • SpeakQueue (14-203)
src/components/settings/messageReceiver.tsx (1)
src/components/link.tsx (1)
  • Link (1-12)
src/hooks/useWhisperRecognition.ts (1)
src/features/messages/speakQueue.ts (1)
  • SpeakQueue (14-203)
src/__tests__/features/messages/speakCharacter.test.ts (1)
src/features/messages/speakCharacter.ts (2)
  • preprocessMessage (27-66)
  • handleTTSError (290-312)
src/__tests__/features/chat/openAIAudioChat.test.ts (5)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/features/chat/handlers.ts (1)
  • handleReceiveTextFromRtFn (813-878)
src/utils/audioBufferManager.ts (1)
  • AudioBufferManager (3-39)
src/features/messages/messageSelectors.ts (1)
  • messageSelectors (4-178)
src/features/chat/openAIAudioChat.ts (1)
  • getOpenAIAudioChatResponseStream (14-73)
src/features/stores/home.ts (3)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/utils/messageUtils.ts (1)
  • generateMessageId (7-7)
src/features/messages/messageSelectors.ts (1)
  • messageSelectors (4-178)
src/__tests__/utils/textProcessing.test.ts (1)
src/utils/textProcessing.ts (3)
  • convertEnglishToJapaneseReading (31-53)
  • asyncConvertEnglishToJapaneseReading (61-68)
  • containsEnglish (75-77)
src/__tests__/features/chat/difyChat.test.ts (3)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/features/chat/difyChat.ts (1)
  • getDifyChatResponseStream (12-111)
src/__tests__/testUtils.ts (1)
  • consumeStream (1-16)
src/pages/api/updateSlideData.ts (1)
src/pages/api/getSupplement.ts (1)
  • handler (11-64)
src/__tests__/features/chat/aiChatFactory.test.ts (2)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/features/chat/aiChatFactory.ts (1)
  • getAIChatResponseStream (8-42)
src/__tests__/features/messages/messageSelectors.test.ts (2)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/features/messages/messageSelectors.ts (1)
  • messageSelectors (4-178)
src/pages/api/save-chat-log.ts (1)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/components/slideControls.tsx (1)
src/components/iconButton.tsx (1)
  • IconButton (14-51)
src/pages/api/getSupplement.ts (1)
src/pages/api/updateSlideData.ts (1)
  • handler (22-107)
src/__mocks__/canvasMock.js (1)
src/__mocks__/node-canvas.js (5)
  • Canvas (1-21)
  • createCanvas (23-28)
  • canvas (24-24)
  • Image (30-35)
  • loadImage (37-37)
src/features/messages/speakQueue.ts (1)
src/features/messages/live2dHandler.ts (1)
  • Live2DHandler (5-257)
🪛 actionlint (1.7.4)
.github/workflows/test.yml

14-14: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


17-17: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 Biome (1.9.4)
src/pages/slide-editor/[slideName].tsx

[error] 290-290: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 413-415: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/features/messages/live2dHandler.ts

[error] 188-188: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (164)
src/features/messages/messages.ts (1)

2-2: メッセージ型にIDプロパティを追加しました

この変更はメッセージを一意に識別するために重要です。メッセージの管理や同期を強化し、チャットログでのメッセージの効率的な更新を可能にします。

src/pages/send-message.tsx (1)

320-320: 背景スタイルをシンプルに変更

背景スタイルをグラデーション(bg-gradient-to-b from-purple-50 to-white)から単色の背景(bg-purple-50)に変更しました。この変更はUIの一貫性を向上させ、プロジェクト全体のデザイン改良と合わせたものです。

src/components/settings/index.tsx (1)

188-188: バージョン番号を更新

バージョンを「2.32.0」から「2.33.0」に更新しました。この更新は多言語対応の改善とツール使用通知に関連する機能追加を反映しています。

tailwind.config.js (1)

27-28: ツール通知用の新しい色を追加

「toast-tool」と「toast-tool-hover」という新しい色定義を追加しました。これらはトースト通知コンポーネントの新しいタイプ「tool」のためのスタイリングをサポートします。紫色の基本色(#9C27B0)とホバー時の暗い紫色(#7B1FA2)で、既存のトースト通知の色スキームと一貫性があります。

src/features/vrmViewer/model.ts (1)

94-99: 音声再生停止機能の追加は適切です

stopSpeaking メソッドは、音声再生と口の動きを同時に停止できる機能を追加しており、ユーザー体験を向上させる重要な機能です。実装は適切で、_lipSyncのヌルチェックも正しく行われています。

src/components/messageReceiver.tsx (1)

121-121: API変更に合わせた適切な更新です

processAIResponse関数の引数が変更されたことに対応した修正です。新しい実装では状態管理がhomeStoreを通じて行われるようになり、チャットログを直接渡す必要がなくなりました。リファクタリングとして適切な変更です。

src/features/stores/toast.ts (1)

6-6: 新しいトーストタイプの追加は適切です

'tool'タイプをトーストの種類に追加することで、ツール使用時の通知機能が強化されています。この変更はコンポーネントとスタイリングの対応する変更と整合性が取れており、適切な拡張です。

jest.setup.js (1)

1-3: テスト設定ファイルの追加は適切です

Jestのセットアップファイルとして、必要なライブラリとモックが適切にインポートされています。DOM関連のテストやストリーム処理のテストをサポートするために必要な依存関係が含まれており、テスト環境の強化に貢献しています。

src/components/toast.tsx (2)

7-7: 新しいトーストタイプ 'tool' が追加されました

ToastPropstype プロパティに 'tool' が追加され、ツール関連の通知に専用のスタイルを適用できるようになりました。


36-37: ツール通知用のスタイル対応が適切に実装されています

getIconColor 関数に 'tool' タイプのケースが追加され、対応するCSSクラス 'text-toast-tool' が設定されています。これにより、ツール関連の通知が視覚的に区別できるようになります。

src/features/chat/openAIAudioChat.ts (1)

55-60: メッセージ更新処理が改善されました

直接 chatLog に追加する代わりに upsertMessage メソッドを使用することで、同一ID (audio.id) のメッセージを適切に更新できるようになりました。これによりメッセージの重複を防ぎ、状態管理が一貫性を持つようになります。

src/hooks/useRealtimeVoiceAPI.ts (3)

10-10: SpeakQueueのインポートが追加されました

音声キューを制御するために必要なインポートが適切に追加されています。


321-321: リスニング開始時に音声再生を停止する処理が追加されました

ユーザーが音声認識を開始する際に SpeakQueue.stopAll() を呼び出すことで、AIの発話を適切に停止し、ユーザー入力に集中できるようになりました。これはUXの改善につながります。


330-330: メッセージ送信時に音声再生を停止する処理が追加されました

ユーザーがメッセージを送信する際に SpeakQueue.stopAll() を呼び出すことで、AIの発話を適切に停止し、新しい会話の流れをスムーズに開始できるようになりました。

src/__mocks__/openai.js (1)

1-9: OpenAI APIのモック実装が適切に追加されました

テスト環境でOpenAI APIの呼び出しをシミュレートするためのモックが実装されています。この実装により、実際のAPIを呼び出すことなく、chat.completions.create メソッドの動作をテストで制御できるようになります。

Jestのモック関数を使用し、OpenAIクライアントの構造を正確に模倣しているため、テストケースで実際のAPIと同様に使用できます。

src/hooks/useVoiceRecognition.ts (1)

47-49: 音声停止処理の完全実装を高く評価します。

SpeakQueue.stopAll()を呼び出すことで、単にステートを更新するだけでなく、キューに入っているすべての発話タスクも確実に停止させる実装になっています。これにより、ユーザーが音声停止を要求した際の動作が一貫して期待通りになります。

src/utils/messageUtils.ts (1)

1-7: メッセージID生成の標準化、良い実装です。

UUIDを利用した一意なメッセージIDの生成を一箇所に集約することで、アプリケーション全体でのメッセージ管理の一貫性が向上します。プレフィックス「msg_」を付けることで、デバッグやログ解析の際にもメッセージIDと他のIDを区別しやすくなっています。

src/hooks/useBrowserSpeechRecognition.ts (1)

258-258: メッセージ送信時の音声停止処理の追加、適切です。

ユーザーがメッセージを送信する際にSpeakQueue.stopAll()を呼び出すことで、進行中の音声合成をすべて停止させる実装になっています。これにより、メッセージ送信と音声再生の競合を防ぎ、よりスムーズなユーザー体験を提供できます。他のフックとの動作の一貫性も確保されており、適切な実装です。

src/pages/api/getSlideFolders.ts (1)

12-19: スライドフォルダのフィルタリング強化、効果的な改善です。

slides.mdscripts.jsonの両方のファイルが存在するディレクトリのみを有効なスライドフォルダとして返すように改善されています。これにより、スライドエディタなどの機能が必要なファイルを持つフォルダのみを対象とすることができ、ユーザーエクスペリエンスが向上します。

エラー処理も考慮されており、堅牢な実装になっています。

src/components/messageInput.tsx (2)

31-31: 新しいプロパティがコンポーネントに追加されました。

isSpeaking プロパティが追加されたことで、音声合成中の状態を適切に管理できるようになりました。


161-161: マイクボタンの無効化条件が改善されました。

音声合成中(isSpeaking)またはチャット処理中(chatProcessing)の場合にマイクボタンを無効化することで、ユーザー体験が向上します。音声合成中にマイク入力を受け付けないようにすることで、音声の衝突を防止できます。

src/components/settings/messageReceiver.tsx (1)

3-3: 必要なインポートが追加されました。

Next.jsのImageLinkコンポーネントをインポートして、モダンなリンク実装が可能になります。

Also applies to: 7-7

src/__tests__/testUtils.ts (1)

1-16:

Details

✅ Verification successful

ストリーム処理のテストユーティリティが追加されました。

consumeStream関数はReadableStream<string>からすべてのチャンクを読み取り、単一の文字列に連結する非同期ユーティリティ関数です。これはストリーミングAIレスポンスのテストに非常に有用です。

コードは簡潔で読みやすく、非同期処理も適切に実装されています。

この関数はテストコード内で実際に使用されていることを確認してください。以下のコマンドで使用状況を確認できます:


🏁 Script executed:

#!/bin/bash
# consumeStream関数の使用箇所を検索
rg -A 2 -B 2 "consumeStream" "src/__tests__"

Length of output: 2301


consumeStream関数がテスト内で使用されていることを確認しました

  • src/__tests__/features/chat/difyChat.test.ts の複数箇所で consumeStream(stream) が呼び出されている

テストユーティリティとして期待通り機能しているため、問題ありません。

locales/ja/translation.json (2)

162-163: スライド機能に関する新しい翻訳キーが追加されました。

スライド編集機能のための新しい翻訳キー「EditSlideScripts」と「PleaseSelectSlide」が適切に追加されています。日本語の翻訳文も自然で問題ありません。


238-238: ツール使用に関する新しいトースト通知の翻訳が追加されました。

「UsingTool」のトースト通知用の翻訳キーが追加されました。{{toolName}}のようなプレースホルダーを使用して動的なコンテンツを表示できるようになっています。

src/features/youtube/youtubeComments.ts (4)

120-120: リファクタリング確認: AIレスポンス処理の直接呼び出し

preProcessAIResponseの間接呼び出しからprocessAIResponseの直接呼び出しへの変更により、処理フローがシンプルになっています。これはprocessAIResponse関数の引数が変更されたことに対応するリファクタリングと思われます。


167-167: リファクタリング確認: AIレスポンス処理の直接呼び出し

preProcessAIResponseの間接呼び出しからprocessAIResponseの直接呼び出しへの変更により、処理フローがシンプルになっています。これはprocessAIResponse関数の引数が変更されたことに対応するリファクタリングと思われます。


177-177: リファクタリング確認: AIレスポンス処理の直接呼び出し

preProcessAIResponseの間接呼び出しからprocessAIResponseの直接呼び出しへの変更により、処理フローがシンプルになっています。これはprocessAIResponse関数の引数が変更されたことに対応するリファクタリングと思われます。


184-184: リファクタリング確認: AIレスポンス処理の直接呼び出し

preProcessAIResponseの間接呼び出しからprocessAIResponseの直接呼び出しへの変更により、処理フローがシンプルになっています。これはprocessAIResponse関数の引数が変更されたことに対応するリファクタリングと思われます。

src/components/Live2DComponent.tsx (2)

225-226: イベントリスナーの登録

新しいドラッグアンドドロップイベントの登録が適切に実装されています。


239-240: イベントリスナーのクリーンアップ

新しいイベントリスナーのクリーンアップが適切に実装されています。コンポーネントのアンマウント時にメモリリークを防ぐ良い実践です。

src/hooks/useWhisperRecognition.ts (3)

7-7: 新しい依存関係のインポートが適切に追加されています

SpeakQueue のインポートは、音声認識中と発話中の競合を解決するために追加されており、適切な対応です。


189-189: 音声認識開始時に発話キューを停止する処理が追加されています

音声認識と発話キューの競合を防ぐための重要な改善です。ユーザーが音声認識を開始する際に、既存の発話キューをすべて停止することで、ユーザー操作の応答性が向上します。


199-199: メッセージ送信時に発話キューを停止する処理が追加されています

ユーザーがメッセージを送信する際にも発話キューを停止することで、一貫した動作が保証されます。これにより、新しい会話の開始時に古い発話が継続するという問題が解決されます。

src/features/chat/difyChat.ts (3)

43-54: エラーハンドリングの改善とストリーム処理の堅牢性向上

レスポンスボディの存在確認と適切なエラーハンドリングが追加され、ストリーム処理の安全性が向上しています。特に、response.bodyが存在しない場合の明示的なエラーハンドリングが追加されたことは重要な改善です。


94-96: リソース解放処理の安全性向上

リーダーのリソース解放前にreader変数の存在確認が追加されており、潜在的なNull参照エラーを防ぐための適切な対策です。


101-103: エラーメッセージ生成の堅牢性向上

エラーオブジェクトのcauseプロパティの存在確認が追加され、エラーコードにフォールバック値が設定されています。これにより、未定義プロパティへのアクセスによる実行時エラーを防止できます。

src/components/settings/slide.tsx (2)

4-4: 新しいコンポーネントのインポートが適切に追加されています

Linkコンポーネントのインポートは、スライドエディタへの直接リンクを実装するために必要であり、適切に追加されています。


89-128: スライド選択UIの改善と編集機能の追加

スライド選択UIが大幅に改善されています。特筆すべき点:

  1. フレックスコンテナを使用して選択ボックスと編集ボタンを横並びに配置
  2. スライド未選択時のプレースホルダーオプションを追加
  3. 選択されたスライドがある場合のみ表示される編集ボタンを追加
  4. 新しいタブでスライドエディタを開くための適切なリンク設定

これらの変更により、ユーザーエクスペリエンスが向上し、スライド管理が容易になります。

locales/th/translation.json (3)

226-235: トースト通知メッセージの改善と洗練

タイ語のトースト通知メッセージが改善され、より明確で礼儀正しい表現になっています。特に、完了を示す「แล้ว」の追加など、文脈に応じた適切な表現が使用されています。これにより、ユーザーへのフィードバックがより直感的になります。


236-236: ツール使用通知のための新しいメッセージキーの追加

ツール使用を示すための新しいトーストメッセージキー "UsingTool": "กำลังใช้{{toolName}}" が追加されています。これは、AIがツールを使用する際にユーザーに適切にフィードバックを提供するための重要な追加です。


302-304: UI要素のローカライズの追加と改善

スライド編集機能に関連する2つの新しいUIラベル(「แก้ไขบทสนทนา」と「โปรดเลือกสไลด์」)が追加されています。これらは、スライド選択コンポーネントの改善に対応する適切なローカライズです。さらに、カスタムAPIの説明文も改善されています。

src/__tests__/utils/textProcessing.test.ts (4)

1-12: モジュールインポートと辞書モックの設定が適切です。

テキスト処理ユーティリティのインポートと英語から日本語への変換辞書のモックが適切に設定されています。モックデータには「hello」、「world」、「test」、「mastra」の4つのキーワードが含まれており、テストケースをカバーするのに十分です。


14-31: convertEnglishToJapaneseReading関数のテストケースが網羅的です。

以下の重要なケースがテストされています:

  1. 英語を日本語読みに変換する基本機能
  2. 英語が含まれていない場合の挙動
  3. 特定の重要な単語(Mastra)の大文字小文字を区別しない変換

テスト説明も明確で、関数の期待される動作を適切に検証しています。


33-39: asyncConvertEnglishToJapaneseReadingの非同期テストが適切に実装されています。

非同期関数のテストがasync/await構文を使って正しく実装されています。また、変換結果に期待される日本語の文字列が含まれているかを適切に検証しています。


41-51: containsEnglish関数のテストが網羅的です。

英語が含まれているケースと含まれていないケースの両方をテストしており、関数の境界条件をよくカバーしています。数字や日本語のみの文字列など、英語以外の文字のケースも適切にテストされています。

locales/hi/translation.json (5)

225-236: トースト通知メッセージが改善されています。

WebSocket接続状態、関数実行、Firefox対応、音声認識エラー、プリセット切り替え、Whisperエラーに関するトースト通知の文言が改善されています。メッセージの表現がより正確になり、ユーザー体験が向上するでしょう。


236-236: ツール使用中を示す新しいトーストメッセージが追加されました。

"UsingTool": "{{toolName}} का उपयोग करते समय"というキーが追加され、ツール使用中であることをユーザーに通知できるようになりました。これはsrc/features/chat/vercelAIChat.tsでのAnthropicツールコール処理の追加と整合性があります。


302-302: カスタムAPI説明のテキストが更新されました。

カスタムAPIに関する説明文が改善され、メッセージが自動的にリクエストボディに含まれること、およびストリーミングモードではサーバーがtext/event-streamを返す必要があることが明確になりました。


303-304: スライド編集関連の新しいキーが追加されました。

スライド編集機能に関連する以下の2つの新しいキーが追加されました:

  • "EditSlideScripts": "सेल्फ़ संपादन" (セルフ編集)
  • "PleaseSelectSlide": "कृपया स्लाइड चुनें" (スライドを選択してください)

これらは、PRの概要に記載されているスライド編集機能の拡張と一致しています。


1-305: ヒンディー語の翻訳ファイル全体について

変更箇所はローカライゼーションの品質を向上させており、新しい機能(ツール使用通知とスライド編集)に対応しています。すべての変更は、他の言語ファイルでも同様に行われるべきものであり、多言語対応を維持する上で重要です。

変更内容はPRの目的(「本番リリース」)と整合性があり、国際化対応の一部として適切に実装されています。

src/features/chat/vercelAIChat.ts (3)

131-134: エラーハンドリングが改善されました。

error.causeの存在を明示的にチェックしてからプロパティにアクセスするように改善されています。エラーコードが存在しない場合は'AIAPIError'をデフォルト値として使用するようになり、より堅牢なエラー処理が実現されています。


264-284: Anthropicのツール呼び出し情報を処理する機能が追加されました。

'9:'で始まる行に含まれるJSON形式のAnthropicツールコール情報を処理するコードが追加されています。ツール名が検出された場合、ログに記録し、国際化対応されたメッセージを使用してトースト通知を表示します。適切なエラーハンドリングも実装されており、JSON解析エラーをキャッチしてログに記録します。

これは新しいトーストタイプ'tool'と、すべての言語ファイルに追加された"UsingTool"メッセージキーと一致しており、ツール使用通知機能を強化します。


322-324: ストリーミングレスポンスのエラーハンドリングが改善されました。

エラーメッセージの生成時にerror.causeの存在をチェックするようになり、より堅牢なエラー処理が実現されています。エラーコードがない場合は'AIAPIError'をデフォルト値として使用する点も適切です。

src/features/lipSync/lipSync.ts (5)

13-13: 再生中のオーディオソースを追跡するプロパティが追加されました。

currentSourceプロパティを追加することで、現在再生中のオーディオソースを追跡できるようになりました。これにより、再生の制御と停止が適切に管理できるようになります。


204-206: バッファソース作成時に現在のソースを設定するように改善されました。

再生するオーディオバッファソースをcurrentSourceに割り当てることで、現在再生中のソースを追跡し、後で必要に応じて停止できるようになりました。


215-219: 再生終了時のクリーンアップが改善されました。

再生終了イベントハンドラが追加され、終了したソースが現在のソースと一致する場合にのみcurrentSourceをクリアするようになりました。これにより、再生状態の追跡が正確になり、複数のソースが同時に存在する場合の問題を防ぎます。


225-226: エラー発生時のクリーンアップが追加されました。

エラー発生時にcurrentSourceをクリアすることで、古い参照が残らないようになり、メモリリークやリソースの不適切な使用を防ぎます。


275-285: 現在の再生を安全に停止するメソッドが追加されました。

新しいstopCurrentPlaybackメソッドにより、現在再生中のオーディオソースを安全に停止できるようになりました。エラーを適切にキャッチしてログに記録し、currentSourceをクリアする処理も含まれています。これはModelクラスのstopSpeakingメソッドやその他の音声制御機能と連携して、一貫した音声再生管理を実現します。

locales/ru/translation.json (4)

226-228: トーストメッセージの表現が改善されています

WebSocket接続関連のトーストメッセージが明確になり、一貫性が向上しています。


231-235: ユーザーフレンドリーなエラーメッセージに改善されています

Firefox対応やWhisper、音声認識エラーなどのメッセージが洗練され、よりユーザーに分かりやすくなっています。


236-236: 新しいツール使用通知が追加されました

ツール使用時のフィードバックを提供する新しいトーストメッセージが追加されました。これはユーザーエクスペリエンスを向上させる良い変更です。


302-304: スライド編集機能のための新しいテキストが追加されました

スライド編集機能のための新しいUI文字列「EditSlideScripts」と「PleaseSelectSlide」が追加されました。これらは他の言語ファイルとも一貫性があり、適切に実装されています。

locales/fr/translation.json (3)

231-235: トーストメッセージの表現が改善されています

Firefox対応や音声認識エラーなどのメッセージが洗練され、より適切なフランス語の表現になっています。ピリオドの追加などで文の完成度が向上しています。


236-236: ツール使用通知のトーストメッセージが追加されました

「Utilisation de {{toolName}}」という新しいトーストメッセージが適切に追加されており、ロシア語版と一貫性があります。


302-304: スライド編集機能のUI文字列が統一的に追加されています

「EditSlideScripts」と「PleaseSelectSlide」の翻訳が適切に追加されており、他の言語ファイルと同様の実装になっています。

locales/de/translation.json (4)

225-228: WebSocket関連のメッセージが文法的に改善されています

WebSocket接続関連のトーストメッセージにピリオドが追加され、より完全な文として改善されています。


231-235: エラーメッセージの文法と明確さが向上しています

Firefox対応や音声認識エラーなどのメッセージが文法的に改善され、明確さが向上しています。


236-236: 他の言語と一貫したツール使用通知が追加されました

「{{toolName}} wird verwendet」という新しいトーストメッセージが追加され、他の言語ファイルと同様の機能を提供しています。


302-304: スライド編集機能のためのUI文字列が適切に翻訳されています

「Dialogbearbeitung」と「Bitte wählen Sie eine Folie aus」という翻訳が適切に追加されており、スライド編集機能の多言語対応が完成しています。

src/components/slideControls.tsx (5)

11-11: 新しいオプションプロパティの追加は適切です

中央のプレイボタン表示を制御するための新しいオプションプロパティshowPlayButtonが追加されました。このプロパティはUIの柔軟性を高め、さまざまな使用シナリオに対応できるようになります。


21-21: デフォルト値の設定が適切です

showPlayButtonのデフォルト値がtrueに設定されており、既存の動作と互換性を維持しています。これは後方互換性を保つための良い実装です。


28-34: Prevボタンの無効化ロジックの改善

showPlayButtonの状態に基づいて無効化ロジックを条件付けすることで、プレイボタンが非表示の場合でもスライド制御が可能になり、UIの柔軟性が向上しています。


35-42: プレイボタンの条件付きレンダリングは適切です

showPlayButtonプロパティに基づいてプレイ/一時停止ボタンを条件付きでレンダリングする実装は適切です。これにより、異なるUI要件に合わせてコンポーネントをカスタマイズできます。


43-51: Nextボタンの無効化ロジックも同様に改善されています

Prevボタンと同様に、NextボタンもshowPlayButtonの状態に基づいて無効化ロジックが更新されており、一貫した動作が確保されています。

src/features/stores/home.ts (1)

151-156: 差分判定が content のみになっています
音声 (audio) や timestamp が変更された場合は保存対象にならないため、
アップデートが取りこぼされる可能性があります。
JSON.stringify(msg) !== JSON.stringify(prevMsg) など広い比較、
または ['content','audio','timestamp'].some(k=>prev[k]!==msg[k]) のように
複数フィールドを対象にするか検討ください。

locales/es/translation.json (1)

225-237: 翻訳キーの追加を確認しました
"UsingTool" など新規キーが問題なく追加されています。
ビルド時の i18n 型安全チェック(例: @formatjs/cli や TypeScript 型生成)が通ることを念のためご確認ください。

locales/vi/translation.json (1)

226-237: ベトナム語翻訳の整合性 OK
同様に "UsingTool" 追加を確認しました。
他言語との差異やプレースホルダーのスペルミスは見当たりません。

src/features/messages/messageSelectors.ts (2)

87-93: メッセージのマージ条件が改善されました

同じロールのメッセージをマージする条件として、IDプロパティの存在と一致を確認するようになりました。これにより、意図しないメッセージ結合を防ぎ、より正確なメッセージ処理が可能になります。


94-114: 内容連結ロジックの強化

メッセージの内容タイプ(文字列または配列)に基づいて適切に処理するよう改善されました。特に:

  • テキスト抽出と連結のロジックが明確に分離されています
  • 配列形式のコンテンツ処理が適切に実装されています

これらの変更により、マルチモーダルコンテンツを含むメッセージの処理が堅牢になっています。

package.json (2)

15-18: テスト環境のセットアップ追加

テスト実行用のスクリプトが追加されました。これはプロジェクトのテスト自動化と品質保証の改善のための重要な一歩です。


71-73: テストフレームワーク依存関係の追加

プロジェクトにJestとTesting Libraryの依存関係が追加されました。これらはReactアプリケーションのテストに最適なツールです。また、@types/uuidが正しく開発依存関係に移動されました。

Also applies to: 76-76, 82-82, 90-93, 100-100, 103-103

src/pages/api/getSupplement.ts (6)

5-9: レスポンスデータ型の適切な定義

APIレスポンスに明確な型定義が追加されました。オプショナルフィールドとしてcontentmessageerrorが含まれており、様々なレスポンスシナリオに対応できます。


15-17: HTTPメソッド検証の追加

APIエンドポイントがGETリクエストのみを受け付けるように適切に制限されています。これはRESTful APIのベストプラクティスに準拠しています。


19-25: クエリパラメータの検証

slideNameパラメータに対する厳格な検証が追加されました。これにより、不正なリクエストが早期に拒否されます。


27-39: パス走査攻撃対策の実装

スライド名のサニタイズ処理が適切に実装されています。これは、ディレクトリトラバーサル攻撃やその他の不正なファイルアクセスを防ぐために重要です。


41-47: ファイルパス構築の適切な実装

path.joinを使用してファイルパスを安全に構築しています。これは、異なるOS間での互換性を確保する上で重要です。


49-62: エラーハンドリングの改善

ファイルの存在確認と読み込みに関するエラーハンドリングが適切に実装されています。特にファイルが存在しない場合(ENOENT)は空の内容を返す処理は、クライアント側のエラーハンドリングを簡素化します。

locales/ar/translation.json (3)

227-227: トーストメッセージの文言改善

WebSocket接続、関数実行、音声認識に関するトーストメッセージがより明確になりました。これにより、ユーザーに対するフィードバックが改善されます。

Also applies to: 229-229, 232-233


236-236: ツール使用中のトーストメッセージの追加

AIがツールを使用している際にユーザーに通知するための新しいトーストメッセージが追加されました。これはユーザーエクスペリエンスを向上させる重要な変更です。


303-304: スライド編集機能のUI要素追加

スライド編集に関連する新しいUIラベルが追加されました。これは新機能の国際化対応として適切に実装されています。

locales/pt/translation.json (2)

236-236: 機能強化:ツール使用通知メッセージの追加

新しい「UsingTool」メッセージが追加され、ユーザーにツールの使用状況を知らせることができるようになりました。これはユーザビリティの向上につながり、特にツールが実行されている間のフィードバックが必要な場合に役立ちます。


303-304: スライド編集機能のUIラベル追加

スライド編集機能に対応する新しいUIラベル「EditSlideScripts」と「PleaseSelectSlide」を追加しました。これらのラベルは新しいスライド編集ページとの整合性を保ちます。

src/__tests__/features/chat/handlers.test.ts (5)

1-48: テストのモック設定が適切に行われています

必要なすべてのモジュールとストアが適切にモック化され、テストの独立性が確保されています。


54-94: WebSocketを使用したメッセージ送信のテストケースが網羅的です

externalLinkageModeが有効な場合のWebSocketを通じたメッセージ送信のテストが適切に行われています。特に、メッセージの形式とupsertMessageメソッドの呼び出しの検証が含まれている点が良いです。


96-126: エラー処理のテストが適切に実装されています

WebSocketが接続されていない場合のエラー処理と適切なトースト表示のテストが実装されています。エラーシナリオの検証は信頼性の高いアプリケーションには不可欠です。


128-190: 通常モードでのAIチャットレスポンス処理のテストが詳細です

AIチャットレスポンスの処理に関するテストが詳細に実装されており、モックされたストリームレスポンスと状態の更新を適切に検証しています。


193-201: ヌルストリームのエッジケースが適切にテストされています

AIレスポンスストリームがnullの場合のエッジケース処理が適切にテストされており、エラー処理の堅牢性を確保しています。

src/pages/api/updateSlideData.ts (6)

32-37: バリデーションが適切に実装されています

リクエストボディの必須フィールド(slideName、scripts、supplementContent)の存在チェックが適切に行われています。これにより、不完全なデータによるエラーを防止できます。


39-51: パストラバーサル攻撃に対するセキュリティ対策が施されています

slideNameのサニタイズ処理が適切に実装されており、パストラバーサル攻撃や無効な文字の使用を防止しています。これはファイルシステムの安全性を確保する上で重要です。


58-67: ディレクトリ存在チェックの実装が堅牢です

対象ディレクトリの存在確認がfs.accessを使って適切に実装されており、存在しない場合は404エラーを返す処理が行われています。エラーメッセージも明確です。


69-90: データ形式の検証が詳細に行われています

scriptsの形式とsupplementContentの型チェックが詳細に実装されており、不正なデータ形式によるエラーを防止しています。


92-97: 並行処理による効率的な実装

Promise.allを使用して2つのファイル書き込み操作を並行処理することで、効率的な実装になっています。


100-106: エラーハンドリングとログ記録が適切に実装されています

エラーが発生した場合のログ記録と適切なエラーレスポンスの返却が実装されています。エラーメッセージにはインスタンス固有の情報が含まれており、デバッグに役立ちます。

locales/zh/translation.json (3)

232-234: エラーメッセージの改善

言語認識エラーと音声検出に関するメッセージが改善され、より明確になりました。これらの変更はユーザーエクスペリエンスの向上に貢献します。


236-236: ツール使用通知の追加

「UsingTool」メッセージが追加され、ツールの使用状況をユーザーに通知できるようになりました。これは操作の透明性を高め、ユーザーがシステムの動作を理解するのに役立ちます。


303-304: スライド編集機能のUIラベル追加

スライド編集機能に必要な「EditSlideScripts」と「PleaseSelectSlide」のUIラベルが追加されました。これらは他の言語ファイルと一貫性があり、多言語対応のスライド編集機能をサポートします。

locales/ko/translation.json (4)

230-230: メッセージの文法が改善されています

関数実行エラーメッセージの表現が改善されています。このような細かな翻訳の調整は、ユーザーエクスペリエンスを向上させる重要な変更です。


234-234: プリセット切り替えメッセージがシンプルになりました

プリセット切り替えのメッセージがより簡潔になっています。シンプルな表現はユーザーにとって理解しやすくなります。


236-237: 新しいトースト通知が追加されました

ツール使用中の状態を示す新しいトースト通知が追加されています。これは他の言語ファイルとの一貫性を保ちながら、UI機能の拡張をサポートするための良い追加です。


303-304: スライド編集関連の新機能のラベルが追加されました

スライド編集機能のためのUIラベル「EditSlideScripts」と「PleaseSelectSlide」が追加されています。これはPRで追加された新しいスライド編集機能と連携する重要な翻訳です。

src/__tests__/features/messages/speakCharacter.test.ts (7)

1-23: 適切なモックセットアップがされています

THREE.jsとVRMの依存関係を正しくモック化しています。これにより、テスト環境でグラフィック関連の処理を適切にシミュレートできます。


24-41: 必要なストアとi18nextのモック化が適切に行われています

settingsStoreとtoastStoreのモックが適切に設定されており、テスト内で正しく利用できるようになっています。


42-58: i18nextとhomeStoreのモックが効果的に実装されています

i18nextのモックは翻訳キーと値のマッピングを適切に処理し、homeStoreも必要なメソッドを提供しています。テストの信頼性が向上します。


60-97: preprocessMessageのテストが包括的です

空文字列、空白のみ、前後の空白の削除、絵文字の削除などのケースを適切にテストしています。各テストケースは明確に分離されており、関数の動作が正確に検証されています。


98-114: 記号の処理テストが適切に実装されています

記号のみの場合と記号と文字が混在する場合のテストがカバーされています。これにより、関数の振る舞いがすべての入力パターンで期待通りであることを確認できます。


115-153: 英語から日本語への変換設定のテストが充実しています

変換設定と言語選択の組み合わせに応じた挙動が適切にテストされています。各条件分岐がカバーされており、設定変更時の動作が正確に検証されています。


154-222: handleTTSErrorのテストが様々なエラー型をカバーしています

Error オブジェクト、文字列、不明なエラー型それぞれのケースについて適切にテストされています。トーストの表示やエラーメッセージのフォーマットが各ケースで正しく処理されていることを確認できます。

src/__mocks__/node-canvas.js (2)

1-21: Canvasモックが必要なメソッドを網羅しています

Canvas のモックが getContext、toBuffer、toDataURL など必要なメソッドと width、height プロパティを適切に提供しています。getContext は描画操作に必要な様々なメソッドを持つオブジェクトを返すよう実装されています。


23-51: 画像関連の機能が適切にモック化されています

createCanvas、Image クラス、loadImage などの関数が適切にモック化されています。また、registerFont、parseFont など補助的な機能も含まれており、テスト時に必要となる canvas モジュールの機能が網羅されています。

jest.config.js (3)

1-6: Next.js用のJest設定が適切に構成されています

next/jestを使用してNext.js環境に最適化されたJest設定を作成しています。プロジェクトのルートディレクトリが正しく指定されています。


7-26: テスト環境の詳細設定が包括的です

setupFilesAfterEnv、testEnvironment、moduleNameMapper など重要な設定が適切に構成されています。特に canvas モジュールのモック化や、テストファイルのパターンマッチング、無視すべきモジュールパスなどが詳細に設定されています。これにより、テスト実行時の問題を回避し、安定したテスト環境を提供できます。


28-28: Next.js用のJest設定が正しくエクスポートされています

createJestConfig 関数を使ってカスタム設定を適用し、最終的な設定をエクスポートしています。これにより Next.js のプロジェクト構造に最適化されたテスト環境が提供されます。

jest.setup.canvas.js (1)

1-48: canvasモジュールのモック設定が適切に実装されています

Jestテスト環境でcanvasを適切にモック化する良い実装です。canvas APIの主要なメソッドがすべてモック化されており、テスト時に実際のキャンバスレンダリングが不要な場合に役立ちます。

src/features/messages/live2dHandler.ts (3)

103-131: 音声再生完了検知の改善とフォールバック機能の追加

speak メソッドの改善点:

  1. live2dViewer.speak の onFinish コールバックを使用して再生完了を検知
  2. フォールバックタイマーの追加による堅牢性の向上
  3. エラーハンドリングの強化

これらの変更により、音声再生が適切に完了または中断された際の処理が改善されています。実装は堅牢で、Promise の解決が保証されるようになりました。


134-139: stopSpeaking メソッドの追加は適切です

音声再生を停止するための新しいメソッドが適切に実装されています。live2dViewer の存在チェックも含まれており、安全に停止処理を行えます。


162-162: startIdleMotion メソッド呼び出しのシンプル化

パラメータの削除によりコードがシンプルになりました。メソッド内部で必要な情報を取得するように修正されており、適切な変更です。

src/__tests__/features/chat/aiChatFactory.test.ts (6)

1-23: 依存モジュールのモック設定が適切です

必要なモジュールが正しくモック化されています。特に各サービス(Vercel AI、Dify、OpenAI Audio)のチャットレスポンスストリーム関数と設定ストアが適切にモック化されており、テストの独立性が保たれています。


24-41: テスト準備とヘルパー関数の実装が適切です

beforeEach でモックをクリアし、テストメッセージとモックストリーム作成関数が適切に定義されています。createMockStream 関数は ReadableStream を正しくシミュレートしており、テストケースで再利用できるようになっています。


42-56: OpenAIオーディオモードのテストが適切に実装されています

OpenAIサービスでオーディオモードが有効な場合のテストケースが正しく実装されています。モックストリームの作成、設定ストアの状態設定、関数呼び出し、結果の検証が適切に行われています。


58-92: Vercel AI SDKサポートサービスのテストが網羅的です

サポートされているすべてのAIサービスに対するテストが実装されており、網羅性が高いです。ループを使用して各サービスをテストすることで、コードの重複を避けながら完全なテストカバレッジを実現しています。


94-114: Difyサービスのテスト実装が適切です

Difyサービスに特有のパラメータ(APIキー、URL、会話ID)が正しく渡されているかの検証が含まれており、適切です。モックの設定と検証が詳細に行われています。


116-126: サポートされていないサービスのエラーハンドリングテストが実装されています

未サポートのAIサービスを指定した場合のエラー処理が適切にテストされています。エラーメッセージの内容も検証されており、適切です。

src/__tests__/features/chat/difyChat.test.ts (9)

1-21: 依存モジュールのモック設定が適切です

必要なモジュール(設定ストア、トーストストア、i18next)が適切にモック化されています。特にi18nextの翻訳関数が単純にキーを返すようにモック化されている点が、テストを簡潔にするのに役立っています。


22-47: グローバルオブジェクトのモック管理が慎重に実装されています

fetch と TextDecoder のグローバルオブジェクトを適切にモック化し、テスト前後で元の状態に戻す処理が実装されています。これはグローバルな状態を変更するテストにおいて重要な実践です。


49-74: テスト準備が適切に行われています

各テスト前にモックをクリアし、設定ストアとトーストストアの初期状態を適切に設定しています。テスト用のメッセージデータも適切に準備されています。


76-132: ストリーミングレスポンス処理のテストが詳細です

ストリーミングレスポンスの処理をテストするケースが詳細に実装されています。特に:

  1. モックリーダーの適切な設定
  2. デコード処理のシミュレーション
  3. 会話IDの状態更新の検証
  4. APIリクエストの詳細(ヘッダー、ボディなど)の検証

これらの点で完全なテストカバレッジが確保されています。


134-176: agent_messageイベント処理のテストが適切です

agent_messageイベントの処理に特化したテストケースが含まれており、異なるイベントタイプの処理を検証できています。レスポンスの内容と会話IDの更新が正しく検証されています。


178-222: JSONパースエラー処理のテストが堅牢です

不正なJSON形式に対するエラー処理をテストしており、例外がキャッチされ適切にログ出力されることを検証しています。console.errorを一時的にモック化し、テスト後に元に戻す処理も適切です。


224-263: ストリーミング中のエラー処理テストが完全です

ストリーミング中のエラー発生時の処理をテストしており、エラーログ出力とトースト通知の両方が検証されています。特にトースト通知のメッセージが適切な翻訳キーを使用していることの検証は重要です。


265-283: 空レスポンス処理のテスト実装が適切です

レスポンスが空の場合(body: null)のケースをテストしており、エラー処理の堅牢性を確認しています。空のストリーム結果が適切に処理されることを検証しています。


285-321: APIエラーレスポンス処理のテストが詳細です

APIからのエラーレスポンス(401 Unauthorized)を適切に処理するテストケースが実装されています。エラーメッセージの内容とトースト通知の詳細(タイプ、メッセージ、タグ)が正確に検証されています。

src/__mocks__/canvasMock.js (4)

1-25: Canvas クラスの実装が適切です

クラスベースのアプローチで Canvas のモック実装を行っており、実際のキャンバス API の主要なメソッドをすべて Jest のモック関数として提供しています。幅と高さのデフォルト値も適切に設定されています。


27-32: createCanvas 関数の実装が良好です

キャンバスの作成と寸法の設定が適切に行われており、デフォルト値も提供されています。実際のキャンバス API と同様の使用感を提供する実装になっています。


34-41: Image と loadImage の実装が適切です

Image コンストラクタと loadImage 関数のモック実装が適切に行われており、テスト環境で画像関連の処理をシミュレートするのに十分な機能を提供しています。


43-55: エクスポートの設定が包括的です

必要なすべてのキャンバス関連機能がエクスポートされており、テスト環境で canvas モジュールを完全に置き換えることができます。ストリーム関連のモックも含まれており、包括的な実装になっています。

src/__mocks__/canvas.js (3)

1-23: canvas モジュールのモック実装が適切です

モジュールエクスポートの形式が適切であり、createCanvas 関数は必要なコンテキストメソッドとプロパティをすべて提供しています。このモックは canvasMock.js と同様の機能を提供していますが、モジュールエクスポートの構造が異なります。


25-30: Image コンストラクタのモック実装が良好です

Image コンストラクタのモックが適切に実装されており、画像関連のテストに必要な基本的なプロパティを提供しています。


32-35: 補助関数のモック実装が適切です

loadImage と registerFont 関数が適切にモック化されており、canvas モジュールの API を完全にシミュレートするために必要な機能が提供されています。

src/__tests__/features/chat/openAIAudioChat.test.ts (4)

1-48: 依存関係のモック設定が適切です

OpenAI、ストア、ハンドラー、オーディオバッファマネージャー、メッセージセレクタなど、必要なすべての依存関係が適切にモック化されています。モックの構造は実際の実装と一致しており、テストの信頼性を確保しています。


49-94: テストの前処理設定が包括的です

beforeEach でモックをクリアし、テストに必要な状態を適切に設定しています。特に homeStore のモックは upsertMessage の実装も含めて詳細に行われており、実際の動作を正確に再現しています。


105-220: オーディオストリーム処理のテストが詳細です

OpenAI からのオーディオレスポンスの処理を詳細にテストしており、トランスクリプト、base64 データ、オーディオ ID の処理が正しく行われることを確認しています。モックされた非同期イテレータの使用と、ストリームの読み取り処理が適切に実装されています。


222-248: エラー処理のテストが適切です

API エラーが発生した場合の処理が適切にテストされており、エラーがコンソールに出力され、再スローされることを確認しています。console.error の元の実装を保存して復元する処理も適切です。

.cursor/rules/test.mdc (5)

1-9: テストガイドラインのドキュメント構造が良好です

フロントマターが適切に設定されており、ドキュメントのタイトルと概要が明確です。このガイドラインはプロジェクトのテスト実装に関する包括的な情報を提供する目的を明確に示しています。


10-49: テスト環境の説明が詳細です

使用するツール、設定ファイル、実行コマンドについて詳細に説明されており、開発者がテスト環境をセットアップして使用するための十分な情報が提供されています。Jest、Testing Library、ts-jest などの主要なテストツールが適切に説明されています。


51-107: テスト構造とパターンの説明が明確です

ディレクトリ構造、命名規則、基本的なテスト構造について明確なガイドラインが提供されており、一貫性のあるテスト実装を促進します。コード例も含まれており、理解しやすい内容になっています。


109-151: モックの使用パターンが詳細に説明されています

依存モジュール、Zustand ストア、外部 API などのモックパターンが詳細に説明されており、実際のコード例も提供されています。これにより、開発者が一貫した方法でモックを実装できます。


153-160: テスト記述のガイドラインが明確です

テストの説明を日本語で記述することや、一つのテストケースで一つの機能をテストすることなど、実用的なガイドラインが提供されています。エッジケースの考慮やモックのリセットなどの重要な実践も含まれています。

locales/pl/translation.json (1)

225-233: 翻訳キー追加ありがとうございます
新しいトースト文言が他言語と整合しています。特に問題ありません。

locales/en/translation.json (1)

224-237: Toast type tool 追加に伴う一貫性確認をお願いします

"UsingTool" と同時にトースト種別 tool が UI 側に追加されたはずですが、
src/components/toast.tsx 等で type の union 型・switch 文が更新されているか確認してください。
未更新の場合、該当トーストがレンダリングされず落ちる可能性があります。

src/pages/api/save-chat-log.ts (1)

116-116: レスポンスメッセージ複数形への変更にあわせてクライアント側を確認

以前の "Log saved successfully" を期待しているフロントコードが残っている場合
トースト等が表示されなくなるため、呼び出し側全てを検索して更新してください。

src/features/messages/speakQueue.ts (1)

63-66: Live2DHandler.stopSpeaking()await が漏れている

Live2DHandler.stopSpeaking()Promise<void> を返すため、未 await だと停止完了前に isSpeaking=false が通知され UI と音声再生状態が不整合になる恐れがあります。

-      Live2DHandler.stopSpeaking()
+      await Live2DHandler.stopSpeaking()

非同期停止が不要なモデル側 (viewer.model?.stopSpeaking()) も Promise を返す場合は合わせて await を検討してください。

locales/it/translation.json (1)

229-236: 占位子キーの整合性を確認してください

"PresetSwitching": "È stato cambiato a {{presetName}}." の占位子 presetName が他の locale で同じキー名か要確認です。異なるキー名だと i18next で置換されずにそのまま表示されます。

src/features/chat/handlers.ts (1)

540-569: id を付けずにコードブロックを upsert しているため、後続更新が出来ません
role: 'code' のメッセージを追加する際に id を渡していないため、

  • upsertMessage が内部で新規作成扱いにする実装でなければ上書きされる危険
  • 後段でメッセージを特定できずインクリメンタル更新不可能
    という問題が起こります。id を必須にしている他箇所との整合も取れないので、生成した generateMessageId() を渡してください。
-      homeStore.getState().upsertMessage({
-        role: 'code',
-        content: codeBlockContent,
-      })
+      homeStore.getState().upsertMessage({
+        id: generateMessageId(),
+        role: 'code',
+        content: codeBlockContent,
+      })

Comment on lines +748 to +760
hs.chatLog[hs.chatLog.length - 1].role === role &&
wsManager?.textBlockStarted
) {
// 既存のメッセージに追加
updateLog[updateLog.length - 1].content += text
// 既存のメッセージに追加(IDを維持)
const lastMessage = hs.chatLog[hs.chatLog.length - 1]
const lastContent =
typeof lastMessage.content === 'string' ? lastMessage.content : ''

homeStore.getState().upsertMessage({
id: lastMessage.id,
role: role,
content: lastContent + text,
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

lastMessage.content が配列の場合に文字列連結で壊れます
upsertMessage で既存メッセージに追記する際、lastMessage.content を文字列と仮定して + text を行っています。しかしユーザ送信時に画像添付をした場合など、content は配列 ({type:'text'|'image', ...}[]) になり得ます。このまま実行すると

  1. 型エラーでクラッシュする
  2. 配列→文字列への暗黙変換で情報が失われる
    いずれかが起こります。少なくとも型ガードを挟み、配列時はテキスト要素のみを抽出して連結する処理にしてください。
-        const lastContent =
-          typeof lastMessage.content === 'string' ? lastMessage.content : ''
+        const lastContent = Array.isArray(lastMessage.content)
+          ? lastMessage.content
+              .filter((c) => c.type === 'text')
+              .map((c) => c.text)
+              .join('')
+          : lastMessage.content ?? ''

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

🔭 Outside diff range comments (2)
src/features/messages/speakQueue.ts (1)

95-107: 🛠️ Refactor suggestion

トークン変更チェックの頻度が不十分です。

現在の実装ではループの各反復でのみトークン変更をチェックしていますが、speak()メソッドが長時間実行される場合、停止リクエストへの応答が遅れる可能性があります。

非同期操作の前後でもトークンのチェックを追加することを推奨します:

while (this.queue.length > 0) {
  // StopAll() によりトークンが変化していたら直ちに処理を中断
  if (startToken !== SpeakQueue.currentStopToken) {
    console.log('�� Stop token changed. Abort current queue processing.')
    break
  }

  const currentState = homeStore.getState()
  if (!currentState.isSpeaking) {
    this.clearQueue()
    homeStore.setState({ isSpeaking: false })
    break
  }

  const task = this.queue.shift()
  if (task) {
    if (task.sessionId !== this.currentSessionId) {
      // 旧セッションのタスクは破棄
      continue
    }
    try {
      const { audioBuffer, talk, isNeedDecode, onComplete } = task
+     // 非同期処理の実行前に再度トークンをチェック
+     if (startToken !== SpeakQueue.currentStopToken) {
+       console.log('Stop token changed before speech execution. Skipping task.')
+       break
+     }
      if (ss.modelType === 'live2d') {
        await Live2DHandler.speak(audioBuffer, talk, isNeedDecode)
      } else {
        await hs.viewer.model?.speak(audioBuffer, talk, isNeedDecode)
      }
+     // 非同期処理の完了後に再度トークンをチェック
+     if (startToken !== SpeakQueue.currentStopToken) {
+       console.log('Stop token changed during speech execution. Stopping further processing.')
+       break
+     }
      onComplete?.()
    } catch (error) {
src/features/chat/handlers.ts (1)

797-862: ⚠️ Potential issue

セッションIDの状態管理に問題があります。

handleReceiveTextFromRtFn 関数内の currentSessionId はクロージャ内に保持され、すべての呼び出し元で共有されています。これにより、複数のチャットが同時進行する場合にセッションIDが混在してしまう可能性があります。

セッションIDをより適切に管理するために、関数の状態を分離するか、呼び出し元ごとに識別子を追加してください:

export const handleReceiveTextFromRtFn = () => {
-  // 連続する response.audio イベントで共通の sessionId を使用するための変数
-  let currentSessionId: string | null = null
+  // 呼び出し元ごとのセッションID管理
+  const sessionsMap = new Map<string, string>()
+  
+  // 呼び出し元の識別子を追加
+  return async (
+    text?: string,
+    role?: string,
+    type?: string,
+    buffer?: ArrayBuffer,
+    callerId: string = 'default'
+  ) => {
-    if (currentSessionId === null) {
-      currentSessionId = generateSessionId()
+    if (!sessionsMap.has(callerId)) {
+      sessionsMap.set(callerId, generateSessionId())
    }

-    const sessionId = currentSessionId
+    const sessionId = sessionsMap.get(callerId)!

    // ... 残りの処理 ...

    // レスポンスが完了したらセッションIDをリセット
    if (type === 'response.content_part.done') {
-      currentSessionId = null
+      sessionsMap.delete(callerId)
    }
  }
}

または、より単純な解決策として、呼び出し元ごとにファクトリ関数のインスタンスを作成するよう設計を変更することも検討してください。

♻️ Duplicate comments (5)
.github/workflows/test.yml (1)

17-20: GitHub Actionsのバージョンを更新してください

現在使用しているactions/setup-node@v3は古くなっています。最新バージョンのactions/setup-node@v4に更新することで、パフォーマンスの向上やバグ修正の恩恵を受けられます。

以下のように更新してください:

-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
🧰 Tools
🪛 actionlint (1.7.4)

17-17: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

src/pages/slide-editor/[slideName].tsx (1)

1-1: ⚠️ Potential issue

未使用 import が残っています

KeyboardEvent は 1 行目でインポートされていますが、このファイル内では型としても値としても使用されていません。globalThis.KeyboardEvent を直接参照しているため不要です。削除してビルドエラー・バンドルサイズを抑制しましょう。

-import React, { useEffect, useState, useCallback, KeyboardEvent } from 'react'
+import React, { useEffect, useState, useCallback } from 'react'
src/features/stores/home.ts (1)

86-90: ⚠️ Potential issue

存在する値が undefined で上書きされる問題が修正されていません。

現在の実装では、Partial<Message> で渡された undefined のプロパティが既存の値を上書きしてしまいます。このため、部分的な更新を意図していても、不要なプロパティが失われる可能性があります。

次のように「定義済みのキーのみマージ」する実装に修正してください:

-updatedChatLog[existingMessageIndex] = {
-  ...existingMessage,
-  ...message,
-  id: messageId,
-}
+updatedChatLog[existingMessageIndex] = {
+  ...existingMessage,
+  ...(message.content !== undefined && { content: message.content }),
+  ...(message.role !== undefined && { role: message.role }),
+  ...(message.audio !== undefined && { audio: message.audio }),
+  ...(message.timestamp !== undefined && { timestamp: message.timestamp }),
+  id: messageId,
+}
src/features/messages/speakQueue.ts (1)

53-68: ⚠️ Potential issue

一度停止した後のプロセス再開に問題があります。

stopAll()メソッドでisProcessing = falseに設定していますが、これだけでは既に実行中のprocessQueue()関数のループを直接停止することはできません。

以下の修正が必要です:

public static stopAll() {
  const instance = SpeakQueue.getInstance()
  instance.stopped = true
  // 発話キューの処理状態をリセットして次回の再生を可能にする
  instance.isProcessing = false
  SpeakQueue.stopTokenCounter++
  instance.clearQueue()
  const hs = homeStore.getState()
  const ss = settingsStore.getState()
  if (ss.modelType === 'live2d') {
    Live2DHandler.stopSpeaking()
  } else {
    hs.viewer.model?.stopSpeaking()
  }
  homeStore.setState({ isSpeaking: false })
+  // すぐに次のキュー処理が始まらないよう、短いタイムアウトを設ける
+  setTimeout(() => {
+    instance.isProcessing = false
+  }, 100)
}

さらに、processQueue()関数内でもトークン変更を検知してループを終了するチェックを強化してください:

private async processQueue() {
  // 既に別の processQueue が動作中の場合は新たに起動しない
  if (this.isProcessing) return

  // Stop ボタンが押された後に再開されたかどうかを判定するためのトークンをキャプチャ
  const startToken = SpeakQueue.currentStopToken

  // 停止中は処理しない
  if (this.stopped) {
    this.clearQueue()
    return
  }

  this.isProcessing = true
+ 
+ // 処理開始直後に停止されていないか確認
+ if (startToken !== SpeakQueue.currentStopToken) {
+   console.log('Stop token changed immediately after processing started. Aborting.')
+   this.isProcessing = false
+   return
+ }
src/features/chat/handlers.ts (1)

738-744: ⚠️ Potential issue

lastMessage.content が配列の場合に文字列連結で壊れます。

upsertMessage で既存メッセージに追記する際、lastMessage.content を文字列と仮定して連結処理を行っています。画像添付メッセージなどの場合、content は配列となり、型エラーやデータ損失が発生する可能性があります。

型に応じた適切な処理を追加してください:

-const lastContent =
-  typeof lastMessage.content === 'string' ? lastMessage.content : ''
+const lastContent = Array.isArray(lastMessage.content)
+  ? lastMessage.content
+      .filter((c) => c.type === 'text')
+      .map((c) => c.text)
+      .join('')
+  : lastMessage.content ?? ''
🧹 Nitpick comments (5)
src/pages/slide-editor/[slideName].tsx (2)

289-307: setScripts 内ループを簡潔化し optional chaining を活用できます

現在の実装は findIndexmap で 2 パスとなり、コード量も多くなっています。
配列をコピーして直接書き換える形にすると読みやすく、Biome の optional-chain 警告も解消します。

-const scriptIndex = prevScripts.findIndex((s) => s.page === pageIndex)
-let updatedScripts
-if (scriptIndex !== -1) {
-  updatedScripts = prevScripts.map((script, index) =>
-    index === scriptIndex ? { ...script, [field]: value } : script
-  )
-} else {
-  ...
-}
-return updatedScripts
+const updated = [...prevScripts]
+const idx = updated.findIndex((s) => s.page === pageIndex)
+if (idx >= 0) {
+  updated[idx] = { ...updated[idx], [field]: value }
+} else {
+  updated.push({
+    page: pageIndex,
+    line: field === 'line' ? value : '',
+    notes: field === 'notes' ? value : '',
+  })
+}
+return updated.sort((a, b) => a.page - b.page)
🧰 Tools
🪛 Biome (1.9.4)

[error] 289-289: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


415-425: 未保存チェックだけでページ遷移・リロードを防げると UX が向上します

isDirtytrue のままブラウザの戻る/更新やルーティング遷移を行うと変更内容が失われます。
beforeunload や Next.js の router.events.on('routeChangeStart', ...) でガードを掛け、確認ダイアログを出すと誤操作を防げます。実装を検討ください。

src/features/stores/home.ts (2)

156-201: デバウンス処理中にクリーンアップが保証されていません。

現在のデバウンス実装では、ページ遷移やコンポーネントのアンマウント時にタイマーがクリーンアップされない可能性があります。これによりメモリリークやバックグラウンドでの不要な処理が続く可能性があります。

zustandStoreApiを利用して、ストアの破棄時にタイマーをクリーンアップする仕組みを追加することを検討してください:

// ストア定義部分(create 呼び出し前)に以下を追加
+const cleanup = () => {
+  if (saveDebounceTimer) {
+    clearTimeout(saveDebounceTimer)
+    saveDebounceTimer = null
+  }
+}

const homeStore = create<HomeState>()(
  persist(
    (set, get, api) => {
+     // ストアAPIの拡張
+     const originalDestroy = api.destroy
+     api.destroy = () => {
+       cleanup()
+       originalDestroy()
+     }
      
      return {
        // ... 既存のストア定義 ...
      }
    },
    // ... 残りのコード ...
  )
)

// さらに、アプリケーションの終了時やコンポーネントのアンマウント時に
// cleanup() を呼び出す仕組みを追加することも検討してください

159-163: 差分検出ロジックが効率的ではありません。

現在の実装では更新されたメッセージを検出するために find() メソッドを使用しており、これは大量のメッセージがある場合に非効率です。O(n²) の計算量を持ち、パフォーマンス問題の原因となる可能性があります。

より効率的なマップベースのアプローチを使用して検出ロジックを改善してください:

-const newMessagesToSave = state.chatLog.filter(
-  (msg, idx) =>
-    idx >= lastSavedLogLength || // 追加分
-    prevState.chatLog.find((p) => p.id === msg.id)?.content !==
-      msg.content // 更新分
-)

+// 前回のメッセージのコンテンツをIDマップとして保持
+const prevMessagesMap = Object.fromEntries(
+  prevState.chatLog.map(msg => [msg.id, msg.content])
+)
+
+const newMessagesToSave = state.chatLog.filter(
+  (msg, idx) =>
+    idx >= lastSavedLogLength || // 追加分
+    prevMessagesMap[msg.id] !== msg.content // 更新分
+)
src/features/chat/handlers.ts (1)

44-57: 文の抽出ロジックに改善の余地があります。

現在の正規表現による文の抽出は複雑で理解しづらく、特殊なケースに対応していない可能性があります。また、日本語と英語が混在するテキストでは適切に文を区切れない場合があります。

より理解しやすく保守しやすい文の抽出ロジックへの改善を検討してください:

const extractSentence = (
  text: string
): { sentence: string; remainingText: string } => {
-  const sentenceMatch = text.match(
-    /^(.{1,19}?(?:[。..!?!?\n]|(?=\[))|.{20,}?(?:[、,。..!?!?\n]|(?=\[)))/
-  )
+  // 文末記号で分割する簡潔なロジック
+  const endOfSentenceRegex = /^(.*?[。..!?!?\n]|.*?(?=\[))/;
+  
+  // 短い文(20文字未満)は文末記号がなくても1つの文として扱う
+  const shortSentenceRegex = /^(.{1,19})/;
+  
+  // 長い文(20文字以上)は読点でも区切る
+  const longSentenceWithCommaRegex = /^(.{20,}?[、,])/;
+  
+  const sentenceMatch = 
+    text.match(endOfSentenceRegex) || 
+    text.match(longSentenceWithCommaRegex) ||
+    text.match(shortSentenceRegex) ||
+    [null, text]; // どのパターンにも一致しない場合は全体を1つの文として扱う

  if (sentenceMatch?.[0]) {
    return {
      sentence: sentenceMatch[0],
      remainingText: text.slice(sentenceMatch[0].length).trimStart(),
    }
  }
  return { sentence: '', remainingText: text }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between d40d909 and 86d2db8.

📒 Files selected for processing (17)
  • .env.example (1 hunks)
  • .github/workflows/lint-and-format.yml (1 hunks)
  • .github/workflows/test.yml (1 hunks)
  • src/__mocks__/readableStream.js (1 hunks)
  • src/__tests__/features/chat/vercelAIChat.test.ts (1 hunks)
  • src/features/chat/handlers.ts (9 hunks)
  • src/features/messages/speakCharacter.ts (2 hunks)
  • src/features/messages/speakQueue.ts (7 hunks)
  • src/features/stores/home.ts (5 hunks)
  • src/pages/api/save-chat-log.ts (3 hunks)
  • src/pages/slide-editor/[slideName].tsx (1 hunks)
  • website/document/en/guide/ai/model-provider.md (1 hunks)
  • website/document/en/guide/environment-variables.md (1 hunks)
  • website/document/guide/ai/model-provider.md (1 hunks)
  • website/document/guide/environment-variables.md (1 hunks)
  • website/document/zh/guide/ai/model-provider.md (1 hunks)
  • website/document/zh/guide/environment-variables.md (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • .env.example
  • website/document/zh/guide/environment-variables.md
  • .github/workflows/lint-and-format.yml
  • website/document/en/guide/environment-variables.md
  • website/document/guide/environment-variables.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/mocks/readableStream.js
  • src/pages/api/save-chat-log.ts
  • src/features/messages/speakCharacter.ts
🧰 Additional context used
🧠 Learnings (1)
src/__tests__/features/chat/vercelAIChat.test.ts (2)
Learnt from: tegnike
PR: tegnike/aituber-kit#364
File: src/__tests__/features/chat/difyChat.test.ts:21-28
Timestamp: 2025-04-24T19:00:22.299Z
Learning: テストファイルでグローバルオブジェクト(例:`global.fetch`や`global.TextDecoder`)を上書きする場合は、`afterAll()`フック内で元の値に戻して、テスト間の副作用を防ぐべきである。
Learnt from: tegnike
PR: tegnike/aituber-kit#364
File: src/__tests__/features/chat/difyChat.test.ts:21-28
Timestamp: 2025-04-24T16:21:20.671Z
Learning: テストファイルでグローバルオブジェクト(例:`global.fetch`や`global.TextDecoder`)を上書きする場合は、`afterAll()`フック内で元の値に戻して、テスト間の副作用を防ぐべきである。
🧬 Code Graph Analysis (2)
src/__tests__/features/chat/vercelAIChat.test.ts (2)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/features/chat/vercelAIChat.ts (2)
  • getVercelAIChatResponse (57-136)
  • getVercelAIChatResponseStream (138-332)
src/features/stores/home.ts (3)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/utils/messageUtils.ts (1)
  • generateMessageId (7-7)
src/features/messages/messageSelectors.ts (1)
  • messageSelectors (4-178)
🪛 Biome (1.9.4)
src/pages/slide-editor/[slideName].tsx

[error] 289-289: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 412-414: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 actionlint (1.7.4)
.github/workflows/test.yml

17-17: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (9)
website/document/zh/guide/ai/model-provider.md (1)

250-251: 新環境変数の追加を確認しました
NEXT_PUBLIC_INCLUDE_SYSTEM_MESSAGES_IN_CUSTOM_API が正しくドキュメントに追加されており、カスタムAPIでシステムメッセージを有効/無効化する方法が明確に示されています。

website/document/guide/ai/model-provider.md (1)

250-251: カスタムAPI向けシステムメッセージ設定を追記
NEXT_PUBLIC_INCLUDE_SYSTEM_MESSAGES_IN_CUSTOM_API の説明が日本語ガイドにも一貫して追加されており、記述とフォーマットに問題はありません。

website/document/en/guide/ai/model-provider.md (1)

250-251: Documented new environment variable
The NEXT_PUBLIC_INCLUDE_SYSTEM_MESSAGES_IN_CUSTOM_API setting has been accurately added to clarify how to enable/disable system messages for custom APIs.

src/__tests__/features/chat/vercelAIChat.test.ts (4)

287-292: グローバルオブジェクトの復元は適切に実装されています

afterAllフックでglobal.fetchglobal.TextDecoderをオリジナルの値に戻していて、テスト間の副作用を防ぐ対策がされています。これは良い実装です。


173-196: ストリーミングレスポンスのテストが適切に実装されています

ストリーミングレスポンスを非常によくテストしています。モックからのデータをデコードし、ReadableStreamを介してデータを読み取り、結果を正しく検証しています。


198-236: エラーハンドリングのテストが包括的です

ストリーミング中のエラーケースを適切にテストし、エラー時にトーストが表示されることを確認しています。エラーメッセージの国際化も確認されています。


125-150: カスタムAPIモードのシステムメッセージフィルタリングテストが重要です

includeSystemMessagesInCustomApiの設定に基づいてシステムメッセージをフィルタリングする機能は重要であり、適切にテストされています。

src/pages/slide-editor/[slideName].tsx (2)

596-604: 初期状態で「元に戻す」ボタンが無効にならないケースがあります

handleRevertcurrentScript/currentNotes を更新していますが、currentSlide が変化していない場合、依存配列に含まれないため最新表示に反映されません。handleRevert 内で setCurrentSlide をトリガするか、依存配列に currentScript などを含めることを検討してください。


610-617: カラークラス bg-primary が Tailwind 設定外の場合はビルド失敗の恐れ

Tailwind で bg-primary / bg-primary-hover を utilities に追加していない場合、purge 対象外で CSS が生成されず意図した色になりません。tailwind.config.js に safelist あるいは theme.colors への追加が済んでいるか確認をお願いします。

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: 1

♻️ Duplicate comments (2)
src/features/stores/home.ts (1)

82-123: ⚠️ Potential issue

既存のレビュー指摘が未修正: undefined 値による既存データの上書き問題

upsertMessage関数の実装において、以前のレビューで指摘された問題が解決されていません。現在の実装では、Partialで渡された値がundefinedの場合でも、スプレッド構文によって既存の値が上書きされてしまいます。

updatedChatLog[existingMessageIndex] = {
  ...existingMessage,
-  ...message,
+  ...(message.content !== undefined && { content: message.content }),
+  ...(message.role !== undefined && { role: message.role }),
+  ...(message.audio !== undefined && { audio: message.audio }),
+  ...(message.timestamp !== undefined && { timestamp: message.timestamp }),
  id: messageId,
}

この変更により、undefinedによる意図しない上書きを防ぎ、より安全な実装になります。

src/features/chat/handlers.ts (1)

741-743: ⚠️ Potential issue

lastMessage.contentが配列の場合の処理が不十分です

前回のレビューで指摘された問題が完全には解決されていません。現在の実装では、lastMessage.contentが配列型の場合に適切に処理されていません:

const lastContent =
-  typeof lastMessage.content === 'string' ? lastMessage.content : ''
+  Array.isArray(lastMessage.content)
+    ? lastMessage.content
+      .filter((c) => c.type === 'text')
+      .map((c) => c.text)
+      .join('')
+    : typeof lastMessage.content === 'string'
+      ? lastMessage.content
+      : ''

この修正により、マルチモーダルメッセージ(テキストと画像を含む配列)の場合でも適切に処理されます。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 86d2db8 and 644729f.

📒 Files selected for processing (3)
  • src/features/chat/handlers.ts (9 hunks)
  • src/features/messages/speakQueue.ts (7 hunks)
  • src/features/stores/home.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/features/messages/speakQueue.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/features/stores/home.ts (3)
src/features/messages/messages.ts (1)
  • Message (1-9)
src/utils/messageUtils.ts (1)
  • generateMessageId (7-7)
src/features/messages/messageSelectors.ts (1)
  • messageSelectors (4-178)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
src/features/stores/home.ts (3)

154-216: デバウンス保存機能の実装がとても良いです!

チャットログの変更をデバウンスして効率的にバッチ保存する実装は非常に優れています。以下の点が特に評価できます:

  1. 2秒間のデバウンス処理によりAPI呼び出し回数を削減
  2. 新規または更新されたメッセージのみを保存することでデータ転送量を削減
  3. 詳細なログ出力によりデバッグが容易
  4. エラーハンドリングが適切に実装されている

この実装により、パフォーマンスとユーザー体験の両方が向上します。


47-55: リセット処理の共通化は良い改善です

resetSaveState関数を作成して、チャットログがクリアされた際のリセット処理を一箇所に集約したのは良い改善です。これにより:

  1. コードの重複が削減される
  2. ロジックの不整合のリスクが減少する
  3. 保守性が向上する

今後も同様のアプローチで処理を共通化することをお勧めします。


40-45: 状態変数の役割を説明するコメントが適切です

保存処理のための状態変数に対するコメントが適切で分かりやすいです。特に:

  • デバウンスのための変数
  • 最後に保存したログの長さを記録する変数
  • 新規ファイル作成のためのフラグ

これらのコメントにより、コードの意図が明確になり、他の開発者が理解しやすくなります。

src/features/chat/handlers.ts (4)

26-41: 感情タグ抽出の改善は素晴らしいです

感情タグの抽出ロジックを改善したことで、以前の問題点が解決されています:

  1. 先頭のスペースを無視して感情タグを検出
  2. タグが見つかった場合の適切な文字列スライス処理
  3. 戻り値のトリミング処理の追加

これにより、より堅牢な感情タグ抽出が可能になりました。


71-112: 発話とステート更新の共通関数化は優れた改善です

handleSpeakAndStateUpdate関数の導入により、以下の点が改善されています:

  1. コードの重複が削減される
  2. 発話ロジックが一箇所に集約され、変更が容易になる
  3. 感情タグの処理と発話不要な文字列のスキップ処理が適切に実装されている

この関数を使用することで、コードの保守性と可読性が向上しています。


801-866: RealtimeAPI用のセッションID管理を改善しました

handleReceiveTextFromRtFn関数において、セッションIDのライフサイクル管理が改善されています:

  1. 関数がファクトリーパターンを使用して状態を保持
  2. セッションIDを適切なタイミングで生成・再利用・リセット
  3. レスポンス完了時にセッションIDをリセットする処理の追加

この実装により、複数の音声応答イベントでセッションの一貫性が保たれます。


160-165: セーブデバウンスタイマーの再設定が適切に行われています

タイマーのクリアと再設定のロジックが適切に実装されています:

if (saveDebounceTimer) {
  clearTimeout(saveDebounceTimer)
}

saveDebounceTimer = setTimeout(() => {
  // ...
}, SAVE_DEBOUNCE_DELAY)

これにより、不要なタイマーがメモリリークを起こす可能性を防いでいます。

Comment on lines +348 to 495
while (processableTextForSpeech.length > 0) {
const originalProcessableText = processableTextForSpeech

if (isCodeBlock) {
codeBlockContent += processableTextForSpeech
processableTextForSpeech = ''

const delimiterIndex = codeBlockContent.lastIndexOf(CODE_DELIMITER)

const sentenceMatch = receivedMessage.match(
/^(.{1,19}?(?:[.!?\n]|(?=\[))|.{20,}?(?:[,.!?\n]|(?=\[)))/
)
if (sentenceMatch?.[0]) {
let sentence = sentenceMatch[0]
// 区切った文字をsentencesに追加
sentences.push(sentence)
// 区切った文字の残りでreceivedMessageを更新
receivedMessage = receivedMessage.slice(sentence.length).trimStart()

// 発話不要/不可能な文字列だった場合はスキップ
if (
!sentence.includes('```') &&
!sentence.replace(
/^[\s\u3000\t\n\r\[\(\{«»\}\)\]'"''"",.!?:;\-_=+~*@#$%^&|\\/`]+$/gu,
''
)
delimiterIndex !== -1 &&
delimiterIndex >=
codeBlockContent.length -
(originalProcessableText.length + CODE_DELIMITER.length - 1)
) {
const actualCode = codeBlockContent.substring(0, delimiterIndex)
const remainingAfterDelimiter = codeBlockContent.substring(
delimiterIndex + CODE_DELIMITER.length
)

if (actualCode.trim()) {
homeStore.getState().upsertMessage({
role: 'code',
content: actualCode,
})
}

codeBlockContent = ''
isCodeBlock = false
currentEmotionTag = ''

currentMessageId = generateMessageId()
currentMessageContent = ''

processableTextForSpeech = remainingAfterDelimiter.trimStart()
continue
} else {
receivedChunksForSpeech = codeBlockContent + receivedChunksForSpeech
codeBlockContent = ''
break
}
} else {
const delimiterIndex =
processableTextForSpeech.indexOf(CODE_DELIMITER)
if (delimiterIndex !== -1) {
const beforeCode = processableTextForSpeech.substring(
0,
delimiterIndex
)
const afterDelimiterRaw = processableTextForSpeech.substring(
delimiterIndex + CODE_DELIMITER.length
)

// 感情と返答を結合(音声再生で使用される)
let aiText = `${emotion} ${sentence}`
aiTextLog.push({ role: 'assistant', content: aiText })

// 文ごとに音声を生成 & 再生、返答を表示
const currentAssistantMessage = sentences.join(' ')
//
let textToProcessBeforeCode = beforeCode.trimStart()
while (textToProcessBeforeCode.length > 0) {
const prevText = textToProcessBeforeCode
const {
emotionTag: extractedEmotion,
remainingText: textAfterEmotion,
} = extractEmotion(textToProcessBeforeCode)
if (extractedEmotion) currentEmotionTag = extractedEmotion
const { sentence, remainingText: textAfterSentence } =
extractSentence(textAfterEmotion)

if (sentence) {
handleSpeakAndStateUpdate(
sessionId,
sentence,
currentEmotionTag,
assistantMessageListRef,
currentSlideMessagesRef
)
textToProcessBeforeCode = textAfterSentence
if (!textAfterSentence) currentEmotionTag = ''
} else {
receivedChunksForSpeech =
textToProcessBeforeCode + receivedChunksForSpeech
textToProcessBeforeCode = ''
break
}

if (
textToProcessBeforeCode.length > 0 &&
textToProcessBeforeCode === prevText
) {
console.warn('Speech processing loop stuck on:', prevText)
receivedChunksForSpeech =
textToProcessBeforeCode + receivedChunksForSpeech
break
}
}

speakCharacter(
sessionId,
{
message: sentence,
emotion: emotion.includes('[')
? (emotion.slice(1, -1).toLowerCase() as EmotionType)
: 'neutral',
},
() => {
homeStore.setState({
assistantMessage: currentAssistantMessage,
})
hs.incrementChatProcessingCount()
// スライド用のメッセージを更新
currentSlideMessages.push(sentence)
homeStore.setState({
slideMessages: currentSlideMessages,
})
},
() => {
hs.decrementChatProcessingCount()
currentSlideMessages.shift()
homeStore.setState({
slideMessages: currentSlideMessages,
})
isCodeBlock = true
codeBlockContent = ''

const langMatch = afterDelimiterRaw.match(/^ *(\w+)? *\n/)
let remainingAfterDelimiter = afterDelimiterRaw
if (langMatch) {
remainingAfterDelimiter = afterDelimiterRaw.substring(
langMatch[0].length
)
}
processableTextForSpeech = remainingAfterDelimiter
continue
} else {
const {
emotionTag: extractedEmotion,
remainingText: textAfterEmotion,
} = extractEmotion(processableTextForSpeech)
if (extractedEmotion) currentEmotionTag = extractedEmotion

const { sentence, remainingText: textAfterSentence } =
extractSentence(textAfterEmotion)

if (sentence) {
handleSpeakAndStateUpdate(
sessionId,
sentence,
currentEmotionTag,
assistantMessageListRef,
currentSlideMessagesRef
)
processableTextForSpeech = textAfterSentence
if (!textAfterSentence) currentEmotionTag = ''
} else {
receivedChunksForSpeech =
processableTextForSpeech + receivedChunksForSpeech
processableTextForSpeech = ''
break
}
}
}

if (
processableTextForSpeech.length > 0 &&
processableTextForSpeech === originalProcessableText
) {
console.warn(
'Main speech processing loop stuck on:',
originalProcessableText
)
} else {
// マッチする文がない場合、ループを抜ける
receivedChunksForSpeech =
processableTextForSpeech + receivedChunksForSpeech
processableTextForSpeech = ''
break
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

コードブロック処理のロジックが複雑すぎます

コードブロック処理のロジックが非常に複雑で、多数のネストされた条件分岐があります。これは保守性の観点から問題となる可能性があります:

  1. 複雑なif-elseのネストにより可読性が低下
  2. 条件分岐が多く、全てのケースを把握するのが困難
  3. 将来的な変更が難しくなる可能性がある

このロジックを小さな関数に分割し、責任範囲を明確にすることを推奨します:

// コードブロック処理を別関数に抽出する例
+ const processCodeBlock = (content, codeBlockContent) => {
+   // コードブロック関連の処理ロジック
+   return { processedContent, updatedCodeBlockContent, isCodeBlockEnded };
+ }
+
+ const processTextWithEmotion = (text) => {
+   // 感情タグと文の抽出処理
+   return { processedText, extractedEmotion };
+ }

これにより、メイン関数のフローがよりシンプルになり、個々の責任を持つ関数をテストしやすくなります。

Committable suggestion skipped: line range outside the PR's diff.

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: 0

🧹 Nitpick comments (1)
.cursor/rules/development-process.mdc (1)

1-5: フロントマターの情報をより充実させることを推奨します

フロントマターにおいて、descriptionglobsが空になっています。このドキュメントの目的や適用範囲をより明確にするために、これらのフィールドに適切な情報を追加することを検討してください。

---
-description: 
+description: プロジェクトの開発プロセスとガイドラインを定義した文書。すべての開発者がタスク実行時に従うべき構造化されたフレームワークを提供します。
-globs: 
+globs: ["**/*.{js,jsx,ts,tsx}"]
alwaysApply: false
---
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 644729f and 64a531f.

📒 Files selected for processing (21)
  • .cursor/rules/api-management.mdc (1 hunks)
  • .cursor/rules/code-conventions.mdc (1 hunks)
  • .cursor/rules/development-process.mdc (1 hunks)
  • .cursor/rules/project-structure.mdc (1 hunks)
  • .cursor/rules/task-guides.mdc (1 hunks)
  • .cursor/rules/tech-stack.mdc (1 hunks)
  • .cursor/rules/test.mdc (1 hunks)
  • README.md (1 hunks)
  • docs/README_en.md (1 hunks)
  • docs/README_ko.md (1 hunks)
  • docs/README_pl.md (1 hunks)
  • docs/README_zh.md (1 hunks)
  • docs/license.md (1 hunks)
  • docs/license_en.md (1 hunks)
  • locales/ja/translation.json (3 hunks)
  • website/document/en/guide/ai/model-provider.md (2 hunks)
  • website/document/en/guide/character/live2d.md (1 hunks)
  • website/document/guide/ai/model-provider.md (2 hunks)
  • website/document/guide/character/live2d.md (1 hunks)
  • website/document/zh/guide/ai/model-provider.md (2 hunks)
  • website/document/zh/guide/character/live2d.md (1 hunks)
✅ Files skipped from review due to trivial changes (12)
  • docs/README_en.md
  • docs/README_zh.md
  • docs/license.md
  • README.md
  • website/document/guide/character/live2d.md
  • docs/README_pl.md
  • .cursor/rules/api-management.mdc
  • .cursor/rules/code-conventions.mdc
  • .cursor/rules/tech-stack.mdc
  • docs/README_ko.md
  • .cursor/rules/project-structure.mdc
  • .cursor/rules/task-guides.mdc
🚧 Files skipped from review as they are similar to previous changes (5)
  • website/document/guide/ai/model-provider.md
  • website/document/zh/guide/ai/model-provider.md
  • website/document/en/guide/ai/model-provider.md
  • .cursor/rules/test.mdc
  • locales/ja/translation.json
🧰 Additional context used
🪛 LanguageTool
docs/license_en.md

[uncategorized] ~60-~60: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...f Live2D, the conditions stated in the "Third Party Licenses" section below also apply. Fo...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
website/document/en/guide/character/live2d.md (1)

133-133: 追加費用を25%から40%に更新
Live2Dモデル商用利用時の追加費用が40%に更新されていることを確認しました。

website/document/zh/guide/character/live2d.md (1)

131-131: 中国語ドキュメントの追加費用40%更新を確認
基本ライセンス費用の40%追加料金への変更が正しく反映されています。

docs/license_en.md (1)

60-60: ライセンス文書における追加費用40%への更新確認
25%から40%への変更が適切に反映されています。

🧰 Tools
🪛 LanguageTool

[uncategorized] ~60-~60: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...f Live2D, the conditions stated in the "Third Party Licenses" section below also apply. Fo...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

.cursor/rules/development-process.mdc (8)

6-9: 明確な開発プロセスの導入を評価します

プロジェクトに構造化された開発プロセスを導入することは、コード品質の向上と一貫性のある開発アプローチの確立に役立ちます。特に本番リリースに向けて、このような明確なガイドラインが存在することは重要です。


10-19: 分析と計画フェーズの充実した内容を評価します

実装前に重複機能の確認を強調している点は特に価値があります。これにより、コードの重複を防ぎ、メンテナンス性の向上に貢献します。


20-25: タスク実行フェースの適切な構成を評価します

進捗報告やディレクトリ構造・命名規則の遵守など、重要な開発プラクティスが含まれています。問題発生時の即時報告と解決策の提案を求める指針は、プロジェクトの健全な進行に貢献します。


26-38: 品質管理プロセスの詳細化を評価します

エラー発生時の対応手順が明確に定義されており、問題の切り分けから修正検証までの流れが具体的です。検証結果の記録方法も明確に指定されており、品質管理の徹底に役立ちます。


39-43: 最終確認ステップの重要性を評価します

特に実装の重複がないかの最終確認(行42)は、コードベースの品質維持に重要な役割を果たします。この確認プロセスにより、潜在的な重複実装を防ぐことができます。


44-67: 構造化された結果報告テンプレートを評価します

結果報告の標準テンプレートを提供することで、報告の一貫性と完全性が確保されます。概要、実行ステップ、成果物、課題対応など、必要な要素が網羅されています。


68-75: 重要な注意点の明確化を評価します

特にUI/UXの変更制限(行73)と技術スタックのバージョン変更制限(行74)は、プロジェクトの安定性を保つ上で重要な指針です。明示的な承認なしにこれらを変更しないという方針は、一貫性のある製品開発をサポートします。


1-75: プロジェクトガバナンスの強化を評価します

このドキュメントは、AIの要約で言及されている他の追加されたガバナンス文書(API管理、コード規約、プロジェクト構造、タスクガイド、技術スタック)と合わせて、一貫性のある開発フレームワークを構築しています。本番リリースに向けて、このような明確な開発プロセスとガイドラインの導入は非常に価値があります。

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: 0

🧹 Nitpick comments (6)
website/document/zh/guide/slide-settings.md (2)

148-152: 用語を統一してください:脚本 vs 台本

現在のセクションでは「台本」を使用していますが、ドキュメントの他の箇所(例: scripts.json の説明など)では「脚本」という訳語が使われています。訳語を統一し、読者の混乱を避けるため「脚本」に揃えることを提案します。


153-154: 画像のaltテキストをより具体的に

幻灯片台本编辑1幻灯片台本编辑2 は汎用的すぎるため、アクセシビリティ向上のために表示内容を簡潔に説明したaltテキスト(例: 编辑台本界面:页面脚本编辑区整体补充信息编辑区)に置き換えることをおすすめします。

website/document/guide/slide-settings.md (4)

114-114: ファイル配置場所と形式の明確化
scripts.json の配置先ディレクトリ(例:public/slides/[フォルダ名]/scripts.json)およびJSON形式であることを追記すると、ユーザーがセットアップを間違えにくくなります。


131-131: ファイル配置場所と形式の明確化
supplement.txt の配置先ディレクトリ(例:public/slides/[フォルダ名]/supplement.txt)およびテキスト形式であることを追記すると、ドキュメントがより親切になります。


148-152: UI遷移先パスの明示を検討してください
「セリフ編集」ボタンをクリックした後に遷移するページのURLやルートを明記すると、ユーザーが編集画面へのアクセス方法を把握しやすくなります。

🧰 Tools
🪛 LanguageTool

[uncategorized] ~150-~150: 「ことができる」という表現は冗長な可能性があります。
Context: ...格納するフォルダ ## スライド台本の編集 スライドの台本は画面上で編集することができます。 スライド設定で、選択したスライドの右隣にある「セリフ編集」ボタンをクリッ...

(DOUSI_KOTOGADEKIRU)


150-150: 冗長表現の簡素化
「編集することができます」は「編集できます」に変更すると、より簡潔な日本語になります。

🧰 Tools
🪛 LanguageTool

[uncategorized] ~150-~150: 「ことができる」という表現は冗長な可能性があります。
Context: ...格納するフォルダ ## スライド台本の編集 スライドの台本は画面上で編集することができます。 スライド設定で、選択したスライドの右隣にある「セリフ編集」ボタンをクリッ...

(DOUSI_KOTOGADEKIRU)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between abb48a0 and 261504c.

⛔ Files ignored due to path filters (2)
  • website/document/public/images/slide_fa3sf.png is excluded by !**/*.png
  • website/document/public/images/slide_vhy4s.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • website/document/en/guide/slide-settings.md (1 hunks)
  • website/document/guide/slide-settings.md (3 hunks)
  • website/document/zh/guide/slide-settings.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • website/document/en/guide/slide-settings.md
🧰 Additional context used
🪛 LanguageTool
website/document/guide/slide-settings.md

[uncategorized] ~150-~150: 「ことができる」という表現は冗長な可能性があります。
Context: ...格納するフォルダ ## スライド台本の編集 スライドの台本は画面上で編集することができます。 スライド設定で、選択したスライドの右隣にある「セリフ編集」ボタンをクリッ...

(DOUSI_KOTOGADEKIRU)


[uncategorized] ~163-~163: 「ことができる」という表現は冗長な可能性があります。
Context: ...> 質疑の内容がこの追加情報に記載されている場合に、自動的にそのページに切り替えることができます。 ### スライド全体の補足情報 スライド全体の補足情報を追記します...

(DOUSI_KOTOGADEKIRU)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Cloudflare Pages

@tegnike tegnike merged commit baf46ff into main Apr 26, 2025
7 checks passed
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