llm-deep-trace 0.1.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/LICENSE +21 -0
- package/README.md +159 -0
- package/bin/llm-deep-trace.js +24 -0
- package/next.config.ts +8 -0
- package/package.json +56 -0
- package/postcss.config.mjs +5 -0
- package/public/banner-v2.png +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.png +0 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agent-config/route.ts +31 -0
- package/src/app/api/all-sessions/route.ts +9 -0
- package/src/app/api/analytics/route.ts +379 -0
- package/src/app/api/detect-agents/route.ts +170 -0
- package/src/app/api/image/route.ts +73 -0
- package/src/app/api/search/route.ts +28 -0
- package/src/app/api/session-by-key/route.ts +21 -0
- package/src/app/api/sessions/[sessionId]/messages/route.ts +46 -0
- package/src/app/api/sse/route.ts +86 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +3518 -0
- package/src/app/icon.svg +4 -0
- package/src/app/layout.tsx +20 -0
- package/src/app/page.tsx +5 -0
- package/src/components/AnalyticsDashboard.tsx +393 -0
- package/src/components/App.tsx +243 -0
- package/src/components/CopyButton.tsx +42 -0
- package/src/components/Logo.tsx +20 -0
- package/src/components/MainPanel.tsx +1128 -0
- package/src/components/MessageRenderer.tsx +983 -0
- package/src/components/SessionTree.tsx +505 -0
- package/src/components/SettingsPanel.tsx +160 -0
- package/src/components/SetupView.tsx +206 -0
- package/src/components/Sidebar.tsx +714 -0
- package/src/components/ThemeToggle.tsx +54 -0
- package/src/lib/client-utils.ts +360 -0
- package/src/lib/normalizers.ts +371 -0
- package/src/lib/sessions.ts +1223 -0
- package/src/lib/store.ts +518 -0
- package/src/lib/types.ts +112 -0
- package/src/lib/useSSE.ts +81 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useStore } from "@/lib/store";
|
|
4
|
+
|
|
5
|
+
const SystemIcon = () => (
|
|
6
|
+
<svg width="11" height="11" viewBox="0 0 16 16" fill="none">
|
|
7
|
+
<rect x="1" y="2" width="14" height="10" rx="1.5" stroke="currentColor" strokeWidth="1.4" />
|
|
8
|
+
<path d="M5 14h6M8 12v2" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
|
|
9
|
+
</svg>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const DarkIcon = () => (
|
|
13
|
+
<svg width="11" height="11" viewBox="0 0 16 16" fill="none">
|
|
14
|
+
<path d="M13.5 10.5A6 6 0 115.5 2.5a5 5 0 008 8z" fill="currentColor" />
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const LightIcon = () => (
|
|
19
|
+
<svg width="11" height="11" viewBox="0 0 16 16" fill="none">
|
|
20
|
+
<circle cx="8" cy="8" r="3.5" stroke="currentColor" strokeWidth="1.4" />
|
|
21
|
+
<path
|
|
22
|
+
d="M8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2M3.7 3.7l1.4 1.4M10.9 10.9l1.4 1.4M3.7 12.3l1.4-1.4M10.9 5.1l1.4-1.4"
|
|
23
|
+
stroke="currentColor"
|
|
24
|
+
strokeWidth="1.3"
|
|
25
|
+
strokeLinecap="round"
|
|
26
|
+
/>
|
|
27
|
+
</svg>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export default function ThemeToggle() {
|
|
31
|
+
const theme = useStore((s) => s.theme);
|
|
32
|
+
const setTheme = useStore((s) => s.setTheme);
|
|
33
|
+
|
|
34
|
+
const buttons = [
|
|
35
|
+
{ key: "system", icon: <SystemIcon />, title: "System default" },
|
|
36
|
+
{ key: "dark", icon: <DarkIcon />, title: "Dark" },
|
|
37
|
+
{ key: "light", icon: <LightIcon />, title: "Light" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="theme-toggle" title="Switch theme">
|
|
42
|
+
{buttons.map(({ key, icon, title }) => (
|
|
43
|
+
<button
|
|
44
|
+
key={key}
|
|
45
|
+
onClick={() => setTheme(key)}
|
|
46
|
+
title={title}
|
|
47
|
+
className={`theme-btn ${theme === key ? "active" : "inactive"}`}
|
|
48
|
+
>
|
|
49
|
+
{icon}
|
|
50
|
+
</button>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { marked } from "marked";
|
|
2
|
+
import hljs from "highlight.js";
|
|
3
|
+
|
|
4
|
+
// Configure marked
|
|
5
|
+
marked.setOptions({
|
|
6
|
+
breaks: true,
|
|
7
|
+
gfm: true,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// Custom renderer for code highlighting
|
|
11
|
+
const renderer = new marked.Renderer();
|
|
12
|
+
renderer.code = function ({ text, lang }: { text: string; lang?: string }) {
|
|
13
|
+
let highlighted = text;
|
|
14
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
15
|
+
try {
|
|
16
|
+
highlighted = hljs.highlight(text, { language: lang }).value;
|
|
17
|
+
} catch {
|
|
18
|
+
// fallback
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
try {
|
|
22
|
+
highlighted = hljs.highlightAuto(text).value;
|
|
23
|
+
} catch {
|
|
24
|
+
// fallback
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return `<pre><code class="hljs${lang ? " language-" + lang : ""}">${highlighted}</code></pre>`;
|
|
28
|
+
};
|
|
29
|
+
marked.use({ renderer });
|
|
30
|
+
|
|
31
|
+
export function esc(text: string): string {
|
|
32
|
+
if (!text) return "";
|
|
33
|
+
const el = document.createElement("span");
|
|
34
|
+
el.textContent = text;
|
|
35
|
+
return el.innerHTML;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function relativeTime(ts: number): string {
|
|
39
|
+
if (!ts) return "";
|
|
40
|
+
const diff = Date.now() - ts;
|
|
41
|
+
const s = Math.floor(diff / 1000);
|
|
42
|
+
if (s < 60) return "just now";
|
|
43
|
+
const m = Math.floor(s / 60);
|
|
44
|
+
if (m < 60) return m + "m";
|
|
45
|
+
const h = Math.floor(m / 60);
|
|
46
|
+
if (h < 24) return h + "h";
|
|
47
|
+
const d = Math.floor(h / 24);
|
|
48
|
+
if (d === 1) return "yesterday";
|
|
49
|
+
if (d < 30) return d + "d";
|
|
50
|
+
return new Date(ts).toLocaleDateString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function fmtTime(ts: string | undefined): string {
|
|
54
|
+
if (!ts) return "";
|
|
55
|
+
try {
|
|
56
|
+
return new Date(ts).toLocaleTimeString([], {
|
|
57
|
+
hour: "2-digit",
|
|
58
|
+
minute: "2-digit",
|
|
59
|
+
second: "2-digit",
|
|
60
|
+
});
|
|
61
|
+
} catch {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function fileExt(filepath: string): string {
|
|
67
|
+
if (!filepath) return "";
|
|
68
|
+
const m = filepath.match(/\.(\w+)$/);
|
|
69
|
+
return m ? m[1] : "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function extToLang(ext: string): string {
|
|
73
|
+
const map: Record<string, string> = {
|
|
74
|
+
js: "javascript", ts: "typescript", tsx: "typescript", jsx: "javascript",
|
|
75
|
+
py: "python", rb: "ruby", rs: "rust", go: "go", java: "java",
|
|
76
|
+
c: "c", cpp: "cpp", h: "c", hpp: "cpp", cs: "csharp",
|
|
77
|
+
sh: "bash", bash: "bash", zsh: "bash", fish: "bash",
|
|
78
|
+
json: "json", yaml: "yaml", yml: "yaml", toml: "toml",
|
|
79
|
+
xml: "xml", html: "html", css: "css", scss: "scss",
|
|
80
|
+
md: "markdown", sql: "sql",
|
|
81
|
+
tf: "hcl", hcl: "hcl", lua: "lua",
|
|
82
|
+
swift: "swift", kt: "kotlin", scala: "scala",
|
|
83
|
+
r: "r", php: "php", pl: "perl", ex: "elixir", exs: "elixir",
|
|
84
|
+
};
|
|
85
|
+
return map[(ext || "").toLowerCase()] || ext;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function truncStr(text: string, max: number): { t: string; trunc: boolean } {
|
|
89
|
+
if (!text) return { t: "", trunc: false };
|
|
90
|
+
if (text.length <= max) return { t: text, trunc: false };
|
|
91
|
+
return { t: text.slice(0, max), trunc: true };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function stripConversationMeta(text: string): string {
|
|
95
|
+
if (!text) return text;
|
|
96
|
+
if (!text.startsWith("Conversation info")) return text;
|
|
97
|
+
const idx = text.indexOf("```\n\n");
|
|
98
|
+
if (idx !== -1) return text.slice(idx + 5).trim();
|
|
99
|
+
const idx2 = text.indexOf("```\n");
|
|
100
|
+
if (idx2 !== -1) return text.slice(idx2 + 4).trim();
|
|
101
|
+
return text;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function renderMarkdown(rawText: string): string {
|
|
105
|
+
if (!rawText) return "";
|
|
106
|
+
try {
|
|
107
|
+
return marked.parse(rawText) as string;
|
|
108
|
+
} catch {
|
|
109
|
+
return esc(rawText).replace(/\n/g, "<br>");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function highlightCode(code: string, lang: string): string {
|
|
114
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
115
|
+
try {
|
|
116
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
117
|
+
} catch {
|
|
118
|
+
// fallback
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return esc(code);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function syntaxHighlightJson(json: string): string {
|
|
125
|
+
const escaped = esc(json);
|
|
126
|
+
return escaped.replace(
|
|
127
|
+
/("(?:[^"\\]|\\.)*")(\s*:)|("(?:[^"\\]|\\.)*")|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(\btrue\b|\bfalse\b)|(\bnull\b)/g,
|
|
128
|
+
(match, key, colon, str, num, bool, nil) => {
|
|
129
|
+
if (key && colon) return `<span class="jk">${key}</span>${colon}`;
|
|
130
|
+
if (str) return `<span class="js">${str}</span>`;
|
|
131
|
+
if (num !== undefined && num !== "") return `<span class="jn">${num}</span>`;
|
|
132
|
+
if (bool) return `<span class="jb">${bool}</span>`;
|
|
133
|
+
if (nil) return `<span class="jnl">${nil}</span>`;
|
|
134
|
+
return match;
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function extractText(content: unknown): string {
|
|
140
|
+
if (!content) return "";
|
|
141
|
+
if (typeof content === "string") return content;
|
|
142
|
+
if (Array.isArray(content)) {
|
|
143
|
+
return content
|
|
144
|
+
.filter((b) => b && b.type === "text")
|
|
145
|
+
.map((b) => b.text || "")
|
|
146
|
+
.join("\n");
|
|
147
|
+
}
|
|
148
|
+
return "";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function extractResultText(content: unknown): string {
|
|
152
|
+
if (!content) return "";
|
|
153
|
+
if (typeof content === "string") return content;
|
|
154
|
+
if (Array.isArray(content)) {
|
|
155
|
+
return content
|
|
156
|
+
.map((b) => {
|
|
157
|
+
if (!b) return "";
|
|
158
|
+
if (typeof b === "string") return b;
|
|
159
|
+
if (b.type === "text") return b.text || "";
|
|
160
|
+
if (b.content)
|
|
161
|
+
return typeof b.content === "string"
|
|
162
|
+
? b.content
|
|
163
|
+
: JSON.stringify(b.content);
|
|
164
|
+
return JSON.stringify(b);
|
|
165
|
+
})
|
|
166
|
+
.join("\n");
|
|
167
|
+
}
|
|
168
|
+
if (typeof content === "object" && content !== null) {
|
|
169
|
+
const obj = content as Record<string, unknown>;
|
|
170
|
+
return (obj.text as string) || (obj.content as string) || JSON.stringify(content);
|
|
171
|
+
}
|
|
172
|
+
return String(content);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function looksLikeMarkdown(text: string): boolean {
|
|
176
|
+
if (text.length < 200) return false;
|
|
177
|
+
return (
|
|
178
|
+
/^#{1,6} /m.test(text) ||
|
|
179
|
+
/^\|.+\|/m.test(text) ||
|
|
180
|
+
/\*\*.+?\*\*/m.test(text) ||
|
|
181
|
+
/^---/m.test(text)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function cleanPreview(text: string): string {
|
|
186
|
+
if (!text) return "";
|
|
187
|
+
return text
|
|
188
|
+
.replace(/^#{1,6}\s+/gm, "")
|
|
189
|
+
.replace(/\*\*(.+?)\*\*/g, "$1")
|
|
190
|
+
.replace(/\*(.+?)\*/g, "$1")
|
|
191
|
+
.replace(/`{1,3}[^`]*`{1,3}/g, "")
|
|
192
|
+
.replace(/!\[.*?\]\(.*?\)/g, "")
|
|
193
|
+
.replace(/\[(.+?)\]\(.*?\)/g, "$1")
|
|
194
|
+
.replace(/^[-*+]\s+/gm, "")
|
|
195
|
+
.replace(/^\d+\.\s+/gm, "")
|
|
196
|
+
.replace(/^>\s+/gm, "")
|
|
197
|
+
.replace(/[\u{1F000}-\u{1FFFF}]/gu, "")
|
|
198
|
+
.replace(/[\u2600-\u27BF\uFE00-\uFE0F]/g, "")
|
|
199
|
+
.replace(/\s{2,}/g, " ")
|
|
200
|
+
.replace(/\n+/g, " ")
|
|
201
|
+
.trim();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Simple Icons brand SVGs (CC0) — all 24×24 viewBox, rendered at 12×12
|
|
205
|
+
const CHANNEL_SVGS: Record<string, { path: string; color: string }> = {
|
|
206
|
+
telegram: {
|
|
207
|
+
color: "#26A5E4",
|
|
208
|
+
path: "M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z",
|
|
209
|
+
},
|
|
210
|
+
whatsapp: {
|
|
211
|
+
color: "#25D366",
|
|
212
|
+
path: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z",
|
|
213
|
+
},
|
|
214
|
+
discord: {
|
|
215
|
+
color: "#5865F2",
|
|
216
|
+
path: "M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z",
|
|
217
|
+
},
|
|
218
|
+
signal: {
|
|
219
|
+
color: "#3A76F0",
|
|
220
|
+
path: "M12 0q-.934 0-1.83.139l.17 1.111a11 11 0 0 1 3.32 0l.172-1.111A12 12 0 0 0 12 0M9.152.34A12 12 0 0 0 5.77 1.742l.584.961a10.8 10.8 0 0 1 3.066-1.27zm5.696 0-.268 1.094a10.8 10.8 0 0 1 3.066 1.27l.584-.962A12 12 0 0 0 14.848.34M12 2.25a9.75 9.75 0 0 0-8.539 14.459c.074.134.1.292.064.441l-1.013 4.338 4.338-1.013a.62.62 0 0 1 .441.064A9.7 9.7 0 0 0 12 21.75c5.385 0 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25m-7.092.068a12 12 0 0 0-2.59 2.59l.909.664a11 11 0 0 1 2.345-2.345zm14.184 0-.664.909a11 11 0 0 1 2.345 2.345l.909-.664a12 12 0 0 0-2.59-2.59M1.742 5.77A12 12 0 0 0 .34 9.152l1.094.268a10.8 10.8 0 0 1 1.269-3.066zm20.516 0-.961.584a10.8 10.8 0 0 1 1.27 3.066l1.093-.268a12 12 0 0 0-1.402-3.383M.138 10.168A12 12 0 0 0 0 12q0 .934.139 1.83l1.111-.17A11 11 0 0 1 1.125 12q0-.848.125-1.66zm23.723.002-1.111.17q.125.812.125 1.66c0 .848-.042 1.12-.125 1.66l1.111.172a12.1 12.1 0 0 0 0-3.662M1.434 14.58l-1.094.268a12 12 0 0 0 .96 2.591l-.265 1.14 1.096.255.36-1.539-.188-.365a10.8 10.8 0 0 1-.87-2.35m21.133 0a10.8 10.8 0 0 1-1.27 3.067l.962.584a12 12 0 0 0 1.402-3.383zm-1.793 3.848a11 11 0 0 1-2.345 2.345l.664.909a12 12 0 0 0 2.59-2.59zm-19.959 1.1L.357 21.48a1.8 1.8 0 0 0 2.162 2.161l1.954-.455-.256-1.095-1.953.455a.675.675 0 0 1-.81-.81l.454-1.954zm16.832 1.769a10.8 10.8 0 0 1-3.066 1.27l.268 1.093a12 12 0 0 0 3.382-1.402zm-10.94.213-1.54.36.256 1.095 1.139-.266c.814.415 1.683.74 2.591.961l.268-1.094a10.8 10.8 0 0 1-2.35-.869zm3.634 1.24-.172 1.111a12.1 12.1 0 0 0 3.662 0l-.17-1.111q-.812.125-1.66.125a11 11 0 0 1-1.66-.125",
|
|
221
|
+
},
|
|
222
|
+
slack: {
|
|
223
|
+
color: "#4A154B",
|
|
224
|
+
path: "M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z",
|
|
225
|
+
},
|
|
226
|
+
imessage: {
|
|
227
|
+
color: "#1FCA41",
|
|
228
|
+
path: "M5.285 0A5.273 5.273 0 0 0 0 5.285v13.43A5.273 5.273 0 0 0 5.285 24h13.43A5.273 5.273 0 0 0 24 18.715V5.285A5.273 5.273 0 0 0 18.715 0ZM12 4.154a8.809 7.337 0 0 1 8.809 7.338A8.809 7.337 0 0 1 12 18.828a8.809 7.337 0 0 1-2.492-.303A8.656 7.337 0 0 1 5.93 19.93a9.929 7.337 0 0 0 1.54-2.155 8.809 7.337 0 0 1-4.279-6.283A8.809 7.337 0 0 1 12 4.154",
|
|
229
|
+
},
|
|
230
|
+
googlechat: {
|
|
231
|
+
color: "#00AC47",
|
|
232
|
+
path: "M1.637 0C.733 0 0 .733 0 1.637v16.5c0 .904.733 1.636 1.637 1.636h3.955v3.323c0 .804.97 1.207 1.539.638l3.963-3.96h11.27c.903 0 1.636-.733 1.636-1.637V5.592L18.408 0Zm3.955 5.592h12.816v8.59H8.455l-2.863 2.863Z",
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
function brandSvg(ch: string): string {
|
|
237
|
+
const icon = CHANNEL_SVGS[ch];
|
|
238
|
+
if (!icon) return "";
|
|
239
|
+
return `<svg width="12" height="12" viewBox="0 0 24 24" fill="${icon.color}" style="vertical-align:-2px;flex-shrink:0"><path d="${icon.path}"/></svg>`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function channelIcon(ch: string): string {
|
|
243
|
+
if (!ch) return "";
|
|
244
|
+
if (CHANNEL_SVGS[ch]) return brandSvg(ch);
|
|
245
|
+
if (ch.startsWith("codex"))
|
|
246
|
+
return `<svg width="10" height="10" viewBox="0 0 16 16" fill="none" style="vertical-align:-1px"><path d="M5 3l6 5-6 5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
247
|
+
if (ch === "claude-code")
|
|
248
|
+
return `<svg width="10" height="10" viewBox="0 0 16 16" fill="none" style="vertical-align:-1px"><circle cx="8" cy="8" r="5" stroke="currentColor" stroke-width="1.5"/><circle cx="8" cy="8" r="2" fill="currentColor"/></svg>`;
|
|
249
|
+
return "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function sessionLabel(s: {
|
|
253
|
+
key: string;
|
|
254
|
+
label?: string;
|
|
255
|
+
title?: string;
|
|
256
|
+
sessionId: string;
|
|
257
|
+
source?: string;
|
|
258
|
+
preview?: string;
|
|
259
|
+
cwd?: string;
|
|
260
|
+
isSubagent?: boolean;
|
|
261
|
+
}): string {
|
|
262
|
+
const src = s.source || "kova";
|
|
263
|
+
|
|
264
|
+
// Kova/OpenClaw sessions
|
|
265
|
+
if (src === "kova") {
|
|
266
|
+
if (s.key === "agent:main:main") return "main session";
|
|
267
|
+
if (s.title) return s.title;
|
|
268
|
+
if (s.label) return s.label;
|
|
269
|
+
if (s.preview) return s.preview.slice(0, 60);
|
|
270
|
+
if (s.key) {
|
|
271
|
+
const parts = s.key.split(":");
|
|
272
|
+
if (parts.length > 2) return parts.slice(1).join(":");
|
|
273
|
+
return s.key;
|
|
274
|
+
}
|
|
275
|
+
return s.sessionId.slice(0, 14) + "\u2026";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Claude Code sessions
|
|
279
|
+
if (src === "claude") {
|
|
280
|
+
if (s.isSubagent) {
|
|
281
|
+
return "subagent " + s.sessionId.slice(0, 8);
|
|
282
|
+
}
|
|
283
|
+
const slug = s.label || s.key || "";
|
|
284
|
+
let project = slug;
|
|
285
|
+
if (slug.startsWith("~/")) {
|
|
286
|
+
const parts = slug.split("/");
|
|
287
|
+
project = parts[parts.length - 1] || parts[parts.length - 2] || slug;
|
|
288
|
+
} else if (slug.startsWith("-") || slug.includes("-")) {
|
|
289
|
+
const parts = slug.replace(/^-/, "").split(/[-/]/);
|
|
290
|
+
project = parts[parts.length - 1] || slug;
|
|
291
|
+
}
|
|
292
|
+
if (s.preview && project) {
|
|
293
|
+
const snippet = s.preview.slice(0, 40).replace(/\n/g, " ");
|
|
294
|
+
return `${project}: ${snippet}`;
|
|
295
|
+
}
|
|
296
|
+
return project || s.sessionId.slice(0, 14) + "\u2026";
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Codex sessions
|
|
300
|
+
if (src === "codex") {
|
|
301
|
+
if (s.cwd) {
|
|
302
|
+
const base = s.cwd.split("/").pop() || s.cwd;
|
|
303
|
+
if (s.preview) {
|
|
304
|
+
return `${base}: ${s.preview.slice(0, 40).replace(/\n/g, " ")}`;
|
|
305
|
+
}
|
|
306
|
+
return base;
|
|
307
|
+
}
|
|
308
|
+
if (s.label) return s.label;
|
|
309
|
+
return s.sessionId.slice(0, 14) + "\u2026";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Fallback
|
|
313
|
+
if (s.label) return s.label;
|
|
314
|
+
if (s.key) {
|
|
315
|
+
const parts = s.key.split(":");
|
|
316
|
+
if (parts.length > 2) return parts.slice(1).join(":");
|
|
317
|
+
return s.key;
|
|
318
|
+
}
|
|
319
|
+
return s.sessionId.slice(0, 14) + "\u2026";
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Map tool name to its block color category key */
|
|
323
|
+
export function toolColorKey(name: string): string {
|
|
324
|
+
switch (name) {
|
|
325
|
+
case "exec": case "Bash": return "exec";
|
|
326
|
+
case "read": case "Read": case "write": case "Write": case "edit": case "Edit": case "Glob": case "Grep": return "file";
|
|
327
|
+
case "web_search": case "WebSearch": case "web_fetch": case "WebFetch": return "web";
|
|
328
|
+
case "browser": case "Browser": return "browser";
|
|
329
|
+
case "message": case "Message": case "SendMessage": return "msg";
|
|
330
|
+
case "sessions_spawn": case "Task": case "task": return "agent";
|
|
331
|
+
default: return "";
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function copyToClipboard(text: string, label?: string): Promise<void> {
|
|
336
|
+
return navigator.clipboard.writeText(text).catch(() => {
|
|
337
|
+
const ta = document.createElement("textarea");
|
|
338
|
+
ta.value = text;
|
|
339
|
+
ta.style.cssText = "position:fixed;opacity:0;top:0;left:0;";
|
|
340
|
+
document.body.appendChild(ta);
|
|
341
|
+
ta.select();
|
|
342
|
+
try {
|
|
343
|
+
document.execCommand("copy");
|
|
344
|
+
} catch {
|
|
345
|
+
// ignore
|
|
346
|
+
}
|
|
347
|
+
document.body.removeChild(ta);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function debounce<T extends (...args: unknown[]) => void>(
|
|
352
|
+
fn: T,
|
|
353
|
+
ms: number
|
|
354
|
+
): (...args: Parameters<T>) => void {
|
|
355
|
+
let t: ReturnType<typeof setTimeout>;
|
|
356
|
+
return (...args: Parameters<T>) => {
|
|
357
|
+
clearTimeout(t);
|
|
358
|
+
t = setTimeout(() => fn(...args), ms);
|
|
359
|
+
};
|
|
360
|
+
}
|