返回 JoyAI-Echo
types.ts
1 export type Role = "user" | "assistant" | "tool" | "system";
2
3 /** "trace" rows are intermediate agent breadcrumbs (tool-call hints,
4 * progress pings) that should not be rendered as conversational replies. */
5 export type MessageKind = "message" | "trace";
6
7 /** One image attached to a UIMessage.
8 *
9 * ``url`` can arrive in three different shapes, which the bubble renders
10 * identically:
11 * - A ``data:image/...;base64,...`` URL generated by the Composer for the
12 * optimistic preview of an in-flight user turn. Self-contained, no
13 * lifecycle.
14 * - A signed ``/api/media/...`` URL attached to a historical user turn by
15 * the backend on session replay. Safe to drop into an ``<img src>``.
16 * - Absent. The backend couldn't resolve a stored path (file moved,
17 * deleted, or pre-media-persistence session). The bubble shows a
18 * placeholder tile with ``name`` as the label.
19 */
20 export interface UIImage {
21 url?: string;
22 name?: string;
23 }
24
25 export interface UIVideo {
26 url: string;
27 name?: string;
28 }
29
30 export interface UIQuestionCard {
31 id: string;
32 question: string;
33 options: { label: string }[];
34 allowCustom?: boolean;
35 answered?: string | null;
36 }
37
38 export interface UIMessage {
39 id: string;
40 role: Role;
41 content: string;
42 kind?: MessageKind;
43 isStreaming?: boolean;
44 createdAt: number;
45 /** For trace rows: each individual hint line, so consecutive hints can
46 * render as a single collapsible group. */
47 traces?: string[];
48 /** User turn: optimistic blob URLs for preview. Replay: placeholder chips. */
49 images?: UIImage[];
50 /** Assistant turn: resolved video shot URLs rendered inline under the text. */
51 videos?: UIVideo[];
52 /** Structured question cards embedded in an assistant message. */
53 questions?: UIQuestionCard[];
54 /** Human approval gate for VLM-selected image/audio memories. */
55 memoryReview?: MemoryReview;
56 /** Batch id for persisting question-card answers over WebSocket. */
57 questionBatchId?: string;
58 /** Top-of-turn wait indicator (TypingDots); cleared on ``stream_end resuming:false``. */
59 turnWaiting?: boolean;
60 }
61
62 /** Wire shape for question cards on inbound WS / session replay. */
63 export interface WireQuestionCard {
64 id: string;
65 question: string;
66 options: Array<string | { label: string }>;
67 allow_custom?: boolean;
68 allowCustom?: boolean;
69 answered?: string | null;
70 status?: string;
71 }
72
73 export interface WireMediaRef {
74 url: string;
75 name?: string;
76 }
77
78 export type MemoryReviewStatus =
79 | "awaiting_method"
80 | "selecting"
81 | "awaiting_review"
82 | "reselecting"
83 | "approved"
84 | "error";
85
86 export interface MemorySelection {
87 memory_id: string;
88 /** Human-readable label for UI; falls back to memory_id when absent. */
89 display_name?: string;
90 kind: "character" | "previous_shot";
91 candidate_index: number;
92 frame_index: number;
93 timestamp_sec: number;
94 confidence: number;
95 visual_status: string;
96 reasoning: string;
97 source_shot_id: number;
98 audio_source_shot_id?: number | null;
99 image: WireMediaRef;
100 audio?: WireMediaRef | null;
101 }
102
103 /** Prefer display_name for UI labels; keep memory_id as stable fallback. */
104 export function memoryDisplayName(
105 memory: { display_name?: string | null; memory_id?: string; id?: string },
106 ): string {
107 const label = memory.display_name?.trim();
108 if (label) return label;
109 return (memory.memory_id || memory.id || "").trim();
110 }
111
112 export interface MemoryReviewAttempt {
113 attempt: number;
114 rejected_candidate_indices: number[];
115 selections: MemorySelection[];
116 updated_at: string;
117 }
118
119 export interface MemoryReview {
120 review_id: string;
121 shot_id: number;
122 status: MemoryReviewStatus;
123 attempt: number;
124 candidate_count: number;
125 rejected_candidate_indices: number[];
126 selections: MemorySelection[];
127 selection_mode?: "manual" | "vlm" | null;
128 required_memory_ids?: string[];
129 manual_selected_ids?: string[];
130 /** References the user chose to keep; an empty list is valid. */
131 retained_memory_ids?: string[];
132 /** Generated shot video used for VLM and manual frame selection. */
133 source_video?: WireMediaRef | null;
134 history: MemoryReviewAttempt[];
135 error?: string | null;
136 updated_at: string;
137 }
138
139 export interface GenerationMemory {
140 id: string;
141 /** Stable Memory Workspace asset id used to restore a shot's slot draft. */
142 workspace_asset_id?: string;
143 /** Human-readable label for UI; falls back to id when absent. */
144 display_name?: string;
145 image: WireMediaRef;
146 audio?: WireMediaRef | null;
147 metadata: {
148 source?: string;
149 visual_status?: string;
150 source_shot_id?: number | null;
151 frame_index?: number | null;
152 timestamp_sec?: number | null;
153 confidence?: number | null;
154 audio_source_shot_id?: number | null;
155 audio_workspace_asset_id?: string;
156 reference_type?: MemoryReferenceType;
157 reference_label?: string;
158 identity_ids?: string[];
159 profile_text?: string;
160 };
161 }
162
163 export type MemoryWorkspaceSource = "automatic" | "local";
164 export type MemoryReferenceType =
165 | "character"
166 | "scene"
167 | "style"
168 | "object"
169 | "other";
170
171 /** One reusable item in the per-project local Memory Workspace. */
172 export interface MemoryWorkspaceAsset {
173 asset_id: string;
174 display_name: string;
175 source: MemoryWorkspaceSource;
176 kind: "character" | "previous_shot" | "manual";
177 memory_id?: string;
178 source_shot_id?: number | null;
179 frame_index?: number | null;
180 image?: WireMediaRef;
181 audio?: WireMediaRef | null;
182 media_type?: "image" | "audio" | "image_audio";
183 profile_text?: string;
184 profile_status?: "ready" | "missing" | "profiling" | "error" | string;
185 profile_source?: "vlm" | "human" | "none" | string;
186 identity_ids?: string[];
187 reference_type?: MemoryReferenceType;
188 reference_label?: string;
189 provenance?: {
190 source: "generated_shot" | "local_upload" | string;
191 shot_id?: number | null;
192 timestamp_sec?: number | null;
193 };
194 }
195
196 /** Compact reference persisted when applying an ordered slot draft to a shot. */
197 export interface MemorySlotReference {
198 source?: MemoryWorkspaceSource;
199 asset_id?: string;
200 image_asset_id?: string;
201 audio_asset_id?: string;
202 reason?: string;
203 }
204
205 export interface MemoryAssetUpload {
206 asset_id?: string;
207 display_name?: string;
208 image?: OutboundMedia;
209 audio?: OutboundMedia;
210 remove_audio?: boolean;
211 profile_text?: string;
212 identity_ids?: string[];
213 reference_type?: MemoryReferenceType | null;
214 reference_label?: string;
215 }
216
217 export interface ShotMemoryAssetCreate {
218 timestamp_sec: number;
219 reference_type: MemoryReferenceType;
220 reference_label?: string;
221 profile_text?: string;
222 include_audio?: boolean;
223 audio_start_sec?: number;
224 audio_end_sec?: number;
225 }
226
227 export interface StoryProfileBeat {
228 shot_id: number;
229 summary: string;
230 }
231
232 export interface StoryProfile {
233 summary?: string;
234 beats?: StoryProfileBeat[];
235 characters?: unknown;
236 genre?: string;
237 setting?: string;
238 title?: string;
239 tone?: string;
240 language?: string;
241 dialogue_language?: string;
242 anchors?: unknown;
243 shot_to_content?: Record<string, string>;
244 content_to_shots?: Record<string, string[]>;
245 [key: string]: unknown;
246 }
247
248 export interface WorkplaceShot {
249 shot_id: number;
250 shot_key: string;
251 status: string;
252 summary: string;
253 caption?: string;
254 num_frames?: number | null;
255 cut: boolean;
256 video?: WireMediaRef | null;
257 has_video: boolean;
258 has_actions: boolean;
259 accepted: boolean;
260 last_review?: string | null;
261 review_notes?: string;
262 generation_error?: string;
263 /** R2V remote version id when generation has been submitted. */
264 version_id?: string;
265 /** Raw Echo/R2V job status mirrored from shot.echo.status. */
266 echo_status?: string;
267 updated_at?: string | null;
268 planned_reference_shot_ids?: number[];
269 reference_shot_ids?: number[];
270 reference_selection_note?: string;
271 references_planned?: boolean;
272 memory_review?: Omit<MemoryReview, "shot_id"> | null;
273 /** Exact Memory slots submitted when this shot generation was queued. */
274 generation_memories?: GenerationMemory[];
275 /** Ordered Memory Workspace slots currently applied to this shot. */
276 memory_slots?: GenerationMemory[];
277 memory_slots_configured?: boolean;
278 /** Stable references already approved in Build Memory. */
279 approved_memory_slot_refs?: MemorySlotReference[];
280 /** Agent/profile recommendation; never sent to R2V until explicitly applied. */
281 recommended_memory_slots?: GenerationMemory[];
282 recommended_memory_slot_refs?: MemorySlotReference[];
283 memory_recommendation_source?: "agent" | "profile_fallback" | string | null;
284 /** 是否开启首尾衔接(使用前一 Shot 尾帧作为 I2V 条件图) */
285 continuous_enabled?: boolean;
286 /** 本镜头视频尾帧的持久化 URL(由下一次 continuous-generate 提取并回写) */
287 tail_frame_url?: string;
288 timeline: {
289 start_seconds: number;
290 end_seconds: number;
291 duration_seconds: number;
292 label: string;
293 };
294 }
295
296 /** User-uploaded first-frame reference image for shot 1. */
297 export interface WorkplaceReferenceImage {
298 url: string;
299 name?: string;
300 width?: number;
301 height?: number;
302 }
303
304 export interface WorkplaceData {
305 session_key: string;
306 work_id: string | null;
307 story_md: string;
308 story_empty: boolean;
309 story_profile?: StoryProfile;
310 story_editable?: boolean;
311 beats_editable?: boolean;
312 story_confirmed?: boolean;
313 shot_prompts_ready?: boolean;
314 shot_prompts_progress?: { ready: number; total: number } | null;
315 references_ready?: boolean;
316 /** ISO timestamp when stage entered shot_generating via start-generation. */
317 shot_generating_started_at?: string | null;
318 stage: string | null;
319 goal: Record<string, unknown>;
320 final_output_path?: string | null;
321 final_output_url?: string | null;
322 final_video?: WireMediaRef | null;
323 /** Durable character identities plus the latest previous-shot continuity slot. */
324 memory_bank?: MemorySelection[];
325 /** Automatic and locally uploaded assets available for slot assembly. */
326 memory_workspace_assets?: MemoryWorkspaceAsset[];
327 /** Human-readable generation failure when the workflow stage is ``failed``. */
328 generation_error?: string | null;
329 /**
330 * Shot1 first-frame reference image persisted by the backend.
331 * Composer PUT/DELETE; shot 1 I2V reads this from state.
332 */
333 reference_image?: WorkplaceReferenceImage | null;
334 /** True after shot count is confirmed; PUT/DELETE then 409. */
335 reference_image_locked?: boolean;
336 /** Generate remaining shots automatically after story confirmation. */
337 auto_generate?: boolean;
338 /** Workflow bar: ``01``–``04`` or ``done``. */
339 progress?: "01" | "02" | "03" | "04" | "done" | string | null;
340 /** Echo tracking: remote request id; LikeButton hidden when absent. */
341 echo_request_id?: string | null;
342 /** Echo tracking: 0 = none, 1 = like, 2 = dislike. */
343 like_status?: number;
344 prompt_downloaded?: boolean;
345 video_downloaded?: boolean;
346 shots: WorkplaceShot[];
347 updated_at: string | null;
348 }
349
350 export type EchoTrackingResponse = {
351 ok: boolean;
352 session_key: string;
353 echo_request_id: string | null;
354 like_status: number;
355 prompt_downloaded: boolean;
356 video_downloaded: boolean;
357 workplace: WorkplaceData;
358 };
359
360 export interface ChatSummary {
361 /** Server-side session key, e.g. ``websocket:abcd-...``. */
362 key: string;
363 /** Local channel + chat_id parts derived from ``key`` for convenience. */
364 channel: string;
365 chatId: string;
366 createdAt: string | null;
367 updatedAt: string | null;
368 preview: string;
369 /** Director workflow source. */
370 source?: "stepwise" | null;
371 /** Stepwise auto-generate flag persisted on the session. */
372 autoGenerate?: boolean | null;
373 }
374
375 export interface BootstrapResponse {
376 token: string;
377 ws_path: string;
378 expires_in: number;
379 model_name?: string | null;
380 /** Local browser identity used to namespace on-disk sessions. */
381 user_id?: string | null;
382 /** Currently active Prompt Engineering set name (server-global). */
383 active_pe?: string | null;
384 }
385
386 export type ConnectionStatus =
387 | "idle"
388 | "connecting"
389 | "open"
390 | "reconnecting"
391 | "closed"
392 | "error";
393
394 export type InboundEvent =
395 | { event: "ready"; chat_id: string; client_id: string }
396 | { event: "attached"; chat_id: string; active_pe?: string }
397 | {
398 event: "workplace_updated";
399 chat_id: string;
400 work_id?: string;
401 workplace?: WorkplaceData;
402 }
403 | {
404 event: "workplace_action_ok";
405 chat_id: string;
406 request_id: string;
407 work_id: string;
408 workplace: WorkplaceData;
409 }
410 | {
411 event: "workplace_action_error";
412 chat_id: string;
413 request_id: string;
414 detail: string;
415 }
416 | {
417 event: "message";
418 chat_id: string;
419 text: string;
420 reply_to?: string;
421 media?: string[];
422 media_urls?: WireMediaRef[];
423 questions?: WireQuestionCard[];
424 question_batch_id?: string;
425 /** Present when the frame is an agent breadcrumb (e.g. tool hint,
426 * generic progress line) rather than a conversational reply. */
427 kind?: "tool_hint" | "progress";
428 }
429 | {
430 event: "question_answer_ok";
431 chat_id: string;
432 question_batch_id: string;
433 card_id: string;
434 value: string;
435 }
436 | {
437 event: "delta";
438 chat_id: string;
439 text: string;
440 stream_id?: string;
441 }
442 | {
443 event: "stream_end";
444 chat_id: string;
445 stream_id?: string;
446 /** ``false`` means the agent turn is finished; question cards may be answered. */
447 resuming?: boolean;
448 }
449 | { event: "pe_updated"; chat_id: string; active: string }
450 | { event: "error"; chat_id?: string; detail?: string };
451
452 /** Base64-encoded image attached to an outbound ``message`` envelope.
453 *
454 * ``data_url`` must be a ``data:image/<png|jpeg|webp|gif>;base64,...`` string
455 * — the server whitelists those MIME types and rejects everything else
456 * (including SVG, to avoid an XSS surface). ``name`` is advisory: it's
457 * preserved for the file on disk and surfaced as the placeholder label when
458 * the session is replayed.
459 */
460 export interface OutboundMedia {
461 data_url: string;
462 name?: string;
463 }
464
465 export type Outbound =
466 | { type: "new_chat"; autoGenerate?: boolean }
467 | { type: "attach"; chat_id: string }
468 | {
469 type: "message";
470 chat_id: string;
471 content: string;
472 media?: OutboundMedia[];
473 temperature?: number;
474 top_p?: number;
475 top_k?: number;
476 autoGenerate?: boolean;
477 duration_sec?: number;
478 reference_image_url?: string;
479 reference_image_name?: string;
480 reference_image_width?: number;
481 reference_image_height?: number;
482 }
483 | {
484 type: "workplace_save_story";
485 chat_id: string;
486 request_id: string;
487 story_md: string;
488 }
489 | {
490 type: "workplace_save_story_profile";
491 chat_id: string;
492 request_id: string;
493 story_profile: StoryProfile;
494 }
495 | {
496 type: "workplace_save_reference_image";
497 chat_id: string;
498 request_id: string;
499 image: WorkplaceReferenceImage;
500 }
501 | {
502 type: "workplace_save_memory_asset";
503 chat_id: string;
504 request_id: string;
505 asset: MemoryAssetUpload;
506 }
507 | {
508 type: "workplace_create_shot_memory_asset";
509 chat_id: string;
510 request_id: string;
511 shot_id: number;
512 asset: ShotMemoryAssetCreate;
513 }
514 | {
515 type: "workplace_delete_memory_asset";
516 chat_id: string;
517 request_id: string;
518 asset_id: string;
519 }
520 | {
521 type: "workplace_save_shot_memory_slots";
522 chat_id: string;
523 request_id: string;
524 shot_id: number;
525 slots: MemorySlotReference[];
526 }
527 | {
528 type: "answer_question";
529 chat_id: string;
530 question_batch_id: string;
531 card_id: string;
532 value: string;
533 }
534 | { type: "set_pe"; name: string; chat_id: string };
535
535 lines TYPESCRIPT