ai_co_discuss 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -9,7 +9,7 @@ import { parseArgs } from 'node:util';
9
9
  import { atomic_json } from './discussion_adapters.mjs';
10
10
  import { launch_settings, save_launch_settings } from './discussion_launch.mjs';
11
11
  import { assert_shared_home, service_home } from './discussion_service.mjs';
12
- import { install_skills } from './discussion_skills.mjs';
12
+ import { SKILLS, install_skills, remove_legacy_skills } from './discussion_skills.mjs';
13
13
  import { ask, checkbox_prompt, intro, outro, pad, short_path, spinner, step, styled, text_width, tone } from './discussion_tui.mjs';
14
14
  export { checkbox_prompt } from './discussion_tui.mjs';
15
15
 
@@ -22,10 +22,13 @@ const ATTENTION_EXIT_CODE = 2;
22
22
  const AGY_SETTINGS = ['.gemini', 'antigravity-cli', 'settings.json'];
23
23
  const AGY_RULE_RECORD = 'agy-permission.json';
24
24
  const AGY_SETTINGS_BACKUP = 'agy-settings.backup.json';
25
+ const SKILL_RECORD = 'skills-manifest.json';
26
+ const AGY_SKILL_CONFIG = ['.gemini', 'config', 'skills.json'];
25
27
  const CLIENTS = {
26
28
  codex: { name: 'Codex', commands: ['codex'], adapter: 'codex_cli', skills: ['.agents', 'skills'] },
27
29
  claude: { name: 'Claude', commands: ['claude'], adapter: 'claude', skills: ['.claude', 'skills'] },
28
- antigravity: { name: 'Gemini', commands: ['agy'], adapter: 'antigravity_cli', skills: ['.gemini', 'antigravity-cli', 'skills'] },
30
+ antigravity: { name: 'Gemini', commands: ['agy'], adapter: 'antigravity_cli', skills: ['.gemini', 'config', 'skills'],
31
+ legacy_skills: ['.gemini', 'antigravity-cli', 'skills'] },
29
32
  cursor: { name: 'Grok(Cursor CLI)', commands: ['agent', 'cursor-agent'], skills: ['.cursor', 'skills'] },
30
33
  };
