feat(web): add player name, freeform input, and unified settings modal

- Player name: stored in localStorage, injected into Architect/Writer/InsertBeat
  prompts so NPCs address the player by name, displayed in dialogue UI
- Freeform input: compact button at choice nodes expands to text input, LLM
  classifier routes to insert-beat (interactive NPC response) or change-scene
- SettingsModal: unified panel merging player name, voice toggle (with
  collapsible TTS key section), replacing the old TtsKeyModal
- Insert-beat upgrade: prompt now requires NPC reaction when characters are
  present, shared by both freeform and Vision paths
- IME guard: isComposing check on freeform input to prevent CJK mid-composition
  submission

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuanzonghao
2026-06-07 12:03:13 +08:00
parent b0b5630a25
commit ae3dd17e6b
11 changed files with 897 additions and 77 deletions
+23
View File
@@ -280,6 +280,12 @@ export type Session = {
* share one aspect ratio. Absent → "landscape" (back-compat).
*/
orientation?: Orientation;
/**
* Optional player-chosen display name. When set, NPC dialogue will address
* the player by this name instead of the generic "你". Stored client-side
* only (localStorage); never persisted server-side.
*/
playerName?: string;
};
// ──────────────────────────────────────────────────────────────────────
@@ -372,6 +378,8 @@ export type StartRequest = {
* (default) keeps 16:9 widescreen. Locked for the whole session.
*/
orientation?: Orientation;
/** Optional player display name — see Session.playerName. */
playerName?: string;
};
// /api/parse-style-image — vision LLM extracts a textual painting-style
@@ -458,6 +466,21 @@ export type VisionResponse = {
classify: VisionClassify;
};
// /api/classify-freeform — classifies a player's freeform text input
// into one of three paths: match an existing choice, insert a beat
// in-scene, or trigger a scene change.
export type FreeformClassifyRequest = {
session: Session;
freeformText: string;
};
export type FreeformClassify = "insert-beat" | "change-scene";
export type FreeformClassifyResponse = {
classify: FreeformClassify;
freeformAction: string;
};
// /api/insert-beat — generates a single transient beat in response to
// a freeform vision action. Does NOT regenerate the image.
export type InsertBeatRequest = {