ui: added single line reasoning preview (#23601)
* webui: added single line reasoning preview. * patch: reduce width slightly for the previewing section * refactor: move formatter constants to the right file * feat: reimplement reasoning preview with throttled dynamic per-line rendering * chore: fix spacing Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * chore: refactor to requested changes * refactor: grouped by capture pattern instead of block-level + inline * ui: fax interrupt state only trigger for 1st reasoning message * chore: make reasoning preview respects showThoughtInProgress setting * chore; newline at EOF Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * fix: thread rawContent so collapsible content can handle compute preview * patch: showThoughtInProgress accidentally blocks rawContent being passed * chore: fix lint * chore: change smoke test --------- Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>
This commit is contained in:
co-authored by
Aleksander Grygier
parent
0dbfa66a1f
commit
526977068f
@@ -31,7 +31,8 @@
|
||||
agenticPendingPermissionRequest,
|
||||
agenticResolvePermission,
|
||||
agenticPendingContinueRequest,
|
||||
agenticResolveContinue
|
||||
agenticResolveContinue,
|
||||
agenticLastError
|
||||
} from '$lib/stores/agentic.svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
|
||||
@@ -56,6 +57,10 @@
|
||||
const showToolCallInProgress = $derived(config().showToolCallInProgress as boolean);
|
||||
const showThoughtInProgress = $derived(config().showThoughtInProgress as boolean);
|
||||
|
||||
const hasReasoningError = $derived(
|
||||
isLastAssistantMessage ? !!agenticLastError(message.convId) : false
|
||||
);
|
||||
|
||||
let permissionDismissed = $state(false);
|
||||
|
||||
const pendingPermission = $derived(
|
||||
@@ -293,11 +298,21 @@
|
||||
</div>
|
||||
</CollapsibleContentBlock>
|
||||
{:else if section.type === AgenticSectionType.REASONING}
|
||||
{@const reasoningSubtitle = section.wasInterrupted
|
||||
? hasReasoningError
|
||||
? 'Error'
|
||||
: 'Cancelled'
|
||||
: isStreaming
|
||||
? ''
|
||||
: undefined}
|
||||
|
||||
<CollapsibleContentBlock
|
||||
open={isExpanded(index, section)}
|
||||
class="my-2"
|
||||
icon={Brain}
|
||||
title="Reasoning"
|
||||
subtitle={reasoningSubtitle}
|
||||
rawContent={section.content}
|
||||
onToggle={() => toggleExpanded(index, section)}
|
||||
>
|
||||
<div class="pt-3">
|
||||
@@ -308,7 +323,7 @@
|
||||
</CollapsibleContentBlock>
|
||||
{:else if section.type === AgenticSectionType.REASONING_PENDING}
|
||||
{@const reasoningTitle = isStreaming ? 'Reasoning...' : 'Reasoning'}
|
||||
{@const reasoningSubtitle = isStreaming ? '' : 'incomplete'}
|
||||
{@const reasoningSubtitle = isStreaming ? '' : hasReasoningError ? 'Error' : 'Cancelled'}
|
||||
|
||||
<CollapsibleContentBlock
|
||||
open={isExpanded(index, section)}
|
||||
@@ -316,6 +331,7 @@
|
||||
icon={Brain}
|
||||
title={reasoningTitle}
|
||||
subtitle={reasoningSubtitle}
|
||||
rawContent={section.content}
|
||||
{isStreaming}
|
||||
onToggle={() => toggleExpanded(index, section)}
|
||||
>
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
import { buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
import { Card } from '$lib/components/ui/card';
|
||||
import { createAutoScrollController } from '$lib/hooks/use-auto-scroll.svelte';
|
||||
import { useThrottle } from '$lib/hooks/use-throttle.svelte';
|
||||
import { formatReasoningPreview } from '$lib/utils';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
@@ -14,6 +17,8 @@
|
||||
iconClass?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
preview?: string;
|
||||
rawContent?: string;
|
||||
isStreaming?: boolean;
|
||||
onToggle?: () => void;
|
||||
children: Snippet;
|
||||
@@ -26,6 +31,8 @@
|
||||
iconClass = 'h-4 w-4',
|
||||
title,
|
||||
subtitle,
|
||||
preview,
|
||||
rawContent,
|
||||
isStreaming = false,
|
||||
onToggle,
|
||||
children
|
||||
@@ -33,6 +40,20 @@
|
||||
|
||||
let contentContainer: HTMLDivElement | undefined = $state();
|
||||
|
||||
const showThoughtInProgress = $derived(config().showThoughtInProgress as boolean);
|
||||
|
||||
let previewKey = useThrottle(() => rawContent ?? preview ?? '', 500);
|
||||
let displayedPreview = $state('');
|
||||
let displayedOverflow = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
void previewKey.key;
|
||||
const content = rawContent ?? preview ?? '';
|
||||
const result = formatReasoningPreview(content);
|
||||
displayedPreview = result.preview;
|
||||
displayedOverflow = result.overflow;
|
||||
});
|
||||
|
||||
const autoScroll = createAutoScrollController();
|
||||
|
||||
$effect(() => {
|
||||
@@ -58,16 +79,31 @@
|
||||
class={className}
|
||||
>
|
||||
<Card class="gap-0 border-muted bg-muted/30 py-0">
|
||||
<Collapsible.Trigger class="flex w-full cursor-pointer items-center justify-between p-3">
|
||||
<div class="flex items-center gap-2 text-muted-foreground">
|
||||
{#if IconComponent}
|
||||
<IconComponent class={iconClass} />
|
||||
{/if}
|
||||
<Collapsible.Trigger class="flex w-full cursor-pointer items-start justify-between gap-2 p-3">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<div class="flex items-center gap-2 text-muted-foreground">
|
||||
{#if IconComponent}
|
||||
<IconComponent class={iconClass} />
|
||||
{/if}
|
||||
|
||||
<span class="font-mono text-sm font-medium">{title}</span>
|
||||
<span class="font-mono text-sm font-medium">{title}</span>
|
||||
|
||||
{#if subtitle}
|
||||
<span class="text-xs italic">{subtitle}</span>
|
||||
{#if subtitle}
|
||||
<span class="text-xs italic">{subtitle}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if displayedPreview && !showThoughtInProgress}
|
||||
<div class="flex min-w-0 items-baseline justify-between gap-2">
|
||||
<div class="w-3/4 truncate text-xs text-muted-foreground/80">
|
||||
{displayedPreview}
|
||||
</div>
|
||||
{#if displayedOverflow > 0}
|
||||
<span class="shrink-0 text-xs text-muted-foreground/60"
|
||||
>{displayedOverflow}+ chars</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user