diff --git a/src/lib/components/chat/Messages/ContextCompactionLabel.svelte b/src/lib/components/chat/Messages/ContextCompactionLabel.svelte new file mode 100644 index 0000000000..74103c5031 --- /dev/null +++ b/src/lib/components/chat/Messages/ContextCompactionLabel.svelte @@ -0,0 +1,34 @@ + + +{#if summary.trim()} + + +
+ + + {$i18n.t('Context compacted')} + + +
+ {summary} +
+
+{:else} +
+ {$i18n.t('Context compacted')} +
+{/if} diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 3a383252aa..517549925a 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -61,6 +61,7 @@ import Citations from './Citations.svelte'; import CodeExecutions from './CodeExecutions.svelte'; import ContentRenderer from './ContentRenderer.svelte'; + import ContextCompactionLabel from './ContextCompactionLabel.svelte'; import { KokoroWorker } from '$lib/workers/KokoroWorker'; import FileItem from '$lib/components/common/FileItem.svelte'; import FollowUps from './ResponseMessage/FollowUps.svelte'; @@ -78,6 +79,7 @@ model: string; content: string; output?: OutputItem[]; + contextSummary?: string; files?: { type: string; url: string }[]; timestamp: number; role: string; @@ -712,6 +714,10 @@ {/if} + {#if message.contextSummary} + + {/if} +
diff --git a/src/lib/components/chat/Messages/StructuredOutputRenderer.svelte b/src/lib/components/chat/Messages/StructuredOutputRenderer.svelte index f4ae7dcf81..e5d17b79ab 100644 --- a/src/lib/components/chat/Messages/StructuredOutputRenderer.svelte +++ b/src/lib/components/chat/Messages/StructuredOutputRenderer.svelte @@ -8,6 +8,7 @@ import Markdown from './Markdown.svelte'; import ConsecutiveDetailsGroup from './Markdown/ConsecutiveDetailsGroup.svelte'; + import ContextCompactionLabel from './ContextCompactionLabel.svelte'; import { buildOutputDisplayItems, type OutputDetailToken, @@ -170,6 +171,8 @@ {#if displayItem.item?.displayed || $settings?.terminalFileDisplay === 'inline'} {/if} + {:else if displayItem.type === 'context_compaction'} + {:else} {@const detailToken = displayItem.token} {#if detailToken.attributes?.type === 'tool_calls'} diff --git a/src/lib/components/chat/Messages/UserMessage.svelte b/src/lib/components/chat/Messages/UserMessage.svelte index 5908522b0c..816568bc43 100644 --- a/src/lib/components/chat/Messages/UserMessage.svelte +++ b/src/lib/components/chat/Messages/UserMessage.svelte @@ -18,6 +18,7 @@ import equal from 'fast-deep-equal'; import Name from './Name.svelte'; + import ContextCompactionLabel from './ContextCompactionLabel.svelte'; import ProfileImage from './ProfileImage.svelte'; import ProfilePreview from '$lib/components/channel/Messages/Message/ProfilePreview.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; @@ -195,6 +196,10 @@
{/if} + {#if message.contextSummary} + + {/if} +
{#if edit !== true} {#if message.files} diff --git a/src/lib/components/chat/Messages/structuredOutput.ts b/src/lib/components/chat/Messages/structuredOutput.ts index e8b86964fd..df7dfef139 100644 --- a/src/lib/components/chat/Messages/structuredOutput.ts +++ b/src/lib/components/chat/Messages/structuredOutput.ts @@ -14,7 +14,7 @@ export type OutputItem = { status?: string; arguments?: unknown; content?: OutputContentPart[]; - summary?: OutputContentPart[]; + summary?: OutputContentPart[] | string; output?: OutputContentPart[]; files?: unknown; embeds?: unknown; @@ -64,6 +64,11 @@ export type OutputDisplayItem = type: 'file'; id: string; item: Record; + } + | { + type: 'context_compaction'; + id: string; + summary: string; }; type ResponseStreamEvent = { @@ -409,6 +414,16 @@ export function buildOutputDisplayItems(output: OutputItem[] = []): OutputDispla return; } + if (item.type === 'open_webui:context_compaction') { + flushDetails(); + displayItems.push({ + type: 'context_compaction', + id: item.id ?? `context-compaction-${index}`, + summary: typeof item.summary === 'string' ? item.summary : '' + }); + return; + } + if (item.type === 'message') { const text = getMessageText(item); if (text.trim()) { @@ -618,7 +633,7 @@ export function applyResponseStreamEvent( if (!event.part) { return nextOutput; } - item.summary = [...(item.summary ?? [])]; + item.summary = Array.isArray(item.summary) ? [...item.summary] : []; const summaryIndex = event.summary_index ?? item.summary.length; setPart(item.summary, summaryIndex, { ...event.part }, { type: 'summary_text', text: '' }); return nextOutput; @@ -633,7 +648,7 @@ export function applyResponseStreamEvent( if (deltaType === 'reasoning_summary_text') { const summaryIndex = event.summary_index ?? 0; - item.summary = [...(item.summary ?? [])]; + item.summary = Array.isArray(item.summary) ? [...item.summary] : []; const part = ensurePart(item.summary, summaryIndex, { type: 'summary_text', text: '' }); part.text = appendDelta(part.text ?? '', event.delta); return nextOutput; diff --git a/src/lib/components/chat/Overview/Node.svelte b/src/lib/components/chat/Overview/Node.svelte index 08b53edb66..e1654a7a18 100644 --- a/src/lib/components/chat/Overview/Node.svelte +++ b/src/lib/components/chat/Overview/Node.svelte @@ -18,6 +18,10 @@ getOutputText(nodeData?.message?.output) || nodeData?.message?.content || ''; $: messageContent = getMessageContent(data); + $: isCompacted = + data?.message?.contextSummary || + data?.message?.output?.some((item) => item?.type === 'open_webui:context_compaction'); + $: previewClass = `text-xs mt-0.5 ${isCompacted ? 'line-clamp-1' : 'line-clamp-2'}`; $: modelName = data?.model ? resolveLocalizedModelName(data.model, $i18n.language) : (data?.message?.model ?? 'Assistant'); @@ -45,9 +49,13 @@
{#if data?.message?.error} -
{data.message.error.content}
+
+ {data.message.error.content} +
{:else} -
{messageContent}
+
+ {messageContent} +
{/if}
@@ -83,16 +91,25 @@ {#if data?.message?.error} -
+
{data.message.error.content}
{:else} -
{messageContent}
+
+ {messageContent} +
{/if}
{/if} + {#if isCompacted} +
+ {$i18n.t('Context compacted')} +
+ {/if}