Skip to content

GeminiからNeural2に戻した#5597

Closed
TinyKitten wants to merge 9 commits intodevfrom
feature/tts-custom-prompt
Closed

GeminiからNeural2に戻した#5597
TinyKitten wants to merge 9 commits intodevfrom
feature/tts-custom-prompt

Conversation

@TinyKitten
Copy link
Member

@TinyKitten TinyKitten commented Mar 14, 2026

Summary

  • TTS合成をGemini TTSからCloud Text-to-Speech Neural2に切り替え
  • プロンプト機能を全面削除(Cloud Function、クライアント双方)
  • ボイス名のデフォルト値をJA/EN個別に定数化(JA: ja-JP-Neural2-B、EN: en-US-Neural2-F
  • Cloud Function側でボイス名・テキストのバリデーション(空白トリム、バイト長制限、SSMLタグ除去後の空テキスト拒否)を追加
  • getTtsConfig にエラー時キャッシュフォールバックを導入
  • wrapPhoneme にXMLエスケープを追加しSSMLインジェクションを防止
  • TrainType/TrainTypeNestedからnameIpaを削除(GraphQLスキーマ未実装のため)
  • trainType未設定時のLocalフォールバックが効くようnull合体演算子を論理ORに修正

Test plan

  • npm run typecheck パス
  • npm run lint パス
  • npm test 全137 suites / 1281 tests パス
  • チューニング設定画面からTTSボイス設定UIが削除されていることを確認
  • Neural2ボイスでTTS再生が正常に動作することを確認
  • TrainTypeにnameIpaを参照していた箇所でクラッシュしないことを確認

🤖 Generated with Claude Code

Summary by CodeRabbit

リリースノート

  • 新機能

    • 駅名と路線名の音声読み上げに音韻表記(IPA)データを追加し、より正確な発音に対応しました
  • 改善

    • 音声合成システムをアップグレード
    • 音声設定インターフェースを簡素化
    • 複数言語の音声プロンプト設定を追加

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b8a2cf9-69c3-4a07-814f-abd27602b536

📥 Commits

Reviewing files that changed from the base of the PR and between 7bcc921 and 9f09740.

📒 Files selected for processing (6)
  • functions/src/funcs/tts.ts
  • src/components/TuningSettings.tsx
  • src/constants/tts.ts
  • src/hooks/useTTS.ts
  • src/store/atoms/tuning.ts
  • src/utils/ttsSpeechFetcher.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/useTTS.ts
  • src/store/atoms/tuning.ts

📝 Walkthrough

Walkthrough

TTS機能をGeminiからNeural2に移行し、IPA音声ガイド対応を追加。GraphQLスキーマにnameIpaフィールドを追加、TTS設定のデフォルト値を更新、音声選択UI削除、音素ラッピング機能実装。翻訳キーとテスト用フィクスチャも更新。

Changes

Cohort / File(s) Summary
翻訳キー追加
assets/translations/en.json, assets/translations/ja.json
TTS プロンプト用の新しい翻訳キー(tuningItemTTSJaPrompt、tuningItemTTSEnPrompt)を追加。
TTS バックエンド実装
functions/src/funcs/tts.ts
Gemini ベースの TTS フローを Neural2 に置き換え。アクセストークン取得、キャッシング機構の追加、デフォルト音声定数の導入、入力検証の強化、エラー処理の改善。
GraphQL 型定義拡張
src/@types/graphql.d.ts
Line、LineNested、Station、StationNested およびそれらの関連フラグメント型に nameIpa フィールドを追加。複数のクエリとフラグメント定義に一貫して適用。
TTS 定数・設定
src/constants/tts.ts, src/constants/index.ts, src/store/atoms/tuning.ts
DEFAULT_JA_TTS_VOICE_NAME(ja-JP-Neural2-B)と DEFAULT_EN_TTS_VOICE_NAME(en-US-Neural2-F)定数を追加。tuningState のデフォルト音声値を更新。
TTS UI 変更
src/components/TuningSettings.tsx
音声選択UI(ピッカー、ActionSheetIOS、関連状態)を削除。AsyncStorage ロード時に既存値を保持するように変更。
TTS フック・テキスト生成
src/hooks/useTTS.ts, src/hooks/useTTSText.ts
音声名の値を保持・直接渡す。wrapPhoneme ユーティリティを使用して nameIpa に基づく音素ラッピングを追加。複数の TTS テンプレートで音素ラッピングを適用。
音素・IPA ユーティリティ
src/utils/phoneme.ts, src/utils/ttsSpeechFetcher.ts
wrapPhoneme(nameRoman と nameIpa を SSML 音素タグにラッピング)を新規実装。ttsSpeechFetcher で音声名を正規化・キャッシュキー生成に適用。
その他ユーティリティ・修正
src/utils/jr.ts, src/lib/graphql/queries.ts
JR 線オブジェクトに nameIpa フィールドを追加。GraphQL フラグメント(LINE_NESTED_FRAGMENT、STATION_FRAGMENT など)に nameIpa を追加。
テスト用フィクスチャ・ファクトリ
src/__fixtures__/line.ts, src/utils/__fixtures__/search.ts, src/utils/test/factories.ts, src/hooks/useGetStationsWithTermination.test.tsx, src/hooks/useRefreshStation.test.tsx, src/hooks/useTransferLinesFromStation.test.tsx
複数のテストファイルおよびフィクスチャに nameIpa フィールド(null で初期化)を追加。Station、Line、LineNested オブジェクトのテストデータ構造を更新。

Sequence Diagram(s)

sequenceDiagram
    participant Client as クライアント
    participant Auth as 認証サービス
    participant Cache as キャッシュ層
    participant Neural2 as Neural2 TTS API

    Client->>Client: 入力検証(SSML、音声名)
    Client->>Auth: getAccessToken() でトークン取得
    Auth-->>Client: accessToken
    Client->>Cache: キャッシュ確認(ハッシュペイロード)
    alt キャッシュヒット
        Cache-->>Client: キャッシュされた音声データ
    else キャッシュミス
        Client->>Neural2: synthesizeWithNeural2(accessToken, ssml, 音声名)
        Neural2-->>Client: 合成済み音声
        Client->>Cache: 結果をキャッシュに発行(並行実行)
    end
    Client-->>Client: 音声データを返す
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 新しい音声ガイド、Neural2 で華麗に
IPA の音素、ラッピング コード
翻訳も追加で、世界に広がる
キャッシュも改善、速度も向上
ウサギも嬉しい、TTS の進化!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRタイトル「GeminiからNeural2に戻した」は、主要な変更点(TTS処理エンジンをGeminiからNeural2に切り替え)を明確に要約しており、変更セットの中核をよく表現しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/tts-custom-prompt
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@TinyKitten TinyKitten changed the title TTSカスタムプロンプト・ボイス名デフォルト化・TrainType nameIpa削除 TTSカスタムプロンプト・ボイス名デフォルト化 Mar 14, 2026
@TinyKitten TinyKitten changed the title TTSカスタムプロンプト・ボイス名デフォルト化 GeminiからNeural2に戻した Mar 14, 2026
@TinyKitten TinyKitten self-assigned this Mar 14, 2026
Copy link
Contributor

@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: 3

🧹 Nitpick comments (2)
src/components/TuningSettings.tsx (1)

87-109: フロントエンドとバックエンドでデフォルトプロンプトが重複しています。

DEFAULT_JA_PROMPTDEFAULT_EN_PROMPTfunctions/src/funcs/tts.tsJA_TTS_PROMPT / EN_TTS_PROMPT と同一内容です。将来の変更時に同期が取れなくなるリスクがあります。

共有定数ファイルに切り出すか、サーバー側のみで管理してクライアントは空文字を送信する設計を検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/TuningSettings.tsx` around lines 87 - 109, DEFAULT_JA_PROMPT
and DEFAULT_EN_PROMPT on the frontend duplicate JA_TTS_PROMPT and EN_TTS_PROMPT
from the backend, causing maintenance drift; remove the duplicated constants
from the React component (symbols DEFAULT_JA_PROMPT, DEFAULT_EN_PROMPT) and
instead import the canonical prompts from a single shared module (create/export
JA_TTS_PROMPT and EN_TTS_PROMPT in a shared constants file) or change the client
to send empty strings and let the server (JA_TTS_PROMPT / EN_TTS_PROMPT) supply
defaults; update usages in TuningSettings.tsx and any places that referenced the
removed constants to use the shared exports or server-driven defaults.
functions/src/funcs/tts.ts (1)

50-72: フロントエンドとのプロンプト重複(前述)。

JA_TTS_PROMPTEN_TTS_PROMPTsrc/components/TuningSettings.tsxDEFAULT_JA_PROMPT/DEFAULT_EN_PROMPTと同一です。サーバー側を正とし、クライアントはカスタムプロンプト未設定時に空文字を送信する設計を検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/src/funcs/tts.ts` around lines 50 - 72, JA_TTS_PROMPT and
EN_TTS_PROMPT duplicate DEFAULT_JA_PROMPT/DEFAULT_EN_PROMPT in the frontend;
remove the duplication by making the server the single source of truth: delete
or stop using JA_TTS_PROMPT/EN_TTS_PROMPT in funcs/tts.ts and instead import the
canonical prompt constants from a shared module (or export the server constants
so they can be referenced), and update the frontend (TuningSettings.tsx) to send
an empty string when the user has not set a custom prompt so the server-side
DEFAULT_JA_PROMPT/DEFAULT_EN_PROMPT are used. Ensure references to
JA_TTS_PROMPT, EN_TTS_PROMPT, DEFAULT_JA_PROMPT, and DEFAULT_EN_PROMPT are
updated accordingly across code so only the server defaults remain
authoritative.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@functions/src/funcs/tts.ts`:
- Around line 31-48: The implementation of getTtsConfig uses a TTL cache with
error fallback (checks ttsConfigCache + TTS_CONFIG_CACHE_TTL_MS and only returns
stale data on error) but the PR text calls it "stale-while-revalidate"; either
update the code to true stale-while-revalidate or change the docs—pick one. If
you choose to implement stale-while-revalidate: modify getTtsConfig to
immediately return ttsConfigCache.data when present (even if expired) and kick
off a background async refresh to re-fetch and update ttsConfigCache (ensure
errors in background refresh are caught and do not fail the immediate return).
If you choose to update docs: change the description to say "TTL cache with
error fallback (returns cached data on fetch error)" and mention ttsConfigCache
and TTS_CONFIG_CACHE_TTL_MS so behavior matches getTtsConfig.

In `@src/components/TuningSettings.tsx`:
- Around line 435-440: The input uses value={settings.ttsJaPrompt ||
DEFAULT_JA_PROMPT} which forces the default to reappear when a user clears the
field; change the component to render the actual stored value (e.g., use
settings.ttsJaPrompt ?? '' or an empty string fallback) and keep
DEFAULT_JA_PROMPT only as placeholder so the user can leave the field empty;
update handleJaPromptChange to store empty string (or null) when cleared and
update the save/submit logic in the same component so that an empty string is
converted to null/undefined (or a dedicated useServerDefault flag) before
sending to the server, allowing the server-side default to be used.

In `@src/utils/phoneme.ts`:
- Around line 1-8: The wrapPhoneme function is inserting raw nameRoman and
nameIpa into SSML which can produce invalid XML when values contain characters
like & < > " '; modify wrapPhoneme to XML-escape nameRoman for element text
content and XML-escape nameIpa for the ph attribute before building the string
(e.g., add or reuse a helper like escapeXml or escapeXmlAttr and call it inside
wrapPhoneme), ensure null/undefined handling remains the same, and return the
escaped phoneme string so SSML is always valid.

---

Nitpick comments:
In `@functions/src/funcs/tts.ts`:
- Around line 50-72: JA_TTS_PROMPT and EN_TTS_PROMPT duplicate
DEFAULT_JA_PROMPT/DEFAULT_EN_PROMPT in the frontend; remove the duplication by
making the server the single source of truth: delete or stop using
JA_TTS_PROMPT/EN_TTS_PROMPT in funcs/tts.ts and instead import the canonical
prompt constants from a shared module (or export the server constants so they
can be referenced), and update the frontend (TuningSettings.tsx) to send an
empty string when the user has not set a custom prompt so the server-side
DEFAULT_JA_PROMPT/DEFAULT_EN_PROMPT are used. Ensure references to
JA_TTS_PROMPT, EN_TTS_PROMPT, DEFAULT_JA_PROMPT, and DEFAULT_EN_PROMPT are
updated accordingly across code so only the server defaults remain
authoritative.

In `@src/components/TuningSettings.tsx`:
- Around line 87-109: DEFAULT_JA_PROMPT and DEFAULT_EN_PROMPT on the frontend
duplicate JA_TTS_PROMPT and EN_TTS_PROMPT from the backend, causing maintenance
drift; remove the duplicated constants from the React component (symbols
DEFAULT_JA_PROMPT, DEFAULT_EN_PROMPT) and instead import the canonical prompts
from a single shared module (create/export JA_TTS_PROMPT and EN_TTS_PROMPT in a
shared constants file) or change the client to send empty strings and let the
server (JA_TTS_PROMPT / EN_TTS_PROMPT) supply defaults; update usages in
TuningSettings.tsx and any places that referenced the removed constants to use
the shared exports or server-driven defaults.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0891cc21-939d-478f-8b32-c59005c676ff

📥 Commits

Reviewing files that changed from the base of the PR and between e02c6f7 and 57f9806.

📒 Files selected for processing (21)
  • assets/translations/en.json
  • assets/translations/ja.json
  • functions/src/funcs/tts.ts
  • src/@types/graphql.d.ts
  • src/__fixtures__/line.ts
  • src/components/TuningSettings.tsx
  • src/constants/asyncStorage.ts
  • src/constants/index.ts
  • src/constants/tts.ts
  • src/hooks/useGetStationsWithTermination.test.tsx
  • src/hooks/useRefreshStation.test.tsx
  • src/hooks/useTTS.ts
  • src/hooks/useTTSText.ts
  • src/hooks/useTransferLinesFromStation.test.tsx
  • src/lib/graphql/queries.ts
  • src/store/atoms/tuning.ts
  • src/utils/__fixtures__/search.ts
  • src/utils/jr.ts
  • src/utils/phoneme.ts
  • src/utils/test/factories.ts
  • src/utils/ttsSpeechFetcher.ts

Comment on lines +435 to +440
onChangeText={handleJaPromptChange}
value={settings.ttsJaPrompt || DEFAULT_JA_PROMPT}
placeholder={DEFAULT_JA_PROMPT}
placeholderTextColor="#999"
multiline
/>
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 14, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

プロンプトを空にできないUXの問題。

settings.ttsJaPrompt || DEFAULT_JA_PROMPT の論理により、ユーザーがフィールドをクリアすると即座にデフォルトプロンプトが表示されます。サーバー側のデフォルトを使用したい場合、ユーザーは空欄にできません。

意図的な設計であれば問題ありませんが、「サーバーデフォルトを使う」オプションが必要な場合は検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/TuningSettings.tsx` around lines 435 - 440, The input uses
value={settings.ttsJaPrompt || DEFAULT_JA_PROMPT} which forces the default to
reappear when a user clears the field; change the component to render the actual
stored value (e.g., use settings.ttsJaPrompt ?? '' or an empty string fallback)
and keep DEFAULT_JA_PROMPT only as placeholder so the user can leave the field
empty; update handleJaPromptChange to store empty string (or null) when cleared
and update the save/submit logic in the same component so that an empty string
is converted to null/undefined (or a dedicated useServerDefault flag) before
sending to the server, allowing the server-side default to be used.

Copy link
Member Author

Choose a reason for hiding this comment

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

みてるコードかなり古くないすか

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current state of the relevant lines in TuningSettings.tsx
grep -n "ttsJaPrompt\|ttsEnPrompt\|DEFAULT_JA_PROMPT\|DEFAULT_EN_PROMPT" src/components/TuningSettings.tsx

Length of output: 493


@TinyKitten 現在のブランチを確認しましたが、Line 436・455 に同じコードがまだ存在しています:

436: value={settings.ttsJaPrompt || DEFAULT_JA_PROMPT}
455: value={settings.ttsEnPrompt || DEFAULT_EN_PROMPT}

レビューコメントは現在のコードに対して有効です。ご確認いただけますか?


🧠 Learnings used
Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 5533
File: src/components/TuningSettings.tsx:175-195
Timestamp: 2026-03-07T12:02:14.532Z
Learning: In `src/components/TuningSettings.tsx` (TrainLCD/MobileApp), the TTS voice name picker (ttsEnVoiceName) in TuningSettings is an internal developer-facing feature. Adding a "not specified" reset option is intentionally omitted; do not flag the absence of a reset/unset option as an issue.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 5533
File: functions/src/funcs/tts.ts:241-260
Timestamp: 2026-03-07T12:01:15.763Z
Learning: In `functions/src/funcs/tts.ts` (TrainLCD/MobileApp), the Japanese TTS voice name (`jaVoiceName`) is intentionally hardcoded to `'ja-JP-Standard-B'` and is not configurable via Firestore config or request parameters. Only the English voice name is user-selectable. Do not flag this as a bug in future reviews.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 5533
File: src/hooks/useTTS.ts:83-95
Timestamp: 2026-03-07T04:42:17.516Z
Learning: In src/hooks/useTTS.ts (TrainLCD/MobileApp), the AsyncStorage hydration of ttsJaVoiceName/ttsEnVoiceName in a useEffect runs after mount, but the first speechWithText() call always requires an auth token fetch and a network call to the TTS API, which takes far longer than AsyncStorage.getItem. Therefore, the race condition where the first utterance uses the default voice practically never occurs and is not a concern worth addressing.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 4448
File: functions/src/funcs/tts.ts:214-232
Timestamp: 2025-08-11T20:23:23.071Z
Learning: PR `#4448` in TrainLCD/MobileApp is specifically about AI feedback summarization and labeling functionality, not about TTS (Text-to-Speech) improvements. The TTS code changes appear to be part of a modularization refactoring but are not the focus of this PR.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 5464
File: android/app/src/main/java/me/tinykitten/trainlcd/MainApplication.kt:33-37
Timestamp: 2026-02-27T07:30:06.268Z
Learning: TinyKitten prefers not to modify code taken from official templates (e.g., Expo, React Native) to maintain alignment with upstream and ease future updates.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 0
File: :0-0
Timestamp: 2025-08-11T21:13:39.083Z
Learning: TinyKitten prefers concise code reviews for the TrainLCD/MobileApp repository. Keep review comments brief and to the point.

Learnt from: TinyKitten
Repo: TrainLCD/MobileApp PR: 5277
File: src/components/NowHeader.tsx:182-193
Timestamp: 2026-02-14T00:19:25.989Z
Learning: In TrainLCD/MobileApp, Station objects are guaranteed to have non-null latitude and longitude. When reviewing code that uses Station.coordinates in TS/TSX files, assume valid values (no null/undefined checks needed for coordinates) and rely on type-safe access. This applies broadly to TypeScript files that interact with Station data (e.g., UI components, data layers); use a broad pattern to cover related files.

TinyKitten and others added 2 commits March 15, 2026 06:28
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@TinyKitten TinyKitten closed this Mar 14, 2026
@TinyKitten TinyKitten deleted the feature/tts-custom-prompt branch March 14, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant