From 26b2ba0de399ee89d989dc0e696daec7f71f21ad Mon Sep 17 00:00:00 2001 From: kill136 Date: Fri, 22 May 2026 08:22:47 +0000 Subject: [PATCH 001/134] =?UTF-8?q?feat(workbench):=20F5.i=20ChatPane=20to?= =?UTF-8?q?olbar=20=E6=AD=BB=E6=8C=89=E9=92=AE=E6=B8=85=E7=90=86=20?= =?UTF-8?q?=E2=80=94=20=E4=B8=8D=E7=95=99=20placeholder=EF=BC=8C=E5=8F=AA?= =?UTF-8?q?=E7=95=99=E7=9C=9F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 砍掉 4 个无对应功能的 placeholder:AI 增强 / 共享 / 搜索 / 更多。 把原「分享」改造成真功能「导出 Markdown」,接已有 /api/sessions/:id/export?format=md endpoint(浏览器 a 标签 download,无后端改动)。 产品原则:toolbar 任何按钮点击必须有真反馈,否则一律删。 Co-Authored-By: Claude Opus 4.7 --- .../src/pages/ManusWorkbench/ChatPane.tsx | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/web/client/src/pages/ManusWorkbench/ChatPane.tsx b/src/web/client/src/pages/ManusWorkbench/ChatPane.tsx index a0785e77..e13396e6 100644 --- a/src/web/client/src/pages/ManusWorkbench/ChatPane.tsx +++ b/src/web/client/src/pages/ManusWorkbench/ChatPane.tsx @@ -2,11 +2,12 @@ * F0 中栏:对话面板(静态 mock,对齐 Manus 1.6 Lite 截图)。 * * 从上到下: - * 1. 顶部 toolbar: 模型选择器 + AI 按钮 + 分享 / 共享 / ... + 代码/历史/工具 - * 2. 对话区:消息流 + "Manus 将在你回复后继续工作" 看门人 waiting 提示 - * 3. 底部 composer:输入框 + tool 工具栏 + 发送 + * 1. 顶部 toolbar:导出 Markdown + 全屏切换(F5.i 清理后只剩两个真功能按钮) + * 2. 对话区:消息流 + agentStatus + planSteps + Blueprint/Deliverable 卡片 + * 3. 底部 composer:mode chip + 输入框 + 工具栏 + 发送 * - * F1+ 接 ws / messages / 真发送链路;现阶段固定显示截图同款内容。 + * 设计原则:不留死按钮 — toolbar 任何按钮点击必须有真反馈, + * 没对应功能的 placeholder 一律删(F5.i 砍 AI 增强 / 共享 / 搜索 / 更多)。 */ import React, { useState, useEffect, useRef, useCallback } from 'react'; import styles from './ManusWorkbench.module.css'; @@ -984,25 +985,31 @@ export const ChatPane: React.FC = ({ const hasMessages = messages.length > 0; return ( <> - {/* 顶部 toolbar — F5.f 砍掉 Manus 风「Manus 1.6 Lite ▾」套餐 picker。 - 原因:axon 不做 Lite/Pro/Max 套餐分级商业化;model 切换走右栏 settings tab, - 不占顶部空间。 */} + {/* 顶部 toolbar — F5.f/i 清理后只剩两个真功能按钮 + spacer。 + F5.f:砍掉 Manus 风「Manus 1.6 Lite ▾」套餐 picker(axon 不做分级)。 + F5.i:砍掉 4 个无对应功能的 placeholder(AI 增强 / 共享 / 搜索 / 更多), + 把原「分享」改造成真功能「导出 Markdown」(接 /api/sessions/:id/export)。 + 这样符合「不留死按钮」产品原则 — 用户点了什么按钮就必须有真反馈。 */}
- - - - - +
{tplLoading && (
加载模板池 …
)} @@ -757,10 +800,22 @@ export const TaskListPane: React.FC = ({ role="button" tabIndex={0} title={`${t.title} — 点击新建 session 并预填`} + data-official={t.official} >
{coverEmoji}
{t.title}
{t.description}
+ {!t.official && ( + + )}
); })} @@ -806,6 +861,16 @@ export const TaskListPane: React.FC = ({ + + {/* F5.h.3:新建模板 modal — 渲染在组件根,挂 fixed overlay 不影响左栏布局 */} + setTplModalOpen(false)} + onCreated={() => { + // 重新拉取列表 — 简单粗暴但准确,避免本地 push 与后端 store 不一致 + fetchTemplates(); + }} + /> ); }; diff --git a/src/web/client/src/pages/ManusWorkbench/TemplateEditorModal.tsx b/src/web/client/src/pages/ManusWorkbench/TemplateEditorModal.tsx new file mode 100644 index 00000000..fd5d5cab --- /dev/null +++ b/src/web/client/src/pages/ManusWorkbench/TemplateEditorModal.tsx @@ -0,0 +1,248 @@ +/** + * F5.h.3 TemplateEditorModal — 用户自建 SessionTemplate 的表单 modal。 + * + * 设计:极简单表单,5 个必填字段 + 1 个 cover emoji 选择器。 + * - title / description / templateType / mode / prompt(必填) + * - cover(emoji,可选,默认 📋) + * + * templateType 决定 mode 候选集(IMAGE→design / SLIDE→slides / WEBSITE→website)。 + * 用户不应自由填 mode,避免出现 mode 与 templateType 失配(如 IMAGE 模板挂 website mode)。 + * + * 提交时调 POST /api/templates,成功后 onCreated 上抛创建好的模板, + * 由父组件刷新列表。验证错误直接 alert 给用户(极简错误处理)。 + */ +import React, { useState } from 'react'; +import styles from './ManusWorkbench.module.css'; + +type TemplateType = + | 'SESSION_TEMPLATE_TYPE_IMAGE' + | 'SESSION_TEMPLATE_TYPE_SLIDE' + | 'SESSION_TEMPLATE_TYPE_WEBSITE'; + +interface CreatedTemplate { + uid: string; + title: string; + description: string; + templateType: TemplateType; + mode: string; + cover: string; + prompt: string; + official: boolean; +} + +interface TemplateEditorModalProps { + /** modal 是否打开 */ + open: boolean; + /** 关闭(cancel 或 ✕) */ + onClose: () => void; + /** 创建成功回调 — 把新模板上抛给父,让父刷新列表 */ + onCreated: (tpl: CreatedTemplate) => void; +} + +/** type → mode 候选 — 严格对齐 modeRegistry,避免失配 */ +const MODE_OPTIONS_BY_TYPE: Record> = { + SESSION_TEMPLATE_TYPE_IMAGE: [ + { id: 'design', label: '视觉设计' }, + ], + SESSION_TEMPLATE_TYPE_SLIDE: [ + { id: 'slides', label: '幻灯片' }, + ], + SESSION_TEMPLATE_TYPE_WEBSITE: [ + { id: 'website', label: '网站' }, + { id: 'app', label: '应用程序' }, + { id: 'desktop-app', label: '桌面应用' }, + ], +}; + +/** 8 个候选 cover emoji(覆盖常见场景) */ +const COVER_EMOJI_CHOICES = ['📋', '🎨', '🚀', '📊', '🌐', '💼', '📝', '🎮']; + +export const TemplateEditorModal: React.FC = ({ + open, + onClose, + onCreated, +}) => { + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [templateType, setTemplateType] = useState('SESSION_TEMPLATE_TYPE_WEBSITE'); + const [mode, setMode] = useState('website'); + const [cover, setCover] = useState('📋'); + const [prompt, setPrompt] = useState(''); + const [submitting, setSubmitting] = useState(false); + + // type 变化时把 mode 重置为该 type 的第一个候选 + const handleTypeChange = (t: TemplateType): void => { + setTemplateType(t); + const firstMode = MODE_OPTIONS_BY_TYPE[t][0]?.id || ''; + setMode(firstMode); + }; + + const resetForm = (): void => { + setTitle(''); + setDescription(''); + setTemplateType('SESSION_TEMPLATE_TYPE_WEBSITE'); + setMode('website'); + setCover('📋'); + setPrompt(''); + }; + + const handleSubmit = async (): Promise => { + // 前端基本校验(UX 层) + if (!title.trim()) { + alert('请填写模板名'); + return; + } + if (!prompt.trim()) { + alert('请填写默认 prompt(点击模板时预填到 composer)'); + return; + } + setSubmitting(true); + try { + const resp = await fetch('/api/templates', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: title.trim(), + description: description.trim(), + templateType, + mode, + prompt: prompt.trim(), + cover: `emoji:${cover}`, + }), + }); + const json = await resp.json(); + if (!resp.ok || !json.success) { + throw new Error(json.error || `HTTP ${resp.status}`); + } + onCreated(json.data); + resetForm(); + onClose(); + } catch (err) { + alert(`创建失败:${err instanceof Error ? err.message : String(err)}`); + } finally { + setSubmitting(false); + } + }; + + if (!open) return null; + + const modeOptions = MODE_OPTIONS_BY_TYPE[templateType]; + + return ( +
+
e.stopPropagation()}> +
+ 新建模板 + +
+ +
+ {/* 类别 + cover emoji 一行 */} +
+ + +
+ +
+ + +
+ +
+ +
+ {COVER_EMOJI_CHOICES.map((emo) => ( + + ))} +
+
+ +
+ + setTitle(e.target.value)} + placeholder="如:周报生成器" + /> +
+ +
+ + setDescription(e.target.value)} + placeholder="一句话说明用途(卡片副标题)" + /> +
+ +
+ +