Files
Genarrative/src/services/image-editor/editorAgentClient.ts
T
kdletters 8dad52f040 实现画布Agent侧边栏聊天
新增画布Agent会话契约、领域规则、SpacetimeDB元数据表和生成绑定。
新增api-server画布Agent会话CRUD、OSS消息文档读写、SSE消息流和生成工具编排。
新增前端右侧Agent对话面板、会话状态hook、SSE客户端、BFF客户端和相关测试。
调整画布侧栏、任务栏、小地图默认状态和面板互斥交互。
补齐画布Agent、OSS消息存储、SSE收口、后端契约和项目记忆文档。
2026-07-04 12:03:58 +08:00

133 lines
3.9 KiB
TypeScript

import type {
CreateEditorAgentConversationRequest,
EditorAgentConversationDetail,
EditorAgentConversationListResponse,
EditorAgentConversationResponse,
EditorAgentConversationSummary,
EditorAgentSseEvent,
StreamEditorAgentMessageRequest,
} from '../../../packages/shared/src/contracts/editorAgent';
import {
appendApiErrorRequestId,
parseApiErrorMessage,
} from '../../../packages/shared/src/http';
import { fetchWithApiAuth, requestJson } from '../apiClient';
import { readEditorAgentSseEvents } from './editorAgentSse';
const EDITOR_PROJECT_AGENT_CONVERSATION_API_BASE = '/api/editor/projects';
const EDITOR_AGENT_CONVERSATION_API_BASE = '/api/editor/agent-conversations';
const EDITOR_AGENT_STREAM_TIMEOUT_MS = 1_200_000;
export type StreamEditorAgentMessageOptions = {
signal?: AbortSignal;
onEvent?: (event: EditorAgentSseEvent) => void;
};
type DeleteEditorAgentConversationResponse = {
deletedConversationId: string;
conversation: EditorAgentConversationSummary;
};
function jsonRequest(method: 'POST', body: Record<string, unknown>) {
return {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
};
}
function projectAgentConversationsPath(projectId: string) {
return `${EDITOR_PROJECT_AGENT_CONVERSATION_API_BASE}/${encodeURIComponent(
projectId,
)}/agent-conversations`;
}
function agentConversationPath(conversationId: string) {
return `${EDITOR_AGENT_CONVERSATION_API_BASE}/${encodeURIComponent(
conversationId,
)}`;
}
export async function listEditorAgentConversations(
projectId: string,
): Promise<EditorAgentConversationSummary[]> {
const response = await requestJson<EditorAgentConversationListResponse>(
projectAgentConversationsPath(projectId),
{ method: 'GET' },
'读取画布 Agent 会话列表失败',
);
return response.conversations;
}
export async function createEditorAgentConversation(
projectId: string,
input: CreateEditorAgentConversationRequest = {},
): Promise<EditorAgentConversationDetail> {
const body: Record<string, unknown> = {};
if (input.title !== undefined) {
body.title = input.title;
}
const response = await requestJson<EditorAgentConversationResponse>(
projectAgentConversationsPath(projectId),
jsonRequest('POST', body),
'创建画布 Agent 会话失败',
);
return response.conversation;
}
export async function getEditorAgentConversation(
conversationId: string,
): Promise<EditorAgentConversationDetail> {
const response = await requestJson<EditorAgentConversationResponse>(
agentConversationPath(conversationId),
{ method: 'GET' },
'读取画布 Agent 会话失败',
);
return response.conversation;
}
export async function deleteEditorAgentConversation(
conversationId: string,
): Promise<EditorAgentConversationSummary> {
const response = await requestJson<DeleteEditorAgentConversationResponse>(
agentConversationPath(conversationId),
{ method: 'DELETE' },
'删除画布 Agent 会话失败',
);
return response.conversation;
}
export async function streamEditorAgentMessage(
conversationId: string,
payload: StreamEditorAgentMessageRequest,
options: StreamEditorAgentMessageOptions = {},
) {
const response = await fetchWithApiAuth(
`${agentConversationPath(conversationId)}/messages/stream`,
{
...jsonRequest('POST', payload as unknown as Record<string, unknown>),
signal: options.signal,
},
{
timeoutMs: EDITOR_AGENT_STREAM_TIMEOUT_MS,
authImpact: 'local',
},
);
if (!response.ok) {
const responseText = await response.text();
throw new Error(
appendApiErrorRequestId(
parseApiErrorMessage(responseText, '发送画布 Agent 消息失败'),
response.headers.get('x-request-id'),
),
);
}
if (!response.body) {
throw new Error('streaming response body is unavailable');
}
await readEditorAgentSseEvents(response, (event) => options.onEvent?.(event));
}