wezard 2.0.2 → 2.0.4
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/CLAUDE.md +1 -1
- package/README.md +119 -291
- package/dist/daemon/index.js +73 -19
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/mirror-bridge.js +74 -19
- package/dist/daemon/mirror-bridge.js.map +1 -1
- package/dist/daemon/model-select.js +173 -61
- package/dist/daemon/model-select.js.map +1 -1
- package/dist/daemon/peers.js +54 -8
- package/dist/daemon/peers.js.map +1 -1
- package/dist/daemon/spawn-tmux.js +16 -4
- package/dist/daemon/spawn-tmux.js.map +1 -1
- package/dist/daemon/wizard.js +16 -4
- package/dist/daemon/wizard.js.map +1 -1
- package/dist/mcp/server.js +21 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/shared/chat-render.js +1 -1
- package/dist/shared/detail-render.js +6 -230
- package/dist/shared/detail-render.js.map +1 -1
- package/package.json +1 -1
- package/web/chat.css +34 -23
- package/web/chat.js +45 -37
package/web/chat.js
CHANGED
|
@@ -128,11 +128,23 @@
|
|
|
128
128
|
return k.label || (n && n.label) || ({ human: '👤', task: '⏰', system: '🧙' })[kindOf(id)] || '🧙';
|
|
129
129
|
};
|
|
130
130
|
// `.name` 的点由 CSS 画 —— 人与定时任务没有地址, 不画点。
|
|
131
|
-
var nm = function (id, name) {
|
|
132
|
-
return '<span class="nm ' + kindOf(id) + '">' + esc(name || roleName(id)) + '</span>';
|
|
133
|
-
};
|
|
134
131
|
var nameOf = function (id) { return (kindOf(id) === 'wizard' ? '.' : '') + roleName(id); };
|
|
135
132
|
var canSwitch = function (id) { return !!id && kindOf(id) !== 'task' && kindOf(id) !== 'system' && id !== 'human:'; };
|
|
133
|
+
// 头像与名字就是进入那个 role 视角的入口 —— 侧栏项、会话头、消息行同一种读法。
|
|
134
|
+
// 是 <span> 不是 <button>: 侧栏里它嵌在整行那颗按钮里。
|
|
135
|
+
var goSpan = function (cls, id, inner) {
|
|
136
|
+
var on = canSwitch(id) && id !== ROLE;
|
|
137
|
+
return '<span class="' + cls + (on ? ' go" data-r="' + esc(id) + '" title="' + esc('切到 ' + nameOf(id) + ' 的视角') : '') + '">' + inner + '</span>';
|
|
138
|
+
};
|
|
139
|
+
var nm = function (id, name, go) {
|
|
140
|
+
var cls = 'nm ' + kindOf(id), txt = esc(name || roleName(id));
|
|
141
|
+
return go ? goSpan(cls, id, txt) : '<span class="' + cls + '">' + txt + '</span>';
|
|
142
|
+
};
|
|
143
|
+
var bindGo = function (root, sel) {
|
|
144
|
+
root.querySelectorAll(sel || '.go[data-r]').forEach(function (b) {
|
|
145
|
+
b.onclick = function (e) { e.stopPropagation(); switchRole(b.getAttribute('data-r')); };
|
|
146
|
+
});
|
|
147
|
+
};
|
|
136
148
|
|
|
137
149
|
// ── markdown 渲染 (只对未渲染过的节点做, 复用节点不重绘) ──
|
|
138
150
|
var md = null;
|
|
@@ -265,29 +277,21 @@
|
|
|
265
277
|
};
|
|
266
278
|
|
|
267
279
|
var avatarOf = function (c, extra) {
|
|
268
|
-
if (c.kind !== 'group') return '
|
|
280
|
+
if (c.kind !== 'group') return goSpan('av', c.peer, esc(c.label) + (extra || ''));
|
|
269
281
|
var ms = membersOf(c).slice(0, 4);
|
|
270
282
|
return '<span class="av mosaic n' + ms.length + '" aria-hidden="true">' +
|
|
271
283
|
ms.map(function (m) { return '<i>' + esc(m.label || roleLabel(m.role)) + '</i>'; }).join('') + '</span>';
|
|
272
284
|
};
|
|
273
285
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
var SWAP = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M3 5.5h9M9.5 3l2.5 2.5L9.5 8M13 10.5H4M6.5 8 4 10.5 6.5 13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
277
|
-
var swapOf = function (role) {
|
|
278
|
-
return canSwitch(role) && role !== ROLE
|
|
279
|
-
? '<span class="sw" role="button" tabindex="0" data-sw="' + esc(role) + '" title="' + esc('切到 ' + nameOf(role) + ' 的视角') + '">' + SWAP + '</span>'
|
|
280
|
-
: '';
|
|
281
|
-
};
|
|
282
|
-
var line = function (title, sw, ts, pv) {
|
|
283
|
-
return '<span class="b"><span class="l1"><span class="t">' + title + '</span>' + sw +
|
|
286
|
+
var line = function (title, ts, pv) {
|
|
287
|
+
return '<span class="b"><span class="l1"><span class="t">' + title + '</span>' +
|
|
284
288
|
'<span class="ts">' + esc(fmtAgo(ts)) + '</span></span>' +
|
|
285
289
|
'<span class="pv">' + esc(pv) + '</span></span>';
|
|
286
290
|
};
|
|
287
291
|
|
|
288
292
|
var convItem = function (c) {
|
|
289
293
|
var on = c.key === CONV;
|
|
290
|
-
var title = c.kind === 'wizard' ? nm(c.peer, c.name) : '<span class="nm chat">' + esc(c.name) + '</span>';
|
|
294
|
+
var title = c.kind === 'wizard' ? nm(c.peer, c.name, true) : '<span class="nm chat">' + esc(c.name) + '</span>';
|
|
291
295
|
// 只列与我有往来的: 在群里但没和我说过话的人, 点进去也是空的。
|
|
292
296
|
var talked = on ? c.subs.filter(function (s) { return s.count; }) : [];
|
|
293
297
|
var subs = talked.length
|
|
@@ -295,13 +299,13 @@
|
|
|
295
299
|
var sel = s.role === WITH;
|
|
296
300
|
return '<button class="si' + (sel ? ' on' : '') + '" data-conv="' + esc(c.key) + '" data-with="' + esc(s.role) + '" ' +
|
|
297
301
|
'title="' + esc('我与 ' + nameOf(s.role) + ' 在这里的 ' + s.count + ' 条往来') + '">' +
|
|
298
|
-
'
|
|
299
|
-
line(nm(s.role, s.name
|
|
302
|
+
goSpan('av', s.role, esc(s.label) + lampOf(s.status)) +
|
|
303
|
+
line(nm(s.role, s.name, true), s.lastTs, s.preview) + '</button>';
|
|
300
304
|
}).join('') + '</div>'
|
|
301
305
|
: '';
|
|
302
306
|
var sel = on && !WITH;
|
|
303
307
|
return '<button class="ci' + (sel ? ' on' : '') + '" data-conv="' + esc(c.key) + '">' +
|
|
304
|
-
avatarOf(c, lampOf(c.status)) + line(title,
|
|
308
|
+
avatarOf(c, lampOf(c.status)) + line(title, c.lastTs, c.preview) +
|
|
305
309
|
'</button>' + subs;
|
|
306
310
|
};
|
|
307
311
|
|
|
@@ -319,11 +323,7 @@
|
|
|
319
323
|
convsEl.querySelectorAll('[data-conv]').forEach(function (b) {
|
|
320
324
|
b.onclick = function () { selectConv(b.getAttribute('data-conv'), b.getAttribute('data-with') || ''); };
|
|
321
325
|
});
|
|
322
|
-
convsEl
|
|
323
|
-
var go = function (e) { e.stopPropagation(); switchRole(x.getAttribute('data-sw')); };
|
|
324
|
-
x.onclick = go;
|
|
325
|
-
x.onkeydown = function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); go(e); } };
|
|
326
|
-
});
|
|
326
|
+
bindGo(convsEl);
|
|
327
327
|
};
|
|
328
328
|
|
|
329
329
|
var shortCwd = function (p) {
|
|
@@ -378,7 +378,7 @@
|
|
|
378
378
|
if (SESS_OPEN && e.key === 'Escape') setSessOpen(false);
|
|
379
379
|
});
|
|
380
380
|
|
|
381
|
-
// 名片: 身份 · 出身 · cwd ·
|
|
381
|
+
// 名片: 身份 · 出身 · cwd · 出生; 名片下面一排是关系 / 日程入口。
|
|
382
382
|
var renderRole = function () {
|
|
383
383
|
var r = R.role;
|
|
384
384
|
if (!r) return;
|
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
$('#rb-foot').hidden = R.sessions.length < 2;
|
|
414
414
|
bindSessPicker();
|
|
415
415
|
var acts = [];
|
|
416
|
-
//
|
|
416
|
+
// 入口只在有东西可看时出现 —— 挂在名片下、会话列表上, 不挤进名片: 名片的主角是身份。
|
|
417
417
|
if (R.relations) acts.push('<button class="bd' + (VIEW === 'world' ? ' on' : '') + '" data-view="world">关系图</button>');
|
|
418
418
|
if (R.schedules) acts.push('<button class="bd' + (VIEW === 'plan' ? ' on' : '') + '" data-view="plan">日程<b>' + R.schedules + '</b></button>');
|
|
419
419
|
$('#rb-acts').innerHTML = acts.join('');
|
|
@@ -460,12 +460,13 @@
|
|
|
460
460
|
}).join('') + '</span>' +
|
|
461
461
|
'</span>'
|
|
462
462
|
: '';
|
|
463
|
-
|
|
463
|
+
// 条首的标签就是模型名: 这本账是拿什么跑出来的。没有模型记录 (老轮次) 才退回「用量」。
|
|
464
|
+
return '<span class="u-lb" title="' + esc(t.model || '') + '">' +
|
|
465
|
+
esc(t.model ? t.model.replace(/^claude-/, '') : '用量') + '</span>' +
|
|
464
466
|
'<span class="u-kvs">' +
|
|
465
467
|
kv('turns', t.turns, t.turns) + kv('tools', u.tools, u.tools) + kv('api', u.calls, u.calls) +
|
|
466
468
|
kv('ctx', u.ctxPeak, fmtTok(u.ctxPeak)) + kv('time', liveDur(t), fmtDur(liveDur(t))) +
|
|
467
|
-
'</span>' + io
|
|
468
|
-
(t.model ? '<span class="u-model" title="' + esc(t.model) + '">' + esc(t.model.replace(/^claude-/, '')) + '</span>' : '');
|
|
469
|
+
'</span>' + io;
|
|
469
470
|
};
|
|
470
471
|
// 没变就不碰 DOM: 心跳每 3s 来一次, 重建会把悬停中的 tooltip 蹭掉。
|
|
471
472
|
var putUsage = function (el, t) {
|
|
@@ -484,6 +485,14 @@
|
|
|
484
485
|
putUsage($('#ch-usage'), !wiz && WITH && VIEW === 'msgs' ? R.winStats : null);
|
|
485
486
|
};
|
|
486
487
|
|
|
488
|
+
// 只有真群 (`chat:<roomid>`) 才有跳转: `user:` 是人与 bot 的单聊, userid 指向的是人自己而不是这段会话。
|
|
489
|
+
var JUMP = '<svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true"><path d="M9.5 2.5h4v4M13.5 2.5 7.5 8.5M11.5 9.5v3a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1h3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
490
|
+
var openInWecom = function (base) {
|
|
491
|
+
return base.indexOf('chat:') === 0
|
|
492
|
+
? '<a class="jump" href="wxwork://openconversation?roomid=' + encodeURIComponent(base.slice(5)) + '" title="在企业微信中打开这个群聊">' + JUMP + '</a>'
|
|
493
|
+
: '';
|
|
494
|
+
};
|
|
495
|
+
|
|
487
496
|
// ── 右栏头: 这个群聊 / 私聊是什么 (关系 / 日程视图时是视图名) ──
|
|
488
497
|
var renderHead = function () {
|
|
489
498
|
var who = $('#ch-who'), acts = $('#ch-acts');
|
|
@@ -497,18 +506,19 @@
|
|
|
497
506
|
var c = convOf(CONV);
|
|
498
507
|
if (!c) { who.innerHTML = ''; acts.innerHTML = ''; return; }
|
|
499
508
|
if (c.kind === 'wizard') {
|
|
500
|
-
who.innerHTML = avatarOf(c) + '<span class="l"><span class="t">' + nm(c.peer, c.name) + '</span>' +
|
|
509
|
+
who.innerHTML = avatarOf(c) + '<span class="l"><span class="t">' + nm(c.peer, c.name, true) + '</span>' +
|
|
501
510
|
'<span class="sub">私聊 · ' + c.count + ' 条</span></span>';
|
|
502
511
|
} else {
|
|
503
512
|
var ms = membersOf(c);
|
|
504
|
-
who.innerHTML = avatarOf(c) + '<span class="l"><span class="t">' + esc(c.name) + '</span>' +
|
|
513
|
+
who.innerHTML = avatarOf(c) + '<span class="l"><span class="tr"><span class="t">' + esc(c.name) + '</span>' + openInWecom(c.base) + '</span>' +
|
|
505
514
|
'<span class="sub">群聊 · ' + ms.length + ' 位成员 · ' +
|
|
506
515
|
ms.slice(0, 8).map(function (m) { return esc(m.label || roleLabel(m.role)); }).join('') +
|
|
507
516
|
(ms.length > 8 ? '…' : '') + '</span></span>';
|
|
508
517
|
}
|
|
509
518
|
acts.innerHTML = WITH
|
|
510
|
-
? '<span class="with">只看我与 ' + nm(WITH) + '</span><button class="vb" id="ch-all">看全部</button>'
|
|
519
|
+
? '<span class="with">只看我与 ' + nm(WITH, '', true) + '</span><button class="vb" id="ch-all">看全部</button>'
|
|
511
520
|
: '';
|
|
521
|
+
bindGo(who); bindGo(acts);
|
|
512
522
|
var x = $('#ch-all');
|
|
513
523
|
if (x) x.onclick = function () { selectConv(CONV, ''); };
|
|
514
524
|
};
|
|
@@ -529,11 +539,11 @@
|
|
|
529
539
|
// 看 key 而不是会话列表: 换视角就地重包时, 列表还是上一个 role 的。
|
|
530
540
|
var group = CONV.indexOf('p:') !== 0;
|
|
531
541
|
// 群里我不是收信方的那条 (X → Y), 头上写清是说给谁的。
|
|
532
|
-
var to = !mine && m.to !== ROLE && group ? '<span class="to">→ ' + nm(m.to, m.toName) + '</span>' : '';
|
|
542
|
+
var to = !mine && m.to !== ROLE && group ? '<span class="to">→ ' + nm(m.to, m.toName, true) + '</span>' : '';
|
|
533
543
|
var priv = !m.channel && group ? '<span class="ch priv">私聊</span>' : '';
|
|
534
544
|
var who = mine
|
|
535
|
-
? '<span class="to">' + (m.to && m.to !== 'human:' ? '→ ' + nm(m.to, m.toName) : '') + '</span>' + stamp(m.ts)
|
|
536
|
-
: nm(m.from, m.fromName) + to + priv + stamp(m.ts);
|
|
545
|
+
? '<span class="to">' + (m.to && m.to !== 'human:' ? '→ ' + nm(m.to, m.toName, true) : '') + '</span>' + stamp(m.ts)
|
|
546
|
+
: nm(m.from, m.fromName, true) + to + priv + stamp(m.ts);
|
|
537
547
|
var sw = canSwitch(other);
|
|
538
548
|
var flip = '<button class="flip" data-r="' + esc(other) + '"' + (sw ? '' : ' disabled tabindex="-1"') +
|
|
539
549
|
' aria-label="' + esc(sw ? '切到 ' + nameOf(other) + ' 的视角' : '') + '">' +
|
|
@@ -590,9 +600,7 @@
|
|
|
590
600
|
};
|
|
591
601
|
|
|
592
602
|
var bindRow = function (el) {
|
|
593
|
-
el
|
|
594
|
-
b.onclick = function () { switchRole(b.getAttribute('data-r')); };
|
|
595
|
-
});
|
|
603
|
+
bindGo(el, '.flip[data-r]:not([disabled]), .mwho .go[data-r]');
|
|
596
604
|
};
|
|
597
605
|
var rowNode = function (id) {
|
|
598
606
|
var list = inner.querySelectorAll('.mrow');
|