${this.getToolDisplayName(block)}
${customContent} ${toolResult ? this.renderToolResult(toolResult, block) : ""}
`;
}
return this.renderToolContainer(block, toolResult, {
isCollapsible: true,
isCollapsed: true,
isClickToExpand: true,
});
}
return html`([\s\S]*?)<\/system-reminder>/g;
const systemReminders: string[] = [];
let match;
// Extract all system reminder blocks (escaped)
while ((match = systemReminderRegexEscaped.exec(content)) !== null) {
systemReminders.push(match[1].trim());
}
// Extract all system reminder blocks (raw)
while ((match = systemReminderRegexRaw.exec(content)) !== null) {
systemReminders.push(match[1].trim());
}
// Remove system reminder blocks from main content
let mainContent = content.replace(systemReminderRegexEscaped, "").replace(systemReminderRegexRaw, "").trim();
return html`
${mainContent ? html`${unsafeHTML(markdownToHtml(mainContent))}
` : ""}
${systemReminders.length > 0
? this.renderCollapsibleSection(
"System Reminder",
html`
${systemReminders.map(
(reminder, index) => html`
${systemReminders.length > 1
? html`
Reminder ${index + 1}:
`
: ""}
${unsafeHTML(markdownToHtml(reminder))}
`,
)}
`,
{
titleClasses: "text-vs-muted",
containerClasses: "mt-4 mb-4",
count: systemReminders.length > 1 ? systemReminders.length : undefined,
},
)
: ""}
`;
}
private formatSystem(system: string | TextBlockParam[] | undefined): string {
if (!system) return "";
if (typeof system === "string") {
return markdownToHtml(system);
}
if (Array.isArray(system)) {
const textContent = system
.map((block) => {
if (block.type === "text") {
return block.text;
}
return JSON.stringify(block, null, 2);
})
.join("\n");
return markdownToHtml(textContent);
}
return JSON.stringify(system, null, 2);
}
private formatResponseContent(response: Message): TemplateResult {
if (!response) return html``;
if (response.content && Array.isArray(response.content)) {
return html`
${response.content.map((block) => {
if (block.type === "text") {
return html`${unsafeHTML(markdownToHtml(block.text))}
`;
} else if (block.type === "thinking") {
const thinkingBlock = block as any;
return html`
Thinking
${unsafeHTML(markdownToHtml(thinkingBlock.thinking || ""))}
`;
} else if (block.type === "tool_use") {
if (block.name === "TodoWrite") {
return html`
${this.getToolDisplayName(block)}
${this.renderToolUseContent(block)}
`;
}
return html`
[+]
${this.getToolDisplayName(block)}
${this.renderToolUseContent(block)}
`;
}
return html`${JSON.stringify(block, null, 2)}`;
})}
`;
}
return html`${JSON.stringify(response, null, 2)}`;
}
private formatSingleParam(toolName: string, paramValue: string | undefined, paramName: string = ""): TemplateResult {
return paramValue
? html`${toolName}(${this.unescapeHtml(paramValue)})`
: html`${toolName}`;
}
private formatMultiParam(toolName: string, params: string[]): TemplateResult {
return params.length > 0
? html`${toolName}(${params.join(", ")})`
: html`${toolName}`;
}
private unescapeHtml(str: string): string {
const div = document.createElement("div");
div.innerHTML = str;
return div.textContent || div.innerText || "";
}
private wrapInScrollable(content: TemplateResult | string, usePreFormatting: boolean = true): TemplateResult {
if (usePreFormatting && typeof content === "string") {
return html`
`;
}
return html` ${content}
`;
}
private renderCollapsibleSection(
title: string,
content: TemplateResult,
options: {
isExpanded?: boolean;
titleClasses?: string;
containerClasses?: string;
count?: number;
} = {},
): TemplateResult {
const { isExpanded = false, titleClasses = "", containerClasses = "", count } = options;
const expandedClass = isExpanded ? "" : "hidden";
const toggleSymbol = isExpanded ? "[-]" : "[+]";
const displayTitle = count !== undefined ? `${title} (${count})` : title;
return html`
${toggleSymbol}
${displayTitle}
${content}
`;
}
private getToolDisplayName(toolUse: any, toolResult?: any): TemplateResult {
const toolName = toolUse.name;
const input = toolUse.input;
switch (toolName) {
case "Read":
return this.formatSingleParam(toolName, input?.file_path);
case "Bash":
return this.formatSingleParam(toolName, input?.command);
case "Write":
return this.formatSingleParam(toolName, input?.file_path);
case "Glob":
if (input?.pattern) {
const params = [this.unescapeHtml(input.pattern)];
if (input?.path) params.push(this.unescapeHtml(input.path));
return this.formatMultiParam(toolName, params);
}
return html`${toolName}`;
case "Grep":
if (input?.pattern) {
const params = [this.unescapeHtml(input.pattern)];
if (input?.include) params.push(this.unescapeHtml(input.include));
if (input?.path) params.push(this.unescapeHtml(input.path));
return this.formatMultiParam(toolName, params);
}
return html`${toolName}`;
case "LS":
if (input?.path) {
const params = [this.unescapeHtml(input.path)];
if (input?.ignore) {
const ignoreStr = input.ignore.map((p: string) => this.unescapeHtml(p)).join(", ");
params.push(`ignore: ${ignoreStr}`);
}
return this.formatMultiParam(toolName, params);
}
return html`${toolName}`;
case "Edit":
return input?.file_path
? this.formatSingleParam(toolName, this.unescapeHtml(input.file_path).split("/").pop())
: html`${toolName}`;
case "MultiEdit":
if (input?.file_path) {
const fileName = this.unescapeHtml(input.file_path).split("/").pop() || input.file_path;
const editCount = input?.edits ? input.edits.length : 0;
return this.formatMultiParam(toolName, [fileName, `${editCount} edits`]);
}
return html`${toolName}`;
case "NotebookRead":
return input?.notebook_path
? this.formatSingleParam(toolName, this.unescapeHtml(input.notebook_path).split("/").pop())
: html`${toolName}`;
case "NotebookEdit":
if (input?.notebook_path && input?.cell_number !== undefined) {
const fileName = this.unescapeHtml(input.notebook_path).split("/").pop();
const cellNum = input.cell_number;
const mode = input?.edit_mode || "replace";
return this.formatMultiParam(toolName, [fileName, `cell ${cellNum}`, mode]);
}
return html`${toolName}`;
case "WebFetch":
return input?.url ? this.formatSingleParam(toolName, input.url) : html`${toolName}`;
case "WebSearch":
return input?.query ? this.formatSingleParam(toolName, input.query) : html`${toolName}`;
default:
return html`${toolName}`;
}
}
private renderToolUseContent(toolUse: any): TemplateResult {
const toolName = toolUse.name;
const input = toolUse.input;
if (toolName === "TodoWrite" && input?.todos) {
const todos = input.todos;
return this.wrapInScrollable(
html`${todos.map((todo: any) => {
const statusClass =
todo.status === "completed"
? "line-through text-vs-text"
: todo.status === "in_progress"
? "text-green-400"
: "text-vs-muted";
return html`
• ${todo.content}
`;
})}`,
false,
);
}
if (toolName === "NotebookEdit" && input?.new_source) {
const content = input.new_source;
return this.wrapInScrollable(content);
}
if (toolName === "Write" && input?.content) {
const content = input.content;
return this.wrapInScrollable(content);
}
if (toolName === "MultiEdit" && input?.edits) {
const edits = input.edits;
return this.wrapInScrollable(
html`${edits.map((edit: any, index: number) => {
const oldStr = edit.old_string;
const newStr = edit.new_string;
const diffLines = this.renderDiff(oldStr, newStr);
return html`
Edit ${index + 1}:
${diffLines}
`;
})}`,
false,
);
}
if (toolName === "Edit" && input?.old_string && input?.new_string) {
const oldStr = input.old_string;
const newStr = input.new_string;
const diffLines = this.renderDiff(oldStr, newStr);
return this.wrapInScrollable(html`${diffLines}`, false);
}
if (toolName === "WebFetch" && input?.url && input?.prompt) {
return this.wrapInScrollable(
html`
`,
false,
);
}
if (toolName === "WebSearch" && input?.query) {
const params = [];
params.push(html`
`);
if (input?.allowed_domains) {
params.push(html`
Allowed Domains:
${input.allowed_domains.join(", ")}
`);
}
if (input?.blocked_domains) {
params.push(html`
Blocked Domains:
${input.blocked_domains.join(", ")}
`);
}
return this.wrapInScrollable(html`${params}`, false);
}
// Default: show JSON parameters
return this.wrapInScrollable(JSON.stringify(input, null, 2));
}
private renderToolResult(toolResult: any, toolUse?: any): TemplateResult {
return html`
[+]
Tool Result ${toolResult?.is_error ? "❌" : "✅"}
${typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content, null, 2)}
${toolUse
? html`
[+]
Raw Tool Call
${this.wrapInScrollable(JSON.stringify(toolUse, null, 2))}
`
: ""}
`;
}
private renderToolContainer(
toolUse: any,
toolResult?: any,
options: {
isCollapsible?: boolean;
isCollapsed?: boolean;
isClickToExpand?: boolean;
customContent?: TemplateResult;
} = {},
): TemplateResult {
const { isCollapsible = false, isCollapsed = false, isClickToExpand = false, customContent } = options;
const contentDiv = customContent || html`${this.renderToolUseContent(toolUse)}`;
const headerClasses = isClickToExpand
? "text-vs-type px-4 break-all cursor-pointer hover:text-white transition-colors"
: "text-vs-type px-4 break-all";
const contentClasses = isCollapsed
? "bg-vs-bg-secondary mx-4 p-4 text-vs-text hidden"
: "bg-vs-bg-secondary mx-4 p-4 text-vs-text";
return html`
${contentDiv}
${toolResult ? this.renderToolResult(toolResult, toolUse) : ""}
`;
}
private renderWritePreview(toolUse: any): TemplateResult {
const input = toolUse.input;
if (!input?.content) {
return html`No content
`;
}
const content = input.content;
const lines = content.split("\n");
const preview = lines.slice(0, 10);
const hasMore = lines.length > 10;
return html`
${this.wrapInScrollable(preview.join("\n"))}
${hasMore ? html`... ${lines.length - 10} more lines (click to expand)
` : ""}
`;
}
private renderDiff(oldStr: string, newStr: string): TemplateResult[] {
const diff = Diff.diffLines(oldStr, newStr);
const diffLines = [];
for (const part of diff) {
const lines = part.value.split("\n");
// Remove empty last line from split if it exists
if (lines[lines.length - 1] === "") {
lines.pop();
}
for (const line of lines) {
if (part.added) {
diffLines.push(html``);
} else if (part.removed) {
diffLines.push(html``);
} else {
diffLines.push(html``);
}
}
}
return diffLines;
}
private hasTools(conversation: SimpleConversation): boolean {
return !!(conversation.finalPair.request.tools && conversation.finalPair.request.tools.length > 0);
}
private renderTools(tools: ToolUnion[]): TemplateResult {
return html`
${tools.map((tool) => {
if ("name" in tool && tool.name) {
const description = ("description" in tool && tool.description) || "No description";
return this.renderCollapsibleSection(
tool.name,
html`
${unsafeHTML(markdownToHtml(description))}
${"input_schema" in tool && tool.input_schema && typeof tool.input_schema === "object"
? (() => {
const schema = tool.input_schema as any;
if (schema.properties) {
return html`
Parameters:
${Object.entries(schema.properties).map(([paramName, paramDef]) => {
const def = paramDef as any;
const required = schema.required?.includes(paramName) ? " (required)" : "";
const type = def.type ? ` [${def.type}]` : "";
const desc = def.description ? ` - ${def.description}` : "";
return html`
${paramName}
${type}${required}${desc}
`;
})}
`;
}
return html``;
})()
: html``}
`,
{
titleClasses: "text-vs-type font-bold",
containerClasses: "mb-4",
isExpanded: true,
},
);
}
return html`${JSON.stringify(tool, null, 2)}`;
})}
`;
}
private renderConversationContent(conversation: SimpleConversation): TemplateResult {
return html`
${conversation.system
? this.renderCollapsibleSection(
"System Prompt",
html`
${unsafeHTML(this.formatSystem(conversation.system))}
`,
{
titleClasses: "text-vs-function",
containerClasses: "px-4 mt-4",
},
)
: ""}
${this.hasTools(conversation)
? this.renderCollapsibleSection(
"Tools",
html`
${this.renderTools(conversation.finalPair.request.tools || [])}
`,
{
titleClasses: "text-vs-type",
containerClasses: "px-4",
count: conversation.finalPair.request.tools?.length || 0,
},
)
: ""}
${conversation.messages
.filter((message) => !(message as EnhancedMessageParam).hide)
.map(
(message, msgIndex) => html`
${message.role}
${this.formatContent(message.content, (message as EnhancedMessageParam).toolResults)}
`,
)}
assistant
${this.formatResponseContent(conversation.response)}
`;
}
render() {
if (this.conversations.length === 0) {
return html`No conversations found.
`;
}
return html`
${this.conversations.map(
(conversation) => html`
${conversation.compacted
? html`
[+]
Compacted (click to view details)
${Array.from(conversation.models).join(", ")}
${new Date(conversation.metadata.startTime).toLocaleString()}
${conversation.messages.length + 1} messages
${this.renderConversationContent(conversation)}
`
: html`
${Array.from(conversation.models).join(", ")}
${new Date(conversation.metadata.startTime).toLocaleString()}
${conversation.messages.length + 1} messages
${this.renderConversationContent(conversation)}
`}
`,
)}
`;
}
}