import van, { type State } from "vanjs-core"; import type { AppController } from "../../app/app-controller"; import "./conversation-pane.css"; import { ProjectPanel } from "../projects/project-panel"; const { article, button, div, h1, header, main, p, span, textarea } = van.tags; export function ConversationPane(controller: AppController): HTMLElement { const draft = van.state(""); async function submit() { const content = draft.val; if (!content.trim()) return; await controller.send(content); draft.val = ""; } return main( { class: "main-pane" }, header({ class: "mobile-header" }, "Great Agent 2"), div({ class: "pane-content" }, () => controller.selection.val.kind.startsWith("project-") ? ProjectPanel(controller) : controller.selection.val.kind === "conversation" && controller.active.val ? div( { class: "conversation-view" }, h1({ class: "conversation-title" }, controller.active.val.title), div( { class: "message-list" }, controller.active.val.messages.map((message) => article( { class: `message ${message.role}` }, span( { class: `message-role ${message.role}-role` }, message.role === "user" ? "你" : "G", ), p(message.content), ), ), () => controller.assistantDraft.val ? article( { class: "message assistant streaming" }, span({ class: "message-role assistant-role" }, "G"), p(controller.assistantDraft), ) : null, ), ) : div( { class: "onboarding" }, h1(span({ class: "spark" }, "✳"), " 今天想完成什么?"), p("从一个问题、想法或具体任务开始。"), ), ), div( { class: "composer-slot", hidden: () => !canCompose(controller) }, Composer(controller, draft, submit), ), ); } function Composer( controller: AppController, draft: State, submit: () => Promise, ): HTMLElement { return div( { class: "composer-wrap" }, div( { class: "error-banner", role: "alert", hidden: () => !controller.error.val, }, () => controller.error.val, ), div( { class: "composer" }, textarea({ "aria-label": "输入消息", placeholder: "我能帮你做些什么?", value: draft, disabled: controller.sending, oninput: (event: Event) => { draft.val = (event.target as HTMLTextAreaElement).value; }, onkeydown: (event: KeyboardEvent) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); void submit(); } }, }), div( { class: "composer-actions" }, button( { class: "attach", disabled: true, "aria-label": "添加附件" }, "+", ), button( { class: "send", disabled: () => controller.sending.val || !draft.val.trim(), onclick: () => void submit(), "aria-label": "发送消息", }, () => (controller.sending.val ? "…" : "↑"), ), ), ), div( { class: "composer-meta" }, button({ disabled: true }, () => isProjectConversation(controller) ? "项目对话" : "普通对话", ), span("DeepSeek"), ), ); } function isProjectConversation(controller: AppController): boolean { const selection = controller.selection.val; return ( selection.kind === "project-draft" || (selection.kind === "conversation" && controller.active.val?.projectId !== null) ); } function canCompose(controller: AppController): boolean { const selection = controller.selection.val; if (selection.kind === "conversation") return true; if (selection.kind === "ordinary-draft") return true; if (selection.kind === "project-draft") { return ( controller.projects.val.find( (project) => project.id === selection.projectId, )?.workspaceAvailable ?? false ); } return selection.kind === "none"; }