cli-jaw 1.5.0 → 1.6.2
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.ko.md +26 -19
- package/README.md +50 -22
- package/README.zh-CN.md +26 -19
- package/dist/bin/cli-jaw.js +5 -1
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/dispatch.js +57 -0
- package/dist/bin/commands/dispatch.js.map +1 -0
- package/dist/lib/mcp-sync.js +1 -1
- package/dist/lib/mcp-sync.js.map +1 -1
- package/dist/lib/upload.js +14 -1
- package/dist/lib/upload.js.map +1 -1
- package/dist/server.js +57 -42
- package/dist/server.js.map +1 -1
- package/dist/src/agent/events.js +72 -13
- package/dist/src/agent/events.js.map +1 -1
- package/dist/src/agent/spawn.js +71 -29
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/cli/acp-client.js +4 -2
- package/dist/src/cli/acp-client.js.map +1 -1
- package/dist/src/cli/commands.js +6 -6
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers.js +11 -9
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/registry.js +7 -1
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/core/config.js +15 -8
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +18 -3
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/employees.js +34 -0
- package/dist/src/core/employees.js.map +1 -0
- package/dist/src/discord/bot.js +72 -9
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +12 -8
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/orchestrator/distribute.js +57 -35
- package/dist/src/orchestrator/distribute.js.map +1 -1
- package/dist/src/orchestrator/gateway.js +3 -1
- package/dist/src/orchestrator/gateway.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +120 -27
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/research.js +5 -5
- package/dist/src/orchestrator/research.js.map +1 -1
- package/dist/src/orchestrator/scope.js +55 -0
- package/dist/src/orchestrator/scope.js.map +1 -0
- package/dist/src/orchestrator/state-machine.js +23 -21
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/orchestrator/worker-registry.js +9 -2
- package/dist/src/orchestrator/worker-registry.js.map +1 -1
- package/dist/src/prompt/builder.js +76 -37
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +40 -0
- package/dist/src/prompt/templates/employee.md +12 -4
- package/dist/src/prompt/templates/orchestration.md +17 -1
- package/dist/src/telegram/bot.js +7 -1
- package/dist/src/telegram/bot.js.map +1 -1
- package/package.json +4 -2
- package/public/assets/fonts/GeistVF.woff2 +0 -0
- package/public/assets/fonts/JetBrainsMono-Variable.woff2 +0 -0
- package/public/assets/providers/claude-color.svg +1 -0
- package/public/assets/providers/claude.svg +1 -0
- package/public/assets/providers/copilot-color.svg +1 -0
- package/public/assets/providers/copilot.svg +1 -0
- package/public/assets/providers/discord.svg +1 -0
- package/public/assets/providers/gemini-color.svg +1 -0
- package/public/assets/providers/gemini.svg +1 -0
- package/public/assets/providers/openai.svg +1 -0
- package/public/assets/providers/opencode.svg +1 -0
- package/public/assets/providers/telegram.svg +1 -0
- package/public/css/chat.css +79 -51
- package/public/css/diagram.css +348 -0
- package/public/css/layout.css +38 -11
- package/public/css/markdown.css +64 -18
- package/public/css/modals.css +3 -3
- package/public/css/orc-state.css +9 -9
- package/public/css/sidebar.css +37 -3
- package/public/css/tool-ui.css +46 -11
- package/public/css/variables.css +63 -63
- package/public/dist/assets/api-Bbmmo0o1.js +1 -0
- package/public/dist/assets/architecture-YZFGNWBL-BxiHqtOH.js +1 -0
- package/public/dist/assets/architectureDiagram-Q4EWVU46-CJTGDw5p.js +1 -0
- package/public/dist/assets/blockDiagram-DXYQGD6D-Btl5Y-Er.js +1 -0
- package/public/dist/assets/c4Diagram-AHTNJAMY-sDfjW4ZF.js +1 -0
- package/public/dist/assets/classDiagram-6PBFFD2Q-ByCgggL2.js +1 -0
- package/public/dist/assets/classDiagram-v2-HSJHXN6E-t1amaXkU.js +1 -0
- package/public/dist/assets/constants-C8_0OtK2.js +1 -0
- package/public/dist/assets/cose-bilkent-S5V4N54A-eOSFGdaE.js +1 -0
- package/public/dist/assets/dagre-KV5264BT-OCB5j8Q6.js +1 -0
- package/public/dist/assets/diagram-5BDNPKRD-C-4fgK5P.js +1 -0
- package/public/dist/assets/diagram-G4DWMVQ6-C6vmHKDV.js +1 -0
- package/public/dist/assets/diagram-MMDJMWI5-BMCo_wmt.js +1 -0
- package/public/dist/assets/diagram-TYMM5635-DaCLdttJ.js +1 -0
- package/public/dist/assets/employees-D5n7mX5v.js +39 -0
- package/public/dist/assets/erDiagram-SMLLAGMA-BiTRdm5s.js +1 -0
- package/public/dist/assets/flowDiagram-DWJPFMVM-sv6jVi0d.js +1 -0
- package/public/dist/assets/ganttDiagram-T4ZO3ILL-jCP3OW23.js +1 -0
- package/public/dist/assets/gitGraph-7Q5UKJZL-BIeKLxpm.js +1 -0
- package/public/dist/assets/gitGraphDiagram-UUTBAWPF-CokylnbW.js +1 -0
- package/public/dist/assets/index-CLd0BsAu.js +49 -0
- package/public/dist/assets/index-D6ci1wCN.css +1 -0
- package/public/dist/assets/info-OMHHGYJF-CIEl5dWF.js +1 -0
- package/public/dist/assets/infoDiagram-42DDH7IO-BBnK1Bh2.js +1 -0
- package/public/dist/assets/ishikawaDiagram-UXIWVN3A-qXIz0VhS.js +1 -0
- package/public/dist/assets/journeyDiagram-VCZTEJTY-BdrOLof3.js +1 -0
- package/public/dist/assets/kanban-definition-6JOO6SKY-CcX7CE04.js +1 -0
- package/public/dist/assets/mermaid.core-BoxIvw7E.js +1 -0
- package/public/dist/assets/mindmap-definition-QFDTVHPH-CdXARskX.js +1 -0
- package/public/dist/assets/packet-4T2RLAQJ-Bz4ZwYZ3.js +1 -0
- package/public/dist/assets/pie-ZZUOXDRM-AtGL3wZ2.js +1 -0
- package/public/dist/assets/pieDiagram-DEJITSTG-B5SOq9Yr.js +1 -0
- package/public/dist/assets/quadrantDiagram-34T5L4WZ-D0uNWgpI.js +1 -0
- package/public/dist/assets/radar-PYXPWWZC-AbJSfeqB.js +1 -0
- package/public/dist/assets/render-C8N0rp4L.js +25 -0
- package/public/dist/assets/requirementDiagram-MS252O5E-62td6IQ-.js +1 -0
- package/public/dist/assets/sankeyDiagram-XADWPNL6-Crg_02iC.js +1 -0
- package/public/dist/assets/sequenceDiagram-FGHM5R23-fbCocZHj.js +1 -0
- package/public/dist/assets/settings-BJR-IGM5.js +41 -0
- package/public/dist/assets/settings-Dm6OnPmY.js +1 -0
- package/public/dist/assets/skills-D6jv9AIs.js +1 -0
- package/public/dist/assets/skills-uywskdFh.js +12 -0
- package/public/dist/assets/slash-commands-CCDonT40.js +1 -0
- package/public/dist/assets/{slash-commands-DMsE88bu.js → slash-commands-DWvL-VDU.js} +1 -1
- package/public/dist/assets/stateDiagram-FHFEXIEX-Gy3DhXxQ.js +1 -0
- package/public/dist/assets/stateDiagram-v2-QKLJ7IA2-DJc-FW9O.js +1 -0
- package/public/dist/assets/timeline-definition-GMOUNBTQ-B3cA9DgY.js +1 -0
- package/public/dist/assets/treeView-SZITEDCU-Cn58DIbB.js +1 -0
- package/public/dist/assets/treemap-W4RFUUIX-DPcYjDzH.js +1 -0
- package/public/dist/assets/ui-C1daR00l.js +1 -0
- package/public/dist/assets/ui-D-oFkXed.js +131 -0
- package/public/dist/assets/vendor-icons-1Ec7ZWcT.js +1 -0
- package/public/dist/assets/{vendor-mermaid-COidH9HB.js → vendor-mermaid-lvHqQdfg.js} +4 -4
- package/public/dist/assets/vennDiagram-DHZGUBPP-DkBfxilo.js +1 -0
- package/public/dist/assets/wardley-RL74JXVD-6Dg0PJ4H.js +1 -0
- package/public/dist/assets/wardleyDiagram-NUSXRM2D-Dsntl_vy.js +1 -0
- package/public/dist/assets/ws-Dnn8HG8B.js +2 -0
- package/public/dist/assets/xychartDiagram-5P7HB3ND-B_McB5GE.js +1 -0
- package/public/dist/index.html +63 -55
- package/public/index.html +62 -53
- package/public/js/constants.ts +8 -2
- package/public/js/diagram/iframe-renderer.ts +559 -0
- package/public/js/diagram/types.ts +129 -0
- package/public/js/diagram/widget-validator.ts +82 -0
- package/public/js/features/chat.ts +24 -12
- package/public/js/features/employees.ts +3 -2
- package/public/js/features/heartbeat.ts +4 -3
- package/public/js/features/memory.ts +4 -3
- package/public/js/features/process-block.ts +12 -11
- package/public/js/features/settings-cli-status.ts +10 -7
- package/public/js/features/settings-core.ts +13 -3
- package/public/js/features/settings-discord.ts +9 -0
- package/public/js/features/settings-mcp.ts +8 -7
- package/public/js/features/settings-stt.ts +4 -4
- package/public/js/features/settings-templates.ts +10 -8
- package/public/js/features/settings-types.ts +1 -1
- package/public/js/features/settings.ts +1 -1
- package/public/js/features/sidebar.ts +37 -7
- package/public/js/features/skills.ts +4 -3
- package/public/js/features/theme.ts +4 -0
- package/public/js/features/tool-ui.ts +6 -5
- package/public/js/features/voice-recorder.ts +2 -1
- package/public/js/icons.ts +257 -0
- package/public/js/main.ts +9 -3
- package/public/js/provider-icons.ts +88 -0
- package/public/js/render.ts +493 -30
- package/public/js/streaming-render.ts +3 -3
- package/public/js/ui.ts +38 -18
- package/public/js/ws.ts +17 -10
- package/public/locales/en.json +89 -88
- package/public/locales/ko.json +89 -88
- package/scripts/release-1.6.0.sh +69 -0
- package/scripts/release-preview.sh +1 -1
- package/dist/lib/token-keepalive.js +0 -34
- package/dist/lib/token-keepalive.js.map +0 -1
- package/public/dist/assets/api-BlPw3bUI.js +0 -1
- package/public/dist/assets/architecture-YZFGNWBL-BkS7SZQi.js +0 -1
- package/public/dist/assets/architectureDiagram-Q4EWVU46-Dl3iBKeR.js +0 -1
- package/public/dist/assets/blockDiagram-DXYQGD6D-DPr4D17p.js +0 -1
- package/public/dist/assets/c4Diagram-AHTNJAMY-CfoxJtDk.js +0 -1
- package/public/dist/assets/classDiagram-6PBFFD2Q-BOAdHnnB.js +0 -1
- package/public/dist/assets/classDiagram-v2-HSJHXN6E-B2QCJXWC.js +0 -1
- package/public/dist/assets/constants-DshMUJbo.js +0 -1
- package/public/dist/assets/cose-bilkent-S5V4N54A-CA14jk7w.js +0 -1
- package/public/dist/assets/dagre-KV5264BT-BVwNaSwC.js +0 -1
- package/public/dist/assets/diagram-5BDNPKRD-CHqIAvdc.js +0 -1
- package/public/dist/assets/diagram-G4DWMVQ6-DxvsCvTP.js +0 -1
- package/public/dist/assets/diagram-MMDJMWI5-DqOPO7dl.js +0 -1
- package/public/dist/assets/diagram-TYMM5635-C9xMWPQn.js +0 -1
- package/public/dist/assets/employees-CFRlsbHm.js +0 -39
- package/public/dist/assets/erDiagram-SMLLAGMA-BTmpfRkm.js +0 -1
- package/public/dist/assets/flowDiagram-DWJPFMVM-DZBSQAKA.js +0 -1
- package/public/dist/assets/ganttDiagram-T4ZO3ILL-TAEMCRbT.js +0 -1
- package/public/dist/assets/gitGraph-7Q5UKJZL-bbxndRNA.js +0 -1
- package/public/dist/assets/gitGraphDiagram-UUTBAWPF-CBxtx0MB.js +0 -1
- package/public/dist/assets/index-1Gg-6jeC.css +0 -1
- package/public/dist/assets/index-CQHqXjrK.js +0 -49
- package/public/dist/assets/info-OMHHGYJF-DRXq_Ywr.js +0 -1
- package/public/dist/assets/infoDiagram-42DDH7IO-C7FFwX4m.js +0 -1
- package/public/dist/assets/ishikawaDiagram-UXIWVN3A-DEZJE79j.js +0 -1
- package/public/dist/assets/journeyDiagram-VCZTEJTY-ZHLiY6GV.js +0 -1
- package/public/dist/assets/kanban-definition-6JOO6SKY-NP7pY7ML.js +0 -1
- package/public/dist/assets/mermaid.core-CHuluSlD.js +0 -1
- package/public/dist/assets/mindmap-definition-QFDTVHPH-Bd1OgfN_.js +0 -1
- package/public/dist/assets/packet-4T2RLAQJ-CGu8ST97.js +0 -1
- package/public/dist/assets/pie-ZZUOXDRM-CDG65mhS.js +0 -1
- package/public/dist/assets/pieDiagram-DEJITSTG-BbMBHLDM.js +0 -1
- package/public/dist/assets/quadrantDiagram-34T5L4WZ-CDCDgI1e.js +0 -1
- package/public/dist/assets/radar-PYXPWWZC-1gS-6ylu.js +0 -1
- package/public/dist/assets/render-YHvL5VM_.js +0 -6
- package/public/dist/assets/requirementDiagram-MS252O5E-DkmvOtYz.js +0 -1
- package/public/dist/assets/sankeyDiagram-XADWPNL6-GNcXIYsL.js +0 -1
- package/public/dist/assets/sequenceDiagram-FGHM5R23-BFrw2n6x.js +0 -1
- package/public/dist/assets/settings-BQF_u5px.js +0 -37
- package/public/dist/assets/settings-C5Q9IPjJ.js +0 -1
- package/public/dist/assets/skills-9Pd2rOw-.js +0 -1
- package/public/dist/assets/skills-BI7sQAdk.js +0 -12
- package/public/dist/assets/slash-commands-FMpJzlDB.js +0 -1
- package/public/dist/assets/stateDiagram-FHFEXIEX-BLgKnllT.js +0 -1
- package/public/dist/assets/stateDiagram-v2-QKLJ7IA2-CbzuWsSa.js +0 -1
- package/public/dist/assets/timeline-definition-GMOUNBTQ-CcZTJ3gL.js +0 -1
- package/public/dist/assets/treeView-SZITEDCU-BDqBsaH1.js +0 -1
- package/public/dist/assets/treemap-W4RFUUIX-BaWHA3Di.js +0 -1
- package/public/dist/assets/ui-BukgLHuh.js +0 -29
- package/public/dist/assets/ui-Bvz1JfTE.js +0 -1
- package/public/dist/assets/vennDiagram-DHZGUBPP-BWV_j1bh.js +0 -1
- package/public/dist/assets/wardley-RL74JXVD-C7gKA3d7.js +0 -1
- package/public/dist/assets/wardleyDiagram-NUSXRM2D-BUoq_wug.js +0 -1
- package/public/dist/assets/ws-S_AZgx7L.js +0 -2
- package/public/dist/assets/xychartDiagram-5P7HB3ND-PvT5Ec82.js +0 -1
- /package/public/dist/assets/{api-B8XKQ4OT.js → api-Ci-lgwRp.js} +0 -0
- /package/public/dist/assets/{locale-BjoAcbis.js → locale-DIXc-_34.js} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
export interface PerCliConfig { model?: string; effort?: string; fastMode?: boolean; contextWindow?: boolean; contextWindowSize?: number; contextCompactLimit?: number; }
|
|
4
4
|
export interface TelegramConfig { enabled?: boolean; token?: string; allowedChatIds?: number[]; forwardAll?: boolean; }
|
|
5
|
-
export interface DiscordConfig { enabled?: boolean; token?: string; guildId?: string; channelIds?: string[]; forwardAll?: boolean; allowBots?: boolean; }
|
|
5
|
+
export interface DiscordConfig { enabled?: boolean; token?: string; guildId?: string; channelIds?: string[]; forwardAll?: boolean; allowBots?: boolean; mentionOnly?: boolean; }
|
|
6
6
|
export interface QuotaWindow { label: string; percent: number; resetsAt?: string | number | null; }
|
|
7
7
|
export interface QuotaEntry {
|
|
8
8
|
account?: { email?: string; type?: string; plan?: string; tier?: string };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// settings.ts — barrel re-export (preserves all import paths)
|
|
2
2
|
export { loadSettings, updateSettings, setPerm, getModelValue, handleModelSelect, applyCustomModel, onCliChange, saveActiveCliSettings, savePerCli } from './settings-core.js';
|
|
3
3
|
export { setTelegram, setForwardAll, saveTelegramSettings } from './settings-telegram.js';
|
|
4
|
-
export { setDiscord, setDiscordForwardAll, setDiscordAllowBots, saveDiscordSettings } from './settings-discord.js';
|
|
4
|
+
export { setDiscord, setDiscordForwardAll, setDiscordAllowBots, setDiscordMentionOnly, saveDiscordSettings } from './settings-discord.js';
|
|
5
5
|
export { setActiveChannel, loadFallbackOrder, saveFallbackOrder } from './settings-channel.js';
|
|
6
6
|
export { loadMcpServers, syncMcpServers, installMcpGlobal } from './settings-mcp.js';
|
|
7
7
|
export { loadCliStatus } from './settings-cli-status.js';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// ── Sidebar Collapse ──
|
|
2
2
|
// Toggle left/right sidebars. Responsive-aware:
|
|
3
|
-
// - Wide viewport (>900px):
|
|
4
|
-
// - Narrow viewport (≤900px): CSS auto-collapses,
|
|
3
|
+
// - Wide viewport (>900px): persist *-collapsed classes
|
|
4
|
+
// - Narrow viewport (≤900px): CSS auto-collapses, expanded panels are transient
|
|
5
|
+
|
|
6
|
+
import { ICONS } from '../icons.js';
|
|
5
7
|
|
|
6
8
|
interface SidebarState {
|
|
7
9
|
left?: boolean;
|
|
@@ -10,32 +12,60 @@ interface SidebarState {
|
|
|
10
12
|
|
|
11
13
|
const STORAGE_KEY = 'sidebarState';
|
|
12
14
|
const BREAKPOINT = 900;
|
|
15
|
+
const OVERLAY_BREAKPOINT = 768;
|
|
16
|
+
|
|
17
|
+
function isOverlayMode(): boolean {
|
|
18
|
+
return window.innerWidth <= OVERLAY_BREAKPOINT;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function clearExpandedPanels(): void {
|
|
22
|
+
document.body.classList.remove('left-expanded', 'right-expanded');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toggleExpandedPanel(side: 'left' | 'right'): void {
|
|
26
|
+
const ownClass = `${side}-expanded`;
|
|
27
|
+
const otherClass = side === 'left' ? 'right-expanded' : 'left-expanded';
|
|
28
|
+
const willOpen = !document.body.classList.contains(ownClass);
|
|
29
|
+
document.body.classList.remove(otherClass);
|
|
30
|
+
document.body.classList.toggle(ownClass, willOpen);
|
|
31
|
+
}
|
|
13
32
|
|
|
14
33
|
export function initSidebar(): void {
|
|
15
34
|
let saved: SidebarState = {};
|
|
16
35
|
try { saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); } catch { /* corrupted */ }
|
|
17
36
|
if (saved.left) document.body.classList.add('left-collapsed');
|
|
18
37
|
if (saved.right) document.body.classList.add('right-collapsed');
|
|
38
|
+
let wasOverlayMode = isOverlayMode();
|
|
19
39
|
|
|
20
40
|
document.getElementById('toggleLeft')?.addEventListener('click', toggleLeft);
|
|
21
41
|
document.getElementById('toggleRight')?.addEventListener('click', toggleRight);
|
|
22
42
|
|
|
23
43
|
// On resize: sync classes with viewport mode
|
|
24
44
|
window.addEventListener('resize', () => {
|
|
45
|
+
const overlayMode = isOverlayMode();
|
|
46
|
+
|
|
25
47
|
if (window.innerWidth > BREAKPOINT) {
|
|
26
|
-
|
|
48
|
+
clearExpandedPanels();
|
|
27
49
|
let s: SidebarState = {};
|
|
28
50
|
try { s = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); } catch { /* corrupted */ }
|
|
29
51
|
document.body.classList.toggle('left-collapsed', !!s.left);
|
|
30
52
|
document.body.classList.toggle('right-collapsed', !!s.right);
|
|
31
53
|
} else {
|
|
32
54
|
document.body.classList.remove('left-collapsed', 'right-collapsed');
|
|
55
|
+
if (overlayMode !== wasOverlayMode) {
|
|
56
|
+
clearExpandedPanels();
|
|
57
|
+
}
|
|
33
58
|
}
|
|
59
|
+
|
|
60
|
+
wasOverlayMode = overlayMode;
|
|
34
61
|
syncIcons();
|
|
35
62
|
});
|
|
36
63
|
|
|
37
64
|
if (window.innerWidth <= BREAKPOINT) {
|
|
38
65
|
document.body.classList.remove('left-collapsed', 'right-collapsed');
|
|
66
|
+
if (isOverlayMode()) {
|
|
67
|
+
clearExpandedPanels();
|
|
68
|
+
}
|
|
39
69
|
}
|
|
40
70
|
syncIcons();
|
|
41
71
|
}
|
|
@@ -46,7 +76,7 @@ function isNarrow(): boolean {
|
|
|
46
76
|
|
|
47
77
|
export function toggleLeft(): void {
|
|
48
78
|
if (isNarrow()) {
|
|
49
|
-
|
|
79
|
+
toggleExpandedPanel('left');
|
|
50
80
|
} else {
|
|
51
81
|
document.body.classList.toggle('left-collapsed');
|
|
52
82
|
}
|
|
@@ -56,7 +86,7 @@ export function toggleLeft(): void {
|
|
|
56
86
|
|
|
57
87
|
export function toggleRight(): void {
|
|
58
88
|
if (isNarrow()) {
|
|
59
|
-
|
|
89
|
+
toggleExpandedPanel('right');
|
|
60
90
|
} else {
|
|
61
91
|
document.body.classList.toggle('right-collapsed');
|
|
62
92
|
}
|
|
@@ -77,8 +107,8 @@ function isRightOpen(): boolean {
|
|
|
77
107
|
function syncIcons(): void {
|
|
78
108
|
const leftBtn = document.getElementById('toggleLeft');
|
|
79
109
|
const rightBtn = document.getElementById('toggleRight');
|
|
80
|
-
if (leftBtn) leftBtn.
|
|
81
|
-
if (rightBtn) rightBtn.
|
|
110
|
+
if (leftBtn) leftBtn.innerHTML = isLeftOpen() ? ICONS.chevronLeft : ICONS.chevronRight;
|
|
111
|
+
if (rightBtn) rightBtn.innerHTML = isRightOpen() ? ICONS.chevronRight : ICONS.chevronLeft;
|
|
82
112
|
}
|
|
83
113
|
|
|
84
114
|
function save(): void {
|
|
@@ -3,6 +3,7 @@ import { state } from '../state.js';
|
|
|
3
3
|
import { t, fetchWithLocale } from './i18n.js';
|
|
4
4
|
import { apiJson } from '../api.js';
|
|
5
5
|
import { escapeHtml } from '../render.js';
|
|
6
|
+
import { ICONS, emojiToIcon } from '../icons.js';
|
|
6
7
|
|
|
7
8
|
interface SkillItem {
|
|
8
9
|
id: string;
|
|
@@ -46,13 +47,13 @@ export function renderSkills(): void {
|
|
|
46
47
|
|
|
47
48
|
list.innerHTML = filtered.map(s => {
|
|
48
49
|
const reqParts: string[] = [];
|
|
49
|
-
if (s.requires?.env) reqParts.push(
|
|
50
|
-
if (s.requires?.bins) reqParts.push(
|
|
50
|
+
if (s.requires?.env) reqParts.push(`${ICONS.key} ` + s.requires.env.map(e => escapeHtml(e)).join(', '));
|
|
51
|
+
if (s.requires?.bins) reqParts.push(`${ICONS.settings} ` + s.requires.bins.map(b => escapeHtml(b)).join(', '));
|
|
51
52
|
if (s.install) reqParts.push(escapeHtml(s.install));
|
|
52
53
|
return `
|
|
53
54
|
<div class="skill-card ${s.enabled ? 'enabled' : ''}">
|
|
54
55
|
<div class="skill-card-header">
|
|
55
|
-
<span class="skill-emoji">${
|
|
56
|
+
<span class="skill-emoji">${s.emoji ? emojiToIcon(s.emoji) : ICONS.tool}</span>
|
|
56
57
|
<span class="skill-name">${escapeHtml(s.name || s.id)}</span>
|
|
57
58
|
<button class="skill-toggle ${s.enabled ? 'on' : 'off'}"
|
|
58
59
|
data-skill-id="${escapeHtml(s.id)}" data-skill-enabled="${s.enabled}"
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import githubDark from 'highlight.js/styles/github-dark.css?inline';
|
|
6
6
|
import githubLight from 'highlight.js/styles/github.css?inline';
|
|
7
|
+
import { broadcastThemeToIframes } from '../diagram/iframe-renderer.js';
|
|
8
|
+
import { rerenderMermaidDiagrams } from '../render.js';
|
|
7
9
|
|
|
8
10
|
const STORAGE_KEY = 'theme';
|
|
9
11
|
let hljsStyleEl: HTMLStyleElement | null = null;
|
|
@@ -43,4 +45,6 @@ function applyTheme(theme: string): void {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
applyHljsTheme(theme);
|
|
48
|
+
broadcastThemeToIframes();
|
|
49
|
+
rerenderMermaidDiagrams();
|
|
46
50
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Extracted from ui.ts for modularity.
|
|
4
4
|
|
|
5
5
|
import { escapeHtml } from '../render.js';
|
|
6
|
+
import { ICONS } from '../icons.js';
|
|
6
7
|
|
|
7
8
|
export interface ToolLogEntry {
|
|
8
9
|
icon: string;
|
|
@@ -42,7 +43,7 @@ function renderToolItem(tl: ToolLogEntry, idx: number): string {
|
|
|
42
43
|
<span class="tool-item-label">${label}</span>
|
|
43
44
|
${snippetHtml}
|
|
44
45
|
</span>
|
|
45
|
-
<span class="tool-item-chevron"
|
|
46
|
+
<span class="tool-item-chevron">${ICONS.chevronRight}</span>
|
|
46
47
|
</button>
|
|
47
48
|
<div class="tool-item-details collapsed" id="${detailId}">
|
|
48
49
|
<pre class="tool-item-full">${escapeHtml(detail)}</pre>
|
|
@@ -63,14 +64,14 @@ export function buildToolGroupHtml(toolLog: ToolLogEntry[]): string {
|
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
const summaryParts = Object.entries(counts)
|
|
66
|
-
.map(([icon, n]) => `${escapeHtml(icon)}
|
|
67
|
+
.map(([icon, n]) => `${escapeHtml(icon)}×${n}`)
|
|
67
68
|
.join(' ');
|
|
68
69
|
|
|
69
70
|
const toolId = `td-${Date.now()}`;
|
|
70
71
|
|
|
71
72
|
const logLines = toolLog.map((tl, i) => renderToolItem(tl, i)).join('');
|
|
72
73
|
|
|
73
|
-
return `<div class="tool-group"><button class="tool-group-summary" aria-expanded="false" aria-controls="${toolId}"><span class="tool-status-dot done"></span><span class="tool-group-summary-text">${summaryParts}</span><span class="tool-group-chevron"
|
|
74
|
+
return `<div class="tool-group"><button class="tool-group-summary" aria-expanded="false" aria-controls="${toolId}"><span class="tool-status-dot done"></span><span class="tool-group-summary-text">${summaryParts}</span><span class="tool-group-chevron">${ICONS.chevronDown}</span></button><div class="tool-details collapsed" id="${toolId}">${logLines}</div></div>`;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/** Bind expand/collapse click handlers for tool items within a container */
|
|
@@ -89,7 +90,7 @@ export function bindToolItemInteractions(root: HTMLElement): void {
|
|
|
89
90
|
details.classList.toggle('collapsed', !expanding);
|
|
90
91
|
wrapper.classList.toggle('expanded', expanding);
|
|
91
92
|
toggle.setAttribute('aria-expanded', expanding ? 'true' : 'false');
|
|
92
|
-
if (chevron) chevron.
|
|
93
|
+
if (chevron) chevron.innerHTML = expanding ? ICONS.chevronDown : ICONS.chevronRight;
|
|
93
94
|
});
|
|
94
95
|
root.dataset.toolItemBound = '1';
|
|
95
96
|
}
|
|
@@ -103,7 +104,7 @@ export function renderLiveToolActivity(msgDiv: HTMLElement, label: string): void
|
|
|
103
104
|
const content = msgDiv.querySelector('.msg-content');
|
|
104
105
|
if (content) content.before(liveEl);
|
|
105
106
|
}
|
|
106
|
-
liveEl.innerHTML = `<span class="tool-status-dot running"></span><span>${
|
|
107
|
+
liveEl.innerHTML = `<span class="tool-status-dot running"></span><span>${label}</span>`;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
/** Clean up all live tool activity indicators */
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { state } from '../state.js';
|
|
4
4
|
import { addSystemMsg } from '../ui.js';
|
|
5
5
|
import { t } from './i18n.js';
|
|
6
|
+
import { ICONS } from '../icons.js';
|
|
6
7
|
import { sendVoiceToServer } from './chat.js';
|
|
7
8
|
|
|
8
9
|
let cancelled = false;
|
|
@@ -165,7 +166,7 @@ function updateRecordingUI(recording: boolean): void {
|
|
|
165
166
|
const cancelBtn = document.getElementById('btnVoiceCancel');
|
|
166
167
|
if (btn) {
|
|
167
168
|
btn.classList.toggle('recording', recording);
|
|
168
|
-
btn.
|
|
169
|
+
btn.innerHTML = recording ? ICONS.stop : ICONS.mic;
|
|
169
170
|
btn.title = recording ? t('voice.stop') : t('voice.start');
|
|
170
171
|
}
|
|
171
172
|
if (cancelBtn) {
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// ── Icon System ──
|
|
2
|
+
// Central module for all UI icons. Replaces hardcoded emoji strings with
|
|
3
|
+
// Lucide SVG icons and custom SVGs. Every file that needs an icon imports
|
|
4
|
+
// from here — never use emoji literals in UI code.
|
|
5
|
+
|
|
6
|
+
import { buildLucideSvg } from '@lucide/icons/build';
|
|
7
|
+
import {
|
|
8
|
+
CircleCheck,
|
|
9
|
+
CircleX,
|
|
10
|
+
Wrench,
|
|
11
|
+
SkipForward,
|
|
12
|
+
Brain,
|
|
13
|
+
HeartPulse,
|
|
14
|
+
Lock,
|
|
15
|
+
LockOpen,
|
|
16
|
+
KeyRound,
|
|
17
|
+
Settings,
|
|
18
|
+
FileText,
|
|
19
|
+
Trash2,
|
|
20
|
+
TriangleAlert,
|
|
21
|
+
Lightbulb,
|
|
22
|
+
Search,
|
|
23
|
+
Globe,
|
|
24
|
+
Zap,
|
|
25
|
+
MessageSquare,
|
|
26
|
+
NotebookPen,
|
|
27
|
+
RefreshCw,
|
|
28
|
+
Mic,
|
|
29
|
+
Package,
|
|
30
|
+
ClipboardList,
|
|
31
|
+
Bot,
|
|
32
|
+
Palette,
|
|
33
|
+
Link,
|
|
34
|
+
HandMetal,
|
|
35
|
+
Paperclip,
|
|
36
|
+
Save,
|
|
37
|
+
Gamepad2,
|
|
38
|
+
House,
|
|
39
|
+
Radio,
|
|
40
|
+
FolderOpen,
|
|
41
|
+
Pencil,
|
|
42
|
+
ChartBar,
|
|
43
|
+
Hourglass,
|
|
44
|
+
Square,
|
|
45
|
+
X,
|
|
46
|
+
Send,
|
|
47
|
+
Check,
|
|
48
|
+
ChevronLeft,
|
|
49
|
+
ChevronRight,
|
|
50
|
+
ChevronDown,
|
|
51
|
+
ArrowLeft,
|
|
52
|
+
ArrowRight,
|
|
53
|
+
Copy,
|
|
54
|
+
Download,
|
|
55
|
+
} from '@lucide/icons';
|
|
56
|
+
|
|
57
|
+
// ── Size presets ──
|
|
58
|
+
const S = 14; // inline / small
|
|
59
|
+
const M = 16; // default UI
|
|
60
|
+
|
|
61
|
+
function luc(data: Parameters<typeof buildLucideSvg>[0], size = M): string {
|
|
62
|
+
return buildLucideSvg(data, { size });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Shark mascot (🦈 emoji — brand identity) ──
|
|
66
|
+
const SHARK_SVG = '🦈';
|
|
67
|
+
|
|
68
|
+
// ── Icon registry ──
|
|
69
|
+
// Keys match the semantic role, NOT the old emoji codepoint.
|
|
70
|
+
export const ICONS = {
|
|
71
|
+
// Status
|
|
72
|
+
check: luc(CircleCheck),
|
|
73
|
+
error: luc(CircleX),
|
|
74
|
+
warning: luc(TriangleAlert),
|
|
75
|
+
skip: luc(SkipForward),
|
|
76
|
+
|
|
77
|
+
// Tool activity
|
|
78
|
+
tool: luc(Wrench),
|
|
79
|
+
thinking: luc(MessageSquare),
|
|
80
|
+
search: luc(Search),
|
|
81
|
+
web: luc(Globe),
|
|
82
|
+
exec: luc(Zap),
|
|
83
|
+
compacting: luc(Package),
|
|
84
|
+
plan: luc(NotebookPen),
|
|
85
|
+
|
|
86
|
+
// App features
|
|
87
|
+
brain: luc(Brain),
|
|
88
|
+
heartPulse: luc(HeartPulse),
|
|
89
|
+
lock: luc(Lock),
|
|
90
|
+
lockOpen: luc(LockOpen),
|
|
91
|
+
key: luc(KeyRound),
|
|
92
|
+
settings: luc(Settings),
|
|
93
|
+
file: luc(FileText),
|
|
94
|
+
trash: luc(Trash2),
|
|
95
|
+
lightbulb: luc(Lightbulb),
|
|
96
|
+
refresh: luc(RefreshCw),
|
|
97
|
+
mic: luc(Mic),
|
|
98
|
+
clipboard: luc(ClipboardList),
|
|
99
|
+
robot: luc(Bot),
|
|
100
|
+
palette: luc(Palette),
|
|
101
|
+
link: luc(Link),
|
|
102
|
+
salute: luc(HandMetal),
|
|
103
|
+
|
|
104
|
+
// Mascot
|
|
105
|
+
shark: SHARK_SVG,
|
|
106
|
+
|
|
107
|
+
// HTML template icons
|
|
108
|
+
paperclip: luc(Paperclip),
|
|
109
|
+
save: luc(Save),
|
|
110
|
+
gamepad: luc(Gamepad2),
|
|
111
|
+
house: luc(House),
|
|
112
|
+
radio: luc(Radio),
|
|
113
|
+
folder: luc(FolderOpen),
|
|
114
|
+
pencil: luc(Pencil),
|
|
115
|
+
chart: luc(ChartBar),
|
|
116
|
+
hourglass: luc(Hourglass),
|
|
117
|
+
stop: luc(Square),
|
|
118
|
+
close: luc(X, S),
|
|
119
|
+
send: luc(Send),
|
|
120
|
+
copy: luc(Copy, S),
|
|
121
|
+
download: luc(Download, S),
|
|
122
|
+
checkSimple: luc(Check, S),
|
|
123
|
+
chevronLeft: luc(ChevronLeft, S),
|
|
124
|
+
chevronRight:luc(ChevronRight, S),
|
|
125
|
+
chevronDown: luc(ChevronDown, S),
|
|
126
|
+
arrowLeft: luc(ArrowLeft, S),
|
|
127
|
+
arrowRight: luc(ArrowRight, S),
|
|
128
|
+
} as const;
|
|
129
|
+
|
|
130
|
+
export type IconName = keyof typeof ICONS;
|
|
131
|
+
|
|
132
|
+
/** Get an icon SVG string by name, with optional size override. */
|
|
133
|
+
export function icon(name: IconName, size?: number): string {
|
|
134
|
+
if (!size || size === M) return ICONS[name];
|
|
135
|
+
// Regenerate at requested size
|
|
136
|
+
return iconMap[name]?.(size) ?? ICONS[name];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Size-override helpers (only for icons that support it) ──
|
|
140
|
+
const iconMap: Partial<Record<IconName, (s: number) => string>> = {
|
|
141
|
+
check: (s) => luc(CircleCheck, s),
|
|
142
|
+
error: (s) => luc(CircleX, s),
|
|
143
|
+
warning: (s) => luc(TriangleAlert, s),
|
|
144
|
+
skip: (s) => luc(SkipForward, s),
|
|
145
|
+
tool: (s) => luc(Wrench, s),
|
|
146
|
+
thinking: (s) => luc(MessageSquare, s),
|
|
147
|
+
search: (s) => luc(Search, s),
|
|
148
|
+
web: (s) => luc(Globe, s),
|
|
149
|
+
exec: (s) => luc(Zap, s),
|
|
150
|
+
compacting: (s) => luc(Package, s),
|
|
151
|
+
plan: (s) => luc(NotebookPen, s),
|
|
152
|
+
brain: (s) => luc(Brain, s),
|
|
153
|
+
heartPulse: (s) => luc(HeartPulse, s),
|
|
154
|
+
lock: (s) => luc(Lock, s),
|
|
155
|
+
lockOpen: (s) => luc(LockOpen, s),
|
|
156
|
+
key: (s) => luc(KeyRound, s),
|
|
157
|
+
settings: (s) => luc(Settings, s),
|
|
158
|
+
file: (s) => luc(FileText, s),
|
|
159
|
+
trash: (s) => luc(Trash2, s),
|
|
160
|
+
lightbulb: (s) => luc(Lightbulb, s),
|
|
161
|
+
refresh: (s) => luc(RefreshCw, s),
|
|
162
|
+
mic: (s) => luc(Mic, s),
|
|
163
|
+
clipboard: (s) => luc(ClipboardList, s),
|
|
164
|
+
robot: (s) => luc(Bot, s),
|
|
165
|
+
palette: (s) => luc(Palette, s),
|
|
166
|
+
link: (s) => luc(Link, s),
|
|
167
|
+
salute: (s) => luc(HandMetal, s),
|
|
168
|
+
paperclip: (s) => luc(Paperclip, s),
|
|
169
|
+
save: (s) => luc(Save, s),
|
|
170
|
+
gamepad: (s) => luc(Gamepad2, s),
|
|
171
|
+
house: (s) => luc(House, s),
|
|
172
|
+
radio: (s) => luc(Radio, s),
|
|
173
|
+
folder: (s) => luc(FolderOpen, s),
|
|
174
|
+
pencil: (s) => luc(Pencil, s),
|
|
175
|
+
chart: (s) => luc(ChartBar, s),
|
|
176
|
+
copy: (s) => luc(Copy, s),
|
|
177
|
+
download: (s) => luc(Download, s),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// ── Emoji → Icon name mapping (for server protocol backward compat) ──
|
|
181
|
+
const EMOJI_TO_ICON: Record<string, IconName> = {
|
|
182
|
+
'✅': 'check',
|
|
183
|
+
'❌': 'error',
|
|
184
|
+
'🔧': 'tool',
|
|
185
|
+
'⏭': 'skip',
|
|
186
|
+
'🧠': 'brain',
|
|
187
|
+
'💓': 'heartPulse',
|
|
188
|
+
'🔒': 'lock',
|
|
189
|
+
'🔓': 'lockOpen',
|
|
190
|
+
'🔑': 'key',
|
|
191
|
+
'⚙': 'settings',
|
|
192
|
+
'⚙️': 'settings',
|
|
193
|
+
'📄': 'file',
|
|
194
|
+
'🗑': 'trash',
|
|
195
|
+
'🗑️': 'trash',
|
|
196
|
+
'⚠': 'warning',
|
|
197
|
+
'⚠️': 'warning',
|
|
198
|
+
'💡': 'lightbulb',
|
|
199
|
+
'🦈': 'shark',
|
|
200
|
+
'💭': 'thinking',
|
|
201
|
+
'🔍': 'search',
|
|
202
|
+
'🌐': 'web',
|
|
203
|
+
'⚡': 'exec',
|
|
204
|
+
'🗜': 'compacting',
|
|
205
|
+
'🗜️': 'compacting',
|
|
206
|
+
'📝': 'plan',
|
|
207
|
+
'📑': 'clipboard',
|
|
208
|
+
'🤖': 'robot',
|
|
209
|
+
'📋': 'clipboard',
|
|
210
|
+
'🔄': 'refresh',
|
|
211
|
+
'🎨': 'palette',
|
|
212
|
+
'🎤': 'mic',
|
|
213
|
+
'🔗': 'link',
|
|
214
|
+
'🫡': 'salute',
|
|
215
|
+
'📎': 'paperclip',
|
|
216
|
+
'💾': 'save',
|
|
217
|
+
'🎮': 'gamepad',
|
|
218
|
+
'🏠': 'house',
|
|
219
|
+
'📡': 'radio',
|
|
220
|
+
'📂': 'folder',
|
|
221
|
+
'✏️': 'pencil',
|
|
222
|
+
'📊': 'chart',
|
|
223
|
+
'🎙️': 'mic',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/** Convert an emoji string to its icon SVG. Falls back to the emoji if unknown. */
|
|
227
|
+
export function emojiToIcon(emoji: string): string {
|
|
228
|
+
const name = EMOJI_TO_ICON[emoji];
|
|
229
|
+
return name ? ICONS[name] : emoji;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Check if a string is a known status emoji. */
|
|
233
|
+
export function isCompletionEmoji(emoji: string): boolean {
|
|
234
|
+
return emoji === '✅' || emoji === '❌';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Get semantic status from an emoji. */
|
|
238
|
+
export function emojiToStatus(emoji: string): 'done' | 'error' | null {
|
|
239
|
+
if (emoji === '✅') return 'done';
|
|
240
|
+
if (emoji === '❌') return 'error';
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Hydrate all `<span data-icon="NAME">` elements in a container (default: document.body).
|
|
246
|
+
* Call once after DOMContentLoaded to replace HTML icon placeholders with SVGs.
|
|
247
|
+
*/
|
|
248
|
+
export function hydrateIcons(root: Element = document.body): void {
|
|
249
|
+
const els = root.querySelectorAll<HTMLElement>('[data-icon]');
|
|
250
|
+
for (const el of els) {
|
|
251
|
+
const name = el.dataset.icon as IconName;
|
|
252
|
+
if (name && ICONS[name]) {
|
|
253
|
+
el.innerHTML = ICONS[name];
|
|
254
|
+
el.classList.add('icon-hydrated');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
package/public/js/main.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
saveActiveCliSettings, savePerCli, openPromptModal,
|
|
27
27
|
closePromptModal, savePromptFromModal, syncMcpServers, installMcpGlobal,
|
|
28
28
|
loadCliStatus, setTelegram, setForwardAll, saveTelegramSettings,
|
|
29
|
-
setDiscord, setDiscordForwardAll, setDiscordAllowBots, saveDiscordSettings, setActiveChannel,
|
|
29
|
+
setDiscord, setDiscordForwardAll, setDiscordAllowBots, setDiscordMentionOnly, saveDiscordSettings, setActiveChannel,
|
|
30
30
|
saveFallbackOrder,
|
|
31
31
|
openTemplateModal, saveTemplateFromModal, closeTemplateModal, templateGoBack, toggleDevMode
|
|
32
32
|
} from './features/settings.js';
|
|
@@ -53,6 +53,8 @@ import { initTheme } from './features/theme.js';
|
|
|
53
53
|
import { initGestures } from './features/gesture.js';
|
|
54
54
|
import { initI18n, setLang, getLang, t } from './features/i18n.js';
|
|
55
55
|
import { toggleRecording, cancelRecording } from './features/voice-recorder.js';
|
|
56
|
+
import { ICONS, hydrateIcons } from './icons.js';
|
|
57
|
+
import { hydrateProviderIcons } from './provider-icons.js';
|
|
56
58
|
|
|
57
59
|
// ── Chat Actions ──
|
|
58
60
|
document.getElementById('btnSend')?.addEventListener('click', sendMessage);
|
|
@@ -96,7 +98,7 @@ document.getElementById('langToggle')?.addEventListener('click', async () => {
|
|
|
96
98
|
const next = getLang() === 'ko' ? 'en' : 'ko';
|
|
97
99
|
await setLang(next);
|
|
98
100
|
const btn = document.getElementById('langToggle');
|
|
99
|
-
if (btn) btn.
|
|
101
|
+
if (btn) btn.innerHTML = `${ICONS.web} ${t('lang.' + next)}`;
|
|
100
102
|
// Reconnect WS with new locale
|
|
101
103
|
if (state.ws) { state.ws.close(); }
|
|
102
104
|
});
|
|
@@ -185,6 +187,8 @@ document.getElementById('dcForwardOff')?.addEventListener('click', () => setDisc
|
|
|
185
187
|
document.getElementById('dcForwardOn')?.addEventListener('click', () => setDiscordForwardAll(true));
|
|
186
188
|
document.getElementById('dcAllowBotsOff')?.addEventListener('click', () => setDiscordAllowBots(false));
|
|
187
189
|
document.getElementById('dcAllowBotsOn')?.addEventListener('click', () => setDiscordAllowBots(true));
|
|
190
|
+
document.getElementById('dcMentionOff')?.addEventListener('click', () => setDiscordMentionOnly(false));
|
|
191
|
+
document.getElementById('dcMentionOn')?.addEventListener('click', () => setDiscordMentionOnly(true));
|
|
188
192
|
document.getElementById('dcToken')?.addEventListener('change', saveDiscordSettings);
|
|
189
193
|
document.getElementById('dcGuildId')?.addEventListener('change', saveDiscordSettings);
|
|
190
194
|
document.getElementById('dcChannelIds')?.addEventListener('change', saveDiscordSettings);
|
|
@@ -400,9 +404,11 @@ document.getElementById('basicMemoryFiles')?.addEventListener('click', (e) => {
|
|
|
400
404
|
|
|
401
405
|
// ── Init ──
|
|
402
406
|
async function bootstrap(): Promise<void> {
|
|
407
|
+
hydrateIcons();
|
|
408
|
+
hydrateProviderIcons();
|
|
403
409
|
await initI18n();
|
|
404
410
|
const langBtn = document.getElementById('langToggle');
|
|
405
|
-
if (langBtn) langBtn.
|
|
411
|
+
if (langBtn) langBtn.innerHTML = `${ICONS.web} ${t('lang.' + getLang())}`;
|
|
406
412
|
await loadCliRegistry();
|
|
407
413
|
bindPerCliControlEvents();
|
|
408
414
|
connect();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// ── Provider Icons ──
|
|
2
|
+
// AI provider brand icons from lobehub/icons-static-svg.
|
|
3
|
+
// SVGs are downloaded locally for offline support and bundled via Vite ?raw.
|
|
4
|
+
|
|
5
|
+
import claudeSvg from '../assets/providers/claude-color.svg?raw';
|
|
6
|
+
import openaiSvg from '../assets/providers/openai.svg?raw';
|
|
7
|
+
import geminiSvg from '../assets/providers/gemini-color.svg?raw';
|
|
8
|
+
import copilotSvg from '../assets/providers/copilot-color.svg?raw';
|
|
9
|
+
|
|
10
|
+
// Mono variants for dark/light mode flexibility
|
|
11
|
+
import claudeMonoSvg from '../assets/providers/claude.svg?raw';
|
|
12
|
+
import geminiMonoSvg from '../assets/providers/gemini.svg?raw';
|
|
13
|
+
import copilotMonoSvg from '../assets/providers/copilot.svg?raw';
|
|
14
|
+
|
|
15
|
+
// Service icons (Discord, Telegram)
|
|
16
|
+
import discordSvg from '../assets/providers/discord.svg?raw';
|
|
17
|
+
import telegramSvg from '../assets/providers/telegram.svg?raw';
|
|
18
|
+
import opencodeSvg from '../assets/providers/opencode.svg?raw';
|
|
19
|
+
|
|
20
|
+
export type ProviderSlug = 'claude' | 'openai' | 'gemini' | 'copilot' | 'codex' | 'opencode' | 'discord' | 'telegram';
|
|
21
|
+
|
|
22
|
+
interface ProviderIcon {
|
|
23
|
+
color: string;
|
|
24
|
+
mono: string;
|
|
25
|
+
label: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PROVIDER_ICONS: Record<ProviderSlug, ProviderIcon> = {
|
|
29
|
+
claude: { color: claudeSvg, mono: claudeMonoSvg, label: 'Claude' },
|
|
30
|
+
openai: { color: openaiSvg, mono: openaiSvg, label: 'OpenAI' },
|
|
31
|
+
gemini: { color: geminiSvg, mono: geminiMonoSvg, label: 'Gemini' },
|
|
32
|
+
copilot: { color: copilotSvg, mono: copilotMonoSvg, label: 'GitHub Copilot' },
|
|
33
|
+
codex: { color: openaiSvg, mono: openaiSvg, label: 'Codex (OpenAI)' },
|
|
34
|
+
opencode: { color: opencodeSvg, mono: opencodeSvg, label: 'OpenCode' },
|
|
35
|
+
discord: { color: discordSvg, mono: discordSvg, label: 'Discord' },
|
|
36
|
+
telegram: { color: telegramSvg, mono: telegramSvg, label: 'Telegram' },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Get a provider icon SVG string. Returns color variant by default. */
|
|
40
|
+
export function providerIcon(slug: string, variant: 'color' | 'mono' = 'color'): string {
|
|
41
|
+
const normalized = slug.toLowerCase().replace(/[-_\s]/g, '');
|
|
42
|
+
// Handle aliases
|
|
43
|
+
let key: ProviderSlug;
|
|
44
|
+
if (normalized === 'claude' || normalized.startsWith('claude')) key = 'claude';
|
|
45
|
+
else if (normalized === 'gemini' || normalized.startsWith('gemini')) key = 'gemini';
|
|
46
|
+
else if (normalized.startsWith('copilot') || normalized === 'githubcopilot') key = 'copilot';
|
|
47
|
+
else if (normalized === 'codex') key = 'codex';
|
|
48
|
+
else if (normalized === 'opencode') key = 'opencode';
|
|
49
|
+
else if (normalized === 'openai' || normalized.startsWith('gpt') || normalized.startsWith('o1') || normalized.startsWith('o3') || normalized.startsWith('o4')) key = 'openai';
|
|
50
|
+
else if (normalized === 'discord') key = 'discord';
|
|
51
|
+
else if (normalized === 'telegram') key = 'telegram';
|
|
52
|
+
else return '';
|
|
53
|
+
|
|
54
|
+
const entry = PROVIDER_ICONS[key];
|
|
55
|
+
return variant === 'mono' ? entry.mono : entry.color;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Hydrate all `<span data-provider="SLUG">` elements with provider SVG icons.
|
|
60
|
+
* Call once after DOMContentLoaded.
|
|
61
|
+
*/
|
|
62
|
+
export function hydrateProviderIcons(root: Element = document.body): void {
|
|
63
|
+
const els = root.querySelectorAll<HTMLElement>('[data-provider]');
|
|
64
|
+
for (const el of els) {
|
|
65
|
+
const slug = el.dataset.provider || '';
|
|
66
|
+
const svg = providerIcon(slug);
|
|
67
|
+
if (svg) {
|
|
68
|
+
el.innerHTML = svg;
|
|
69
|
+
el.classList.add('cli-provider-icon');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Get a provider's display label. */
|
|
75
|
+
export function providerLabel(slug: string): string {
|
|
76
|
+
const normalized = slug.toLowerCase().replace(/[-_\s]/g, '');
|
|
77
|
+
let key: ProviderSlug;
|
|
78
|
+
if (normalized === 'claude' || normalized.startsWith('claude')) key = 'claude';
|
|
79
|
+
else if (normalized === 'gemini' || normalized.startsWith('gemini')) key = 'gemini';
|
|
80
|
+
else if (normalized.startsWith('copilot') || normalized === 'githubcopilot') key = 'copilot';
|
|
81
|
+
else if (normalized === 'codex') key = 'codex';
|
|
82
|
+
else if (normalized === 'opencode') key = 'opencode';
|
|
83
|
+
else if (normalized === 'openai' || normalized.startsWith('gpt') || normalized.startsWith('o1') || normalized.startsWith('o3') || normalized.startsWith('o4')) key = 'openai';
|
|
84
|
+
else if (normalized === 'discord') key = 'discord';
|
|
85
|
+
else if (normalized === 'telegram') key = 'telegram';
|
|
86
|
+
else return slug;
|
|
87
|
+
return PROVIDER_ICONS[key].label;
|
|
88
|
+
}
|