diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 8a4c978..288db7b 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -263,7 +263,7 @@ function findSidebar(start: HTMLElement | null): HTMLElement | null { const registryStore: { ids: string[]; listeners: Set<() => void> } = { ids: [], listeners: new Set() } /** 自定义窗口 → 宿主会话桥(apply 时注入;不可用时 CustomPane 降级提示) */ -let sessionBridge: { sessions: any; conversation: any } | null = null +let sessionBridge: { sessions: any; conversation: any; list: any } | null = null /** 自定义窗口任务:新建一个专属会话,注入工作台背景 + 用户需求,并打开该会话 */ export async function createCustomSession(projectId: string, projectName: string, requirement: string): Promise { @@ -291,6 +291,25 @@ export async function createCustomSession(projectId: string, projectName: string if (!sent) await b.conversation?.send?.(text) } +/** 把自定义需求直接发到用户选定的已有会话(默认当前会话) */ +export async function sendCustomToSession(sessionId: string, projectName: string, requirement: string): Promise { + const b = sessionBridge + if (!b) throw new Error('bridge unavailable') + const text = [ + '【工作台自定义窗口任务】', + '以下内容由 dsh-worktable(工作台)插件发送:用户正在自定义「' + projectName + '」项目中的一个窗口。', + '1. dsh-worktable 是侧边栏底部的自建「工作台」插件(官方文档没有它的说明,不了解之处可向用户提问);', + '2. 用户需求:' + requirement, + '3. 请帮助完成该窗口的自定义设计(内容/页面/实现方案),该窗口位于「' + projectName + '」项目内。', + ].join('\n') + let sent = false + if (typeof b.conversation?.sendSession === 'function') { + try { await b.conversation.sendSession(sessionId, text); sent = true } catch { sent = false } + } + try { b.sessions.open?.(sessionId) } catch {} + if (!sent) await b.conversation?.send?.(text) +} + /** 会话作用域快照(模块级;apply 里订阅 ctx.sessions.list 写入,组件与引擎只读) */ const sessionScopeStore: { snapshot: { sessionId: string; cwd: string; jobs: any[]; subagents: any[] } | null @@ -411,6 +430,19 @@ function WorktableSection(props: any) { ] }, currentProjectId: () => (splitStore.active && splitStore.spec ? splitStore.spec.id : null), + getSessions: () => { + try { + const snap = sessionBridge?.list?.getSnapshot?.() + const items = Array.isArray(snap?.items) ? snap.items : [] + const current = snap?.current ?? '' + return items.map((it: any) => ({ + id: it?.sessionId ?? it?.id ?? '', + title: it?.title ?? it?.name ?? it?.summary?.title ?? (it?.sessionId ?? it?.id ?? ''), + isCurrent: (it?.sessionId ?? it?.id) === current, + })) + } catch { return [] } + }, + sendToSession: (sessionId, projectName, requirement) => sendCustomToSession(sessionId, projectName, requirement), submit: (projectId, projectName, requirement) => createCustomSession(projectId, projectName, requirement), }, }) @@ -1325,8 +1357,8 @@ function WorktableSection(props: any) { export const inject = ['slots', 'locale', 'sessions', 'conversation'] export function apply(ctx: any) { - // 自定义窗口 → 新建会话桥:保存宿主 sessions/conversation 服务引用(模块级) - sessionBridge = { sessions: ctx.sessions ?? null, conversation: ctx.conversation ?? null } + // 自定义窗口 → 宿主会话桥:保存 sessions/conversation/list 服务引用(模块级) + sessionBridge = { sessions: ctx.sessions ?? null, conversation: ctx.conversation ?? null, list: ctx.sessions?.list ?? null } ctx.effect(() => { const style = document.createElement('style') diff --git a/01_content/src/client/locales.ts b/01_content/src/client/locales.ts index 1db4463..bba2237 100644 --- a/01_content/src/client/locales.ts +++ b/01_content/src/client/locales.ts @@ -62,7 +62,14 @@ export const zh = { 'custom.hint': '发送后将自动新建一个会话,专门负责这个窗口的自定义设计。', 'custom.placeholder': '告诉 AI 你想让这个窗口做什么,例如:做一个展示我旅行足迹的时间线页面…', 'custom.project': '所属项目', + 'custom.modeNew': '新建对话', + 'custom.modeSend': '发送到会话', + 'custom.session': '选择会话', + 'custom.sessionCurrent': '(当前)', + 'custom.hintSend': '内容将直接发送到你选择的会话(默认当前会话),用于讨论这个窗口的自定义设计。', 'custom.send': '发送并新建对话', + 'custom.sendToSession': '发送到该会话', + 'custom.sent': '已发送到会话', 'custom.done': '已新建专属对话', 'custom.doneHint': 'AI 正在新会话中为你设计这个窗口。关闭本标签即可返回窗口选择。', 'custom.fail': '创建对话失败', @@ -151,7 +158,14 @@ export const en = { 'custom.hint': 'Sending will create a new conversation dedicated to designing this pane.', 'custom.placeholder': 'Tell the AI what you want this pane to do, e.g. a timeline page of my travels…', 'custom.project': 'Project', + 'custom.modeNew': 'New conversation', + 'custom.modeSend': 'Send to a conversation', + 'custom.session': 'Conversation', + 'custom.sessionCurrent': ' (current)', + 'custom.hintSend': 'The content will be sent directly to the conversation you pick (current by default).', 'custom.send': 'Send & create conversation', + 'custom.sendToSession': 'Send to this conversation', + 'custom.sent': 'Sent to the conversation', 'custom.done': 'Conversation created', 'custom.doneHint': 'The AI is designing your pane in the new conversation. Close this tab to return to the pane picker.', 'custom.fail': 'Failed to create conversation', diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index 3571d93..31090e1 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -185,11 +185,13 @@ type SplitEnv = { getScope: () => SplitScope | null getJobs: () => SplitJob[] getSubagents: () => any[] - /** 自定义窗口:项目列表 + 当前项目 + 提交创建专属会话 */ + /** 自定义窗口:项目列表 + 会话列表 + 当前项目 + 新建会话 / 发送到已有会话 */ custom?: { getProjects: () => { id: string; name: string }[] currentProjectId: () => string | null + getSessions: () => { id: string; title: string; isCurrent: boolean }[] submit: (projectId: string, projectName: string, requirement: string) => Promise + sendToSession: (sessionId: string, projectName: string, requirement: string) => Promise } } let splitEnv: SplitEnv | null = null @@ -1243,21 +1245,27 @@ function TextViewer(props: { path: string; fileUrl: string; isMd: boolean }) { ) } -/** 自定义窗口:居中对话框。用户写下需求 + 选择所属项目 → 发送 = 新建专属会话并由 AI 辅助设计。 */ +/** 自定义窗口:居中对话框。两种模式:新建专属会话 / 发送到已有会话(默认当前会话)。 */ function CustomPane() { const custom = splitEnv?.custom const [requirement, setRequirement] = useState('') const [projectId, setProjectId] = useState(null) const [projects, setProjects] = useState<{ id: string; name: string }[]>(() => custom?.getProjects?.() ?? []) + const [mode, setMode] = useState<'new' | 'existing'>('new') + const [sessions, setSessions] = useState<{ id: string; title: string; isCurrent: boolean }[]>(() => custom?.getSessions?.() ?? []) + const [sessionId, setSessionId] = useState(null) const [busy, setBusy] = useState(false) const [done, setDone] = useState(false) const [fail, setFail] = useState('') - // 默认项目 = 当前工作区所属项目(用户刚在用的项目) + // 默认项目 = 当前工作区所属项目;默认会话 = 当前会话 useEffect(() => { const list = custom?.getProjects?.() ?? [] setProjects(list) const cur = custom?.currentProjectId?.() ?? null setProjectId(cur && list.some((p) => p.id === cur) ? cur : (list[0]?.id ?? null)) + const slist = custom?.getSessions?.() ?? [] + setSessions(slist) + setSessionId(slist.find((s) => s.isCurrent)?.id ?? slist[0]?.id ?? null) }, [custom]) const submit = async () => { const text = requirement.trim() @@ -1266,7 +1274,12 @@ function CustomPane() { setFail('') try { const proj = projects.find((p) => p.id === projectId) - await custom.submit(projectId, proj?.name ?? projectId, text) + const pname = proj?.name ?? projectId + if (mode === 'new') await custom.submit(projectId, pname, text) + else { + if (!sessionId) return + await custom.sendToSession(sessionId, pname, text) + } setDone(true) } catch (e) { setFail(String(e)) @@ -1281,7 +1294,7 @@ function CustomPane() { return (
✅ -

{T('custom.done')}

+

{mode === 'new' ? T('custom.done') : T('custom.sent')}

{T('custom.doneHint')}

) @@ -1290,7 +1303,11 @@ function CustomPane() {
✨ {T('custom.title')} -

{T('custom.hint')}

+
+ + +
+

{mode === 'new' ? T('custom.hint') : T('custom.hintSend')}