ui: ESLint config updates (#27700)

* chore: Spacing between sibling elements in html markup

* chore: Formatting and linting rules
This commit is contained in:
Aleksander Grygier
2026-08-25 14:34:34 +02:00
committed by GitHub
parent 3737e41370
commit f1357e4998
267 changed files with 1706 additions and 1344 deletions
@@ -381,13 +381,13 @@
}
</script>
<div class="chat-message" class:chat-message--synthetic={isSynthetic}>
<div class:chat-message--synthetic={isSynthetic} class="chat-message">
{#if message.role === MessageRole.SYSTEM}
<ChatMessageSystem bind:textareaElement class={className} {message} />
{:else if mcpPromptExtra}
<ChatMessageMcpPrompt class={className} {message} mcpPrompt={mcpPromptExtra} />
<ChatMessageMcpPrompt class={className} mcpPrompt={mcpPromptExtra} {message} />
{:else if isSynthetic}
<ChatMessageSynthetic {message} class={className} />
<ChatMessageSynthetic class={className} {message} />
{:else if message.role === MessageRole.USER}
<ChatMessageUser class={className} {isLastUserMessage} {message} {nextAssistantMessage} />
{:else}
@@ -396,9 +396,9 @@
class={className}
{isLastAssistantMessage}
{message}
{toolMessages}
onContinue={handleContinue}
onRegenerate={handleRegenerate}
{toolMessages}
/>
{/if}
</div>
@@ -126,16 +126,16 @@
<div
bind:this={assistantEl}
class="chat-message-assistant text-md group w-full leading-7.5 {className}"
style:--assistant-margin-top={assistantMarginTop > 0 ? `${assistantMarginTop}px` : undefined}
style:--last-user-message-height={lastUserMessageHeight > 0
? `${lastUserMessageHeight}px`
: undefined}
style:--assistant-margin-top={assistantMarginTop > 0 ? `${assistantMarginTop}px` : undefined}
role="group"
aria-label="Assistant message with actions"
class="chat-message-assistant text-md group w-full leading-7.5 {className}"
role="group"
>
{#if showProcessingInfoTop}
<ChatMessageAssistantProcessingInfo {modelLoadingText} {processingState} position="top" />
<ChatMessageAssistantProcessingInfo {modelLoadingText} position="top" {processingState} />
{/if}
{#if editCtx.isEditing}
@@ -145,16 +145,16 @@
<ChatMessageAssistantRawOutput {message} {toolMessages} />
{:else}
<ChatMessageAgenticContent
{isLastAssistantMessage}
isStreaming={chatStore.isStreaming()}
{message}
{toolMessages}
isStreaming={chatStore.isStreaming()}
{isLastAssistantMessage}
/>
{/if}
{/if}
{#if showProcessingInfoBottom}
<ChatMessageAssistantProcessingInfo {modelLoadingText} {processingState} position="bottom" />
<ChatMessageAssistantProcessingInfo {modelLoadingText} position="bottom" {processingState} />
{/if}
{#if displayedModel}
@@ -168,8 +168,8 @@
/>
<ChatMessageAssistantStatistics
{message}
isLoading={chatStore.isLoading}
{message}
{processingState}
showMessageStats={currentConfig.showMessageStats}
/>
@@ -179,14 +179,14 @@
{#if message.timestamp && !editCtx.isEditing}
<ChatMessageActionIcons
role={MessageRole.ASSISTANT}
justify="start"
actionsPosition="left"
{onRegenerate}
justify="start"
onContinue={currentConfig.enableContinueGeneration ? onContinue : undefined}
showRawOutputSwitch={currentConfig.showRawOutputSwitch}
rawOutputEnabled={showRawOutput}
onRawOutputToggle={(enabled) => (showRawOutput = enabled)}
{onRegenerate}
rawOutputEnabled={showRawOutput}
role={MessageRole.ASSISTANT}
showRawOutputSwitch={currentConfig.showRawOutputSwitch}
/>
{/if}
</div>
@@ -13,7 +13,7 @@
const marginClass = $derived(position === 'top' ? 'mt-6' : 'mt-4');
</script>
<div class="{marginClass} w-full max-w-3xl" in:fade>
<div in:fade class="{marginClass} w-full max-w-3xl">
<div class="flex flex-col items-start gap-2">
<span class="shimmer-text text-sm">
{modelLoadingText ??
@@ -24,22 +24,22 @@
{#if showMessageStats && isLiveFlowRoot && liveLlm}
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.GENERATION}
isLive
promptTokens={liveLlm.prompt_n}
promptMs={liveLlm.prompt_ms}
predictedTokens={liveLlm.predicted_n}
mode={ChatMessageStatisticsMode.GENERATION}
predictedMs={liveLlm.predicted_ms}
predictedTokens={liveLlm.predicted_n}
promptMs={liveLlm.prompt_ms}
promptTokens={liveLlm.prompt_n}
/>
{:else if showMessageStats && message.timings && message.timings.predicted_n && message.timings.predicted_ms}
{@const agentic = message.timings.agentic}
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.GENERATION}
promptTokens={agentic ? agentic.llm.prompt_n : message.timings.prompt_n}
promptMs={agentic ? agentic.llm.prompt_ms : message.timings.prompt_ms}
predictedTokens={agentic ? agentic.llm.predicted_n : message.timings.predicted_n}
predictedMs={agentic ? agentic.llm.predicted_ms : message.timings.predicted_ms}
agenticTimings={agentic}
mode={ChatMessageStatisticsMode.GENERATION}
predictedMs={agentic ? agentic.llm.predicted_ms : message.timings.predicted_ms}
predictedTokens={agentic ? agentic.llm.predicted_n : message.timings.predicted_n}
promptMs={agentic ? agentic.llm.prompt_ms : message.timings.prompt_ms}
promptTokens={agentic ? agentic.llm.prompt_n : message.timings.prompt_n}
/>
{:else if isLoading && showMessageStats}
{@const liveStats = processingState.getLiveProcessingStats()}
@@ -47,12 +47,12 @@
{#if genStats}
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.GENERATION}
isLive
promptTokens={liveStats?.tokensProcessed}
promptMs={liveStats?.timeMs}
predictedTokens={genStats.tokensGenerated}
mode={ChatMessageStatisticsMode.GENERATION}
predictedMs={genStats.timeMs}
predictedTokens={genStats.tokensGenerated}
promptMs={liveStats?.timeMs}
promptTokens={liveStats?.tokensProcessed}
/>
{/if}
{/if}
@@ -19,10 +19,13 @@
<div class="text-muted-foreground flex items-center gap-2 py-1.5 {className}">
{#if info.path === null}
<FolderX class="text-muted-foreground/60 h-3.5 w-3.5 shrink-0" />
<span class="text-foreground/80 text-sm font-medium">Working directory cleared</span>
{:else}
<Folder class="text-muted-foreground/60 h-3.5 w-3.5 shrink-0" />
<span class="text-foreground/80 text-sm font-medium">Set working directory to&nbsp;</span>
<span class="font-mono text-foreground/90 text-sm break-all" title={info.path}>
{info.display}
</span>
@@ -29,9 +29,9 @@
<ChatMessageEditForm />
{:else}
<ChatMessageMcpPromptContent
class="w-full max-w-[80%]"
prompt={mcpPrompt}
variant={McpPromptVariant.MESSAGE}
class="w-full max-w-[80%]"
/>
{#if message.timestamp}
@@ -99,12 +99,12 @@
<Tooltip.Trigger>
{#if serverFavicon}
<img
src={serverFavicon}
alt=""
class="h-3.5 w-3.5 shrink-0 rounded-sm"
onerror={(e) => {
(e.currentTarget as HTMLImageElement).style.display = 'none';
}}
src={serverFavicon}
/>
{/if}
</Tooltip.Trigger>
@@ -17,7 +17,7 @@
</script>
{#if isCwdChange}
<ChatMessageCwdChange {message} class={className} />
<ChatMessageCwdChange class={className} {message} />
{:else}
<span class="text-muted-foreground block text-sm {className}">{message.content}</span>
{/if}
@@ -83,16 +83,16 @@
{#if editCtx.isEditing}
<div class="w-full max-w-[80%]">
<textarea
style="max-height: var(--max-message-height);"
bind:this={textareaElement}
value={editCtx.editedContent}
class="min-h-[60px] w-full resize-none rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
onkeydown={handleEditKeydown}
oninput={(e) => {
autoResizeTextarea(e.currentTarget);
editCtx.setContent(e.currentTarget.value);
}}
onkeydown={handleEditKeydown}
placeholder="Edit system message..."
style="max-height: var(--max-message-height);"
value={editCtx.editedContent}
></textarea>
<div class="mt-2 flex justify-end gap-2">
@@ -104,8 +104,8 @@
<Button
class="h-8 px-3"
onclick={editCtx.save}
disabled={!editCtx.editedContent.trim()}
onclick={editCtx.save}
size="sm"
>
<Check class="mr-1 h-3 w-3" />
@@ -34,34 +34,34 @@
</script>
{#if isSearchCall}
<ChatMessageToolCallBlockSearchResults {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockSearchResults {isStreaming} {onToggle} {open} {section} />
{:else if section.toolName === BuiltInTool.BROWSER_GET_DATETIME}
<ChatMessageToolCallBlockGetDatetime {section} {isStreaming} />
<ChatMessageToolCallBlockGetDatetime {isStreaming} {section} />
{:else if section.toolName === BuiltInTool.SERVER_GET_INFO}
<ChatMessageToolCallBlockGetInfo {section} {isStreaming} />
<ChatMessageToolCallBlockGetInfo {isStreaming} {section} />
{:else if section.toolName === BuiltInTool.SERVER_READ_FILE}
<ChatMessageToolCallBlockReadFile {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockReadFile {isStreaming} {onToggle} {open} {section} />
{:else if section.toolName === BuiltInTool.BROWSER_READ_MEDIA}
<ChatMessageToolCallBlockReadMedia {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockReadMedia {isStreaming} {onToggle} {open} {section} />
{:else if section.toolName === BuiltInTool.SERVER_EDIT_FILE}
<ChatMessageToolCallBlockEditFile {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockEditFile {isStreaming} {onToggle} {open} {section} />
{:else if section.toolName === BuiltInTool.SERVER_WRITE_FILE}
<ChatMessageToolCallBlockWriteFile {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockWriteFile {isStreaming} {onToggle} {open} {section} />
{:else if section.toolName === BuiltInTool.SERVER_EXEC_SHELL_COMMAND}
<ChatMessageToolCallBlockExecShellCommand
{section}
{open}
{isStreaming}
{isExecuting}
{attachments}
{isExecuting}
{isStreaming}
{onToggle}
{open}
{section}
/>
{:else if section.toolName === BuiltInTool.SERVER_FILE_GLOB_SEARCH}
<ChatMessageToolCallBlockFileGlobSearch {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockFileGlobSearch {isStreaming} {onToggle} {open} {section} />
{:else if section.toolName === BuiltInTool.SERVER_GREP_SEARCH}
<ChatMessageToolCallBlockGrepSearch {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockGrepSearch {isStreaming} {onToggle} {open} {section} />
{:else if section.toolName === BuiltInTool.BROWSER_RUN_JAVASCRIPT}
<ChatMessageToolCallBlockRunJavascript {section} {open} {isStreaming} {onToggle} />
<ChatMessageToolCallBlockRunJavascript {isStreaming} {onToggle} {open} {section} />
{:else}
<ChatMessageToolCallBlockDefault {section} {open} {isStreaming} {attachments} {onToggle} />
<ChatMessageToolCallBlockDefault {attachments} {isStreaming} {onToggle} {open} {section} />
{/if}
@@ -34,15 +34,17 @@
);
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={null} {title} {onToggle}>
<ToolCallBlock {isStreaming} meta={null} {onToggle} {open} {section} {title}>
{#snippet children(_meta, ctx)}
{#if ctx.isStreamingCall}
<div class="mb-2 flex items-center gap-2 text-xs text-muted-foreground/70">
<span>Input</span>
{#if ctx.isStreaming}
<Loader2 class="h-3 w-3 animate-spin" />
{/if}
</div>
{#if section.toolArgs}
<SyntaxHighlightedCode
code={formatJsonPretty(section.toolArgs)}
@@ -67,6 +69,7 @@
<div class="mb-1.5 flex items-center gap-2 text-xs text-muted-foreground/70">
<span>Input</span>
</div>
<SyntaxHighlightedCode
code={formatJsonPretty(section.toolArgs ?? '')}
language={FileTypeText.JSON}
@@ -74,16 +77,19 @@
streaming={ctx.isCodeStreaming}
/>
{/if}
<div
class={showInput
? 'mt-4 mb-1.5 flex items-center gap-2 text-xs text-muted-foreground/70'
: 'mb-1.5 flex items-center gap-2 text-xs text-muted-foreground/70'}
>
<span>Output</span>
{#if ctx.isPending}
<Loader2 class="h-3 w-3 animate-spin" />
{/if}
</div>
{#if ctx.isPending}
<div class="rounded bg-muted/20 p-2 text-xs text-muted-foreground/70 italic">
Waiting for result...
@@ -96,18 +102,19 @@
maxHeight={MAX_HEIGHT_CODE_BLOCK}
/>
{:else if outputKind === ToolResultKind.MARKDOWN}
<MarkdownContent content={section.toolResult} {attachments} />
<MarkdownContent {attachments} content={section.toolResult} />
{:else}
<div class="overflow-auto">
{#each parsedLines as line, i (i)}
<div class="font-mono text-[11px] leading-relaxed whitespace-pre-wrap">
{line.text}
</div>
{#if line.media}
{#if line.media.type === AttachmentType.AUDIO}
{@const audioMimeType = line.media.mimeType ?? MimeTypeAudio.MP3_MPEG}
<div class="mt-2 mb-2">
<audio controls class="w-full rounded-lg">
<audio class="w-full rounded-lg" controls>
<source
src={createBase64DataUrl(audioMimeType, line.media.base64Data)}
type={audioMimeType}
@@ -117,10 +124,10 @@
</div>
{:else}
<img
src={line.media.base64Url}
alt={line.media.name}
class="mt-2 mb-2 h-auto max-w-full rounded-lg"
loading="lazy"
src={line.media.base64Url}
/>
{/if}
{/if}
@@ -23,12 +23,14 @@
);
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={editFileMeta} {onToggle}>
<ToolCallBlock {isStreaming} meta={editFileMeta} {onToggle} {open} {section}>
{#snippet titleSnippet()}
<span class="text-muted-foreground">Edit file </span>
<span class="font-mono" title={editFileMeta?.filePath}
>{abbreviateHome(editFileMeta?.filePath ?? '', home)}</span
>
{#if editFileMeta?.errorMessage}
<span class="ml-1 text-xs italic text-muted-foreground/70">(failed)</span>
{/if}
@@ -40,6 +42,7 @@
class="flex items-start gap-2 rounded bg-red-500/10 p-2 text-xs text-red-600 italic dark:text-red-400"
>
<XCircle class="mt-0.5 h-3 w-3 shrink-0" />
<span>{meta.errorMessage}</span>
</div>
{:else if meta && meta.edits.length > 0}
@@ -48,13 +51,17 @@
<div class="mb-1.5 text-xs text-muted-foreground/70 italic">
Edit {ei + 1}&nbsp;of&nbsp;{meta.edits.length}
</div>
<div class="diff-block" style:max-height={MAX_HEIGHT_CODE_BLOCK}>
<div style:max-height={MAX_HEIGHT_CODE_BLOCK} class="diff-block">
<div class="diff-pre">
{#each diffLines as line, li (li)}
<div class="diff-line diff-{line.kind}">
<span class="diff-old-num">{line.oldLine ?? ''}</span>
<span class="diff-marker">{prefixFor(line.kind)}</span>
<span class="diff-new-num">{line.newLine ?? ''}</span>
<span class="diff-text">{line.text || ' '}</span>
</div>
{/each}
@@ -62,9 +69,11 @@
</div>
</div>
{/each}
<div class="mt-1.5 text-xs text-muted-foreground/70 italic">
{#if meta.resultMessage}
{meta.resultMessage}{meta.editsApplied != null ? RESULT_STAT_SEPARATOR : ''}{/if}
{#if meta.editsApplied != null}
<span class="font-mono">{meta.editsApplied}</span>
{meta.editsApplied === 1 ? 'edit' : 'edits'}&nbsp;applied
@@ -176,6 +176,7 @@
{#snippet execShellTitle()}
{#if cwd}
<span class="exec-wd" title={cwd}>{wdDisplay}</span>
<span class="exec-prompt">$</span>
{/if}
@@ -187,14 +188,14 @@
{/snippet}
<ToolCallBlock
{section}
{open}
extraLiveStreaming={isLive}
{isStreaming}
meta={execShellMeta ? { errorMessage: execShellError } : null}
wrapper={CollapsibleTerminalBlock}
extraLiveStreaming={isLive}
spinIconWhenActive={true}
{onToggle}
{open}
{section}
spinIconWhenActive={true}
wrapper={CollapsibleTerminalBlock}
>
{#snippet titleSnippet()}
{@render execShellTitle()}
@@ -209,23 +210,25 @@
{:else if execShellError}
<div class="flex items-start gap-2 text-xs text-red-600 italic dark:text-red-400">
<XCircle class="mt-0.5 h-3 w-3 shrink-0" />
<span>{execShellError}</span>
</div>
{:else if section.toolResult}
<div
bind:this={scrollEl}
class="terminal-output"
class:is-clamped={!useFullHeightCodeBlocks}
class="terminal-output"
onscroll={handleScrollEvent}
>
{#each outputLines as line, i (i)}
<div class="font-mono text-[11px] leading-relaxed whitespace-pre-wrap">{line.text}</div>
{#if line.media?.type === AttachmentType.IMAGE}
<img
src={line.media.base64Url}
alt={line.media.name}
class="mt-2 mb-2 h-auto max-w-full rounded-lg"
loading="lazy"
src={line.media.base64Url}
/>
{/if}
{/each}
@@ -234,14 +237,19 @@
<div class={exitBadgeClass}>
{#if execShellExitStatus.timedOut}
<AlertTriangle class="h-3 w-3" />
<span>timed out</span>
<span class="exit-sep">&middot;</span>
<span>exit {execShellExitStatus.code}</span>
{:else if execShellExitStatus.code === 0}
<Check class="h-3 w-3" />
<span>exit 0</span>
{:else}
<XCircle class="h-3 w-3" />
<span>exit {execShellExitStatus.code}</span>
{/if}
</div>
@@ -19,16 +19,19 @@
const home = $derived(toolsStore.serverHome);
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={fileGlobMeta} {onToggle}>
<ToolCallBlock {isStreaming} meta={fileGlobMeta} {onToggle} {open} {section}>
{#snippet titleSnippet()}
{#if fileGlobMeta}
<span class="text-muted-foreground"
>{fileGlobMeta.include === '**' ? 'List files' : 'Search files'}&nbsp;</span
>
{#if fileGlobMeta.include !== '**'}
<span class="font-mono">{fileGlobMeta.include}</span>
{/if}
<span class="text-muted-foreground">&nbsp;in&nbsp;</span>
<span class="font-mono" title={fileGlobMeta.path}
>{abbreviateHome(fileGlobMeta.path, home)}</span
>
@@ -45,6 +48,7 @@
class="flex items-start gap-2 rounded bg-red-500/10 p-2 text-xs text-red-600 italic dark:text-red-400"
>
<XCircle class="mt-0.5 h-3 w-3 shrink-0" />
<span>{meta.errorMessage}</span>
</div>
{:else if meta && meta.matches.length > 0}
@@ -53,11 +57,13 @@
<div class="font-mono text-[11px] leading-relaxed whitespace-pre-wrap">{match}</div>
{/each}
</div>
<div class="mt-1.5 text-xs text-muted-foreground/70 italic">
Total matches: <span class="font-mono">{meta.totalMatches ?? meta.matches.length}</span>
</div>
{:else}
<div class="text-xs text-muted-foreground/70 italic">No matches</div>
<div class="mt-1.5 text-xs text-muted-foreground/70 italic">
Total matches: <span class="font-mono">{meta?.totalMatches ?? 0}</span>
</div>
@@ -44,15 +44,19 @@
<div class="text-muted-foreground flex items-center gap-2 py-1.5">
<Clock class="text-muted-foreground/60 h-3.5 w-3.5 shrink-0" />
{#if showSpinner}
<span class="text-foreground/80 text-sm font-medium">Current time</span>
<Loader2 class="text-muted-foreground/70 h-3 w-3 animate-spin" />
{:else if dateMeta.errorMessage}
<span class="text-foreground/80 text-sm font-medium">Current time&nbsp;</span>
<span class="text-red-600 text-xs italic dark:text-red-400">-&nbsp;{dateMeta.errorMessage}</span
>
{:else if dateMeta.dateString}
<span class="text-foreground/80 text-sm font-medium">Current time is&nbsp;</span>
<span class="font-mono text-foreground/90 text-sm">{dateMeta.dateString}</span>
{:else}
<span class="text-foreground/80 text-sm font-medium">Current time</span>
@@ -52,18 +52,23 @@
<div class="text-muted-foreground flex items-center gap-2 py-1.5">
<Info class="text-muted-foreground/60 h-3.5 w-3.5 shrink-0" />
{#if showSpinner}
<span class="text-foreground/80 text-sm font-medium">Runtime info</span>
<Loader2 class="text-muted-foreground/70 h-3 w-3 animate-spin" />
{:else if infoMeta.errorMessage}
<span class="text-foreground/80 text-sm font-medium">Runtime info&nbsp;</span>
<span class="text-red-600 text-xs italic dark:text-red-400">-&nbsp;{infoMeta.errorMessage}</span
>
{:else if infoMeta.os || infoMeta.cwd}
<span class="text-foreground/80 text-sm font-medium">Runtime info&nbsp;</span>
{#if infoMeta.os}
<span class="font-mono text-foreground/90 text-sm">{infoMeta.os}</span>
{/if}
{#if infoMeta.cwd}
<span class="font-mono text-foreground/90 text-sm" title={infoMeta.cwd}>{cwdDisplay}</span>
{/if}
@@ -19,12 +19,15 @@
const home = $derived(toolsStore.serverHome);
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={grepMeta} {onToggle}>
<ToolCallBlock {isStreaming} meta={grepMeta} {onToggle} {open} {section}>
{#snippet titleSnippet()}
{#if grepMeta}
<span class="text-muted-foreground">Search for&nbsp;</span>
<span class="font-mono">{grepMeta.pattern}</span>
<span class="text-muted-foreground">&nbsp;in&nbsp;</span>
<span class="font-mono" title={grepMeta.path}>{abbreviateHome(grepMeta.path, home)}</span>
{/if}
{/snippet}
@@ -39,6 +42,7 @@
class="flex items-start gap-2 rounded bg-red-500/10 p-2 text-xs text-red-600 italic dark:text-red-400"
>
<XCircle class="mt-0.5 h-3 w-3 shrink-0" />
<span>{meta.errorMessage}</span>
</div>
{:else if meta && meta.matches.length > 0}
@@ -46,22 +50,28 @@
{#each meta.matches as match, mi (mi)}
<div class="font-mono text-[11px] leading-relaxed">
<span class="text-muted-foreground/70">{match.file}</span>
{#if meta.showLineNumbers && match.line != null}
<span class="text-muted-foreground/70">:{match.line}</span>
{/if}
<span class="text-muted-foreground/70">:</span>
<span>{match.content}</span>
</div>
{/each}
</div>
<div class="mt-1.5 text-xs text-muted-foreground/70 italic">
Total matches: <span class="font-mono">{meta.totalMatches ?? meta.matches.length}</span>
{#if meta.showLineNumbers}
&nbsp;<span class="italic">(with line numbers)</span>
{/if}
</div>
{:else}
<div class="text-xs text-muted-foreground/70 italic">No matches</div>
<div class="mt-1.5 text-xs text-muted-foreground/70 italic">
Total matches: <span class="font-mono">{meta?.totalMatches ?? 0}</span>
</div>
@@ -17,10 +17,12 @@
const readFileMeta = $derived(parseReadFileMeta(section));
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={readFileMeta} {onToggle}>
<ToolCallBlock {isStreaming} meta={readFileMeta} {onToggle} {open} {section}>
{#snippet titleSnippet()}
<span class="text-muted-foreground">Read file </span>
<span class="font-mono">{readFileMeta?.fileName}</span>
{#if readFileMeta?.lineRange}
<span class="text-muted-foreground"
>&nbsp;(lines {readFileMeta.lineRange.start}-{readFileMeta.lineRange.end})</span
@@ -43,9 +43,10 @@
const audioMimeType = $derived(readMediaMeta?.mimeType ?? MimeTypeAudio.MP3_MPEG);
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={readMediaMeta} {onToggle}>
<ToolCallBlock {isStreaming} meta={readMediaMeta} {onToggle} {open} {section}>
{#snippet titleSnippet()}
<span class="text-muted-foreground">Read media </span>
<span class="font-mono">{readMediaMeta?.fileName}</span>
{/snippet}
@@ -57,7 +58,7 @@
</div>
{:else if mediaAttachment.type === AttachmentType.AUDIO}
<div class="mt-2">
<audio controls class="w-full rounded-lg">
<audio class="w-full rounded-lg" controls>
<source
src={createBase64DataUrl(audioMimeType, mediaAttachment.base64Data)}
type={audioMimeType}
@@ -68,10 +69,10 @@
{:else}
<div class="mt-2">
<img
src={mediaAttachment.base64Url}
alt={readMediaMeta?.fileName ?? 'media'}
class="max-h-[60vh] max-w-full rounded-lg object-contain shadow-lg"
loading="lazy"
src={mediaAttachment.base64Url}
/>
</div>
{/if}
@@ -81,6 +82,7 @@
{#if readMediaMeta?.sizeBytes}
<span>Size: {readMediaMeta.sizeBytes} bytes</span>
{/if}
{#if readMediaMeta?.mimeType}
<span>MIME: {readMediaMeta.mimeType}</span>
{/if}
@@ -21,7 +21,7 @@
const title = $derived(getToolUi(section.toolName)?.label ?? section.toolName ?? '');
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={runJsMeta} {title} {onToggle}>
<ToolCallBlock {isStreaming} meta={runJsMeta} {onToggle} {open} {section} {title}>
{#snippet children(meta, ctx)}
{#if ctx.isPending}
<div class="rounded bg-muted/20 p-2 text-xs text-muted-foreground/70 italic">Running...</div>
@@ -30,8 +30,10 @@
class="flex items-start gap-2 rounded bg-red-500/10 p-2 text-xs text-red-600 italic dark:text-red-400"
>
<XCircle class="mt-0.5 h-3 w-3 shrink-0" />
<span>{meta.errorMessage}</span>
</div>
<div class="mt-3">
<SyntaxHighlightedCode
code={meta.code}
@@ -47,13 +49,17 @@
maxHeight={MAX_HEIGHT_CODE_BLOCK}
streaming={ctx.isCodeStreaming}
/>
<div class="mb-2 mt-3 flex items-center gap-2 text-xs text-muted-foreground/70">
<Terminal class="h-3 w-3" />
<span>Console</span>
{#if meta.timeoutMs != null}
<span class="font-mono">&middot;&nbsp;timeout&nbsp;{meta.timeoutMs}&nbsp;ms</span>
{/if}
</div>
{#if section.toolResult}
<div class="mt-1">
<SyntaxHighlightedCode
@@ -86,55 +86,60 @@
{@const safeUrl = sanitizeExternalUrl(result.url)}
{@const showHoverCard = safeUrl !== null && hasDetails(result)}
{#if safeUrl}
<HoverCard.Root openDelay={150} closeDelay={100}>
<HoverCard.Root closeDelay={100} openDelay={150}>
<HoverCard.Trigger
href={safeUrl}
target="_blank"
rel="noopener noreferrer"
class="hover:bg-muted/80 focus-visible:ring-ring inline-flex max-w-full items-center gap-1.5 rounded-full border bg-muted px-2.5 py-1 text-xs transition-colors outline-none focus-visible:ring-2"
href={safeUrl}
rel="noopener noreferrer"
target="_blank"
>
{#if faviconUrl}
<img
src={faviconUrl}
alt=""
class="h-3 w-3 shrink-0 rounded-sm"
onerror={hideBrokenIcon}
src={faviconUrl}
/>
{:else}
<Globe class="text-muted-foreground/70 h-3 w-3 shrink-0" />
{/if}
<span class="truncate font-medium text-foreground/80">{result.title}</span>
</HoverCard.Trigger>
{#if showHoverCard}
{@const publishDate = formatPublishDate(result.published)}
{@const host = hostFor(safeUrl)}
<HoverCard.Content
side="top"
align="start"
sideOffset={6}
class="bg-popover text-popover-foreground z-50 w-80 max-w-[90vw] rounded-lg border p-0 shadow-lg"
side="top"
sideOffset={6}
>
<div class="flex flex-col gap-2 p-3">
<a
href={safeUrl}
target="_blank"
rel="noopener noreferrer"
class="line-clamp-3 text-sm font-medium leading-snug hover:underline"
>{result.title}</a
href={safeUrl}
rel="noopener noreferrer"
target="_blank">{result.title}</a
>
{#if publishDate || result.author}
<div class="text-muted-foreground flex items-center gap-1.5 text-[11px]">
{#if publishDate}
<span>{publishDate}</span>
{/if}
{#if publishDate && result.author}
<span class="opacity-50">&middot;</span>
{/if}
{#if result.author}
<span class="truncate">{result.author}</span>
{/if}
</div>
{/if}
{#if result.highlights}
<p
class="text-popover-foreground/85 line-clamp-5 text-xs leading-relaxed whitespace-pre-line"
@@ -142,6 +147,7 @@
{result.highlights}
</p>
{/if}
{#if host}
<div class="text-muted-foreground/80 truncate text-[11px]">{host}</div>
{/if}
@@ -152,7 +158,7 @@
{/if}
{/snippet}
<CollapsibleContentBlock {open} class="my-2" {icon} {iconClass} {iconUrl} {title} {onToggle}>
<CollapsibleContentBlock class="my-2" {icon} {iconClass} {iconUrl} {onToggle} {open} {title}>
{#if results.length > 0}
<div class="flex flex-wrap items-center gap-2 pb-1">
{#each results as result (result.url)}
@@ -162,6 +168,7 @@
{:else if showSpinner}
<div class="text-muted-foreground/70 flex items-center gap-2 py-1 text-xs italic">
<Loader2 class="h-3 w-3 animate-spin" />
<span>Searching...</span>
</div>
{:else}
@@ -21,12 +21,14 @@
const home = $derived(toolsStore.serverHome);
</script>
<ToolCallBlock {section} {open} {isStreaming} meta={writeFileMeta} {onToggle}>
<ToolCallBlock {isStreaming} meta={writeFileMeta} {onToggle} {open} {section}>
{#snippet titleSnippet()}
<span class="text-muted-foreground">Write file </span>
<span class="font-mono" title={writeFileMeta?.filePath}
>{abbreviateHome(writeFileMeta?.filePath ?? '', home)}</span
>
{#if writeFileMeta?.errorMessage}
<span class="ml-1 text-xs italic text-muted-foreground/70">(failed)</span>
{/if}
@@ -38,6 +40,7 @@
class="flex items-start gap-2 rounded bg-red-500/10 p-2 text-xs text-red-600 italic dark:text-red-400"
>
<XCircle class="mt-0.5 h-3 w-3 shrink-0" />
<span>{meta.errorMessage}</span>
</div>
{:else if meta}
@@ -47,9 +50,11 @@
maxHeight={MAX_HEIGHT_CODE_BLOCK}
streaming={ctx.isCodeStreaming}
/>
<div class="mt-1.5 text-xs text-muted-foreground/70 italic">
{#if meta.resultMessage}
{meta.resultMessage}{meta.bytesWritten != null ? RESULT_STAT_SEPARATOR : ''}{/if}
{#if meta.bytesWritten != null}
<span class="font-mono">{meta.bytesWritten}</span>
bytes
@@ -1,4 +1,4 @@
<script lang="ts" generics="TMeta">
<script generics="TMeta" lang="ts">
// Generic chrome shell shared by every per-tool block under
// `ChatMessageToolCall/`. Owns:
// - the collapsible wrapper (defaults to CollapsibleContentBlock;
@@ -114,15 +114,15 @@
</script>
<Wrapper
{open}
class="my-2"
icon={toolIcon}
iconClass={toolIconClass}
{iconUrl}
{onToggle}
{open}
{subtitle}
{title}
{titleSnippet}
{subtitle}
{onToggle}
>
{@render children(meta, {
isCodeStreaming,
@@ -69,8 +69,8 @@
<ChatMessageEditForm />
{:else}
<ChatMessageUserBubble
content={message.content}
attachments={message.extra}
content={message.content}
renderMarkdown={true}
/>
@@ -82,8 +82,8 @@
>
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.READING}
promptTokens={storedReadingStats!.promptTokens}
promptMs={storedReadingStats!.promptMs}
promptTokens={storedReadingStats!.promptTokens}
/>
</div>
</div>
@@ -95,10 +95,10 @@
class="inline-flex flex-wrap items-start justify-end gap-2 text-xs text-muted-foreground"
>
<ChatMessageStatistics
mode={ChatMessageStatisticsMode.READING}
isLive
promptTokens={liveStats.tokensProcessed}
mode={ChatMessageStatisticsMode.READING}
promptMs={liveStats.timeMs}
promptTokens={liveStats.tokensProcessed}
/>
</div>
</div>
@@ -54,7 +54,7 @@
{#if attachments && attachments.length > 0}
<div class="mb-2 max-w-[80%]">
<ChatAttachmentsList {attachments} readonly imageHeight="h-40" />
<ChatAttachmentsList {attachments} imageHeight="h-40" readonly />
</div>
{/if}
@@ -37,11 +37,11 @@
<ChatMessageEditForm />
{:else}
<ChatMessageUserBubble
{content}
attachments={extras}
textColorClass="text-muted-foreground"
cardBgClass="dark:bg-primary/8"
{content}
maxHeightStyle="overflow-wrap: anywhere; word-break: break-word;"
textColorClass="text-muted-foreground"
/>
<div class="max-w-[80%]">
@@ -50,9 +50,11 @@
<div
class="pointer-events-auto inset-0 flex items-center gap-1 opacity-0 transition-all duration-150 group-hover:opacity-100"
>
<ActionIcon icon={Edit} tooltip="Edit" onclick={editCtx.handleEdit} />
<ActionIcon icon={Trash2} tooltip="Delete" onclick={onDelete} />
<ActionIcon icon={ArrowUp} tooltip="Send immediately" onclick={onSendImmediately} />
<ActionIcon icon={Edit} onclick={editCtx.handleEdit} tooltip="Edit" />
<ActionIcon icon={Trash2} onclick={onDelete} tooltip="Delete" />
<ActionIcon icon={ArrowUp} onclick={onSendImmediately} tooltip="Send immediately" />
</div>
</div>
</div>
@@ -14,10 +14,12 @@
<div class="my-2 rounded-lg border border-border bg-card p-3">
<div class="mb-3 flex items-center gap-2 text-sm">
<IconComponent class="{ICON_CLASS_DEFAULT} shrink-0 text-muted-foreground" />
<span>
{@render message()}
</span>
</div>
<div class="flex flex-wrap items-center gap-2">
{@render actions()}
</div>
@@ -16,13 +16,13 @@
{/snippet}
{#snippet actions()}
<Button size="sm" onclick={() => onDecision(true)}>Continue</Button>
<Button onclick={() => onDecision(true)} size="sm">Continue</Button>
<Button
variant="destructive"
size="sm"
class="text-destructive hover:text-destructive"
onclick={() => onDecision(false)}
size="sm"
variant="destructive"
>
Stop
</Button>
@@ -28,10 +28,10 @@
<DropdownMenu.Root>
<ButtonGroup.Root class="overflow-hidden rounded-md shadow-sm">
<Button
variant="secondary"
size="sm"
class="!rounded-r-none !shadow-none"
onclick={() => onDecision(ToolPermissionDecision.ONCE)}
size="sm"
variant="secondary"
>
Allow once
</Button>
@@ -39,11 +39,11 @@
<ButtonGroup.Separator />
<DropdownMenu.Trigger
aria-label="More allow options"
class={cn(
buttonVariants({ size: 'sm', variant: 'secondary' }),
'inline-flex cursor-pointer items-center !rounded-l-none !shadow-none !px-2'
)}
aria-label="More allow options"
>
<ChevronDown class="h-3.5 w-3.5" />
</DropdownMenu.Trigger>
@@ -54,6 +54,7 @@
Always allow <pre>{toolName}</pre>
tool
</DropdownMenu.Item>
{#if serverLabel}
<DropdownMenu.Item onclick={() => onDecision(ToolPermissionDecision.ALWAYS_SERVER)}>
Always allow all tools from {serverLabel}
@@ -73,7 +74,7 @@
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button variant="destructive" size="sm" onclick={() => onDecision(ToolPermissionDecision.DENY)}>
<Button onclick={() => onDecision(ToolPermissionDecision.DENY)} size="sm" variant="destructive">
Deny
</Button>
{/snippet}
@@ -77,29 +77,30 @@
<div
class="pointer-events-auto inset-0 flex items-center gap-1 opacity-100 transition-all duration-150"
>
<ActionIcon icon={Copy} tooltip="Copy" onclick={messageActions.copy} />
<ActionIcon icon={Copy} onclick={messageActions.copy} tooltip="Copy" />
<ActionIcon icon={Edit} tooltip="Edit" onclick={editCtx.startEdit} />
<ActionIcon icon={Edit} onclick={editCtx.startEdit} tooltip="Edit" />
{#if role === MessageRole.ASSISTANT && onRegenerate}
<ActionIcon icon={RefreshCw} tooltip="Regenerate" onclick={() => onRegenerate()} />
<ActionIcon icon={RefreshCw} onclick={() => onRegenerate()} tooltip="Regenerate" />
{/if}
{#if role === MessageRole.ASSISTANT && onContinue}
<ActionIcon icon={ArrowRight} tooltip="Continue" onclick={onContinue} />
<ActionIcon icon={ArrowRight} onclick={onContinue} tooltip="Continue" />
{/if}
{#if messageActions.forkConversation}
<ActionIcon icon={GitBranch} tooltip="Fork conversation" onclick={handleOpenForkDialog} />
<ActionIcon icon={GitBranch} onclick={handleOpenForkDialog} tooltip="Fork conversation" />
{/if}
<ActionIcon icon={Trash2} tooltip="Delete" onclick={messageActions.requestDelete} />
<ActionIcon icon={Trash2} onclick={messageActions.requestDelete} tooltip="Delete" />
</div>
</div>
{#if showRawOutputSwitch}
<div class="flex items-center gap-2">
<span class="text-xs text-muted-foreground">Show raw output</span>
<Switch
checked={rawOutputEnabled}
onCheckedChange={(checked) => onRawOutputToggle?.(checked)}
@@ -109,54 +110,54 @@
</div>
<DialogConfirmation
open={messageActions.showDeleteDialog}
title="Delete Message"
description={messageActions.deletionInfo && messageActions.deletionInfo.totalCount > 1
? `This will delete ${messageActions.deletionInfo.totalCount} messages including: ${messageActions.deletionInfo.userMessages} user message${messageActions.deletionInfo.userMessages > 1 ? 's' : ''} and ${messageActions.deletionInfo.assistantMessages} assistant response${messageActions.deletionInfo.assistantMessages > 1 ? 's' : ''}. All messages in this branch and their responses will be permanently removed. This action cannot be undone.`
: 'Are you sure you want to delete this message? This action cannot be undone.'}
cancelText="Cancel"
confirmText={messageActions.deletionInfo && messageActions.deletionInfo.totalCount > 1
? `Delete ${messageActions.deletionInfo.totalCount} Messages`
: 'Delete'}
cancelText="Cancel"
variant="destructive"
description={messageActions.deletionInfo && messageActions.deletionInfo.totalCount > 1
? `This will delete ${messageActions.deletionInfo.totalCount} messages including: ${messageActions.deletionInfo.userMessages} user message${messageActions.deletionInfo.userMessages > 1 ? 's' : ''} and ${messageActions.deletionInfo.assistantMessages} assistant response${messageActions.deletionInfo.assistantMessages > 1 ? 's' : ''}. All messages in this branch and their responses will be permanently removed. This action cannot be undone.`
: 'Are you sure you want to delete this message? This action cannot be undone.'}
icon={Trash2}
onConfirm={handleConfirmDelete}
onCancel={() => messageActions.setShowDeleteDialog(false)}
onConfirm={handleConfirmDelete}
open={messageActions.showDeleteDialog}
title="Delete Message"
variant="destructive"
/>
<DialogConfirmation
bind:open={showForkDialog}
title="Fork Conversation"
description="Create a new conversation branching from this message."
confirmText="Fork"
cancelText="Cancel"
confirmText="Fork"
description="Create a new conversation branching from this message."
icon={GitBranch}
onConfirm={handleConfirmFork}
onCancel={() => (showForkDialog = false)}
onConfirm={handleConfirmFork}
title="Fork Conversation"
>
<div class="flex flex-col gap-4 py-2">
<div class="flex flex-col gap-2">
<Label for="fork-name">Title</Label>
<Input
id="fork-name"
bind:value={forkName}
class="text-foreground"
id="fork-name"
placeholder="Enter fork name"
type="text"
bind:value={forkName}
/>
</div>
<div class="flex items-center gap-2">
<Checkbox
id="fork-attachments"
checked={forkIncludeAttachments}
id="fork-attachments"
onCheckedChange={(checked) => {
forkIncludeAttachments = checked === true;
}}
/>
<Label for="fork-attachments" class="cursor-pointer text-sm font-normal">
<Label class="cursor-pointer text-sm font-normal" for="fork-attachments">
Include all attachments
</Label>
</div>
@@ -30,11 +30,11 @@
role="navigation"
>
<ActionIcon
icon={ChevronLeft}
tooltip="Previous version"
disabled={!hasPrevious}
class="h-5 w-5 p-0 {!hasPrevious ? '!cursor-not-allowed opacity-30' : ''}"
disabled={!hasPrevious}
icon={ChevronLeft}
onclick={() => messageActions.navigateToSibling(previousSiblingId!)}
tooltip="Previous version"
/>
<span class="px-1 font-mono text-xs">
@@ -42,11 +42,11 @@
</span>
<ActionIcon
icon={ChevronRight}
tooltip="Next version"
disabled={!hasNext}
class="h-5 w-5 p-0 {!hasNext ? 'opacity-30' : ''}"
disabled={!hasNext}
icon={ChevronRight}
onclick={() => messageActions.navigateToSibling(nextSiblingId!)}
tooltip="Next version"
/>
</div>
{/if}
@@ -181,26 +181,26 @@
{#snippet renderSection(section: AgenticSection, index: number)}
{#if section.type === AgenticSectionType.TEXT}
<div class="agentic-text">
<MarkdownContent content={section.content} attachments={message?.extra} />
<MarkdownContent attachments={message?.extra} content={section.content} />
</div>
{:else if section.type === AgenticSectionType.REASONING || section.type === AgenticSectionType.REASONING_PENDING}
<ChatMessageReasoningBlock
{section}
open={isExpanded(index, section)}
{isStreaming}
{hasReasoningError}
attachments={message?.extra}
{hasReasoningError}
{isStreaming}
onToggle={() => toggleExpanded(index, section)}
open={isExpanded(index, section)}
{section}
/>
{:else if section.type === AgenticSectionType.TOOL_CALL || section.type === AgenticSectionType.TOOL_CALL_PENDING || section.type === AgenticSectionType.TOOL_CALL_STREAMING}
<ChatMessageToolCallBlock
{section}
open={isExpanded(index, section)}
{isStreaming}
attachments={message?.extra}
isExecuting={section.toolCallId !== undefined &&
section.toolCallId === currentlyExecutingToolCallId}
attachments={message?.extra}
{isStreaming}
onToggle={() => toggleExpanded(index, section)}
open={isExpanded(index, section)}
{section}
/>
{/if}
{/snippet}
@@ -218,15 +218,15 @@
{#if turnStats && showAgenticTurnStats}
<div class="turn-stats transition-opacity duration-150 mt-1 mb-4">
<ChatMessageStatistics
promptTokens={turnStats.llm.prompt_n}
promptMs={turnStats.llm.prompt_ms}
predictedTokens={turnStats.llm.predicted_n}
predictedMs={turnStats.llm.predicted_ms}
agenticTimings={turnStats.toolCalls.length > 0
? buildTurnAgenticTimings(turnStats)
: undefined}
initialView={ChatMessageStatsView.GENERATION}
hideSummary
initialView={ChatMessageStatsView.GENERATION}
predictedMs={turnStats.llm.predicted_ms}
predictedTokens={turnStats.llm.predicted_n}
promptMs={turnStats.llm.prompt_ms}
promptTokens={turnStats.llm.prompt_n}
/>
</div>
{/if}
@@ -240,9 +240,9 @@
{#if pendingPermission && !permissionDismissed}
<ChatMessageActionCardPermissionRequest
toolName={pendingPermission.toolName}
serverLabel={pendingPermission.serverLabel}
onDecision={handlePermission}
serverLabel={pendingPermission.serverLabel}
toolName={pendingPermission.toolName}
/>
{/if}
@@ -102,35 +102,35 @@
<div class="relative w-full max-w-[80%]">
<ChatForm
value={editCtx.editedContent}
attachments={editCtx.editedExtras}
bind:uploadedFiles={editCtx.editedUploadedFiles}
placeholder="Edit your message..."
showMcpPromptButton
showAddButton={editCtx.messageRole === MessageRole.USER}
showModelSelector={editCtx.messageRole === MessageRole.USER}
onValueChange={editCtx.setContent}
attachments={editCtx.editedExtras}
onAttachmentRemove={handleAttachmentRemove}
onUploadedFileRemove={handleUploadedFileRemove}
onFilesAdd={handleFilesAdd}
onSubmit={handleSubmit}
onUploadedFileRemove={handleUploadedFileRemove}
onValueChange={editCtx.setContent}
placeholder="Edit your message..."
showAddButton={editCtx.messageRole === MessageRole.USER}
showMcpPromptButton
showModelSelector={editCtx.messageRole === MessageRole.USER}
value={editCtx.editedContent}
/>
</div>
<div class="mt-2 flex w-full max-w-[80%] items-center justify-between">
{#if isUserMessage && editCtx.showSaveOnlyOption}
<div class="flex items-center gap-2">
<Switch id="save-only-switch" bind:checked={saveWithoutRegenerate} class="scale-75" />
<Switch bind:checked={saveWithoutRegenerate} class="scale-75" id="save-only-switch" />
<label for="save-only-switch" class="cursor-pointer text-xs text-muted-foreground">
<label class="cursor-pointer text-xs text-muted-foreground" for="save-only-switch">
Update without re-sending
</label>
</div>
{:else if isAssistantMessage}
<div class="flex items-center gap-2">
<Switch id="branch-after-edit" bind:checked={branchAfterEdit} class="scale-75" />
<Switch bind:checked={branchAfterEdit} class="scale-75" id="branch-after-edit" />
<label for="branch-after-edit" class="cursor-pointer text-xs text-muted-foreground">
<label class="cursor-pointer text-xs text-muted-foreground" for="branch-after-edit">
Branch conversation after edit
</label>
</div>
@@ -147,12 +147,12 @@
<DialogConfirmation
bind:open={showDiscardDialog}
title="Discard changes?"
description="You have unsaved changes. Are you sure you want to discard them?"
confirmText="Discard"
cancelText="Keep editing"
variant="destructive"
confirmText="Discard"
description="You have unsaved changes. Are you sure you want to discard them?"
icon={AlertTriangle}
onConfirm={editCtx.cancel}
onCancel={() => (showDiscardDialog = false)}
onConfirm={editCtx.cancel}
title="Discard changes?"
variant="destructive"
/>
@@ -124,23 +124,23 @@
</script>
<CollapsibleContentBlock
{open}
class="my-2"
icon={Lightbulb}
iconClass="h-3.5 w-3.5"
{title}
{subtitle}
{shimmerTitle}
{onToggle}
{open}
{shimmerTitle}
{subtitle}
{title}
>
<div
bind:this={scrollEl}
class="reasoning-content"
class:is-streaming={isPending}
class="reasoning-content"
onscroll={handleScrollEvent}
>
{#if currentConfig.renderThinkingAsMarkdown}
<MarkdownContent content={section.content} class="text-muted-foreground" {attachments} />
<MarkdownContent {attachments} class="text-muted-foreground" content={section.content} />
{:else}
<div
class="text-[13px] leading-relaxed wrap-break-word whitespace-pre-wrap text-muted-foreground"
@@ -140,15 +140,15 @@
{#snippet child({ props })}
<button
{...props}
type="button"
class="inline-flex h-5 w-5 items-center justify-center rounded-sm transition-colors {activeView ===
opts.view
? 'bg-background text-foreground shadow-sm'
: opts.disabled
? 'cursor-not-allowed opacity-40'
: 'hover:text-foreground'}"
onclick={() => !opts.disabled && (activeView = opts.view)}
disabled={opts.disabled}
onclick={() => !opts.disabled && (activeView = opts.view)}
type="button"
>
<IconComponent class="h-3 w-3" />
@@ -208,85 +208,85 @@
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={WholeWord}
value="{predictedTokens?.toLocaleString()} tokens"
tooltipLabel="Generated tokens"
value="{predictedTokens?.toLocaleString()} tokens"
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Clock}
value={formattedTime}
tooltipLabel="Generation time"
value={formattedTime}
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Gauge}
value="{tokensPerSecond.toFixed(2)} t/s"
tooltipLabel="Generation speed"
value="{tokensPerSecond.toFixed(2)} t/s"
/>
{:else if activeView === ChatMessageStatsView.TOOLS && hasAgenticStats}
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Wrench}
value="{agenticTimings!.toolCallsCount} calls"
tooltipLabel="Tool calls executed"
value="{agenticTimings!.toolCallsCount} calls"
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Clock}
value={formattedAgenticToolsTime}
tooltipLabel="Tool execution time"
value={formattedAgenticToolsTime}
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Gauge}
value="{agenticToolsPerSecond.toFixed(2)} calls/s"
tooltipLabel="Tool execution rate"
value="{agenticToolsPerSecond.toFixed(2)} calls/s"
/>
{:else if activeView === ChatMessageStatsView.SUMMARY && hasAgenticStats}
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Layers}
value="{agenticTimings!.turns} turns"
tooltipLabel="Agentic turns (LLM calls)"
value="{agenticTimings!.turns} turns"
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={WholeWord}
value="{agenticTimings!.llm.predicted_n.toLocaleString()} tokens"
tooltipLabel="Total tokens generated"
value="{agenticTimings!.llm.predicted_n.toLocaleString()} tokens"
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Clock}
value={formattedAgenticTotalTime}
tooltipLabel="Total time (LLM + tools)"
value={formattedAgenticTotalTime}
/>
{:else if hasPromptStats && (mode === ChatMessageStatisticsMode.READING || isSwitchable)}
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={WholeWord}
value="{promptTokens} tokens"
tooltipLabel="Prompt tokens"
value="{promptTokens} tokens"
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Clock}
value={formattedPromptTime ?? '0s'}
tooltipLabel="Prompt processing time"
value={formattedPromptTime ?? '0s'}
/>
<ChatMessageStatisticsBadge
class="bg-transparent"
icon={Gauge}
value="{promptTokensPerSecond!.toFixed(2)} tokens/s"
tooltipLabel="Prompt processing speed"
value="{promptTokensPerSecond!.toFixed(2)} tokens/s"
/>
{/if}
</div>
@@ -32,6 +32,7 @@
</BadgeInfo>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content>
<p>{tooltipLabel}</p>
</Tooltip.Content>
@@ -227,14 +227,14 @@
<div>
{#each displayMessages as { isLastAssistantMessage, isLastUserMessage, message, nextAssistantMessage, siblingInfo, toolMessages } (message.id)}
<ChatMessage
class="mx-auto mt-12 w-full max-w-3xl"
{chatActions}
{message}
{toolMessages}
class="mx-auto mt-12 w-full max-w-3xl"
{isLastAssistantMessage}
{isLastUserMessage}
{message}
{nextAssistantMessage}
{siblingInfo}
{toolMessages}
/>
{/each}
@@ -247,10 +247,10 @@
class="mx-auto mt-12 w-full max-w-[48rem]"
content={pendingContent}
extras={agenticStore.getPendingSteeringMessageExtras(convId)}
onSendImmediately={() => chatStore.abortCurrentFlow(convId)}
onDelete={() => agenticStore.clearSteeringMessage(convId)}
onEdit={(newContent, extras) =>
agenticStore.injectSteeringMessage(convId, newContent, extras)}
onDelete={() => agenticStore.clearSteeringMessage(convId)}
onSendImmediately={() => chatStore.abortCurrentFlow(convId)}
/>
{/if}
{:else if conversationsStore.activeConversation && chatStore.getPendingMessageContent(conversationsStore.activeConversation!.id)}
@@ -262,9 +262,9 @@
class="mx-auto mt-12 w-full max-w-[48rem]"
content={pendingContent}
extras={chatStore.getPendingMessageExtras(convId)}
onSendImmediately={() => chatStore.abortCurrentFlow(convId)}
onEdit={(newContent, extras) => chatStore.injectPendingMessage(convId, newContent, extras)}
onDelete={() => chatStore.clearPendingMessage(convId)}
onEdit={(newContent, extras) => chatStore.injectPendingMessage(convId, newContent, extras)}
onSendImmediately={() => chatStore.abortCurrentFlow(convId)}
/>
{/if}
{/if}