fix title display mistake
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// 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: Some(title),
|
||||
title: was_empty.then(|| derive_conversation_title(user_message.text.as_str())),
|
||||
updated_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
.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,
|
||||
|
||||
@@ -7,10 +7,10 @@ pub struct EditorToolContext {
|
||||
pub images: HashMap<ImageId, ImageMetadata>,
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,13 +128,6 @@ impl Display for ImageId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAgentMessageResponse {
|
||||
pub delta_messages: Vec<EditorAgentMessage>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
type EditorAgentConversationLockMap = Mutex<BTreeMap<String, Arc<tokio::sync::Mutex<()>>>>;
|
||||
static EDITOR_AGENT_CONVERSATION_LOCKS: OnceLock<EditorAgentConversationLockMap> = 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<EditorAgentAttachmentRef>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAgentMessageResponse {
|
||||
pub conversation: EditorAgentConversationSummary,
|
||||
pub delta_messages: Vec<EditorAgentMessage>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
+17
@@ -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,
|
||||
|
||||
+1
-1
@@ -525,7 +525,7 @@ export function EditorAgentConversationPanelView({
|
||||
key={conversation.conversationId}
|
||||
value={conversation.conversationId}
|
||||
>
|
||||
{conversation.conversationId}
|
||||
{conversation.title}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
|
||||
+22
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<EditorAgentStreamMessage>,
|
||||
ReturnType<EditorAgentStreamMessage>
|
||||
>(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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user