pi-web-ui 0.63.2 → 0.63.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/agent-service.js +220 -3
- package/dist/server/client-state.js +18 -0
- package/dist/server/dsh/dsh-agent-service.js +21 -0
- package/dist/server/index.js +11 -0
- package/dist/server/marker-service.js +270 -0
- package/dist/server/markers/builtins/notify.js +20 -0
- package/dist/server/markers/builtins/rename.js +102 -0
- package/dist/server/markers/builtins/services.js +119 -0
- package/dist/server/markers/builtins/todo.js +130 -0
- package/dist/server/markers/index.js +24 -0
- package/dist/server/markers/marker.js +53 -0
- package/dist/server/markers/registry.js +28 -0
- package/dist/server/markers/store.js +47 -0
- package/dist/server/settings-service.js +3 -0
- package/dist/server/slash-commands.js +34 -0
- package/dist/server/terminals.js +11 -0
- package/package.json +1 -1
- package/web/dist/assets/TerminalPanel-BFrV6B8W.js +2 -0
- package/web/dist/assets/index-BFoSybNe.js +324 -0
- package/web/dist/assets/index-D9G_7fPE.css +10 -0
- package/web/dist/icons/icon-1024.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/maskable-1024.png +0 -0
- package/web/dist/icons/maskable-192.png +0 -0
- package/web/dist/icons/maskable-512.png +0 -0
- package/web/dist/index.html +20 -14
- package/web/dist/manifest.webmanifest +50 -0
- package/web/dist/sw.js +126 -0
- package/web/public/icons/icon-1024.png +0 -0
- package/web/public/icons/icon-192.png +0 -0
- package/web/public/icons/icon-512.png +0 -0
- package/web/public/icons/maskable-1024.png +0 -0
- package/web/public/icons/maskable-192.png +0 -0
- package/web/public/icons/maskable-512.png +0 -0
- package/web/public/manifest.webmanifest +50 -0
- package/web/public/sw.js +126 -0
- package/web/dist/assets/TerminalPanel-BK0SyRCf.js +0 -2
- package/web/dist/assets/index-BPbGqUpD.js +0 -323
- package/web/dist/assets/index-CrDJOsa5.css +0 -10
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtins/notify.ts — 纯提醒标记。
|
|
3
|
+
*/
|
|
4
|
+
export const notifyMarker = {
|
|
5
|
+
name: "notify",
|
|
6
|
+
guidance: [
|
|
7
|
+
"- [[notify:<级别>:<内容>]] 仅向用户显示一个非打断性提醒,不会进入正文。级别为 info|warning|success|error。",
|
|
8
|
+
],
|
|
9
|
+
async apply(token, ctx) {
|
|
10
|
+
const level = token.op || token.kwargs["level"] || "info";
|
|
11
|
+
const text = token.kwargs["text"] || token.args.join(" ") || "";
|
|
12
|
+
if (!text)
|
|
13
|
+
return { applied: false, error: "notify 需要内容" };
|
|
14
|
+
const safe = (level === "warning" || level === "error" ? level : "info");
|
|
15
|
+
ctx.notify(text, safe);
|
|
16
|
+
return { applied: true, feedback: "notified" };
|
|
17
|
+
},
|
|
18
|
+
overlay: undefined,
|
|
19
|
+
init: () => undefined,
|
|
20
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtins/rename.ts — 重命名当前对话标记。
|
|
3
|
+
*
|
|
4
|
+
* 需求:加个重命名当前对话 marker。
|
|
5
|
+
*
|
|
6
|
+
* 语法:
|
|
7
|
+
* [[conv:rename:<新标题>]] 重命名当前对话
|
|
8
|
+
* [[rename:set:<新标题>]] 同上(兼容别名)
|
|
9
|
+
* [[title:rename:<新标题>]] 同上
|
|
10
|
+
* [[rename:new:<新标题>]] 同上
|
|
11
|
+
*
|
|
12
|
+
* 兼容:rename 前缀的任意 op 都会被视为重命名,例如 [[rename:set:My Chat]]、
|
|
13
|
+
* [[rename:to:My Chat]]。标题来自 args[0] + kwargs[text/name/title] 的
|
|
14
|
+
* 拼接,未提供则报错。
|
|
15
|
+
*
|
|
16
|
+
* 持久化:通过宿主回调直接改对话标题(内存 + 磁盘 transcript session_info),
|
|
17
|
+
* 不需要额外状态。
|
|
18
|
+
*/
|
|
19
|
+
export const RENAME_NAMESPACE = "conv";
|
|
20
|
+
function extractTitle(token) {
|
|
21
|
+
// args[0] 是主标题;kwargs 兼容 text/name/title
|
|
22
|
+
const fromArgs = token.args.join(" ").trim();
|
|
23
|
+
const fromKw = (token.kwargs["text"] ?? token.kwargs["name"] ?? token.kwargs["title"] ?? "").trim();
|
|
24
|
+
if (fromArgs && fromKw)
|
|
25
|
+
return `${fromArgs} ${fromKw}`.trim();
|
|
26
|
+
return fromArgs || fromKw;
|
|
27
|
+
}
|
|
28
|
+
export const renameMarker = {
|
|
29
|
+
name: "conv",
|
|
30
|
+
guidance: [
|
|
31
|
+
"- 重命名当前对话:[[conv:rename:<新标题>]](或 [[rename:set:<标题>]])。标题尽量简短、能概括本次任务。首次回复用户问题后根据情况重命名当前会话",
|
|
32
|
+
],
|
|
33
|
+
async apply(token, ctx) {
|
|
34
|
+
if (token.op !== "rename") {
|
|
35
|
+
return { applied: false, error: `conv 未知操作: ${token.op}(当前仅支持 conv:rename)` };
|
|
36
|
+
}
|
|
37
|
+
const title = extractTitle(token);
|
|
38
|
+
if (!title)
|
|
39
|
+
return { applied: false, error: "conv:rename 需要一个标题参数 [[conv:rename:<新标题>]]" };
|
|
40
|
+
if (title.length > 80)
|
|
41
|
+
return { applied: false, error: "标题过长(最多 80 字)" };
|
|
42
|
+
if (!ctx.renameConversation)
|
|
43
|
+
return { applied: false, error: "当前环境不支持重命名" };
|
|
44
|
+
try {
|
|
45
|
+
ctx.renameConversation(title);
|
|
46
|
+
ctx.notify(`已重命名为:${title}`, "info");
|
|
47
|
+
return { applied: true, feedback: `renamed to "${title}"` };
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return { applied: false, error: `重命名失败: ${e.message ?? String(e)}` };
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
overlay: undefined,
|
|
54
|
+
init: () => undefined,
|
|
55
|
+
};
|
|
56
|
+
/** 别名:[[rename:set:标题]] 等同 [[conv:rename:标题]],方便模型直觉书写。 */
|
|
57
|
+
export const renameAliasMarker = {
|
|
58
|
+
name: "rename",
|
|
59
|
+
guidance: [
|
|
60
|
+
"- [[rename:set:<新标题>]] 同 [[conv:rename:<新标题>]]:重命名当前对话。",
|
|
61
|
+
],
|
|
62
|
+
async apply(token, ctx) {
|
|
63
|
+
// 兼容任意 op:只要能取到标题就重命名
|
|
64
|
+
const title = extractTitle(token) || token.op?.trim() || "";
|
|
65
|
+
// 若 token 是 [[rename:My Title:]] 形式,op=My Title, args 空 —— 用 op 当标题
|
|
66
|
+
const effective = title || token.op;
|
|
67
|
+
if (!effective?.trim())
|
|
68
|
+
return { applied: false, error: "rename 需要标题参数 [[rename:set:<新标题>]]" };
|
|
69
|
+
const trimmed = effective.trim().slice(0, 80);
|
|
70
|
+
if (!ctx.renameConversation)
|
|
71
|
+
return { applied: false, error: "当前环境不支持重命名" };
|
|
72
|
+
try {
|
|
73
|
+
ctx.renameConversation(trimmed);
|
|
74
|
+
ctx.notify(`已重命名为:${trimmed}`, "info");
|
|
75
|
+
return { applied: true, feedback: `renamed to "${trimmed}"` };
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
return { applied: false, error: `重命名失败: ${e.message ?? String(e)}` };
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
overlay: undefined,
|
|
82
|
+
init: () => undefined,
|
|
83
|
+
};
|
|
84
|
+
/** title 前缀别名:[[title:rename:标题]] */
|
|
85
|
+
export const titleAliasMarker = {
|
|
86
|
+
name: "title",
|
|
87
|
+
guidance: [],
|
|
88
|
+
async apply(token, ctx) {
|
|
89
|
+
if (token.op !== "rename")
|
|
90
|
+
return { applied: false, error: `title 未知操作: ${token.op}` };
|
|
91
|
+
const title = extractTitle(token);
|
|
92
|
+
if (!title)
|
|
93
|
+
return { applied: false, error: "title:rename 需要标题" };
|
|
94
|
+
if (!ctx.renameConversation)
|
|
95
|
+
return { applied: false, error: "当前环境不支持重命名" };
|
|
96
|
+
ctx.renameConversation(title.slice(0, 80));
|
|
97
|
+
ctx.notify(`已重命名为:${title.slice(0, 80)}`, "info");
|
|
98
|
+
return { applied: true, feedback: `renamed` };
|
|
99
|
+
},
|
|
100
|
+
overlay: undefined,
|
|
101
|
+
init: () => undefined,
|
|
102
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtins/services.ts — 后台服务登记(复刻 pi-marker-tools)。
|
|
3
|
+
*/
|
|
4
|
+
export const SVC_NAMESPACE = "svc";
|
|
5
|
+
export function initServiceState() {
|
|
6
|
+
return { services: [], nextId: 1 };
|
|
7
|
+
}
|
|
8
|
+
function parseId(raw) {
|
|
9
|
+
if (raw === undefined)
|
|
10
|
+
return null;
|
|
11
|
+
const n = Number(raw);
|
|
12
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
13
|
+
}
|
|
14
|
+
function toInt(raw) {
|
|
15
|
+
if (raw === undefined || raw === "")
|
|
16
|
+
return undefined;
|
|
17
|
+
const n = Number(raw);
|
|
18
|
+
return Number.isInteger(n) && n >= 0 ? n : undefined;
|
|
19
|
+
}
|
|
20
|
+
function findService(state, id) {
|
|
21
|
+
return state.services.find((s) => s.id === id);
|
|
22
|
+
}
|
|
23
|
+
function lineOf(s) {
|
|
24
|
+
const meta = [s.pid !== undefined ? `pid ${s.pid}` : "", s.port !== undefined ? `:${s.port}` : ""].filter(Boolean).join(" ");
|
|
25
|
+
return `${s.stopped ? "[x]" : "[ ]"} #${s.id}: ${s.name}${meta ? ` (${meta})` : ""}${s.command ? ` — ${s.command}` : ""}`;
|
|
26
|
+
}
|
|
27
|
+
export function describeServices(state) {
|
|
28
|
+
if (!state || state.services.length === 0)
|
|
29
|
+
return "[svc] (空)";
|
|
30
|
+
return state.services.map(lineOf).join("\n");
|
|
31
|
+
}
|
|
32
|
+
export const servicesMarker = {
|
|
33
|
+
name: "svc",
|
|
34
|
+
guidance: [
|
|
35
|
+
"- 后台服务登记(todo 风格):[[svc:add:名称,pid=,port=,cmd=]] 登记;[[svc:stop:<id>]] 标记停止;[[svc:resume:<id>]] 标记运行;[[svc:remove:<id>]] 删除;[[svc:clear:]] 清空。",
|
|
36
|
+
"- 用 bash 启动后台服务(nohup ... &、pm2 start、docker run -d 等)后,立即写 [[svc:add:...]] 登记,尽量带 pid= 和 port=;服务退出或被关闭后写 [[svc:stop:<id>]] 保持清单准确。",
|
|
37
|
+
],
|
|
38
|
+
async apply(token, _ctx, _state) {
|
|
39
|
+
const state = _state;
|
|
40
|
+
const op = token.op;
|
|
41
|
+
switch (op) {
|
|
42
|
+
case "add": {
|
|
43
|
+
const name = token.args[0]?.trim();
|
|
44
|
+
if (!name)
|
|
45
|
+
return { applied: false, error: "svc:add 需要一个名称参数 [[svc:add:<名称>,pid=,port=,cmd=]]" };
|
|
46
|
+
const svc = {
|
|
47
|
+
id: state.nextId++,
|
|
48
|
+
name,
|
|
49
|
+
command: token.kwargs["cmd"],
|
|
50
|
+
pid: toInt(token.kwargs["pid"]),
|
|
51
|
+
port: toInt(token.kwargs["port"]),
|
|
52
|
+
startedAt: Date.now(),
|
|
53
|
+
stopped: false,
|
|
54
|
+
};
|
|
55
|
+
state.services.push(svc);
|
|
56
|
+
const meta = [svc.pid !== undefined ? `pid ${svc.pid}` : "", svc.port !== undefined ? `:${svc.port}` : ""].filter(Boolean).join(" ");
|
|
57
|
+
return { applied: true, feedback: `Registered #${svc.id}: ${name}${meta ? ` (${meta})` : ""}` };
|
|
58
|
+
}
|
|
59
|
+
case "stop": {
|
|
60
|
+
const id = parseId(token.args[0]);
|
|
61
|
+
if (id === null)
|
|
62
|
+
return { applied: false, error: `svc:stop 的 id 无效: "${token.args[0] ?? ""}"` };
|
|
63
|
+
const svc = findService(state, id);
|
|
64
|
+
if (!svc)
|
|
65
|
+
return { applied: false, error: `svc:stop 服务 #${id} 不存在` };
|
|
66
|
+
svc.stopped = true;
|
|
67
|
+
svc.stoppedAt = Date.now();
|
|
68
|
+
return { applied: true, feedback: `#${id} ${svc.name} marked stopped` };
|
|
69
|
+
}
|
|
70
|
+
case "resume": {
|
|
71
|
+
const id = parseId(token.args[0]);
|
|
72
|
+
if (id === null)
|
|
73
|
+
return { applied: false, error: `svc:resume 的 id 无效: "${token.args[0] ?? ""}"` };
|
|
74
|
+
const svc = findService(state, id);
|
|
75
|
+
if (!svc)
|
|
76
|
+
return { applied: false, error: `svc:resume 服务 #${id} 不存在` };
|
|
77
|
+
svc.stopped = false;
|
|
78
|
+
svc.stoppedAt = undefined;
|
|
79
|
+
return { applied: true, feedback: `#${id} ${svc.name} marked running` };
|
|
80
|
+
}
|
|
81
|
+
case "remove": {
|
|
82
|
+
const id = parseId(token.args[0]);
|
|
83
|
+
if (id === null)
|
|
84
|
+
return { applied: false, error: `svc:remove 的 id 无效: "${token.args[0] ?? ""}"` };
|
|
85
|
+
const before = state.services.length;
|
|
86
|
+
state.services = state.services.filter((s) => s.id !== id);
|
|
87
|
+
if (state.services.length === before)
|
|
88
|
+
return { applied: false, error: `svc:remove 服务 #${id} 不存在` };
|
|
89
|
+
return { applied: true, feedback: `Removed #${id}` };
|
|
90
|
+
}
|
|
91
|
+
case "clear": {
|
|
92
|
+
const n = state.services.length;
|
|
93
|
+
state.services = [];
|
|
94
|
+
state.nextId = 1;
|
|
95
|
+
return { applied: true, feedback: `Cleared ${n} service(s)` };
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
return { applied: false, error: `svc 未知操作: ${op}` };
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
overlay(state) {
|
|
102
|
+
if (!state || state.services.length === 0)
|
|
103
|
+
return undefined;
|
|
104
|
+
const running = state.services.filter((s) => !s.stopped).length;
|
|
105
|
+
const lines = state.services.map((s) => {
|
|
106
|
+
const mark = s.stopped ? "○" : "●";
|
|
107
|
+
let line = ` ${mark} #${s.id} ${s.name}`;
|
|
108
|
+
if (s.port !== undefined)
|
|
109
|
+
line += ` :${s.port}`;
|
|
110
|
+
if (s.pid !== undefined)
|
|
111
|
+
line += ` (pid ${s.pid})`;
|
|
112
|
+
if (s.stopped)
|
|
113
|
+
line += " 已停止";
|
|
114
|
+
return line;
|
|
115
|
+
});
|
|
116
|
+
return { tool: "svc", lines: [`${running}/${state.services.length} running`, ...lines] };
|
|
117
|
+
},
|
|
118
|
+
init: initServiceState,
|
|
119
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtins/todo.ts — 内置任务标记(复刻 pi-marker-tools)。
|
|
3
|
+
*/
|
|
4
|
+
export const TODO_NAMESPACE = "todo";
|
|
5
|
+
export function initTodoState() {
|
|
6
|
+
return { tasks: [], nextId: 1 };
|
|
7
|
+
}
|
|
8
|
+
function findTask(state, id) {
|
|
9
|
+
return state.tasks.find((t) => t.id === id && t.status !== "deleted");
|
|
10
|
+
}
|
|
11
|
+
function parseId(raw) {
|
|
12
|
+
if (raw === undefined)
|
|
13
|
+
return null;
|
|
14
|
+
const n = Number(raw);
|
|
15
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
16
|
+
}
|
|
17
|
+
function formatStatus(s) {
|
|
18
|
+
switch (s) {
|
|
19
|
+
case "pending": return "pending";
|
|
20
|
+
case "in_progress": return "in_progress";
|
|
21
|
+
case "completed": return "completed";
|
|
22
|
+
default: return s;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function describeTodos(state, includeDeleted = false) {
|
|
26
|
+
const visible = state.tasks.filter((t) => includeDeleted || t.status !== "deleted");
|
|
27
|
+
if (visible.length === 0)
|
|
28
|
+
return "[todo] (空)";
|
|
29
|
+
return visible
|
|
30
|
+
.map((t) => {
|
|
31
|
+
const form = t.status === "in_progress" && t.activeForm ? ` (${t.activeForm})` : "";
|
|
32
|
+
const deps = t.blockedBy.length ? ` ⛓ ${t.blockedBy.join(",")}` : "";
|
|
33
|
+
return `[${formatStatus(t.status)}] #${t.id} ${t.subject}${form}${deps}`;
|
|
34
|
+
})
|
|
35
|
+
.join("\n");
|
|
36
|
+
}
|
|
37
|
+
export const todoMarker = {
|
|
38
|
+
name: "todo",
|
|
39
|
+
guidance: [
|
|
40
|
+
"# 内联标记工具(状态类操作请写在回答正文,不要调用工具)",
|
|
41
|
+
"- 标记语法:[[todo:new:<主题>]] 新建;[[todo:set:<id>,completed|in_progress|pending]] 状态;[[todo:remove:<id>]] 删除;[[todo:dep:<id>,blocks=<依赖id,逗号分隔>]] 设依赖。",
|
|
42
|
+
"- 状态变化全部用上面的 [[todo:...]] 内联标记表达,不会中断回答,无需等待返回。",
|
|
43
|
+
"- 想查看/list 当前任务列表时,才用 `markers_list` 工具(读操作走工具)。",
|
|
44
|
+
"- 不要编造不存在的任务 id;id 由 [[todo:new:...]] 分配,首次分配是自增整数。",
|
|
45
|
+
],
|
|
46
|
+
async apply(token, _ctx, _state) {
|
|
47
|
+
const state = _state;
|
|
48
|
+
const op = token.op;
|
|
49
|
+
switch (op) {
|
|
50
|
+
case "new": {
|
|
51
|
+
const subject = token.args[0]?.trim();
|
|
52
|
+
if (!subject)
|
|
53
|
+
return { applied: false, error: "todo:new 需要一个主题参数 [[todo:new:<主题>]]" };
|
|
54
|
+
const id = state.nextId++;
|
|
55
|
+
state.tasks.push({ id, subject, status: "pending", blockedBy: [], createdAt: Date.now() });
|
|
56
|
+
return { applied: true, feedback: `Created #${id}: ${subject} (pending)` };
|
|
57
|
+
}
|
|
58
|
+
case "set": {
|
|
59
|
+
const id = parseId(token.args[0]);
|
|
60
|
+
if (id === null)
|
|
61
|
+
return { applied: false, error: `todo:set 的 id 无效: "${token.args[0] ?? ""}"` };
|
|
62
|
+
const status = token.args[1]?.trim();
|
|
63
|
+
if (!status || !(status === "pending" || status === "in_progress" || status === "completed")) {
|
|
64
|
+
return { applied: false, error: `todo:set 状态无效: "${status ?? ""}",应为 pending|in_progress|completed` };
|
|
65
|
+
}
|
|
66
|
+
const task = findTask(state, id);
|
|
67
|
+
if (!task)
|
|
68
|
+
return { applied: false, error: `todo:set 任务 #${id} 不存在` };
|
|
69
|
+
const activeForm = token.kwargs["activeForm"];
|
|
70
|
+
const from = task.status;
|
|
71
|
+
if (status === "pending" && from === "completed") {
|
|
72
|
+
return { applied: false, error: `任务 #${id} 已完成,不能置回 pending` };
|
|
73
|
+
}
|
|
74
|
+
if (status === "in_progress" && from === "completed") {
|
|
75
|
+
return { applied: false, error: `任务 #${id} 已完成,不能重新进行中` };
|
|
76
|
+
}
|
|
77
|
+
task.status = status;
|
|
78
|
+
if (status === "in_progress" && activeForm)
|
|
79
|
+
task.activeForm = activeForm;
|
|
80
|
+
const change = from !== status ? ` (${from} → ${status})` : "";
|
|
81
|
+
return { applied: true, feedback: `Updated #${id}${change}` };
|
|
82
|
+
}
|
|
83
|
+
case "remove": {
|
|
84
|
+
const id = parseId(token.args[0]);
|
|
85
|
+
if (id === null)
|
|
86
|
+
return { applied: false, error: `todo:remove 的 id 无效: "${token.args[0] ?? ""}"` };
|
|
87
|
+
const task = findTask(state, id);
|
|
88
|
+
if (!task)
|
|
89
|
+
return { applied: false, error: `todo:remove 任务 #${id} 不存在` };
|
|
90
|
+
task.status = "deleted";
|
|
91
|
+
return { applied: true, feedback: `Deleted #${id}: ${task.subject}` };
|
|
92
|
+
}
|
|
93
|
+
case "dep": {
|
|
94
|
+
const id = parseId(token.args[0]);
|
|
95
|
+
if (id === null)
|
|
96
|
+
return { applied: false, error: `todo:dep 的 id 无效: "${token.args[0] ?? ""}"` };
|
|
97
|
+
const task = findTask(state, id);
|
|
98
|
+
if (!task)
|
|
99
|
+
return { applied: false, error: `todo:dep 任务 #${id} 不存在` };
|
|
100
|
+
const depRaw = token.kwargs["blocks"] ?? token.args[1] ?? "";
|
|
101
|
+
const deps = depRaw
|
|
102
|
+
.split(",")
|
|
103
|
+
.map((x) => parseId(x.trim()))
|
|
104
|
+
.filter((x) => x !== null);
|
|
105
|
+
const bad = deps.filter((d) => d === id || !findTask(state, d));
|
|
106
|
+
if (bad.length)
|
|
107
|
+
return { applied: false, error: `todo:dep 检测到非法依赖 ${bad.join(",")}(不存在或自环)` };
|
|
108
|
+
task.blockedBy = deps;
|
|
109
|
+
return { applied: true, feedback: `#${id} blocks: ${deps.length ? deps.join(",") : "(none)"}` };
|
|
110
|
+
}
|
|
111
|
+
default:
|
|
112
|
+
return { applied: false, error: `todo 未知操作: ${op}` };
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
overlay(state) {
|
|
116
|
+
if (!state || state.tasks.length === 0)
|
|
117
|
+
return undefined;
|
|
118
|
+
const visible = state.tasks.filter((t) => t.status !== "deleted");
|
|
119
|
+
if (visible.length === 0)
|
|
120
|
+
return undefined;
|
|
121
|
+
const done = visible.filter((t) => t.status === "completed").length;
|
|
122
|
+
const lines = visible.map((t) => {
|
|
123
|
+
const mark = t.status === "completed" ? "✓" : t.status === "in_progress" ? "◐" : "○";
|
|
124
|
+
const form = t.status === "in_progress" && t.activeForm ? ` (${t.activeForm})` : "";
|
|
125
|
+
return ` ${mark} #${t.id} ${t.subject}${form}`;
|
|
126
|
+
});
|
|
127
|
+
return { tool: "todo", lines: [`${done}/${visible.length} done`, ...lines] };
|
|
128
|
+
},
|
|
129
|
+
init: initTodoState,
|
|
130
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* markers/index.ts — 聚合内置标记并提供便捷初始化。
|
|
3
|
+
*/
|
|
4
|
+
import { registerMarker } from "./registry.js";
|
|
5
|
+
import { todoMarker } from "./builtins/todo.js";
|
|
6
|
+
import { servicesMarker } from "./builtins/services.js";
|
|
7
|
+
import { notifyMarker } from "./builtins/notify.js";
|
|
8
|
+
import { renameMarker, renameAliasMarker, titleAliasMarker } from "./builtins/rename.js";
|
|
9
|
+
let initialized = false;
|
|
10
|
+
export function ensureMarkersRegistered() {
|
|
11
|
+
if (initialized)
|
|
12
|
+
return;
|
|
13
|
+
registerMarker(todoMarker);
|
|
14
|
+
registerMarker(servicesMarker);
|
|
15
|
+
registerMarker(notifyMarker);
|
|
16
|
+
registerMarker(renameMarker);
|
|
17
|
+
registerMarker(renameAliasMarker);
|
|
18
|
+
registerMarker(titleAliasMarker);
|
|
19
|
+
initialized = true;
|
|
20
|
+
}
|
|
21
|
+
export { todoMarker, servicesMarker, notifyMarker, renameMarker, renameAliasMarker, titleAliasMarker };
|
|
22
|
+
export * from "./marker.js";
|
|
23
|
+
export * from "./registry.js";
|
|
24
|
+
export * from "./store.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* marker.ts — 通用内联标记核心抽象(内置版)。
|
|
3
|
+
* 复刻自 pi-marker-tools,保持相同解析语义,便于 AI 无缝迁移。
|
|
4
|
+
*/
|
|
5
|
+
export const MARKER_OPEN = "[[";
|
|
6
|
+
export const MARKER_CLOSE = "]]";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// 解析器
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const TOKEN_RE = /\[\[\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*([A-Za-z][A-Za-z0-9_-]*)\s*:(.*?)\s*\]\]/g;
|
|
11
|
+
function splitArgs(body) {
|
|
12
|
+
const args = [];
|
|
13
|
+
const kwargs = {};
|
|
14
|
+
for (const piece of body.split(",")) {
|
|
15
|
+
const trimmed = piece.trim();
|
|
16
|
+
if (!trimmed)
|
|
17
|
+
continue;
|
|
18
|
+
const eq = trimmed.indexOf("=");
|
|
19
|
+
if (eq > 0 && /^[A-Za-z][A-Za-z0-9_-]*$/.test(trimmed.slice(0, eq))) {
|
|
20
|
+
kwargs[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
args.push(trimmed);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { args, kwargs };
|
|
27
|
+
}
|
|
28
|
+
export function parseMarkers(text) {
|
|
29
|
+
const tokens = [];
|
|
30
|
+
TOKEN_RE.lastIndex = 0;
|
|
31
|
+
let m;
|
|
32
|
+
while ((m = TOKEN_RE.exec(text)) !== null) {
|
|
33
|
+
const [, tool, op, body] = m;
|
|
34
|
+
if (body.includes("[["))
|
|
35
|
+
continue;
|
|
36
|
+
const { args, kwargs } = splitArgs(body);
|
|
37
|
+
tokens.push({ tool, op, args, kwargs, raw: m[0] });
|
|
38
|
+
}
|
|
39
|
+
return tokens;
|
|
40
|
+
}
|
|
41
|
+
export function stripMarkers(text) {
|
|
42
|
+
return text.replace(TOKEN_RE, () => "");
|
|
43
|
+
}
|
|
44
|
+
export function replaceToken(text, raw, replacement) {
|
|
45
|
+
return text.split(raw).join(replacement);
|
|
46
|
+
}
|
|
47
|
+
export function serializeToken(token) {
|
|
48
|
+
const parts = [token.tool, token.op, ...token.args];
|
|
49
|
+
const kwargs = Object.entries(token.kwargs)
|
|
50
|
+
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
51
|
+
.map(([k, v]) => `${k}=${v}`);
|
|
52
|
+
return `${MARKER_OPEN}${[...parts, ...kwargs].join(":")}${MARKER_CLOSE}`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry.ts — 标记工具注册表(内置版)。
|
|
3
|
+
*/
|
|
4
|
+
const registry = new Map();
|
|
5
|
+
export function registerMarker(marker) {
|
|
6
|
+
registry.set(marker.name, marker);
|
|
7
|
+
}
|
|
8
|
+
export function getMarker(name) {
|
|
9
|
+
return registry.get(name);
|
|
10
|
+
}
|
|
11
|
+
export function allMarkers() {
|
|
12
|
+
return [...registry.values()];
|
|
13
|
+
}
|
|
14
|
+
export function lookupToken(name) {
|
|
15
|
+
return registry.get(name);
|
|
16
|
+
}
|
|
17
|
+
export function collectGuidance(disabled = new Set()) {
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const m of allMarkers()) {
|
|
20
|
+
if (disabled.has(m.name))
|
|
21
|
+
continue;
|
|
22
|
+
out.push(...m.guidance);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
export function listMarkerNames() {
|
|
27
|
+
return [...registry.keys()];
|
|
28
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* store.ts — 标记状态持久化(内置版)。
|
|
3
|
+
*
|
|
4
|
+
* 策略:优先用 SDK 会话的 custom entry(appendCustomEntry + getBranch 扫描),
|
|
5
|
+
* 失败时回退到内存 Map(保证 rename 等轻量标记仍可用)。custom entry 类型与
|
|
6
|
+
* pi-marker-tools 保持兼容(marker-tools/store),便于复用既有会话数据。
|
|
7
|
+
*/
|
|
8
|
+
export const STORE_CUSTOM_TYPE = "marker-tools/store";
|
|
9
|
+
const SNAPSHOT_VERSION = 1;
|
|
10
|
+
/** 从分支重建某命名空间最新快照;branch 为 SessionManager.getBranch() 返回的数组。 */
|
|
11
|
+
export function loadStateFromBranch(branch, namespace) {
|
|
12
|
+
let latest;
|
|
13
|
+
for (const entry of branch) {
|
|
14
|
+
if (entry.type !== "custom")
|
|
15
|
+
continue;
|
|
16
|
+
if (entry.customType !== STORE_CUSTOM_TYPE)
|
|
17
|
+
continue;
|
|
18
|
+
const snap = entry.data;
|
|
19
|
+
if (!snap || snap.namespace !== namespace || snap.state === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
if (!latest || snap.ts >= latest.ts)
|
|
22
|
+
latest = snap;
|
|
23
|
+
}
|
|
24
|
+
return latest?.state;
|
|
25
|
+
}
|
|
26
|
+
export function hasStateInBranch(branch, namespace) {
|
|
27
|
+
return loadStateFromBranch(branch, namespace) !== undefined;
|
|
28
|
+
}
|
|
29
|
+
/** 追加快照:优先走 sessionManager.appendCustomEntry,否则回退到回调。 */
|
|
30
|
+
export function appendSnapshot(mgr, namespace, state, fallbackSave) {
|
|
31
|
+
const snapshot = {
|
|
32
|
+
namespace,
|
|
33
|
+
version: SNAPSHOT_VERSION,
|
|
34
|
+
state,
|
|
35
|
+
ts: Date.now(),
|
|
36
|
+
};
|
|
37
|
+
if (mgr?.appendCustomEntry) {
|
|
38
|
+
try {
|
|
39
|
+
mgr.appendCustomEntry(STORE_CUSTOM_TYPE, snapshot);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// 回退到内存
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
fallbackSave?.(snapshot);
|
|
47
|
+
}
|
|
@@ -210,6 +210,9 @@ export class SettingsService {
|
|
|
210
210
|
reviewSkills,
|
|
211
211
|
extensions,
|
|
212
212
|
presets: this.presets.map((p) => ({ ...p })),
|
|
213
|
+
...(this.host.getMarkerState
|
|
214
|
+
? this.host.getMarkerState()
|
|
215
|
+
: { markersEnabled: true, disabledMarkers: [], markers: [] }),
|
|
213
216
|
subagentTemplates: this.templates.list(),
|
|
214
217
|
subagentDefaultTemplates: DEFAULT_TEMPLATES.map((t) => t.name),
|
|
215
218
|
},
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* sync with exec(). */
|
|
5
5
|
export const NATIVE_COMMANDS = [
|
|
6
6
|
{ name: "new", description: "新建对话", descriptionEn: "New chat" },
|
|
7
|
+
{
|
|
8
|
+
name: "name",
|
|
9
|
+
description: "重命名当前会话",
|
|
10
|
+
descriptionEn: "Set session display name",
|
|
11
|
+
argumentHint: "<名称>",
|
|
12
|
+
argumentHintEn: "<name>",
|
|
13
|
+
},
|
|
7
14
|
{
|
|
8
15
|
name: "model",
|
|
9
16
|
description: "切换模型",
|
|
@@ -131,6 +138,33 @@ export class SlashCommandsService {
|
|
|
131
138
|
case "new":
|
|
132
139
|
await this.host.newChat();
|
|
133
140
|
return true;
|
|
141
|
+
case "name": {
|
|
142
|
+
const trimmed = args.trim();
|
|
143
|
+
if (!trimmed) {
|
|
144
|
+
const current = this.host.getSession().sessionName;
|
|
145
|
+
this.host.emit({
|
|
146
|
+
type: "notice",
|
|
147
|
+
level: "info",
|
|
148
|
+
text: current ? `当前会话名称:${current}。用法:/name <名称>` : `用法:/name <名称>`,
|
|
149
|
+
textEn: current ? `Current session name: ${current}. Usage: /name <name>` : `Usage: /name <name>`,
|
|
150
|
+
});
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (this.host.renameSession) {
|
|
154
|
+
await this.host.renameSession(trimmed);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.host.getSession().setSessionName(trimmed);
|
|
158
|
+
await this.host.refreshSessions();
|
|
159
|
+
this.host.emit({
|
|
160
|
+
type: "notice",
|
|
161
|
+
level: "info",
|
|
162
|
+
text: `已重命名当前会话为「${trimmed}」`,
|
|
163
|
+
textEn: `Renamed current session to "${trimmed}"`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
134
168
|
case "model": {
|
|
135
169
|
if (!args) {
|
|
136
170
|
const current = this.host.getSession().model;
|
package/dist/server/terminals.js
CHANGED
|
@@ -1240,6 +1240,17 @@ export class TerminalManager {
|
|
|
1240
1240
|
if (this.history.delete(id))
|
|
1241
1241
|
this.emitList();
|
|
1242
1242
|
}
|
|
1243
|
+
/** Rename a terminal tab (live or retained history). Empty names ignored. */
|
|
1244
|
+
rename(id, title) {
|
|
1245
|
+
const trimmed = (title ?? "").trim();
|
|
1246
|
+
if (!trimmed)
|
|
1247
|
+
return;
|
|
1248
|
+
const entry = this.find(id);
|
|
1249
|
+
if (!entry)
|
|
1250
|
+
return;
|
|
1251
|
+
entry.title = trimmed;
|
|
1252
|
+
this.emitList();
|
|
1253
|
+
}
|
|
1243
1254
|
/** Kill every terminal owned by this conversation. */
|
|
1244
1255
|
killAll() {
|
|
1245
1256
|
for (const entry of this.terms.values()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.63.
|
|
3
|
+
"version": "0.63.4",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as r,j as n}from"./markdown-DRBrS2Nf.js";import{u as V,b as B,T as O,a as W,F as Y,c as P,d as Z,e as M,f as ee,g as ne,h as te,i as se,r as ae}from"./index-BFoSybNe.js";import{D as ie,o as le}from"./xterm-D1D2FVe3.js";import"./react-C9ovnpIm.js";function re({conversationId:t,terminalId:c,command:x,cwd:a,title:w,active:u,send:h,register:j}){const E=r.useRef(null),v=r.useRef(null),{locale:b}=V(),g=r.useRef(b);g.current=b;const m=x?JSON.stringify(x):"";return r.useEffect(()=>{const d=E.current;if(!d)return;const i=new ie({theme:B(),fontFamily:'"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',fontSize:13,cursorBlink:!0,scrollback:8e3}),y=new le;i.loadAddon(y),i.open(d),v.current={term:i,fit:y},u&&i.focus();const f=()=>{i.options.theme=B()};window.addEventListener(O,f),i.attachCustomKeyEventHandler(o=>{var T;if(o.type!=="keydown")return!0;const S=(T=o.key)==null?void 0:T.toLowerCase();if((o.ctrlKey||o.metaKey)&&S==="v")return!1;if(o.ctrlKey&&!o.shiftKey&&!o.altKey&&S==="c"&&i.hasSelection()){const k=i.textarea;return k&&(k.value=i.getSelection(),k.select()),!1}return!0});const F=j(t,c,{write:o=>i.write(o),dispose:()=>i.dispose()}),R=()=>{try{y.fit(),h({type:"terminal_resize",terminalId:c,conversationId:t,cols:i.cols,rows:i.rows})}catch{}},D=requestAnimationFrame(()=>{try{y.fit()}catch{}h(x?{type:"run_command",terminalId:c,conversationId:t,command:x,cols:i.cols,rows:i.rows}:{type:"terminal_create",terminalId:c,title:w,locale:g.current,conversationId:t,cwd:a,cols:i.cols,rows:i.rows})}),C=i.onData(o=>{h({type:"terminal_input",terminalId:c,conversationId:t,data:o})});let N=null;return typeof ResizeObserver<"u"&&(N=new ResizeObserver(()=>{d.offsetWidth>0&&d.offsetHeight>0&&R()}),N.observe(d)),()=>{cancelAnimationFrame(D),C.dispose(),window.removeEventListener(O,f),N==null||N.disconnect(),F(),i.dispose(),v.current=null}},[t,c,m,h,j]),r.useEffect(()=>{if(!u)return;const d=requestAnimationFrame(()=>{const i=v.current;if(i){try{i.fit.fit(),h({type:"terminal_resize",terminalId:c,conversationId:t,cols:i.term.cols,rows:i.term.rows})}catch{}i.term.focus()}});return()=>cancelAnimationFrame(d)},[u]),n.jsx("div",{ref:E,className:`term-xterm ${u?"":"hidden"}`})}const z={name:"",command:"",cwd:"${pwd}"};function ue({chat:t,send:c,terminal:x}){const a=W(),[w,u]=r.useState(null),[h,j]=r.useState(!1),[E,v]=r.useState(!1),[b,g]=r.useState(null),[m,d]=r.useState(z),[i,y]=r.useState(null),f=r.useRef(null),[F,R]=r.useState(!0),[D,C]=r.useState(null),[N,o]=r.useState("");r.useEffect(()=>{t.terminals.length===0?u(null):t.terminals.some(e=>e.id===w)||u(t.terminals[t.terminals.length-1].id)},[t.terminals,w]),r.useEffect(()=>{t.terminalActiveId&&(u(t.terminalActiveId),j(!1))},[t.terminalActiveId]),r.useEffect(()=>()=>{f.current&&clearTimeout(f.current)},[]);const S=t.terminals.filter(e=>!e.agentBash),T=t.terminals.filter(e=>e.agentBash),k=e=>{var p;if(!t.ready)return;const s=ae(),l=t.activeConversationId||((p=t.state)==null?void 0:p.conversationId)||"";x.create({...e,id:s,conversationId:l,cols:e.cols??80,rows:e.rows??24,running:!0,exitCode:null}),u(s),j(!1)},H=()=>{var e;return k({title:a("terminalTitle",{n:S.length+1}),cwd:((e=t.state)==null?void 0:e.cwd)??""})},I=e=>{var p;const s=e.name||e.command,l=t.terminals.find($=>$.title===s);if(l){x.restart(l.id),u(l.id),c({type:"run_command",terminalId:l.id,conversationId:l.conversationId,command:e,cols:80,rows:24});return}k({title:s,cwd:((p=t.state)==null?void 0:p.cwd)??"",command:e})},q=e=>{const s=t.terminals.find(l=>l.id===e);if(s&&c({type:"terminal_kill",terminalId:e,conversationId:s.conversationId}),x.close(e),w===e){const l=t.terminals.filter(p=>p.id!==e);u(l.length>0?l[l.length-1].id:null)}},A=e=>n.jsxs("div",{className:`term-tab ${e.id===w?"active":""}`,children:[n.jsxs("button",{type:"button",className:"term-tab-main",title:`${e.cwd}${e.command?`
|
|
2
|
+
> ${e.command.command}`:""}`,onClick:()=>{D||(u(e.id),j(!1))},children:[n.jsx("span",{className:`term-tab-dot ${e.running?"run":"exit"}`}),n.jsxs("span",{className:"term-tab-title",children:[D===e.id?n.jsx("input",{autoFocus:!0,className:"term-tab-rename-input",value:N,placeholder:e.title,onClick:s=>s.stopPropagation(),onChange:s=>o(s.target.value),onKeyDown:s=>{if(s.stopPropagation(),s.key==="Enter"&&!s.nativeEvent.isComposing){const l=N.trim();l&&c({type:"rename_terminal",terminalId:e.id,conversationId:e.conversationId,title:l}),C(null)}else s.key==="Escape"&&C(null)},onBlur:()=>C(null)}):e.title,!e.running&&n.jsx("span",{className:"term-tab-exit",children:a("exited",{code:e.exitCode===null?"":` ${e.exitCode}`})})]})]}),n.jsx("button",{type:"button",className:"term-tab-close term-tab-rename",title:a("renameTerminal"),onClick:s=>{s.stopPropagation(),o(e.title),C(e.id)},children:n.jsx(M,{})}),n.jsx("button",{type:"button",className:"term-tab-close",title:a("closeTerminal"),onClick:()=>q(e.id),children:n.jsx(se,{})})]},e.id),G=()=>{v(!0),g(null),d(z)},L=e=>{const s=t.commands[e];s&&(v(!1),g(e),d({name:s.name,command:s.command,cwd:s.cwd??""}))},K=()=>{v(!1),g(null)},_=()=>{const e=m.name.trim(),s=m.command.trim();if(!e||!s)return;const l=m.cwd.trim(),p={name:e,command:s,cwd:l||void 0},$=E?[...t.commands,p]:b!==null?t.commands.map((Q,U)=>U===b?p:Q):t.commands;c({type:"save_commands",commands:$}),K()},J=e=>{if(i===e){const s=t.commands.filter((l,p)=>p!==e);c({type:"save_commands",commands:s}),y(null),f.current&&clearTimeout(f.current)}else y(e),f.current&&clearTimeout(f.current),f.current=setTimeout(()=>y(null),2500)},X=E||b!==null;return n.jsxs("div",{className:"terminal-view",children:[n.jsxs("aside",{className:`term-side term-commands ${h?"open":""}`,children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("commands")}),n.jsxs("div",{className:"panel-header-actions",children:[n.jsx("button",{type:"button",className:"panel-refresh",title:a("rerun"),onClick:()=>c({type:"list_commands"}),children:n.jsx(Y,{})}),n.jsx("button",{type:"button",className:"panel-new",title:a("newCommand"),onClick:G,children:n.jsx(P,{})})]})]}),n.jsx("div",{className:"panel-body",children:X?n.jsxs("div",{className:"cmd-form",children:[n.jsx("label",{htmlFor:"cmd-name",children:a("name")}),n.jsx("input",{id:"cmd-name",className:"cmd-input",value:m.name,placeholder:a("exampleName"),autoFocus:!0,onChange:e=>d({...m,name:e.target.value})}),n.jsx("label",{htmlFor:"cmd-command",children:a("command")}),n.jsx("input",{id:"cmd-command",className:"cmd-input",value:m.command,placeholder:a("exampleCommand"),onChange:e=>d({...m,command:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&_()}}),n.jsxs("label",{htmlFor:"cmd-cwd",children:[a("directory")," ",n.jsx("span",{className:"cmd-hint",children:a("cwdHint")})]}),n.jsx("input",{id:"cmd-cwd",className:"cmd-input",value:m.cwd,placeholder:"${pwd}",onChange:e=>d({...m,cwd:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&_()}}),n.jsxs("div",{className:"cmd-form-actions",children:[n.jsx("button",{type:"button",className:"btn",onClick:K,children:a("cancel")}),n.jsx("button",{type:"button",className:"btn primary",disabled:!m.name.trim()||!m.command.trim(),onClick:_,children:a("save")})]})]}):n.jsxs(n.Fragment,{children:[t.commands.length===0&&n.jsx("div",{className:"panel-empty",children:a("noCommands")}),t.commands.map((e,s)=>n.jsxs("div",{className:"cmd-item",children:[n.jsx("button",{type:"button",className:"cmd-run",title:a("clickToRun"),onClick:()=>I(e),children:n.jsx(Z,{})}),n.jsxs("button",{type:"button",className:"cmd-main",title:a("clickToRun"),onClick:()=>I(e),children:[n.jsx("span",{className:"cmd-name",children:e.name}),n.jsx("span",{className:"cmd-command",children:e.command}),e.cwd&&n.jsx("span",{className:"cmd-cwd",children:e.cwd})]}),n.jsx("button",{type:"button",className:"cmd-act",title:a("edit"),onClick:()=>L(s),children:n.jsx(M,{})}),n.jsx("button",{type:"button",className:`cmd-act del ${i===s?"confirm":""}`,title:a("delete"),onClick:()=>J(s),children:i===s?a("confirmQ"):n.jsx(ee,{})})]},s))]})}),n.jsxs("div",{className:"term-tabs-block",children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("terminal")}),n.jsx("button",{type:"button",className:"panel-new",title:a("newTerminal"),onClick:H,children:n.jsx(P,{})})]}),n.jsxs("div",{className:"panel-body",children:[t.terminals.length===0&&n.jsx("div",{className:"panel-empty",children:a("noTerminal")}),S.map(A),T.length>0&&n.jsxs("div",{className:"term-folder",children:[n.jsxs("button",{type:"button",className:`term-folder-header ${F?"open":""}`,title:a("aiBashGroup"),onClick:()=>R(e=>!e),children:[n.jsx("span",{className:"term-folder-caret",children:F?"▾":"▸"}),n.jsx("span",{className:"term-folder-title",children:a("aiBashGroup")}),n.jsx("span",{className:"term-folder-count",children:T.length})]}),F&&n.jsx("div",{className:"term-folder-body",children:T.map(A)})]})]})]})]}),n.jsxs("div",{className:"term-main",children:[h&&n.jsx("div",{className:"drawer-backdrop",onClick:()=>j(!1)}),n.jsx("button",{type:"button",className:"term-side-toggle",title:a("commands"),onClick:()=>j(e=>!e),children:n.jsx(ne,{})}),t.terminals.length===0?n.jsxs("div",{className:"term-empty",children:[n.jsx(te,{className:"term-empty-icon"}),n.jsx("div",{className:"term-empty-title",children:a("builtinTerminal")}),n.jsx("div",{className:"term-empty-sub",children:a("termEmptySub")})]}):t.terminals.map(e=>n.jsx(re,{conversationId:e.conversationId,terminalId:e.id,command:e.command,cwd:e.cwd,title:e.title,active:e.id===w,send:c,register:x.register},`${e.conversationId}:${e.id}`))]})]})}export{ue as TerminalPanel};
|