deepccc 0.2.6 → 0.2.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/README.md +3 -0
- package/dist/context.d.ts +21 -0
- package/dist/context.js +82 -0
- package/dist/index.js +35 -8
- package/dist/web-page.d.ts +1 -1
- package/dist/web-page.js +328 -12
- package/dist/web-tool-presentation.d.ts +21 -0
- package/dist/web-tool-presentation.js +123 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ npm run dev
|
|
|
58
58
|
## 核心能力
|
|
59
59
|
|
|
60
60
|
- Web-first:多会话、持久化历史、实时流式过程、停止和恢复
|
|
61
|
+
- 工具时间线:ChatCCC 风格 emoji 摘要、参数/结果折叠、省略行展开和状态记忆
|
|
61
62
|
- 会话配置:每个会话独立选择 model、subModel 和 effort
|
|
62
63
|
- 图片附件:Web 支持选择、粘贴和拖拽 PNG/JPEG/WebP;Agent 可用 `present_file` 回传图片
|
|
63
64
|
- 本地工具:代码搜索、文件读写、补丁、命令执行、Git、网页搜索和抓取
|
|
@@ -154,6 +155,8 @@ deepccc
|
|
|
154
155
|
|
|
155
156
|
Web UI 支持新建、恢复、重命名和删除多会话,多个会话可以同时运行,即使它们指向同一个工作目录。每个会话可独立选择 model、subModel 和 effort,并持续复用 CLI 已保存的历史。注意:当前版本不自动创建 Git worktree;同目录的多个运行中 Agent 直接修改同一组文件,页面会提示覆盖与冲突风险。
|
|
156
157
|
|
|
158
|
+
模型文本、reasoning 心跳和工具事件通过 SSE 实时更新。每轮消息按真实发生顺序持久化和回放,因此刷新后仍会保持“阶段说明 → 工具调用 → 后续结论”的交错时间线;旧版会话没有顺序数据时,会兼容显示为“工具调用 → 最终回答”。每次工具调用与对应结果合并成一张卡片,折叠态显示 emoji、工具名、状态和关键参数;展开后调用参数默认保留前 8/后 4 行,工具结果保留前 12/后 6 行,省略内容可继续展开。工具卡及省略行的展开状态保存在当前浏览器标签页的 `sessionStorage`,持续生成、切换会话和刷新页面均不会自动收起。消息正文支持标题、表格、列表、引用、链接和代码块等常用 Markdown。
|
|
159
|
+
|
|
157
160
|
图片始终按本地附件处理,不转换为 Provider 原生多模态消息。Web 支持文件选择、剪贴板粘贴和拖拽,每条消息最多 10 张 PNG/JPEG/WebP、单张最大 20 MB;附件复制到 `~/.deepccc/attachments/<session-id>/`,Agent 收到本地绝对路径后使用可用工具自行处理。Agent 可调用 `present_file` 把当前工作目录或会话附件目录中的图片直接展示在会话中;删除会话时对应附件一并清理。
|
|
158
161
|
|
|
159
162
|
API 设置采用单一 Provider 配置,支持 OpenAI-compatible 与 Anthropic Messages。完整 API Key 只保存在本机 `~/.deepccc/config.json`,浏览器读取设置时仅返回掩码。危险命令会在会话中暂停并请求“拒绝、允许一次、本会话允许、永久允许”,浏览器断开或审批超时默认拒绝。
|
package/dist/context.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type BuiltinContextRole = "user" | "assistant";
|
|
|
4
4
|
* content 文本保持不变供模型消费;toolCalls 数组提供可检索、可回放的结构化数据。
|
|
5
5
|
*/
|
|
6
6
|
export interface BuiltinContextToolCall {
|
|
7
|
+
/** Provider tool-call id,用于 Web 在流式运行与持久化消息之间保持同一张工具卡状态。 */
|
|
8
|
+
id?: string;
|
|
7
9
|
name: string;
|
|
8
10
|
/** 工具入参(JSON 文本,已截断到安全上限) */
|
|
9
11
|
input?: string;
|
|
@@ -11,11 +13,28 @@ export interface BuiltinContextToolCall {
|
|
|
11
13
|
output?: string;
|
|
12
14
|
is_error?: boolean;
|
|
13
15
|
}
|
|
16
|
+
export type BuiltinContextTimelineEntry = {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: "tool_use";
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
input?: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "tool_result";
|
|
26
|
+
tool_use_id: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
output?: string;
|
|
29
|
+
is_error?: boolean;
|
|
30
|
+
};
|
|
14
31
|
export interface BuiltinContextMessage {
|
|
15
32
|
role: BuiltinContextRole;
|
|
16
33
|
content: string;
|
|
17
34
|
/** 可选结构化工具调用记录(assistant 消息专用) */
|
|
18
35
|
toolCalls?: BuiltinContextToolCall[];
|
|
36
|
+
/** 文本与工具事件的真实发生顺序,仅供 UI 回放;模型上下文仍使用 content。 */
|
|
37
|
+
timeline?: BuiltinContextTimelineEntry[];
|
|
19
38
|
}
|
|
20
39
|
export interface BuiltinContextState {
|
|
21
40
|
version: 1;
|
|
@@ -88,6 +107,8 @@ export declare function buildPersistedAssistantMessage(params: {
|
|
|
88
107
|
transcriptLines: readonly string[];
|
|
89
108
|
/** 结构化工具调用(按调用顺序);有则写入消息的 toolCalls 字段 */
|
|
90
109
|
toolCalls?: readonly BuiltinContextToolCall[];
|
|
110
|
+
/** 文本和工具事件的有序时间线;不参与模型输入。 */
|
|
111
|
+
timeline?: readonly BuiltinContextTimelineEntry[];
|
|
91
112
|
maxAssistantChars?: number;
|
|
92
113
|
maxTranscriptChars?: number;
|
|
93
114
|
}): BuiltinContextMessage;
|
package/dist/context.js
CHANGED
|
@@ -49,8 +49,42 @@ function normalizeMessage(value) {
|
|
|
49
49
|
const toolCalls = normalizeToolCalls(raw.toolCalls);
|
|
50
50
|
if (toolCalls.length > 0)
|
|
51
51
|
message.toolCalls = toolCalls;
|
|
52
|
+
const timeline = normalizeTimeline(raw.timeline);
|
|
53
|
+
if (timeline.length > 0)
|
|
54
|
+
message.timeline = timeline;
|
|
52
55
|
return message;
|
|
53
56
|
}
|
|
57
|
+
function normalizeTimeline(value) {
|
|
58
|
+
if (!Array.isArray(value))
|
|
59
|
+
return [];
|
|
60
|
+
const entries = [];
|
|
61
|
+
for (const item of value.slice(0, 1000)) {
|
|
62
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
63
|
+
continue;
|
|
64
|
+
const raw = item;
|
|
65
|
+
if (raw.type === "text" && typeof raw.text === "string") {
|
|
66
|
+
entries.push({ type: "text", text: raw.text });
|
|
67
|
+
}
|
|
68
|
+
else if (raw.type === "tool_use" && typeof raw.id === "string" && typeof raw.name === "string") {
|
|
69
|
+
entries.push({
|
|
70
|
+
type: "tool_use",
|
|
71
|
+
id: raw.id,
|
|
72
|
+
name: raw.name,
|
|
73
|
+
...(typeof raw.input === "string" ? { input: raw.input } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else if (raw.type === "tool_result" && typeof raw.tool_use_id === "string") {
|
|
77
|
+
entries.push({
|
|
78
|
+
type: "tool_result",
|
|
79
|
+
tool_use_id: raw.tool_use_id,
|
|
80
|
+
...(typeof raw.name === "string" ? { name: raw.name } : {}),
|
|
81
|
+
...(typeof raw.output === "string" ? { output: raw.output } : {}),
|
|
82
|
+
...(typeof raw.is_error === "boolean" ? { is_error: raw.is_error } : {}),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return entries;
|
|
87
|
+
}
|
|
54
88
|
function normalizeToolCalls(value) {
|
|
55
89
|
if (!Array.isArray(value))
|
|
56
90
|
return [];
|
|
@@ -62,6 +96,8 @@ function normalizeToolCalls(value) {
|
|
|
62
96
|
if (typeof raw.name !== "string" || raw.name.length === 0)
|
|
63
97
|
continue;
|
|
64
98
|
const call = { name: raw.name };
|
|
99
|
+
if (typeof raw.id === "string" && raw.id.length > 0)
|
|
100
|
+
call.id = raw.id;
|
|
65
101
|
if (typeof raw.input === "string")
|
|
66
102
|
call.input = raw.input;
|
|
67
103
|
if (typeof raw.output === "string")
|
|
@@ -215,8 +251,54 @@ export function buildPersistedAssistantMessage(params) {
|
|
|
215
251
|
const toolCalls = normalizeToolCalls(params.toolCalls);
|
|
216
252
|
if (toolCalls.length > 0)
|
|
217
253
|
message.toolCalls = toolCalls;
|
|
254
|
+
const timeline = truncatePersistedTimeline(normalizeTimeline(params.timeline));
|
|
255
|
+
if (timeline.length > 0)
|
|
256
|
+
message.timeline = timeline;
|
|
218
257
|
return message;
|
|
219
258
|
}
|
|
259
|
+
function truncatePersistedTimeline(entries) {
|
|
260
|
+
const maxTextChars = DEFAULT_PERSISTED_ASSISTANT_TEXT_CHARS;
|
|
261
|
+
const maxToolChars = DEFAULT_PERSISTED_TOOL_TRANSCRIPT_CHARS;
|
|
262
|
+
const totalText = entries.reduce((sum, entry) => sum + (entry.type === "text" ? entry.text.length : 0), 0);
|
|
263
|
+
const textPayloads = entries.filter((entry) => entry.type === "text" && entry.text.length > 0).length;
|
|
264
|
+
const totalTool = entries.reduce((sum, entry) => {
|
|
265
|
+
if (entry.type === "tool_use")
|
|
266
|
+
return sum + (entry.input?.length ?? 0);
|
|
267
|
+
if (entry.type === "tool_result")
|
|
268
|
+
return sum + (entry.output?.length ?? 0);
|
|
269
|
+
return sum;
|
|
270
|
+
}, 0);
|
|
271
|
+
const toolPayloads = entries.filter((entry) => (entry.type === "tool_use" && (entry.input?.length ?? 0) > 0)
|
|
272
|
+
|| (entry.type === "tool_result" && (entry.output?.length ?? 0) > 0)).length;
|
|
273
|
+
const payloadBudget = (length, total, count, maximum) => {
|
|
274
|
+
if (total <= maximum)
|
|
275
|
+
return length;
|
|
276
|
+
const sharedBudget = Math.max(0, maximum - count);
|
|
277
|
+
return length > 0 ? 1 + Math.floor(sharedBudget * length / Math.max(1, total)) : 0;
|
|
278
|
+
};
|
|
279
|
+
const truncatePayload = (value, budget, marker) => {
|
|
280
|
+
if (value.length <= budget)
|
|
281
|
+
return value;
|
|
282
|
+
if (budget <= marker.length + 2)
|
|
283
|
+
return value.slice(0, budget);
|
|
284
|
+
return truncateMiddle(value, budget, marker);
|
|
285
|
+
};
|
|
286
|
+
return entries.map((entry) => {
|
|
287
|
+
if (entry.type === "text") {
|
|
288
|
+
const budget = payloadBudget(entry.text.length, totalText, textPayloads, maxTextChars);
|
|
289
|
+
return { ...entry, text: truncatePayload(entry.text, budget, ASSISTANT_TRUNCATED_MARKER) };
|
|
290
|
+
}
|
|
291
|
+
if (entry.type === "tool_use" && entry.input !== undefined) {
|
|
292
|
+
const budget = payloadBudget(entry.input.length, totalTool, toolPayloads, maxToolChars);
|
|
293
|
+
return { ...entry, input: truncatePayload(entry.input, budget, TOOL_TRANSCRIPT_TRUNCATED_MARKER) };
|
|
294
|
+
}
|
|
295
|
+
if (entry.type === "tool_result" && entry.output !== undefined) {
|
|
296
|
+
const budget = payloadBudget(entry.output.length, totalTool, toolPayloads, maxToolChars);
|
|
297
|
+
return { ...entry, output: truncatePayload(entry.output, budget, TOOL_TRANSCRIPT_TRUNCATED_MARKER) };
|
|
298
|
+
}
|
|
299
|
+
return entry;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
220
302
|
function serializeMessagesForCompaction(messages) {
|
|
221
303
|
const sections = [];
|
|
222
304
|
let remaining = MAX_COMPACTION_SOURCE_CHARS;
|
package/dist/index.js
CHANGED
|
@@ -393,6 +393,8 @@ export class ChatSession {
|
|
|
393
393
|
// assistant 消息 toolCalls 字段;[Tool transcript] 文本视图仍按原格式生成。
|
|
394
394
|
const toolCallsById = new Map();
|
|
395
395
|
const toolCallOrder = [];
|
|
396
|
+
const timeline = [];
|
|
397
|
+
let toolContext = [];
|
|
396
398
|
try {
|
|
397
399
|
if (this.context.planCompaction()) {
|
|
398
400
|
yield { type: "status", phase: "compacting" };
|
|
@@ -460,9 +462,10 @@ export class ChatSession {
|
|
|
460
462
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
461
463
|
fullText = "";
|
|
462
464
|
safeAccumulated = "";
|
|
463
|
-
|
|
465
|
+
toolContext = [];
|
|
464
466
|
toolCallsById.clear();
|
|
465
467
|
toolCallOrder.length = 0;
|
|
468
|
+
timeline.length = 0;
|
|
466
469
|
let lastReasoningProgressAt;
|
|
467
470
|
const attemptMessages = attempt === 0
|
|
468
471
|
? modelMessages
|
|
@@ -493,6 +496,11 @@ export class ChatSession {
|
|
|
493
496
|
}
|
|
494
497
|
else if (part.type === "text-delta") {
|
|
495
498
|
fullText += part.text;
|
|
499
|
+
const previous = timeline[timeline.length - 1];
|
|
500
|
+
if (previous?.type === "text")
|
|
501
|
+
previous.text += part.text;
|
|
502
|
+
else
|
|
503
|
+
timeline.push({ type: "text", text: part.text });
|
|
496
504
|
// 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
|
|
497
505
|
// fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
|
|
498
506
|
const safeText = applyPrivacy(part.text);
|
|
@@ -500,9 +508,11 @@ export class ChatSession {
|
|
|
500
508
|
yield { type: "text", text: safeText, accumulated: safeAccumulated };
|
|
501
509
|
}
|
|
502
510
|
else if (part.type === "tool-call") {
|
|
503
|
-
|
|
504
|
-
|
|
511
|
+
const input = safeJson(part.input);
|
|
512
|
+
toolContext.push(`tool_call ${part.toolName}: ${input}`);
|
|
513
|
+
toolCallsById.set(part.toolCallId, { id: part.toolCallId, name: part.toolName, input });
|
|
505
514
|
toolCallOrder.push(part.toolCallId);
|
|
515
|
+
timeline.push({ type: "tool_use", id: part.toolCallId, name: part.toolName, input });
|
|
506
516
|
yield {
|
|
507
517
|
type: "tool_use",
|
|
508
518
|
id: part.toolCallId,
|
|
@@ -511,10 +521,12 @@ export class ChatSession {
|
|
|
511
521
|
};
|
|
512
522
|
}
|
|
513
523
|
else if (part.type === "tool-result") {
|
|
514
|
-
|
|
524
|
+
const output = truncateToolContext(safeJson(part.output));
|
|
525
|
+
toolContext.push(`tool_result ${part.toolName}: ${output}`);
|
|
515
526
|
const call = toolCallsById.get(part.toolCallId);
|
|
516
527
|
if (call)
|
|
517
|
-
call.output =
|
|
528
|
+
call.output = output;
|
|
529
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output });
|
|
518
530
|
yield {
|
|
519
531
|
type: "tool_result",
|
|
520
532
|
tool_use_id: part.toolCallId,
|
|
@@ -531,6 +543,7 @@ export class ChatSession {
|
|
|
531
543
|
call.output = message;
|
|
532
544
|
call.is_error = true;
|
|
533
545
|
}
|
|
546
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output: message, is_error: true });
|
|
534
547
|
yield {
|
|
535
548
|
type: "tool_result",
|
|
536
549
|
tool_use_id: part.toolCallId,
|
|
@@ -570,6 +583,7 @@ export class ChatSession {
|
|
|
570
583
|
fullText,
|
|
571
584
|
transcriptLines: toolContext,
|
|
572
585
|
toolCalls: collectedToolCalls,
|
|
586
|
+
timeline,
|
|
573
587
|
}));
|
|
574
588
|
yield { type: "done", text: safeAccumulated };
|
|
575
589
|
return;
|
|
@@ -581,9 +595,22 @@ export class ChatSession {
|
|
|
581
595
|
if (malformedProtocolOutput)
|
|
582
596
|
yield { type: "text_reset" };
|
|
583
597
|
if (err.name === "AbortError" || signal?.aborted) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
598
|
+
if ((fullText || toolCallOrder.length > 0) && !malformedProtocolOutput) {
|
|
599
|
+
const collectedToolCalls = toolCallOrder
|
|
600
|
+
.map((id) => toolCallsById.get(id))
|
|
601
|
+
.filter((call) => call !== undefined);
|
|
602
|
+
const interruptedTimeline = timeline.map((entry) => ({ ...entry }));
|
|
603
|
+
const previous = interruptedTimeline[interruptedTimeline.length - 1];
|
|
604
|
+
if (previous?.type === "text")
|
|
605
|
+
previous.text += "\n[interrupted]";
|
|
606
|
+
else
|
|
607
|
+
interruptedTimeline.push({ type: "text", text: "[interrupted]" });
|
|
608
|
+
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
609
|
+
fullText: `${fullText}\n[interrupted]`,
|
|
610
|
+
transcriptLines: toolContext,
|
|
611
|
+
toolCalls: collectedToolCalls,
|
|
612
|
+
timeline: interruptedTimeline,
|
|
613
|
+
}));
|
|
587
614
|
}
|
|
588
615
|
yield { type: "done", text: safeAccumulated };
|
|
589
616
|
return;
|
package/dist/web-page.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const DEEPCCC_WEB_PAGE = "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>DeepCCC</title>\n<style>\n:root{color-scheme:light;--bg:#f5f7fb;--panel:#fff;--panel2:#fff;--soft:#f8f9fc;--line:#e3e7ef;--text:#182033;--muted:#737d91;--accent:#6758e8;--accent-soft:#eeeaff;--accent2:#23825d;--danger:#c2415a;--warning:#b7791f;--shadow:0 8px 28px rgba(26,35,58,.08)}\n*{box-sizing:border-box}html,body{height:100%}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Microsoft YaHei\",sans-serif;overflow:hidden}button,input,select,textarea{font:inherit}button{cursor:pointer}.app{height:100%;display:grid;grid-template-columns:286px minmax(0,1fr)}\n.sidebar{min-width:0;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column}.brand{height:70px;display:flex;align-items:center;gap:12px;padding:0 18px;border-bottom:1px solid var(--line)}.mark{width:36px;height:36px;border-radius:12px;display:grid;place-items:center;background:var(--accent);color:#fff;box-shadow:0 9px 25px rgba(103,88,232,.25);font-weight:900}.brand-copy strong{display:block;font-size:16px;letter-spacing:.01em}.brand-copy span{font-size:10px;color:var(--muted);letter-spacing:.13em}.sidebar-actions{display:flex;gap:8px;padding:14px}.primary,.ghost,.icon{border:1px solid var(--line);border-radius:10px;color:var(--text);background:var(--panel);font-weight:700}.primary{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 8px 22px rgba(103,88,232,.22)}.sidebar-actions .primary{flex:1;padding:10px}.icon{width:40px;padding:0;font-size:17px}.ghost{padding:9px 12px}.primary:hover,.ghost:hover,.icon:hover{filter:brightness(1.03)}\n.session-list{flex:1;overflow:auto;padding:0 10px 14px}.session-empty{padding:38px 18px;color:var(--muted);text-align:center}.session-item{width:100%;display:block;padding:12px;border:1px solid transparent;border-radius:12px;background:transparent;color:var(--text);text-align:left;margin:3px 0}.session-item:hover{background:var(--panel2)}.session-item.active{background:linear-gradient(135deg,rgba(124,105,241,.2),rgba(93,74,207,.08));border-color:rgba(139,124,246,.35)}.session-title{display:flex;align-items:center;gap:8px;font-weight:750}.status-dot{width:7px;height:7px;border-radius:50%;background:#626a78}.status-dot.running{background:var(--accent2);box-shadow:0 0 0 4px rgba(94,228,177,.1)}.session-meta{margin-top:5px;color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sidebar-foot{padding:12px 16px;border-top:1px solid var(--line);color:var(--muted);font-size:10px;display:flex;justify-content:space-between}\n.main{min-width:0;height:100%;display:flex;flex-direction:column}.topbar{height:70px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 22px;border-bottom:1px solid var(--line);background:rgba(12,14,19,.7);backdrop-filter:blur(18px)}.session-heading{min-width:0}.session-heading h1{font-size:16px;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-heading p{font-size:11px;color:var(--muted);margin:3px 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.top-actions{display:flex;gap:8px}.top-actions button{height:38px}.content{flex:1;min-height:0;overflow-y:auto;scroll-behavior:smooth}.welcome{height:100%;display:grid;place-items:center;padding:30px}.welcome-card{max-width:650px;text-align:center}.welcome-mark{width:72px;height:72px;margin:0 auto 20px;border-radius:24px;display:grid;place-items:center;background:linear-gradient(135deg,#7764eb,#b45fd7);font-size:29px;box-shadow:0 20px 50px rgba(119,100,235,.28)}.welcome h2{font-size:28px;margin:0 0 10px;letter-spacing:-.02em}.welcome p{color:var(--muted);margin:0 auto;max-width:480px}.hint-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:28px}.hint{padding:14px;border:1px solid var(--line);border-radius:13px;background:rgba(24,28,37,.65);font-size:12px;color:var(--muted)}\n.thread{width:min(900px,calc(100% - 36px));margin:0 auto;padding:28px 0 150px}.empty-thread{margin:80px auto 0;max-width:520px;padding:28px;border:1px dashed #353c4a;border-radius:18px;background:rgba(20,23,31,.55);text-align:center}.empty-thread strong{display:block;font-size:18px;margin-bottom:7px}.empty-thread span{color:var(--muted);font-size:12px}.message{display:grid;grid-template-columns:30px minmax(0,1fr);gap:12px;margin:0 0 22px}.avatar{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;background:var(--soft);font-size:11px;font-weight:900}.message.user .avatar{background:#304058}.message.assistant .avatar{background:linear-gradient(135deg,#7363e8,#9a61dc)}.bubble{min-width:0;padding:2px 0;color:#dfe3eb}.bubble .role{font-size:11px;color:var(--muted);font-weight:800;margin-bottom:7px}.markdown{font-size:14px;line-height:1.75;overflow-wrap:anywhere}.markdown p{margin:.35em 0}.markdown pre{padding:14px;border:1px solid var(--line);border-radius:11px;background:#0a0c11;overflow:auto;font:12px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace}.markdown code{padding:2px 5px;border-radius:5px;background:var(--soft);font:12px ui-monospace,SFMono-Regular,Consolas,monospace}.markdown pre code{padding:0;background:transparent}.tool-card{margin:8px 0;border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:hidden}.tool-card summary{padding:9px 11px;color:#bbc2d0;font-size:12px;font-weight:700}.tool-card pre{margin:0;border:0;border-top:1px solid var(--line);border-radius:0;max-height:260px}.run-state{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px;margin:8px 0 20px}.pulse{width:8px;height:8px;border-radius:50%;background:var(--accent2);animation:pulse 1.4s infinite}@keyframes pulse{50%{opacity:.35;transform:scale(.72)}}.workspace-warning{width:min(900px,calc(100% - 36px));margin:14px auto 0;padding:9px 12px;border:1px solid rgba(242,184,75,.3);background:rgba(242,184,75,.08);border-radius:10px;color:#e8c879;font-size:11px}\n.composer-shell{position:absolute;left:286px;right:0;bottom:0;padding:0 20px 18px;background:linear-gradient(transparent,var(--bg) 32%)}.composer{width:min(900px,100%);margin:auto;border:1px solid #343b49;border-radius:17px;background:rgba(25,29,38,.96);box-shadow:var(--shadow);overflow:hidden}.composer textarea{width:100%;min-height:68px;max-height:180px;resize:none;border:0;outline:0;padding:15px 16px 8px;background:transparent;color:var(--text)}.composer textarea::placeholder{color:#737b8b}.composer-bar{display:flex;align-items:center;gap:8px;padding:7px 9px}.composer-bar input,.composer-bar select{min-width:0;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--muted);padding:7px 9px;font-size:11px}.composer-bar input{width:180px}.composer-bar select{width:104px}.send{margin-left:auto;width:36px;height:36px;border:0;border-radius:10px;background:var(--accent);color:#fff;font-size:17px}.stop{margin-left:auto;height:36px;border:1px solid rgba(255,111,137,.4);border-radius:10px;background:rgba(255,111,137,.12);color:#ff91a5;padding:0 13px;font-weight:700}.composer-disabled{opacity:.5;pointer-events:none}\n.attachment-drafts{display:flex;gap:8px;padding:0 12px;overflow-x:auto}.attachment-draft{position:relative;flex:none;width:74px}.attachment-draft img{width:74px;height:54px;object-fit:cover;border:1px solid var(--line);border-radius:9px;background:var(--soft)}.attachment-draft button{position:absolute;right:-5px;top:-6px;width:20px;height:20px;padding:0;border:1px solid var(--line);border-radius:50%;background:#fff;color:var(--danger);font-weight:900}.attach{width:36px;height:36px;border:1px solid var(--line);border-radius:10px;background:var(--soft);color:var(--muted);font-size:17px}.image-gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:9px;margin-top:10px;max-width:650px}.image-card{display:block;border:1px solid var(--line);border-radius:11px;background:var(--soft);overflow:hidden;color:var(--text);text-decoration:none}.image-card img{display:block;width:100%;height:150px;object-fit:cover;background:#eef1f6}.image-card span{display:block;padding:7px 9px;color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.composer.dragging{outline:2px solid var(--accent);outline-offset:2px}\n.modal-backdrop{position:fixed;inset:0;z-index:30;display:grid;place-items:center;padding:20px;background:rgba(4,5,8,.66);backdrop-filter:blur(7px)}.modal-backdrop.hidden{display:none}.modal{width:min(560px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid #353c4a;border-radius:18px;background:#171a22;box-shadow:0 30px 100px rgba(0,0,0,.55);padding:22px}.modal h2{margin:0 0 5px;font-size:20px}.modal>.subtitle{margin:0 0 20px;color:var(--muted);font-size:12px}.field{display:grid;gap:7px;margin:13px 0}.field label{font-size:11px;color:#b7becb;font-weight:800}.field input,.field select{width:100%;border:1px solid var(--line);border-radius:10px;background:#10131a;color:var(--text);padding:10px 11px;outline:none}.field input:focus,.field select:focus{border-color:var(--accent)}.row{display:grid;grid-template-columns:1fr 1fr;gap:10px}.check{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.danger{border:1px solid rgba(255,111,137,.35);background:rgba(255,111,137,.1);color:#ff849b;border-radius:10px;padding:9px 12px;font-weight:700}.approval{width:min(620px,100%)}.approval-badge{display:inline-flex;padding:5px 8px;border-radius:999px;background:rgba(242,184,75,.13);color:#f1c565;font-size:10px;font-weight:900}.approval pre{white-space:pre-wrap;overflow-wrap:anywhere;padding:13px;border:1px solid var(--line);border-radius:10px;background:#0d0f15;color:#dce0e9}.approval-actions{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.toast{position:fixed;right:20px;top:20px;z-index:50;max-width:420px;padding:11px 14px;border-radius:10px;background:#283143;border:1px solid #3b465b;box-shadow:var(--shadow)}.toast.error{background:#602536;border-color:#913a52}.hidden{display:none!important}\n.topbar{background:rgba(255,255,255,.86)}.welcome-mark{background:var(--accent);color:#fff}.hint,.empty-thread{background:var(--panel);border-color:var(--line)}.session-item.active{background:var(--accent-soft);border-color:#d8d1ff}.status-dot{background:#a3aabd}.status-dot.running{background:var(--accent2);box-shadow:0 0 0 4px rgba(35,130,93,.1)}.message.user .avatar{background:#e8edf6}.message.assistant .avatar{background:var(--accent);color:#fff}.bubble{color:var(--text)}.markdown pre,.tool-card pre{background:var(--soft);color:var(--text)}.tool-card summary{color:var(--muted)}.composer{border-color:var(--line);background:rgba(255,255,255,.98)}.composer textarea::placeholder{color:#9aa3b4}.composer-bar input,.composer-bar select{background:var(--soft)}.modal-backdrop{background:rgba(24,32,51,.36)}.modal{border-color:var(--line);background:var(--panel);box-shadow:0 28px 90px rgba(35,43,65,.24)}.field label{color:var(--text)}.field input,.field select{background:var(--soft);color:var(--text)}.danger{border-color:#f3c4ce;background:#fff1f3;color:var(--danger)}.toast{background:#253047;color:#fff}.toast.error{background:#9f2942}.approval-card{margin:16px 0;padding:14px;border:1px solid #ead49b;border-radius:13px;background:#fffaf0;box-shadow:0 5px 18px rgba(94,70,15,.06)}.approval-card.resolved{border-color:var(--line);background:var(--soft)}.approval-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}.approval-head strong{font-size:12px}.approval-status{padding:3px 7px;border-radius:999px;background:#fff0c2;color:#8a6110;font-size:10px;font-weight:800}.approval-card.resolved .approval-status{background:#e5f5ee;color:var(--accent2)}.approval-detail{color:var(--muted);font-size:11px;margin-bottom:8px}.approval-command{margin:0;padding:10px;border:1px solid var(--line);border-radius:9px;background:#fff;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace}.approval-inline-actions{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}.approval-inline-actions button{padding:7px 10px;font-size:11px}\n.sidebar{height:100%;overflow:hidden}.main{min-height:0!important;overflow:hidden}\n@media(max-width:760px){.app{grid-template-columns:78px minmax(0,1fr)}.brand{padding:0;justify-content:center}.brand-copy,.sidebar-actions .primary,.session-item>div:not(.session-title),.session-title span:last-child,.sidebar-foot{display:none}.sidebar-actions{justify-content:center;padding:12px 8px}.session-item{padding:11px;display:grid;place-items:center}.composer-shell{left:78px}.hint-grid{grid-template-columns:1fr}.topbar{padding:0 14px}.thread{width:calc(100% - 24px)}.row{grid-template-columns:1fr}.approval-inline-actions{display:grid;grid-template-columns:1fr 1fr}}\n</style>\n</head>\n<body>\n<div class=\"app\">\n <aside class=\"sidebar\">\n <div class=\"brand\"><div class=\"mark\">D</div><div class=\"brand-copy\"><strong>DeepCCC</strong><span>LOCAL AGENT</span></div></div>\n <div class=\"sidebar-actions\"><button id=\"new-session\" class=\"primary\">\uFF0B \u65B0\u5EFA\u4F1A\u8BDD</button><button id=\"open-settings\" class=\"icon\" title=\"API \u8BBE\u7F6E\">\u2699</button></div>\n <div id=\"session-list\" class=\"session-list\"></div>\n <div class=\"sidebar-foot\"><span>127.0.0.1</span><span id=\"server-port\">28080</span></div>\n </aside>\n <main class=\"main\">\n <header class=\"topbar\"><div class=\"session-heading\"><h1 id=\"session-title\">DeepCCC</h1><p id=\"session-cwd\">\u9009\u62E9\u6216\u521B\u5EFA\u4E00\u4E2A\u4F1A\u8BDD</p></div><div class=\"top-actions\"><button id=\"rename-session\" class=\"ghost hidden\">\u91CD\u547D\u540D</button><button id=\"delete-session\" class=\"danger hidden\">\u5220\u9664</button></div></header>\n <div id=\"warning\" class=\"workspace-warning hidden\"></div>\n <section id=\"content\" class=\"content\"><div class=\"welcome\"><div class=\"welcome-card\"><div class=\"welcome-mark\">\u2726</div><h2>\u628A\u5DE5\u4F5C\u4EA4\u7ED9 DeepCCC</h2><p>\u5728\u6D4F\u89C8\u5668\u91CC\u7BA1\u7406\u591A\u4E2A\u672C\u5730 Coding Agent \u4F1A\u8BDD\uFF0C\u5207\u6362\u6A21\u578B\u4E0E\u63A8\u7406\u5F3A\u5EA6\uFF0C\u5E76\u5728\u4F1A\u8BDD\u5185\u5BA1\u6279\u64CD\u4F5C\u3002</p><div class=\"hint-grid\"><div class=\"hint\">\u591A\u4F1A\u8BDD\u5E76\u884C\u8FD0\u884C</div><div class=\"hint\">\u672C\u5730\u6587\u4EF6\u4E0E\u5386\u53F2</div><div class=\"hint\">API \u81EA\u7531\u914D\u7F6E</div></div></div></div></section>\n </main>\n</div>\n<div id=\"composer-shell\" class=\"composer-shell hidden\"><div id=\"composer\" class=\"composer\"><textarea id=\"prompt\" placeholder=\"\u63CF\u8FF0\u4F60\u60F3\u5B8C\u6210\u7684\u4EFB\u52A1\u2026\u53EF\u7C98\u8D34\u6216\u62D6\u5165\u56FE\u7247\"></textarea><div id=\"attachment-drafts\" class=\"attachment-drafts\"></div><div class=\"composer-bar\"><input id=\"attachment-input\" type=\"file\" accept=\"image/png,image/jpeg,image/webp\" multiple hidden><button id=\"attach-image\" class=\"attach\" type=\"button\" title=\"\u6DFB\u52A0\u56FE\u7247\">\uFF0B</button><input id=\"session-model\" list=\"recent-models\" aria-label=\"\u6A21\u578B\"><input id=\"session-sub-model\" list=\"recent-models\" aria-label=\"\u5B50\u6A21\u578B\" placeholder=\"\u9ED8\u8BA4\u5B50\u6A21\u578B\"><datalist id=\"recent-models\"></datalist><select id=\"session-effort\" aria-label=\"\u63A8\u7406\u5F3A\u5EA6\"><option value=\"\">\u9ED8\u8BA4 effort</option><option>none</option><option>minimal</option><option>low</option><option>medium</option><option>high</option><option>xhigh</option><option>max</option></select><button id=\"send\" class=\"send\" title=\"\u53D1\u9001\">\u2191</button><button id=\"stop\" class=\"stop hidden\">\u25A0 \u505C\u6B62</button></div></div></div>\n\n<div id=\"new-modal\" class=\"modal-backdrop hidden\"><form id=\"new-form\" class=\"modal\"><h2>\u65B0\u5EFA Agent \u4F1A\u8BDD</h2><p class=\"subtitle\">\u6BCF\u4E2A\u4F1A\u8BDD\u62E5\u6709\u72EC\u7ACB\u4E0A\u4E0B\u6587\uFF1B\u540C\u76EE\u5F55\u4F1A\u8BDD\u53EF\u4EE5\u5E76\u53D1\uFF0C\u4F46\u6587\u4EF6\u4FEE\u6539\u53EF\u80FD\u51B2\u7A81\u3002</p><div class=\"field\"><label>\u5DE5\u4F5C\u76EE\u5F55</label><input id=\"new-cwd\" required></div><div class=\"field\"><label>\u4F1A\u8BDD\u540D\u79F0\uFF08\u53EF\u9009\uFF09</label><input id=\"new-title\" placeholder=\"\u9ED8\u8BA4\u4F7F\u7528\u76EE\u5F55\u540D\"></div><div class=\"row\"><div class=\"field\"><label>\u6A21\u578B</label><input id=\"new-model\" list=\"recent-models\"></div><div class=\"field\"><label>\u5B50\u6A21\u578B\uFF08\u53EF\u9009\uFF09</label><input id=\"new-sub-model\" list=\"recent-models\"></div></div><div class=\"field\"><label>Effort</label><select id=\"new-effort\"><option value=\"\">\u9ED8\u8BA4</option><option>none</option><option>minimal</option><option>low</option><option>medium</option><option>high</option><option>xhigh</option><option>max</option></select></div><div class=\"modal-actions\"><button type=\"button\" data-close=\"new-modal\" class=\"ghost\">\u53D6\u6D88</button><button class=\"primary\" type=\"submit\">\u521B\u5EFA\u4F1A\u8BDD</button></div></form></div>\n\n<div id=\"settings-modal\" class=\"modal-backdrop hidden\"><form id=\"settings-form\" class=\"modal\"><h2>API \u4E0E Web \u8BBE\u7F6E</h2><p class=\"subtitle\">\u5355\u4E00 API \u914D\u7F6E\u4F5C\u4E3A\u6240\u6709\u65B0\u4F1A\u8BDD\u7684\u9ED8\u8BA4\u503C\uFF1B\u4F1A\u8BDD\u53EF\u5355\u72EC\u8986\u76D6\u6A21\u578B\u548C effort\u3002</p><div class=\"row\"><div class=\"field\"><label>API \u534F\u8BAE</label><select id=\"cfg-provider\"><option value=\"openai\">OpenAI-compatible</option><option value=\"anthropic\">Anthropic Messages</option></select></div><div class=\"field\"><label>API Key</label><input id=\"cfg-api-key\" type=\"password\" placeholder=\"\u7559\u7A7A\u4FDD\u7559\u5F53\u524D Key\"></div></div><div class=\"field\"><label>Base URL</label><input id=\"cfg-base-url\" required></div><div class=\"row\"><div class=\"field\"><label>\u9ED8\u8BA4\u6A21\u578B</label><input id=\"cfg-model\" required></div><div class=\"field\"><label>\u5B50\u6A21\u578B\uFF08\u53EF\u9009\uFF09</label><input id=\"cfg-sub-model\"></div></div><div class=\"row\"><div class=\"field\"><label>\u9ED8\u8BA4 Effort</label><select id=\"cfg-effort\"><option value=\"\">\u4E0D\u4F20</option><option>none</option><option>minimal</option><option>low</option><option>medium</option><option>high</option><option>xhigh</option><option>max</option></select></div><div class=\"field\"><label>Context Window</label><input id=\"cfg-context\" type=\"number\" min=\"1\"></div></div><div class=\"row\"><div class=\"field\"><label>Web \u7AEF\u53E3</label><input id=\"cfg-port\" type=\"number\" min=\"1\" max=\"65535\"></div><div class=\"field\"><label>Max Output Tokens\uFF08\u53EF\u9009\uFF09</label><input id=\"cfg-max-output\" type=\"number\" min=\"1\"></div></div><label class=\"check\"><input id=\"cfg-streaming\" type=\"checkbox\">\u542F\u7528\u6D41\u5F0F\u8BF7\u6C42</label><label class=\"check\"><input id=\"cfg-open\" type=\"checkbox\">\u8FD0\u884C deepccc web \u65F6\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668</label><div id=\"key-state\" class=\"subtitle\"></div><div class=\"modal-actions\"><button type=\"button\" data-close=\"settings-modal\" class=\"ghost\">\u53D6\u6D88</button><button class=\"primary\" type=\"submit\">\u4FDD\u5B58\u8BBE\u7F6E</button></div></form></div>\n\n<div id=\"rename-modal\" class=\"modal-backdrop hidden\"><form id=\"rename-form\" class=\"modal\"><h2>\u91CD\u547D\u540D\u4F1A\u8BDD</h2><div class=\"field\"><label>\u540D\u79F0</label><input id=\"rename-title\" required maxlength=\"120\"></div><div class=\"modal-actions\"><button type=\"button\" data-close=\"rename-modal\" class=\"ghost\">\u53D6\u6D88</button><button class=\"primary\">\u4FDD\u5B58</button></div></form></div>\n\n<div id=\"toast\" class=\"toast hidden\"></div>\n<script>\n(function(){\n var state={sessions:[],selectedId:null,detail:null,config:null,eventSource:null,globalEventSource:null,reloadTimer:null,sessionsReloadTimer:null,detailRequestToken:0,configDrafts:{},promptDrafts:{},attachmentDrafts:{},uploading:false};\n var $=function(id){return document.getElementById(id);};\n async function api(path,options){var response=await fetch(path,Object.assign({headers:{'content-type':'application/json; charset=utf-8'}},options||{}));var data=await response.json().catch(function(){return {error:'\u54CD\u5E94\u4E0D\u662F\u6709\u6548 JSON'};});if(!response.ok)throw new Error(data.error||('HTTP '+response.status));return data;}\n function toast(message,error){var el=$('toast');el.textContent=message;el.className='toast'+(error?' error':'');clearTimeout(toast.timer);toast.timer=setTimeout(function(){el.className='toast hidden';},3500);}\n function openModal(id){$(id).classList.remove('hidden');}function closeModal(id){$(id).classList.add('hidden');}\n function escapeHtml(value){return String(value||'').replace(/[&<>\"']/g,function(ch){return {'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[ch];});}\n function markdown(value){var text=escapeHtml(value);var blocks=[];var tick=String.fromCharCode(96);var fence=tick+tick+tick;var pattern=new RegExp(fence+'([a-z0-9_-]*)\\\\n([\\\\s\\\\S]*?)'+fence,'gi');text=text.replace(pattern,function(_,lang,code){blocks.push('<pre><code data-lang=\"'+lang+'\">'+code+'</code></pre>');return '@@BLOCK'+(blocks.length-1)+'@@';});text=text.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>').replace(new RegExp(tick+'([^'+tick+']+)'+tick,'g'),'<code>$1</code>').replace(/\\n/g,'<br>');text=text.replace(/@@BLOCK(\\d+)@@/g,function(_,index){return blocks[Number(index)];});return text;}\n function statusText(status){return status==='running'?'\u6267\u884C\u4E2D':'\u7A7A\u95F2';}\n function renderSessions(){var root=$('session-list');root.innerHTML='';if(!state.sessions.length){root.innerHTML='<div class=\"session-empty\">\u8FD8\u6CA1\u6709\u4F1A\u8BDD<br>\u70B9\u51FB\u4E0A\u65B9\u6309\u94AE\u5F00\u59CB</div>';return;}state.sessions.forEach(function(session){var button=document.createElement('button');button.className='session-item'+(session.sessionId===state.selectedId?' active':'');button.innerHTML='<div class=\"session-title\"><i class=\"status-dot '+session.status+'\"></i><span>'+escapeHtml(session.title)+'</span></div><div class=\"session-meta\">'+escapeHtml(session.model||'\u9ED8\u8BA4\u6A21\u578B')+' \u00B7 '+escapeHtml(session.effort||'\u9ED8\u8BA4 effort')+'</div><div class=\"session-meta\">'+escapeHtml(session.cwd)+'</div>';button.addEventListener('click',function(){selectSession(session.sessionId).catch(function(error){toast(error.message,true);});});root.appendChild(button);});}\n function decodeAttachmentMessage(content){var start=content.indexOf('<deepccc-attachments>');var end=content.indexOf('</deepccc-attachments>',start);if(start<0||end<0)return{text:content,attachments:[]};var attachments=[];try{attachments=JSON.parse(content.slice(start+21,end).trim());if(!Array.isArray(attachments))attachments=[];}catch(error){}return{text:content.slice(0,start).trim(),attachments:attachments};}\n function attachmentUrl(sessionId,id){return '/api/sessions/'+encodeURIComponent(sessionId)+'/attachments/'+encodeURIComponent(id);}\n function attachmentGallery(items,sessionId){if(!items||!items.length)return'';return '<div class=\"image-gallery\">'+items.map(function(item){var url=attachmentUrl(sessionId,item.attachmentId);return '<a class=\"image-card\" href=\"'+escapeHtml(url)+'\" target=\"_blank\"><img src=\"'+escapeHtml(url)+'\" alt=\"'+escapeHtml(item.originalName)+'\"><span>'+escapeHtml(item.originalName)+'</span></a>';}).join('')+'</div>';}\n function artifactFromOutput(output){try{var value=typeof output==='string'?JSON.parse(output):output;return value&&typeof value.path==='string'&&String(value.mimeType||'').indexOf('image/')===0?value:null;}catch(error){return null;}}\n function artifactGallery(toolCalls,sessionId){var items=(toolCalls||[]).filter(function(call){return call.name==='present_file';}).map(function(call){return artifactFromOutput(call.output);}).filter(Boolean);if(!items.length)return'';return '<div class=\"image-gallery\">'+items.map(function(item){var url='/api/sessions/'+encodeURIComponent(sessionId)+'/artifact?path='+encodeURIComponent(item.path);return '<a class=\"image-card\" href=\"'+escapeHtml(url)+'\" target=\"_blank\"><img src=\"'+escapeHtml(url)+'\" alt=\"'+escapeHtml(item.caption||item.name)+'\"><span>'+escapeHtml(item.caption||item.name)+'</span></a>';}).join('')+'</div>';}\n function withoutToolTranscript(content){var marker='\\n\\n[\u5DE5\u5177\u8BB0\u5F55]\\n';var index=content.indexOf(marker);return index>=0?content.slice(0,index):content;}\n function messageHtml(role,content,toolCalls){var decoded=role==='user'?decodeAttachmentMessage(content):{text:withoutToolTranscript(content),attachments:[]};return '<article class=\"message '+role+'\"><div class=\"avatar\">'+(role==='user'?'\u4F60':'D')+'</div><div class=\"bubble\"><div class=\"role\">'+(role==='user'?'YOU':'DEEPCCC')+'</div><div class=\"markdown\">'+markdown(decoded.text)+'</div>'+attachmentGallery(decoded.attachments,state.detail.sessionId)+artifactGallery(toolCalls,state.detail.sessionId)+'</div></article>';}\n function toolHtml(event){var data=event.data||{};var item=data.type==='agent'?data.data:data;var artifact=item.type==='tool_result'&&item.name==='present_file'?artifactFromOutput(item.content):null;if(artifact)return artifactGallery([{name:'present_file',output:JSON.stringify(artifact)}],state.detail.sessionId);return '<details class=\"tool-card\"><summary>'+escapeHtml(item.type==='tool_use'?'\u8C03\u7528\u5DE5\u5177 \u00B7 '+(item.name||'tool'):'\u5DE5\u5177\u7ED3\u679C \u00B7 '+(item.name||item.tool_use_id||'tool'))+'</summary><pre>'+escapeHtml(JSON.stringify(item.type==='tool_use'?item.input:item.content,null,2))+'</pre></details>';}\n function approvalAnswerLabel(answer){var labels={allow:'\u5DF2\u5141\u8BB8\u4E00\u6B21','allow-session':'\u672C\u4F1A\u8BDD\u5DF2\u5141\u8BB8','allow-always':'\u5DF2\u6C38\u4E45\u5141\u8BB8',deny:'\u5DF2\u62D2\u7EDD'};return labels[answer]||'\u7B49\u5F85\u51B3\u5B9A';}\n function approvalHtml(approval){var resolved=approval.status==='resolved';var html='<section class=\"approval-card'+(resolved?' resolved':'')+'\"><div class=\"approval-head\"><strong>\u26A0 \u64CD\u4F5C\u5BA1\u6279</strong><span class=\"approval-status\">'+escapeHtml(resolved?approvalAnswerLabel(approval.answer):'\u7B49\u5F85\u6279\u51C6')+'</span></div><div class=\"approval-detail\">'+escapeHtml(approval.detail||('\u5DE5\u5177\uFF1A'+approval.tool))+'</div><pre class=\"approval-command\">'+escapeHtml(approval.action)+'</pre>';if(!resolved)html+='<div class=\"approval-inline-actions\"><button class=\"danger\" data-approval-answer=\"deny\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u62D2\u7EDD</button><button class=\"ghost\" data-approval-answer=\"allow\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u5141\u8BB8\u4E00\u6B21</button><button class=\"ghost\" data-approval-answer=\"allow-session\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u672C\u4F1A\u8BDD\u5141\u8BB8</button><button class=\"primary\" data-approval-answer=\"allow-always\" data-approval-id=\"'+escapeHtml(approval.approvalId)+'\">\u6C38\u4E45\u5141\u8BB8</button></div>';return html+'</section>';}\n function sessionDraft(detail){return state.configDrafts[detail.sessionId]||{model:detail.model||'',subModel:detail.subModel||'',effort:detail.effort||''};}\n function rememberDraft(){if(!state.selectedId)return;state.configDrafts[state.selectedId]={model:$('session-model').value,subModel:$('session-sub-model').value,effort:$('session-effort').value};}\n function rememberPromptDraft(){if(!state.selectedId)return;state.promptDrafts[state.selectedId]=$('prompt').value;}\n function currentAttachments(){return state.attachmentDrafts[state.selectedId]||[];}\n function renderAttachmentDrafts(){var root=$('attachment-drafts');if(!root)return;root.innerHTML=currentAttachments().map(function(item){return '<div class=\"attachment-draft\"><img src=\"'+escapeHtml(attachmentUrl(state.selectedId,item.attachmentId))+'\" alt=\"'+escapeHtml(item.originalName)+'\"><button type=\"button\" data-remove-attachment=\"'+escapeHtml(item.attachmentId)+'\">\u00D7</button></div>';}).join('');}\n function clearSelection(message){if(state.eventSource)state.eventSource.close();state.eventSource=null;state.detailRequestToken++;state.selectedId=null;state.detail=null;$('composer-shell').classList.add('hidden');$('rename-session').classList.add('hidden');$('delete-session').classList.add('hidden');$('warning').classList.add('hidden');$('session-title').textContent='DeepCCC';$('session-cwd').textContent='\u9009\u62E9\u6216\u521B\u5EFA\u4E00\u4E2A\u4F1A\u8BDD';$('content').innerHTML='<div class=\"welcome\"><div class=\"welcome-card\"><div class=\"welcome-mark\">\u2726</div><h2>'+escapeHtml(message||'\u9009\u62E9\u4E00\u4E2A\u4F1A\u8BDD')+'</h2><p>\u4ECE\u5DE6\u4FA7\u9009\u62E9\u5176\u4ED6\u4F1A\u8BDD\u6216\u521B\u5EFA\u4E00\u4E2A\u65B0\u7684\u4EFB\u52A1\u3002</p></div></div>';}\n function renderDetail(){var detail=state.detail;if(!detail)return;var draft=sessionDraft(detail);$('session-title').textContent=detail.title;$('session-cwd').textContent=detail.cwd+' \u00B7 '+statusText(detail.status);$('rename-session').classList.remove('hidden');$('delete-session').classList.remove('hidden');$('composer-shell').classList.remove('hidden');$('prompt').value=state.promptDrafts[detail.sessionId]||'';$('session-model').value=draft.model;$('session-sub-model').value=draft.subModel;$('session-effort').value=draft.effort;renderAttachmentDrafts();var html='<div class=\"thread\">';if(!(detail.messages||[]).length&&detail.status!=='running')html+='<div class=\"empty-thread\"><strong>\u51C6\u5907\u5F00\u59CB\u8FD9\u4E2A\u4F1A\u8BDD</strong><span>\u5728\u4E0B\u65B9\u8F93\u5165\u4EFB\u52A1\uFF0C\u53EF\u9644\u52A0\u622A\u56FE\u3002DeepCCC \u4F1A\u8BFB\u53D6\u5F53\u524D\u9879\u76EE\u6307\u4EE4\u3001\u8C03\u7528\u5DE5\u5177\u5E76\u6301\u7EED\u4FDD\u5B58\u4E0A\u4E0B\u6587\u3002</span></div>';(detail.messages||[]).forEach(function(message){html+=messageHtml(message.role,message.content,message.toolCalls);});if(detail.status==='running'){var live=(detail.events||[]).slice().reverse().find(function(event){return event.type==='agent'&&event.data&&event.data.type==='text';});var current=(detail.events||[]).filter(function(event){return event.type==='agent'&&event.data&&(event.data.type==='tool_use'||event.data.type==='tool_result');});current.forEach(function(event){html+=toolHtml(event);});if(live)html+=messageHtml('assistant',live.data.accumulated||live.data.text||'',[]);}var approvals=(detail.approvals||[]).slice();if(detail.pendingApproval&&!approvals.some(function(item){return item.approvalId===detail.pendingApproval.approvalId;}))approvals.push(Object.assign({status:'pending'},detail.pendingApproval));approvals.forEach(function(approval){html+=approvalHtml(approval);});if(detail.status==='running')html+='<div class=\"run-state\"><i class=\"pulse\"></i>Agent \u6B63\u5728\u5DE5\u4F5C</div>';html+='</div>';$('content').innerHTML=html;$('send').classList.toggle('hidden',detail.status==='running');$('stop').classList.toggle('hidden',detail.status!=='running');$('prompt').disabled=detail.status==='running';$('session-model').disabled=detail.status==='running';$('session-sub-model').disabled=detail.status==='running';$('session-effort').disabled=detail.status==='running';$('attach-image').disabled=detail.status==='running'||state.uploading;renderWarning();requestAnimationFrame(function(){$('content').scrollTop=$('content').scrollHeight;});}\n function cwdKey(value){var raw=String(value||'');var normalized=raw.replace(/\\\\/g,'/').replace(new RegExp('/+$'),'');var windowsStyle=new RegExp('^[a-z]:/','i').test(normalized)||raw.indexOf(String.fromCharCode(92))>=0;return windowsStyle?normalized.toLowerCase():normalized;}\n function renderWarning(){if(!state.detail)return;var selectedCwd=cwdKey(state.detail.cwd);var conflicts=state.sessions.filter(function(item){return item.sessionId!==state.detail.sessionId&&item.status==='running'&&cwdKey(item.cwd)===selectedCwd;});var el=$('warning');if(conflicts.length){el.textContent='\u26A0 \u5F53\u524D\u76EE\u5F55\u8FD8\u6709 '+conflicts.length+' \u4E2A Agent \u4F1A\u8BDD\u6B63\u5728\u8FD0\u884C\u3002\u591A\u4E2A\u4F1A\u8BDD\u76F4\u63A5\u4FEE\u6539\u540C\u4E00\u76EE\u5F55\uFF0C\u53EF\u80FD\u4EA7\u751F\u8986\u76D6\u6216\u51B2\u7A81\u3002';el.classList.remove('hidden');}else el.classList.add('hidden');}\n async function loadSessions(){var result=await api('/api/sessions');state.sessions=result.sessions||[];if(state.selectedId&&!state.sessions.some(function(item){return item.sessionId===state.selectedId;}))clearSelection('\u4F1A\u8BDD\u5DF2\u5728\u5176\u4ED6\u9875\u9762\u5220\u9664');renderSessions();renderWarning();}\n async function loadDetail(id,token){var sessionId=id||state.selectedId;if(!sessionId)return false;var requestToken=token===undefined?++state.detailRequestToken:token;var result=await api('/api/sessions/'+encodeURIComponent(sessionId));if(state.selectedId!==sessionId||requestToken!==state.detailRequestToken)return false;await loadSessions();if(state.selectedId!==sessionId||requestToken!==state.detailRequestToken)return false;state.detail=result.session;renderDetail();return true;}\n function scheduleDetail(id){if(id&&id!==state.selectedId)return;clearTimeout(state.reloadTimer);var token=++state.detailRequestToken;state.reloadTimer=setTimeout(function(){loadDetail(id||state.selectedId,token).catch(function(error){toast(error.message,true);});},120);}\n function scheduleSessions(){clearTimeout(state.sessionsReloadTimer);state.sessionsReloadTimer=setTimeout(function(){loadSessions().catch(function(error){toast(error.message,true);});},80);}\n async function selectSession(id){state.selectedId=id;var token=++state.detailRequestToken;renderSessions();if(state.eventSource)state.eventSource.close();state.eventSource=new EventSource('/api/sessions/'+encodeURIComponent(id)+'/events');state.eventSource.onmessage=function(){scheduleDetail(id);};await loadDetail(id,token);}\n async function loadConfig(){var result=await api('/api/config');state.config=result.config;$('cfg-provider').value=state.config.provider;$('cfg-base-url').value=state.config.baseURL;$('cfg-model').value=state.config.model;$('cfg-sub-model').value=state.config.subModel||'';$('cfg-effort').value=state.config.effort||'';$('cfg-context').value=state.config.contextWindow;$('cfg-max-output').value=state.config.maxOutputTokens||'';$('cfg-streaming').checked=state.config.streaming!==false;$('cfg-port').value=state.config.web.port;$('cfg-open').checked=state.config.web.openOnStart!==false;$('server-port').textContent=state.config.web.port;$('key-state').textContent=state.config.apiKeyConfigured?'\u5F53\u524D API Key\uFF1A'+state.config.apiKeyMask:'\u5C1A\u672A\u914D\u7F6E API Key';$('new-cwd').value=state.config.defaultCwd||'';$('new-model').value=state.config.model;$('new-sub-model').value=state.config.subModel||'';var models=[state.config.model,state.config.subModel].concat(state.sessions.reduce(function(all,item){return all.concat([item.model,item.subModel]);},[])).filter(Boolean).filter(function(value,index,array){return array.indexOf(value)===index;});$('recent-models').innerHTML=models.map(function(model){return '<option value=\"'+escapeHtml(model)+'\">';}).join('');}\n $('new-session').addEventListener('click',function(){openModal('new-modal');setTimeout(function(){$('new-cwd').focus();},0);});$('open-settings').addEventListener('click',function(){loadConfig().then(function(){openModal('settings-modal');}).catch(function(error){toast(error.message,true);});});document.querySelectorAll('[data-close]').forEach(function(button){button.addEventListener('click',function(){closeModal(button.dataset.close);});});\n $('new-form').addEventListener('submit',async function(event){event.preventDefault();try{var result=await api('/api/sessions',{method:'POST',body:JSON.stringify({cwd:$('new-cwd').value,title:$('new-title').value,model:$('new-model').value,subModel:$('new-sub-model').value,effort:$('new-effort').value})});closeModal('new-modal');$('new-title').value='';await loadSessions();await selectSession(result.session.sessionId);}catch(error){toast(error.message,true);}});\n $('settings-form').addEventListener('submit',async function(event){event.preventDefault();var body={provider:$('cfg-provider').value,baseURL:$('cfg-base-url').value,model:$('cfg-model').value,subModel:$('cfg-sub-model').value,effort:$('cfg-effort').value,contextWindow:Number($('cfg-context').value),streaming:$('cfg-streaming').checked,web:{port:Number($('cfg-port').value),openOnStart:$('cfg-open').checked}};if($('cfg-max-output').value)body.maxOutputTokens=Number($('cfg-max-output').value);if($('cfg-api-key').value)body.apiKey=$('cfg-api-key').value;try{await api('/api/config',{method:'PUT',body:JSON.stringify(body)});$('cfg-api-key').value='';closeModal('settings-modal');toast('\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u65B0\u914D\u7F6E\u4ECE\u4E0B\u4E00\u6B21\u65B0\u5EFA\u4F1A\u8BDD\u751F\u6548');await loadConfig();}catch(error){toast(error.message,true);}});\n async function uploadAttachment(sessionId,file){var response=await fetch('/api/sessions/'+encodeURIComponent(sessionId)+'/attachments?name='+encodeURIComponent(file.name||'image'),{method:'POST',headers:{'content-type':file.type||'application/octet-stream'},body:file});var result=await response.json().catch(function(){return{error:'\u4E0A\u4F20\u54CD\u5E94\u4E0D\u662F\u6709\u6548 JSON'};});if(!response.ok)throw new Error(result.error||('HTTP '+response.status));return result.attachment;}\n async function handleAttachmentFiles(files){var sessionId=state.selectedId;if(!sessionId||state.uploading)return;var candidates=[].slice.call(files||[]).filter(function(file){return ['image/png','image/jpeg','image/webp'].includes(file.type);});var existing=state.attachmentDrafts[sessionId]||[];if(existing.length+candidates.length>10){toast('\u6BCF\u6761\u6D88\u606F\u6700\u591A\u6DFB\u52A0 10 \u5F20\u56FE\u7247',true);return;}if(!candidates.length){toast('\u4EC5\u652F\u6301 PNG\u3001JPEG\u3001WebP \u56FE\u7247',true);return;}state.uploading=true;if(state.detail)renderDetail();try{for(var i=0;i<candidates.length;i++){if(candidates[i].size>20*1024*1024)throw new Error('\u5355\u5F20\u56FE\u7247\u4E0D\u80FD\u8D85\u8FC7 20 MB\uFF1A'+candidates[i].name);existing.push(await uploadAttachment(sessionId,candidates[i]));state.attachmentDrafts[sessionId]=existing;renderAttachmentDrafts();}}catch(error){toast(error.message,true);}finally{state.uploading=false;if(state.detail)renderDetail();$('attachment-input').value='';}}\n async function removeAttachment(id){var sessionId=state.selectedId;if(!sessionId)return;try{await api('/api/sessions/'+encodeURIComponent(sessionId)+'/attachments/'+encodeURIComponent(id),{method:'DELETE'});state.attachmentDrafts[sessionId]=(state.attachmentDrafts[sessionId]||[]).filter(function(item){return item.attachmentId!==id;});renderAttachmentDrafts();}catch(error){toast(error.message,true);}}\n async function send(){var sessionId=state.selectedId;var text=$('prompt').value;var attachments=currentAttachments();if(!sessionId||(!text.trim()&&!attachments.length)||state.uploading)return;try{var patch={model:$('session-model').value,subModel:$('session-sub-model').value,effort:$('session-effort').value};if(patch.model!==state.detail.model||patch.subModel!==state.detail.subModel||patch.effort!==state.detail.effort){var updated=await api('/api/sessions/'+encodeURIComponent(sessionId),{method:'PATCH',body:JSON.stringify(patch)});if(state.selectedId===sessionId){state.detail=Object.assign({},state.detail,updated.session);renderDetail();}await loadSessions();}await api('/api/sessions/'+encodeURIComponent(sessionId)+'/messages',{method:'POST',body:JSON.stringify({text:text,attachmentIds:attachments.map(function(item){return item.attachmentId;})})});delete state.configDrafts[sessionId];delete state.promptDrafts[sessionId];delete state.attachmentDrafts[sessionId];if(state.selectedId===sessionId){$('prompt').value='';renderAttachmentDrafts();}var token=++state.detailRequestToken;await loadDetail(sessionId,token);}catch(error){toast(error.message,true);}}\n ['session-model','session-sub-model','session-effort'].forEach(function(id){$(id).addEventListener('input',rememberDraft);$(id).addEventListener('change',rememberDraft);});\n $('attach-image').addEventListener('click',function(){$('attachment-input').click();});$('attachment-input').addEventListener('change',function(){handleAttachmentFiles(this.files);});$('attachment-drafts').addEventListener('click',function(event){var button=event.target.closest('[data-remove-attachment]');if(button)removeAttachment(button.dataset.removeAttachment);});document.addEventListener('paste',function(event){var files=[].slice.call(event.clipboardData&&event.clipboardData.files||[]).filter(function(file){return file.type.indexOf('image/')===0;});if(files.length){event.preventDefault();handleAttachmentFiles(files);}});$('composer').addEventListener('dragover',function(event){event.preventDefault();this.classList.add('dragging');});$('composer').addEventListener('dragleave',function(){this.classList.remove('dragging');});$('composer').addEventListener('drop',function(event){event.preventDefault();this.classList.remove('dragging');handleAttachmentFiles(event.dataTransfer.files);});\n $('send').addEventListener('click',send);$('prompt').addEventListener('input',rememberPromptDraft);$('prompt').addEventListener('keydown',function(event){if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send();}});$('stop').addEventListener('click',async function(){try{await api('/api/sessions/'+encodeURIComponent(state.selectedId)+'/stop',{method:'POST',body:'{}'});toast('\u5DF2\u8BF7\u6C42\u505C\u6B62');}catch(error){toast(error.message,true);}});\n $('rename-session').addEventListener('click',function(){$('rename-title').value=state.detail.title;openModal('rename-modal');});$('rename-form').addEventListener('submit',async function(event){event.preventDefault();try{await api('/api/sessions/'+encodeURIComponent(state.selectedId),{method:'PATCH',body:JSON.stringify({title:$('rename-title').value})});closeModal('rename-modal');var token=++state.detailRequestToken;await loadDetail(state.selectedId,token);}catch(error){toast(error.message,true);}});$('delete-session').addEventListener('click',async function(){if(!confirm('\u5220\u9664\u8FD9\u4E2A\u4F1A\u8BDD\u53CA\u5176\u672C\u5730\u5386\u53F2\uFF1F\u4E0D\u4F1A\u5220\u9664\u5DE5\u4F5C\u76EE\u5F55\u4E2D\u7684\u6587\u4EF6\u3002'))return;try{var deletedId=state.selectedId;await api('/api/sessions/'+encodeURIComponent(deletedId),{method:'DELETE'});delete state.configDrafts[deletedId];delete state.promptDrafts[deletedId];delete state.attachmentDrafts[deletedId];clearSelection('\u4F1A\u8BDD\u5DF2\u5220\u9664');await loadSessions();}catch(error){toast(error.message,true);}});\n $('content').addEventListener('click',async function(event){var button=event.target.closest('[data-approval-answer]');if(!button)return;button.disabled=true;try{await api('/api/approvals/'+encodeURIComponent(button.dataset.approvalId),{method:'POST',body:JSON.stringify({answer:button.dataset.approvalAnswer})});var token=++state.detailRequestToken;await loadDetail(state.selectedId,token);}catch(error){button.disabled=false;toast(error.message,true);}});\n function handleGlobalEvent(event){try{var update=JSON.parse(event.data);if(update.type==='session_deleted'){delete state.configDrafts[update.sessionId];delete state.promptDrafts[update.sessionId];delete state.attachmentDrafts[update.sessionId];}}catch(error){}scheduleSessions();}\n state.globalEventSource=new EventSource('/api/events');state.globalEventSource.onmessage=handleGlobalEvent;\n Promise.all([loadSessions(),loadConfig()]).then(function(){if(state.sessions[0])return selectSession(state.sessions[0].sessionId);}).catch(function(error){toast(error.message,true);});\n})();\n</script>\n</body></html>";
|
|
1
|
+
export declare const DEEPCCC_WEB_PAGE: string;
|
package/dist/web-page.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { WEB_TOOL_SUMMARY_RULES } from "./web-tool-presentation.js";
|
|
2
|
+
const WEB_TOOL_SUMMARY_RULES_JSON = JSON.stringify(WEB_TOOL_SUMMARY_RULES).replace(/</g, "\\u003c");
|
|
1
3
|
export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
2
4
|
<html lang="zh-CN">
|
|
3
5
|
<head>
|
|
@@ -10,9 +12,10 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
10
12
|
.sidebar{min-width:0;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column}.brand{height:70px;display:flex;align-items:center;gap:12px;padding:0 18px;border-bottom:1px solid var(--line)}.mark{width:36px;height:36px;border-radius:12px;display:grid;place-items:center;background:var(--accent);color:#fff;box-shadow:0 9px 25px rgba(103,88,232,.25);font-weight:900}.brand-copy strong{display:block;font-size:16px;letter-spacing:.01em}.brand-copy span{font-size:10px;color:var(--muted);letter-spacing:.13em}.sidebar-actions{display:flex;gap:8px;padding:14px}.primary,.ghost,.icon{border:1px solid var(--line);border-radius:10px;color:var(--text);background:var(--panel);font-weight:700}.primary{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 8px 22px rgba(103,88,232,.22)}.sidebar-actions .primary{flex:1;padding:10px}.icon{width:40px;padding:0;font-size:17px}.ghost{padding:9px 12px}.primary:hover,.ghost:hover,.icon:hover{filter:brightness(1.03)}
|
|
11
13
|
.session-list{flex:1;overflow:auto;padding:0 10px 14px}.session-empty{padding:38px 18px;color:var(--muted);text-align:center}.session-item{width:100%;display:block;padding:12px;border:1px solid transparent;border-radius:12px;background:transparent;color:var(--text);text-align:left;margin:3px 0}.session-item:hover{background:var(--panel2)}.session-item.active{background:linear-gradient(135deg,rgba(124,105,241,.2),rgba(93,74,207,.08));border-color:rgba(139,124,246,.35)}.session-title{display:flex;align-items:center;gap:8px;font-weight:750}.status-dot{width:7px;height:7px;border-radius:50%;background:#626a78}.status-dot.running{background:var(--accent2);box-shadow:0 0 0 4px rgba(94,228,177,.1)}.session-meta{margin-top:5px;color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sidebar-foot{padding:12px 16px;border-top:1px solid var(--line);color:var(--muted);font-size:10px;display:flex;justify-content:space-between}
|
|
12
14
|
.main{min-width:0;height:100%;display:flex;flex-direction:column}.topbar{height:70px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 22px;border-bottom:1px solid var(--line);background:rgba(12,14,19,.7);backdrop-filter:blur(18px)}.session-heading{min-width:0}.session-heading h1{font-size:16px;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-heading p{font-size:11px;color:var(--muted);margin:3px 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.top-actions{display:flex;gap:8px}.top-actions button{height:38px}.content{flex:1;min-height:0;overflow-y:auto;scroll-behavior:smooth}.welcome{height:100%;display:grid;place-items:center;padding:30px}.welcome-card{max-width:650px;text-align:center}.welcome-mark{width:72px;height:72px;margin:0 auto 20px;border-radius:24px;display:grid;place-items:center;background:linear-gradient(135deg,#7764eb,#b45fd7);font-size:29px;box-shadow:0 20px 50px rgba(119,100,235,.28)}.welcome h2{font-size:28px;margin:0 0 10px;letter-spacing:-.02em}.welcome p{color:var(--muted);margin:0 auto;max-width:480px}.hint-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:28px}.hint{padding:14px;border:1px solid var(--line);border-radius:13px;background:rgba(24,28,37,.65);font-size:12px;color:var(--muted)}
|
|
13
|
-
.thread{width:min(900px,calc(100% - 36px));margin:0 auto;padding:28px 0 150px}.empty-thread{margin:80px auto 0;max-width:520px;padding:28px;border:1px dashed #353c4a;border-radius:18px;background:rgba(20,23,31,.55);text-align:center}.empty-thread strong{display:block;font-size:18px;margin-bottom:7px}.empty-thread span{color:var(--muted);font-size:12px}.message{display:grid;grid-template-columns:30px minmax(0,1fr);gap:12px;margin:0 0 22px}.avatar{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;background:var(--soft);font-size:11px;font-weight:900}.message.user .avatar{background:#304058}.message.assistant .avatar{background:linear-gradient(135deg,#7363e8,#9a61dc)}.bubble{min-width:0;padding:2px 0;color:#dfe3eb}.bubble .role{font-size:11px;color:var(--muted);font-weight:800;margin-bottom:7px}.markdown{font-size:14px;line-height:1.75;overflow-wrap:anywhere}.markdown p{margin:.35em 0}.markdown pre{padding:14px;border:1px solid var(--line);border-radius:11px;background:#0a0c11;overflow:auto;font:12px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace}.markdown code{padding:2px 5px;border-radius:5px;background:var(--soft);font:12px ui-monospace,SFMono-Regular,Consolas,monospace}.markdown pre code{padding:0;background:transparent}.tool-card{margin:8px 0;border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:hidden}.tool-card summary{padding:9px 11px;color:#bbc2d0;font-size:12px;font-weight:700}.tool-card pre{margin:0;border:0;border-top:1px solid var(--line);border-radius:0;max-height:260px}.run-state{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px;margin:8px 0 20px}.pulse{width:8px;height:8px;border-radius:50%;background:var(--accent2);animation:pulse 1.4s infinite}@keyframes pulse{50%{opacity:.35;transform:scale(.72)}}.workspace-warning{width:min(900px,calc(100% - 36px));margin:14px auto 0;padding:9px 12px;border:1px solid rgba(242,184,75,.3);background:rgba(242,184,75,.08);border-radius:10px;color:#e8c879;font-size:11px}
|
|
15
|
+
.thread{width:min(900px,calc(100% - 36px));margin:0 auto;padding:28px 0 150px}.empty-thread{margin:80px auto 0;max-width:520px;padding:28px;border:1px dashed #353c4a;border-radius:18px;background:rgba(20,23,31,.55);text-align:center}.empty-thread strong{display:block;font-size:18px;margin-bottom:7px}.empty-thread span{color:var(--muted);font-size:12px}.message{display:grid;grid-template-columns:30px minmax(0,1fr);gap:12px;margin:0 0 22px}.avatar{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;background:var(--soft);font-size:11px;font-weight:900}.message.user .avatar{background:#304058}.message.assistant .avatar{background:linear-gradient(135deg,#7363e8,#9a61dc)}.bubble{min-width:0;padding:2px 0;color:#dfe3eb}.bubble .role{font-size:11px;color:var(--muted);font-weight:800;margin-bottom:7px}.markdown{font-size:14px;line-height:1.75;overflow-wrap:anywhere}.markdown p{margin:.35em 0}.markdown pre{padding:14px;border:1px solid var(--line);border-radius:11px;background:#0a0c11;overflow:auto;font:12px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace}.markdown code{padding:2px 5px;border-radius:5px;background:var(--soft);font:12px ui-monospace,SFMono-Regular,Consolas,monospace}.markdown pre code{padding:0;background:transparent}.markdown h1,.markdown h2,.markdown h3,.markdown h4,.markdown h5,.markdown h6{margin:.85em 0 .45em;line-height:1.35;font-weight:800}.markdown h1{font-size:1.5em}.markdown h2{font-size:1.32em}.markdown h3{font-size:1.16em}.markdown h4{font-size:1.05em}.markdown h5,.markdown h6{font-size:1em}.markdown ul,.markdown ol{margin:.35em 0;padding-left:1.6em}.markdown li{margin:.15em 0}.markdown blockquote{margin:.6em 0;padding:2px 0 2px 14px;border-left:3px solid var(--line);color:var(--muted)}.markdown a{color:var(--accent);text-decoration:none}.markdown a:hover{text-decoration:underline}.markdown hr{border:0;border-top:1px solid var(--line);margin:1.1em 0}.markdown table{border-collapse:collapse;margin:.7em 0;width:100%}.markdown th,.markdown td{border:1px solid var(--line);padding:7px 11px;text-align:left;font-size:13px;vertical-align:top}.markdown th{background:var(--soft);font-weight:800}.markdown tr:nth-child(2n) td{background:var(--soft)}.tool-card{margin:8px 0;border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:hidden}.tool-card summary{padding:9px 11px;color:#bbc2d0;font-size:12px;font-weight:700}.tool-card pre{margin:0;border:0;border-top:1px solid var(--line);border-radius:0;max-height:260px}.tool-status{display:inline-flex;align-items:center;margin-left:6px;padding:1px 7px;border-radius:999px;font-size:10px;font-weight:800}.tool-status.pending{background:#fff0c2;color:#8a6110}.tool-status.ok{background:#e5f5ee;color:#23825d}.tool-status.error{background:#fde4e9;color:#c2415a}.run-state{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px;margin:8px 0 20px}.pulse{width:8px;height:8px;border-radius:50%;background:var(--accent2);animation:pulse 1.4s infinite}@keyframes pulse{50%{opacity:.35;transform:scale(.72)}}.workspace-warning{width:min(900px,calc(100% - 36px));margin:14px auto 0;padding:9px 12px;border:1px solid rgba(242,184,75,.3);background:rgba(242,184,75,.08);border-radius:10px;color:#e8c879;font-size:11px}
|
|
14
16
|
.composer-shell{position:absolute;left:286px;right:0;bottom:0;padding:0 20px 18px;background:linear-gradient(transparent,var(--bg) 32%)}.composer{width:min(900px,100%);margin:auto;border:1px solid #343b49;border-radius:17px;background:rgba(25,29,38,.96);box-shadow:var(--shadow);overflow:hidden}.composer textarea{width:100%;min-height:68px;max-height:180px;resize:none;border:0;outline:0;padding:15px 16px 8px;background:transparent;color:var(--text)}.composer textarea::placeholder{color:#737b8b}.composer-bar{display:flex;align-items:center;gap:8px;padding:7px 9px}.composer-bar input,.composer-bar select{min-width:0;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--muted);padding:7px 9px;font-size:11px}.composer-bar input{width:180px}.composer-bar select{width:104px}.send{margin-left:auto;width:36px;height:36px;border:0;border-radius:10px;background:var(--accent);color:#fff;font-size:17px}.stop{margin-left:auto;height:36px;border:1px solid rgba(255,111,137,.4);border-radius:10px;background:rgba(255,111,137,.12);color:#ff91a5;padding:0 13px;font-weight:700}.composer-disabled{opacity:.5;pointer-events:none}
|
|
15
17
|
.attachment-drafts{display:flex;gap:8px;padding:0 12px;overflow-x:auto}.attachment-draft{position:relative;flex:none;width:74px}.attachment-draft img{width:74px;height:54px;object-fit:cover;border:1px solid var(--line);border-radius:9px;background:var(--soft)}.attachment-draft button{position:absolute;right:-5px;top:-6px;width:20px;height:20px;padding:0;border:1px solid var(--line);border-radius:50%;background:#fff;color:var(--danger);font-weight:900}.attach{width:36px;height:36px;border:1px solid var(--line);border-radius:10px;background:var(--soft);color:var(--muted);font-size:17px}.image-gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:9px;margin-top:10px;max-width:650px}.image-card{display:block;border:1px solid var(--line);border-radius:11px;background:var(--soft);overflow:hidden;color:var(--text);text-decoration:none}.image-card img{display:block;width:100%;height:150px;object-fit:cover;background:#eef1f6}.image-card span{display:block;padding:7px 9px;color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.composer.dragging{outline:2px solid var(--accent);outline-offset:2px}
|
|
18
|
+
.tool-card summary{display:flex;align-items:center;gap:8px;min-width:0}.tool-summary-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-weight:700}.tool-section{border-top:1px solid var(--line)}.tool-section-title{padding:7px 11px 0;color:var(--muted);font-size:10px;font-weight:800;letter-spacing:.04em}.tool-card .tool-section pre{margin:0;border:0;background:var(--soft);max-height:none;white-space:pre-wrap;overflow-wrap:anywhere}.tool-overflow-toggle{margin:0 11px 9px;padding:5px 9px;border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--accent);font-size:10px;font-weight:700}
|
|
16
19
|
.modal-backdrop{position:fixed;inset:0;z-index:30;display:grid;place-items:center;padding:20px;background:rgba(4,5,8,.66);backdrop-filter:blur(7px)}.modal-backdrop.hidden{display:none}.modal{width:min(560px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid #353c4a;border-radius:18px;background:#171a22;box-shadow:0 30px 100px rgba(0,0,0,.55);padding:22px}.modal h2{margin:0 0 5px;font-size:20px}.modal>.subtitle{margin:0 0 20px;color:var(--muted);font-size:12px}.field{display:grid;gap:7px;margin:13px 0}.field label{font-size:11px;color:#b7becb;font-weight:800}.field input,.field select{width:100%;border:1px solid var(--line);border-radius:10px;background:#10131a;color:var(--text);padding:10px 11px;outline:none}.field input:focus,.field select:focus{border-color:var(--accent)}.row{display:grid;grid-template-columns:1fr 1fr;gap:10px}.check{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.danger{border:1px solid rgba(255,111,137,.35);background:rgba(255,111,137,.1);color:#ff849b;border-radius:10px;padding:9px 12px;font-weight:700}.approval{width:min(620px,100%)}.approval-badge{display:inline-flex;padding:5px 8px;border-radius:999px;background:rgba(242,184,75,.13);color:#f1c565;font-size:10px;font-weight:900}.approval pre{white-space:pre-wrap;overflow-wrap:anywhere;padding:13px;border:1px solid var(--line);border-radius:10px;background:#0d0f15;color:#dce0e9}.approval-actions{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.toast{position:fixed;right:20px;top:20px;z-index:50;max-width:420px;padding:11px 14px;border-radius:10px;background:#283143;border:1px solid #3b465b;box-shadow:var(--shadow)}.toast.error{background:#602536;border-color:#913a52}.hidden{display:none!important}
|
|
17
20
|
.topbar{background:rgba(255,255,255,.86)}.welcome-mark{background:var(--accent);color:#fff}.hint,.empty-thread{background:var(--panel);border-color:var(--line)}.session-item.active{background:var(--accent-soft);border-color:#d8d1ff}.status-dot{background:#a3aabd}.status-dot.running{background:var(--accent2);box-shadow:0 0 0 4px rgba(35,130,93,.1)}.message.user .avatar{background:#e8edf6}.message.assistant .avatar{background:var(--accent);color:#fff}.bubble{color:var(--text)}.markdown pre,.tool-card pre{background:var(--soft);color:var(--text)}.tool-card summary{color:var(--muted)}.composer{border-color:var(--line);background:rgba(255,255,255,.98)}.composer textarea::placeholder{color:#9aa3b4}.composer-bar input,.composer-bar select{background:var(--soft)}.modal-backdrop{background:rgba(24,32,51,.36)}.modal{border-color:var(--line);background:var(--panel);box-shadow:0 28px 90px rgba(35,43,65,.24)}.field label{color:var(--text)}.field input,.field select{background:var(--soft);color:var(--text)}.danger{border-color:#f3c4ce;background:#fff1f3;color:var(--danger)}.toast{background:#253047;color:#fff}.toast.error{background:#9f2942}.approval-card{margin:16px 0;padding:14px;border:1px solid #ead49b;border-radius:13px;background:#fffaf0;box-shadow:0 5px 18px rgba(94,70,15,.06)}.approval-card.resolved{border-color:var(--line);background:var(--soft)}.approval-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}.approval-head strong{font-size:12px}.approval-status{padding:3px 7px;border-radius:999px;background:#fff0c2;color:#8a6110;font-size:10px;font-weight:800}.approval-card.resolved .approval-status{background:#e5f5ee;color:var(--accent2)}.approval-detail{color:var(--muted);font-size:11px;margin-bottom:8px}.approval-command{margin:0;padding:10px;border:1px solid var(--line);border-radius:9px;background:#fff;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace}.approval-inline-actions{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}.approval-inline-actions button{padding:7px 10px;font-size:11px}
|
|
18
21
|
.sidebar{height:100%;overflow:hidden}.main{min-height:0!important;overflow:hidden}
|
|
@@ -44,13 +47,263 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
44
47
|
<div id="toast" class="toast hidden"></div>
|
|
45
48
|
<script>
|
|
46
49
|
(function(){
|
|
47
|
-
var
|
|
50
|
+
var toolSummaryRules=${WEB_TOOL_SUMMARY_RULES_JSON};
|
|
51
|
+
var state={sessions:[],selectedId:null,detail:null,config:null,eventSource:null,globalEventSource:null,reloadTimer:null,sessionsReloadTimer:null,detailRequestToken:0,configDrafts:{},promptDrafts:{},attachmentDrafts:{},uploading:false,renderQueued:false};
|
|
48
52
|
var $=function(id){return document.getElementById(id);};
|
|
49
53
|
async function api(path,options){var response=await fetch(path,Object.assign({headers:{'content-type':'application/json; charset=utf-8'}},options||{}));var data=await response.json().catch(function(){return {error:'响应不是有效 JSON'};});if(!response.ok)throw new Error(data.error||('HTTP '+response.status));return data;}
|
|
50
54
|
function toast(message,error){var el=$('toast');el.textContent=message;el.className='toast'+(error?' error':'');clearTimeout(toast.timer);toast.timer=setTimeout(function(){el.className='toast hidden';},3500);}
|
|
51
55
|
function openModal(id){$(id).classList.remove('hidden');}function closeModal(id){$(id).classList.add('hidden');}
|
|
52
56
|
function escapeHtml(value){return String(value||'').replace(/[&<>"']/g,function(ch){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch];});}
|
|
53
|
-
function markdown(value){
|
|
57
|
+
function markdown(value){
|
|
58
|
+
var text = escapeHtml(value == null ? '' : String(value));
|
|
59
|
+
var tick = String.fromCharCode(96);
|
|
60
|
+
var NL = String.fromCharCode(10);
|
|
61
|
+
var fence = tick + tick + tick;
|
|
62
|
+
var blocks = [];
|
|
63
|
+
var lines = text.split(NL);
|
|
64
|
+
var out = [];
|
|
65
|
+
var i = 0;
|
|
66
|
+
var listStack = [];
|
|
67
|
+
var inFence = false;
|
|
68
|
+
var fenceLang = '';
|
|
69
|
+
var fenceBuf = [];
|
|
70
|
+
var gt = '>';
|
|
71
|
+
function closeLists(){ while(listStack.length){ out.push(listStack.pop()); } }
|
|
72
|
+
function restore(s, prefix, items){
|
|
73
|
+
var result = '';
|
|
74
|
+
var endMark = '@@';
|
|
75
|
+
while(true){
|
|
76
|
+
var idx = s.indexOf(prefix);
|
|
77
|
+
if(idx < 0){ result += s; break; }
|
|
78
|
+
result += s.slice(0, idx);
|
|
79
|
+
var numStart = idx + prefix.length;
|
|
80
|
+
var end = s.indexOf(endMark, numStart);
|
|
81
|
+
if(end < 0){ result += s.slice(idx); break; }
|
|
82
|
+
var num = Number(s.slice(numStart, end));
|
|
83
|
+
result += items[num] == null ? '' : items[num];
|
|
84
|
+
s = s.slice(end + endMark.length);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function safeHref(url){
|
|
89
|
+
var value = String(url || '').trim();
|
|
90
|
+
var lower = value.toLowerCase();
|
|
91
|
+
if(lower.indexOf('https://') === 0 || lower.indexOf('http://') === 0 || lower.indexOf('mailto:') === 0 || lower.charAt(0) === '#' || lower.charAt(0) === '/') return value;
|
|
92
|
+
return '#';
|
|
93
|
+
}
|
|
94
|
+
function inline(s){
|
|
95
|
+
var codes = [];
|
|
96
|
+
s = s.replace(new RegExp(tick + '([^' + tick + ']+)' + tick, 'g'), function(_, code){
|
|
97
|
+
codes.push('<code>' + code + '</code>');
|
|
98
|
+
return '@@CODE' + (codes.length - 1) + '@@';
|
|
99
|
+
});
|
|
100
|
+
var linked = '';
|
|
101
|
+
while(true){
|
|
102
|
+
var open = s.indexOf('[');
|
|
103
|
+
if(open < 0){ linked += s; break; }
|
|
104
|
+
linked += s.slice(0, open);
|
|
105
|
+
var mid = s.indexOf('](', open + 1);
|
|
106
|
+
if(mid < 0){ linked += s.slice(open); break; }
|
|
107
|
+
var close = s.indexOf(')', mid + 2);
|
|
108
|
+
if(close < 0){ linked += s.slice(open); break; }
|
|
109
|
+
var label = s.slice(open + 1, mid);
|
|
110
|
+
var url = s.slice(mid + 2, close);
|
|
111
|
+
linked += '<a href="' + safeHref(url) + '" target="_blank" rel="noopener">' + label + '</a>';
|
|
112
|
+
s = s.slice(close + 1);
|
|
113
|
+
}
|
|
114
|
+
s = linked;
|
|
115
|
+
s = s.replace(new RegExp('[*][*]([^*]+)[*][*]', 'g'), '<strong>$1</strong>');
|
|
116
|
+
s = s.replace(new RegExp('[*]([^*]+)[*]', 'g'), '<em>$1</em>');
|
|
117
|
+
return restore(s, '@@CODE', codes);
|
|
118
|
+
}
|
|
119
|
+
function splitRow(line){
|
|
120
|
+
var s = line.trim();
|
|
121
|
+
if(s.charAt(0) === '|') s = s.slice(1);
|
|
122
|
+
if(s.charAt(s.length - 1) === '|') s = s.slice(0, -1);
|
|
123
|
+
return s.split('|');
|
|
124
|
+
}
|
|
125
|
+
function alignOf(cell){
|
|
126
|
+
var t = cell.trim();
|
|
127
|
+
var left = t.charAt(0) === ':';
|
|
128
|
+
var right = t.charAt(t.length - 1) === ':' && t.length > 1;
|
|
129
|
+
if(left && right) return 'center';
|
|
130
|
+
if(right) return 'right';
|
|
131
|
+
if(left) return 'left';
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
function isHr(t){
|
|
135
|
+
if(t.length < 3) return false;
|
|
136
|
+
var ch = t.charAt(0);
|
|
137
|
+
if(ch !== '-' && ch !== '*' && ch !== '_') return false;
|
|
138
|
+
for(var k = 0; k < t.length; k++){
|
|
139
|
+
var c = t.charAt(k);
|
|
140
|
+
if(c !== ch && c !== ' ') return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
function isTableSep(t){
|
|
145
|
+
if(t.indexOf('|') < 0 || t.indexOf('-') < 0) return false;
|
|
146
|
+
for(var k = 0; k < t.length; k++){
|
|
147
|
+
var c = t.charAt(k);
|
|
148
|
+
if(c !== '|' && c !== '-' && c !== ':' && c !== ' ') return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
function tableAt(start){
|
|
153
|
+
var header = lines[start];
|
|
154
|
+
if(!header || header.indexOf('|') < 0) return null;
|
|
155
|
+
var sep = lines[start + 1];
|
|
156
|
+
if(sep === undefined || !isTableSep(sep)) return null;
|
|
157
|
+
var heads = splitRow(header);
|
|
158
|
+
var sepCells = splitRow(sep);
|
|
159
|
+
var aligns = [];
|
|
160
|
+
for(var a = 0; a < sepCells.length; a++) aligns.push(alignOf(sepCells[a]));
|
|
161
|
+
var rows = [];
|
|
162
|
+
var j = start + 2;
|
|
163
|
+
while(j < lines.length && lines[j].indexOf('|') >= 0 && lines[j].trim() !== ''){
|
|
164
|
+
rows.push(splitRow(lines[j]));
|
|
165
|
+
j++;
|
|
166
|
+
}
|
|
167
|
+
var html = '<table><thead><tr>';
|
|
168
|
+
for(var h = 0; h < heads.length; h++){
|
|
169
|
+
html += '<th' + (aligns[h] ? ' style="text-align:' + aligns[h] + '"' : '') + '>' + inline(heads[h].trim()) + '</th>';
|
|
170
|
+
}
|
|
171
|
+
html += '</tr></thead><tbody>';
|
|
172
|
+
for(var r = 0; r < rows.length; r++){
|
|
173
|
+
html += '<tr>';
|
|
174
|
+
for(var c = 0; c < rows[r].length; c++){
|
|
175
|
+
html += '<td' + (aligns[c] ? ' style="text-align:' + aligns[c] + '"' : '') + '>' + inline(rows[r][c].trim()) + '</td>';
|
|
176
|
+
}
|
|
177
|
+
html += '</tr>';
|
|
178
|
+
}
|
|
179
|
+
html += '</tbody></table>';
|
|
180
|
+
return { html: html, next: j };
|
|
181
|
+
}
|
|
182
|
+
function isHeading(t){
|
|
183
|
+
if(t.charAt(0) !== '#') return -1;
|
|
184
|
+
var n = 0;
|
|
185
|
+
while(n < t.length && n < 6 && t.charAt(n) === '#') n++;
|
|
186
|
+
return t.charAt(n) === ' ' ? n : -1;
|
|
187
|
+
}
|
|
188
|
+
function isOrdered(t){
|
|
189
|
+
var k = 0;
|
|
190
|
+
while(k < t.length && t.charAt(k) >= '0' && t.charAt(k) <= '9') k++;
|
|
191
|
+
if(k === 0 || k >= t.length) return -1;
|
|
192
|
+
var ch = t.charAt(k);
|
|
193
|
+
if((ch === '.' || ch === ')') && t.charAt(k + 1) === ' ') return k + 2;
|
|
194
|
+
return -1;
|
|
195
|
+
}
|
|
196
|
+
while(i < lines.length){
|
|
197
|
+
var line = lines[i];
|
|
198
|
+
if(inFence){
|
|
199
|
+
if(line.trim().slice(0, 3) === fence){
|
|
200
|
+
blocks.push('<pre><code data-lang="' + fenceLang + '">' + fenceBuf.join(NL) + '</code></pre>');
|
|
201
|
+
out.push('@@BLOCK' + (blocks.length - 1) + '@@');
|
|
202
|
+
inFence = false;
|
|
203
|
+
fenceBuf = [];
|
|
204
|
+
fenceLang = '';
|
|
205
|
+
} else {
|
|
206
|
+
fenceBuf.push(line);
|
|
207
|
+
}
|
|
208
|
+
i++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
var trimmed = line.trim();
|
|
212
|
+
if(trimmed.slice(0, 3) === fence){
|
|
213
|
+
inFence = true;
|
|
214
|
+
fenceLang = trimmed.slice(3).trim();
|
|
215
|
+
fenceBuf = [];
|
|
216
|
+
i++;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
var table = tableAt(i);
|
|
220
|
+
if(table){
|
|
221
|
+
closeLists();
|
|
222
|
+
out.push(table.html);
|
|
223
|
+
i = table.next;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
var headingLevel = isHeading(trimmed);
|
|
227
|
+
if(headingLevel > 0){
|
|
228
|
+
closeLists();
|
|
229
|
+
out.push('<h' + headingLevel + '>' + inline(trimmed.slice(headingLevel + 1).trim()) + '</h' + headingLevel + '>');
|
|
230
|
+
i++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if(isHr(trimmed)){
|
|
234
|
+
closeLists();
|
|
235
|
+
out.push('<hr>');
|
|
236
|
+
i++;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if(trimmed === gt || trimmed.slice(0, gt.length + 1) === gt + ' '){
|
|
240
|
+
closeLists();
|
|
241
|
+
var quote = [];
|
|
242
|
+
while(i < lines.length){
|
|
243
|
+
var q = lines[i].trim();
|
|
244
|
+
if(q === gt || q.slice(0, gt.length + 1) === gt + ' '){
|
|
245
|
+
quote.push(q === gt ? '' : q.slice(gt.length + 1));
|
|
246
|
+
i++;
|
|
247
|
+
} else {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
out.push('<blockquote><p>' + inline(quote.join('<br>')) + '</p></blockquote>');
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
var c0 = trimmed.charAt(0);
|
|
255
|
+
if((c0 === '-' || c0 === '*' || c0 === '+') && trimmed.charAt(1) === ' '){
|
|
256
|
+
if(listStack[listStack.length - 1] !== '</ul>'){
|
|
257
|
+
closeLists();
|
|
258
|
+
out.push('<ul>');
|
|
259
|
+
listStack.push('</ul>');
|
|
260
|
+
}
|
|
261
|
+
out.push('<li>' + inline(trimmed.slice(2).trim()) + '</li>');
|
|
262
|
+
i++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
var orderedAt = isOrdered(trimmed);
|
|
266
|
+
if(orderedAt > 0){
|
|
267
|
+
if(listStack[listStack.length - 1] !== '</ol>'){
|
|
268
|
+
closeLists();
|
|
269
|
+
out.push('<ol>');
|
|
270
|
+
listStack.push('</ol>');
|
|
271
|
+
}
|
|
272
|
+
out.push('<li>' + inline(trimmed.slice(orderedAt).trim()) + '</li>');
|
|
273
|
+
i++;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if(trimmed === ''){
|
|
277
|
+
closeLists();
|
|
278
|
+
i++;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
closeLists();
|
|
282
|
+
var para = [];
|
|
283
|
+
while(i < lines.length){
|
|
284
|
+
var l = lines[i];
|
|
285
|
+
var t = l.trim();
|
|
286
|
+
if(t === '') break;
|
|
287
|
+
if(t.slice(0, 3) === fence) break;
|
|
288
|
+
if(isHeading(t) > 0) break;
|
|
289
|
+
var cc0 = t.charAt(0);
|
|
290
|
+
if((cc0 === '-' || cc0 === '*' || cc0 === '+') && t.charAt(1) === ' ') break;
|
|
291
|
+
if(isOrdered(t) > 0) break;
|
|
292
|
+
if(t === gt || t.slice(0, gt.length + 1) === gt + ' ') break;
|
|
293
|
+
if(isHr(t)) break;
|
|
294
|
+
if(t.indexOf('|') >= 0 && tableAt(i)) break;
|
|
295
|
+
para.push(t);
|
|
296
|
+
i++;
|
|
297
|
+
}
|
|
298
|
+
if(para.length){ out.push('<p>' + inline(para.join('<br>')) + '</p>'); }
|
|
299
|
+
}
|
|
300
|
+
if(inFence){
|
|
301
|
+
blocks.push('<pre><code data-lang="' + fenceLang + '">' + fenceBuf.join(NL) + '</code></pre>');
|
|
302
|
+
out.push('@@BLOCK' + (blocks.length - 1) + '@@');
|
|
303
|
+
}
|
|
304
|
+
closeLists();
|
|
305
|
+
return restore(out.join(NL), '@@BLOCK', blocks);
|
|
306
|
+
}
|
|
54
307
|
function statusText(status){return status==='running'?'执行中':'空闲';}
|
|
55
308
|
function renderSessions(){var root=$('session-list');root.innerHTML='';if(!state.sessions.length){root.innerHTML='<div class="session-empty">还没有会话<br>点击上方按钮开始</div>';return;}state.sessions.forEach(function(session){var button=document.createElement('button');button.className='session-item'+(session.sessionId===state.selectedId?' active':'');button.innerHTML='<div class="session-title"><i class="status-dot '+session.status+'"></i><span>'+escapeHtml(session.title)+'</span></div><div class="session-meta">'+escapeHtml(session.model||'默认模型')+' · '+escapeHtml(session.effort||'默认 effort')+'</div><div class="session-meta">'+escapeHtml(session.cwd)+'</div>';button.addEventListener('click',function(){selectSession(session.sessionId).catch(function(error){toast(error.message,true);});});root.appendChild(button);});}
|
|
56
309
|
function decodeAttachmentMessage(content){var start=content.indexOf('<deepccc-attachments>');var end=content.indexOf('</deepccc-attachments>',start);if(start<0||end<0)return{text:content,attachments:[]};var attachments=[];try{attachments=JSON.parse(content.slice(start+21,end).trim());if(!Array.isArray(attachments))attachments=[];}catch(error){}return{text:content.slice(0,start).trim(),attachments:attachments};}
|
|
@@ -59,8 +312,26 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
59
312
|
function artifactFromOutput(output){try{var value=typeof output==='string'?JSON.parse(output):output;return value&&typeof value.path==='string'&&String(value.mimeType||'').indexOf('image/')===0?value:null;}catch(error){return null;}}
|
|
60
313
|
function artifactGallery(toolCalls,sessionId){var items=(toolCalls||[]).filter(function(call){return call.name==='present_file';}).map(function(call){return artifactFromOutput(call.output);}).filter(Boolean);if(!items.length)return'';return '<div class="image-gallery">'+items.map(function(item){var url='/api/sessions/'+encodeURIComponent(sessionId)+'/artifact?path='+encodeURIComponent(item.path);return '<a class="image-card" href="'+escapeHtml(url)+'" target="_blank"><img src="'+escapeHtml(url)+'" alt="'+escapeHtml(item.caption||item.name)+'"><span>'+escapeHtml(item.caption||item.name)+'</span></a>';}).join('')+'</div>';}
|
|
61
314
|
function withoutToolTranscript(content){var marker='\\n\\n[工具记录]\\n';var index=content.indexOf(marker);return index>=0?content.slice(0,index):content;}
|
|
62
|
-
function
|
|
63
|
-
function
|
|
315
|
+
function tryToolJson(value){if(typeof value!=='string')return value;var text=value.trim();if(!text||(['{','['].indexOf(text.charAt(0))<0))return value;try{return JSON.parse(text);}catch(error){return value;}}
|
|
316
|
+
function toolRecord(value){var parsed=tryToolJson(value);return parsed&&typeof parsed==='object'&&!Array.isArray(parsed)?parsed:{};}
|
|
317
|
+
function toolOneLine(value){if(value==null)return'';var text=typeof value==='string'?value:JSON.stringify(value);return text.split(String.fromCharCode(10)).join(' ').trim().slice(0,220);}
|
|
318
|
+
function toolFormatBytes(value){if(value<1024)return value+' B';if(value<1024*1024)return(value/1024).toFixed(1)+' KB';return(value/1024/1024).toFixed(1)+' MB';}
|
|
319
|
+
function toolResultSummary(call){var output=toolRecord(call.output);if(call.isError)return toolOneLine(output.message||call.output)||'失败';if(typeof output.exitCode==='number')return'exit '+output.exitCode;if(Array.isArray(output.entries))return output.entries.length+' 项';if(Array.isArray(output.matches))return output.matches.length+' 条匹配';if(Array.isArray(output.changedFiles))return output.changedFiles.length+' 个文件';if(Array.isArray(output.results))return output.results.length+' 条结果';if(typeof output.size==='number')return toolFormatBytes(output.size);if(call.name==='task'&&typeof output.result==='string')return'已返回结果';return'完成';}
|
|
320
|
+
function toolSummary(call){var rule=toolSummaryRules[call.name]||{emoji:'🔧',inputFields:[]};var input=toolRecord(call.input);var details=(rule.inputFields||[]).map(function(field){return toolOneLine(input[field]);}).filter(Boolean);if(!call.pending)details.push(toolResultSummary(call));var mark=call.pending?'…':(call.isError?'✗':'✓');return[rule.emoji,call.name||'tool',mark,details.join(' · ')].filter(Boolean).join(' ').slice(0,420);}
|
|
321
|
+
function toolPayloadText(value){if(value===undefined)return'';if(value===null)return'null';var parsed=tryToolJson(value);if(typeof parsed==='string')return parsed;try{return JSON.stringify(parsed,null,2);}catch(error){return String(value);}}
|
|
322
|
+
function truncateToolPayload(value,headLines,tailLines){var full=toolPayloadText(value);var NL=String.fromCharCode(10);var lines=full.split(NL);var omitted=Math.max(0,lines.length-headLines-tailLines);var selected=omitted>0?lines.slice(0,headLines).concat(['… 已省略 '+omitted+' 行'],lines.slice(-tailLines)):lines;var clipped=false;var preview=selected.map(function(line){if(line.length<=240)return line;clipped=true;return line.slice(0,239)+'…';}).join(NL);return{full:full,preview:preview,omittedLines:omitted,truncated:omitted>0||clipped};}
|
|
323
|
+
function toolStorageKey(key){return'deepccc:tool-ui:v1:'+state.selectedId+':'+key;}
|
|
324
|
+
function toolState(key){try{return sessionStorage.getItem(toolStorageKey(key))==='1';}catch(error){return false;}}
|
|
325
|
+
function setToolState(key,value){try{sessionStorage.setItem(toolStorageKey(key),value?'1':'0');}catch(error){}}
|
|
326
|
+
function clearToolSessionState(sessionId){try{var prefix='deepccc:tool-ui:v1:'+sessionId+':';for(var index=sessionStorage.length-1;index>=0;index--){var key=sessionStorage.key(index);if(key&&key.indexOf(prefix)===0)sessionStorage.removeItem(key);}}catch(error){}}
|
|
327
|
+
function toolSectionHtml(call,kind,value,headLines,tailLines){if(value===undefined)return'';var result=truncateToolPayload(value,headLines,tailLines);var stateKey=call.uiKey+':'+kind+'-full';var expanded=toolState(stateKey);var text=expanded?result.full:result.preview;var label=kind==='input'?'调用参数':'工具结果';var button=result.truncated?'<button type="button" class="tool-overflow-toggle" data-tool-overflow="'+escapeHtml(stateKey)+'">'+escapeHtml(expanded?'收起省略内容':(result.omittedLines>0?'展开省略的 '+result.omittedLines+' 行':'展开完整内容'))+'</button>':'';return'<section class="tool-section"><div class="tool-section-title">'+label+'</div><pre>'+escapeHtml(text)+'</pre>'+button+'</section>';}
|
|
328
|
+
function timelineHtml(timeline,messageIndex){var results={};(timeline||[]).forEach(function(item){if(item&&item.type==='tool_result'&&item.tool_use_id)results[item.tool_use_id]=item;});var renderedCalls={};var html='';(timeline||[]).forEach(function(item,index){if(!item)return;if(item.type==='text'){if(item.text)html+='<div class="markdown">'+markdown(item.text)+'</div>';return;}if(item.type==='tool_use'){var id=item.id||('timeline-'+messageIndex+'-'+index);var result=results[id];renderedCalls[id]=true;html+=toolCardHtml({uiKey:'tool:'+id,name:item.name||'tool',input:item.input,output:result?result.output:undefined,isError:!!(result&&result.is_error),pending:!result});return;}if(item.type==='tool_result'&&!renderedCalls[item.tool_use_id]){var fallbackId=item.tool_use_id||('result-'+messageIndex+'-'+index);renderedCalls[fallbackId]=true;html+=toolCardHtml({uiKey:'tool:'+fallbackId,name:item.name||'tool',output:item.output,isError:!!item.is_error,pending:false});}});return html;}
|
|
329
|
+
function liveTimeline(events){var timeline=[];(events||[]).forEach(function(event){if(event.type!=='agent'||!event.data)return;var item=event.data;if(item.type==='text_reset'){timeline.length=0;return;}if(item.type==='text'){var previous=timeline[timeline.length-1];if(previous&&previous.type==='text')previous.text+=(item.text||'');else timeline.push({type:'text',text:item.text||''});return;}if(item.type==='tool_use')timeline.push({type:'tool_use',id:item.id,name:item.name,input:item.input});else if(item.type==='tool_result')timeline.push({type:'tool_result',tool_use_id:item.tool_use_id,name:item.name,output:item.content,is_error:!!item.is_error});});return timeline;}
|
|
330
|
+
function legacyToolsHtml(toolCalls,messageIndex){var tools='';(toolCalls||[]).forEach(function(call,index){tools+=toolCardHtml({uiKey:call.id?'tool:'+call.id:'message:'+messageIndex+':tool:'+index,name:call.name,input:call.input,output:call.output,isError:!!call.is_error,pending:false});});return tools;}
|
|
331
|
+
function messageHtml(role,content,toolCalls,messageIndex,timeline){var decoded=role==='user'?decodeAttachmentMessage(content):{text:withoutToolTranscript(content),attachments:[]};var body;if(role==='user')body='<div class="markdown">'+markdown(decoded.text)+'</div>'+attachmentGallery(decoded.attachments,state.detail.sessionId);else if(timeline&&timeline.length)body=timelineHtml(timeline,messageIndex);else body=legacyToolsHtml(toolCalls,messageIndex)+'<div class="markdown">'+markdown(decoded.text)+'</div>';return '<article class="message '+role+'"><div class="avatar">'+(role==='user'?'你':'D')+'</div><div class="bubble"><div class="role">'+(role==='user'?'YOU':'DEEPCCC')+'</div>'+body+'</div></article>';}
|
|
332
|
+
function buildToolGroups(events){var groups=[];var byId={};var order=[];(events||[]).forEach(function(event){if(event.type!=='agent'||!event.data)return;var item=event.data;if(item.type==='tool_use'){var id=item.id||('call-'+order.length);var group={id:id,uiKey:'tool:'+id,name:item.name||'tool',input:item.input,output:undefined,isError:false,pending:true};byId[id]=group;order.push(id);groups.push(group);}else if(item.type==='tool_result'){var key=item.tool_use_id;var target=key?byId[key]:null;if(target){target.output=item.content;target.isError=!!item.is_error;target.pending=false;}}});return groups;}
|
|
333
|
+
function toolCardHtml(call){var open=toolState(call.uiKey+':card');var present=call.name==='present_file'&&!call.pending?artifactFromOutput(call.output):null;var inputHtml=toolSectionHtml(call,'input',call.input,8,4);var outputHtml=call.pending?'':(present?'<section class="tool-section"><div class="tool-section-title">工具结果</div>'+artifactGallery([{name:'present_file',output:call.output}],state.detail.sessionId)+'</section>':toolSectionHtml(call,'output',call.output==null?'(无输出)':call.output,12,6));return'<details class="tool-card" data-tool-state="'+escapeHtml(call.uiKey+':card')+'"'+(open?' open':'')+'><summary><span class="tool-summary-text">'+escapeHtml(toolSummary(call))+'</span></summary>'+inputHtml+outputHtml+'</details>';}
|
|
334
|
+
function toolCardsHtml(calls){return (calls||[]).map(toolCardHtml).join('');}
|
|
64
335
|
function approvalAnswerLabel(answer){var labels={allow:'已允许一次','allow-session':'本会话已允许','allow-always':'已永久允许',deny:'已拒绝'};return labels[answer]||'等待决定';}
|
|
65
336
|
function approvalHtml(approval){var resolved=approval.status==='resolved';var html='<section class="approval-card'+(resolved?' resolved':'')+'"><div class="approval-head"><strong>⚠ 操作审批</strong><span class="approval-status">'+escapeHtml(resolved?approvalAnswerLabel(approval.answer):'等待批准')+'</span></div><div class="approval-detail">'+escapeHtml(approval.detail||('工具:'+approval.tool))+'</div><pre class="approval-command">'+escapeHtml(approval.action)+'</pre>';if(!resolved)html+='<div class="approval-inline-actions"><button class="danger" data-approval-answer="deny" data-approval-id="'+escapeHtml(approval.approvalId)+'">拒绝</button><button class="ghost" data-approval-answer="allow" data-approval-id="'+escapeHtml(approval.approvalId)+'">允许一次</button><button class="ghost" data-approval-answer="allow-session" data-approval-id="'+escapeHtml(approval.approvalId)+'">本会话允许</button><button class="primary" data-approval-answer="allow-always" data-approval-id="'+escapeHtml(approval.approvalId)+'">永久允许</button></div>';return html+'</section>';}
|
|
66
337
|
function sessionDraft(detail){return state.configDrafts[detail.sessionId]||{model:detail.model||'',subModel:detail.subModel||'',effort:detail.effort||''};}
|
|
@@ -69,14 +340,58 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
69
340
|
function currentAttachments(){return state.attachmentDrafts[state.selectedId]||[];}
|
|
70
341
|
function renderAttachmentDrafts(){var root=$('attachment-drafts');if(!root)return;root.innerHTML=currentAttachments().map(function(item){return '<div class="attachment-draft"><img src="'+escapeHtml(attachmentUrl(state.selectedId,item.attachmentId))+'" alt="'+escapeHtml(item.originalName)+'"><button type="button" data-remove-attachment="'+escapeHtml(item.attachmentId)+'">×</button></div>';}).join('');}
|
|
71
342
|
function clearSelection(message){if(state.eventSource)state.eventSource.close();state.eventSource=null;state.detailRequestToken++;state.selectedId=null;state.detail=null;$('composer-shell').classList.add('hidden');$('rename-session').classList.add('hidden');$('delete-session').classList.add('hidden');$('warning').classList.add('hidden');$('session-title').textContent='DeepCCC';$('session-cwd').textContent='选择或创建一个会话';$('content').innerHTML='<div class="welcome"><div class="welcome-card"><div class="welcome-mark">✦</div><h2>'+escapeHtml(message||'选择一个会话')+'</h2><p>从左侧选择其他会话或创建一个新的任务。</p></div></div>';}
|
|
72
|
-
function renderDetail(){
|
|
343
|
+
function renderDetail(){
|
|
344
|
+
var detail=state.detail;
|
|
345
|
+
if(!detail)return;
|
|
346
|
+
var contentEl=$('content');
|
|
347
|
+
var previousScrollTop=contentEl.scrollTop;
|
|
348
|
+
var stickToBottom=contentEl.scrollHeight-contentEl.scrollTop-contentEl.clientHeight<80;
|
|
349
|
+
var draft=sessionDraft(detail);
|
|
350
|
+
$('session-title').textContent=detail.title;
|
|
351
|
+
$('session-cwd').textContent=detail.cwd+' · '+statusText(detail.status);
|
|
352
|
+
$('rename-session').classList.remove('hidden');
|
|
353
|
+
$('delete-session').classList.remove('hidden');
|
|
354
|
+
$('composer-shell').classList.remove('hidden');
|
|
355
|
+
$('prompt').value=state.promptDrafts[detail.sessionId]||'';
|
|
356
|
+
$('session-model').value=draft.model;
|
|
357
|
+
$('session-sub-model').value=draft.subModel;
|
|
358
|
+
$('session-effort').value=draft.effort;
|
|
359
|
+
renderAttachmentDrafts();
|
|
360
|
+
var html='<div class="thread">';
|
|
361
|
+
if(!(detail.messages||[]).length&&detail.status!=='running')html+='<div class="empty-thread"><strong>准备开始这个会话</strong><span>在下方输入任务,可附加截图。DeepCCC 会读取当前项目指令、调用工具并持续保存上下文。</span></div>';
|
|
362
|
+
(detail.messages||[]).forEach(function(message,index){html+=messageHtml(message.role,message.content,message.toolCalls,index,message.timeline);});
|
|
363
|
+
if(detail.status==='running'){
|
|
364
|
+
var currentTimeline=liveTimeline(detail.events);
|
|
365
|
+
if(currentTimeline.length)html+=messageHtml('assistant','',[],'live',currentTimeline);
|
|
366
|
+
}
|
|
367
|
+
var approvals=(detail.approvals||[]).slice();
|
|
368
|
+
if(detail.pendingApproval&&!approvals.some(function(item){return item.approvalId===detail.pendingApproval.approvalId;}))approvals.push(Object.assign({status:'pending'},detail.pendingApproval));
|
|
369
|
+
approvals.forEach(function(approval){html+=approvalHtml(approval);});
|
|
370
|
+
if(detail.status==='running'){
|
|
371
|
+
var lastAgent=(detail.events||[]).slice().reverse().find(function(event){return event.type==='agent';});
|
|
372
|
+
var runningLabel=lastAgent&&lastAgent.data&&lastAgent.data.type==='progress'?'Agent 正在思考…':'Agent 正在工作';
|
|
373
|
+
html+='<div class="run-state"><i class="pulse"></i>'+runningLabel+'</div>';
|
|
374
|
+
}
|
|
375
|
+
html+='</div>';
|
|
376
|
+
contentEl.innerHTML=html;
|
|
377
|
+
$('send').classList.toggle('hidden',detail.status==='running');
|
|
378
|
+
$('stop').classList.toggle('hidden',detail.status!=='running');
|
|
379
|
+
$('prompt').disabled=detail.status==='running';
|
|
380
|
+
$('session-model').disabled=detail.status==='running';
|
|
381
|
+
$('session-sub-model').disabled=detail.status==='running';
|
|
382
|
+
$('session-effort').disabled=detail.status==='running';
|
|
383
|
+
$('attach-image').disabled=detail.status==='running'||state.uploading;
|
|
384
|
+
renderWarning();
|
|
385
|
+
requestAnimationFrame(function(){contentEl.scrollTop=stickToBottom?contentEl.scrollHeight:previousScrollTop;});
|
|
386
|
+
}
|
|
73
387
|
function cwdKey(value){var raw=String(value||'');var normalized=raw.replace(/\\\\/g,'/').replace(new RegExp('/+$'),'');var windowsStyle=new RegExp('^[a-z]:/','i').test(normalized)||raw.indexOf(String.fromCharCode(92))>=0;return windowsStyle?normalized.toLowerCase():normalized;}
|
|
74
388
|
function renderWarning(){if(!state.detail)return;var selectedCwd=cwdKey(state.detail.cwd);var conflicts=state.sessions.filter(function(item){return item.sessionId!==state.detail.sessionId&&item.status==='running'&&cwdKey(item.cwd)===selectedCwd;});var el=$('warning');if(conflicts.length){el.textContent='⚠ 当前目录还有 '+conflicts.length+' 个 Agent 会话正在运行。多个会话直接修改同一目录,可能产生覆盖或冲突。';el.classList.remove('hidden');}else el.classList.add('hidden');}
|
|
75
389
|
async function loadSessions(){var result=await api('/api/sessions');state.sessions=result.sessions||[];if(state.selectedId&&!state.sessions.some(function(item){return item.sessionId===state.selectedId;}))clearSelection('会话已在其他页面删除');renderSessions();renderWarning();}
|
|
76
390
|
async function loadDetail(id,token){var sessionId=id||state.selectedId;if(!sessionId)return false;var requestToken=token===undefined?++state.detailRequestToken:token;var result=await api('/api/sessions/'+encodeURIComponent(sessionId));if(state.selectedId!==sessionId||requestToken!==state.detailRequestToken)return false;await loadSessions();if(state.selectedId!==sessionId||requestToken!==state.detailRequestToken)return false;state.detail=result.session;renderDetail();return true;}
|
|
77
|
-
function
|
|
391
|
+
function scheduleRender(){if(state.renderQueued)return;state.renderQueued=true;requestAnimationFrame(function(){state.renderQueued=false;if(state.detail)renderDetail();});}
|
|
78
392
|
function scheduleSessions(){clearTimeout(state.sessionsReloadTimer);state.sessionsReloadTimer=setTimeout(function(){loadSessions().catch(function(error){toast(error.message,true);});},80);}
|
|
79
|
-
|
|
393
|
+
function handleSessionEvent(msg){var update;try{update=JSON.parse(msg.data);}catch(error){return;}if(update.sessionId!==state.selectedId||!state.detail)return;if(update.type==='agent'||update.type==='run_started'){state.detail.events=(state.detail.events||[]).concat([update]).slice(-500);if(update.type==='run_started')state.detail.status='running';scheduleRender();}else if(update.type==='run_finished'){state.detail.events=(state.detail.events||[]).concat([update]).slice(-500);state.detail.status='idle';loadDetail(state.selectedId).catch(function(error){toast(error.message,true);});}else if(update.type==='approval'||update.type==='approval_resolved'||update.type==='session_updated'){loadDetail(state.selectedId).catch(function(error){toast(error.message,true);});}}
|
|
394
|
+
async function selectSession(id){state.selectedId=id;state.detail=null;var token=++state.detailRequestToken;renderSessions();$('composer-shell').classList.add('hidden');$('content').innerHTML='<div class="welcome"><div class="welcome-card"><div class="welcome-mark">✦</div><h2>正在加载会话</h2><p>正在读取消息、工具记录与运行状态…</p></div></div>';if(state.eventSource)state.eventSource.close();state.eventSource=new EventSource('/api/sessions/'+encodeURIComponent(id)+'/events');state.eventSource.onmessage=handleSessionEvent;await loadDetail(id,token);}
|
|
80
395
|
async function loadConfig(){var result=await api('/api/config');state.config=result.config;$('cfg-provider').value=state.config.provider;$('cfg-base-url').value=state.config.baseURL;$('cfg-model').value=state.config.model;$('cfg-sub-model').value=state.config.subModel||'';$('cfg-effort').value=state.config.effort||'';$('cfg-context').value=state.config.contextWindow;$('cfg-max-output').value=state.config.maxOutputTokens||'';$('cfg-streaming').checked=state.config.streaming!==false;$('cfg-port').value=state.config.web.port;$('cfg-open').checked=state.config.web.openOnStart!==false;$('server-port').textContent=state.config.web.port;$('key-state').textContent=state.config.apiKeyConfigured?'当前 API Key:'+state.config.apiKeyMask:'尚未配置 API Key';$('new-cwd').value=state.config.defaultCwd||'';$('new-model').value=state.config.model;$('new-sub-model').value=state.config.subModel||'';var models=[state.config.model,state.config.subModel].concat(state.sessions.reduce(function(all,item){return all.concat([item.model,item.subModel]);},[])).filter(Boolean).filter(function(value,index,array){return array.indexOf(value)===index;});$('recent-models').innerHTML=models.map(function(model){return '<option value="'+escapeHtml(model)+'">';}).join('');}
|
|
81
396
|
$('new-session').addEventListener('click',function(){openModal('new-modal');setTimeout(function(){$('new-cwd').focus();},0);});$('open-settings').addEventListener('click',function(){loadConfig().then(function(){openModal('settings-modal');}).catch(function(error){toast(error.message,true);});});document.querySelectorAll('[data-close]').forEach(function(button){button.addEventListener('click',function(){closeModal(button.dataset.close);});});
|
|
82
397
|
$('new-form').addEventListener('submit',async function(event){event.preventDefault();try{var result=await api('/api/sessions',{method:'POST',body:JSON.stringify({cwd:$('new-cwd').value,title:$('new-title').value,model:$('new-model').value,subModel:$('new-sub-model').value,effort:$('new-effort').value})});closeModal('new-modal');$('new-title').value='';await loadSessions();await selectSession(result.session.sessionId);}catch(error){toast(error.message,true);}});
|
|
@@ -84,13 +399,14 @@ export const DEEPCCC_WEB_PAGE = `<!DOCTYPE html>
|
|
|
84
399
|
async function uploadAttachment(sessionId,file){var response=await fetch('/api/sessions/'+encodeURIComponent(sessionId)+'/attachments?name='+encodeURIComponent(file.name||'image'),{method:'POST',headers:{'content-type':file.type||'application/octet-stream'},body:file});var result=await response.json().catch(function(){return{error:'上传响应不是有效 JSON'};});if(!response.ok)throw new Error(result.error||('HTTP '+response.status));return result.attachment;}
|
|
85
400
|
async function handleAttachmentFiles(files){var sessionId=state.selectedId;if(!sessionId||state.uploading)return;var candidates=[].slice.call(files||[]).filter(function(file){return ['image/png','image/jpeg','image/webp'].includes(file.type);});var existing=state.attachmentDrafts[sessionId]||[];if(existing.length+candidates.length>10){toast('每条消息最多添加 10 张图片',true);return;}if(!candidates.length){toast('仅支持 PNG、JPEG、WebP 图片',true);return;}state.uploading=true;if(state.detail)renderDetail();try{for(var i=0;i<candidates.length;i++){if(candidates[i].size>20*1024*1024)throw new Error('单张图片不能超过 20 MB:'+candidates[i].name);existing.push(await uploadAttachment(sessionId,candidates[i]));state.attachmentDrafts[sessionId]=existing;renderAttachmentDrafts();}}catch(error){toast(error.message,true);}finally{state.uploading=false;if(state.detail)renderDetail();$('attachment-input').value='';}}
|
|
86
401
|
async function removeAttachment(id){var sessionId=state.selectedId;if(!sessionId)return;try{await api('/api/sessions/'+encodeURIComponent(sessionId)+'/attachments/'+encodeURIComponent(id),{method:'DELETE'});state.attachmentDrafts[sessionId]=(state.attachmentDrafts[sessionId]||[]).filter(function(item){return item.attachmentId!==id;});renderAttachmentDrafts();}catch(error){toast(error.message,true);}}
|
|
87
|
-
async function send(){var sessionId=state.selectedId;var text=$('prompt').value;var attachments=currentAttachments();if(!sessionId||(!text.trim()&&!attachments.length)||state.uploading)return;try{var patch={model:$('session-model').value,subModel:$('session-sub-model').value,effort:$('session-effort').value};if(patch.model!==state.detail.model||patch.subModel!==state.detail.subModel||patch.effort!==state.detail.effort){var updated=await api('/api/sessions/'+encodeURIComponent(sessionId),{method:'PATCH',body:JSON.stringify(patch)});if(state.selectedId===sessionId){state.detail=Object.assign({},state.detail,updated.session);renderDetail();}await loadSessions();}await api('/api/sessions/'+encodeURIComponent(sessionId)+'/messages',{method:'POST',body:JSON.stringify({text:text,attachmentIds:attachments.map(function(item){return item.attachmentId;})})});delete state.configDrafts[sessionId];delete state.promptDrafts[sessionId];delete state.attachmentDrafts[sessionId];if(state.selectedId===sessionId){$('prompt').value='';renderAttachmentDrafts();}var token=++state.detailRequestToken;await loadDetail(sessionId,token);}catch(error){toast(error.message,true);}}
|
|
402
|
+
async function send(){var sessionId=state.selectedId;var text=$('prompt').value;var attachments=currentAttachments();if(!sessionId||(!text.trim()&&!attachments.length)||state.uploading)return;try{var patch={model:$('session-model').value,subModel:$('session-sub-model').value,effort:$('session-effort').value};if(patch.model!==state.detail.model||patch.subModel!==state.detail.subModel||patch.effort!==state.detail.effort){var updated=await api('/api/sessions/'+encodeURIComponent(sessionId),{method:'PATCH',body:JSON.stringify(patch)});if(state.selectedId===sessionId){state.detail=Object.assign({},state.detail,updated.session);renderDetail();}await loadSessions();}await api('/api/sessions/'+encodeURIComponent(sessionId)+'/messages',{method:'POST',body:JSON.stringify({text:text,attachmentIds:attachments.map(function(item){return item.attachmentId;})})});delete state.configDrafts[sessionId];delete state.promptDrafts[sessionId];delete state.attachmentDrafts[sessionId];if(state.selectedId===sessionId){$('prompt').value='';renderAttachmentDrafts();state.detail.events=[];state.detail.status='running';renderDetail();}var token=++state.detailRequestToken;await loadDetail(sessionId,token);}catch(error){toast(error.message,true);}}
|
|
88
403
|
['session-model','session-sub-model','session-effort'].forEach(function(id){$(id).addEventListener('input',rememberDraft);$(id).addEventListener('change',rememberDraft);});
|
|
89
404
|
$('attach-image').addEventListener('click',function(){$('attachment-input').click();});$('attachment-input').addEventListener('change',function(){handleAttachmentFiles(this.files);});$('attachment-drafts').addEventListener('click',function(event){var button=event.target.closest('[data-remove-attachment]');if(button)removeAttachment(button.dataset.removeAttachment);});document.addEventListener('paste',function(event){var files=[].slice.call(event.clipboardData&&event.clipboardData.files||[]).filter(function(file){return file.type.indexOf('image/')===0;});if(files.length){event.preventDefault();handleAttachmentFiles(files);}});$('composer').addEventListener('dragover',function(event){event.preventDefault();this.classList.add('dragging');});$('composer').addEventListener('dragleave',function(){this.classList.remove('dragging');});$('composer').addEventListener('drop',function(event){event.preventDefault();this.classList.remove('dragging');handleAttachmentFiles(event.dataTransfer.files);});
|
|
90
405
|
$('send').addEventListener('click',send);$('prompt').addEventListener('input',rememberPromptDraft);$('prompt').addEventListener('keydown',function(event){if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send();}});$('stop').addEventListener('click',async function(){try{await api('/api/sessions/'+encodeURIComponent(state.selectedId)+'/stop',{method:'POST',body:'{}'});toast('已请求停止');}catch(error){toast(error.message,true);}});
|
|
91
|
-
$('rename-session').addEventListener('click',function(){$('rename-title').value=state.detail.title;openModal('rename-modal');});$('rename-form').addEventListener('submit',async function(event){event.preventDefault();try{await api('/api/sessions/'+encodeURIComponent(state.selectedId),{method:'PATCH',body:JSON.stringify({title:$('rename-title').value})});closeModal('rename-modal');var token=++state.detailRequestToken;await loadDetail(state.selectedId,token);}catch(error){toast(error.message,true);}});$('delete-session').addEventListener('click',async function(){if(!confirm('删除这个会话及其本地历史?不会删除工作目录中的文件。'))return;try{var deletedId=state.selectedId;await api('/api/sessions/'+encodeURIComponent(deletedId),{method:'DELETE'});delete state.configDrafts[deletedId];delete state.promptDrafts[deletedId];delete state.attachmentDrafts[deletedId];clearSelection('会话已删除');await loadSessions();}catch(error){toast(error.message,true);}});
|
|
92
|
-
$('content').addEventListener('
|
|
93
|
-
function
|
|
406
|
+
$('rename-session').addEventListener('click',function(){$('rename-title').value=state.detail.title;openModal('rename-modal');});$('rename-form').addEventListener('submit',async function(event){event.preventDefault();try{await api('/api/sessions/'+encodeURIComponent(state.selectedId),{method:'PATCH',body:JSON.stringify({title:$('rename-title').value})});closeModal('rename-modal');var token=++state.detailRequestToken;await loadDetail(state.selectedId,token);}catch(error){toast(error.message,true);}});$('delete-session').addEventListener('click',async function(){if(!confirm('删除这个会话及其本地历史?不会删除工作目录中的文件。'))return;try{var deletedId=state.selectedId;await api('/api/sessions/'+encodeURIComponent(deletedId),{method:'DELETE'});delete state.configDrafts[deletedId];delete state.promptDrafts[deletedId];delete state.attachmentDrafts[deletedId];clearToolSessionState(deletedId);clearSelection('会话已删除');await loadSessions();}catch(error){toast(error.message,true);}});
|
|
407
|
+
$('content').addEventListener('toggle',function(event){var card=event.target.closest&&event.target.closest('details.tool-card[data-tool-state]');if(card)setToolState(card.dataset.toolState,card.open);},true);
|
|
408
|
+
$('content').addEventListener('click',async function(event){var overflow=event.target.closest('[data-tool-overflow]');if(overflow){setToolState(overflow.dataset.toolOverflow,!toolState(overflow.dataset.toolOverflow));renderDetail();return;}var button=event.target.closest('[data-approval-answer]');if(!button)return;button.disabled=true;try{await api('/api/approvals/'+encodeURIComponent(button.dataset.approvalId),{method:'POST',body:JSON.stringify({answer:button.dataset.approvalAnswer})});var token=++state.detailRequestToken;await loadDetail(state.selectedId,token);}catch(error){button.disabled=false;toast(error.message,true);}});
|
|
409
|
+
function handleGlobalEvent(event){try{var update=JSON.parse(event.data);if(update.type==='session_deleted'){delete state.configDrafts[update.sessionId];delete state.promptDrafts[update.sessionId];delete state.attachmentDrafts[update.sessionId];clearToolSessionState(update.sessionId);}}catch(error){}scheduleSessions();}
|
|
94
410
|
state.globalEventSource=new EventSource('/api/events');state.globalEventSource.onmessage=handleGlobalEvent;
|
|
95
411
|
Promise.all([loadSessions(),loadConfig()]).then(function(){if(state.sessions[0])return selectSession(state.sessions[0].sessionId);}).catch(function(error){toast(error.message,true);});
|
|
96
412
|
})();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface WebToolSummaryRule {
|
|
2
|
+
emoji: string;
|
|
3
|
+
inputFields: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare const WEB_TOOL_SUMMARY_RULES: Record<string, WebToolSummaryRule>;
|
|
6
|
+
export interface ToolSummaryInput {
|
|
7
|
+
name: string;
|
|
8
|
+
input?: unknown;
|
|
9
|
+
output?: unknown;
|
|
10
|
+
pending?: boolean;
|
|
11
|
+
isError?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface TruncatedToolPayload {
|
|
14
|
+
full: string;
|
|
15
|
+
preview: string;
|
|
16
|
+
omittedLines: number;
|
|
17
|
+
truncated: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function buildWebToolSummary(call: ToolSummaryInput): string;
|
|
20
|
+
export declare function truncateToolPayload(value: unknown, headLines: number, tailLines: number, maxLineChars?: number): TruncatedToolPayload;
|
|
21
|
+
export declare function formatToolPayload(value: unknown): string;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export const WEB_TOOL_SUMMARY_RULES = {
|
|
2
|
+
read_file: { emoji: "📖", inputFields: ["path"] },
|
|
3
|
+
list_dir: { emoji: "📂", inputFields: ["path"] },
|
|
4
|
+
search_code: { emoji: "🔎", inputFields: ["query", "path"] },
|
|
5
|
+
run_command: { emoji: "🖥️", inputFields: ["command"] },
|
|
6
|
+
edit_file: { emoji: "✏️", inputFields: ["path"] },
|
|
7
|
+
create_file: { emoji: "✍️", inputFields: ["path"] },
|
|
8
|
+
delete_file: { emoji: "🗑️", inputFields: ["path"] },
|
|
9
|
+
move_file: { emoji: "📦", inputFields: ["sourcePath", "destinationPath"] },
|
|
10
|
+
apply_patch: { emoji: "📋", inputFields: [] },
|
|
11
|
+
task: { emoji: "🤖", inputFields: ["description", "cwd"] },
|
|
12
|
+
websearch: { emoji: "🌐", inputFields: ["query"] },
|
|
13
|
+
webfetch: { emoji: "📥", inputFields: ["url"] },
|
|
14
|
+
session_search: { emoji: "🗂️", inputFields: ["query", "session_id"] },
|
|
15
|
+
present_file: { emoji: "🖼️", inputFields: ["path"] },
|
|
16
|
+
};
|
|
17
|
+
export function buildWebToolSummary(call) {
|
|
18
|
+
const rule = WEB_TOOL_SUMMARY_RULES[call.name] ?? { emoji: "🔧", inputFields: [] };
|
|
19
|
+
const mark = call.pending ? "…" : call.isError ? "✗" : "✓";
|
|
20
|
+
const input = asRecord(call.input);
|
|
21
|
+
const details = rule.inputFields
|
|
22
|
+
.map((field) => oneLine(input[field]))
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
const result = call.pending ? "" : resultSummary(call.name, call.output, !!call.isError);
|
|
25
|
+
return [rule.emoji, call.name || "tool", mark, [...details, result].filter(Boolean).join(" · ")]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join(" ")
|
|
28
|
+
.slice(0, 420);
|
|
29
|
+
}
|
|
30
|
+
export function truncateToolPayload(value, headLines, tailLines, maxLineChars = 240) {
|
|
31
|
+
const full = formatToolPayload(value);
|
|
32
|
+
const lines = full.split("\n");
|
|
33
|
+
const keep = Math.max(0, headLines) + Math.max(0, tailLines);
|
|
34
|
+
const omittedLines = Math.max(0, lines.length - keep);
|
|
35
|
+
const selected = omittedLines > 0
|
|
36
|
+
? [
|
|
37
|
+
...lines.slice(0, Math.max(0, headLines)),
|
|
38
|
+
`… 已省略 ${omittedLines} 行`,
|
|
39
|
+
...lines.slice(-Math.max(0, tailLines)),
|
|
40
|
+
]
|
|
41
|
+
: lines;
|
|
42
|
+
let clipped = false;
|
|
43
|
+
const preview = selected.map((line) => {
|
|
44
|
+
if (line.length <= maxLineChars)
|
|
45
|
+
return line;
|
|
46
|
+
clipped = true;
|
|
47
|
+
return `${line.slice(0, Math.max(0, maxLineChars - 1))}…`;
|
|
48
|
+
}).join("\n");
|
|
49
|
+
return {
|
|
50
|
+
full,
|
|
51
|
+
preview,
|
|
52
|
+
omittedLines,
|
|
53
|
+
truncated: omittedLines > 0 || clipped,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function formatToolPayload(value) {
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
return "";
|
|
59
|
+
if (value === null)
|
|
60
|
+
return "null";
|
|
61
|
+
if (typeof value === "string") {
|
|
62
|
+
const parsed = tryJson(value);
|
|
63
|
+
if (parsed !== undefined)
|
|
64
|
+
return JSON.stringify(parsed, null, 2);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(value, null, 2);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return String(value);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function resultSummary(name, raw, isError) {
|
|
75
|
+
const output = asRecord(raw);
|
|
76
|
+
if (isError)
|
|
77
|
+
return oneLine(output.message ?? raw) || "失败";
|
|
78
|
+
if (typeof output.exitCode === "number")
|
|
79
|
+
return `exit ${output.exitCode}`;
|
|
80
|
+
if (Array.isArray(output.entries))
|
|
81
|
+
return `${output.entries.length} 项`;
|
|
82
|
+
if (Array.isArray(output.matches))
|
|
83
|
+
return `${output.matches.length} 条匹配`;
|
|
84
|
+
if (Array.isArray(output.changedFiles))
|
|
85
|
+
return `${output.changedFiles.length} 个文件`;
|
|
86
|
+
if (Array.isArray(output.results))
|
|
87
|
+
return `${output.results.length} 条结果`;
|
|
88
|
+
if (typeof output.size === "number")
|
|
89
|
+
return formatBytes(output.size);
|
|
90
|
+
if (name === "task" && typeof output.result === "string")
|
|
91
|
+
return "已返回结果";
|
|
92
|
+
return "完成";
|
|
93
|
+
}
|
|
94
|
+
function asRecord(value) {
|
|
95
|
+
const parsed = typeof value === "string" ? tryJson(value) : value;
|
|
96
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
97
|
+
? parsed
|
|
98
|
+
: {};
|
|
99
|
+
}
|
|
100
|
+
function tryJson(value) {
|
|
101
|
+
const trimmed = value.trim();
|
|
102
|
+
if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("[")))
|
|
103
|
+
return undefined;
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(trimmed);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function oneLine(value) {
|
|
112
|
+
if (value === undefined || value === null)
|
|
113
|
+
return "";
|
|
114
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
115
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 220);
|
|
116
|
+
}
|
|
117
|
+
function formatBytes(value) {
|
|
118
|
+
if (value < 1024)
|
|
119
|
+
return `${value} B`;
|
|
120
|
+
if (value < 1024 * 1024)
|
|
121
|
+
return `${(value / 1024).toFixed(1)} KB`;
|
|
122
|
+
return `${(value / 1024 / 1024).toFixed(1)} MB`;
|
|
123
|
+
}
|