webui: Add switcher to Chat Message UI to show raw LLM output (#19571)
This commit is contained in:
committed by
GitHub
parent
4b385bfcf8
commit
4c61875bf8
Binary file not shown.
@@ -139,6 +139,6 @@ sequenceDiagram
|
|||||||
|
|
||||||
Note over settingsStore: UI-only (not synced):
|
Note over settingsStore: UI-only (not synced):
|
||||||
rect rgb(255, 240, 240)
|
rect rgb(255, 240, 240)
|
||||||
Note over settingsStore: systemMessage, custom (JSON)<br/>showStatistics, enableContinueGeneration<br/>autoMicOnEmpty, disableAutoScroll<br/>apiKey, pdfAsImage, disableReasoningFormat
|
Note over settingsStore: systemMessage, custom (JSON)<br/>showStatistics, enableContinueGeneration<br/>autoMicOnEmpty, disableAutoScroll<br/>apiKey, pdfAsImage, disableReasoningParsing, showRawOutputSwitch
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|||||||
+20
-3
@@ -5,6 +5,7 @@
|
|||||||
ChatMessageBranchingControls,
|
ChatMessageBranchingControls,
|
||||||
DialogConfirmation
|
DialogConfirmation
|
||||||
} from '$lib/components/app';
|
} from '$lib/components/app';
|
||||||
|
import { Switch } from '$lib/components/ui/switch';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
role: 'user' | 'assistant';
|
role: 'user' | 'assistant';
|
||||||
@@ -26,6 +27,9 @@
|
|||||||
onConfirmDelete: () => void;
|
onConfirmDelete: () => void;
|
||||||
onNavigateToSibling?: (siblingId: string) => void;
|
onNavigateToSibling?: (siblingId: string) => void;
|
||||||
onShowDeleteDialogChange: (show: boolean) => void;
|
onShowDeleteDialogChange: (show: boolean) => void;
|
||||||
|
showRawOutputSwitch?: boolean;
|
||||||
|
rawOutputEnabled?: boolean;
|
||||||
|
onRawOutputToggle?: (enabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -42,7 +46,10 @@
|
|||||||
onRegenerate,
|
onRegenerate,
|
||||||
role,
|
role,
|
||||||
siblingInfo = null,
|
siblingInfo = null,
|
||||||
showDeleteDialog
|
showDeleteDialog,
|
||||||
|
showRawOutputSwitch = false,
|
||||||
|
rawOutputEnabled = false,
|
||||||
|
onRawOutputToggle
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
function handleConfirmDelete() {
|
function handleConfirmDelete() {
|
||||||
@@ -51,9 +58,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative {justify === 'start' ? 'mt-2' : ''} flex h-6 items-center justify-{justify}">
|
<div class="relative {justify === 'start' ? 'mt-2' : ''} flex h-6 items-center justify-between">
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 {actionsPosition === 'left'
|
class="{actionsPosition === 'left'
|
||||||
? 'left-0'
|
? 'left-0'
|
||||||
: 'right-0'} flex items-center gap-2 opacity-100 transition-opacity"
|
: 'right-0'} flex items-center gap-2 opacity-100 transition-opacity"
|
||||||
>
|
>
|
||||||
@@ -81,6 +88,16 @@
|
|||||||
<ActionButton icon={Trash2} tooltip="Delete" onclick={onDelete} />
|
<ActionButton icon={Trash2} tooltip="Delete" onclick={onDelete} />
|
||||||
</div>
|
</div>
|
||||||
</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)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogConfirmation
|
<DialogConfirmation
|
||||||
|
|||||||
+7
-1
@@ -90,6 +90,9 @@
|
|||||||
|
|
||||||
const processingState = useProcessingState();
|
const processingState = useProcessingState();
|
||||||
|
|
||||||
|
// Local state for raw output toggle (per message)
|
||||||
|
let showRawOutput = $state(false);
|
||||||
|
|
||||||
let currentConfig = $derived(config());
|
let currentConfig = $derived(config());
|
||||||
let isRouter = $derived(isRouterMode());
|
let isRouter = $derived(isRouterMode());
|
||||||
let displayedModel = $derived((): string | null => {
|
let displayedModel = $derived((): string | null => {
|
||||||
@@ -238,7 +241,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if message.role === 'assistant'}
|
{:else if message.role === 'assistant'}
|
||||||
{#if config().disableReasoningFormat}
|
{#if showRawOutput}
|
||||||
<pre class="raw-output">{messageContent || ''}</pre>
|
<pre class="raw-output">{messageContent || ''}</pre>
|
||||||
{:else}
|
{:else}
|
||||||
<MarkdownContent content={messageContent || ''} />
|
<MarkdownContent content={messageContent || ''} />
|
||||||
@@ -352,6 +355,9 @@
|
|||||||
{onConfirmDelete}
|
{onConfirmDelete}
|
||||||
{onNavigateToSibling}
|
{onNavigateToSibling}
|
||||||
{onShowDeleteDialogChange}
|
{onShowDeleteDialogChange}
|
||||||
|
showRawOutputSwitch={currentConfig.showRawOutputSwitch}
|
||||||
|
rawOutputEnabled={showRawOutput}
|
||||||
|
onRawOutputToggle={(enabled) => (showRawOutput = enabled)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
chatStore,
|
chatStore,
|
||||||
errorDialog,
|
errorDialog,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isChatStreaming,
|
||||||
isEditing,
|
isEditing,
|
||||||
getAddFilesHandler
|
getAddFilesHandler
|
||||||
} from '$lib/stores/chat.svelte';
|
} from '$lib/stores/chat.svelte';
|
||||||
@@ -81,7 +82,7 @@
|
|||||||
let isServerLoading = $derived(serverLoading());
|
let isServerLoading = $derived(serverLoading());
|
||||||
let hasPropsError = $derived(!!serverError());
|
let hasPropsError = $derived(!!serverError());
|
||||||
|
|
||||||
let isCurrentConversationLoading = $derived(isLoading());
|
let isCurrentConversationLoading = $derived(isLoading() || isChatStreaming());
|
||||||
|
|
||||||
let isRouter = $derived(isRouterMode());
|
let isRouter = $derived(isRouterMode());
|
||||||
|
|
||||||
|
|||||||
@@ -254,8 +254,13 @@
|
|||||||
type: 'checkbox'
|
type: 'checkbox'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'disableReasoningFormat',
|
key: 'disableReasoningParsing',
|
||||||
label: 'Show raw LLM output',
|
label: 'Disable reasoning content parsing',
|
||||||
|
type: 'checkbox'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'showRawOutputSwitch',
|
||||||
|
label: 'Enable raw output toggle',
|
||||||
type: 'checkbox'
|
type: 'checkbox'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
|
|||||||
theme: 'system',
|
theme: 'system',
|
||||||
showThoughtInProgress: false,
|
showThoughtInProgress: false,
|
||||||
showToolCalls: false,
|
showToolCalls: false,
|
||||||
disableReasoningFormat: false,
|
disableReasoningParsing: false,
|
||||||
|
showRawOutputSwitch: false,
|
||||||
keepStatsVisible: false,
|
keepStatsVisible: false,
|
||||||
showMessageStats: true,
|
showMessageStats: true,
|
||||||
askForTitleConfirmation: false,
|
askForTitleConfirmation: false,
|
||||||
@@ -92,8 +93,10 @@ export const SETTING_CONFIG_INFO: Record<string, string> = {
|
|||||||
showThoughtInProgress: 'Expand thought process by default when generating messages.',
|
showThoughtInProgress: 'Expand thought process by default when generating messages.',
|
||||||
showToolCalls:
|
showToolCalls:
|
||||||
'Display tool call labels and payloads from Harmony-compatible delta.tool_calls data below assistant messages.',
|
'Display tool call labels and payloads from Harmony-compatible delta.tool_calls data below assistant messages.',
|
||||||
disableReasoningFormat:
|
disableReasoningParsing:
|
||||||
'Show raw LLM output without backend parsing and frontend Markdown rendering to inspect streaming across different models.',
|
'Send reasoning_format=none to prevent server-side extraction of reasoning tokens into separate field',
|
||||||
|
showRawOutputSwitch:
|
||||||
|
'Show toggle button to display messages as plain text instead of Markdown-formatted content',
|
||||||
keepStatsVisible: 'Keep processing statistics visible after generation finishes.',
|
keepStatsVisible: 'Keep processing statistics visible after generation finishes.',
|
||||||
showMessageStats:
|
showMessageStats:
|
||||||
'Display generation statistics (tokens/second, token count, duration) below each assistant message.',
|
'Display generation statistics (tokens/second, token count, duration) below each assistant message.',
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export class ChatService {
|
|||||||
custom,
|
custom,
|
||||||
timings_per_token,
|
timings_per_token,
|
||||||
// Config options
|
// Config options
|
||||||
disableReasoningFormat
|
disableReasoningParsing
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const normalizedMessages: ApiChatMessageData[] = messages
|
const normalizedMessages: ApiChatMessageData[] = messages
|
||||||
@@ -127,7 +127,7 @@ export class ChatService {
|
|||||||
requestBody.model = options.model;
|
requestBody.model = options.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestBody.reasoning_format = disableReasoningFormat ? 'none' : 'auto';
|
requestBody.reasoning_format = disableReasoningParsing ? 'none' : 'auto';
|
||||||
|
|
||||||
if (temperature !== undefined) requestBody.temperature = temperature;
|
if (temperature !== undefined) requestBody.temperature = temperature;
|
||||||
if (max_tokens !== undefined) {
|
if (max_tokens !== undefined) {
|
||||||
|
|||||||
@@ -70,12 +70,6 @@ export const SYNCABLE_PARAMETERS: SyncableParameter[] = [
|
|||||||
canSync: true
|
canSync: true
|
||||||
},
|
},
|
||||||
{ key: 'showToolCalls', serverKey: 'showToolCalls', type: 'boolean', canSync: true },
|
{ key: 'showToolCalls', serverKey: 'showToolCalls', type: 'boolean', canSync: true },
|
||||||
{
|
|
||||||
key: 'disableReasoningFormat',
|
|
||||||
serverKey: 'disableReasoningFormat',
|
|
||||||
type: 'boolean',
|
|
||||||
canSync: true
|
|
||||||
},
|
|
||||||
{ key: 'keepStatsVisible', serverKey: 'keepStatsVisible', type: 'boolean', canSync: true },
|
{ key: 'keepStatsVisible', serverKey: 'keepStatsVisible', type: 'boolean', canSync: true },
|
||||||
{ key: 'showMessageStats', serverKey: 'showMessageStats', type: 'boolean', canSync: true },
|
{ key: 'showMessageStats', serverKey: 'showMessageStats', type: 'boolean', canSync: true },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -118,6 +118,16 @@ class ChatStore {
|
|||||||
this.isLoading = this.isChatLoading(convId);
|
this.isLoading = this.isChatLoading(convId);
|
||||||
const streamingState = this.getChatStreaming(convId);
|
const streamingState = this.getChatStreaming(convId);
|
||||||
this.currentResponse = streamingState?.response || '';
|
this.currentResponse = streamingState?.response || '';
|
||||||
|
this.isStreamingActive = streamingState !== undefined;
|
||||||
|
this.setActiveProcessingConversation(convId);
|
||||||
|
|
||||||
|
// Sync streaming content to activeMessages so UI displays current content
|
||||||
|
if (streamingState?.response && streamingState?.messageId) {
|
||||||
|
const idx = conversationsStore.findMessageIndex(streamingState.messageId);
|
||||||
|
if (idx !== -1) {
|
||||||
|
conversationsStore.updateMessageAtIndex(idx, { content: streamingState.response });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1639,7 +1649,7 @@ class ChatStore {
|
|||||||
|
|
||||||
// Config options needed by ChatService
|
// Config options needed by ChatService
|
||||||
if (currentConfig.systemMessage) apiOptions.systemMessage = currentConfig.systemMessage;
|
if (currentConfig.systemMessage) apiOptions.systemMessage = currentConfig.systemMessage;
|
||||||
if (currentConfig.disableReasoningFormat) apiOptions.disableReasoningFormat = true;
|
if (currentConfig.disableReasoningParsing) apiOptions.disableReasoningParsing = true;
|
||||||
|
|
||||||
if (hasValue(currentConfig.temperature))
|
if (hasValue(currentConfig.temperature))
|
||||||
apiOptions.temperature = Number(currentConfig.temperature);
|
apiOptions.temperature = Number(currentConfig.temperature);
|
||||||
|
|||||||
+2
-2
@@ -18,8 +18,8 @@ export interface SettingsChatServiceOptions {
|
|||||||
model?: string;
|
model?: string;
|
||||||
// System message to inject
|
// System message to inject
|
||||||
systemMessage?: string;
|
systemMessage?: string;
|
||||||
// Disable reasoning format (use 'none' instead of 'auto')
|
// Disable reasoning parsing (use 'none' instead of 'auto')
|
||||||
disableReasoningFormat?: boolean;
|
disableReasoningParsing?: boolean;
|
||||||
// Generation parameters
|
// Generation parameters
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
max_tokens?: number;
|
max_tokens?: number;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
settingsStore.updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
settingsStore.updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
settingsStore.updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
settingsStore.updateConfig('disableReasoningFormat', true);
|
settingsStore.updateConfig('showRawOutputSwitch', true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
asChild
|
asChild
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
settingsStore.updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||||
// Phase 1: Stream reasoning content in chunks
|
// Phase 1: Stream reasoning content in chunks
|
||||||
let reasoningText =
|
let reasoningText =
|
||||||
'I need to think about this carefully. Let me break down the problem:\n\n1. The user is asking for help with something complex\n2. I should provide a thorough and helpful response\n3. I need to consider multiple approaches\n4. The best solution would be to explain step by step\n\nThis approach will ensure clarity and understanding.';
|
'I need to think about this carefully. Let me break down the problem:\n\n1. The user is asking for help with something complex\n2. I should provide a thorough and helpful response\n3. I need to consider multiple approaches\n4. The best solution would be to explain step by step\n\nThis approach will ensure clarity and understanding.';
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
settingsStore.updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||||
// Import the chat store to simulate loading state
|
// Import the chat store to simulate loading state
|
||||||
const { chatStore } = await import('$lib/stores/chat.svelte');
|
const { chatStore } = await import('$lib/stores/chat.svelte');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user