-
-
Notifications
You must be signed in to change notification settings - Fork 143
本番リリース #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…を整理し、可読性を向上させました。
…、コードブロックの処理を強化しました。
…た。また、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>
テスト後のグローバルオブジェクト復元
Feature/fix stop button
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Walkthroughこの変更は、プロジェクト全体にわたる大規模なアップデートです。主な内容は、包括的なテスト環境の導入、JestとTesting Libraryによるユニットテスト・モックの整備、CI/CDによる自動テストワークフローの追加、チャット・メッセージ処理・音声合成・スライド編集機能の大幅なリファクタリングおよび強化です。また、多言語対応のトーストメッセージやスライド編集用の新規ページ・APIエンドポイントの追加、UIコンポーネントの改良、ユーティリティ関数や型定義の拡張も含まれています。 Changes
Changes(ファイル詳細)
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(エラー/ツール通知等)
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: 成功/エラー応答
Possibly related PRs
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Deploying aituber-kit with
|
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
🔭 Outside diff range comments (1)
src/features/messages/live2dHandler.ts (1)
166-192: 🛠️ Refactor suggestionstartIdleMotion メソッドの堅牢性向上
インターバル内での 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が大幅に改善されました。シンプルなリンクからスタイリングされたボタンへの変更は、以下の点で優れています:
- Next.jsの
Linkコンポーネントを使用してクライアントサイドナビゲーションを最適化- 新しいタブでの開き方と適切なセキュリティ属性(
rel="noopener noreferrer")- Tailwind CSSによる一貫したスタイリング
- アイコンの追加による視覚的なわかりやすさ
既存の
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: ドラッグアンドドロップ機能の実装画像ファイルのドラッグアンドドロップ機能の実装は良いユーザビリティ向上です。ただし、以下の改善点を検討してください:
- ドラッグ中のビジュアルフィードバックがありません
- 複数ファイルドロップ時に最初のファイルだけ処理され、ユーザーへの通知がありません
- 非画像ファイルのドロップ時の処理や通知がありません
以下のように改善することを提案します:
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: テスト全体のカバレッジと構造についてテストファイル全体が適切に構造化されており、各関数の基本機能と特殊ケースを網羅しています。しかし、以下のようなエッジケースのテストを追加することを検討してもよいでしょう:
- 空文字列の処理
- null/undefined値のハンドリング
- 混合文字列(日本語と英語が混在)の正確な変換
それぞれの関数の主要な機能は現在のテストでカバーされていますが、これらのエッジケースを追加することで堅牢性がさらに向上します。
src/features/chat/vercelAIChat.ts (1)
264-284: ツール呼び出し通知の改善提案ツール呼び出し通知の実装は素晴らしいですが、以下の点を考慮することで、さらに改善できる可能性があります:
トースト通知の重複防止: 同じツールが頻繁に呼び出される場合、同一のトースト通知が連続して表示される可能性があります。すでにタグに
toolNameを含めていますが、既存のトーストを更新する、または表示頻度を制限する機能を追加することを検討してください。ツールの状態管理: ツールの開始だけでなく、完了や失敗したときの通知も追加するとユーザー体験が向上するでしょう。
ツールの詳細情報: 特定のツールについて、より詳細な情報をログに記録することを検討してください。これはデバッグや分析に役立ちます。
src/features/lipSync/lipSync.ts (2)
275-285: 停止処理の拡張提案現在の
stopCurrentPlaybackメソッドは基本的な停止機能を提供していますが、以下の拡張を検討することでさらに機能を向上させることができます:
- オプションのコールバックパラメータを追加して、停止完了時や停止できなかった場合に呼び出せるようにする
- 現在の再生状態(再生中かどうか)を返す
isPlayingようなメソッドの追加- フェードアウト効果を適用してから停止する機能(より自然な停止体験のため)
これらの拡張により、より柔軟で洗練された音声制御が可能になるでしょう。
1-286: LipSyncクラス全体についてLipSyncクラスへの変更は、オーディオ再生の管理を大幅に改善しています。現在再生中のソースを明示的に追跡し、適切に停止する機能を追加することで、以下のような利点があります:
- リソースの適切な管理(不要になったオーディオソースを適切に解放)
- ユーザー体験の向上(新しい音声再生前に古い再生を適切に停止)
- 他のコンポーネント(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エラー処理の冗長ログとキャンセル制御
console.errorが多発すると本番でノイズになるため、自前ロガーや Sentry 等に統一しても良いでしょう。- ページ離脱時・ネットワーク切断時に未完了リクエストが残ります。
AbortControllerを使ってタイムアウト/キャンセル対応すると UX が向上します。src/__tests__/features/chat/handlers.test.ts (1)
1-203: テストカバレッジを拡充する余地があります現在のテストは
handleSendChatFnとprocessAIResponseの基本的なシナリオをカバーしていますが、以下のようなケースも追加することでテスト範囲を拡張できます:
- スライドモードが有効な場合の処理
- 実時間APIモードが有効な場合の処理
- さまざまなエラーシナリオでの
processAIResponseの動作- タイムスタンプが含まれるユーザーメッセージの処理
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内の条件分岐を簡潔に
findIndexとmapで二度走査しています。オプショナルチェーンを使った一発書きや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()でデフォルトエクスポートをオブジェクトにすげ替えていますが、
settingsStoreにgetState以外のプロパティ/メソッドが存在する場合、それらが未定義になりテストが落ちる可能性があります。
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-10とlog_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が再レンダリングを誘発タスク追加ごとに
isSpeakingをtrueに設定していますが、既に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: 文章抽出・コードブロック判定ロジックが重複しており保守コストが高いです
speakMessageHandlerとprocessAIResponseにほぼ同一の
- コードブロック検出 (
CODE_DELIMITER)- 感情タグ抽出
- 文抽出・音声合成トリガ
の巨大ループが存在します。バグ修正や仕様変更時に二重対応が必要となり、将来的な不整合の温床になります。
- 共通の ストリームパーサー (ジェネレータ/クラス) を
src/utils/chatParser.ts等に切り出す- その結果を
handleSpeakAndStateUpdateに流し込む
の2段構成にすればロジック共有・テスト容易化が図れます。Also applies to: 298-526
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonpublic/images/icons/external-link.svgis 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'が追加されました
ToastPropsのtypeプロパティに'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.mdとscripts.jsonの両方のファイルが存在するディレクトリのみを有効なスライドフォルダとして返すように改善されています。これにより、スライドエディタなどの機能が必要なファイルを持つフォルダのみを対象とすることができ、ユーザーエクスペリエンスが向上します。エラー処理も考慮されており、堅牢な実装になっています。
src/components/messageInput.tsx (2)
31-31: 新しいプロパティがコンポーネントに追加されました。
isSpeakingプロパティが追加されたことで、音声合成中の状態を適切に管理できるようになりました。
161-161: マイクボタンの無効化条件が改善されました。音声合成中(
isSpeaking)またはチャット処理中(chatProcessing)の場合にマイクボタンを無効化することで、ユーザー体験が向上します。音声合成中にマイク入力を受け付けないようにすることで、音声の衝突を防止できます。src/components/settings/messageReceiver.tsx (1)
3-3: 必要なインポートが追加されました。Next.jsの
ImageとLinkコンポーネントをインポートして、モダンなリンク実装が可能になります。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が大幅に改善されています。特筆すべき点:
- フレックスコンテナを使用して選択ボックスと編集ボタンを横並びに配置
- スライド未選択時のプレースホルダーオプションを追加
- 選択されたスライドがある場合のみ表示される編集ボタンを追加
- 新しいタブでスライドエディタを開くための適切なリンク設定
これらの変更により、ユーザーエクスペリエンスが向上し、スライド管理が容易になります。
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関数のテストケースが網羅的です。以下の重要なケースがテストされています:
- 英語を日本語読みに変換する基本機能
- 英語が含まれていない場合の挙動
- 特定の重要な単語(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レスポンスに明確な型定義が追加されました。オプショナルフィールドとして
content、message、errorが含まれており、様々なレスポンスシナリオに対応できます。
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 メソッドの改善点:
- live2dViewer.speak の onFinish コールバックを使用して再生完了を検知
- フォールバックタイマーの追加による堅牢性の向上
- エラーハンドリングの強化
これらの変更により、音声再生が適切に完了または中断された際の処理が改善されています。実装は堅牢で、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: ストリーミングレスポンス処理のテストが詳細ですストリーミングレスポンスの処理をテストするケースが詳細に実装されています。特に:
- モックリーダーの適切な設定
- デコード処理のシミュレーション
- 会話IDの状態更新の検証
- 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 typetool追加に伴う一貫性確認をお願いします
"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, + })
| 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, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lastMessage.content が配列の場合に文字列連結で壊れます
upsertMessage で既存メッセージに追記する際、lastMessage.content を文字列と仮定して + text を行っています。しかしユーザ送信時に画像添付をした場合など、content は配列 ({type:'text'|'image', ...}[]) になり得ます。このまま実行すると
- 型エラーでクラッシュする
- 配列→文字列への暗黙変換で情報が失われる
いずれかが起こります。少なくとも型ガードを挟み、配列時はテキスト要素のみを抽出して連結する処理にしてください。
- 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 ?? ''There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 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 を活用できます現在の実装は
findIndex→mapで 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 が向上します
isDirtyがtrueのままブラウザの戻る/更新やルーティング遷移を行うと変更内容が失われます。
beforeunloadや Next.js のrouter.events.on('routeChangeStart', ...)でガードを掛け、確認ダイアログを出すと誤操作を防げます。実装を検討ください。src/features/stores/home.ts (2)
156-201: デバウンス処理中にクリーンアップが保証されていません。現在のデバウンス実装では、ページ遷移やコンポーネントのアンマウント時にタイマーがクリーンアップされない可能性があります。これによりメモリリークやバックグラウンドでの不要な処理が続く可能性があります。
zustandのStoreApiを利用して、ストアの破棄時にタイマーをクリーンアップする仕組みを追加することを検討してください:// ストア定義部分(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)
📒 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
TheNEXT_PUBLIC_INCLUDE_SYSTEM_MESSAGES_IN_CUSTOM_APIsetting 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.fetchとglobal.TextDecoderをオリジナルの値に戻していて、テスト間の副作用を防ぐ対策がされています。これは良い実装です。
173-196: ストリーミングレスポンスのテストが適切に実装されていますストリーミングレスポンスを非常によくテストしています。モックからのデータをデコードし、
ReadableStreamを介してデータを読み取り、結果を正しく検証しています。
198-236: エラーハンドリングのテストが包括的ですストリーミング中のエラーケースを適切にテストし、エラー時にトーストが表示されることを確認しています。エラーメッセージの国際化も確認されています。
125-150: カスタムAPIモードのシステムメッセージフィルタリングテストが重要です
includeSystemMessagesInCustomApiの設定に基づいてシステムメッセージをフィルタリングする機能は重要であり、適切にテストされています。src/pages/slide-editor/[slideName].tsx (2)
596-604: 初期状態で「元に戻す」ボタンが無効にならないケースがあります
handleRevertでcurrentScript/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への追加が済んでいるか確認をお願いします。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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)
📒 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: デバウンス保存機能の実装がとても良いです!チャットログの変更をデバウンスして効率的にバッチ保存する実装は非常に優れています。以下の点が特に評価できます:
- 2秒間のデバウンス処理によりAPI呼び出し回数を削減
- 新規または更新されたメッセージのみを保存することでデータ転送量を削減
- 詳細なログ出力によりデバッグが容易
- エラーハンドリングが適切に実装されている
この実装により、パフォーマンスとユーザー体験の両方が向上します。
47-55: リセット処理の共通化は良い改善です
resetSaveState関数を作成して、チャットログがクリアされた際のリセット処理を一箇所に集約したのは良い改善です。これにより:
- コードの重複が削減される
- ロジックの不整合のリスクが減少する
- 保守性が向上する
今後も同様のアプローチで処理を共通化することをお勧めします。
40-45: 状態変数の役割を説明するコメントが適切です保存処理のための状態変数に対するコメントが適切で分かりやすいです。特に:
- デバウンスのための変数
- 最後に保存したログの長さを記録する変数
- 新規ファイル作成のためのフラグ
これらのコメントにより、コードの意図が明確になり、他の開発者が理解しやすくなります。
src/features/chat/handlers.ts (4)
26-41: 感情タグ抽出の改善は素晴らしいです感情タグの抽出ロジックを改善したことで、以前の問題点が解決されています:
- 先頭のスペースを無視して感情タグを検出
- タグが見つかった場合の適切な文字列スライス処理
- 戻り値のトリミング処理の追加
これにより、より堅牢な感情タグ抽出が可能になりました。
71-112: 発話とステート更新の共通関数化は優れた改善です
handleSpeakAndStateUpdate関数の導入により、以下の点が改善されています:
- コードの重複が削減される
- 発話ロジックが一箇所に集約され、変更が容易になる
- 感情タグの処理と発話不要な文字列のスキップ処理が適切に実装されている
この関数を使用することで、コードの保守性と可読性が向上しています。
801-866: RealtimeAPI用のセッションID管理を改善しました
handleReceiveTextFromRtFn関数において、セッションIDのライフサイクル管理が改善されています:
- 関数がファクトリーパターンを使用して状態を保持
- セッションIDを適切なタイミングで生成・再利用・リセット
- レスポンス完了時にセッションIDをリセットする処理の追加
この実装により、複数の音声応答イベントでセッションの一貫性が保たれます。
160-165: セーブデバウンスタイマーの再設定が適切に行われていますタイマーのクリアと再設定のロジックが適切に実装されています:
if (saveDebounceTimer) { clearTimeout(saveDebounceTimer) } saveDebounceTimer = setTimeout(() => { // ... }, SAVE_DEBOUNCE_DELAY)これにより、不要なタイマーがメモリリークを起こす可能性を防いでいます。
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
コードブロック処理のロジックが複雑すぎます
コードブロック処理のロジックが非常に複雑で、多数のネストされた条件分岐があります。これは保守性の観点から問題となる可能性があります:
- 複雑なif-elseのネストにより可読性が低下
- 条件分岐が多く、全てのケースを把握するのが困難
- 将来的な変更が難しくなる可能性がある
このロジックを小さな関数に分割し、責任範囲を明確にすることを推奨します:
// コードブロック処理を別関数に抽出する例
+ const processCodeBlock = (content, codeBlockContent) => {
+ // コードブロック関連の処理ロジック
+ return { processedContent, updatedCodeBlockContent, isCodeBlockEnded };
+ }
+
+ const processTextWithEmotion = (text) => {
+ // 感情タグと文の抽出処理
+ return { processedText, extractedEmotion };
+ }これにより、メイン関数のフローがよりシンプルになり、個々の責任を持つ関数をテストしやすくなります。
Committable suggestion skipped: line range outside the PR's diff.
Feature/update document
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
.cursor/rules/development-process.mdc (1)
1-5: フロントマターの情報をより充実させることを推奨しますフロントマターにおいて、
descriptionとglobsが空になっています。このドキュメントの目的や適用範囲をより明確にするために、これらのフィールドに適切な情報を追加することを検討してください。--- -description: +description: プロジェクトの開発プロセスとガイドラインを定義した文書。すべての開発者がタスク実行時に従うべき構造化されたフレームワークを提供します。 -globs: +globs: ["**/*.{js,jsx,ts,tsx}"] alwaysApply: false ---
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 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管理、コード規約、プロジェクト構造、タスクガイド、技術スタック)と合わせて、一貫性のある開発フレームワークを構築しています。本番リリースに向けて、このような明確な開発プロセスとガイドラインの導入は非常に価値があります。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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)
⛔ Files ignored due to path filters (2)
website/document/public/images/slide_fa3sf.pngis excluded by!**/*.pngwebsite/document/public/images/slide_vhy4s.pngis 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
新機能
リファクタリング
バグ修正
テスト
ドキュメント
ライセンス
Summary by CodeRabbit
新機能
改善
多言語対応
ドキュメント
NEXT_PUBLIC_INCLUDE_SYSTEM_MESSAGES_IN_CUSTOM_APIの追加と関連ドキュメント整備バグ修正
テスト・CI/CD