/* 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)}
`;
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 ``;
}
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();
})();