refactor: Collapsible Content Block & small fixes

This commit is contained in:
Aleksander Grygier 2026-01-08 09:17:24 +01:00
parent d89ada8cee
commit 56b34bf63b
8 changed files with 43 additions and 30 deletions

View File

@ -24,7 +24,7 @@
onSystemPromptClick
}: Props = $props();
const fileUploadTooltipText = 'Add files';
const fileUploadTooltipText = 'Add files or context';
function handleFileUpload(fileType?: FileTypeCategory) {
onFileUpload?.(fileType);

View File

@ -7,17 +7,21 @@
* similar to the reasoning/thinking block UI.
*/
import { MarkdownContent, SyntaxHighlightedCode } from '$lib/components/app';
import {
CollapsibleContentBlock,
MarkdownContent,
SyntaxHighlightedCode
} from '$lib/components/app';
import { config } from '$lib/stores/settings.svelte';
import { Wrench, Loader2 } from '@lucide/svelte';
import CollapsibleInfoCard from './CollapsibleInfoCard.svelte';
import { AgenticSectionType } from '$lib/types/agentic';
interface Props {
content: string;
}
interface AgenticSection {
type: 'text' | 'tool_call' | 'tool_call_pending' | 'tool_call_streaming';
type: AgenticSectionType;
content: string;
toolName?: string;
toolArgs?: string;
@ -66,7 +70,7 @@
if (match.index > lastIndex) {
const textBefore = rawContent.slice(lastIndex, match.index).trim();
if (textBefore) {
sections.push({ type: 'text', content: textBefore });
sections.push({ type: AgenticSectionType.TEXT, content: textBefore });
}
}
@ -82,7 +86,7 @@
const toolResult = match[3].replace(/^\n+|\n+$/g, '');
sections.push({
type: 'tool_call',
type: AgenticSectionType.TOOL_CALL,
content: toolResult,
toolName,
toolArgs,
@ -115,7 +119,7 @@
if (pendingIndex > 0) {
const textBefore = remainingContent.slice(0, pendingIndex).trim();
if (textBefore) {
sections.push({ type: 'text', content: textBefore });
sections.push({ type: AgenticSectionType.TEXT, content: textBefore });
}
}
@ -132,7 +136,7 @@
const streamingResult = (pendingMatch[3] || '').replace(/^\n+|\n+$/g, '');
sections.push({
type: 'tool_call_pending',
type: AgenticSectionType.TOOL_CALL_PENDING,
content: streamingResult,
toolName,
toolArgs,
@ -144,7 +148,7 @@
if (pendingIndex > 0) {
const textBefore = remainingContent.slice(0, pendingIndex).trim();
if (textBefore) {
sections.push({ type: 'text', content: textBefore });
sections.push({ type: AgenticSectionType.TEXT, content: textBefore });
}
}
@ -169,7 +173,7 @@
}
sections.push({
type: 'tool_call_streaming',
type: AgenticSectionType.TOOL_CALL_STREAMING,
content: '',
toolName: partialWithNameMatch[1],
toolArgs: partialArgs || undefined,
@ -181,7 +185,7 @@
if (pendingIndex > 0) {
const textBefore = remainingContent.slice(0, pendingIndex).trim();
if (textBefore) {
sections.push({ type: 'text', content: textBefore });
sections.push({ type: AgenticSectionType.TEXT, content: textBefore });
}
}
@ -189,7 +193,7 @@
const nameMatch = earlyMatch[1]?.match(/<<<TOOL_NAME:([^>]+)>>>/);
sections.push({
type: 'tool_call_streaming',
type: AgenticSectionType.TOOL_CALL_STREAMING,
content: '',
toolName: nameMatch?.[1],
toolArgs: undefined,
@ -207,13 +211,13 @@
}
if (remainingText) {
sections.push({ type: 'text', content: remainingText });
sections.push({ type: AgenticSectionType.TEXT, content: remainingText });
}
}
// If no tool calls found, return content as single text section
if (sections.length === 0 && rawContent.trim()) {
sections.push({ type: 'text', content: rawContent });
sections.push({ type: AgenticSectionType.TEXT, content: rawContent });
}
return sections;
@ -231,13 +235,12 @@
<div class="agentic-content">
{#each sections as section, index (index)}
{#if section.type === 'text'}
{#if section.type === AgenticSectionType.TEXT}
<div class="agentic-text">
<MarkdownContent content={section.content} />
</div>
{:else if section.type === 'tool_call_streaming'}
<!-- Streaming state - show CollapsibleInfoCard with live streaming args -->
<CollapsibleInfoCard
{:else if section.type === AgenticSectionType.TOOL_CALL_STREAMING}
<CollapsibleContentBlock
open={isExpanded(index, true)}
class="my-2"
icon={Loader2}
@ -264,12 +267,12 @@
</div>
{/if}
</div>
</CollapsibleInfoCard>
{:else if section.type === 'tool_call' || section.type === 'tool_call_pending'}
{@const isPending = section.type === 'tool_call_pending'}
</CollapsibleContentBlock>
{:else if section.type === AgenticSectionType.TOOL_CALL || section.type === AgenticSectionType.TOOL_CALL_PENDING}
{@const isPending = section.type === AgenticSectionType.TOOL_CALL_PENDING}
{@const toolIcon = isPending ? Loader2 : Wrench}
{@const toolIconClass = isPending ? 'h-4 w-4 animate-spin' : 'h-4 w-4'}
<CollapsibleInfoCard
<CollapsibleContentBlock
open={isExpanded(index, isPending)}
class="my-2"
icon={toolIcon}
@ -308,7 +311,7 @@
</div>
{/if}
</div>
</CollapsibleInfoCard>
</CollapsibleContentBlock>
{/if}
{/each}
</div>

View File

@ -134,7 +134,7 @@
function handleEdit() {
isEditing = true;
// Clear placeholder content for system messages
// Clear temporary placeholder content for system messages
editedContent =
message.role === 'system' && message.content === SYSTEM_MESSAGE_PLACEHOLDER
? ''
@ -183,7 +183,7 @@
// System messages: update in place without branching
const newContent = editedContent.trim();
// If content is empty or still the placeholder, remove without deleting children
// If content is empty, remove without deleting children
if (!newContent) {
const conversationDeleted = await removeSystemPromptPlaceholder(message.id);
isEditing = false;

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Brain } from '@lucide/svelte';
import { config } from '$lib/stores/settings.svelte';
import CollapsibleInfoCard from './CollapsibleInfoCard.svelte';
import { CollapsibleContentBlock } from '$lib/components/app';
interface Props {
class?: string;
@ -28,7 +28,7 @@
});
</script>
<CollapsibleInfoCard
<CollapsibleContentBlock
bind:open={isExpanded}
class="mb-6 {className}"
icon={Brain}
@ -39,4 +39,4 @@
{reasoningContent ?? ''}
</div>
</div>
</CollapsibleInfoCard>
</CollapsibleContentBlock>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import * as Dialog from '$lib/components/ui/dialog';
import { McpSettingsSection } from '$lib/components/app';
import McpSettingsSection from '../mcp/McpSettingsSection.svelte';
import { config, settingsStore } from '$lib/stores/settings.svelte';
import { Button } from '$lib/components/ui/button';
import McpLogo from '../misc/McpLogo.svelte';

View File

@ -23,7 +23,7 @@ export { default as ChatMessageStatistics } from './chat/ChatMessages/ChatMessag
export { default as ChatMessageSystem } from './chat/ChatMessages/ChatMessageSystem.svelte';
export { default as ChatMessageThinkingBlock } from './chat/ChatMessages/ChatMessageThinkingBlock.svelte';
export { default as ChatMessages } from './chat/ChatMessages/ChatMessages.svelte';
export { default as CollapsibleInfoCard } from './chat/ChatMessages/CollapsibleInfoCard.svelte';
export { default as CollapsibleContentBlock } from './chat/ChatMessages/CollapsibleContentBlock.svelte';
export { default as MessageBranchingControls } from './chat/ChatMessages/ChatMessageBranchingControls.svelte';
export { default as ChatScreen } from './chat/ChatScreen/ChatScreen.svelte';

View File

@ -7,3 +7,13 @@ export interface AgenticConfig {
maxToolPreviewLines: number;
filterReasoningAfterFirstTurn: boolean;
}
/**
* Types of sections in agentic content display.
*/
export enum AgenticSectionType {
TEXT = 'text',
TOOL_CALL = 'tool_call',
TOOL_CALL_PENDING = 'tool_call_pending',
TOOL_CALL_STREAMING = 'tool_call_streaming'
}