31
34
  const OPTIONS = { yes: { type: 'boolean' }, clients: { type: 'string' }, python: { type: 'string' },
@@ -222,16 +225,58 @@ export function remove_legacy_agy_rules(home, user_home) {
222
225
  return { client: 'Gemini', status: next.length !== allow.length ? 'removed' : 'unchanged', path };
223
226
  }
224
227
 
228
+ /** Follow a linked skill directory so clients sharing one folder install a single copy. */
229
+ function real_directory(path) {
230
+ try { return realpathSync.native(path); } catch { return resolve(path); }
231
+ }
232
+
233
+ /** True when an entry's include_only/exclude patterns let agy load every discussion skill; invalid patterns count as filtering. */
234
+ function entry_loads_skills(entry) {
235
+ const matches = (patterns, name) => patterns.some(pattern => {
236
+ try { return new RegExp(`^(?:${pattern})$`).test(name); } catch { return true; }
237
+ });
238
+ const include = Array.isArray(entry.include_only) ? entry.include_only : null;
239
+ const exclude = Array.isArray(entry.exclude) ? entry.exclude : [];
240
+ return SKILLS.every(name => (!include || matches(include, name)) && !matches(exclude, name));
241
+ }
242
+
243
+ /** Absolute directories agy loads the discussion skills from through its global skills.json; agy leaves ~/ unexpanded on Windows. */
244
+ function agy_skill_entries(user_home) {
245
+ const path = join(user_home, ...AGY_SKILL_CONFIG);
246
+ let config;
247
+ try { config = JSON.parse(readFileSync(path, 'utf8')); } catch { return []; }
248
+ return (Array.isArray(config?.entries) ? config.entries : [])
249
+ .filter(entry => typeof entry?.path === 'string' && entry_loads_skills(entry)).map(entry => entry.path)
250
+ .map(value => process.platform !== 'win32' && value.startsWith('~/') ? join(user_home, value.slice(2)) : value)
251
+ .filter(value => isAbsolute(value)).map(real_directory);
252
+ }
253
+
254
+ /** Pick the directory each client really reads; agy reuses a skills.json entry instead of its own folder. */
255
+ function skill_targets(selected, user_home) {
256
+ const own = new Map(selected.map(client => [client.key, real_directory(join(user_home, ...client.skills))]));
257
+ const entries = agy_skill_entries(user_home);
258
+ return selected.map(client => {
259
+ const directory = own.get(client.key);
260
+ if (client.key !== 'antigravity' || !entries.length) return { client, directory, own: directory };
261
+ const others = selected.filter(item => item.key !== client.key).map(item => own.get(item.key));
262
+ return { client, directory: entries.find(entry => others.includes(entry)) || entries[0], own: directory };
263
+ });
264
+ }
265
+
225
266
  /** Save the selected effective profiles and copy complete skills without overwriting edits. */
226
267
  async function configure_clients(home, selected, options, existing) {
227
268
  const participants = { ...existing };
228
269
  const skills = [], profiles = [];
229
270
  const user_home = resolve(options['skills-home'] || homedir());
230
- for (const client of selected) {
271
+ const record = join(home, SKILL_RECORD), installed = new Map();
272
+ for (const { client, directory, own } of skill_targets(selected, user_home)) {
231
273
  const profile = await choose_profile(client, participants[client.name], options);
232
274
  profiles.push(profile);
233
275
  if (profile.status === 'configured') participants[client.name] = { ...participants[client.name], ...profile.effective };
234
- skills.push({ client: client.name, results: install_skills(join(user_home, ...client.skills)) });
276
+ if (!installed.has(directory)) installed.set(directory, install_skills(directory, record));
277
+ const retired = [client.legacy_skills && real_directory(join(user_home, ...client.legacy_skills)), own]
278
+ .filter(path => path && path !== directory);
279
+ skills.push({ client: client.name, results: installed.get(directory), legacy: retired.flatMap(path => remove_legacy_skills(path, record)) });
235
280
  }
236
281
  save_launch_settings(home, { participants });
237
282
  const legacy = remove_legacy_agy_rules(home, user_home);
@@ -273,6 +318,9 @@ function setup_issues(result) {
273
318
  for (const group of result.skills) for (const skill of group.results) {
274
319
  if (skill.status === 'conflict') issues.push(`${group.client} 技能冲突,已保留 ${skill.path};请核对并移走需替换的旧副本,再运行 setup。`);
275
320
  }
321
+ for (const group of result.skills) for (const skill of group.legacy) {
322
+ if (skill.status === 'kept') issues.push(`${group.client} 旧技能目录中的副本有改动,已保留 ${skill.path};软件会同时加载新旧两份,请核对后删除。`);
323
+ }
276
324
  for (const permission of result.permissions) {
277
325
  if (permission.status === 'invalid') issues.push(`${permission.client} 设置文件无法解析,未能移除旧版全局放行规则:${permission.path};修复后重新运行 setup,否则全自动讨论会拒绝交付给 agy。`);
278
326
  }
@@ -1,14 +1,14 @@
1
- /** Install complete skills without replacing an unmanaged or locally edited copy. */
1
+ /** Install complete skills without replacing an unmanaged or locally edited copy; hashes live in the service home. */
2
2
  import { createHash } from 'node:crypto';
3
3
  import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'node:fs';
4
- import { join, resolve } from 'node:path';
4
+ import { dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
 
7
7
  import { atomic_json } from './discussion_adapters.mjs';
8
8
 
9
9
  const SOURCE = fileURLToPath(new URL('../.agents/skills/', import.meta.url));
10
- const SKILLS = ['discuss-host', 'discuss-participant', 'discuss-report'];
11
- const MANIFEST = '.codiscuss-skills.json';
10
+ export const SKILLS = ['discuss-host', 'discuss-participant', 'discuss-report'];
11
+ const LEGACY_MANIFEST = '.codiscuss-skills.json';
12
12
 
13
13
  /** Hash names and contents, rejecting links instead of following them outside a skill. */
14
14
  function tree_hash(directory) {
@@ -41,15 +41,49 @@ function install_skill(root, name, previous) {
41
41
  return { name, status: previous ? 'updated' : 'installed', path: destination, hash };
42
42
  }
43
43
 
44
+ /** Read hashes recorded for one directory, folding in the copy older versions kept inside it. */
45
+ function recorded_hashes(record, root) {
46
+ const data = existsSync(record) ? JSON.parse(readFileSync(record, 'utf8')) : {};
47
+ const legacy = join(root, LEGACY_MANIFEST);
48
+ return { data, previous: { ...(existsSync(legacy) ? JSON.parse(readFileSync(legacy, 'utf8')) : {}), ...data[root] } };
49
+ }
50
+
51
+ /** Save the record outside skill directories and drop the in-directory file older versions wrote. */
52
+ function save_record(record, root, data) {
53
+ mkdirSync(dirname(record), { recursive: true });
54
+ atomic_json(record, data);
55
+ rmSync(join(root, LEGACY_MANIFEST), { force: true });
56
+ }
57
+
44
58
  /** Install all three skills together and remember only copies this installer manages. */
45
- export function install_skills(directory) {
59
+ export function install_skills(directory, record) {
46
60
  const root = resolve(directory);
47
61
  mkdirSync(root, { recursive: true });
48
- const manifest = join(root, MANIFEST);
49
- const previous = existsSync(manifest) ? JSON.parse(readFileSync(manifest, 'utf8')) : {};
62
+ const { data, previous } = recorded_hashes(record, root);
50
63
  const results = SKILLS.map(name => install_skill(root, name, previous[name]));
51
64
  const managed = { ...previous };
52
65
  for (const result of results) if (result.hash) managed[result.name] = result.hash;
53
- atomic_json(manifest, managed);
66
+ save_record(record, root, { ...data, [root]: managed });
54
67
  return results.map(({ hash, ...result }) => result);
55
68
  }
69
+
70
+ /** Remove unmodified copies a previous install left in a retired directory; edited copies stay. */
71
+ export function remove_legacy_skills(directory, record) {
72
+ const root = resolve(directory);
73
+ if (!existsSync(root)) return [];
74
+ const { data, previous } = recorded_hashes(record, root);
75
+ if (!Object.keys(previous).length) return [];
76
+ const results = SKILLS.filter(name => previous[name] && existsSync(join(root, name))).map(name => {
77
+ const path = join(root, name);
78
+ let current;
79
+ try { current = tree_hash(path); } catch { return { name, status: 'kept', path }; }
80
+ if (current !== previous[name]) return { name, status: 'kept', path };
81
+ rmSync(path, { recursive: true });
82
+ return { name, status: 'removed', path };
83
+ });
84
+ const kept = Object.fromEntries(results.filter(result => result.status === 'kept').map(result => [result.name, previous[result.name]]));
85
+ const { [root]: _, ...rest } = data;
86
+ save_record(record, root, Object.keys(kept).length ? { ...rest, [root]: kept } : rest);
87
+ if (!readdirSync(root).length) rmSync(root, { recursive: true });
88
+ return results;
89
+ }
@@ -12,7 +12,9 @@ export const ROUTE_CONFIDENCE = 0.7;
12
12
  /** Waking everyone or no one costs more when wrong (N runs, or an ignored message), so it needs more certainty. */
13
13
  export const ROUTE_WIDE_CONFIDENCE = 0.85;
14
14
  /** Bump when the routing question or its option descriptions change, so ratings can be compared across wordings. */
15
- export const ROUTE_PROMPT_VERSION = 6;
15
+ export const ROUTE_PROMPT_VERSION = 7;
16
+ /** The same for the three discussion questions (interjection, next speaker, next round). */
17
+ export const DISCUSSION_PROMPT_VERSION = 2;
16
18
  const HOST_CHOICE = '__host__';
17
19
  const NOTE_CHOICE = '__note__';
18
20
  const ALL_CHOICE = '__all__';
@@ -101,6 +103,11 @@ function judged_of(state, questions, answers) {
101
103
  return { detail: detail_of(questions, answers), input: { state, questions } };
102
104
  }
103
105
 
106
+ /** Who has and has not spoken this round, so a decision knows who the plan still owes a turn. */
107
+ function round_progress(spoken, waiting) {
108
+ return `本轮已发言:${spoken.join('、') || '无'};本轮尚未发言:${waiting.join('、') || '无'}。`;
109
+ }
110
+
104
111
  /** Say which option the provider leaned to when it was not sure enough, so the transcript shows how close it was. */
105
112
  function unsure_label(lean) {
106
113
  return lean ? `倾向${lean},把握不足,交给主持人` : '拿不准,交给主持人';
@@ -152,7 +159,7 @@ export async function route_free_message(home, scheduler, { title, goal, context
152
159
  { value: ALL_CHOICE, description: '全体成员(含主持人)各自回应一次,彼此不需要看到别人的回答;只在要求简单明确、每人独立回应一次就算完成时选,如“大家打个招呼”“各自报一下在用的模型”' },
153
160
  { value: NONE_CHOICE, description: '无需回应:只是确认、致谢或旁注,不需要任何人回答' }];
154
161
  const questions = [{ type: 'choice', name: 'handler', choices,
155
- instructions: '这条用户消息没有用 @ 点名成员。判断它的回应对象,按下面的顺序:1)正文叫了某位成员的名字,或在向其提问(包括“X在吗”“X还在吗”“X你怎么看”),选该成员,即使上一条是别人的回答;2)正文没有指向任何人时,看它是否在延续某位成员的回答(追问、要求修改、引用,或“回复”指向其消息),或在补充、纠正一条正由某位成员处理的消息,是则选该成员,是主持人就选主持人;3)符合“全体”选项的条件时选全体;只出现“大家”“都”“各自”“一起”等字眼不够;4)单独的问候或闲聊(如“hi”)既没有指向某位成员,也没有要求大家一起回应,选主持人;5)只是确认、致谢或旁注选无需回应;6)对象不明确,或要讨论、互相回应、分工、排先后时选主持人,由主持人安排。' }];
162
+ instructions: '这条用户消息没有用 @ 点名成员。判断它的回应对象,按下面的顺序:1)正文叫了某位成员的名字,或在向其提问(包括“X在吗”“X还在吗”“X你怎么看”),选该成员,即使上一条是别人的回答;同时提到几位成员时,选被直接称呼、要求其回应或做事的那位(如“A 你让 B 去改”是在对 A 说);2)正文没有指向任何人时,看它是否在延续某位成员的回答(追问、要求修改、引用,或“回复”指向其消息),或在补充、纠正一条正由某位成员处理的消息,是则选该成员,是主持人就选主持人;3)符合“全体”选项的条件时选全体;只出现“大家”“都”“各自”“一起”等字眼不够;4)单独的问候或闲聊(如“hi”)既没有指向某位成员,也没有要求大家一起回应,选主持人;5)只是确认、致谢或旁注选无需回应;6)对象不明确,或要讨论、互相回应、分工、排先后时选主持人,由主持人安排。' }];
156
163
  const roster = [`${host.name}(主持人)`, ...members.map(member => member.name)].join('、');
157
164
  const state = scheduler_state(title, context, message, goal, '', { roster }, ROUTE_CONTEXT_MESSAGES);
158
165
  const answers = await decide(home, scheduler, state, questions, fetch_impl);
@@ -187,12 +194,13 @@ export async function decide_group_order(home, scheduler, { title, goal, context
187
194
  * Decide one interjection in a discussion: continue the plan (note), ask one expert (ask) or wake the host (host).
188
195
  * Anything but a confident note or ask is returned as host.
189
196
  */
190
- export async function decide_interjection(home, scheduler, { title, goal, user_request, context, message, host, experts, ...extra }, fetch_impl) {
197
+ export async function decide_interjection(home, scheduler, { title, goal, user_request, context, message, host, experts, spoken = [], ...extra }, fetch_impl) {
198
+ // Only experts still owed a turn this round can be asked: asking one who has spoken would open a new round.
191
199
  const choices = [...experts.map(name => ({ value: name, description: `请 ${name} 回应这条留言:留言是向该专家(或其负责的内容)提问、质疑或补充要求` })),
192
- { value: HOST_CHOICE, description: `由主持人 ${host} 处理:留言要改变讨论目标、规则、分工、安排或结束讨论,或同时涉及多位专家` },
193
- { value: NOTE_CHOICE, description: '仅补充信息:留言只是旁注、信息或鼓励,不要求任何人回应,按当前安排继续' }];
200
+ { value: HOST_CHOICE, description: `由主持人 ${host} 处理:留言要改变讨论目标、规则、分工、安排或结束讨论,同时涉及多位专家,或针对的是不在选项里的人(如本轮已发言的专家、主持人自己)` },
201
+ { value: NOTE_CHOICE, description: '仅补充信息:留言补充背景、约束或偏好,或只是旁注、鼓励,没有要求某人立即回应;后续发言的专家都能看到它,按当前安排继续' }];
194
202
  const questions = [{ type: 'choice', name: 'handling', instructions: '讨论进行中用户发来这条留言。对照当前焦点、主持简报和安排,判断它最适合怎样处理。', choices }];
195
- const state = scheduler_state(title, context, message, goal, user_request, extra);
203
+ const state = scheduler_state(title, context, message, goal, user_request, { ...extra, progress: round_progress(spoken, experts) });
196
204
  const answers = await decide(home, scheduler, state, questions, fetch_impl);
197
205
  const judged = judged_of(state, questions, answers);
198
206
  const choice = answers.handling?.choice;
@@ -210,27 +218,19 @@ export async function decide_interjection(home, scheduler, { title, goal, user_r
210
218
  */
211
219
  export async function decide_next_speaker(home, scheduler, { title, goal, user_request, context, host, candidates, spoken = [], ...extra }, fetch_impl) {
212
220
  const recent = context.slice(-CONTEXT_MESSAGES).map(excerpt);
213
- const progress = `本轮已发言:${spoken.join('、') || '无'};本轮尚未发言:${candidates.join('、')}。`;
214
- const state = [topic_lines(title, goal, user_request, { ...extra, progress }), recent.length ? `本轮及最近发言(旧→新):\n${recent.join('\n')}` : '还没有发言。'].join('\n\n');
215
- const choices = [...candidates.map(name => ({ value: name, description: `请 ${name} 接着发言:简报的分工或接龙顺序轮到 ${name},或刚才的发言点名追问了 ${name}` })),
216
- { value: HOST_CHOICE, description: `交给主持人 ${host}:没有明确该谁发言,或需要主持人重新安排` }];
217
- // Having not spoken yet is no reason to speak: the host's plan may have ended on purpose, so the scheduler acts only on an explicit obligation.
221
+ const state = [topic_lines(title, goal, user_request, { ...extra, progress: round_progress(spoken, candidates) }), recent.length ? `本轮及最近发言(旧→新):\n${recent.join('\n')}` : '还没有发言。'].join('\n\n');
222
+ // Having not spoken yet is no reason to speak: the host's plan may have ended on purpose, so a candidate needs an explicit basis.
223
+ const choices = [...candidates.map(name => ({ value: name, description: `请 ${name} 接着发言:简报的分工或流程规定轮到 ${name},或最近的发言点名追问了 ${name}` })),
224
+ { value: HOST_CHOICE, description: `交给主持人 ${host}:简报和最近发言都没有明确规定下一位(仅仅是本轮还没发言不算依据),或需要主持人重新安排` }];
218
225
  const questions = [{ type: 'choice', name: 'speaker',
219
- instructions: '主持人预排的安排已用完或被取消。对照主持简报的分工、当前焦点和主持人最近的安排,判断下一位应请谁发言。', choices },
220
- { type: 'predicate', name: 'obligation',
221
- instructions: `是否存在明确依据要求 ${candidates.join('、')} 之一接着发言:简报分工或流程规定轮到其人,或最近发言点名追问了其人?仅仅是“本轮还没发言”不算依据。` }];
226
+ instructions: '主持人预排的安排已用完或被取消,主持人可能有意只安排到这里。对照主持简报、当前焦点和主持人最近的安排,判断下一位应请谁发言。', choices }];
222
227
  const answers = await decide(home, scheduler, state, questions, fetch_impl);
223
228
  const judged = judged_of(state, questions, answers);
224
229
  const choice = answers.speaker?.choice;
225
230
  const confidence = certainty(answers.speaker);
226
- const obligation = answers.obligation?.probability;
227
- if (candidates.includes(choice) && confidence !== null && confidence >= ROUTE_CONFIDENCE && obligation >= ROUTE_CONFIDENCE) {
228
- return { action: 'next', expert: choice, confidence, ...judged, label: `本轮继续,请 ${choice} 接着发言` };
229
- }
230
- if (typeof obligation === 'number' && obligation < ROUTE_CONFIDENCE) {
231
- return { action: 'host', confidence, ...judged, label: `有明确依据的概率 ${obligation.toFixed(2)},未达 ${ROUTE_CONFIDENCE.toFixed(2)},交给主持人安排` };
232
- }
233
- return { action: 'host', confidence, ...judged, label: choice === HOST_CHOICE && confidence >= ROUTE_CONFIDENCE ? '建议由主持人调整安排,交给主持人' : unsure_label(candidates.includes(choice) ? `请 ${choice} 发言` : choice === HOST_CHOICE ? '交给主持人' : '') };
231
+ const sure = confidence !== null && confidence >= ROUTE_CONFIDENCE;
232
+ if (sure && candidates.includes(choice)) return { action: 'next', expert: choice, confidence, ...judged, label: `本轮继续,请 ${choice} 接着发言` };
233
+ return { action: 'host', confidence, ...judged, label: choice === HOST_CHOICE && sure ? '没有明确该谁发言,交给主持人安排' : unsure_label(candidates.includes(choice) ? `请 ${choice} 发言` : choice === HOST_CHOICE ? '交给主持人' : '') };
234
234
  }
235
235
 
236
236
  /**
@@ -239,7 +239,8 @@ export async function decide_next_speaker(home, scheduler, { title, goal, user_r
239
239
  */
240
240
  export async function decide_round(home, scheduler, { title, goal, user_request, context, host, experts, last_speaker, ...extra }, fetch_impl) {
241
241
  const candidates = experts.filter(name => name !== last_speaker);
242
- const recent = context.slice(-CONTEXT_MESSAGES).map(excerpt);
242
+ // Telling repetition or drift needs at least the last two rounds side by side.
243
+ const recent = context.slice(-ROUTE_CONTEXT_MESSAGES).map(excerpt);
243
244
  const state = [topic_lines(title, goal, user_request, extra), recent.length ? `本轮及最近发言(旧→新):\n${recent.join('\n')}` : '还没有发言。'].join('\n\n');
244
245
  const questions = [{ type: 'choice', name: 'next', instructions: '一轮发言刚结束。对照讨论目标、主持简报与当前焦点判断:下一轮是否还有明确的内容可以让大家互相回应、追问或补充?', choices: [
245
246
  { value: 'continue', description: '继续下一轮:最近发言里有未回应的分歧、追问或可展开的点,或简报规定的流程还没走完' },
@@ -250,8 +251,9 @@ export async function decide_round(home, scheduler, { title, goal, user_request,
250
251
  const judged = judged_of(state, questions, answers);
251
252
  const choice = answers.next?.choice;
252
253
  const confidence = certainty(answers.next);
253
- if (choice !== 'continue' || confidence === null || confidence < ROUTE_CONFIDENCE || !candidates.length) {
254
- const sure = confidence !== null && confidence >= ROUTE_CONFIDENCE;
254
+ // A wrong "continue" costs a whole round of runs, so it needs the same certainty as waking everyone.
255
+ if (choice !== 'continue' || confidence === null || confidence < ROUTE_WIDE_CONFIDENCE || !candidates.length) {
256
+ const sure = confidence !== null && confidence >= (choice === 'continue' ? ROUTE_WIDE_CONFIDENCE : ROUTE_CONFIDENCE);
255
257
  return { action: 'host', confidence, ...judged, label: choice === HOST_CHOICE && sure ? '建议收束或调整方向,交给主持人' : unsure_label(choice === 'continue' ? '继续下一轮' : choice === HOST_CHOICE ? '收束或调整方向' : '') };
256
258
  }
257
259
  const first = candidates.includes(answers.first?.choice) ? answers.first.choice : candidates[0];
@@ -1,9 +1,14 @@
1
1
  /** Smart scheduling and limit checks for a discussion hosted inside a free room. */
2
+ import { readFileSync } from 'node:fs';
2
3
  import { api, peer_round_question } from './discussion_runner.mjs';
3
- import { decide_interjection, decide_next_speaker, decide_round } from './scheduler.mjs';
4
+ import { DISCUSSION_PROMPT_VERSION, ROUTE_CONFIDENCE, ROUTE_CONTEXT_MESSAGES, ROUTE_WIDE_CONFIDENCE, decide_interjection, decide_next_speaker, decide_round } from './scheduler.mjs';
4
5
 
5
- const CONTEXT_MESSAGES = 12;
6
+ const PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
6
7
  const MAX_QUOTE_CHARS = 300;
8
+ /** How many of the latest plans describe "the host's recent arrangement" to the scheduler. */
9
+ const ARRANGEMENT_TURNS = 3;
10
+ const MAX_ARRANGEMENT_CHARS = 200;
11
+ const TURN_STATUS_LABELS = { answered: '已发言', open: '发言中', queued: '排队中', cancelled: '已取消', skipped: '已跳过' };
7
12
 
8
13
  /** Read every page of current-session discussion plans; the list endpoint is capped at 20 rows per page. */
9
14
  async function read_all_turns(config) {
@@ -49,8 +54,9 @@ export class SessionDriver {
49
54
  try {
50
55
  const [roster, turns] = await Promise.all([api(this.config, '/turns/roster'), read_all_turns(this.config)]);
51
56
  const open_turn = turns.some(turn => ['open', 'queued'].includes(turn.status));
57
+ // An interjection is judged when someone can still be asked or a plan is running that a side note can leave alone.
52
58
  const should_decide = request.cause_kind === 'user_message'
53
- ? roster.waiting.length > 0
59
+ ? roster.waiting.length > 0 || open_turn
54
60
  : !open_turn && (roster.waiting.length > 0 || !session.named_followup);
55
61
  if (!should_decide) return false;
56
62
  this.handled_requests.add(request.id);
@@ -60,7 +66,7 @@ export class SessionDriver {
60
66
  const suggestion = { message_id: request.source_message_id || latest_id, suggestion: plan.label,
61
67
  confidence: plan.confidence ?? null };
62
68
 
63
- if (plan.action !== 'schedule') {
69
+ if (plan.action !== 'schedule' && plan.action !== 'continue') {
64
70
  await this.record_decision(plan, false, suggestion.message_id);
65
71
  this.suggestions.set(request.id, [suggestion]);
66
72
  return false;
@@ -76,7 +82,9 @@ export class SessionDriver {
76
82
  if (latest_roster.revision !== roster.revision) throw new Error('讨论名单或安排已变化,交回主持人');
77
83
  const host_token = this.config.members?.[room.host]?.token;
78
84
  if (!host_token) throw new Error('缺少主持人成员凭据,交回主持人');
79
- await api(this.config, '/turns', { actor: room.host, turns: [plan.turn], roster: latest_roster.revision,
85
+ // A side note keeps the running plan: closing the host request is all that is needed.
86
+ if (plan.action === 'continue') await api(this.config, '/turns/continue', { actor: room.host, revision: latest_roster.revision }, host_token);
87
+ else await api(this.config, '/turns', { actor: room.host, turns: [plan.turn], roster: latest_roster.revision,
80
88
  submission_id: `session-driver-${session.id}-${request.id}` }, host_token);
81
89
  await this.record_decision(plan, true, suggestion.message_id);
82
90
  this.log(`智能调度 #${request.id}:${plan.label}`);
@@ -108,14 +116,15 @@ export class SessionDriver {
108
116
  /** Choose an eligible next task; manual and web members remain valid candidates from the roster. */
109
117
  async plan(session, roster, turns, request, room) {
110
118
  const latest_id = room.latest_id || 0;
111
- const page = await api(this.config, `/messages?after=${Math.max(0, latest_id - CONTEXT_MESSAGES)}`);
119
+ const page = await api(this.config, `/messages?after=${Math.max(0, latest_id - ROUTE_CONTEXT_MESSAGES)}`);
112
120
  const base = { title: room.title, goal: session.goal || room.goal, user_request: room.user_request,
113
- context: page.messages, host: room.host, brief: room.host_brief?.text, focus: room.focus, profiles: room.descriptions };
121
+ context: page.messages, host: room.host, brief: room.host_brief?.text, focus: room.focus, profiles: room.descriptions,
122
+ arrangement: arrangement_of(turns) };
114
123
  const last_answer = [...turns].filter(turn => turn.status === 'answered')
115
124
  .sort((left, right) => Number(left.answer_id || 0) - Number(right.answer_id || 0)).at(-1);
116
125
  const anchor = last_answer?.answer_id || request.source_message_id || latest_id;
117
126
 
118
- if (request.cause_kind === 'user_message') return this.plan_interjection(base, page.messages, session, roster, request);
127
+ if (request.cause_kind === 'user_message') return this.plan_interjection(base, page.messages, session, roster, request, turns);
119
128
  const open = turns.some(turn => ['open', 'queued'].includes(turn.status));
120
129
  if (open) return null;
121
130
  if (roster.waiting.length) return this.plan_speaker(base, session, roster, turns, anchor);
@@ -130,12 +139,18 @@ export class SessionDriver {
130
139
  }
131
140
 
132
141
  /** Decide whether an unnamed user message should be answered by an expert or returned to the host. */
133
- async plan_interjection(base, messages, session, roster, request) {
142
+ async plan_interjection(base, messages, session, roster, request, turns) {
134
143
  const message = messages.find(item => item.id === request.source_message_id)
135
144
  || messages.find(item => item.author === '用户' && item.id >= (session.held_since || 0));
136
- if (!message || !roster.waiting.length) return null;
145
+ if (!message) return null;
146
+ const spoken = turns.filter(turn => turn.round === roster.round && turn.status === 'answered').map(turn => turn.expert);
137
147
  const result = await decide_interjection(this.config.api_home, this.config.scheduler,
138
- { ...base, message, experts: roster.waiting }, this.fetch);
148
+ { ...base, message, experts: roster.waiting, spoken }, this.fetch);
149
+ if (result.action === 'note') {
150
+ // Continuing needs a plan to continue; with nothing open or queued the discussion would stall without the host.
151
+ if (turns.some(turn => ['open', 'queued'].includes(turn.status))) return { ...result, action: 'continue', kind: 'interjection', after_message_id: message.id };
152
+ return { ...result, action: 'host', kind: 'interjection', after_message_id: message.id, label: '仅补充信息,但当前没有进行中的安排,交给主持人' };
153
+ }
139
154
  if (result.action !== 'ask' || !roster.waiting.includes(result.expert)) {
140
155
  return { ...result, action: 'host', kind: 'interjection', after_message_id: message.id };
141
156
  }
@@ -165,11 +180,21 @@ export class SessionDriver {
165
180
  try {
166
181
  await api(this.config, '/scheduler-decisions', { kind: plan.kind, label: plan.label,
167
182
  confidence: plan.confidence ?? null, applied, provider: this.config.scheduler.provider,
168
- after_message_id, detail: plan.detail, input: plan.input });
183
+ after_message_id, detail: plan.detail, input: plan.input,
184
+ context: { model: this.config.scheduler.model, threshold: ROUTE_CONFIDENCE, wide_threshold: ROUTE_WIDE_CONFIDENCE,
185
+ prompt: DISCUSSION_PROMPT_VERSION, package: PACKAGE_VERSION } });
169
186
  } catch (error) { this.log(`智能调度决策记录未保存:${error.message}`); }
170
187
  }
171
188
  }
172
189
 
190
+ /** The latest plans with their state, as the host's recent arrangement the questions refer to. */
191
+ function arrangement_of(turns) {
192
+ return [...turns].sort((left, right) => Number(left.id || 0) - Number(right.id || 0)).slice(-ARRANGEMENT_TURNS).map(turn => {
193
+ const question = String(turn.question || '').replace(/\s+/g, ' ').trim();
194
+ return `${turn.expert}(第 ${turn.round} 轮,${TURN_STATUS_LABELS[turn.status] || turn.status}):${question.length > MAX_ARRANGEMENT_CHARS ? `${question.slice(0, MAX_ARRANGEMENT_CHARS)}…` : question}`;
195
+ }).join(';');
196
+ }
197
+
173
198
  function opening_question(phase) {
174
199
  return phase === 'initial' ? '请先独立阅读议题,给出你的开场观点。' : '请先阅读议题和已有回答,给出你的观点。';
175
200
  }
package/web/app.js CHANGED
@@ -64,6 +64,7 @@ const live_updates = { source: null, connected: false, enabled: false, timer: nu
64
64
  state.messages = [];
65
65
  state.message_history = null;
66
66
  state.connected = false;
67
+ state.reconnecting = false;
67
68
  state.attachment_drafts = [];
68
69
  state.composer_error = "";
69
70
  state.upload_status = "";
@@ -313,6 +314,7 @@ function management_error_message(room_id, error) {
313
314
  /** Show connection state without claiming that any AI is online. */
314
315
  function set_connection(connected) {
315
316
  state.connected = connected;
317
+ state.reconnecting = false;
316
318
  element("connection-dot").className = `status-dot ${connected ? "connected" : "failed"}`;
317
319
  element("connection-status").textContent = connected ? "讨论网站已连接" : "网站连接中断,可手动刷新";
318
320
  publish_view();
@@ -3614,7 +3616,11 @@ async function flush_live_refresh() {
3614
3616
  const full = live_updates.full;
3615
3617
  live_updates.full = false;
3616
3618
  live_updates.running = true;
3617
- try { await refresh_room({ room: full }); } catch { set_connection(false); }
3619
+ try { await refresh_room({ room: full }); } catch (error) {
3620
+ console.warn("refresh failed", error);
3621
+ if (state.reconnecting) show_toast(`仍未连接:${error.message}`, true);
3622
+ set_connection(false);
3623
+ }
3618
3624
  finally {
3619
3625
  live_updates.running = false;
3620
3626
  if (live_updates.dirty) queue_live_refresh();
@@ -3628,7 +3634,7 @@ function arm_update_poll() {
3628
3634
  live_updates.poll_timer = setTimeout(() => {
3629
3635
  poll_live_updates();
3630
3636
  arm_update_poll();
3631
- }, live_updates.connected ? REFRESH_INTERVAL_MS : FALLBACK_REFRESH_INTERVAL_MS);
3637
+ }, live_updates.connected && state.connected ? REFRESH_INTERVAL_MS : FALLBACK_REFRESH_INTERVAL_MS);
3632
3638
  }
3633
3639
 
3634
3640
  /**
@@ -3637,9 +3643,10 @@ function arm_update_poll() {
3637
3643
  * A site without revisions is read in full as before.
3638
3644
  */
3639
3645
  async function poll_live_updates() {
3646
+ if (!live_updates.source && typeof EventSource !== "undefined") return reconnect_live_updates();
3640
3647
  const room_id = state.room?.id || "";
3641
3648
  const working = (state.room?.free?.members || []).some((member) => WORKING_STATUSES.includes(member.execution?.status));
3642
- if (working || Date.now() - live_updates.refreshed_at >= SAFETY_REFRESH_MS) return queue_live_refresh();
3649
+ if (!state.connected || working || Date.now() - live_updates.refreshed_at >= SAFETY_REFRESH_MS) return queue_live_refresh();
3643
3650
  let revisions;
3644
3651
  try { revisions = await request_json(`/api/revisions?rooms=${encodeURIComponent(room_id)}`); }
3645
3652
  catch { return queue_live_refresh(); }
@@ -3669,6 +3676,8 @@ function start_live_updates() {
3669
3676
  if (live_updates.source !== source) return;
3670
3677
  const was_connected = live_updates.connected;
3671
3678
  live_updates.connected = false;
3679
+ // A closed stream is never retried by the browser; the next poll opens a new one.
3680
+ if (source.readyState === EventSource.CLOSED) live_updates.source = null;
3672
3681
  if (was_connected) arm_update_poll();
3673
3682
  });
3674
3683
  }
@@ -3676,6 +3685,17 @@ function start_live_updates() {
3676
3685
  queue_live_refresh();
3677
3686
  }
3678
3687
 
3688
+ /** Reopen the update stream and read the site now; used by the footer retry and when the stream was closed. */
3689
+ async function reconnect_live_updates() {
3690
+ if (document.hidden) return;
3691
+ state.reconnecting = true;
3692
+ publish_view();
3693
+ live_updates.source?.close();
3694
+ live_updates.source = null;
3695
+ live_updates.connected = false;
3696
+ start_live_updates();
3697
+ }
3698
+
3679
3699
  /** Release streams and scheduled reads while hidden or leaving the page. */
3680
3700
  function stop_live_updates() {
3681
3701
  live_updates.enabled = false;
@@ -3751,7 +3771,7 @@ function view_snapshot() {
3751
3771
  return { rooms: ordered_rooms(), folders: state.folders, folder_groups: ordered_folders(), room: state.room, archive_view: state.archive_view, directory_sort: state.directory_sort, messages: state.messages, total: state.message_total,
3752
3772
  archive_pending: [...state.archive_changes.keys()], directory_error: state.directory_error, directory_notice: state.directory_notice, import_progress: structuredClone(state.import_progress),
3753
3773
  directory_pending: [...state.directory_pending.keys()], folder_pending: [...state.folder_moves.keys()], creating_room: state.creating_room,
3754
- connected: state.connected, backup_pending: Boolean(state.backup_pending), page: state.page, pages: state.pages, query: state.query,
3774
+ connected: state.connected, reconnecting: state.reconnecting, backup_pending: Boolean(state.backup_pending), page: state.page, pages: state.pages, query: state.query,
3755
3775
  attachment_drafts: state.attachment_drafts.map(item => ({ id: item.id, file_name: item.file.name, size: item.file.size, preview_url: item.preview_url })),
3756
3776
  composer_error: state.composer_error, upload_status: state.upload_status, sending_message: state.sending_message,
3757
3777
  mention_only: state.mention_only, reply_to: state.reply_to, turn_reply_to: state.turn_reply_to,
@@ -3996,7 +4016,7 @@ function change_view_theme(theme) {
3996
4016
  window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
3997
4017
  write_launch: write_launch_field, default_launch_entry, description_template, save_description_template, description_draft, write_description_draft, describe_member, set_chain_rounds, continue_chain, expert_presets, apply_expert_preset, unique_instance, member_label, preset_identity, save_expert_preset, delete_expert_preset, write_room_connection, write_room_adapter, launch_clients: LAUNCH_CLIENTS,
3998
4018
  launch_model_state, load_launch_models, write_launch_model, save_launch_api_key, write_scheduler, save_scheduler_api_key, check_scheduler, write_named_followup, write_create_discussion, write_create_chain_rounds, check_launch_api, launch_summary, launch_can_set_access, pick_launch_directory, browse_local_directory, save_launch_projects, restore_launch_profile, reset_create_form, empty_discussion_message,
3999
- start_drag: start_directory_drag,
4019
+ start_drag: start_directory_drag, reconnect: reconnect_live_updates,
4000
4020
  attachment_markup, attachment_size, attachment_file_icon, drop_attachments, paste_attachments,
4001
4021
  write_expert: write_view_expert, add_instance: add_view_instance, add_preset: add_preset_instance, platform_of, execute: execute_view_action, theme: change_view_theme,
4002
4022
  admin: { register: register_admin, login: login_admin, logout: logout_admin,
package/web/modern.js CHANGED
@@ -54881,7 +54881,7 @@ var k1 = {
54881
54881
  style: A(de.value)
54882
54882
  }, [
54883
54883
  K("aside", t2, [
54884
- r[30] ||= ws("<header class=\"channel-workspace-heading\"><span class=\"channel-brand-icon\" aria-hidden=\"true\"><svg class=\"channel-brand-dark\" viewBox=\"0 0 100 100\"><defs><linearGradient id=\"brand-d0\" gradientUnits=\"userSpaceOnUse\" x1=\"4.5\" y1=\"64\" x2=\"95.5\" y2=\"64\"><stop offset=\"0.0%\" stop-color=\"#e9ebed\"></stop><stop offset=\"6.3%\" stop-color=\"#e2e5e7\"></stop><stop offset=\"12.5%\" stop-color=\"#dcdee1\"></stop><stop offset=\"18.8%\" stop-color=\"#d5d8db\"></stop><stop offset=\"25.0%\" stop-color=\"#ced2d5\"></stop><stop offset=\"31.3%\" stop-color=\"#c8cbcf\"></stop><stop offset=\"37.5%\" stop-color=\"#c1c5ca\"></stop><stop offset=\"43.8%\" stop-color=\"#babfc4\"></stop><stop offset=\"50.0%\" stop-color=\"#b4b9be\"></stop><stop offset=\"56.3%\" stop-color=\"#a9aeb3\"></stop><stop offset=\"62.5%\" stop-color=\"#9ea3a9\"></stop><stop offset=\"68.8%\" stop-color=\"#93999f\"></stop><stop offset=\"75.0%\" stop-color=\"#888e95\"></stop><stop offset=\"81.3%\" stop-color=\"#7e848a\"></stop><stop offset=\"87.5%\" stop-color=\"#737a80\"></stop><stop offset=\"93.8%\" stop-color=\"#697077\"></stop><stop offset=\"100.0%\" stop-color=\"#5f666d\"></stop></linearGradient><linearGradient id=\"brand-d1\" gradientUnits=\"userSpaceOnUse\" x1=\"8.5\" y1=\"36\" x2=\"91.5\" y2=\"36\"><stop offset=\"0.0%\" stop-color=\"#3f86ff\"></stop><stop offset=\"6.3%\" stop-color=\"#5588ff\"></stop><stop offset=\"12.5%\" stop-color=\"#6889ff\"></stop><stop offset=\"18.8%\" stop-color=\"#788aff\"></stop><stop offset=\"25.0%\" stop-color=\"#878bff\"></stop><stop offset=\"31.3%\" stop-color=\"#958cff\"></stop><stop offset=\"37.5%\" stop-color=\"#a78aef\"></stop><stop offset=\"43.8%\" stop-color=\"#b986d7\"></stop><stop offset=\"50.0%\" stop-color=\"#ca80bf\"></stop><stop offset=\"56.3%\" stop-color=\"#da78a6\"></stop><stop offset=\"62.5%\" stop-color=\"#ea6e8c\"></stop><stop offset=\"68.8%\" stop-color=\"#f56d78\"></stop><stop offset=\"75.0%\" stop-color=\"#f77f72\"></stop><stop offset=\"81.3%\" stop-color=\"#f8906b\"></stop><stop offset=\"87.5%\" stop-color=\"#f9a163\"></stop><stop offset=\"93.8%\" stop-color=\"#fab058\"></stop><stop offset=\"100.0%\" stop-color=\"#fac04a\"></stop></linearGradient><linearGradient id=\"brand-d2\" gradientUnits=\"userSpaceOnUse\" x1=\"28.021\" y1=\"95.435\" x2=\"39.978\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#d06a49\"></stop><stop offset=\"6.3%\" stop-color=\"#d36f4f\"></stop><stop offset=\"12.5%\" stop-color=\"#d67456\"></stop><stop offset=\"18.8%\" stop-color=\"#d97a5c\"></stop><stop offset=\"25.0%\" stop-color=\"#dc7f62\"></stop><stop offset=\"31.3%\" stop-color=\"#df8468\"></stop><stop offset=\"37.5%\" stop-color=\"#e2896e\"></stop><stop offset=\"43.8%\" stop-color=\"#e58e74\"></stop><stop offset=\"50.0%\" stop-color=\"#e8937a\"></stop><stop offset=\"56.3%\" stop-color=\"#eb9d85\"></stop><stop offset=\"62.5%\" stop-color=\"#eea68f\"></stop><stop offset=\"68.8%\" stop-color=\"#f1b09a\"></stop><stop offset=\"75.0%\" stop-color=\"#f4baa4\"></stop><stop offset=\"81.3%\" stop-color=\"#f6c3af\"></stop><stop offset=\"87.5%\" stop-color=\"#f9cdb9\"></stop><stop offset=\"93.8%\" stop-color=\"#fbd6c4\"></stop><stop offset=\"100.0%\" stop-color=\"#fde0cf\"></stop></linearGradient><linearGradient id=\"brand-d3\" gradientUnits=\"userSpaceOnUse\" x1=\"71.978\" y1=\"95.435\" x2=\"60.021\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#4a5cff\"></stop><stop offset=\"6.3%\" stop-color=\"#5062ff\"></stop><stop offset=\"12.5%\" stop-color=\"#5768ff\"></stop><stop offset=\"18.8%\" stop-color=\"#5d6dff\"></stop><stop offset=\"25.0%\" stop-color=\"#6472ff\"></stop><stop offset=\"31.3%\" stop-color=\"#6b77ff\"></stop><stop offset=\"37.5%\" stop-color=\"#717cff\"></stop><stop offset=\"43.8%\" stop-color=\"#7881ff\"></stop><stop offset=\"50.0%\" stop-color=\"#7f86ff\"></stop><stop offset=\"56.3%\" stop-color=\"#8990ff\"></stop><stop offset=\"62.5%\" stop-color=\"#9399ff\"></stop><stop offset=\"68.8%\" stop-color=\"#9da2ff\"></stop><stop offset=\"75.0%\" stop-color=\"#a8abff\"></stop><stop offset=\"81.3%\" stop-color=\"#b3b4ff\"></stop><stop offset=\"87.5%\" stop-color=\"#bebdff\"></stop><stop offset=\"93.8%\" stop-color=\"#c9c5ff\"></stop><stop offset=\"100.0%\" stop-color=\"#d4ceff\"></stop></linearGradient><clipPath id=\"brand-d4\"><polygon points=\"16,46.5 84,46.5 84,25.5 16,25.5\"></polygon></clipPath><clipPath id=\"brand-d5\"><polygon points=\"16,48.5 84,48.5 84,23.5 16,23.5\"></polygon></clipPath><clipPath id=\"brand-d6\"><polygon points=\"81.410,86.630 71.410,10.630 50.589,13.369 60.589,89.369\"></polygon></clipPath><clipPath id=\"brand-d7\"><polygon points=\"83.393,86.369 73.393,10.369 48.606,13.630 58.606,89.630\"></polygon></clipPath><clipPath id=\"brand-d8\"><polygon points=\"12,74.5 88,74.5 88,53.5 12,53.5\"></polygon></clipPath><clipPath id=\"brand-d9\"><polygon points=\"12,76.5 88,76.5 88,51.5 12,51.5\"></polygon></clipPath><clipPath id=\"brand-d10\"><polygon points=\"39.410,89.369 49.410,13.369 28.589,10.630 18.589,86.630\"></polygon></clipPath><clipPath id=\"brand-d11\"><polygon points=\"41.393,89.630 51.393,13.630 26.606,10.369 16.606,86.369\"></polygon></clipPath></defs><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-d0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-d1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-d2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-d3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><g clip-path=\"url(#brand-d4)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d5)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-d2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d6)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d7)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-d1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d8)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d9)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-d3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d10)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d11)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-d0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g></svg><svg class=\"channel-brand-light\" viewBox=\"0 0 100 100\"><defs><linearGradient id=\"brand-l0\" gradientUnits=\"userSpaceOnUse\" x1=\"4.5\" y1=\"64\" x2=\"95.5\" y2=\"64\"><stop offset=\"0.0%\" stop-color=\"#0b0d0f\"></stop><stop offset=\"6.3%\" stop-color=\"#101315\"></stop><stop offset=\"12.5%\" stop-color=\"#16191b\"></stop><stop offset=\"18.8%\" stop-color=\"#1c1f22\"></stop><stop offset=\"25.0%\" stop-color=\"#222529\"></stop><stop offset=\"31.3%\" stop-color=\"#282c2f\"></stop><stop offset=\"37.5%\" stop-color=\"#2e3237\"></stop><stop offset=\"43.8%\" stop-color=\"#34393e\"></stop><stop offset=\"50.0%\" stop-color=\"#3b4045\"></stop><stop offset=\"56.3%\" stop-color=\"#484d52\"></stop><stop offset=\"62.5%\" stop-color=\"#555a5f\"></stop><stop offset=\"68.8%\" stop-color=\"#62676d\"></stop><stop offset=\"75.0%\" stop-color=\"#70757b\"></stop><stop offset=\"81.3%\" stop-color=\"#7e8389\"></stop><stop offset=\"87.5%\" stop-color=\"#8c9297\"></stop><stop offset=\"93.8%\" stop-color=\"#9aa0a6\"></stop><stop offset=\"100.0%\" stop-color=\"#a9afb5\"></stop></linearGradient><linearGradient id=\"brand-l1\" gradientUnits=\"userSpaceOnUse\" x1=\"8.5\" y1=\"36\" x2=\"91.5\" y2=\"36\"><stop offset=\"0.0%\" stop-color=\"#2f7bff\"></stop><stop offset=\"6.3%\" stop-color=\"#487cff\"></stop><stop offset=\"12.5%\" stop-color=\"#5a7cff\"></stop><stop offset=\"18.8%\" stop-color=\"#6a7dff\"></stop><stop offset=\"25.0%\" stop-color=\"#797dff\"></stop><stop offset=\"31.3%\" stop-color=\"#877dff\"></stop><stop offset=\"37.5%\" stop-color=\"#997cee\"></stop><stop offset=\"43.8%\" stop-color=\"#ae79d3\"></stop><stop offset=\"50.0%\" stop-color=\"#c174b8\"></stop><stop offset=\"56.3%\" stop-color=\"#d36b9d\"></stop><stop offset=\"62.5%\" stop-color=\"#e56080\"></stop><stop offset=\"68.8%\" stop-color=\"#f15d69\"></stop><stop offset=\"75.0%\" stop-color=\"#f37162\"></stop><stop offset=\"81.3%\" stop-color=\"#f5835a\"></stop><stop offset=\"87.5%\" stop-color=\"#f79550\"></stop><stop offset=\"93.8%\" stop-color=\"#f8a542\"></stop><stop offset=\"100.0%\" stop-color=\"#f8b52c\"></stop></linearGradient><linearGradient id=\"brand-l2\" gradientUnits=\"userSpaceOnUse\" x1=\"28.021\" y1=\"95.435\" x2=\"39.978\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#b9502f\"></stop><stop offset=\"6.3%\" stop-color=\"#bd5534\"></stop><stop offset=\"12.5%\" stop-color=\"#c15a39\"></stop><stop offset=\"18.8%\" stop-color=\"#c55f3e\"></stop><stop offset=\"25.0%\" stop-color=\"#c96443\"></stop><stop offset=\"31.3%\" stop-color=\"#cd6848\"></stop><stop offset=\"37.5%\" stop-color=\"#d16d4d\"></stop><stop offset=\"43.8%\" stop-color=\"#d57252\"></stop><stop offset=\"50.0%\" stop-color=\"#d97757\"></stop><stop offset=\"56.3%\" stop-color=\"#dd8060\"></stop><stop offset=\"62.5%\" stop-color=\"#e08a69\"></stop><stop offset=\"68.8%\" stop-color=\"#e49372\"></stop><stop offset=\"75.0%\" stop-color=\"#e79c7b\"></stop><stop offset=\"81.3%\" stop-color=\"#eaa584\"></stop><stop offset=\"87.5%\" stop-color=\"#edae8d\"></stop><stop offset=\"93.8%\" stop-color=\"#f0b796\"></stop><stop offset=\"100.0%\" stop-color=\"#f3c09f\"></stop></linearGradient><linearGradient id=\"brand-l3\" gradientUnits=\"userSpaceOnUse\" x1=\"71.978\" y1=\"95.435\" x2=\"60.021\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#2b33e6\"></stop><stop offset=\"6.3%\" stop-color=\"#3139e9\"></stop><stop offset=\"12.5%\" stop-color=\"#363fec\"></stop><stop offset=\"18.8%\" stop-color=\"#3c44ef\"></stop><stop offset=\"25.0%\" stop-color=\"#4249f3\"></stop><stop offset=\"31.3%\" stop-color=\"#484ef6\"></stop><stop offset=\"37.5%\" stop-color=\"#4e53f9\"></stop><stop offset=\"43.8%\" stop-color=\"#5457fc\"></stop><stop offset=\"50.0%\" stop-color=\"#5a5cff\"></stop><stop offset=\"56.3%\" stop-color=\"#6569ff\"></stop><stop offset=\"62.5%\" stop-color=\"#7075ff\"></stop><stop offset=\"68.8%\" stop-color=\"#7c80ff\"></stop><stop offset=\"75.0%\" stop-color=\"#888bff\"></stop><stop offset=\"81.3%\" stop-color=\"#9595ff\"></stop><stop offset=\"87.5%\" stop-color=\"#a29fff\"></stop><stop offset=\"93.8%\" stop-color=\"#afa9ff\"></stop><stop offset=\"100.0%\" stop-color=\"#bdb3ff\"></stop></linearGradient><clipPath id=\"brand-l4\"><polygon points=\"16,46.5 84,46.5 84,25.5 16,25.5\"></polygon></clipPath><clipPath id=\"brand-l5\"><polygon points=\"16,48.5 84,48.5 84,23.5 16,23.5\"></polygon></clipPath><clipPath id=\"brand-l6\"><polygon points=\"81.410,86.630 71.410,10.630 50.589,13.369 60.589,89.369\"></polygon></clipPath><clipPath id=\"brand-l7\"><polygon points=\"83.393,86.369 73.393,10.369 48.606,13.630 58.606,89.630\"></polygon></clipPath><clipPath id=\"brand-l8\"><polygon points=\"12,74.5 88,74.5 88,53.5 12,53.5\"></polygon></clipPath><clipPath id=\"brand-l9\"><polygon points=\"12,76.5 88,76.5 88,51.5 12,51.5\"></polygon></clipPath><clipPath id=\"brand-l10\"><polygon points=\"39.410,89.369 49.410,13.369 28.589,10.630 18.589,86.630\"></polygon></clipPath><clipPath id=\"brand-l11\"><polygon points=\"41.393,89.630 51.393,13.630 26.606,10.369 16.606,86.369\"></polygon></clipPath></defs><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-l0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-l1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-l2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-l3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><g clip-path=\"url(#brand-l4)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l5)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-l2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l6)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l7)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-l1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l8)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l9)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-l3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l10)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l11)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-l0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g></svg></span><span class=\"channel-brand-text\"><strong>共议</strong><span>AI CoDiscuss</span></span></header>", 1),
54884
+ r[31] ||= ws("<header class=\"channel-workspace-heading\"><span class=\"channel-brand-icon\" aria-hidden=\"true\"><svg class=\"channel-brand-dark\" viewBox=\"0 0 100 100\"><defs><linearGradient id=\"brand-d0\" gradientUnits=\"userSpaceOnUse\" x1=\"4.5\" y1=\"64\" x2=\"95.5\" y2=\"64\"><stop offset=\"0.0%\" stop-color=\"#e9ebed\"></stop><stop offset=\"6.3%\" stop-color=\"#e2e5e7\"></stop><stop offset=\"12.5%\" stop-color=\"#dcdee1\"></stop><stop offset=\"18.8%\" stop-color=\"#d5d8db\"></stop><stop offset=\"25.0%\" stop-color=\"#ced2d5\"></stop><stop offset=\"31.3%\" stop-color=\"#c8cbcf\"></stop><stop offset=\"37.5%\" stop-color=\"#c1c5ca\"></stop><stop offset=\"43.8%\" stop-color=\"#babfc4\"></stop><stop offset=\"50.0%\" stop-color=\"#b4b9be\"></stop><stop offset=\"56.3%\" stop-color=\"#a9aeb3\"></stop><stop offset=\"62.5%\" stop-color=\"#9ea3a9\"></stop><stop offset=\"68.8%\" stop-color=\"#93999f\"></stop><stop offset=\"75.0%\" stop-color=\"#888e95\"></stop><stop offset=\"81.3%\" stop-color=\"#7e848a\"></stop><stop offset=\"87.5%\" stop-color=\"#737a80\"></stop><stop offset=\"93.8%\" stop-color=\"#697077\"></stop><stop offset=\"100.0%\" stop-color=\"#5f666d\"></stop></linearGradient><linearGradient id=\"brand-d1\" gradientUnits=\"userSpaceOnUse\" x1=\"8.5\" y1=\"36\" x2=\"91.5\" y2=\"36\"><stop offset=\"0.0%\" stop-color=\"#3f86ff\"></stop><stop offset=\"6.3%\" stop-color=\"#5588ff\"></stop><stop offset=\"12.5%\" stop-color=\"#6889ff\"></stop><stop offset=\"18.8%\" stop-color=\"#788aff\"></stop><stop offset=\"25.0%\" stop-color=\"#878bff\"></stop><stop offset=\"31.3%\" stop-color=\"#958cff\"></stop><stop offset=\"37.5%\" stop-color=\"#a78aef\"></stop><stop offset=\"43.8%\" stop-color=\"#b986d7\"></stop><stop offset=\"50.0%\" stop-color=\"#ca80bf\"></stop><stop offset=\"56.3%\" stop-color=\"#da78a6\"></stop><stop offset=\"62.5%\" stop-color=\"#ea6e8c\"></stop><stop offset=\"68.8%\" stop-color=\"#f56d78\"></stop><stop offset=\"75.0%\" stop-color=\"#f77f72\"></stop><stop offset=\"81.3%\" stop-color=\"#f8906b\"></stop><stop offset=\"87.5%\" stop-color=\"#f9a163\"></stop><stop offset=\"93.8%\" stop-color=\"#fab058\"></stop><stop offset=\"100.0%\" stop-color=\"#fac04a\"></stop></linearGradient><linearGradient id=\"brand-d2\" gradientUnits=\"userSpaceOnUse\" x1=\"28.021\" y1=\"95.435\" x2=\"39.978\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#d06a49\"></stop><stop offset=\"6.3%\" stop-color=\"#d36f4f\"></stop><stop offset=\"12.5%\" stop-color=\"#d67456\"></stop><stop offset=\"18.8%\" stop-color=\"#d97a5c\"></stop><stop offset=\"25.0%\" stop-color=\"#dc7f62\"></stop><stop offset=\"31.3%\" stop-color=\"#df8468\"></stop><stop offset=\"37.5%\" stop-color=\"#e2896e\"></stop><stop offset=\"43.8%\" stop-color=\"#e58e74\"></stop><stop offset=\"50.0%\" stop-color=\"#e8937a\"></stop><stop offset=\"56.3%\" stop-color=\"#eb9d85\"></stop><stop offset=\"62.5%\" stop-color=\"#eea68f\"></stop><stop offset=\"68.8%\" stop-color=\"#f1b09a\"></stop><stop offset=\"75.0%\" stop-color=\"#f4baa4\"></stop><stop offset=\"81.3%\" stop-color=\"#f6c3af\"></stop><stop offset=\"87.5%\" stop-color=\"#f9cdb9\"></stop><stop offset=\"93.8%\" stop-color=\"#fbd6c4\"></stop><stop offset=\"100.0%\" stop-color=\"#fde0cf\"></stop></linearGradient><linearGradient id=\"brand-d3\" gradientUnits=\"userSpaceOnUse\" x1=\"71.978\" y1=\"95.435\" x2=\"60.021\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#4a5cff\"></stop><stop offset=\"6.3%\" stop-color=\"#5062ff\"></stop><stop offset=\"12.5%\" stop-color=\"#5768ff\"></stop><stop offset=\"18.8%\" stop-color=\"#5d6dff\"></stop><stop offset=\"25.0%\" stop-color=\"#6472ff\"></stop><stop offset=\"31.3%\" stop-color=\"#6b77ff\"></stop><stop offset=\"37.5%\" stop-color=\"#717cff\"></stop><stop offset=\"43.8%\" stop-color=\"#7881ff\"></stop><stop offset=\"50.0%\" stop-color=\"#7f86ff\"></stop><stop offset=\"56.3%\" stop-color=\"#8990ff\"></stop><stop offset=\"62.5%\" stop-color=\"#9399ff\"></stop><stop offset=\"68.8%\" stop-color=\"#9da2ff\"></stop><stop offset=\"75.0%\" stop-color=\"#a8abff\"></stop><stop offset=\"81.3%\" stop-color=\"#b3b4ff\"></stop><stop offset=\"87.5%\" stop-color=\"#bebdff\"></stop><stop offset=\"93.8%\" stop-color=\"#c9c5ff\"></stop><stop offset=\"100.0%\" stop-color=\"#d4ceff\"></stop></linearGradient><clipPath id=\"brand-d4\"><polygon points=\"16,46.5 84,46.5 84,25.5 16,25.5\"></polygon></clipPath><clipPath id=\"brand-d5\"><polygon points=\"16,48.5 84,48.5 84,23.5 16,23.5\"></polygon></clipPath><clipPath id=\"brand-d6\"><polygon points=\"81.410,86.630 71.410,10.630 50.589,13.369 60.589,89.369\"></polygon></clipPath><clipPath id=\"brand-d7\"><polygon points=\"83.393,86.369 73.393,10.369 48.606,13.630 58.606,89.630\"></polygon></clipPath><clipPath id=\"brand-d8\"><polygon points=\"12,74.5 88,74.5 88,53.5 12,53.5\"></polygon></clipPath><clipPath id=\"brand-d9\"><polygon points=\"12,76.5 88,76.5 88,51.5 12,51.5\"></polygon></clipPath><clipPath id=\"brand-d10\"><polygon points=\"39.410,89.369 49.410,13.369 28.589,10.630 18.589,86.630\"></polygon></clipPath><clipPath id=\"brand-d11\"><polygon points=\"41.393,89.630 51.393,13.630 26.606,10.369 16.606,86.369\"></polygon></clipPath></defs><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-d0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-d1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-d2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-d3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><g clip-path=\"url(#brand-d4)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d5)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-d2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d6)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d7)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-d1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d8)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d9)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-d3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d10)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"#202127\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-d11)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-d0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g></svg><svg class=\"channel-brand-light\" viewBox=\"0 0 100 100\"><defs><linearGradient id=\"brand-l0\" gradientUnits=\"userSpaceOnUse\" x1=\"4.5\" y1=\"64\" x2=\"95.5\" y2=\"64\"><stop offset=\"0.0%\" stop-color=\"#0b0d0f\"></stop><stop offset=\"6.3%\" stop-color=\"#101315\"></stop><stop offset=\"12.5%\" stop-color=\"#16191b\"></stop><stop offset=\"18.8%\" stop-color=\"#1c1f22\"></stop><stop offset=\"25.0%\" stop-color=\"#222529\"></stop><stop offset=\"31.3%\" stop-color=\"#282c2f\"></stop><stop offset=\"37.5%\" stop-color=\"#2e3237\"></stop><stop offset=\"43.8%\" stop-color=\"#34393e\"></stop><stop offset=\"50.0%\" stop-color=\"#3b4045\"></stop><stop offset=\"56.3%\" stop-color=\"#484d52\"></stop><stop offset=\"62.5%\" stop-color=\"#555a5f\"></stop><stop offset=\"68.8%\" stop-color=\"#62676d\"></stop><stop offset=\"75.0%\" stop-color=\"#70757b\"></stop><stop offset=\"81.3%\" stop-color=\"#7e8389\"></stop><stop offset=\"87.5%\" stop-color=\"#8c9297\"></stop><stop offset=\"93.8%\" stop-color=\"#9aa0a6\"></stop><stop offset=\"100.0%\" stop-color=\"#a9afb5\"></stop></linearGradient><linearGradient id=\"brand-l1\" gradientUnits=\"userSpaceOnUse\" x1=\"8.5\" y1=\"36\" x2=\"91.5\" y2=\"36\"><stop offset=\"0.0%\" stop-color=\"#2f7bff\"></stop><stop offset=\"6.3%\" stop-color=\"#487cff\"></stop><stop offset=\"12.5%\" stop-color=\"#5a7cff\"></stop><stop offset=\"18.8%\" stop-color=\"#6a7dff\"></stop><stop offset=\"25.0%\" stop-color=\"#797dff\"></stop><stop offset=\"31.3%\" stop-color=\"#877dff\"></stop><stop offset=\"37.5%\" stop-color=\"#997cee\"></stop><stop offset=\"43.8%\" stop-color=\"#ae79d3\"></stop><stop offset=\"50.0%\" stop-color=\"#c174b8\"></stop><stop offset=\"56.3%\" stop-color=\"#d36b9d\"></stop><stop offset=\"62.5%\" stop-color=\"#e56080\"></stop><stop offset=\"68.8%\" stop-color=\"#f15d69\"></stop><stop offset=\"75.0%\" stop-color=\"#f37162\"></stop><stop offset=\"81.3%\" stop-color=\"#f5835a\"></stop><stop offset=\"87.5%\" stop-color=\"#f79550\"></stop><stop offset=\"93.8%\" stop-color=\"#f8a542\"></stop><stop offset=\"100.0%\" stop-color=\"#f8b52c\"></stop></linearGradient><linearGradient id=\"brand-l2\" gradientUnits=\"userSpaceOnUse\" x1=\"28.021\" y1=\"95.435\" x2=\"39.978\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#b9502f\"></stop><stop offset=\"6.3%\" stop-color=\"#bd5534\"></stop><stop offset=\"12.5%\" stop-color=\"#c15a39\"></stop><stop offset=\"18.8%\" stop-color=\"#c55f3e\"></stop><stop offset=\"25.0%\" stop-color=\"#c96443\"></stop><stop offset=\"31.3%\" stop-color=\"#cd6848\"></stop><stop offset=\"37.5%\" stop-color=\"#d16d4d\"></stop><stop offset=\"43.8%\" stop-color=\"#d57252\"></stop><stop offset=\"50.0%\" stop-color=\"#d97757\"></stop><stop offset=\"56.3%\" stop-color=\"#dd8060\"></stop><stop offset=\"62.5%\" stop-color=\"#e08a69\"></stop><stop offset=\"68.8%\" stop-color=\"#e49372\"></stop><stop offset=\"75.0%\" stop-color=\"#e79c7b\"></stop><stop offset=\"81.3%\" stop-color=\"#eaa584\"></stop><stop offset=\"87.5%\" stop-color=\"#edae8d\"></stop><stop offset=\"93.8%\" stop-color=\"#f0b796\"></stop><stop offset=\"100.0%\" stop-color=\"#f3c09f\"></stop></linearGradient><linearGradient id=\"brand-l3\" gradientUnits=\"userSpaceOnUse\" x1=\"71.978\" y1=\"95.435\" x2=\"60.021\" y2=\"4.564\"><stop offset=\"0.0%\" stop-color=\"#2b33e6\"></stop><stop offset=\"6.3%\" stop-color=\"#3139e9\"></stop><stop offset=\"12.5%\" stop-color=\"#363fec\"></stop><stop offset=\"18.8%\" stop-color=\"#3c44ef\"></stop><stop offset=\"25.0%\" stop-color=\"#4249f3\"></stop><stop offset=\"31.3%\" stop-color=\"#484ef6\"></stop><stop offset=\"37.5%\" stop-color=\"#4e53f9\"></stop><stop offset=\"43.8%\" stop-color=\"#5457fc\"></stop><stop offset=\"50.0%\" stop-color=\"#5a5cff\"></stop><stop offset=\"56.3%\" stop-color=\"#6569ff\"></stop><stop offset=\"62.5%\" stop-color=\"#7075ff\"></stop><stop offset=\"68.8%\" stop-color=\"#7c80ff\"></stop><stop offset=\"75.0%\" stop-color=\"#888bff\"></stop><stop offset=\"81.3%\" stop-color=\"#9595ff\"></stop><stop offset=\"87.5%\" stop-color=\"#a29fff\"></stop><stop offset=\"93.8%\" stop-color=\"#afa9ff\"></stop><stop offset=\"100.0%\" stop-color=\"#bdb3ff\"></stop></linearGradient><clipPath id=\"brand-l4\"><polygon points=\"16,46.5 84,46.5 84,25.5 16,25.5\"></polygon></clipPath><clipPath id=\"brand-l5\"><polygon points=\"16,48.5 84,48.5 84,23.5 16,23.5\"></polygon></clipPath><clipPath id=\"brand-l6\"><polygon points=\"81.410,86.630 71.410,10.630 50.589,13.369 60.589,89.369\"></polygon></clipPath><clipPath id=\"brand-l7\"><polygon points=\"83.393,86.369 73.393,10.369 48.606,13.630 58.606,89.630\"></polygon></clipPath><clipPath id=\"brand-l8\"><polygon points=\"12,74.5 88,74.5 88,53.5 12,53.5\"></polygon></clipPath><clipPath id=\"brand-l9\"><polygon points=\"12,76.5 88,76.5 88,51.5 12,51.5\"></polygon></clipPath><clipPath id=\"brand-l10\"><polygon points=\"39.410,89.369 49.410,13.369 28.589,10.630 18.589,86.630\"></polygon></clipPath><clipPath id=\"brand-l11\"><polygon points=\"41.393,89.630 51.393,13.630 26.606,10.369 16.606,86.369\"></polygon></clipPath></defs><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-l0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-l1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-l2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-l3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line><g clip-path=\"url(#brand-l4)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l5)\"><line x1=\"29\" y1=\"88\" x2=\"39\" y2=\"12\" stroke=\"url(#brand-l2)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l6)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l7)\"><line x1=\"16\" y1=\"36\" x2=\"84\" y2=\"36\" stroke=\"url(#brand-l1)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l8)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l9)\"><line x1=\"71\" y1=\"88\" x2=\"61\" y2=\"12\" stroke=\"url(#brand-l3)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l10)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"#f6f6f6\" stroke-width=\"21\" stroke-linecap=\"round\"></line></g><g clip-path=\"url(#brand-l11)\"><line x1=\"12\" y1=\"64\" x2=\"88\" y2=\"64\" stroke=\"url(#brand-l0)\" stroke-width=\"15\" stroke-linecap=\"round\"></line></g></svg></span><span class=\"channel-brand-text\"><strong>共议</strong><span>AI CoDiscuss</span></span></header>", 1),
54885
54885
  K("div", n2, [
54886
54886
  q(jZ, {
54887
54887
  onSelect: Ee,
@@ -54898,7 +54898,7 @@ var k1 = {
54898
54898
  class: "channel-section-toggle",
54899
54899
  "aria-expanded": f.value,
54900
54900
  onClick: r[0] ||= (e) => f.value = !f.value
54901
- }, [q(y, { name: f.value ? "i-lucide-chevron-down" : "i-lucide-chevron-right" }, null, 8, ["name"]), r[29] ||= J("频道工具", -1)], 8, i2)]),
54901
+ }, [q(y, { name: f.value ? "i-lucide-chevron-down" : "i-lucide-chevron-right" }, null, 8, ["name"]), r[30] ||= J("频道工具", -1)], 8, i2)]),
54902
54902
  f.value ? (U(), G(b, {
54903
54903
  key: 0,
54904
54904
  items: xe.value,
@@ -54914,37 +54914,56 @@ var k1 = {
54914
54914
  }
54915
54915
  }, null, 8, ["items"])) : Y("", !0)
54916
54916
  ]),
54917
- K("footer", a2, [K("button", {
54918
- type: "button",
54919
- class: "channel-self-button",
54920
- "aria-label": `账号与设置:${I(n).display_name(I(_))}${Se.value ? "," + Se.value : ""}`,
54921
- onClick: r[1] ||= (e) => s.value = "settings"
54922
- }, [q(w, {
54923
- src: I(n).identities[I(_)]?.icon,
54924
- text: I(n).identities[I(_)]?.initials,
54925
- alt: I(n).display_name(I(_)),
54926
- size: "sm",
54927
- class: j(["channel-avatar", I(n).identities[I(_)]?.style])
54928
- }, null, 8, [
54929
- "src",
54930
- "text",
54931
- "alt",
54932
- "class"
54933
- ]), K("span", s2, [K("strong", null, P(I(n).display_name(I(_))), 1), K("span", { class: j(["channel-connection", { "is-action": Se.value }]) }, [K("span", {
54934
- class: j({ connected: I(t).connected }),
54935
- title: I(t).connected ? "网站已连接" : "网站未连接"
54936
- }, null, 10, c2), J(P(Se.value || (I(t).connected ? "网站已连接" : "网站未连接")), 1)], 2)])], 8, o2), q(D, { text: "设置" }, {
54937
- default: L(() => [q(T, {
54938
- icon: "i-lucide-settings-2",
54939
- color: "neutral",
54940
- variant: "ghost",
54917
+ K("footer", a2, [
54918
+ K("button", {
54919
+ type: "button",
54920
+ class: "channel-self-button",
54921
+ "aria-label": `账号与设置:${I(n).display_name(I(_))}${Se.value ? "," + Se.value : ""}`,
54922
+ onClick: r[1] ||= (e) => s.value = "settings"
54923
+ }, [q(w, {
54924
+ src: I(n).identities[I(_)]?.icon,
54925
+ text: I(n).identities[I(_)]?.initials,
54926
+ alt: I(n).display_name(I(_)),
54941
54927
  size: "sm",
54942
- class: "channel-icon-button",
54943
- "aria-label": "设置",
54944
- onClick: r[2] ||= (e) => s.value = "settings"
54945
- })]),
54946
- _: 1
54947
- })])
54928
+ class: j(["channel-avatar", I(n).identities[I(_)]?.style])
54929
+ }, null, 8, [
54930
+ "src",
54931
+ "text",
54932
+ "alt",
54933
+ "class"
54934
+ ]), K("span", s2, [K("strong", null, P(I(n).display_name(I(_))), 1), K("span", { class: j(["channel-connection", { "is-action": Se.value }]) }, [K("span", {
54935
+ class: j({ connected: I(t).connected }),
54936
+ title: I(t).connected ? "网站已连接" : "网站未连接"
54937
+ }, null, 10, c2), J(P(Se.value || (I(t).connected ? "网站已连接" : I(t).reconnecting ? "正在重新连接…" : "网站未连接")), 1)], 2)])], 8, o2),
54938
+ I(t).connected ? Y("", !0) : (U(), G(D, {
54939
+ key: 0,
54940
+ text: "重新连接"
54941
+ }, {
54942
+ default: L(() => [q(T, {
54943
+ icon: "i-lucide-refresh-cw",
54944
+ color: "neutral",
54945
+ variant: "ghost",
54946
+ size: "sm",
54947
+ class: "channel-icon-button",
54948
+ "aria-label": "重新连接网站",
54949
+ loading: I(t).reconnecting,
54950
+ onClick: r[2] ||= (e) => I(n).reconnect()
54951
+ }, null, 8, ["loading"])]),
54952
+ _: 1
54953
+ })),
54954
+ q(D, { text: "设置" }, {
54955
+ default: L(() => [q(T, {
54956
+ icon: "i-lucide-settings-2",
54957
+ color: "neutral",
54958
+ variant: "ghost",
54959
+ size: "sm",
54960
+ class: "channel-icon-button",
54961
+ "aria-label": "设置",
54962
+ onClick: r[3] ||= (e) => s.value = "settings"
54963
+ })]),
54964
+ _: 1
54965
+ })
54966
+ ])
54948
54967
  ]),
54949
54968
  te.value ? (U(), W("div", {
54950
54969
  key: 0,
@@ -54985,7 +55004,7 @@ var k1 = {
54985
55004
  "aria-label": E.value ? "展开房间栏" : "收起房间栏",
54986
55005
  "aria-expanded": !E.value,
54987
55006
  "aria-controls": "channel-sidebar",
54988
- onClick: r[3] ||= (e) => E.value = !E.value
55007
+ onClick: r[4] ||= (e) => E.value = !E.value
54989
55008
  }, null, 8, ["aria-label", "aria-expanded"])]),
54990
55009
  _: 1
54991
55010
  }, 8, ["text"])) : (U(), G(T, {
@@ -54997,7 +55016,7 @@ var k1 = {
54997
55016
  class: "channel-mobile-nav channel-icon-button channel-panel-toggle",
54998
55017
  "aria-label": "打开房间导航",
54999
55018
  "aria-expanded": d.value,
55000
- onClick: r[4] ||= (e) => d.value = !0
55019
+ onClick: r[5] ||= (e) => d.value = !0
55001
55020
  }, null, 8, ["aria-expanded"])),
55002
55021
  K("span", f2, [q(y, { name: "i-lucide-hash" })]),
55003
55022
  K("div", p2, [K("div", m2, [K("h1", { title: o.value?.title }, P(o.value?.title || "共议"), 9, h2)]), o.value?.archived ? (U(), G(oe, {
@@ -55006,22 +55025,22 @@ var k1 = {
55006
55025
  variant: "subtle",
55007
55026
  size: "sm"
55008
55027
  }, {
55009
- default: L(() => [...r[31] ||= [J("已存档", -1)]]),
55028
+ default: L(() => [...r[32] ||= [J("已存档", -1)]]),
55010
55029
  _: 1
55011
55030
  })) : Y("", !0)]),
55012
55031
  o.value ? (U(), W("span", g2, [
55013
55032
  J(P(me.find((e) => e.value === I(t).tab)?.label), 1),
55014
- r[32] ||= K("span", null, "·", -1),
55033
+ r[33] ||= K("span", null, "·", -1),
55015
55034
  J(P(o.value.message_count) + " 条记录", 1)
55016
55035
  ])) : Y("", !0),
55017
55036
  K("div", _2, [
55018
55037
  I(t).tab === "discussion" ? (U(), W("form", {
55019
55038
  key: 0,
55020
55039
  class: "channel-search",
55021
- onSubmit: r[7] ||= uu((e) => I(i)("filter", { query: I(g) }), ["prevent"])
55040
+ onSubmit: r[8] ||= uu((e) => I(i)("filter", { query: I(g) }), ["prevent"])
55022
55041
  }, [q(k, {
55023
55042
  modelValue: I(g),
55024
- "onUpdate:modelValue": r[6] ||= (e) => /* @__PURE__ */ ln(g) ? g.value = e : null,
55043
+ "onUpdate:modelValue": r[7] ||= (e) => /* @__PURE__ */ ln(g) ? g.value = e : null,
55025
55044
  icon: "i-lucide-search",
55026
55045
  variant: "none",
55027
55046
  size: "sm",
@@ -55037,7 +55056,7 @@ var k1 = {
55037
55056
  variant: "ghost",
55038
55057
  size: "xs",
55039
55058
  "aria-label": "清除搜索",
55040
- onClick: r[5] ||= (e) => {
55059
+ onClick: r[6] ||= (e) => {
55041
55060
  g.value = "", I(i)("filter", { query: "" });
55042
55061
  }
55043
55062
  })) : Y("", !0), q(T, {
@@ -55064,7 +55083,7 @@ var k1 = {
55064
55083
  "aria-pressed": I(t).mention_only,
55065
55084
  "aria-label": "提到我",
55066
55085
  disabled: !o.value,
55067
- onClick: r[8] ||= (e) => I(i)("filter", { mention: !I(t).mention_only })
55086
+ onClick: r[9] ||= (e) => I(i)("filter", { mention: !I(t).mention_only })
55068
55087
  }, null, 8, [
55069
55088
  "variant",
55070
55089
  "aria-pressed",
@@ -55094,7 +55113,7 @@ var k1 = {
55094
55113
  }, 8, ["text"])
55095
55114
  ]),
55096
55115
  I(t).launch.recoveries.length ? (U(), W("section", v2, [
55097
- r[33] ||= K("h2", null, "继续未完成的请求", -1),
55116
+ r[34] ||= K("h2", null, "继续未完成的请求", -1),
55098
55117
  (U(!0), W(H, null, B(I(t).launch.recoveries, (e) => (U(), W("article", { key: e.id }, [
55099
55118
  K("strong", null, P(e.title), 1),
55100
55119
  K("p", y2, P(e.error || "启动结果尚未确认;原请求已保存在此浏览器中。"), 1),
@@ -55129,11 +55148,11 @@ var k1 = {
55129
55148
  }, null, 8, ["disabled", "onClick"])
55130
55149
  ])
55131
55150
  ]))), 128)),
55132
- r[34] ||= K("small", null, "移除待办不会删除房间或停止讨论。", -1)
55151
+ r[35] ||= K("small", null, "移除待办不会删除房间或停止讨论。", -1)
55133
55152
  ])) : Y("", !0),
55134
55153
  o.value ? (U(), W("div", x2, [q(he, {
55135
55154
  modelValue: pe.value,
55136
- "onUpdate:modelValue": r[9] ||= (e) => pe.value = e,
55155
+ "onUpdate:modelValue": r[10] ||= (e) => pe.value = e,
55137
55156
  items: me,
55138
55157
  content: !1,
55139
55158
  variant: "pill",
@@ -55164,7 +55183,7 @@ var k1 = {
55164
55183
  variant: "link",
55165
55184
  size: "xs",
55166
55185
  class: "channel-brief-update",
55167
- onClick: r[10] ||= (e) => I(i)("tab", "context")
55186
+ onClick: r[11] ||= (e) => I(i)("tab", "context")
55168
55187
  }))]), vr(K("div", E2, [K("div", {
55169
55188
  class: "channel-brief-text markdown-body",
55170
55189
  innerHTML: o.value.rendered_brief
@@ -55175,31 +55194,31 @@ var k1 = {
55175
55194
  }, [K("div", A2, [
55176
55195
  vr(q(uK, {
55177
55196
  onDetail: De,
55178
- onEdit: r[11] ||= (e) => s.value = "edit"
55197
+ onEdit: r[12] ||= (e) => s.value = "edit"
55179
55198
  }, null, 512), [[Vc, I(t).tab === "discussion"]]),
55180
55199
  I(t).tab === "history" ? (U(), W("div", j2, [
55181
- ge.value ? (U(), W("section", M2, [K("div", N2, [r[35] ||= K("div", null, [K("h2", null, "当前讨论"), K("p", null, "没有进行中的讨论。开启后由主持人推进成员轮流发言,到上限或结束后房间回到自由协作,记录与结论保留。")], -1), q(T, {
55200
+ ge.value ? (U(), W("section", M2, [K("div", N2, [r[36] ||= K("div", null, [K("h2", null, "当前讨论"), K("p", null, "没有进行中的讨论。开启后由主持人推进成员轮流发言,到上限或结束后房间回到自由协作,记录与结论保留。")], -1), q(T, {
55182
55201
  icon: "i-lucide-messages-square",
55183
55202
  label: "开启讨论",
55184
55203
  variant: "outline",
55185
55204
  color: "neutral",
55186
55205
  class: "channel-surface-button",
55187
55206
  disabled: !ve.value,
55188
- onClick: r[13] ||= (e) => _e.value = !0
55207
+ onClick: r[14] ||= (e) => _e.value = !0
55189
55208
  }, null, 8, ["disabled"])]), !ve.value && !o.value.archived ? (U(), W("p", P2, "开启讨论需要一位由本机唤醒的 AI 主持人,且本机服务在线。")) : Y("", !0)])) : (U(), G(jK, {
55190
55209
  key: 0,
55191
55210
  section: "host",
55192
- onOpen: r[12] ||= (e) => s.value = e,
55211
+ onOpen: r[13] ||= (e) => s.value = e,
55193
55212
  onDetail: De
55194
55213
  })),
55195
- K("section", F2, [K("div", I2, [r[36] ||= K("div", null, [K("h2", null, "当前共识"), K("p", null, "主持人整理的结论、共识与分歧,用 #消息编号 保留依据;讨论结束时保存进下方记录。")], -1), M.value ? Y("", !0) : (U(), G(T, {
55214
+ K("section", F2, [K("div", I2, [r[37] ||= K("div", null, [K("h2", null, "当前共识"), K("p", null, "主持人整理的结论、共识与分歧,用 #消息编号 保留依据;讨论结束时保存进下方记录。")], -1), M.value ? Y("", !0) : (U(), G(T, {
55196
55215
  key: 0,
55197
55216
  icon: "i-lucide-pencil",
55198
55217
  label: "编辑",
55199
55218
  variant: "outline",
55200
55219
  color: "neutral",
55201
55220
  class: "channel-surface-button",
55202
- onClick: r[14] ||= (e) => M.value = !0
55221
+ onClick: r[15] ||= (e) => M.value = !0
55203
55222
  }))]), M.value ? (U(), W("form", {
55204
55223
  key: 0,
55205
55224
  class: "summary-form",
@@ -55212,7 +55231,7 @@ var k1 = {
55212
55231
  }, {
55213
55232
  default: L(() => [q(N, {
55214
55233
  modelValue: I(h),
55215
- "onUpdate:modelValue": r[15] ||= (e) => /* @__PURE__ */ ln(h) ? h.value = e : null,
55234
+ "onUpdate:modelValue": r[16] ||= (e) => /* @__PURE__ */ ln(h) ? h.value = e : null,
55216
55235
  "aria-label": "主持简报正文",
55217
55236
  rows: 4,
55218
55237
  class: "w-full summary-brief-editor",
@@ -55227,7 +55246,7 @@ var k1 = {
55227
55246
  }, {
55228
55247
  default: L(() => [q(N, {
55229
55248
  modelValue: I(m),
55230
- "onUpdate:modelValue": r[16] ||= (e) => /* @__PURE__ */ ln(m) ? m.value = e : null,
55249
+ "onUpdate:modelValue": r[17] ||= (e) => /* @__PURE__ */ ln(m) ? m.value = e : null,
55231
55250
  "aria-label": "共识与分歧正文",
55232
55251
  rows: 12,
55233
55252
  maxlength: 4e3,
@@ -55240,7 +55259,7 @@ var k1 = {
55240
55259
  label: "取消",
55241
55260
  color: "neutral",
55242
55261
  variant: "ghost",
55243
- onClick: r[17] ||= (e) => M.value = !1
55262
+ onClick: r[18] ||= (e) => M.value = !1
55244
55263
  }), q(T, {
55245
55264
  type: "submit",
55246
55265
  color: "neutral",
@@ -55249,17 +55268,17 @@ var k1 = {
55249
55268
  class: "channel-primary-button",
55250
55269
  loading: I(a) === "save_context"
55251
55270
  }, null, 8, ["loading"])])])
55252
- ], 32)) : o.value.host_brief?.text || o.value.state_text ? (U(), W(H, { key: 1 }, [K("div", z2, [K("h3", null, [r[37] ||= J("主持简报", -1), o.value.state_until ? (U(), W("button", {
55271
+ ], 32)) : o.value.host_brief?.text || o.value.state_text ? (U(), W(H, { key: 1 }, [K("div", z2, [K("h3", null, [r[38] ||= J("主持简报", -1), o.value.state_until ? (U(), W("button", {
55253
55272
  key: 0,
55254
55273
  type: "button",
55255
55274
  class: "subtle-link",
55256
- onClick: r[18] ||= (e) => De(o.value.state_until)
55275
+ onClick: r[19] ||= (e) => De(o.value.state_until)
55257
55276
  }, "整理至 #" + P(o.value.state_until), 1)) : Y("", !0)]), K("div", {
55258
55277
  class: "markdown-body",
55259
55278
  innerHTML: o.value.rendered_brief
55260
55279
  }, null, 8, B2)]), ye.value ? (U(), W("details", V2, [K("summary", null, [
55261
- r[38] ||= K("h3", null, "详细共识与进度", -1),
55262
- r[39] ||= K("span", null, "依据、已处理内容、未决问题与下一步", -1),
55280
+ r[39] ||= K("h3", null, "详细共识与进度", -1),
55281
+ r[40] ||= K("span", null, "依据、已处理内容、未决问题与下一步", -1),
55263
55282
  q(y, {
55264
55283
  name: "i-lucide-chevron-down",
55265
55284
  class: "disclosure-icon"
@@ -55271,25 +55290,25 @@ var k1 = {
55271
55290
  q(S$, { onDetail: De }),
55272
55291
  q(YZ, {
55273
55292
  open: _e.value,
55274
- "onUpdate:open": r[19] ||= (e) => _e.value = e
55293
+ "onUpdate:open": r[20] ||= (e) => _e.value = e
55275
55294
  }, null, 8, ["open"])
55276
55295
  ])) : Y("", !0),
55277
55296
  I(t).tab === "overview" ? (U(), W("div", W2, [q(e1), ge.value ? Y("", !0) : (U(), G(jK, {
55278
55297
  key: 0,
55279
55298
  section: "access",
55280
- onOpen: r[20] ||= (e) => s.value = e,
55299
+ onOpen: r[21] ||= (e) => s.value = e,
55281
55300
  onDetail: De
55282
55301
  }))])) : Y("", !0),
55283
55302
  I(t).tab === "timeline" ? (U(), W("div", G2, [q(e2, { onDetail: De })])) : Y("", !0)
55284
55303
  ])], 2)) : (U(), W("div", K2, [
55285
55304
  q(y, { name: "i-lucide-hash" }),
55286
- r[40] ||= K("h2", null, "新建一个协作房间", -1),
55287
- r[41] ||= K("p", null, "邀请 AI 一起协作,随时 @ 提问;需要时开启讨论,保留所有人的观点。", -1),
55305
+ r[41] ||= K("h2", null, "新建一个协作房间", -1),
55306
+ r[42] ||= K("p", null, "邀请 AI 一起协作,随时 @ 提问;需要时开启讨论,保留所有人的观点。", -1),
55288
55307
  q(T, {
55289
55308
  icon: "i-lucide-plus",
55290
55309
  label: "新建房间",
55291
55310
  color: "neutral",
55292
- onClick: r[21] ||= (e) => Oe()
55311
+ onClick: r[22] ||= (e) => Oe()
55293
55312
  })
55294
55313
  ]))]),
55295
55314
  ne.value ? (U(), W("div", {
@@ -55312,10 +55331,10 @@ var k1 = {
55312
55331
  onLostpointercapture: He,
55313
55332
  onKeydown: Ue
55314
55333
  }, null, 40, q2)) : Y("", !0),
55315
- ne.value ? (U(), W("aside", J2, [K("section", Y2, [o.value ? (U(), G(n$, { key: 0 })) : (U(), W("div", X2, [...r[42] ||= [K("header", { class: "pb-header" }, [K("strong", null, "讨论进度")], -1), K("p", { class: "channel-rail-empty" }, "暂无房间,新建讨论后在这里查看进度与参与者。", -1)]]))])])) : Y("", !0),
55334
+ ne.value ? (U(), W("aside", J2, [K("section", Y2, [o.value ? (U(), G(n$, { key: 0 })) : (U(), W("div", X2, [...r[43] ||= [K("header", { class: "pb-header" }, [K("strong", null, "讨论进度")], -1), K("p", { class: "channel-rail-empty" }, "暂无房间,新建讨论后在这里查看进度与参与者。", -1)]]))])])) : Y("", !0),
55316
55335
  q(Pe, {
55317
55336
  open: d.value,
55318
- "onUpdate:open": r[23] ||= (e) => d.value = e,
55337
+ "onUpdate:open": r[24] ||= (e) => d.value = e,
55319
55338
  side: "left",
55320
55339
  title: "协作房间",
55321
55340
  description: "共议 · AI CoDiscuss",
@@ -55339,7 +55358,7 @@ var k1 = {
55339
55358
  color: "neutral",
55340
55359
  variant: "ghost",
55341
55360
  label: "设置",
55342
- onClick: r[22] ||= (e) => {
55361
+ onClick: r[23] ||= (e) => {
55343
55362
  d.value = !1, s.value = "settings";
55344
55363
  }
55345
55364
  })]),
@@ -55347,7 +55366,7 @@ var k1 = {
55347
55366
  }, 8, ["open"]),
55348
55367
  q(Pe, {
55349
55368
  open: p.value,
55350
- "onUpdate:open": r[25] ||= (e) => p.value = e,
55369
+ "onUpdate:open": r[26] ||= (e) => p.value = e,
55351
55370
  title: "房间资料",
55352
55371
  description: "目标、背景与参与方式",
55353
55372
  ui: {
@@ -55383,7 +55402,7 @@ var k1 = {
55383
55402
  class: "info-section"
55384
55403
  }, [K("h3", null, P(e.label), 1), K("p", null, P(o.value[e.key] || "尚未填写"), 1)])), 64)),
55385
55404
  q(Fe),
55386
- K("section", Q2, [r[43] ||= K("h3", null, "参与身份", -1), (U(!0), W(H, null, B(Ce.value, (e) => (U(), W("div", {
55405
+ K("section", Q2, [r[44] ||= K("h3", null, "参与身份", -1), (U(!0), W(H, null, B(Ce.value, (e) => (U(), W("div", {
55387
55406
  key: e.name,
55388
55407
  class: "expert-row"
55389
55408
  }, [
@@ -55409,7 +55428,7 @@ var k1 = {
55409
55428
  block: "",
55410
55429
  class: "channel-surface-button",
55411
55430
  label: "编辑议题",
55412
- onClick: r[24] ||= (e) => {
55431
+ onClick: r[25] ||= (e) => {
55413
55432
  p.value = !1, s.value = "edit";
55414
55433
  }
55415
55434
  })
@@ -55418,11 +55437,11 @@ var k1 = {
55418
55437
  }, 8, ["open"]),
55419
55438
  q(QX, {
55420
55439
  modelValue: s.value,
55421
- "onUpdate:modelValue": r[26] ||= (e) => s.value = e,
55440
+ "onUpdate:modelValue": r[27] ||= (e) => s.value = e,
55422
55441
  "reading-width": x.value,
55423
- "onUpdate:readingWidth": r[27] ||= (e) => x.value = e,
55442
+ "onUpdate:readingWidth": r[28] ||= (e) => x.value = e,
55424
55443
  effects: S.value,
55425
- "onUpdate:effects": r[28] ||= (e) => S.value = e,
55444
+ "onUpdate:effects": r[29] ||= (e) => S.value = e,
55426
55445
  "reading-options": I(fe),
55427
55446
  "effect-options": v,
55428
55447
  "detail-id": u.value,