@mariozechner/pi-coding-agent 0.27.4 → 0.27.6
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/CHANGELOG.md +25 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +137 -40
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction.d.ts +10 -0
- package/dist/core/compaction.d.ts.map +1 -1
- package/dist/core/compaction.js +35 -0
- package/dist/core/compaction.js.map +1 -1
- package/dist/core/export-html.d.ts +11 -2
- package/dist/core/export-html.d.ts.map +1 -1
- package/dist/core/export-html.js +551 -94
- package/dist/core/export-html.js.map +1 -1
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +11 -3
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/types.d.ts +28 -2
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/model-config.d.ts +7 -2
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +7 -2
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +24 -11
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +25 -21
- package/dist/core/session-manager.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +5 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/hooks.md +126 -12
- package/examples/hooks/README.md +3 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/package.json +6 -5
package/dist/core/export-html.js
CHANGED
|
@@ -1,27 +1,192 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import hljs from "highlight.js";
|
|
3
|
+
import { marked } from "marked";
|
|
2
4
|
import { homedir } from "os";
|
|
5
|
+
import * as path from "path";
|
|
3
6
|
import { basename } from "path";
|
|
4
|
-
import { APP_NAME, VERSION } from "../config.js";
|
|
7
|
+
import { APP_NAME, getCustomThemesDir, getThemesDir, VERSION } from "../config.js";
|
|
5
8
|
import { isBashExecutionMessage } from "./messages.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
/** Resolve a theme color value, following variable references until we get a final value. */
|
|
10
|
+
function resolveColorValue(value, vars, defaultValue, visited = new Set()) {
|
|
11
|
+
if (value === "")
|
|
12
|
+
return defaultValue;
|
|
13
|
+
if (typeof value !== "string")
|
|
14
|
+
return defaultValue;
|
|
15
|
+
if (visited.has(value))
|
|
16
|
+
return defaultValue;
|
|
17
|
+
if (!(value in vars))
|
|
18
|
+
return value; // Return as-is (hex colors work in CSS)
|
|
19
|
+
visited.add(value);
|
|
20
|
+
return resolveColorValue(vars[value], vars, defaultValue, visited);
|
|
21
|
+
}
|
|
22
|
+
/** Load theme JSON from built-in or custom themes directory. */
|
|
23
|
+
function loadThemeJson(name) {
|
|
24
|
+
// Try built-in themes first
|
|
25
|
+
const themesDir = getThemesDir();
|
|
26
|
+
const builtinPath = path.join(themesDir, `${name}.json`);
|
|
27
|
+
if (existsSync(builtinPath)) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(readFileSync(builtinPath, "utf-8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Try custom themes
|
|
36
|
+
const customThemesDir = getCustomThemesDir();
|
|
37
|
+
const customPath = path.join(customThemesDir, `${name}.json`);
|
|
38
|
+
if (existsSync(customPath)) {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(readFileSync(customPath, "utf-8"));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/** Build complete theme colors object, resolving theme JSON values against defaults. */
|
|
49
|
+
function getThemeColors(themeName) {
|
|
50
|
+
const isLight = isLightTheme(themeName);
|
|
51
|
+
// Default colors based on theme type
|
|
52
|
+
const defaultColors = isLight
|
|
53
|
+
? {
|
|
54
|
+
// Light theme defaults
|
|
55
|
+
accent: "rgb(95, 135, 135)",
|
|
56
|
+
border: "rgb(95, 135, 175)",
|
|
57
|
+
borderAccent: "rgb(95, 135, 135)",
|
|
58
|
+
success: "rgb(135, 175, 135)",
|
|
59
|
+
error: "rgb(175, 95, 95)",
|
|
60
|
+
warning: "rgb(215, 175, 95)",
|
|
61
|
+
muted: "rgb(108, 108, 108)",
|
|
62
|
+
dim: "rgb(138, 138, 138)",
|
|
63
|
+
text: "rgb(0, 0, 0)",
|
|
64
|
+
userMessageBg: "rgb(232, 232, 232)",
|
|
65
|
+
userMessageText: "rgb(0, 0, 0)",
|
|
66
|
+
toolPendingBg: "rgb(232, 232, 240)",
|
|
67
|
+
toolSuccessBg: "rgb(232, 240, 232)",
|
|
68
|
+
toolErrorBg: "rgb(240, 232, 232)",
|
|
69
|
+
toolOutput: "rgb(108, 108, 108)",
|
|
70
|
+
mdHeading: "rgb(215, 175, 95)",
|
|
71
|
+
mdLink: "rgb(95, 135, 175)",
|
|
72
|
+
mdLinkUrl: "rgb(138, 138, 138)",
|
|
73
|
+
mdCode: "rgb(95, 135, 135)",
|
|
74
|
+
mdCodeBlock: "rgb(135, 175, 135)",
|
|
75
|
+
mdCodeBlockBorder: "rgb(108, 108, 108)",
|
|
76
|
+
mdQuote: "rgb(108, 108, 108)",
|
|
77
|
+
mdQuoteBorder: "rgb(108, 108, 108)",
|
|
78
|
+
mdHr: "rgb(108, 108, 108)",
|
|
79
|
+
mdListBullet: "rgb(135, 175, 135)",
|
|
80
|
+
toolDiffAdded: "rgb(135, 175, 135)",
|
|
81
|
+
toolDiffRemoved: "rgb(175, 95, 95)",
|
|
82
|
+
toolDiffContext: "rgb(108, 108, 108)",
|
|
83
|
+
syntaxComment: "rgb(0, 128, 0)",
|
|
84
|
+
syntaxKeyword: "rgb(0, 0, 255)",
|
|
85
|
+
syntaxFunction: "rgb(121, 94, 38)",
|
|
86
|
+
syntaxVariable: "rgb(0, 16, 128)",
|
|
87
|
+
syntaxString: "rgb(163, 21, 21)",
|
|
88
|
+
syntaxNumber: "rgb(9, 134, 88)",
|
|
89
|
+
syntaxType: "rgb(38, 127, 153)",
|
|
90
|
+
syntaxOperator: "rgb(0, 0, 0)",
|
|
91
|
+
syntaxPunctuation: "rgb(0, 0, 0)",
|
|
92
|
+
}
|
|
93
|
+
: {
|
|
94
|
+
// Dark theme defaults
|
|
95
|
+
accent: "rgb(138, 190, 183)",
|
|
96
|
+
border: "rgb(95, 135, 255)",
|
|
97
|
+
borderAccent: "rgb(0, 215, 255)",
|
|
98
|
+
success: "rgb(181, 189, 104)",
|
|
99
|
+
error: "rgb(204, 102, 102)",
|
|
100
|
+
warning: "rgb(255, 255, 0)",
|
|
101
|
+
muted: "rgb(128, 128, 128)",
|
|
102
|
+
dim: "rgb(102, 102, 102)",
|
|
103
|
+
text: "rgb(229, 229, 231)",
|
|
104
|
+
userMessageBg: "rgb(52, 53, 65)",
|
|
105
|
+
userMessageText: "rgb(229, 229, 231)",
|
|
106
|
+
toolPendingBg: "rgb(40, 40, 50)",
|
|
107
|
+
toolSuccessBg: "rgb(40, 50, 40)",
|
|
108
|
+
toolErrorBg: "rgb(60, 40, 40)",
|
|
109
|
+
toolOutput: "rgb(128, 128, 128)",
|
|
110
|
+
mdHeading: "rgb(240, 198, 116)",
|
|
111
|
+
mdLink: "rgb(129, 162, 190)",
|
|
112
|
+
mdLinkUrl: "rgb(102, 102, 102)",
|
|
113
|
+
mdCode: "rgb(138, 190, 183)",
|
|
114
|
+
mdCodeBlock: "rgb(181, 189, 104)",
|
|
115
|
+
mdCodeBlockBorder: "rgb(128, 128, 128)",
|
|
116
|
+
mdQuote: "rgb(128, 128, 128)",
|
|
117
|
+
mdQuoteBorder: "rgb(128, 128, 128)",
|
|
118
|
+
mdHr: "rgb(128, 128, 128)",
|
|
119
|
+
mdListBullet: "rgb(138, 190, 183)",
|
|
120
|
+
toolDiffAdded: "rgb(181, 189, 104)",
|
|
121
|
+
toolDiffRemoved: "rgb(204, 102, 102)",
|
|
122
|
+
toolDiffContext: "rgb(128, 128, 128)",
|
|
123
|
+
syntaxComment: "rgb(106, 153, 85)",
|
|
124
|
+
syntaxKeyword: "rgb(86, 156, 214)",
|
|
125
|
+
syntaxFunction: "rgb(220, 220, 170)",
|
|
126
|
+
syntaxVariable: "rgb(156, 220, 254)",
|
|
127
|
+
syntaxString: "rgb(206, 145, 120)",
|
|
128
|
+
syntaxNumber: "rgb(181, 206, 168)",
|
|
129
|
+
syntaxType: "rgb(78, 201, 176)",
|
|
130
|
+
syntaxOperator: "rgb(212, 212, 212)",
|
|
131
|
+
syntaxPunctuation: "rgb(212, 212, 212)",
|
|
132
|
+
};
|
|
133
|
+
if (!themeName)
|
|
134
|
+
return defaultColors;
|
|
135
|
+
const themeJson = loadThemeJson(themeName);
|
|
136
|
+
if (!themeJson)
|
|
137
|
+
return defaultColors;
|
|
138
|
+
const vars = themeJson.vars || {};
|
|
139
|
+
const colors = themeJson.colors;
|
|
140
|
+
const resolve = (key) => {
|
|
141
|
+
const value = colors[key];
|
|
142
|
+
if (value === undefined)
|
|
143
|
+
return defaultColors[key];
|
|
144
|
+
return resolveColorValue(value, vars, defaultColors[key]);
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
accent: resolve("accent"),
|
|
148
|
+
border: resolve("border"),
|
|
149
|
+
borderAccent: resolve("borderAccent"),
|
|
150
|
+
success: resolve("success"),
|
|
151
|
+
error: resolve("error"),
|
|
152
|
+
warning: resolve("warning"),
|
|
153
|
+
muted: resolve("muted"),
|
|
154
|
+
dim: resolve("dim"),
|
|
155
|
+
text: resolve("text"),
|
|
156
|
+
userMessageBg: resolve("userMessageBg"),
|
|
157
|
+
userMessageText: resolve("userMessageText"),
|
|
158
|
+
toolPendingBg: resolve("toolPendingBg"),
|
|
159
|
+
toolSuccessBg: resolve("toolSuccessBg"),
|
|
160
|
+
toolErrorBg: resolve("toolErrorBg"),
|
|
161
|
+
toolOutput: resolve("toolOutput"),
|
|
162
|
+
mdHeading: resolve("mdHeading"),
|
|
163
|
+
mdLink: resolve("mdLink"),
|
|
164
|
+
mdLinkUrl: resolve("mdLinkUrl"),
|
|
165
|
+
mdCode: resolve("mdCode"),
|
|
166
|
+
mdCodeBlock: resolve("mdCodeBlock"),
|
|
167
|
+
mdCodeBlockBorder: resolve("mdCodeBlockBorder"),
|
|
168
|
+
mdQuote: resolve("mdQuote"),
|
|
169
|
+
mdQuoteBorder: resolve("mdQuoteBorder"),
|
|
170
|
+
mdHr: resolve("mdHr"),
|
|
171
|
+
mdListBullet: resolve("mdListBullet"),
|
|
172
|
+
toolDiffAdded: resolve("toolDiffAdded"),
|
|
173
|
+
toolDiffRemoved: resolve("toolDiffRemoved"),
|
|
174
|
+
toolDiffContext: resolve("toolDiffContext"),
|
|
175
|
+
syntaxComment: resolve("syntaxComment"),
|
|
176
|
+
syntaxKeyword: resolve("syntaxKeyword"),
|
|
177
|
+
syntaxFunction: resolve("syntaxFunction"),
|
|
178
|
+
syntaxVariable: resolve("syntaxVariable"),
|
|
179
|
+
syntaxString: resolve("syntaxString"),
|
|
180
|
+
syntaxNumber: resolve("syntaxNumber"),
|
|
181
|
+
syntaxType: resolve("syntaxType"),
|
|
182
|
+
syntaxOperator: resolve("syntaxOperator"),
|
|
183
|
+
syntaxPunctuation: resolve("syntaxPunctuation"),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** Check if theme is a light theme (currently only matches "light" exactly). */
|
|
187
|
+
function isLightTheme(themeName) {
|
|
188
|
+
return themeName === "light";
|
|
189
|
+
}
|
|
25
190
|
// ============================================================================
|
|
26
191
|
// Utility functions
|
|
27
192
|
// ============================================================================
|
|
@@ -46,9 +211,152 @@ function formatTimestamp(timestamp) {
|
|
|
46
211
|
const date = new Date(typeof timestamp === "string" ? timestamp : timestamp);
|
|
47
212
|
return date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
48
213
|
}
|
|
49
|
-
|
|
214
|
+
/** Highlight code using highlight.js. Returns HTML with syntax highlighting spans. */
|
|
215
|
+
function highlightCode(code, lang) {
|
|
216
|
+
if (!lang) {
|
|
217
|
+
return escapeHtml(code);
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
// Check if language is supported
|
|
221
|
+
if (hljs.getLanguage(lang)) {
|
|
222
|
+
return hljs.highlight(code, { language: lang, ignoreIllegals: true }).value;
|
|
223
|
+
}
|
|
224
|
+
// Try common aliases
|
|
225
|
+
const aliases = {
|
|
226
|
+
ts: "typescript",
|
|
227
|
+
js: "javascript",
|
|
228
|
+
py: "python",
|
|
229
|
+
rb: "ruby",
|
|
230
|
+
sh: "bash",
|
|
231
|
+
yml: "yaml",
|
|
232
|
+
md: "markdown",
|
|
233
|
+
};
|
|
234
|
+
const aliasedLang = aliases[lang];
|
|
235
|
+
if (aliasedLang && hljs.getLanguage(aliasedLang)) {
|
|
236
|
+
return hljs.highlight(code, { language: aliasedLang, ignoreIllegals: true }).value;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Fall through to escaped output
|
|
241
|
+
}
|
|
242
|
+
return escapeHtml(code);
|
|
243
|
+
}
|
|
244
|
+
/** Get language from file path extension. */
|
|
245
|
+
function getLanguageFromPath(filePath) {
|
|
246
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
247
|
+
if (!ext)
|
|
248
|
+
return undefined;
|
|
249
|
+
const extToLang = {
|
|
250
|
+
ts: "typescript",
|
|
251
|
+
tsx: "typescript",
|
|
252
|
+
js: "javascript",
|
|
253
|
+
jsx: "javascript",
|
|
254
|
+
mjs: "javascript",
|
|
255
|
+
cjs: "javascript",
|
|
256
|
+
py: "python",
|
|
257
|
+
rb: "ruby",
|
|
258
|
+
rs: "rust",
|
|
259
|
+
go: "go",
|
|
260
|
+
java: "java",
|
|
261
|
+
kt: "kotlin",
|
|
262
|
+
swift: "swift",
|
|
263
|
+
c: "c",
|
|
264
|
+
h: "c",
|
|
265
|
+
cpp: "cpp",
|
|
266
|
+
cc: "cpp",
|
|
267
|
+
cxx: "cpp",
|
|
268
|
+
hpp: "cpp",
|
|
269
|
+
cs: "csharp",
|
|
270
|
+
php: "php",
|
|
271
|
+
sh: "bash",
|
|
272
|
+
bash: "bash",
|
|
273
|
+
zsh: "bash",
|
|
274
|
+
fish: "bash",
|
|
275
|
+
ps1: "powershell",
|
|
276
|
+
sql: "sql",
|
|
277
|
+
html: "html",
|
|
278
|
+
htm: "html",
|
|
279
|
+
xml: "xml",
|
|
280
|
+
css: "css",
|
|
281
|
+
scss: "scss",
|
|
282
|
+
sass: "scss",
|
|
283
|
+
less: "less",
|
|
284
|
+
json: "json",
|
|
285
|
+
yaml: "yaml",
|
|
286
|
+
yml: "yaml",
|
|
287
|
+
toml: "toml",
|
|
288
|
+
ini: "ini",
|
|
289
|
+
md: "markdown",
|
|
290
|
+
markdown: "markdown",
|
|
291
|
+
dockerfile: "dockerfile",
|
|
292
|
+
makefile: "makefile",
|
|
293
|
+
cmake: "cmake",
|
|
294
|
+
lua: "lua",
|
|
295
|
+
r: "r",
|
|
296
|
+
scala: "scala",
|
|
297
|
+
clj: "clojure",
|
|
298
|
+
cljs: "clojure",
|
|
299
|
+
ex: "elixir",
|
|
300
|
+
exs: "elixir",
|
|
301
|
+
erl: "erlang",
|
|
302
|
+
hrl: "erlang",
|
|
303
|
+
hs: "haskell",
|
|
304
|
+
ml: "ocaml",
|
|
305
|
+
mli: "ocaml",
|
|
306
|
+
fs: "fsharp",
|
|
307
|
+
fsx: "fsharp",
|
|
308
|
+
vue: "vue",
|
|
309
|
+
svelte: "xml",
|
|
310
|
+
tf: "hcl",
|
|
311
|
+
hcl: "hcl",
|
|
312
|
+
proto: "protobuf",
|
|
313
|
+
graphql: "graphql",
|
|
314
|
+
gql: "graphql",
|
|
315
|
+
};
|
|
316
|
+
return extToLang[ext];
|
|
317
|
+
}
|
|
318
|
+
/** Render markdown to HTML server-side with TUI-style code block formatting and syntax highlighting. */
|
|
319
|
+
function renderMarkdown(text) {
|
|
320
|
+
// Custom renderer for code blocks to match TUI style
|
|
321
|
+
const renderer = new marked.Renderer();
|
|
322
|
+
renderer.code = ({ text: code, lang }) => {
|
|
323
|
+
const language = lang || "";
|
|
324
|
+
const highlighted = highlightCode(code, lang);
|
|
325
|
+
return ('<div class="code-block-wrapper">' +
|
|
326
|
+
`<div class="code-block-header">\`\`\`${language}</div>` +
|
|
327
|
+
`<pre><code class="hljs">${highlighted}</code></pre>` +
|
|
328
|
+
'<div class="code-block-footer">```</div>' +
|
|
329
|
+
"</div>");
|
|
330
|
+
};
|
|
331
|
+
// Configure marked for safe rendering
|
|
332
|
+
marked.setOptions({
|
|
333
|
+
breaks: true,
|
|
334
|
+
gfm: true,
|
|
335
|
+
});
|
|
336
|
+
// Parse markdown (marked escapes HTML by default in newer versions)
|
|
337
|
+
return marked.parse(text, { renderer });
|
|
338
|
+
}
|
|
339
|
+
function formatExpandableOutput(lines, maxLines, lang) {
|
|
50
340
|
const displayLines = lines.slice(0, maxLines);
|
|
51
341
|
const remaining = lines.length - maxLines;
|
|
342
|
+
// If language is provided, highlight the entire code block
|
|
343
|
+
if (lang) {
|
|
344
|
+
const code = lines.join("\n");
|
|
345
|
+
const highlighted = highlightCode(code, lang);
|
|
346
|
+
if (remaining > 0) {
|
|
347
|
+
// For expandable, we need preview and full versions
|
|
348
|
+
const previewCode = displayLines.join("\n");
|
|
349
|
+
const previewHighlighted = highlightCode(previewCode, lang);
|
|
350
|
+
let out = '<div class="tool-output expandable" onclick="this.classList.toggle(\'expanded\')">';
|
|
351
|
+
out += `<div class="output-preview"><pre><code class="hljs">${previewHighlighted}</code></pre>`;
|
|
352
|
+
out += `<div class="expand-hint">... (${remaining} more lines) - click to expand</div>`;
|
|
353
|
+
out += "</div>";
|
|
354
|
+
out += `<div class="output-full"><pre><code class="hljs">${highlighted}</code></pre></div></div>`;
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
return `<div class="tool-output"><pre><code class="hljs">${highlighted}</code></pre></div>`;
|
|
358
|
+
}
|
|
359
|
+
// No language - plain text output
|
|
52
360
|
if (remaining > 0) {
|
|
53
361
|
let out = '<div class="tool-output expandable" onclick="this.classList.toggle(\'expanded\')">';
|
|
54
362
|
out += '<div class="output-preview">';
|
|
@@ -250,10 +558,10 @@ function parseSessionFile(content) {
|
|
|
250
558
|
// ============================================================================
|
|
251
559
|
// HTML formatting functions
|
|
252
560
|
// ============================================================================
|
|
253
|
-
function formatToolExecution(toolName, args, result) {
|
|
561
|
+
function formatToolExecution(toolName, args, result, colors) {
|
|
254
562
|
let html = "";
|
|
255
563
|
const isError = result?.isError || false;
|
|
256
|
-
const bgColor = result ? (isError ?
|
|
564
|
+
const bgColor = result ? (isError ? colors.toolErrorBg : colors.toolSuccessBg) : colors.toolPendingBg;
|
|
257
565
|
const getTextOutput = () => {
|
|
258
566
|
if (!result)
|
|
259
567
|
return "";
|
|
@@ -273,36 +581,40 @@ function formatToolExecution(toolName, args, result) {
|
|
|
273
581
|
break;
|
|
274
582
|
}
|
|
275
583
|
case "read": {
|
|
276
|
-
const
|
|
584
|
+
const filePath = args?.file_path || args?.path || "";
|
|
585
|
+
const shortenedPath = shortenPath(filePath);
|
|
277
586
|
const offset = args?.offset;
|
|
278
587
|
const limit = args?.limit;
|
|
279
|
-
|
|
280
|
-
|
|
588
|
+
const lang = getLanguageFromPath(filePath);
|
|
589
|
+
// Build path display with offset/limit suffix
|
|
590
|
+
let pathHtml = escapeHtml(shortenedPath || "...");
|
|
281
591
|
if (offset !== undefined || limit !== undefined) {
|
|
282
592
|
const startLine = offset ?? 1;
|
|
283
593
|
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
284
|
-
pathHtml += `<span class="line-numbers"
|
|
594
|
+
pathHtml += `<span class="line-numbers">:${startLine}${endLine ? `-${endLine}` : ""}</span>`;
|
|
285
595
|
}
|
|
286
596
|
html = `<div class="tool-header"><span class="tool-name">read</span> <span class="tool-path">${pathHtml}</span></div>`;
|
|
287
597
|
if (result) {
|
|
288
598
|
const output = getTextOutput();
|
|
289
599
|
if (output) {
|
|
290
|
-
html += formatExpandableOutput(output.split("\n"), 10);
|
|
600
|
+
html += formatExpandableOutput(output.split("\n"), 10, lang);
|
|
291
601
|
}
|
|
292
602
|
}
|
|
293
603
|
break;
|
|
294
604
|
}
|
|
295
605
|
case "write": {
|
|
296
|
-
const
|
|
606
|
+
const filePath = args?.file_path || args?.path || "";
|
|
607
|
+
const shortenedPath = shortenPath(filePath);
|
|
297
608
|
const fileContent = args?.content || "";
|
|
298
609
|
const lines = fileContent ? fileContent.split("\n") : [];
|
|
299
|
-
|
|
610
|
+
const lang = getLanguageFromPath(filePath);
|
|
611
|
+
html = `<div class="tool-header"><span class="tool-name">write</span> <span class="tool-path">${escapeHtml(shortenedPath || "...")}</span>`;
|
|
300
612
|
if (lines.length > 10) {
|
|
301
613
|
html += ` <span class="line-count">(${lines.length} lines)</span>`;
|
|
302
614
|
}
|
|
303
615
|
html += "</div>";
|
|
304
616
|
if (fileContent) {
|
|
305
|
-
html += formatExpandableOutput(lines, 10);
|
|
617
|
+
html += formatExpandableOutput(lines, 10, lang);
|
|
306
618
|
}
|
|
307
619
|
if (result) {
|
|
308
620
|
const output = getTextOutput().trim();
|
|
@@ -352,7 +664,7 @@ function formatToolExecution(toolName, args, result) {
|
|
|
352
664
|
}
|
|
353
665
|
return { html, bgColor };
|
|
354
666
|
}
|
|
355
|
-
function formatMessage(message, toolResultsMap) {
|
|
667
|
+
function formatMessage(message, toolResultsMap, colors) {
|
|
356
668
|
let html = "";
|
|
357
669
|
const timestamp = message.timestamp;
|
|
358
670
|
const timestampHtml = timestamp ? `<div class="message-timestamp">${formatTimestamp(timestamp)}</div>` : "";
|
|
@@ -360,8 +672,7 @@ function formatMessage(message, toolResultsMap) {
|
|
|
360
672
|
if (isBashExecutionMessage(message)) {
|
|
361
673
|
const bashMsg = message;
|
|
362
674
|
const isError = bashMsg.cancelled || (bashMsg.exitCode !== 0 && bashMsg.exitCode !== null);
|
|
363
|
-
|
|
364
|
-
html += `<div class="tool-execution" style="background-color: ${bgColor}">`;
|
|
675
|
+
html += `<div class="tool-execution user-bash${isError ? " user-bash-error" : ""}">`;
|
|
365
676
|
html += timestampHtml;
|
|
366
677
|
html += `<div class="tool-command">$ ${escapeHtml(bashMsg.command)}</div>`;
|
|
367
678
|
if (bashMsg.output) {
|
|
@@ -369,13 +680,13 @@ function formatMessage(message, toolResultsMap) {
|
|
|
369
680
|
html += formatExpandableOutput(lines, 10);
|
|
370
681
|
}
|
|
371
682
|
if (bashMsg.cancelled) {
|
|
372
|
-
html += `<div class="bash-status
|
|
683
|
+
html += `<div class="bash-status warning">(cancelled)</div>`;
|
|
373
684
|
}
|
|
374
685
|
else if (bashMsg.exitCode !== 0 && bashMsg.exitCode !== null) {
|
|
375
|
-
html += `<div class="bash-status
|
|
686
|
+
html += `<div class="bash-status error">(exit ${bashMsg.exitCode})</div>`;
|
|
376
687
|
}
|
|
377
688
|
if (bashMsg.truncated && bashMsg.fullOutputPath) {
|
|
378
|
-
html += `<div class="bash-truncation
|
|
689
|
+
html += `<div class="bash-truncation warning">Output truncated. Full output: ${escapeHtml(bashMsg.fullOutputPath)}</div>`;
|
|
379
690
|
}
|
|
380
691
|
html += `</div>`;
|
|
381
692
|
return html;
|
|
@@ -383,23 +694,42 @@ function formatMessage(message, toolResultsMap) {
|
|
|
383
694
|
if (message.role === "user") {
|
|
384
695
|
const userMsg = message;
|
|
385
696
|
let textContent = "";
|
|
697
|
+
const images = [];
|
|
386
698
|
if (typeof userMsg.content === "string") {
|
|
387
699
|
textContent = userMsg.content;
|
|
388
700
|
}
|
|
389
701
|
else {
|
|
390
|
-
const
|
|
391
|
-
|
|
702
|
+
for (const block of userMsg.content) {
|
|
703
|
+
if (block.type === "text") {
|
|
704
|
+
textContent += block.text;
|
|
705
|
+
}
|
|
706
|
+
else if (block.type === "image") {
|
|
707
|
+
images.push(block);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
392
710
|
}
|
|
711
|
+
html += `<div class="user-message">${timestampHtml}`;
|
|
712
|
+
// Render images first
|
|
713
|
+
if (images.length > 0) {
|
|
714
|
+
html += `<div class="message-images">`;
|
|
715
|
+
for (const img of images) {
|
|
716
|
+
html += `<img src="data:${img.mimeType};base64,${img.data}" alt="User uploaded image" class="message-image" />`;
|
|
717
|
+
}
|
|
718
|
+
html += `</div>`;
|
|
719
|
+
}
|
|
720
|
+
// Render text as markdown (server-side)
|
|
393
721
|
if (textContent.trim()) {
|
|
394
|
-
html += `<div class="
|
|
722
|
+
html += `<div class="markdown-content">${renderMarkdown(textContent)}</div>`;
|
|
395
723
|
}
|
|
724
|
+
html += `</div>`;
|
|
396
725
|
}
|
|
397
726
|
else if (message.role === "assistant") {
|
|
398
727
|
const assistantMsg = message;
|
|
399
728
|
html += timestampHtml ? `<div class="assistant-message">${timestampHtml}` : "";
|
|
400
729
|
for (const content of assistantMsg.content) {
|
|
401
730
|
if (content.type === "text" && content.text.trim()) {
|
|
402
|
-
|
|
731
|
+
// Render markdown server-side
|
|
732
|
+
html += `<div class="assistant-text markdown-content">${renderMarkdown(content.text)}</div>`;
|
|
403
733
|
}
|
|
404
734
|
else if (content.type === "thinking" && content.thinking.trim()) {
|
|
405
735
|
html += `<div class="thinking-text">${escapeHtml(content.thinking.trim()).replace(/\n/g, "<br>")}</div>`;
|
|
@@ -408,7 +738,7 @@ function formatMessage(message, toolResultsMap) {
|
|
|
408
738
|
for (const content of assistantMsg.content) {
|
|
409
739
|
if (content.type === "toolCall") {
|
|
410
740
|
const toolResult = toolResultsMap.get(content.id);
|
|
411
|
-
const { html: toolHtml, bgColor } = formatToolExecution(content.name, content.arguments, toolResult);
|
|
741
|
+
const { html: toolHtml, bgColor } = formatToolExecution(content.name, content.arguments, toolResult, colors);
|
|
412
742
|
html += `<div class="tool-execution" style="background-color: ${bgColor}">${toolHtml}</div>`;
|
|
413
743
|
}
|
|
414
744
|
}
|
|
@@ -457,7 +787,7 @@ function formatCompaction(event) {
|
|
|
457
787
|
// ============================================================================
|
|
458
788
|
// HTML generation
|
|
459
789
|
// ============================================================================
|
|
460
|
-
function generateHtml(data, filename) {
|
|
790
|
+
function generateHtml(data, filename, colors, isLight) {
|
|
461
791
|
const userMessages = data.messages.filter((m) => m.role === "user").length;
|
|
462
792
|
const assistantMessages = data.messages.filter((m) => m.role === "assistant").length;
|
|
463
793
|
let toolCallsCount = 0;
|
|
@@ -486,7 +816,7 @@ function generateHtml(data, filename) {
|
|
|
486
816
|
switch (event.type) {
|
|
487
817
|
case "message":
|
|
488
818
|
if (event.message.role !== "toolResult") {
|
|
489
|
-
messagesHtml += formatMessage(event.message, data.toolResultsMap);
|
|
819
|
+
messagesHtml += formatMessage(event.message, data.toolResultsMap, colors);
|
|
490
820
|
}
|
|
491
821
|
break;
|
|
492
822
|
case "model_change":
|
|
@@ -519,6 +849,15 @@ function generateHtml(data, filename) {
|
|
|
519
849
|
const contextUsageText = contextPercent
|
|
520
850
|
? `${contextTokens.toLocaleString()} / ${contextWindow.toLocaleString()} tokens (${contextPercent}%) - ${escapeHtml(lastModelInfo)}`
|
|
521
851
|
: `${contextTokens.toLocaleString()} tokens (last turn) - ${escapeHtml(lastModelInfo)}`;
|
|
852
|
+
// Compute body background based on theme
|
|
853
|
+
const bodyBg = isLight ? "rgb(248, 248, 248)" : "rgb(24, 24, 30)";
|
|
854
|
+
const containerBg = isLight ? "rgb(255, 255, 255)" : "rgb(30, 30, 36)";
|
|
855
|
+
const compactionBg = isLight ? "rgb(255, 248, 220)" : "rgb(60, 55, 35)";
|
|
856
|
+
const systemPromptBg = isLight ? "rgb(255, 250, 230)" : "rgb(60, 55, 40)";
|
|
857
|
+
const streamingNoticeBg = isLight ? "rgb(250, 245, 235)" : "rgb(50, 45, 35)";
|
|
858
|
+
const modelChangeBg = isLight ? "rgb(240, 240, 250)" : "rgb(40, 40, 50)";
|
|
859
|
+
const userBashBg = isLight ? "rgb(255, 250, 240)" : "rgb(50, 48, 35)";
|
|
860
|
+
const userBashErrorBg = isLight ? "rgb(255, 245, 235)" : "rgb(60, 45, 35)";
|
|
522
861
|
return `<!DOCTYPE html>
|
|
523
862
|
<html lang="en">
|
|
524
863
|
<head>
|
|
@@ -531,72 +870,68 @@ function generateHtml(data, filename) {
|
|
|
531
870
|
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
|
532
871
|
font-size: 12px;
|
|
533
872
|
line-height: 1.6;
|
|
534
|
-
color: ${
|
|
535
|
-
background: ${
|
|
873
|
+
color: ${colors.text};
|
|
874
|
+
background: ${bodyBg};
|
|
536
875
|
padding: 24px;
|
|
537
876
|
}
|
|
538
877
|
.container { max-width: 700px; margin: 0 auto; }
|
|
539
878
|
.header {
|
|
540
879
|
margin-bottom: 24px;
|
|
541
880
|
padding: 16px;
|
|
542
|
-
background: ${
|
|
881
|
+
background: ${containerBg};
|
|
543
882
|
border-radius: 4px;
|
|
544
883
|
}
|
|
545
884
|
.header h1 {
|
|
546
885
|
font-size: 14px;
|
|
547
886
|
font-weight: bold;
|
|
548
887
|
margin-bottom: 12px;
|
|
549
|
-
color: ${
|
|
888
|
+
color: ${colors.borderAccent};
|
|
550
889
|
}
|
|
551
890
|
.header-info { display: flex; flex-direction: column; gap: 3px; font-size: 11px; }
|
|
552
|
-
.info-item { color: ${
|
|
891
|
+
.info-item { color: ${colors.dim}; display: flex; align-items: baseline; }
|
|
553
892
|
.info-label { font-weight: 600; margin-right: 8px; min-width: 100px; }
|
|
554
|
-
.info-value { color: ${
|
|
893
|
+
.info-value { color: ${colors.text}; flex: 1; }
|
|
555
894
|
.info-value.cost { font-family: 'SF Mono', monospace; }
|
|
556
895
|
.messages { display: flex; flex-direction: column; gap: 16px; }
|
|
557
|
-
.message-timestamp { font-size: 10px; color: ${
|
|
896
|
+
.message-timestamp { font-size: 10px; color: ${colors.dim}; margin-bottom: 4px; opacity: 0.8; }
|
|
558
897
|
.user-message {
|
|
559
|
-
background: ${
|
|
898
|
+
background: ${colors.userMessageBg};
|
|
899
|
+
color: ${colors.userMessageText};
|
|
560
900
|
padding: 12px 16px;
|
|
561
901
|
border-radius: 4px;
|
|
562
|
-
white-space: pre-wrap;
|
|
563
|
-
word-wrap: break-word;
|
|
564
|
-
overflow-wrap: break-word;
|
|
565
|
-
word-break: break-word;
|
|
566
902
|
}
|
|
567
903
|
.assistant-message { padding: 0; }
|
|
568
904
|
.assistant-text, .thinking-text {
|
|
569
905
|
padding: 12px 16px;
|
|
570
|
-
white-space: pre-wrap;
|
|
571
|
-
word-wrap: break-word;
|
|
572
|
-
overflow-wrap: break-word;
|
|
573
|
-
word-break: break-word;
|
|
574
906
|
}
|
|
575
|
-
.thinking-text { color: ${
|
|
576
|
-
.model-change { padding: 8px 16px; background:
|
|
577
|
-
.model-change-text { color: ${
|
|
578
|
-
.model-name { color: ${
|
|
579
|
-
.compaction-container { background:
|
|
907
|
+
.thinking-text { color: ${colors.dim}; font-style: italic; white-space: pre-wrap; }
|
|
908
|
+
.model-change { padding: 8px 16px; background: ${modelChangeBg}; border-radius: 4px; }
|
|
909
|
+
.model-change-text { color: ${colors.dim}; font-size: 11px; }
|
|
910
|
+
.model-name { color: ${colors.borderAccent}; font-weight: bold; }
|
|
911
|
+
.compaction-container { background: ${compactionBg}; border-radius: 4px; overflow: hidden; }
|
|
580
912
|
.compaction-header { padding: 12px 16px; cursor: pointer; }
|
|
581
|
-
.compaction-header:hover { background: rgba(255, 255, 255, 0.05); }
|
|
913
|
+
.compaction-header:hover { background: rgba(${isLight ? "0, 0, 0" : "255, 255, 255"}, 0.05); }
|
|
582
914
|
.compaction-header-row { display: flex; align-items: center; gap: 8px; }
|
|
583
|
-
.compaction-toggle { color: ${
|
|
915
|
+
.compaction-toggle { color: ${colors.borderAccent}; font-size: 10px; transition: transform 0.2s; }
|
|
584
916
|
.compaction-container.expanded .compaction-toggle { transform: rotate(90deg); }
|
|
585
|
-
.compaction-title { color: ${
|
|
586
|
-
.compaction-hint { color: ${
|
|
917
|
+
.compaction-title { color: ${colors.text}; font-weight: bold; }
|
|
918
|
+
.compaction-hint { color: ${colors.dim}; font-size: 11px; }
|
|
587
919
|
.compaction-content { display: none; padding: 0 16px 16px 16px; }
|
|
588
920
|
.compaction-container.expanded .compaction-content { display: block; }
|
|
589
|
-
.compaction-summary { background: rgba(0, 0, 0, 0.
|
|
590
|
-
.compaction-summary-header { font-weight: bold; color: ${
|
|
591
|
-
.compaction-summary-content { color: ${
|
|
921
|
+
.compaction-summary { background: rgba(0, 0, 0, 0.1); border-radius: 4px; padding: 12px; }
|
|
922
|
+
.compaction-summary-header { font-weight: bold; color: ${colors.borderAccent}; margin-bottom: 8px; font-size: 11px; }
|
|
923
|
+
.compaction-summary-content { color: ${colors.text}; white-space: pre-wrap; word-wrap: break-word; }
|
|
592
924
|
.tool-execution { padding: 12px 16px; border-radius: 4px; margin-top: 8px; }
|
|
925
|
+
.tool-execution.user-bash { background: ${userBashBg}; }
|
|
926
|
+
.tool-execution.user-bash-error { background: ${userBashErrorBg}; }
|
|
593
927
|
.tool-header, .tool-name { font-weight: bold; }
|
|
594
|
-
.tool-path { color: ${
|
|
595
|
-
.line-
|
|
928
|
+
.tool-path { color: ${colors.borderAccent}; word-break: break-all; }
|
|
929
|
+
.line-numbers { color: ${colors.warning}; }
|
|
930
|
+
.line-count { color: ${colors.dim}; }
|
|
596
931
|
.tool-command { font-weight: bold; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; }
|
|
597
932
|
.tool-output {
|
|
598
933
|
margin-top: 12px;
|
|
599
|
-
color: ${
|
|
934
|
+
color: ${colors.toolOutput};
|
|
600
935
|
white-space: pre-wrap;
|
|
601
936
|
word-wrap: break-word;
|
|
602
937
|
overflow-wrap: break-word;
|
|
@@ -611,19 +946,126 @@ function generateHtml(data, filename) {
|
|
|
611
946
|
.tool-output.expandable .output-full { display: none; }
|
|
612
947
|
.tool-output.expandable.expanded .output-preview { display: none; }
|
|
613
948
|
.tool-output.expandable.expanded .output-full { display: block; }
|
|
614
|
-
.expand-hint { color: ${
|
|
615
|
-
.system-prompt, .tools-list { background:
|
|
616
|
-
.system-prompt-header, .tools-header { font-weight: bold; color: ${
|
|
617
|
-
.system-prompt-content, .tools-content { color: ${
|
|
949
|
+
.expand-hint { color: ${colors.borderAccent}; font-style: italic; margin-top: 4px; }
|
|
950
|
+
.system-prompt, .tools-list { background: ${systemPromptBg}; padding: 12px 16px; border-radius: 4px; margin-bottom: 16px; }
|
|
951
|
+
.system-prompt-header, .tools-header { font-weight: bold; color: ${colors.warning}; margin-bottom: 8px; }
|
|
952
|
+
.system-prompt-content, .tools-content { color: ${colors.dim}; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; font-size: 11px; }
|
|
618
953
|
.tool-item { margin: 4px 0; }
|
|
619
|
-
.tool-item-name { font-weight: bold; color: ${
|
|
954
|
+
.tool-item-name { font-weight: bold; color: ${colors.text}; }
|
|
620
955
|
.tool-diff { margin-top: 12px; font-size: 11px; font-family: inherit; overflow-x: auto; max-width: 100%; }
|
|
621
|
-
.diff-line-old { color: ${
|
|
622
|
-
.diff-line-new { color: ${
|
|
623
|
-
.diff-line-context { color: ${
|
|
624
|
-
.error-text { color: ${
|
|
625
|
-
.
|
|
626
|
-
.
|
|
956
|
+
.diff-line-old { color: ${colors.toolDiffRemoved}; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; }
|
|
957
|
+
.diff-line-new { color: ${colors.toolDiffAdded}; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; }
|
|
958
|
+
.diff-line-context { color: ${colors.toolDiffContext}; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; }
|
|
959
|
+
.error-text { color: ${colors.error}; padding: 12px 16px; }
|
|
960
|
+
.bash-status.warning { color: ${colors.warning}; }
|
|
961
|
+
.bash-status.error { color: ${colors.error}; }
|
|
962
|
+
.bash-truncation.warning { color: ${colors.warning}; }
|
|
963
|
+
.footer { margin-top: 48px; padding: 20px; text-align: center; color: ${colors.dim}; font-size: 10px; }
|
|
964
|
+
.streaming-notice { background: ${streamingNoticeBg}; padding: 12px 16px; border-radius: 4px; margin-bottom: 16px; color: ${colors.dim}; font-size: 11px; }
|
|
965
|
+
|
|
966
|
+
/* Image styles */
|
|
967
|
+
.message-images { margin-bottom: 12px; }
|
|
968
|
+
.message-image { max-width: 100%; max-height: 400px; border-radius: 4px; margin: 4px 0; }
|
|
969
|
+
|
|
970
|
+
/* Markdown styles */
|
|
971
|
+
.markdown-content h1, .markdown-content h2, .markdown-content h3,
|
|
972
|
+
.markdown-content h4, .markdown-content h5, .markdown-content h6 {
|
|
973
|
+
color: ${colors.mdHeading};
|
|
974
|
+
margin: 1em 0 0.5em 0;
|
|
975
|
+
font-weight: bold;
|
|
976
|
+
}
|
|
977
|
+
.markdown-content h1 { font-size: 1.4em; text-decoration: underline; }
|
|
978
|
+
.markdown-content h2 { font-size: 1.2em; }
|
|
979
|
+
.markdown-content h3 { font-size: 1.1em; }
|
|
980
|
+
.markdown-content p { margin: 0.5em 0; }
|
|
981
|
+
.markdown-content a { color: ${colors.mdLink}; text-decoration: underline; }
|
|
982
|
+
.markdown-content a:hover { opacity: 0.8; }
|
|
983
|
+
.markdown-content code {
|
|
984
|
+
background: rgba(${isLight ? "0, 0, 0" : "255, 255, 255"}, 0.1);
|
|
985
|
+
color: ${colors.mdCode};
|
|
986
|
+
padding: 2px 6px;
|
|
987
|
+
border-radius: 3px;
|
|
988
|
+
font-family: inherit;
|
|
989
|
+
}
|
|
990
|
+
.markdown-content pre {
|
|
991
|
+
background: transparent;
|
|
992
|
+
border: none;
|
|
993
|
+
border-radius: 0;
|
|
994
|
+
padding: 0;
|
|
995
|
+
margin: 0.5em 0;
|
|
996
|
+
overflow-x: auto;
|
|
997
|
+
}
|
|
998
|
+
.markdown-content pre code {
|
|
999
|
+
display: block;
|
|
1000
|
+
background: none;
|
|
1001
|
+
color: ${colors.mdCodeBlock};
|
|
1002
|
+
padding: 8px 12px;
|
|
1003
|
+
}
|
|
1004
|
+
.code-block-wrapper {
|
|
1005
|
+
margin: 0.5em 0;
|
|
1006
|
+
}
|
|
1007
|
+
.code-block-header {
|
|
1008
|
+
color: ${colors.mdCodeBlockBorder};
|
|
1009
|
+
font-size: 11px;
|
|
1010
|
+
}
|
|
1011
|
+
.code-block-footer {
|
|
1012
|
+
color: ${colors.mdCodeBlockBorder};
|
|
1013
|
+
font-size: 11px;
|
|
1014
|
+
}
|
|
1015
|
+
.markdown-content blockquote {
|
|
1016
|
+
border-left: 3px solid ${colors.mdQuoteBorder};
|
|
1017
|
+
padding-left: 12px;
|
|
1018
|
+
margin: 0.5em 0;
|
|
1019
|
+
color: ${colors.mdQuote};
|
|
1020
|
+
font-style: italic;
|
|
1021
|
+
}
|
|
1022
|
+
.markdown-content ul, .markdown-content ol {
|
|
1023
|
+
margin: 0.5em 0;
|
|
1024
|
+
padding-left: 24px;
|
|
1025
|
+
}
|
|
1026
|
+
.markdown-content li { margin: 0.25em 0; }
|
|
1027
|
+
.markdown-content li::marker { color: ${colors.mdListBullet}; }
|
|
1028
|
+
.markdown-content hr {
|
|
1029
|
+
border: none;
|
|
1030
|
+
border-top: 1px solid ${colors.mdHr};
|
|
1031
|
+
margin: 1em 0;
|
|
1032
|
+
}
|
|
1033
|
+
.markdown-content table {
|
|
1034
|
+
border-collapse: collapse;
|
|
1035
|
+
margin: 0.5em 0;
|
|
1036
|
+
width: 100%;
|
|
1037
|
+
}
|
|
1038
|
+
.markdown-content th, .markdown-content td {
|
|
1039
|
+
border: 1px solid ${colors.mdCodeBlockBorder};
|
|
1040
|
+
padding: 6px 10px;
|
|
1041
|
+
text-align: left;
|
|
1042
|
+
}
|
|
1043
|
+
.markdown-content th {
|
|
1044
|
+
background: rgba(${isLight ? "0, 0, 0" : "255, 255, 255"}, 0.05);
|
|
1045
|
+
font-weight: bold;
|
|
1046
|
+
}
|
|
1047
|
+
.markdown-content img {
|
|
1048
|
+
max-width: 100%;
|
|
1049
|
+
border-radius: 4px;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/* Syntax highlighting (highlight.js) */
|
|
1053
|
+
.hljs { background: transparent; }
|
|
1054
|
+
.hljs-comment, .hljs-quote { color: ${colors.syntaxComment}; }
|
|
1055
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-addition { color: ${colors.syntaxKeyword}; }
|
|
1056
|
+
.hljs-number, .hljs-literal, .hljs-symbol, .hljs-bullet { color: ${colors.syntaxNumber}; }
|
|
1057
|
+
.hljs-string, .hljs-doctag, .hljs-regexp { color: ${colors.syntaxString}; }
|
|
1058
|
+
.hljs-title, .hljs-section, .hljs-name, .hljs-selector-id, .hljs-selector-class { color: ${colors.syntaxFunction}; }
|
|
1059
|
+
.hljs-type, .hljs-class, .hljs-built_in { color: ${colors.syntaxType}; }
|
|
1060
|
+
.hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-params { color: ${colors.syntaxVariable}; }
|
|
1061
|
+
.hljs-attribute { color: ${colors.syntaxVariable}; }
|
|
1062
|
+
.hljs-meta { color: ${colors.syntaxKeyword}; }
|
|
1063
|
+
.hljs-formula { background: rgba(${isLight ? "0, 0, 0" : "255, 255, 255"}, 0.05); }
|
|
1064
|
+
.hljs-deletion { color: ${colors.toolDiffRemoved}; }
|
|
1065
|
+
.hljs-emphasis { font-style: italic; }
|
|
1066
|
+
.hljs-strong { font-weight: bold; }
|
|
1067
|
+
.hljs-link { color: ${colors.mdLink}; text-decoration: underline; }
|
|
1068
|
+
|
|
627
1069
|
@media print { body { background: white; color: black; } .tool-execution { border: 1px solid #ddd; } }
|
|
628
1070
|
</style>
|
|
629
1071
|
</head>
|
|
@@ -681,28 +1123,36 @@ function generateHtml(data, filename) {
|
|
|
681
1123
|
</body>
|
|
682
1124
|
</html>`;
|
|
683
1125
|
}
|
|
684
|
-
// ============================================================================
|
|
685
|
-
// Public API
|
|
686
|
-
// ============================================================================
|
|
687
1126
|
/**
|
|
688
1127
|
* Export session to HTML using SessionManager and AgentState.
|
|
689
1128
|
* Used by TUI's /export command.
|
|
1129
|
+
* @param sessionManager The session manager
|
|
1130
|
+
* @param state The agent state
|
|
1131
|
+
* @param options Export options including output path and theme name
|
|
690
1132
|
*/
|
|
691
|
-
export function exportSessionToHtml(sessionManager, state,
|
|
1133
|
+
export function exportSessionToHtml(sessionManager, state, options) {
|
|
1134
|
+
// Handle backwards compatibility: options can be just the output path string
|
|
1135
|
+
const opts = typeof options === "string" ? { outputPath: options } : options || {};
|
|
692
1136
|
const sessionFile = sessionManager.getSessionFile();
|
|
693
1137
|
const content = readFileSync(sessionFile, "utf8");
|
|
694
1138
|
const data = parseSessionFile(content);
|
|
695
1139
|
// Enrich with data from AgentState (tools, context window)
|
|
696
|
-
data.tools = state.tools.map((t) => ({
|
|
1140
|
+
data.tools = state.tools.map((t) => ({
|
|
1141
|
+
name: t.name,
|
|
1142
|
+
description: t.description,
|
|
1143
|
+
}));
|
|
697
1144
|
data.contextWindow = state.model?.contextWindow;
|
|
698
1145
|
if (!data.systemPrompt) {
|
|
699
1146
|
data.systemPrompt = state.systemPrompt;
|
|
700
1147
|
}
|
|
1148
|
+
let outputPath = opts.outputPath;
|
|
701
1149
|
if (!outputPath) {
|
|
702
1150
|
const sessionBasename = basename(sessionFile, ".jsonl");
|
|
703
1151
|
outputPath = `${APP_NAME}-session-${sessionBasename}.html`;
|
|
704
1152
|
}
|
|
705
|
-
const
|
|
1153
|
+
const colors = getThemeColors(opts.themeName);
|
|
1154
|
+
const isLight = isLightTheme(opts.themeName);
|
|
1155
|
+
const html = generateHtml(data, basename(sessionFile), colors, isLight);
|
|
706
1156
|
writeFileSync(outputPath, html, "utf8");
|
|
707
1157
|
return outputPath;
|
|
708
1158
|
}
|
|
@@ -710,18 +1160,25 @@ export function exportSessionToHtml(sessionManager, state, outputPath) {
|
|
|
710
1160
|
* Export session file to HTML (standalone, without AgentState).
|
|
711
1161
|
* Auto-detects format: session manager format or streaming event format.
|
|
712
1162
|
* Used by CLI for exporting arbitrary session files.
|
|
1163
|
+
* @param inputPath Path to the session file
|
|
1164
|
+
* @param options Export options including output path and theme name
|
|
713
1165
|
*/
|
|
714
|
-
export function exportFromFile(inputPath,
|
|
1166
|
+
export function exportFromFile(inputPath, options) {
|
|
1167
|
+
// Handle backwards compatibility: options can be just the output path string
|
|
1168
|
+
const opts = typeof options === "string" ? { outputPath: options } : options || {};
|
|
715
1169
|
if (!existsSync(inputPath)) {
|
|
716
1170
|
throw new Error(`File not found: ${inputPath}`);
|
|
717
1171
|
}
|
|
718
1172
|
const content = readFileSync(inputPath, "utf8");
|
|
719
1173
|
const data = parseSessionFile(content);
|
|
1174
|
+
let outputPath = opts.outputPath;
|
|
720
1175
|
if (!outputPath) {
|
|
721
1176
|
const inputBasename = basename(inputPath, ".jsonl");
|
|
722
1177
|
outputPath = `${APP_NAME}-session-${inputBasename}.html`;
|
|
723
1178
|
}
|
|
724
|
-
const
|
|
1179
|
+
const colors = getThemeColors(opts.themeName);
|
|
1180
|
+
const isLight = isLightTheme(opts.themeName);
|
|
1181
|
+
const html = generateHtml(data, basename(inputPath), colors, isLight);
|
|
725
1182
|
writeFileSync(outputPath, html, "utf8");
|
|
726
1183
|
return outputPath;
|
|
727
1184
|
}
|