wezard 2.1.3 → 2.1.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/dist/daemon/detail.js +12 -0
- package/dist/daemon/detail.js.map +1 -1
- package/dist/daemon/inbound.js +11 -3
- package/dist/daemon/inbound.js.map +1 -1
- package/dist/daemon/mirror-bridge.js +26 -1
- package/dist/daemon/mirror-bridge.js.map +1 -1
- package/dist/daemon/ws.js +3 -0
- package/dist/daemon/ws.js.map +1 -1
- package/package.json +1 -1
- package/web/chat.css +14 -1
- package/web/chat.js +31 -6
package/dist/daemon/ws.js
CHANGED
|
@@ -25,6 +25,8 @@ const MAX_AUTH_FAIL = 5;
|
|
|
25
25
|
// ws 库拿 handshakeTimeout 当 req 的 idle timeout, 超时走 abortHandshake ->
|
|
26
26
|
// emitErrorAndClose -> emit 'close' -> SDK close 回调 -> scheduleReconnect, 链路续上。
|
|
27
27
|
const HANDSHAKE_TIMEOUT_MS = 15_000;
|
|
28
|
+
// SDK 只拿它当媒体下载 (axios) 的超时; 默认 10s 下大图/慢链路时常超时, 图就被丢了。
|
|
29
|
+
const MEDIA_TIMEOUT_MS = 60_000;
|
|
28
30
|
const sdkLogger = (log) => ({
|
|
29
31
|
debug: (msg, ...a) => log.debug({ a }, String(msg)),
|
|
30
32
|
info: (msg, ...a) => log.info({ a }, String(msg)),
|
|
@@ -43,6 +45,7 @@ export const startWs = (cfg, log) => {
|
|
|
43
45
|
maxReconnectAttempts: MAX_RECONNECT,
|
|
44
46
|
maxAuthFailureAttempts: MAX_AUTH_FAIL,
|
|
45
47
|
wsOptions: { handshakeTimeout: HANDSHAKE_TIMEOUT_MS },
|
|
48
|
+
requestTimeout: MEDIA_TIMEOUT_MS,
|
|
46
49
|
scene: SCENE,
|
|
47
50
|
plug_version: PLUG_VERSION,
|
|
48
51
|
});
|
package/dist/daemon/ws.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../../daemon/ws.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,sDAAsD;AACtD,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,GAE1B,MAAM,uBAAuB,CAAC;AAI/B,kEAAkE;AAClE,2BAA2B;AAC3B,MAAM,KAAK,GAAG,CAAC,CAAC;AAChB,MAAM,YAAY,GAAG,OAAO,CAAC;AAC7B,MAAM,YAAY,GAAG,MAAM,CAAC;AAC5B,2DAA2D;AAC3D,yDAAyD;AACzD,gEAAgE;AAChE,qDAAqD;AACrD,kDAAkD;AAClD,0DAA0D;AAC1D,4DAA4D;AAC5D,MAAM,aAAa,GAAG,CAAC,CAAC,CAAC;AACzB,MAAM,aAAa,GAAG,CAAC,CAAC;AACxB,iEAAiE;AACjE,uDAAuD;AACvD,sDAAsD;AACtD,+DAA+D;AAC/D,8DAA8D;AAC9D,mDAAmD;AACnD,gCAAgC;AAChC,qEAAqE;AACrE,gFAAgF;AAChF,MAAM,oBAAoB,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../../daemon/ws.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/web/chat.css
CHANGED
|
@@ -240,6 +240,15 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
|
|
|
240
240
|
}
|
|
241
241
|
.chathead .back { display: none; border: 0; background: none; font-size: 22px; color: var(--mute); cursor: pointer; padding: 0 4px }
|
|
242
242
|
.ch-who { display: flex; gap: 11px; align-items: center; min-width: 0; flex: 1 }
|
|
243
|
+
/* 一对一的两端头像: 同单个角色头像的画法, 后一个略压住前一个。 */
|
|
244
|
+
.ch-who .pair { display: flex; flex: none; align-items: center }
|
|
245
|
+
.ch-who .pair .av {
|
|
246
|
+
display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;
|
|
247
|
+
background: #fff; border: 1px solid var(--line); font-size: 20px; line-height: 1;
|
|
248
|
+
}
|
|
249
|
+
.ch-who .pair .av + .av { margin-left: -8px; box-shadow: -2px 0 0 var(--card) }
|
|
250
|
+
.ch-who .pair .av.go { cursor: pointer }
|
|
251
|
+
.ch-who .pair .av.go:hover { border-color: var(--accent) }
|
|
243
252
|
.ch-who .t { font-size: 16px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
244
253
|
.ch-who > .t:not(:only-child) { flex: none }
|
|
245
254
|
.ch-who .sub { font-size: 11.5px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
@@ -278,7 +287,7 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
|
|
|
278
287
|
.mrow.mine { flex-direction: row-reverse }
|
|
279
288
|
.mrow.mine .mcol { align-items: flex-end }
|
|
280
289
|
.mwho { font-size: 11.5px; color: var(--mute); display: flex; gap: 6px; align-items: center; flex-wrap: wrap }
|
|
281
|
-
.mwho .to { color: var(--faint) }
|
|
290
|
+
.mwho .to { color: var(--faint); display: inline-flex; gap: 6px; align-items: center }
|
|
282
291
|
.mwho .ch { font-size: 10.5px; border-radius: 4px; padding: 0 4px }
|
|
283
292
|
.mwho .ch.priv { color: var(--priv); background: #9a5b1014 }
|
|
284
293
|
/* 时刻只此一处 —— 气泡外, 跟着名字走。等宽数字让一列时间对得齐。 */
|
|
@@ -294,6 +303,10 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
|
|
|
294
303
|
.say-cap { font-size: 11.5px; color: var(--mute) }
|
|
295
304
|
.mb .mq .mmeta { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; padding: 8px 14px 0; font-size: 11px; color: var(--faint) }
|
|
296
305
|
.mb .mq .mmeta + .md-body { padding-top: 4px }
|
|
306
|
+
/* 随这句话来的图: 详情页那份是裁成缩略的 (cover), 这里要整张看得见 —— 按原比例缩进气泡, 不裁。 */
|
|
307
|
+
.mb .mq .q-imgs { padding: 10px 14px 4px }
|
|
308
|
+
.mb .mq .q-imgs a { display: block; max-width: 100% }
|
|
309
|
+
.mb .mq .q-imgs img { max-width: min(100%, 420px); max-height: 360px; width: auto; height: auto; object-fit: contain }
|
|
297
310
|
.mchip { border-radius: 4px; padding: 0 5px; background: #f1f3f6; color: var(--mute) }
|
|
298
311
|
.mchip.priv { color: var(--priv); background: #9a5b1014 }
|
|
299
312
|
.mchip.graph { color: #6639ba; background: #8250df12 }
|
package/web/chat.js
CHANGED
|
@@ -279,6 +279,16 @@
|
|
|
279
279
|
|
|
280
280
|
// ── 左栏: 当前 role 的名片 + 会话列表 ──
|
|
281
281
|
var convOf = function (key) { return R.convs.filter(function (c) { return c.key === key; })[0]; };
|
|
282
|
+
// 人的单聊 (user:…) 在数据上是频道, 但对端只有那一个人时同样是一对一。
|
|
283
|
+
var dmOf = function (c) {
|
|
284
|
+
return c && c.kind === 'group' && c.base.indexOf('user:') === 0 && (c.subs || []).length === 1 ? c.subs[0] : null;
|
|
285
|
+
};
|
|
286
|
+
// 一对一会话的对端 (wizard 私聊 / 人的单聊 / 「只看我与 X」); 不是一对一则空。
|
|
287
|
+
var pairPeer = function (c) {
|
|
288
|
+
if (!c) return WITH || '';
|
|
289
|
+
var dm = dmOf(c);
|
|
290
|
+
return c.kind !== 'group' ? c.peer : WITH || (dm ? dm.role : '');
|
|
291
|
+
};
|
|
282
292
|
|
|
283
293
|
// 群聊头像 = 参与者头像的拼图 (最多 4 格, 自己排第一)。私聊就是对端自己的头像。
|
|
284
294
|
var membersOf = function (c) {
|
|
@@ -311,6 +321,11 @@
|
|
|
311
321
|
ms.map(function (m) { return '<i>' + esc(m.label || roleLabel(m.role)) + '</i>'; }).join('') + '</span>';
|
|
312
322
|
};
|
|
313
323
|
|
|
324
|
+
// ends = [[id, 已知头像]]; 没带头像的按 role 查。
|
|
325
|
+
var pairOf = function (ends) {
|
|
326
|
+
return '<span class="pair">' + ends.map(function (e) { return goSpan('av', e[0], esc(e[1] || roleLabel(e[0]))); }).join('') + '</span>';
|
|
327
|
+
};
|
|
328
|
+
|
|
314
329
|
var line = function (title, ts, pv, lamp, unread) {
|
|
315
330
|
return '<span class="b"><span class="l1"><span class="t">' + title + '</span>' + (lamp || '') +
|
|
316
331
|
'<span class="ts">' + esc(fmtAgo(ts)) + '</span></span>' +
|
|
@@ -591,7 +606,12 @@
|
|
|
591
606
|
}
|
|
592
607
|
var c = convOf(CONV);
|
|
593
608
|
if (!c) { who.innerHTML = ''; acts.innerHTML = ''; return; }
|
|
594
|
-
|
|
609
|
+
// 一对一 (私聊, 或群里「只看我与 X」) 两端都亮头像: 我在前, 对端在后, 各自是切视角的入口。
|
|
610
|
+
var dm = dmOf(c);
|
|
611
|
+
var peer = pairPeer(c);
|
|
612
|
+
var peerLabel = c.kind !== 'group' ? c.label : dm && peer === dm.role ? dm.label : '';
|
|
613
|
+
who.innerHTML = (peer ? pairOf([[ROLE, R.role && R.role.label], [peer, peerLabel]]) : '') +
|
|
614
|
+
'<span class="t">' + (c.kind === 'wizard' ? nm(c.peer, c.name, true) : esc(c.name)) + '</span>';
|
|
595
615
|
acts.innerHTML = WITH
|
|
596
616
|
? '<span class="with">只看我与 ' + nm(WITH, '', true) + '</span><button class="vb" id="ch-all">看全部</button>'
|
|
597
617
|
: '';
|
|
@@ -605,6 +625,11 @@
|
|
|
605
625
|
// 箭头朝外 = 这条气泡换视角后要去的那一侧; mine 行靠 CSS 翻转。
|
|
606
626
|
var CHEVRON = '<svg class="fc" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M6 3.5 10.5 8 6 12.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
607
627
|
// 行的另一侧是 .flip: 点它 = 换成这条消息的对端 (我发的 → 收信方; 别人发的 → 发话方)。
|
|
628
|
+
// 消息头上的角色头像 (发话方 / 收信方同一种画法), 点它切到那个角色的视角。
|
|
629
|
+
var avBtn = function (id, label) {
|
|
630
|
+
var sw = canSwitch(id) && id !== ROLE;
|
|
631
|
+
return '<button class="av' + (sw ? ' go' : '') + '" data-r="' + esc(id) + '" title="' + esc(nameOf(id)) + '"' + (sw ? '' : ' disabled') + '>' + esc(label || roleLabel(id)) + '</button>';
|
|
632
|
+
};
|
|
608
633
|
var rowHTML = function (m) {
|
|
609
634
|
learn(m.from, m.fromName, m.fromLabel);
|
|
610
635
|
learn(m.to, m.toName, m.toLabel);
|
|
@@ -616,21 +641,21 @@
|
|
|
616
641
|
// 看 key 而不是会话列表: 换视角就地重包时, 列表还是上一个 role 的。
|
|
617
642
|
var group = CONV.indexOf('p:') !== 0;
|
|
618
643
|
// 群里我不是收信方的那条 (X → Y), 头上写清是说给谁的。
|
|
619
|
-
var to = !mine && m.to !== ROLE && group ? '<span class="to">→ ' + nm(m.to, m.toName, true) + '</span>' : '';
|
|
644
|
+
var to = !mine && m.to !== ROLE && group ? '<span class="to">→ ' + avBtn(m.to, m.toLabel) + nm(m.to, m.toName, true) + '</span>' : '';
|
|
620
645
|
var priv = !m.channel && group ? '<span class="ch priv">私聊</span>' : '';
|
|
621
646
|
// 本轮的账 (呼吸点 + 模型 / token / 耗时) 跟在时刻后面 —— 片段是服务端渲染好的。
|
|
622
647
|
var stat = m.meta ? '<span class="mstat">' + m.meta + '</span>' : '';
|
|
648
|
+
// 一对一里收信方就是顶栏那个对端, 我的消息头不再重复写。
|
|
623
649
|
var who = mine
|
|
624
|
-
? '<span class="to">' + (m.to && m.to !== 'human:' ? '→ ' + nm(m.to, m.toName, true) : '') + '</span>' + stamp(m.ts) + stat
|
|
625
|
-
: nm(m.from, m.fromName, true) + to + priv + stamp(m.ts) + stat;
|
|
650
|
+
? '<span class="to">' + (m.to && m.to !== 'human:' && m.to !== (group ? pairPeer(convOf(CONV)) : CONV.slice(2)) ? '→ ' + avBtn(m.to, m.toLabel) + nm(m.to, m.toName, true) : '') + '</span>' + stamp(m.ts) + stat + avBtn(m.from, m.fromLabel)
|
|
651
|
+
: avBtn(m.from, m.fromLabel) + nm(m.from, m.fromName, true) + to + priv + stamp(m.ts) + stat;
|
|
626
652
|
var sw = canSwitch(other);
|
|
627
653
|
var flip = '<button class="flip" data-r="' + esc(other) + '"' + (sw ? '' : ' disabled tabindex="-1"') +
|
|
628
654
|
' aria-label="' + esc(sw ? '切到 ' + nameOf(other) + ' 的视角' : '') + '">' +
|
|
629
655
|
(sw ? '<span class="fi"><span class="fn">' + esc(nameOf(other)) + '</span>' + CHEVRON + '</span>' : '') + '</button>';
|
|
630
|
-
var av = mine ? '' : '<button class="av' + (canSwitch(m.from) ? ' go' : '') + '" data-r="' + esc(m.from) + '" title="' + esc(nameOf(m.from)) + '"' + (canSwitch(m.from) ? '' : ' disabled') + '>' + esc(m.fromLabel || roleLabel(m.from)) + '</button>';
|
|
631
656
|
return '<div class="mrow ' + (mine ? 'mine' : 'them') + '" data-id="' + esc(m.id) + '" data-turn="' + esc(m.turnId || m.id) + '" data-ts="' + m.ts + '"' +
|
|
632
657
|
(m.ping ? ' data-ping="1" data-ping-who="' + esc(m.dir === 'in' ? m.toName : m.fromName) + '"' : '') + ' data-sig="' + esc(m.sig) + '" data-stale-at="' + (m.staleAt || 0) + '">' +
|
|
633
|
-
'<div class="mcol"><div class="mwho">' +
|
|
658
|
+
'<div class="mcol"><div class="mwho">' + who + '</div><div class="mb">' + m.html + '</div></div>' +
|
|
634
659
|
flip +
|
|
635
660
|
'</div>';
|
|
636
661
|
};
|