@xgspz/cli-ui 0.6.14 → 0.6.15
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/CHANGELOG.md +12 -3
- package/README.md +9 -4
- package/dist/{cli-ui-embed-D-3DvyTN.js → cli-ui-embed-vIXVNs6k.js} +7202 -7186
- package/dist/{cli-ui-index-C34qFb4u.js → cli-ui-index-R24x28OX.js} +2 -2
- package/dist/{cli-ui-jszip.min-DojbZPiE.js → cli-ui-jszip.min-DUghzByz.js} +1 -1
- package/dist/{cli-ui-jszip.min-BObcE2nn.js → cli-ui-jszip.min-DfH6rdQD.js} +2 -2
- package/dist/{cli-ui-xlsx-JVLtx0qR.js → cli-ui-xlsx-CP0lJhff.js} +1 -1
- package/dist/cli-ui.css +1 -1
- package/dist/cli-ui.js +1 -1
- package/package.json +1 -1
- package/src/chat/CliChat.tsx +29 -1
- package/src/core/conversation.ts +8 -4
- package/src/core/store.ts +8 -2
- package/src/core/types.ts +2 -0
- package/src/operator.base.css +3 -0
- package/src/operator.css +3 -0
package/dist/cli-ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as a, b as i, d as r, e as t, f as o, h as C, i as l, j as n, k as m, m as d, l as u, n as w, o as B, p as f } from "./cli-ui-embed-
|
|
1
|
+
import { C as a, b as i, d as r, e as t, f as o, h as C, i as l, j as n, k as m, m as d, l as u, n as w, o as B, p as f } from "./cli-ui-embed-vIXVNs6k.js";
|
|
2
2
|
export {
|
|
3
3
|
a as CliBoard,
|
|
4
4
|
i as CliBrowserView,
|
package/package.json
CHANGED
package/src/chat/CliChat.tsx
CHANGED
|
@@ -122,6 +122,11 @@ function batchStatusView(taskStatus: string | null, jobStatus: string | null): {
|
|
|
122
122
|
|
|
123
123
|
/** 「智能体在工作」那一行:跑了多久,很久没输出时提示可能卡住。与 Operator 的 RunningIndicator 一致。 */
|
|
124
124
|
const QUIET_AFTER_MS = 180_000;
|
|
125
|
+
/**
|
|
126
|
+
* 「正在加载会话」最多等这么久:会话记录还没到(执行节点暂时读不到、网关缓冲了实时数据、旧版中枢补发失败后
|
|
127
|
+
* 不再重试)就改成说明 +「重新读取」,不一直转圈。中枢报了 transcript_unavailable 时立刻说明。
|
|
128
|
+
*/
|
|
129
|
+
export const TRANSCRIPT_WAIT_MS = 20_000;
|
|
125
130
|
function RunningIndicator({ agent, agentName, startedAt, lastActivityRef }: { agent: string | null; agentName: string; startedAt: number | null; lastActivityRef: React.MutableRefObject<number> }) {
|
|
126
131
|
const [now, setNow] = useState(() => Date.now());
|
|
127
132
|
useEffect(() => { const t = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(t); }, []);
|
|
@@ -288,6 +293,24 @@ function ChatInner({ slots, passiveContext = true, configurationBusy, onClear, j
|
|
|
288
293
|
&& (remoteAwaiting === true || (!running && !!lastTurn?.asked && lastTurn.status === "succeeded"));
|
|
289
294
|
// 一次性任务的子任务还没派发(没有节点对话)、或者流已经结束还没有转录:不再转圈,转录里有说明。
|
|
290
295
|
const loading = !!key && !isNew && !!conv && !conv.hasTranscript && !conv.turns.length && conv.stream !== "error" && conv.stream !== "ended" && !(conv.batch && !conv.remoteTaskId);
|
|
296
|
+
// 记录迟迟不来:到时改成说明(见 TRANSCRIPT_WAIT_MS);「重新读取」后重新计时。
|
|
297
|
+
const [loadStalled, setLoadStalled] = useState(false);
|
|
298
|
+
const [loadAttempt, setLoadAttempt] = useState(0);
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
setLoadStalled(false);
|
|
301
|
+
if (!loading) return;
|
|
302
|
+
const timer = setTimeout(() => setLoadStalled(true), TRANSCRIPT_WAIT_MS);
|
|
303
|
+
return () => clearTimeout(timer);
|
|
304
|
+
}, [loading, key, loadAttempt]);
|
|
305
|
+
// 已结束的一次性任务:中枢补不到记录时关流前会先报 transcript_unavailable。
|
|
306
|
+
const transcriptProblem = !!key && !isNew && !!conv && !conv.hasTranscript && !conv.turns.length
|
|
307
|
+
? conv.transcriptError ?? (loading && loadStalled
|
|
308
|
+
? conv.stream === "connecting" || (conv.stream === "live" && !conv.job)
|
|
309
|
+
? "实时连接还没有收到数据:网络或网关可能缓冲了实时推送。"
|
|
310
|
+
: "会话记录还没读到:执行节点暂时没有响应。"
|
|
311
|
+
: null)
|
|
312
|
+
: null;
|
|
313
|
+
const rereadTranscript = () => { if (!key) return; setLoadAttempt((n) => n + 1); store.reconnect(key, { force: true }); };
|
|
291
314
|
const batchStatus = batch ? conv?.batchTask?.status ?? task?.status ?? null : null;
|
|
292
315
|
|
|
293
316
|
// 用量:任务详情里的累计 + 实时流里上次刷新后的增量(「新开」的对话还没有用量,不借第一个对话的)。
|
|
@@ -790,7 +813,12 @@ function ChatInner({ slots, passiveContext = true, configurationBusy, onClear, j
|
|
|
790
813
|
</div></div>
|
|
791
814
|
)}
|
|
792
815
|
{contextReset && <div className="msg system info"><div className="msg-body">ℹ 执行节点上的会话重建过:从那一轮起是新的 Agent 会话,之前的对话上下文没有带过去(工作区文件还在)。</div></div>}
|
|
793
|
-
{
|
|
816
|
+
{transcriptProblem && (
|
|
817
|
+
<div className="msg system info cli-transcript-problem" role="status"><div className="msg-body">⚠ {transcriptProblem}
|
|
818
|
+
<button className="btn btn-line btn-sm" style={{ marginLeft: 8 }} onClick={rereadTranscript}>重新读取</button>
|
|
819
|
+
</div></div>
|
|
820
|
+
)}
|
|
821
|
+
{loading && !transcriptProblem && (
|
|
794
822
|
<div aria-hidden>
|
|
795
823
|
<div className="session-label"><span className="ln" />正在加载会话<span className="ln" /></div>
|
|
796
824
|
<div className="msg user" style={{ opacity: .7 }}>
|
package/src/core/conversation.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface ConvState {
|
|
|
68
68
|
/** 节点转录(Operator 消息)。 */
|
|
69
69
|
msgs: Msg[];
|
|
70
70
|
hasTranscript: boolean;
|
|
71
|
+
/** 中枢说这段对话记录暂时读不到(transcript_unavailable);读到转录后清掉。 */
|
|
72
|
+
transcriptError: string | null;
|
|
71
73
|
/** 节点转录里比 msgs 更早、还没取的条数(实时流只给最新一段;往上翻时 store.loadOlder 分页取)。 */
|
|
72
74
|
older: number;
|
|
73
75
|
nodeRunning: boolean;
|
|
@@ -97,7 +99,7 @@ export interface ConvState {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
export const emptyConv = (key: string): ConvState => ({
|
|
100
|
-
key, ...parseConvKey(key), job: null, task: null, turns: [], msgs: [], hasTranscript: false, older: 0, nodeRunning: false, settled: 0, resolvedModel: null,
|
|
102
|
+
key, ...parseConvKey(key), job: null, task: null, turns: [], msgs: [], hasTranscript: false, transcriptError: null, older: 0, nodeRunning: false, settled: 0, resolvedModel: null,
|
|
101
103
|
liveUsage: { fresh: 0, cacheRead: 0 }, stream: "idle", streamError: null, endReason: null, continuedBy: null, localAnswers: {},
|
|
102
104
|
batch: false, batchTask: null, remoteTaskId: null,
|
|
103
105
|
});
|
|
@@ -161,7 +163,7 @@ function focusBatchTask(conv: ConvState, task: BatchStreamTask | null): ConvStat
|
|
|
161
163
|
const batchTask = task ? { ...kept, ...task } : null;
|
|
162
164
|
return {
|
|
163
165
|
...conv, batch: true, batchTask, task: batchTask ? batchStreamTask(batchTask) : null, remoteTaskId: remote,
|
|
164
|
-
...(sameRemote ? {} : { msgs: [], hasTranscript: false, older: 0, nodeRunning: false }),
|
|
166
|
+
...(sameRemote ? {} : { msgs: [], hasTranscript: false, transcriptError: null, older: 0, nodeRunning: false }),
|
|
165
167
|
};
|
|
166
168
|
}
|
|
167
169
|
|
|
@@ -190,12 +192,14 @@ export function applyItems(conv: ConvState, items: StreamItem[]): ConvState {
|
|
|
190
192
|
let next = conv;
|
|
191
193
|
for (const item of items) {
|
|
192
194
|
// 一次性任务:只认看的这个子任务所在的节点对话。
|
|
193
|
-
if (next.batch && (item.type === "transcript" || item.type === "node") && item.remote_task_id && item.remote_task_id !== next.remoteTaskId) continue;
|
|
195
|
+
if (next.batch && (item.type === "transcript" || item.type === "node" || item.type === "transcript_unavailable") && item.remote_task_id && item.remote_task_id !== next.remoteTaskId) continue;
|
|
194
196
|
if (item.type === "transcript") {
|
|
195
197
|
// 节点转录全量(连上 / 重连节点时各一次):整体替换,没变的消息保留对象引用。
|
|
196
198
|
const messages = Array.isArray(item.messages) ? item.messages.map(toMsg) : [];
|
|
197
199
|
const merged = mergeSnapshot(next.msgs, messages, typeof item.older === "number" ? item.older : 0);
|
|
198
|
-
next = { ...next, msgs: merged.msgs, older: merged.older, hasTranscript: true, nodeRunning: item.running === true };
|
|
200
|
+
next = { ...next, msgs: merged.msgs, older: merged.older, hasTranscript: true, transcriptError: null, nodeRunning: item.running === true };
|
|
201
|
+
} else if (item.type === "transcript_unavailable") {
|
|
202
|
+
if (!next.hasTranscript) next = { ...next, transcriptError: typeof item.error === "string" && item.error ? item.error : "暂时读不到这段对话记录" };
|
|
199
203
|
} else if (item.type === "node") {
|
|
200
204
|
next = applyNodeEvent(next, item.event);
|
|
201
205
|
} else if (item.type === "turn") {
|
package/src/core/store.ts
CHANGED
|
@@ -573,12 +573,18 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
/** 手动重连:给了对话键只连这一个,否则所有停下的 / 正在退避等待的都立刻再连(重试次数重新计)。 */
|
|
576
|
-
|
|
576
|
+
/**
|
|
577
|
+
* 立刻重连。缺省只接上停下的(offline、页面停放)或正在等重连的;force:连着的也断开重连一次,
|
|
578
|
+
* 让中枢重新补发快照(例如会话记录一直没读到时的「重新读取」)。
|
|
579
|
+
*/
|
|
580
|
+
function reconnect(key?: string, options: { force?: boolean } = {}) {
|
|
577
581
|
if (!key) { reconnectNow(); return; }
|
|
578
582
|
const entry = streams.get(key);
|
|
579
|
-
if (!entry || entry.refs <= 0 || (!entry.parked && !entry.timer)) return;
|
|
583
|
+
if (!entry || entry.refs <= 0 || (!options.force && !entry.parked && !entry.timer)) return;
|
|
580
584
|
if (entry.timer) { clearTimeout(entry.timer); entry.timer = null; }
|
|
581
585
|
entry.parked = false; entry.attempt = 0;
|
|
586
|
+
if (options.force) { settleDrop(entry); patchConv(key, (conv) => ({ ...conv, transcriptError: null })); }
|
|
587
|
+
// connect 会先关掉还连着的那条(包括已经收到 end 的)。
|
|
582
588
|
connect(key);
|
|
583
589
|
}
|
|
584
590
|
// 页面在后台太久:停掉实时流(主动停止不算断线,不会触发重连),切回来由 reconnectNow 接上。
|
package/src/core/types.ts
CHANGED
|
@@ -251,6 +251,8 @@ export type StreamItem =
|
|
|
251
251
|
/** remote_task_id:一次性任务的流里才有(大任务里可能同时有几个节点对话)。 */
|
|
252
252
|
/** older:快照只有最新一段时,更早还有几条(往上翻时经 history 分页取)。旧版中枢没有 = 全量。 */
|
|
253
253
|
| { type: "transcript"; remote_task_id?: string; messages: Message[] | null; pending: PendingMessage[] | null; running: boolean | null; older?: number }
|
|
254
|
+
/** 中枢读不到这个对话的记录(执行节点暂时连不上、读超时),会继续重试;旧版中枢没有。 */
|
|
255
|
+
| { type: "transcript_unavailable"; remote_task_id?: string; error: string }
|
|
254
256
|
| { type: "node"; remote_task_id?: string; event: NodeEvent }
|
|
255
257
|
| { type: "turn"; turn: CenterTurn }
|
|
256
258
|
| { type: "job"; job: { id: string; status: JobStatus; closed_reason?: string | null; node_id: string | null } }
|
package/src/operator.base.css
CHANGED
|
@@ -1476,6 +1476,9 @@ button.pop-item:hover{background:var(--surface-2);}
|
|
|
1476
1476
|
.composer .comp-model .comp-setting>svg{width:12px;height:12px;}
|
|
1477
1477
|
.composer .comp-foot{flex-wrap:wrap;gap:6px;}
|
|
1478
1478
|
.composer .comp-foot>.send-group{margin-left:auto;}
|
|
1479
|
+
/* 智能体紧挨发送按钮:它和发送都用 margin-left:auto 会平分空位,智能体被挤到中间(PC 窄聊天栏也会进到这一档)。
|
|
1480
|
+
改为智能体占满空位、按钮靠右;换行时发送仍靠右。 */
|
|
1481
|
+
.composer .comp-foot>.comp-model{flex:1 1 auto;display:flex;justify-content:flex-end;margin-left:0;}
|
|
1479
1482
|
}
|
|
1480
1483
|
@media(pointer:coarse){
|
|
1481
1484
|
.composer-popover .composer-option{min-height:52px;}
|
package/src/operator.css
CHANGED
|
@@ -1476,6 +1476,9 @@
|
|
|
1476
1476
|
.cli-ui .composer .comp-model .comp-setting>svg{width:12px;height:12px;}
|
|
1477
1477
|
.cli-ui .composer .comp-foot{flex-wrap:wrap;gap:6px;}
|
|
1478
1478
|
.cli-ui .composer .comp-foot>.send-group{margin-left:auto;}
|
|
1479
|
+
/* 智能体紧挨发送按钮:它和发送都用 margin-left:auto 会平分空位,智能体被挤到中间(PC 窄聊天栏也会进到这一档)。
|
|
1480
|
+
改为智能体占满空位、按钮靠右;换行时发送仍靠右。 */
|
|
1481
|
+
.cli-ui .composer .comp-foot>.comp-model{flex:1 1 auto;display:flex;justify-content:flex-end;margin-left:0;}
|
|
1479
1482
|
}
|
|
1480
1483
|
@media(pointer:coarse){
|
|
1481
1484
|
.cli-ui .composer-popover .composer-option{min-height:52px;}
|