Skip to content

Commit 333a167

Browse files
Algorithm5838github-actions[bot]
authored andcommitted
refactor: render from output and derive content at save
1 parent cd5a06c commit 333a167

4 files changed

Lines changed: 542 additions & 75 deletions

File tree

backend/open_webui/utils/middleware.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4184,7 +4184,6 @@ async def flush_pending_delta_data(threshold: int = 0):
41844184

41854185
processed_data = {
41864186
'output': full_output(),
4187-
'content': serialize_output(full_output()),
41884187
}
41894188

41904189
# print(data)
@@ -4350,7 +4349,7 @@ async def flush_pending_delta_data(threshold: int = 0):
43504349
{
43514350
'type': 'chat:completion',
43524351
'data': {
4353-
'content': serialize_output(full_output() + pending_fc_items),
4352+
'output': full_output() + pending_fc_items,
43544353
},
43554354
}
43564355
)
@@ -4428,7 +4427,7 @@ async def flush_pending_delta_data(threshold: int = 0):
44284427
_pending_reasoning_details.extend(items)
44294428

44304429
if reasoning_content or reasoning_details_chunk:
4431-
data = {'content': serialize_output(full_output())}
4430+
data = {'output': full_output()}
44324431

44334432
if value:
44344433
if (
@@ -4587,7 +4586,7 @@ async def flush_pending_delta_data(threshold: int = 0):
45874586
)
45884587
else:
45894588
data = {
4590-
'content': serialize_output(full_output()),
4589+
'output': full_output(),
45914590
}
45924591

45934592
if delta:

src/lib/components/chat/Chat.svelte

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,9 +1168,17 @@
11681168
const messages = history ? createMessagesList(history, history.currentId) : [];
11691169
let contents = [];
11701170
messages.forEach((message) => {
1171-
if (message?.role !== 'user' && message?.content) {
1171+
if (message?.role !== 'user') {
1172+
let text = message?.content || '';
1173+
if (!text && message?.output?.length) {
1174+
text = message.output
1175+
.filter((i) => i.type === 'message')
1176+
.flatMap((i) => (i.content ?? []).map((p) => p.text ?? ''))
1177+
.join('\n');
1178+
}
1179+
if (!text) return;
11721180
const { codeBlocks: codeBlocks, htmlGroups: htmlGroups } = getCodeBlockContents(
1173-
message.content
1181+
text
11741182
);
11751183
11761184
if (htmlGroups && htmlGroups.length > 0) {
@@ -1825,9 +1833,34 @@
18251833
const chatCompletionEventHandler = async (data, message, chatId) => {
18261834
const { id, done, choices, content, output, sources, selected_model_id, error, usage } = data;
18271835
1836+
const extractTextFromOutput = (o) =>
1837+
o
1838+
.filter((i) => i.type === 'message')
1839+
.flatMap((i) => (i.content ?? []).map((p) => (p.text ?? '').trim()))
1840+
.filter(Boolean)
1841+
.join('\n');
1842+
1843+
const dispatchStreamingTTS = (text) => {
1844+
if (!$showCallOverlay) return;
1845+
const parts = getMessageContentParts(
1846+
text,
1847+
$config?.audio?.tts?.split_on ?? 'punctuation'
1848+
);
1849+
parts.pop();
1850+
if (parts.length > 0 && parts[parts.length - 1] !== message.lastSentence) {
1851+
message.lastSentence = parts[parts.length - 1];
1852+
eventTarget.dispatchEvent(
1853+
new CustomEvent('chat', {
1854+
detail: { id: message.id, content: parts[parts.length - 1] }
1855+
})
1856+
);
1857+
}
1858+
};
1859+
18281860
// Store raw OR-aligned output items from backend
18291861
if (output) {
18301862
message.output = output;
1863+
dispatchStreamingTTS(extractTextFromOutput(output));
18311864
}
18321865
18331866
if (error) {
@@ -1838,7 +1871,9 @@
18381871
message.sources = sources;
18391872
}
18401873
1841-
if (choices) {
1874+
// Only accumulate content from choices when there's no structured output
1875+
// (output-based rendering takes priority — avoids redundant serialize/parse round-trip)
1876+
if (choices && !output) {
18421877
if (choices[0]?.message?.content) {
18431878
// Non-stream response
18441879
message.content += choices[0]?.message?.content;
@@ -1854,30 +1889,7 @@
18541889
navigator.vibrate(5);
18551890
}
18561891
1857-
// Emit chat event for TTS (only when call overlay is active)
1858-
if ($showCallOverlay) {
1859-
const messageContentParts = getMessageContentParts(
1860-
removeAllDetails(message.content),
1861-
$config?.audio?.tts?.split_on ?? 'punctuation'
1862-
);
1863-
messageContentParts.pop();
1864-
1865-
// dispatch only last sentence and make sure it hasn't been dispatched before
1866-
if (
1867-
messageContentParts.length > 0 &&
1868-
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1869-
) {
1870-
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1871-
eventTarget.dispatchEvent(
1872-
new CustomEvent('chat', {
1873-
detail: {
1874-
id: message.id,
1875-
content: messageContentParts[messageContentParts.length - 1]
1876-
}
1877-
})
1878-
);
1879-
}
1880-
}
1892+
dispatchStreamingTTS(removeAllDetails(message.content));
18811893
}
18821894
}
18831895
}
@@ -1890,30 +1902,7 @@
18901902
navigator.vibrate(5);
18911903
}
18921904
1893-
// Emit chat event for TTS (only when call overlay is active)
1894-
if ($showCallOverlay) {
1895-
const messageContentParts = getMessageContentParts(
1896-
removeAllDetails(message.content),
1897-
$config?.audio?.tts?.split_on ?? 'punctuation'
1898-
);
1899-
messageContentParts.pop();
1900-
1901-
// dispatch only last sentence and make sure it hasn't been dispatched before
1902-
if (
1903-
messageContentParts.length > 0 &&
1904-
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1905-
) {
1906-
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1907-
eventTarget.dispatchEvent(
1908-
new CustomEvent('chat', {
1909-
detail: {
1910-
id: message.id,
1911-
content: messageContentParts[messageContentParts.length - 1]
1912-
}
1913-
})
1914-
);
1915-
}
1916-
}
1905+
dispatchStreamingTTS(removeAllDetails(message.content));
19171906
}
19181907
19191908
if (selected_model_id) {

0 commit comments

Comments
 (0)