From 5444b5cb39d54f17fa2358ade93d8699c738e298 Mon Sep 17 00:00:00 2001 From: kvtodev Date: Fri, 10 Jul 2026 17:15:53 +0800 Subject: [PATCH] fix title display mistake --- packages/shared/src/contracts/editorAgent.ts | 2 + .../crates/api-server/src/editor_agent/api.rs | 56 ++++++++++--------- .../src/editor_agent/editor_tools/common.rs | 2 +- .../api-server/src/editor_agent/utils.rs | 8 +-- .../shared-contracts/src/editor_agent.rs | 9 +++ .../EditorAgentConversationPanelView.test.tsx | 17 ++++++ .../EditorAgentConversationPanelView.tsx | 2 +- .../useEditorAgentConversation.test.tsx | 22 ++++++++ .../useEditorAgentConversation.ts | 7 +++ .../ImageCanvasEditorView.test.tsx | 9 +++ .../image-editor/editorAgentClient.test.ts | 6 ++ 11 files changed, 106 insertions(+), 34 deletions(-) diff --git a/packages/shared/src/contracts/editorAgent.ts b/packages/shared/src/contracts/editorAgent.ts index 483ec85bc..732319684 100644 --- a/packages/shared/src/contracts/editorAgent.ts +++ b/packages/shared/src/contracts/editorAgent.ts @@ -58,6 +58,7 @@ export interface EditorAgentMessage { export interface EditorAgentConversationSummary { conversationId: string; projectId: string; + title: string; updatedAt: string; } @@ -94,6 +95,7 @@ export interface EditorAgentMessageRequest { } export interface EditorAgentMessageResponse { + conversation: EditorAgentConversationSummary; deltaMessages: EditorAgentMessage[]; errorMessage: string | null; } diff --git a/server-rs/crates/api-server/src/editor_agent/api.rs b/server-rs/crates/api-server/src/editor_agent/api.rs index bf72db7ec..c42398316 100644 --- a/server-rs/crates/api-server/src/editor_agent/api.rs +++ b/server-rs/crates/api-server/src/editor_agent/api.rs @@ -18,8 +18,8 @@ use shared_contracts::editor_agent::{ CreateEditorAgentConversationRequest, EditorAgentConversationListResponse, EditorAgentConversationMessagesDocument, EditorAgentConversationResponse, EditorAgentConversationSummary, EditorAgentGeneratedImage, EditorAgentMessage, - EditorAgentMessageRequest, EditorAgentMessageRole, EditorAgentToolCall, - EditorAgentToolCallStatus, + EditorAgentMessageRequest, EditorAgentMessageResponse, EditorAgentMessageRole, + EditorAgentToolCall, EditorAgentToolCallStatus, }; use spacetime_client::{ EditorAgentConversationCreateRecordInput, EditorAgentConversationDeleteRecordInput, @@ -34,11 +34,11 @@ use crate::editor_agent::editor_tools::common::EditorToolContext; use crate::editor_agent::editor_tools::edit_image::{EditImageTool, EditImageToolArgs}; use crate::editor_agent::editor_tools::generate_image::{GenerateImageTool, GenerateImageToolArgs}; use crate::editor_agent::utils::{ - EditorAgentMessageResponse, ImageId, ImageMetadata, IntoImageId, - build_editor_agent_canvas_completion, conversation_detail_from_record, - conversation_summary_from_record, editor_agent_bad_request, empty_messages_document, - ensure_editor_project_access, normalize_editor_agent_attachments, now_rfc3339, - read_messages_document, require_editor_agent_sidebar_enabled, write_messages_document, + ImageId, ImageMetadata, IntoImageId, build_editor_agent_canvas_completion, + conversation_detail_from_record, conversation_summary_from_record, editor_agent_bad_request, + empty_messages_document, ensure_editor_project_access, normalize_editor_agent_attachments, + now_rfc3339, read_messages_document, require_editor_agent_sidebar_enabled, + write_messages_document, }; use crate::editor_project::{EditorGenerationCaller, current_utc_micros, map_editor_project_error}; use crate::http_error::AppError; @@ -77,6 +77,8 @@ pub async fn editor_agent_message( let mut document: EditorAgentConversationMessagesDocument = read_messages_document(&state, &conversation).await?; + // Determine initialization before attachment bookkeeping adds a system message. + let was_empty = document.messages.is_empty(); let now = now_rfc3339(); if !attachments.is_empty() { let mut attachment_info = String::new(); @@ -105,9 +107,7 @@ pub async fn editor_agent_message( }) .collect(); - // Save user message to document - let was_empty = document.messages.is_empty(); - // TODO tell agent info about attachments + // Save user message to document. let user_message = EditorAgentMessage { id: document.messages.len(), role: EditorAgentMessageRole::User, @@ -119,19 +119,19 @@ pub async fn editor_agent_message( document.messages.push(user_message.clone()); write_messages_document(&state, &conversation, &document).await?; - // Touch conversation (set title if first message) - if was_empty { - let title = derive_conversation_title(user_message.text.as_str()); - let _ = state - .spacetime_client() - .touch_editor_agent_conversation(EditorAgentConversationTouchRecordInput { - conversation_id: conversation.conversation_id.clone(), - owner_user_id: conversation.owner_user_id.clone(), - title: Some(title), - updated_at_micros: current_utc_micros(), - }) - .await; - } + // Persist and return the authoritative summary for every turn. Initialization sets the + // title from the first user prompt; a metadata write failure must fail the request. + let updated_conversation = state + .spacetime_client() + .touch_editor_agent_conversation(EditorAgentConversationTouchRecordInput { + conversation_id: conversation.conversation_id.clone(), + owner_user_id: conversation.owner_user_id.clone(), + title: was_empty.then(|| derive_conversation_title(user_message.text.as_str())), + updated_at_micros: current_utc_micros(), + }) + .await + .map_err(map_editor_project_error)?; + let conversation_summary = conversation_summary_from_record(updated_conversation); // Build tool context from document let tool_context = build_tool_context(&document); @@ -150,7 +150,9 @@ pub async fn editor_agent_message( .tool(EditImageTool { context: tool_context.clone(), }) - .tool(GenerateImageTool { context: tool_context }) + .tool(GenerateImageTool { + context: tool_context, + }) .max_turns(3) .memory(memory) .build(); @@ -161,6 +163,7 @@ pub async fn editor_agent_message( match build_delta_messages(agent_result, &assistant_now, document.messages.len()) { Err(err) => Ok(Json(EditorAgentMessageResponse { + conversation: conversation_summary, delta_messages: vec![], error_message: Some(err.to_string()), })), @@ -171,6 +174,7 @@ pub async fn editor_agent_message( write_messages_document(&state, &conversation, &document).await?; Ok(Json(EditorAgentMessageResponse { + conversation: conversation_summary, delta_messages, error_message: None, })) @@ -529,7 +533,9 @@ pub async fn confirm_editor_agent_tool_call( let canvas_completion = build_editor_agent_canvas_completion(&project, GenerateImageTool::NAME, &title); let tool_context = build_tool_context(&document); - let generate_tool = GenerateImageTool { context: tool_context }; + let generate_tool = GenerateImageTool { + context: tool_context, + }; let result = generate_tool .execute( &state, diff --git a/server-rs/crates/api-server/src/editor_agent/editor_tools/common.rs b/server-rs/crates/api-server/src/editor_agent/editor_tools/common.rs index 6690b3f29..e39e372a9 100644 --- a/server-rs/crates/api-server/src/editor_agent/editor_tools/common.rs +++ b/server-rs/crates/api-server/src/editor_agent/editor_tools/common.rs @@ -7,10 +7,10 @@ pub struct EditorToolContext { pub images: HashMap, } + impl EditorToolContext { /// Check if an image with the given ID exists in the context. pub fn contains_image(&self, image_id: &ImageId) -> bool { self.images.contains_key(image_id) } } - diff --git a/server-rs/crates/api-server/src/editor_agent/utils.rs b/server-rs/crates/api-server/src/editor_agent/utils.rs index 58ecb2f92..6e2a9bd7b 100644 --- a/server-rs/crates/api-server/src/editor_agent/utils.rs +++ b/server-rs/crates/api-server/src/editor_agent/utils.rs @@ -128,13 +128,6 @@ impl Display for ImageId { } } -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentMessageResponse { - pub delta_messages: Vec, - pub error_message: Option, -} - type EditorAgentConversationLockMap = Mutex>>>; static EDITOR_AGENT_CONVERSATION_LOCKS: OnceLock = OnceLock::new(); @@ -536,6 +529,7 @@ pub fn conversation_summary_from_record( EditorAgentConversationSummary { conversation_id: conversation.conversation_id, project_id: conversation.project_id, + title: conversation.title, updated_at: conversation.updated_at, } } diff --git a/server-rs/crates/shared-contracts/src/editor_agent.rs b/server-rs/crates/shared-contracts/src/editor_agent.rs index 87c55d818..b24ab82ef 100644 --- a/server-rs/crates/shared-contracts/src/editor_agent.rs +++ b/server-rs/crates/shared-contracts/src/editor_agent.rs @@ -102,6 +102,7 @@ pub struct EditorAgentMessage { pub struct EditorAgentConversationSummary { pub conversation_id: String, pub project_id: String, + pub title: String, pub updated_at: String, } @@ -150,3 +151,11 @@ pub struct EditorAgentMessageRequest { #[serde(default)] pub attachments: Vec, } + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct EditorAgentMessageResponse { + pub conversation: EditorAgentConversationSummary, + pub delta_messages: Vec, + pub error_message: Option, +} diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx index 724237e0a..1104adac2 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx @@ -53,6 +53,7 @@ function createClient(): EditorAgentConversationClient { { conversationId: 'conversation-1', projectId: 'project-1', + title: '角色参考', updatedAt: '2026-07-03T00:00:00.000Z', }, ]), @@ -90,6 +91,12 @@ function createClient(): EditorAgentConversationClient { updatedAt: '2026-07-03T00:00:00.000Z', }), sendMessage: vi.fn().mockResolvedValue({ + conversation: { + conversationId: 'conversation-1', + projectId: 'project-1', + title: '角色参考', + updatedAt: '2026-07-03T00:00:10.000Z', + }, deltaMessages: [ { id: 2, @@ -331,6 +338,10 @@ describe('EditorAgentConversationPanelView', () => { expect.any(Object), ); }); + expect(screen.getByRole('option', { name: '角色参考' })).toBeTruthy(); + expect( + screen.queryByRole('option', { name: 'conversation-1' }), + ).toBeNull(); }); it('sends selected attachments even when the text input is empty', async () => { @@ -398,6 +409,12 @@ describe('EditorAgentConversationPanelView', () => { it('preserves messages when the panel is collapsed and reopened', async () => { const client = createClient(); vi.mocked(client.sendMessage).mockResolvedValue({ + conversation: { + conversationId: 'conversation-1', + projectId: 'project-1', + title: '角色参考', + updatedAt: '2026-07-03T00:00:10.000Z', + }, deltaMessages: [ { id: 3, diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx index 1207c2449..e07cce48e 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx @@ -525,7 +525,7 @@ export function EditorAgentConversationPanelView({ key={conversation.conversationId} value={conversation.conversationId} > - {conversation.conversationId} + {conversation.title} )) ) : ( diff --git a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx index 7b2296344..6b134edee 100644 --- a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx +++ b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx @@ -18,6 +18,7 @@ function createClient(): EditorAgentConversationClient { { conversationId: 'conversation-1', projectId: 'project-1', + title: '角色参考', updatedAt: '2026-07-03T00:00:00.000Z', }, ]), @@ -46,6 +47,12 @@ function createClient(): EditorAgentConversationClient { updatedAt: '2026-07-03T00:00:00.000Z', }), sendMessage: vi.fn().mockResolvedValue({ + conversation: { + conversationId: 'conversation-1', + projectId: 'project-1', + title: '把这个角色改成像素风', + updatedAt: '2026-07-03T00:00:01.000Z', + }, deltaMessages: [ { id: 1, @@ -115,6 +122,9 @@ describe('useEditorAgentConversation', () => { }), ); expect(result.current.isWaiting).toBe(false); + expect(result.current.activeConversation?.title).toBe( + '把这个角色改成像素风', + ); expect(onCanvasRefreshRequested).toHaveBeenCalledTimes(1); expect(result.current.messages.map((message) => message.text)).toEqual([ '把这个角色改成像素风', @@ -199,6 +209,12 @@ describe('useEditorAgentConversation', () => { it('handles backend error responses', async () => { const client = createClient(); vi.mocked(client.sendMessage).mockResolvedValue({ + conversation: { + conversationId: 'conversation-1', + projectId: 'project-1', + title: '这是美术素材', + updatedAt: '2026-07-03T00:00:01.000Z', + }, deltaMessages: [], errorMessage: 'LLM 未配置,无法处理这句话。', } as EditorAgentMessageResponse); @@ -222,6 +238,12 @@ describe('useEditorAgentConversation', () => { it('confirms a pending tool call, replaces its message and requests a canvas refresh', async () => { const client = createClient(); vi.mocked(client.sendMessage).mockResolvedValue({ + conversation: { + conversationId: 'conversation-1', + projectId: 'project-1', + title: '角色参考', + updatedAt: '2026-07-03T00:00:01.000Z', + }, deltaMessages: [ { id: 1, diff --git a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts index 89a59a80d..4b9002760 100644 --- a/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts +++ b/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts @@ -113,6 +113,7 @@ function summaryFromDetail( return { conversationId: detail.conversationId, projectId: detail.projectId, + title: detail.title, updatedAt: detail.updatedAt, }; } @@ -362,6 +363,12 @@ export function useEditorAgentConversation({ }, ); + setConversations((currentConversations) => + upsertConversationSummary( + currentConversations, + response.conversation, + ), + ); if (response.errorMessage) { setErrorMessage(response.errorMessage); } else { diff --git a/src/components/image-editor/ImageCanvasEditorView.test.tsx b/src/components/image-editor/ImageCanvasEditorView.test.tsx index a5237411b..4d5cfaf81 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.test.tsx @@ -68,6 +68,7 @@ const getEditorAgentConversationMock = vi.hoisted(() => >(async () => ({ conversationId: 'editor-agent-conv-test', projectId: 'editor-project-default', + title: '画布 Agent', updatedAt: '2026-07-03T00:00:00.000Z', messages: [], })), @@ -89,6 +90,12 @@ const sendEditorAgentMessageMock = vi.hoisted(() => Parameters, ReturnType >(async () => ({ + conversation: { + conversationId: 'editor-agent-conv-test', + projectId: 'editor-project-default', + title: '画布 Agent', + updatedAt: '2026-07-03T00:00:00.000Z', + }, deltaMessages: [], errorMessage: null, })), @@ -163,6 +170,7 @@ function createEditorAgentConversationSummary( return { conversationId: 'editor-agent-conv-test', projectId: 'editor-project-default', + title: '画布 Agent', updatedAt: '2026-07-03T00:00:00.000Z', ...overrides, }; @@ -274,6 +282,7 @@ describe('ImageCanvasEditorView', () => { createEditorAgentConversationSummary(), ); sendEditorAgentMessageMock.mockResolvedValue({ + conversation: createEditorAgentConversationSummary(), deltaMessages: [], errorMessage: null, }); diff --git a/src/services/image-editor/editorAgentClient.test.ts b/src/services/image-editor/editorAgentClient.test.ts index e043226a7..7b68126bf 100644 --- a/src/services/image-editor/editorAgentClient.test.ts +++ b/src/services/image-editor/editorAgentClient.test.ts @@ -102,6 +102,12 @@ describe('editorAgentClient', () => { it('sends an editor agent message and returns delta messages', async () => { const responseBody = { + conversation: { + conversationId: 'conversation-1', + projectId: 'project-1', + title: '帮我把角色改成像素风', + updatedAt: '2026-07-03T00:00:01.000Z', + }, deltaMessages: [ { id: 1,