feat: 对接设计 Agent Gateway WebSocket

需求:服务端统一 Agent Gateway 将设计任务状态流切换为 WebSocket,客户端需要实时展示生成任务并支持断线恢复。

实现:Electron Main 管理 Session、一次性 Ticket、WebSocket 心跳与游标续传,按关闭码回收会话;Renderer 继续通过本机 Host API 的 SSE 投影接收任务事件,并保留 REST 降级同步。

验证:typecheck、变更文件 ESLint、37 个聚焦测试及 build:vite 通过。
This commit is contained in:
2026-08-02 20:15:37 +08:00
parent 83e21563dc
commit 518d7ab4cd
17 changed files with 1816 additions and 50 deletions

View File

@@ -194,6 +194,8 @@ export function ImageCanvas() {
const load = useImageWorkspaceStore((state) => state.load);
const refreshWorkspace = useImageWorkspaceStore((state) => state.refreshWorkspace);
const refreshTasks = useImageWorkspaceStore((state) => state.refreshTasks);
const connectTaskStream = useImageWorkspaceStore((state) => state.connectTaskStream);
const disconnectTaskStream = useImageWorkspaceStore((state) => state.disconnectTaskStream);
const sendMessage = useImageWorkspaceStore((state) => state.sendMessage);
const confirmGeneration = useImageWorkspaceStore((state) => state.confirmGeneration);
const [prompt, setPrompt] = useState('');
@@ -206,6 +208,7 @@ export function ImageCanvas() {
() => workspace ? activeQuote(workspace.messages) : null,
[workspace],
);
const taskWorkspaceId = workspace?.workspaceId ?? null;
const hasActiveTasks = tasks.some((task) => ACTIVE_TASK_STATUSES.has(task.status));
useEffect(() => {
@@ -213,12 +216,10 @@ export function ImageCanvas() {
}, [authenticated, load, status]);
useEffect(() => {
if (!hasActiveTasks || !workspace) return;
const timer = window.setInterval(() => {
void refreshTasks().catch(() => undefined);
}, 2_500);
return () => window.clearInterval(timer);
}, [hasActiveTasks, refreshTasks, workspace]);
if (!taskWorkspaceId) return;
connectTaskStream();
return disconnectTaskStream;
}, [connectTaskStream, disconnectTaskStream, taskWorkspaceId]);
useEffect(() => {
if (typeof conversationEndRef.current?.scrollIntoView === 'function') {
@@ -354,7 +355,9 @@ export function ImageCanvas() {
aria-label="刷新设计项目"
className="h-9 w-9 rounded-full"
onClick={() => {
void Promise.all([refreshWorkspace(), refreshTasks()]).catch(() => undefined);
void refreshWorkspace()
.then(() => refreshTasks())
.catch(() => undefined);
}}
>
<RefreshCw className="h-4 w-4" />