/* global document, window, localStorage, crypto */ (() => { const key = 'goodbuddy-workspace-demo-v1'; const people = [{ id: 'lin', name: '林知远', job: '产品负责人' }, { id: 'zhang', name: '张明', job: '技术顾问' }, { id: 'chen', name: '陈悦', job: '产品设计师' }]; const modes = { human: '本人处理', auto: '分身先处理', draft: '分身起草,本人确认' }; const initial = () => ({ spaces: [ { id: 'product', name: '产品设计', members: ['lin', 'zhang', 'chen'], modes: { lin: 'human', zhang: 'auto', chen: 'draft' }, messages: [] }, { id: 'research', name: 'AI 应用研究', members: ['lin', 'zhang'], modes: { lin: 'human', zhang: 'human' }, messages: [] } ] }); const read = () => { try { return JSON.parse(localStorage.getItem(key)) || initial(); } catch { return initial(); } }; const escape = value => String(value).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]); const person = id => people.find(p => p.id === id); let data = read(); let spaceId = data.spaces[0].id; let actor = 'lin'; let recipient = ''; let status = ''; let draft = ''; let contexts = []; let requireHuman = false; const current = () => data.spaces.find(s => s.id === spaceId); const options = (value) => Object.entries(modes).map(([id, label]) => ``).join(''); function save(next) { try { localStorage.setItem(key, JSON.stringify(next)); data = next; return true; } catch { status = '浏览器存储不可用,更改未保存。'; return false; } } const contextItems = () => [ { id: 'brief', title: `${current().name} · 空间讨论摘要`, text: `空间:${current().name}\n目标:验证共享知识与数字分身协作的最小可用流程。\n待讨论:如何区分分身回复和本人的确认。` }, { id: 'doc', title: '共享设置草稿 · 空间文档', text: '知识库与分身复用已授权资料。监督回顾的回答使用权限与原文查看权限分开设置。' } ]; function render() { const space = current(); document.querySelector('#app').innerHTML = `
明日工作室 / 共享工作空间
设置 ↗
TEAM WORKSPACE

${escape(space.name)}

把问题交给具体的人,由本人或获准的数字分身接着处理。

当前以 ${person(actor).name} 的身份体验。切换成员可演示接收、确认草稿和本人接手;这是本地角色模拟,不是真实登录。
${space.messages.length ? space.messages.map(thread).join('') : '

从一个明确的问题开始

在下方 @ 一位空间成员,选择要附带的资料。消息和处理结果会留在这里。

'}
${composer()}
${escape(status)}

新建共享工作空间

加入空间的成员

${people.map(p => ``).join('')}

成员立即加入仅为演示;真实邀请和组织管理尚未接入。

`; bind(); } function thread(message) { const mine = actor === message.to; const labels = { pending: '待本人处理', draft: '待本人确认草稿', replied: '分身已回复', handling: '本人已接手', done: '本人已回复' }; return `
${person(message.from).name} → @${person(message.to).name}${labels[message.status]}
${escape(message.text)}
${message.requireHuman ? '需要本人确认 · ' : ''}接收方式:${modes[message.mode]} · 附带 ${message.contexts.length} 项上下文
${message.contexts.length ? `
查看发送时的上下文${message.contexts.map(c => `

${escape(c.title)}

${escape(c.text)}
`).join('')}
` : ''}${message.replies.map(r => `

${person(message.to).name}${r.ai ? '的数字分身 · AI 示例回复' : ' · 本人(模拟)'}

${escape(r.text)}

`).join('')}${message.status === 'draft' && mine ? `

分身草稿 · 仅接收者可见

` : ''}${mine && message.status !== 'done' ? `
${message.status !== 'handling' ? `` : ''}
${message.status === 'handling' ? `
` : ''}` : ''}
`; } function composer() { return `

发起协作

${recipient ? '@' + person(recipient).name : '尚未指定接收人'}
选择附带上下文 · ${contexts.length} 项
${contextItems().map(c => ``).join('')}
`; } function bind() { function switchSpace(id) { spaceId = id; if (!current().members.includes(actor)) actor = current().members[0]; recipient = ''; draft = ''; contexts = []; requireHuman = false; status = ''; render(); } document.querySelectorAll('[data-space]').forEach(el => el.onclick = () => switchSpace(el.dataset.space)); document.querySelector('#space-select').onchange = e => switchSpace(e.target.value); document.querySelector('#actor').onchange = e => { actor = e.target.value; recipient = ''; draft = ''; contexts = []; requireHuman = false; render(); }; document.querySelectorAll('[data-create]').forEach(el => el.onclick = () => document.querySelector('#create-dialog').showModal()); document.querySelector('#cancel-create').onclick = () => document.querySelector('#create-dialog').close(); document.querySelector('#create-form').onsubmit = e => { e.preventDefault(); const input = document.querySelector('#space-name'); const name = input.value.trim(); if (!name || data.spaces.some(s => s.name === name)) { input.setCustomValidity('请输入不重复的空间名称'); input.reportValidity(); return; } const members = [...document.querySelectorAll('[name=member]:checked')].map(el => el.value); const id = crypto.randomUUID(); const next = read(); next.spaces.push({ id, name, members, modes: Object.fromEntries(members.map(id => [id, 'human'])), messages: [] }); if (save(next)) switchSpace(id); else render(); }; document.querySelector('#space-name').oninput = e => e.target.setCustomValidity(''); document.querySelector('#mention-toggle').onclick = () => { const list = document.querySelector('#mention-list'); list.hidden = !list.hidden; if (!list.hidden) document.querySelector('#member-search').focus(); }; document.querySelector('#member-search').oninput = e => { let count = 0; document.querySelectorAll('[data-recipient]').forEach(el => { el.hidden = !el.textContent.includes(e.target.value.trim()); if (!el.hidden) count++; }); document.querySelector('#no-members').hidden = count > 0; }; document.querySelectorAll('[data-recipient]').forEach(el => el.onclick = () => { recipient = el.dataset.recipient; document.querySelector('#recipient-label').textContent = '@' + person(recipient).name; document.querySelector('#mention-list').hidden = true; const input = document.querySelector('#message-text'); input.value = input.value.replace(/@$/, ''); draft = input.value; input.focus(); }); document.querySelector('#message-text').oninput = e => { draft = e.target.value; if (draft.endsWith('@')) document.querySelector('#mention-list').hidden = false; }; document.querySelectorAll('[data-context]').forEach(el => el.onchange = () => { contexts = [...document.querySelectorAll('[data-context]:checked')].map(el => el.dataset.context); document.querySelector('#context-count').textContent = contexts.length; }); document.querySelector('#require-human').onchange = e => requireHuman = e.target.checked; document.querySelector('#save-mode').onclick = () => { const next = read(); next.spaces.find(s => s.id === spaceId).modes[actor] = document.querySelector('#mode').value; if (save(next)) status = '处理偏好已保存,仅影响后续消息。'; render(); }; document.querySelector('#send-form').onsubmit = e => { e.preventDefault(); if (!recipient) { document.querySelector('#mention-list').hidden = false; document.querySelector('#member-search').focus(); return; } if (!draft.trim()) { document.querySelector('#message-text').focus(); return; } const next = read(); const space = next.spaces.find(s => s.id === spaceId); const mode = space.modes[recipient] || 'human'; const attached = contextItems().filter(c => contexts.includes(c.id)); const reply = attached.length ? `已收到问题。当前附带 ${attached.length} 项上下文,建议先核对资料授权范围,并明确标注分身回复与本人确认。具体方案仍需本人审阅。` : '已收到问题,但未附带具体资料。请补充需要评估的方案和验收条件,再进行具体判断。'; const human = requireHuman || mode === 'human'; space.messages.push({ id: crypto.randomUUID(), from: actor, to: recipient, text: draft.trim(), contexts: attached, requireHuman, mode, status: human ? 'pending' : mode === 'draft' ? 'draft' : 'replied', draft: !human && mode === 'draft' ? reply : '', replies: !human && mode === 'auto' ? [{ ai: true, text: reply }] : [] }); if (save(next)) { draft = ''; contexts = []; recipient = ''; requireHuman = false; status = '消息已保存在当前演示空间。'; } render(); }; function update(id, change) { const next = read(); const message = next.spaces.find(s => s.id === spaceId).messages.find(m => m.id === id); if (message.to !== actor || message.status === 'done') return; change(message); save(next); render(); } document.querySelectorAll('[data-takeover]').forEach(el => el.onclick = () => update(el.dataset.takeover, m => { m.status = 'handling'; m.draft = ''; })); document.querySelectorAll('[data-confirm]').forEach(el => el.onclick = () => { const text = document.querySelector(`[data-draft="${el.dataset.confirm}"]`).value.trim(); if (text) update(el.dataset.confirm, m => { m.status = 'done'; m.replies.push({ ai: false, text }); m.draft = ''; }); }); document.querySelectorAll('[data-reply]').forEach(el => el.onsubmit = e => { e.preventDefault(); const text = el.querySelector('textarea').value.trim(); if (text) update(el.dataset.reply, m => { m.status = 'done'; m.replies.push({ ai: false, text }); }); }); } window.mountWorkspacePreferences = () => { data = read(); const section = document.createElement('section'); section.className = 'panel workspace-preferences'; section.innerHTML = `

组织工作空间 · 我的处理偏好

明日工作室 / 林知远。与个人访客分身入口分别设置,以下偏好独立保存。

进入协作空间 ↗
${data.spaces.filter(s => s.members.includes('lin')).map(s => ``).join('')}
@ 指向你本人;可授权分身先回复或仅起草。对方要求本人确认时,优先交给你。更改仅影响后续消息。
`; document.querySelector('.content').append(section); section.querySelector('#save-preferences').onclick = () => { const next = read(); section.querySelectorAll('[data-space-mode]').forEach(el => { const s = next.spaces.find(s => s.id === el.dataset.spaceMode); if (s?.members.includes('lin')) s.modes.lin = el.value; }); section.querySelector('#preferences-status').textContent = save(next) ? '空间偏好已保存' : status; }; }; if (document.body.dataset.page === 'workspace') render(); })();