-
Arguments:
-
+ {/if}
+ {:else if isStreamingTool}
+
Arguments
+ {#if isStreaming}
+
+ Receiving arguments...
+
+ {:else}
+
+ Response was truncated
{/if}
+ {/if}
+
+ {#if section.toolResult || isPending}
+
Result
+ {#if section.toolResult}
+
+ {#each section.parsedLines as line, i (i)}
+
{line.text}
+ {#if line.image}
+

+ {/if}
+ {/each}
+
+ {:else}
+
+ Waiting for result...
+
+ {/if}
+ {/if}
+
+{/snippet}
-
-
-
Result:
+{#snippet renderFlowGroup(group: InlineFlowGroup)}
+ {@const hasReasoning = group.sections.some((section) => isReasoningSection(section))}
+ {@const isPendingFlow = group.sections.some((section) => isPendingFlowSection(section))}
+ {@const groupTitle = hasReasoning ? (isPendingFlow && isStreaming ? 'Reasoning...' : 'Reasoning') : 'Tool call'}
+ {@const groupSubtitle = hasReasoning && isPendingFlow && !isStreaming ? 'incomplete' : undefined}
+ {@const groupIcon = hasReasoning ? Brain : Wrench}
+
+
toggleExpanded(group)}
+ >
+
+ {#each group.sections as section, groupIndex (group.flatIndices[groupIndex])}
+ {#if groupIndex > 0}
+
+ {/if}
- {#if isPending}
-
- {/if}
-
- {#if section.toolResult}
-
- {#each section.parsedLines as line, i (i)}
-
{line.text}
- {#if line.image}
-

- {/if}
- {/each}
-
- {:else if isPending}
-
- Waiting for result...
+ {#if isReasoningSection(section)}
+
+ {section.content}
+ {:else if isToolSection(section)}
+ {@render renderInlineToolSection(section)}
{/if}
-
-
- {:else if section.type === AgenticSectionType.REASONING}
-
toggleExpanded(index, section)}
- >
-
-
- {section.content}
-
-
-
- {:else if section.type === AgenticSectionType.REASONING_PENDING}
- {@const reasoningTitle = isStreaming ? 'Reasoning...' : 'Reasoning'}
- {@const reasoningSubtitle = isStreaming ? '' : 'incomplete'}
-
-
toggleExpanded(index, section)}
- >
-
-
- {section.content}
-
-
-
+ {/each}
+
+
+{/snippet}
+
+{#snippet renderGroup(group: InlineRenderGroup)}
+ {#if group.kind === 'text'}
+
+
+
+ {:else}
+ {@render renderFlowGroup(group)}
{/if}
{/snippet}
@@ -283,10 +446,11 @@
{#if highlightTurns && turnGroups.length > 1}
{#each turnGroups as turn, turnIndex (turnIndex)}
{@const turnStats = message?.timings?.agentic?.perTurn?.[turnIndex]}
+ {@const turnGroupsInline = buildInlineRenderGroups(turn.sections, turn.flatIndices)}
Turn {turnIndex + 1}
- {#each turn.sections as section, sIdx (turn.flatIndices[sIdx])}
- {@render renderSection(section, turn.flatIndices[sIdx])}
+ {#each turnGroupsInline as group, groupIndex (`${turnIndex}-${groupIndex}`)}
+ {@render renderGroup(group)}
{/each}
{#if turnStats}
@@ -306,8 +470,8 @@
{/each}
{:else}
- {#each sectionsParsed as section, index (index)}
- {@render renderSection(section, index)}
+ {#each inlineGroups as group, groupIndex (groupIndex)}
+ {@render renderGroup(group)}
{/each}
{/if}
@@ -325,6 +489,42 @@
width: 100%;
}
+ .agentic-inline-flow {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+ padding-top: 0.75rem;
+ }
+
+ .agentic-flow-divider {
+ border-top: 1px solid hsl(var(--muted) / 0.75);
+ }
+
+ .agentic-inline-tool {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ padding: 0.875rem;
+ border: 1px solid hsl(var(--border) / 0.75);
+ border-radius: 0.75rem;
+ background: hsl(var(--muted) / 0.35);
+ }
+
+ .agentic-inline-tool-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+ }
+
+ .agentic-inline-label {
+ font-size: 0.7rem;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--muted-foreground);
+ }
+
.agentic-turn {
position: relative;
border: 1.5px dashed var(--muted-foreground);
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettings.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettings.svelte
index 44d59e2b360..844cf5c7adf 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettings.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettings.svelte
@@ -5,6 +5,9 @@
AlertTriangle,
Code,
Monitor,
+ Sun,
+ Moon,
+ Wrench,
ChevronLeft,
ChevronRight,
Database
@@ -23,13 +26,15 @@
type SettingsSectionTitle,
NUMERIC_FIELDS,
POSITIVE_INTEGER_FIELDS,
- SETTINGS_COLOR_MODES_CONFIG,
SETTINGS_KEYS
} from '$lib/constants';
import { setMode } from 'mode-watcher';
import { ColorMode } from '$lib/enums/ui';
import { SettingsFieldType } from '$lib/enums/settings';
import type { Component } from 'svelte';
+ import '$lib/services/tools';
+ import { getAllTools } from '$lib/services/tools';
+ import type { ToolRegistration } from '$lib/services/tools/registry';
interface Props {
onSave?: () => void;
@@ -38,7 +43,13 @@
let { onSave, initialSection }: Props = $props();
- const settingSections: Array<{
+ const THEME_OPTIONS = [
+ { value: ColorMode.SYSTEM, label: 'System', icon: Monitor },
+ { value: ColorMode.LIGHT, label: 'Light', icon: Sun },
+ { value: ColorMode.DARK, label: 'Dark', icon: Moon }
+ ];
+
+ const baseSettingSections: Array<{
fields: SettingsFieldConfig[];
icon: Component;
title: SettingsSectionTitle;
@@ -51,7 +62,7 @@
key: SETTINGS_KEYS.THEME,
label: 'Theme',
type: SettingsFieldType.SELECT,
- options: SETTINGS_COLOR_MODES_CONFIG
+ options: THEME_OPTIONS
},
{ key: SETTINGS_KEYS.API_KEY, label: 'API Key', type: SettingsFieldType.INPUT },
{
@@ -323,13 +334,69 @@
// }
];
+ let localConfig: SettingsConfigType = $state({ ...config() });
+
+ function getToolFields(cfg: SettingsConfigType): SettingsFieldConfig[] {
+ const toolGroups = new Map
();
+
+ for (const tool of getAllTools()) {
+ const existing = toolGroups.get(tool.enableConfigKey);
+ if (existing) {
+ existing.push(tool);
+ } else {
+ toolGroups.set(tool.enableConfigKey, [tool]);
+ }
+ }
+
+ return Array.from(toolGroups.values()).flatMap((tools) => {
+ const [primaryTool] = tools;
+ const enabled = Boolean(cfg[primaryTool.enableConfigKey]);
+ const mergedSettings = new Map();
+
+ for (const tool of tools) {
+ for (const setting of tool.settings ?? []) {
+ if (!mergedSettings.has(setting.key)) {
+ mergedSettings.set(setting.key, setting);
+ }
+ }
+ }
+
+ return [
+ {
+ key: primaryTool.enableConfigKey,
+ label: primaryTool.label,
+ type: SettingsFieldType.CHECKBOX,
+ help: primaryTool.description
+ },
+ ...Array.from(mergedSettings.values()).map((setting) => ({
+ ...setting,
+ disabled: !enabled
+ }))
+ ];
+ });
+ }
+
+ let settingSections = $derived.by(
+ (): Array<{
+ fields: SettingsFieldConfig[];
+ icon: Component;
+ title: SettingsSectionTitle;
+ }> => [
+ ...baseSettingSections,
+ {
+ title: SETTINGS_SECTION_TITLES.TOOLS,
+ icon: Wrench,
+ fields: getToolFields(localConfig)
+ }
+ ]
+ );
+
let activeSection = $derived(
initialSection ?? SETTINGS_SECTION_TITLES.GENERAL
);
let currentSection = $derived(
settingSections.find((section) => section.title === activeSection) || settingSections[0]
);
- let localConfig: SettingsConfigType = $state({ ...config() });
let canScrollLeft = $state(false);
let canScrollRight = $state(false);
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte
index b9015c196c1..282ffc5a764 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte
@@ -70,6 +70,7 @@
{
// Update local config immediately for real-time badge feedback
onConfigChange(field.key, e.currentTarget.value);
@@ -111,6 +112,7 @@