pi-web-ui 0.30.0 → 0.31.0
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 +53 -17
- package/dist/server/client-state.js +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/settings-service.js +9 -1
- package/dist/server/slash-commands.js +2 -0
- package/dist/server/terminals.js +19 -2
- package/dist/server/themes.js +20 -2
- package/package.json +1 -1
- package/themes/light.css +65 -0
- package/themes/md-preview.css +6911 -0
- package/themes/white.css +6972 -0
- package/web/dist/assets/{TerminalPanel-CMBWruhs.js → TerminalPanel-CVOwLwwF.js} +1 -1
- package/web/dist/assets/index-BYW5b0ZE.js +19 -0
- package/web/dist/assets/index-C9LigvHH.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-C8YkKrP3.css +0 -10
- package/web/dist/assets/index-DpzPCOcG.js +0 -19
|
@@ -24,7 +24,7 @@ import { ModelAdminService } from "./model-admin.js";
|
|
|
24
24
|
import { FilesService, workspacePath } from "./files-service.js";
|
|
25
25
|
import { isExtensionDisabled, ClientStateStore, } from "./client-state.js";
|
|
26
26
|
import { saveUpload } from "./uploads.js";
|
|
27
|
-
import { makePersistentTerminalTools } from "./terminals.js";
|
|
27
|
+
import { makePersistentTerminalTools, TERMINAL_TOOLS_GUIDANCE, TERMINAL_TOOL_NAMES, } from "./terminals.js";
|
|
28
28
|
import { WebUIContext } from "./webui-context.js";
|
|
29
29
|
import { buildAttachmentMessages, parseModelSpec, } from "./attachments.js";
|
|
30
30
|
import { serializeMessage, serializeStreamingMessage, } from "./serialize.js";
|
|
@@ -415,6 +415,8 @@ export class ClientSession {
|
|
|
415
415
|
isStreaming: () => this.session.isStreaming,
|
|
416
416
|
reloadSession: async () => {
|
|
417
417
|
await this.session.reload();
|
|
418
|
+
// reload() 会把 custom 工具重新加回活跃集——重放终端开关。
|
|
419
|
+
this.applyTerminalToolGating(this.session);
|
|
418
420
|
await this.pushSlashCommands();
|
|
419
421
|
},
|
|
420
422
|
effectiveDefaultSystemPrompt: () => this.effectiveDefaultSystemPrompt(),
|
|
@@ -520,6 +522,11 @@ export class ClientSession {
|
|
|
520
522
|
// GBK 老中文文件让模型改用终端按正确编码读(iconv/chcp/Get-Content)。
|
|
521
523
|
out.push(WINDOWS_PERSONA);
|
|
522
524
|
}
|
|
525
|
+
if (this.settingsSvc.current.terminalToolsEnabled !== false) {
|
|
526
|
+
// 终端工具使用引导(全平台):告诉模型什么场景该用持久终端
|
|
527
|
+
// 而不是一次性 bash——没有这段模型几乎从不主动选终端工具。
|
|
528
|
+
out.push(TERMINAL_TOOLS_GUIDANCE);
|
|
529
|
+
}
|
|
523
530
|
return out;
|
|
524
531
|
},
|
|
525
532
|
// 技能开关:禁用的技能从系统提示词和 /skill: 目录中剔除。
|
|
@@ -536,18 +543,21 @@ export class ClientSession {
|
|
|
536
543
|
}),
|
|
537
544
|
},
|
|
538
545
|
});
|
|
546
|
+
const created = await createAgentSessionFromServices({
|
|
547
|
+
services,
|
|
548
|
+
sessionManager,
|
|
549
|
+
// 可手动停止的 bash 工具:覆盖 SDK 内置 bash(customTools 按 name
|
|
550
|
+
// 覆盖),执行时把自己的 AbortController 注册进客户端集合——
|
|
551
|
+
// abortBash() 只杀这些命令,agent run 与对话继续。
|
|
552
|
+
customTools: [
|
|
553
|
+
makeKillableBashTool(effectiveCwd, this.bashKills),
|
|
554
|
+
...makePersistentTerminalTools(terminals, effectiveCwd),
|
|
555
|
+
],
|
|
556
|
+
});
|
|
557
|
+
// 终端工具开关从创建起就生效(工具始终注册进注册表,只调活跃集)。
|
|
558
|
+
this.applyTerminalToolGating(created.session);
|
|
539
559
|
return {
|
|
540
|
-
...
|
|
541
|
-
services,
|
|
542
|
-
sessionManager,
|
|
543
|
-
// 可手动停止的 bash 工具:覆盖 SDK 内置 bash(customTools 按 name
|
|
544
|
-
// 覆盖),执行时把自己的 AbortController 注册进客户端集合——
|
|
545
|
-
// abortBash() 只杀这些命令,agent run 与对话继续。
|
|
546
|
-
customTools: [
|
|
547
|
-
makeKillableBashTool(effectiveCwd, this.bashKills),
|
|
548
|
-
...makePersistentTerminalTools(terminals, effectiveCwd),
|
|
549
|
-
],
|
|
550
|
-
})),
|
|
560
|
+
...created,
|
|
551
561
|
services,
|
|
552
562
|
diagnostics: services.diagnostics,
|
|
553
563
|
};
|
|
@@ -590,8 +600,8 @@ export class ClientSession {
|
|
|
590
600
|
uiMessageCache: new Map(),
|
|
591
601
|
lastMessagesSig: "",
|
|
592
602
|
lastMessagesArray: [],
|
|
593
|
-
queueSteering:
|
|
594
|
-
queueFollowUp:
|
|
603
|
+
queueSteering: [],
|
|
604
|
+
queueFollowUp: [],
|
|
595
605
|
toolStartTimes: new Map(),
|
|
596
606
|
toolWatchdogs: new Map(),
|
|
597
607
|
};
|
|
@@ -849,8 +859,8 @@ export class ClientSession {
|
|
|
849
859
|
break;
|
|
850
860
|
}
|
|
851
861
|
case "queue_update":
|
|
852
|
-
conv.queueSteering = event.steering
|
|
853
|
-
conv.queueFollowUp = event.followUp
|
|
862
|
+
conv.queueSteering = [...event.steering];
|
|
863
|
+
conv.queueFollowUp = [...event.followUp];
|
|
854
864
|
break;
|
|
855
865
|
// A run finished or a new entry was persisted — keep the session list fresh
|
|
856
866
|
// (new chat + first message, completed turns, compaction, etc.).
|
|
@@ -1453,6 +1463,7 @@ export class ClientSession {
|
|
|
1453
1463
|
setCwd: (path) => this.setCwd(path),
|
|
1454
1464
|
setThinking: (level) => this.setThinking(level),
|
|
1455
1465
|
refreshSessions: () => this.refreshSessions(),
|
|
1466
|
+
afterReload: () => this.applyTerminalToolGating(this.session),
|
|
1456
1467
|
onQuit: () => this.onQuit?.() ?? false,
|
|
1457
1468
|
});
|
|
1458
1469
|
/** Catalog push — index.ts get_commands / attach / cwd 切换等都会调用。 */
|
|
@@ -1495,6 +1506,12 @@ export class ClientSession {
|
|
|
1495
1506
|
pushSettings() {
|
|
1496
1507
|
this.settingsSvc.push();
|
|
1497
1508
|
}
|
|
1509
|
+
/** Extensions/skills changed externally (e.g. `pi remove` finished in the
|
|
1510
|
+
* terminal): re-run session.reload() and re-push state. Streaming-safe —
|
|
1511
|
+
* deferred to agent_end, same as settings reloads. */
|
|
1512
|
+
async reloadExtensions() {
|
|
1513
|
+
return this.settingsSvc.applyRuntime();
|
|
1514
|
+
}
|
|
1498
1515
|
/** Persist + apply a partial settings update (prompt text/mode, toggles). */
|
|
1499
1516
|
async setSettings(partial) {
|
|
1500
1517
|
await this.settingsSvc.set(partial);
|
|
@@ -1515,6 +1532,25 @@ export class ClientSession {
|
|
|
1515
1532
|
async applyRuntimeSettings() {
|
|
1516
1533
|
return this.settingsSvc.applyRuntime();
|
|
1517
1534
|
}
|
|
1535
|
+
/** 把终端工具开关应用到 session 的活跃工具集:关闭时从活跃集中剔除
|
|
1536
|
+
* terminal_*(工具仍留在注册表,重开时可直接加回)。session.reload() 与新
|
|
1537
|
+
* 会话创建都会把 custom 工具加回活跃集,所以这两条路径之后都要重放本方法。 */
|
|
1538
|
+
applyTerminalToolGating(session) {
|
|
1539
|
+
try {
|
|
1540
|
+
const enabled = this.settingsSvc.current.terminalToolsEnabled !== false;
|
|
1541
|
+
const names = new Set(session.getActiveToolNames());
|
|
1542
|
+
for (const n of TERMINAL_TOOL_NAMES) {
|
|
1543
|
+
if (enabled)
|
|
1544
|
+
names.add(n);
|
|
1545
|
+
else
|
|
1546
|
+
names.delete(n);
|
|
1547
|
+
}
|
|
1548
|
+
session.setActiveToolsByName([...names]);
|
|
1549
|
+
}
|
|
1550
|
+
catch {
|
|
1551
|
+
// Session 未就绪——下次创建/reload 会再应用。
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1518
1554
|
async applySettingsReload() {
|
|
1519
1555
|
// 兼容旧入口:reload + 刷目录在宿主回调里完成
|
|
1520
1556
|
return this.settingsSvc.applyRuntime();
|
|
@@ -1557,7 +1593,7 @@ export class ClientSession {
|
|
|
1557
1593
|
pendingMessages() {
|
|
1558
1594
|
let n = 0;
|
|
1559
1595
|
for (const c of this.convs.values())
|
|
1560
|
-
n += c.queueFollowUp + c.queueSteering;
|
|
1596
|
+
n += c.queueFollowUp.length + c.queueSteering.length;
|
|
1561
1597
|
return n;
|
|
1562
1598
|
}
|
|
1563
1599
|
async prompt(text, attachments,
|
|
@@ -151,6 +151,7 @@ export class ClientStateStore {
|
|
|
151
151
|
customSystemPrompt: s?.settings?.customSystemPrompt ?? "",
|
|
152
152
|
disabledSkills: s?.settings?.disabledSkills ?? [],
|
|
153
153
|
disabledExtensions: s?.settings?.disabledExtensions ?? [],
|
|
154
|
+
terminalToolsEnabled: s?.settings?.terminalToolsEnabled ?? true,
|
|
154
155
|
visionBridgeEnabled: s?.settings?.visionBridgeEnabled ?? true,
|
|
155
156
|
visionBridgeModel: s?.settings?.visionBridgeModel ?? null,
|
|
156
157
|
visionBridgePromptMode: s?.settings?.visionBridgePromptMode === "replace" ? "replace" : "append",
|
|
@@ -169,6 +170,7 @@ export class ClientStateStore {
|
|
|
169
170
|
customSystemPrompt: settings.customSystemPrompt ?? cur.customSystemPrompt ?? "",
|
|
170
171
|
disabledSkills: settings.disabledSkills ?? cur.disabledSkills ?? [],
|
|
171
172
|
disabledExtensions: settings.disabledExtensions ?? cur.disabledExtensions ?? [],
|
|
173
|
+
terminalToolsEnabled: settings.terminalToolsEnabled ?? cur.terminalToolsEnabled ?? true,
|
|
172
174
|
visionBridgeEnabled: settings.visionBridgeEnabled ?? cur.visionBridgeEnabled ?? true,
|
|
173
175
|
visionBridgeModel: settings.visionBridgeModel ?? cur.visionBridgeModel ?? null,
|
|
174
176
|
visionBridgePromptMode: settings.visionBridgePromptMode ??
|
package/dist/server/index.js
CHANGED
|
@@ -660,6 +660,7 @@ wss.on("connection", (ws) => {
|
|
|
660
660
|
customSystemPrompt: msg.customSystemPrompt,
|
|
661
661
|
disabledSkills: msg.disabledSkills,
|
|
662
662
|
disabledExtensions: msg.disabledExtensions,
|
|
663
|
+
terminalToolsEnabled: msg.terminalToolsEnabled,
|
|
663
664
|
visionBridgeEnabled: msg.visionBridgeEnabled,
|
|
664
665
|
visionBridgeModel: msg.visionBridgeModel,
|
|
665
666
|
visionBridgePromptMode: msg.visionBridgePromptMode,
|
|
@@ -668,6 +669,9 @@ wss.on("connection", (ws) => {
|
|
|
668
669
|
reviewDisabledSkills: msg.reviewDisabledSkills,
|
|
669
670
|
});
|
|
670
671
|
break;
|
|
672
|
+
case "extensions_reload":
|
|
673
|
+
void cs.reloadExtensions();
|
|
674
|
+
break;
|
|
671
675
|
case "save_preset":
|
|
672
676
|
void cs.savePreset(msg.name);
|
|
673
677
|
break;
|
|
@@ -99,6 +99,7 @@ export class SettingsService {
|
|
|
99
99
|
settings: {
|
|
100
100
|
promptMode: this.settings.promptMode,
|
|
101
101
|
customSystemPrompt: this.settings.customSystemPrompt,
|
|
102
|
+
terminalToolsEnabled: this.settings.terminalToolsEnabled,
|
|
102
103
|
visionBridgeEnabled: this.settings.visionBridgeEnabled,
|
|
103
104
|
visionBridgeModel: this.settings.visionBridgeModel,
|
|
104
105
|
visionBridgePromptMode: this.settings.visionBridgePromptMode,
|
|
@@ -139,7 +140,8 @@ export class SettingsService {
|
|
|
139
140
|
const needsReload = partial.promptMode !== undefined ||
|
|
140
141
|
partial.customSystemPrompt !== undefined ||
|
|
141
142
|
partial.disabledSkills !== undefined ||
|
|
142
|
-
partial.disabledExtensions !== undefined
|
|
143
|
+
partial.disabledExtensions !== undefined ||
|
|
144
|
+
partial.terminalToolsEnabled !== undefined;
|
|
143
145
|
if (partial.promptMode !== undefined)
|
|
144
146
|
this.settings.promptMode = partial.promptMode;
|
|
145
147
|
if (partial.customSystemPrompt !== undefined) {
|
|
@@ -151,6 +153,9 @@ export class SettingsService {
|
|
|
151
153
|
if (partial.disabledExtensions !== undefined) {
|
|
152
154
|
this.settings.disabledExtensions = partial.disabledExtensions;
|
|
153
155
|
}
|
|
156
|
+
if (partial.terminalToolsEnabled !== undefined) {
|
|
157
|
+
this.settings.terminalToolsEnabled = partial.terminalToolsEnabled;
|
|
158
|
+
}
|
|
154
159
|
if (partial.visionBridgeEnabled !== undefined) {
|
|
155
160
|
this.settings.visionBridgeEnabled = partial.visionBridgeEnabled;
|
|
156
161
|
}
|
|
@@ -187,6 +192,7 @@ export class SettingsService {
|
|
|
187
192
|
customSystemPrompt: this.settings.customSystemPrompt,
|
|
188
193
|
disabledSkills: [...this.settings.disabledSkills],
|
|
189
194
|
disabledExtensions: [...this.settings.disabledExtensions],
|
|
195
|
+
terminalToolsEnabled: this.settings.terminalToolsEnabled,
|
|
190
196
|
reviewPrompt: this.settings.reviewPrompt,
|
|
191
197
|
reviewDisabledSkills: [...this.settings.reviewDisabledSkills],
|
|
192
198
|
};
|
|
@@ -210,6 +216,8 @@ export class SettingsService {
|
|
|
210
216
|
customSystemPrompt: p.customSystemPrompt,
|
|
211
217
|
disabledSkills: [...p.disabledSkills],
|
|
212
218
|
disabledExtensions: [...p.disabledExtensions],
|
|
219
|
+
// 旧版持久化的预设可能没有该字段——保留当前值。
|
|
220
|
+
terminalToolsEnabled: p.terminalToolsEnabled ?? this.settings.terminalToolsEnabled,
|
|
213
221
|
reviewPrompt: p.reviewPrompt ?? this.settings.reviewPrompt,
|
|
214
222
|
reviewDisabledSkills: [
|
|
215
223
|
...(p.reviewDisabledSkills ?? this.settings.reviewDisabledSkills),
|
|
@@ -204,6 +204,8 @@ export class SlashCommandsService {
|
|
|
204
204
|
// Re-discovers extensions / skills / prompt templates from disk and
|
|
205
205
|
// re-pushes the picker catalog (the CLI's /reload semantics).
|
|
206
206
|
await this.host.getSession().reload();
|
|
207
|
+
// reload() 会把 custom 工具加回活跃集——重放设置门控(终端开关等)。
|
|
208
|
+
this.host.afterReload?.();
|
|
207
209
|
await this.push();
|
|
208
210
|
this.host.emit({
|
|
209
211
|
type: "notice",
|
package/dist/server/terminals.js
CHANGED
|
@@ -778,6 +778,23 @@ export class TerminalManager {
|
|
|
778
778
|
this.emitList();
|
|
779
779
|
}
|
|
780
780
|
}
|
|
781
|
+
/** Names of the agent-facing persistent-terminal tools(设置开关门控用)。 */
|
|
782
|
+
export const TERMINAL_TOOL_NAMES = [
|
|
783
|
+
"terminal_create",
|
|
784
|
+
"terminal_list",
|
|
785
|
+
"terminal_close",
|
|
786
|
+
"terminal_input",
|
|
787
|
+
"terminal_key",
|
|
788
|
+
"terminal_read",
|
|
789
|
+
];
|
|
790
|
+
/** System-prompt guidance teaching the model WHEN to prefer the terminal tools
|
|
791
|
+
* over one-shot bash. Without it models almost never pick them — bash returns
|
|
792
|
+
* complete output in a single call, so it always wins on convenience. */
|
|
793
|
+
export const TERMINAL_TOOLS_GUIDANCE = `Persistent interactive terminal tools are available (terminal_create / terminal_list / terminal_close / terminal_input / terminal_key / terminal_read). The one-shot bash tool stays the DEFAULT for ordinary commands - it runs once and returns the full output. Switch to the terminal tools only when:
|
|
794
|
+
- The program is interactive or TUI-based (REPLs like python/node, vim/htop, installers asking y/n, anything waiting on stdin).
|
|
795
|
+
- You start a long-running server or watcher and want to keep watching its output (terminal_read with waitMs) or send keys to it later (e.g. interrupt via terminal_key with Ctrl+c).
|
|
796
|
+
- The user explicitly asks you to work in the visible terminal panel.
|
|
797
|
+
Do NOT use them for simple one-shot commands; bash remains cheaper and simpler there.`;
|
|
781
798
|
/** Build the agent-facing persistent terminal tools for one conversation. */
|
|
782
799
|
export function makePersistentTerminalTools(terminals, cwd) {
|
|
783
800
|
const result = (text, details = {}) => ({ content: [{ type: "text", text }], details });
|
|
@@ -789,8 +806,8 @@ export function makePersistentTerminalTools(terminals, cwd) {
|
|
|
789
806
|
defineTool({
|
|
790
807
|
name: "terminal_create",
|
|
791
808
|
label: "Create terminal",
|
|
792
|
-
description: "Create a named persistent interactive PTY in the current workspace. Use terminal_input or terminal_key to interact with it and terminal_read to inspect incremental output.",
|
|
793
|
-
promptSnippet: "
|
|
809
|
+
description: "Create a named persistent interactive PTY in the current workspace. Use terminal_input or terminal_key to interact with it and terminal_read to inspect incremental output. Prefer this over bash when the program is interactive/TUI-based (REPLs, vim/htop, y/n prompts), when starting a long-running server you want to keep observing or interrupt, or when the user asks to work in the visible terminal. For simple one-shot commands use bash instead.",
|
|
810
|
+
promptSnippet: "run interactive programs or long-running servers in a persistent visible PTY (multi-step: create → input/key → read)",
|
|
794
811
|
parameters: Type.Object({
|
|
795
812
|
terminalId: Type.String({ description: "Stable terminal name" }),
|
|
796
813
|
cwd: Type.Optional(Type.String({ description: "Workspace-relative directory" })),
|
package/dist/server/themes.js
CHANGED
|
@@ -7,10 +7,24 @@
|
|
|
7
7
|
* - builtin: <pkgRoot>/themes/*.css (ships with the npm package)
|
|
8
8
|
* - user : <dataDir>/themes/*.css (drop a css file here to add a theme)
|
|
9
9
|
*/
|
|
10
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
/** Only simple file ids — no path traversal. */
|
|
13
13
|
const ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
14
|
+
/** Display-name marker inside a theme css file (first lines):
|
|
15
|
+
* `/* theme-name: 中文名 *∕` — falls back to the file id when absent.
|
|
16
|
+
* Lets built-in AND user themes carry a human-readable label while the
|
|
17
|
+
* filename stays ASCII (id must match ID_RE). */
|
|
18
|
+
const THEME_NAME_RE = /\/\*\s*theme-name:\s*(.+?)\s*\*\//;
|
|
19
|
+
function readDisplayName(path, fallback) {
|
|
20
|
+
try {
|
|
21
|
+
const head = readFileSync(path, "utf8").slice(0, 300);
|
|
22
|
+
return head.match(THEME_NAME_RE)?.[1]?.trim() || fallback;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
14
28
|
export function listThemes(builtinDir, userDir) {
|
|
15
29
|
const scan = (dir, builtin) => {
|
|
16
30
|
if (!existsSync(dir))
|
|
@@ -19,7 +33,11 @@ export function listThemes(builtinDir, userDir) {
|
|
|
19
33
|
.filter((f) => f.endsWith(".css"))
|
|
20
34
|
.filter((f) => ID_RE.test(f.slice(0, -4)))
|
|
21
35
|
.sort()
|
|
22
|
-
.map((f) => ({
|
|
36
|
+
.map((f) => ({
|
|
37
|
+
id: f.slice(0, -4),
|
|
38
|
+
name: readDisplayName(join(dir, f), f.slice(0, -4)),
|
|
39
|
+
builtin,
|
|
40
|
+
}));
|
|
23
41
|
};
|
|
24
42
|
const builtin = scan(builtinDir, true);
|
|
25
43
|
const user = scan(userDir, false);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
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
|
"type": "module",
|
package/themes/light.css
CHANGED
|
@@ -3926,6 +3926,46 @@ body.panel-resizing {
|
|
|
3926
3926
|
|
|
3927
3927
|
/* ----------------------------------------------------------- message roles */
|
|
3928
3928
|
|
|
3929
|
+
/* Queued prompts (steer = 插队 / followUp = 排队) — pending user bubbles at
|
|
3930
|
+
* the bottom of the real message list, styled like user messages but dimmed
|
|
3931
|
+
* with a tag until the server actually delivers them. */
|
|
3932
|
+
.queued-msg {
|
|
3933
|
+
display: flex;
|
|
3934
|
+
justify-content: flex-end;
|
|
3935
|
+
padding: 6px 16px;
|
|
3936
|
+
}
|
|
3937
|
+
.queued-bubble {
|
|
3938
|
+
max-width: min(85%, 720px);
|
|
3939
|
+
background: var(--accent-soft);
|
|
3940
|
+
border: 1px dashed rgba(139, 92, 246, 0.35);
|
|
3941
|
+
border-radius: 12px;
|
|
3942
|
+
padding: 8px 12px;
|
|
3943
|
+
opacity: 0.75;
|
|
3944
|
+
}
|
|
3945
|
+
.queued-tag {
|
|
3946
|
+
display: inline-block;
|
|
3947
|
+
font-size: 10px;
|
|
3948
|
+
line-height: 1.4;
|
|
3949
|
+
padding: 1px 7px;
|
|
3950
|
+
border-radius: 999px;
|
|
3951
|
+
margin-bottom: 4px;
|
|
3952
|
+
font-weight: 600;
|
|
3953
|
+
}
|
|
3954
|
+
.queued-tag.steer {
|
|
3955
|
+
color: var(--amber);
|
|
3956
|
+
background: rgba(251, 191, 36, 0.14);
|
|
3957
|
+
}
|
|
3958
|
+
.queued-tag.follow {
|
|
3959
|
+
color: var(--text-dim);
|
|
3960
|
+
background: var(--bg-elev2);
|
|
3961
|
+
}
|
|
3962
|
+
.queued-text {
|
|
3963
|
+
white-space: pre-wrap;
|
|
3964
|
+
word-break: break-word;
|
|
3965
|
+
font-size: 13px;
|
|
3966
|
+
color: var(--text);
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3929
3969
|
.msg-user .msg-body {
|
|
3930
3970
|
background: var(--accent-soft);
|
|
3931
3971
|
border: 1px solid rgba(139, 92, 246, 0.25);
|
|
@@ -5360,6 +5400,31 @@ body.panel-resizing {
|
|
|
5360
5400
|
text-overflow: ellipsis;
|
|
5361
5401
|
}
|
|
5362
5402
|
|
|
5403
|
+
/* Switch */
|
|
5404
|
+
.set-uninstall {
|
|
5405
|
+
flex: none;
|
|
5406
|
+
display: inline-flex;
|
|
5407
|
+
align-items: center;
|
|
5408
|
+
gap: 4px;
|
|
5409
|
+
padding: 3px 8px;
|
|
5410
|
+
font-size: 11px;
|
|
5411
|
+
border-radius: 6px;
|
|
5412
|
+
border: 1px solid var(--border);
|
|
5413
|
+
background: transparent;
|
|
5414
|
+
color: var(--text-dim);
|
|
5415
|
+
cursor: pointer;
|
|
5416
|
+
transition: color 0.15s, border-color 0.15s;
|
|
5417
|
+
}
|
|
5418
|
+
.set-uninstall:hover {
|
|
5419
|
+
color: var(--red);
|
|
5420
|
+
border-color: var(--red);
|
|
5421
|
+
}
|
|
5422
|
+
.set-uninstall.confirm {
|
|
5423
|
+
color: var(--red);
|
|
5424
|
+
border-color: var(--red);
|
|
5425
|
+
font-weight: 600;
|
|
5426
|
+
}
|
|
5427
|
+
|
|
5363
5428
|
/* Switch */
|
|
5364
5429
|
.set-switch {
|
|
5365
5430
|
flex: none;
|