const state = { sessions: [], messages: [], tasks: [], approvals: [], workers: [], events: [], selectedSessionId: null, }; const elements = { sessionList: document.querySelector("#session-list"), workerList: document.querySelector("#worker-list"), messageList: document.querySelector("#message-list"), taskList: document.querySelector("#task-list"), approvalList: document.querySelector("#approval-list"), eventList: document.querySelector("#event-list"), sessionTitleDisplay: document.querySelector("#session-title-display"), sessionSummary: document.querySelector("#session-summary"), createSessionForm: document.querySelector("#create-session-form"), sessionTitleInput: document.querySelector("#session-title"), messageForm: document.querySelector("#message-form"), messageInput: document.querySelector("#message-input"), resetDemo: document.querySelector("#reset-demo"), }; function escapeHtml(input) { return input .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } async function request(url, options = {}) { const response = await fetch(url, { headers: { "Content-Type": "application/json" }, ...options, }); if (!response.ok) { throw new Error(`${response.status} ${response.statusText}`); } return response.json(); } function selectedSession() { return state.sessions.find((session) => session.id === state.selectedSessionId) ?? null; } function tasksForSelectedSession() { return state.tasks.filter((task) => task.sessionId === state.selectedSessionId); } function messagesForSelectedSession() { return state.messages.filter((message) => message.sessionId === state.selectedSessionId); } function approvalsForSelectedSession() { return state.approvals.filter((approval) => approval.sessionId === state.selectedSessionId); } function eventsForSelectedSession() { return state.events .filter((event) => event.sessionId === null || event.sessionId === state.selectedSessionId) .slice(-50) .reverse(); } function renderSessions() { elements.sessionList.innerHTML = state.sessions .map((session) => { const active = session.id === state.selectedSessionId ? "active" : ""; return ` `; }) .join(""); elements.sessionList.querySelectorAll("[data-session-id]").forEach((button) => { button.addEventListener("click", async () => { state.selectedSessionId = button.dataset.sessionId; await loadSession(state.selectedSessionId); render(); }); }); } function renderWorkers() { elements.workerList.innerHTML = state.workers .map( (worker) => `
${escapeHtml(worker.name)} ${escapeHtml(worker.status)}
${escapeHtml(worker.os)}
${worker.capabilities.map((capability) => `${escapeHtml(capability)}`).join("")}
`, ) .join(""); } function renderSessionHeader() { const session = selectedSession(); if (!session) { elements.sessionTitleDisplay.textContent = "选择一个项目会话"; elements.sessionSummary.textContent = "创建会话后,在这里持续对话并观察任务状态。"; return; } elements.sessionTitleDisplay.textContent = session.title; elements.sessionSummary.textContent = session.lastPlannerSummary || session.activeObjective || "等待用户输入。"; } function renderMessages() { const messages = messagesForSelectedSession(); elements.messageList.innerHTML = messages.length ? messages .map( (message) => `
${escapeHtml(message.role)} ${new Date(message.createdAt).toLocaleTimeString()}

${escapeHtml(message.content)}

`, ) .join("") : `

当前没有消息。

`; } function renderTasks() { const tasks = tasksForSelectedSession(); elements.taskList.innerHTML = tasks.length ? tasks .map( (task) => `
${escapeHtml(task.title)} ${escapeHtml(task.status)}

${escapeHtml(task.description)}

worker: ${escapeHtml(task.assignedWorkerId || "未分配")}
progress: ${task.progressPercent}%
summary: ${escapeHtml(task.summary || "暂无")}
`, ) .join("") : `

当前没有任务。

`; elements.taskList.querySelectorAll("[data-action]").forEach((button) => { button.addEventListener("click", async () => { const taskId = button.dataset.taskId; const action = button.dataset.action; await request(`/api/tasks/${taskId}/${action}`, { method: "POST", body: "{}" }); await loadSession(state.selectedSessionId); render(); }); }); } function renderApprovals() { const approvals = approvalsForSelectedSession(); elements.approvalList.innerHTML = approvals.length ? approvals .map( (approval) => `
${escapeHtml(approval.summary)} ${escapeHtml(approval.status)}
risk: ${escapeHtml(approval.riskLevel)}
`, ) .join("") : `

当前没有待审批项。

`; elements.approvalList.querySelectorAll("[data-approval-id]").forEach((button) => { button.addEventListener("click", async () => { const approvalId = button.dataset.approvalId; const approved = button.dataset.approved === "true"; await request(`/api/approvals/${approvalId}/respond`, { method: "POST", body: JSON.stringify({ approved, responder: "web-user" }), }); await loadSession(state.selectedSessionId); render(); }); }); } function renderEvents() { const events = eventsForSelectedSession(); elements.eventList.innerHTML = events.length ? events .map( (event) => `
${escapeHtml(event.type)} ${new Date(event.timestamp).toLocaleTimeString()}
${escapeHtml(JSON.stringify(event.payload, null, 2))}
`, ) .join("") : `

当前没有事件。

`; } function render() { renderSessions(); renderWorkers(); renderSessionHeader(); renderMessages(); renderTasks(); renderApprovals(); renderEvents(); } async function loadBootstrap() { const bootstrap = await request("/api/bootstrap"); state.sessions = bootstrap.sessions; state.messages = bootstrap.messages; state.tasks = bootstrap.tasks; state.workers = bootstrap.workers; state.approvals = bootstrap.approvals; state.events = bootstrap.events; if (!state.selectedSessionId && state.sessions[0]) { state.selectedSessionId = state.sessions[0].id; } } async function loadSession(sessionId) { if (!sessionId) return; const details = await request(`/api/sessions/${sessionId}`); state.sessions = state.sessions.map((session) => (session.id === sessionId ? details.session : session)); state.messages = [ ...state.messages.filter((message) => message.sessionId !== sessionId), ...details.messages, ]; state.tasks = [ ...state.tasks.filter((task) => task.sessionId !== sessionId), ...details.tasks, ]; state.approvals = [ ...state.approvals.filter((approval) => approval.sessionId !== sessionId), ...details.approvals, ]; } elements.createSessionForm.addEventListener("submit", async (event) => { event.preventDefault(); const title = elements.sessionTitleInput.value.trim(); const details = await request("/api/sessions", { method: "POST", body: JSON.stringify({ title }), }); state.sessions.unshift(details.session); state.selectedSessionId = details.session.id; await loadSession(details.session.id); elements.sessionTitleInput.value = ""; render(); }); elements.messageForm.addEventListener("submit", async (event) => { event.preventDefault(); if (!state.selectedSessionId) return; const content = elements.messageInput.value.trim(); if (!content) return; await request(`/api/sessions/${state.selectedSessionId}/messages`, { method: "POST", body: JSON.stringify({ content, channel: "web" }), }); elements.messageInput.value = ""; await loadSession(state.selectedSessionId); await loadBootstrap(); render(); }); elements.resetDemo.addEventListener("click", async () => { await request("/api/demo/reset", { method: "POST", body: "{}" }); state.sessions = []; state.messages = []; state.tasks = []; state.workers = []; state.approvals = []; state.events = []; state.selectedSessionId = null; render(); }); const stream = new EventSource("/api/events/stream"); stream.onmessage = async (event) => { const payload = JSON.parse(event.data); state.events.push(payload); if (payload.sessionId) { await loadSession(payload.sessionId); } await loadBootstrap(); render(); }; loadBootstrap().then(render).catch((error) => { console.error(error); elements.sessionSummary.textContent = error.message; });