Skip to content

Commit 928af2e

Browse files
refactor: address review feedback from allozaur
- Passed the assistant message content directly to ChatMessageAssistant to drop the redundant derived state in the chat message component - Simplified chat streaming updates by removing unused partial-thinking handling and persisting partial responses straight from currentResponse - Refreshed the ChatMessage stories to cover standard and reasoning scenarios without the old THINK-tag parsing examples Co-authored-by: Aleksander Grygier <[email protected]>
1 parent d70fd06 commit 928af2e

File tree

3 files changed

+27
-171
lines changed

3 files changed

+27
-171
lines changed

tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessage.svelte

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@
5252
return null;
5353
});
5454
55-
let messageContent = $derived.by(() => message.content);
56-
5755
function handleCancelEdit() {
5856
isEditing = false;
5957
editedContent = message.content;
@@ -152,7 +150,7 @@
152150
{editedContent}
153151
{isEditing}
154152
{message}
155-
{messageContent}
153+
messageContent={message.content}
156154
onCancelEdit={handleCancelEdit}
157155
onConfirmDelete={handleConfirmDelete}
158156
onCopy={handleCopy}

tools/server/webui/src/lib/stores/chat.svelte.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ class ChatStore {
340340
streamedContent += chunk;
341341
this.currentResponse = streamedContent;
342342

343+
captureModelIfNeeded();
343344
const messageIndex = this.findMessageIndex(assistantMessage.id);
344345
this.updateMessageAtIndex(messageIndex, {
345346
content: streamedContent
@@ -690,14 +691,12 @@ class ChatStore {
690691

691692
if (lastMessage && lastMessage.role === 'assistant') {
692693
try {
693-
const contentToSave = this.currentResponse;
694-
695694
const updateData: {
696695
content: string;
697696
thinking?: string;
698697
timings?: ChatMessageTimings;
699698
} = {
700-
content: contentToSave
699+
content: this.currentResponse
701700
};
702701

703702
if (lastMessage.thinking?.trim()) {
@@ -721,7 +720,7 @@ class ChatStore {
721720

722721
await DatabaseStore.updateMessage(lastMessage.id, updateData);
723722

724-
lastMessage.content = contentToSave;
723+
lastMessage.content = this.currentResponse;
725724
if (updateData.thinking !== undefined) {
726725
lastMessage.thinking = updateData.thinking;
727726
}

tools/server/webui/src/stories/ChatMessage.stories.svelte

Lines changed: 23 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -36,76 +36,34 @@
3636
children: []
3737
};
3838
39-
let processingMessage = $state({
40-
id: '4',
41-
convId: 'conv-1',
42-
type: 'message',
43-
timestamp: 0, // No timestamp = processing
44-
role: 'assistant',
45-
content: '',
46-
parent: '1',
47-
thinking: '',
48-
children: []
49-
});
50-
51-
let streamingMessage = $state({
52-
id: '5',
53-
convId: 'conv-1',
54-
type: 'message',
55-
timestamp: 0, // No timestamp = streaming
56-
role: 'assistant',
57-
content: '',
58-
parent: '1',
59-
thinking: '',
60-
children: []
61-
});
62-
63-
// Message with dedicated thinking content
64-
const thinkTagMessage: DatabaseMessage = {
65-
id: '6',
39+
const assistantWithReasoning: DatabaseMessage = {
40+
id: '3',
6641
convId: 'conv-1',
6742
type: 'message',
6843
timestamp: Date.now() - 1000 * 60 * 2,
6944
role: 'assistant',
7045
content:
71-
"Here's my response after thinking through the problem. The reasoning should appear separately from this main response content.",
46+
"Here's the concise answer, now that I've thought it through carefully for you.",
7247
parent: '1',
7348
thinking:
74-
"Let me analyze this step by step:\n\n1. The user is asking about thinking formats\n2. I need to demonstrate the &lt;think&gt; tag format\n3. This content should be displayed in the thinking section\n4. The main response should be separate\n\nThis is a good example of reasoning content.",
49+
"Let's consider the user's question step by step:\\n\\n1. Identify the core problem\\n2. Evaluate relevant information\\n3. Formulate a clear answer\\n\\nFollowing this process ensures the final response stays focused and accurate.",
7550
children: []
7651
};
7752
78-
// Message with separate DeepSeek-style thinking content
79-
const thinkBracketMessage: DatabaseMessage = {
80-
id: '7',
81-
convId: 'conv-1',
82-
type: 'message',
83-
timestamp: Date.now() - 1000 * 60 * 1,
84-
role: 'assistant',
85-
content:
86-
"Here's my response after using the [THINK] format to organize the answer. The reasoning content should be shown separately from this response.",
87-
parent: '1',
88-
thinking:
89-
"This is the DeepSeek-style thinking format:\n\n- Using square brackets instead of angle brackets\n- Should work identically to the &lt;think&gt; format\n- Reasoning content should display in the thinking section\n- Main response should be separate\n\nBoth formats should be supported seamlessly.",
90-
children: []
91-
};
92-
93-
// Streaming message for dedicated thinking content
94-
let streamingThinkMessage = $state({
95-
id: '8',
53+
let processingMessage = $state({
54+
id: '4',
9655
convId: 'conv-1',
9756
type: 'message',
98-
timestamp: 0, // No timestamp = streaming
57+
timestamp: 0, // No timestamp = processing
9958
role: 'assistant',
10059
content: '',
10160
parent: '1',
10261
thinking: '',
10362
children: []
10463
});
10564
106-
// Streaming message for DeepSeek-style thinking content
107-
let streamingBracketMessage = $state({
108-
id: '9',
65+
let streamingMessage = $state({
66+
id: '5',
10967
convId: 'conv-1',
11068
type: 'message',
11169
timestamp: 0, // No timestamp = streaming
@@ -133,11 +91,19 @@
13391
/>
13492

13593
<Story
136-
name="WithThinkingBlock"
94+
name="AssistantWithReasoning"
95+
args={{
96+
class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
97+
message: assistantWithReasoning
98+
}}
99+
/>
100+
101+
<Story
102+
name="WithReasoningContent"
137103
args={{
138104
message: streamingMessage
139105
}}
140-
asChild
106+
asChild
141107
play={async () => {
142108
// Phase 1: Stream reasoning content in chunks
143109
let reasoningText =
@@ -183,6 +149,7 @@
183149
</div>
184150
</Story>
185151

152+
186153
<Story
187154
name="Processing"
188155
args={{
@@ -191,120 +158,12 @@
191158
play={async () => {
192159
// Import the chat store to simulate loading state
193160
const { chatStore } = await import('$lib/stores/chat.svelte');
194-
161+
195162
// Set loading state to true to trigger the processing UI
196163
chatStore.isLoading = true;
197-
164+
198165
// Simulate the processing state hook behavior
199166
// This will show the "Generating..." text and parameter details
200-
await new Promise(resolve => setTimeout(resolve, 100));
201-
}}
202-
/>
203-
204-
<Story
205-
name="ThinkTagFormat"
206-
args={{
207-
class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
208-
message: thinkTagMessage
167+
await new Promise((resolve) => setTimeout(resolve, 100));
209168
}}
210169
/>
211-
212-
<Story
213-
name="ThinkBracketFormat"
214-
args={{
215-
class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
216-
message: thinkBracketMessage
217-
}}
218-
/>
219-
220-
<Story
221-
name="StreamingThinkTag"
222-
args={{
223-
message: streamingThinkMessage
224-
}}
225-
parameters={{
226-
test: {
227-
timeout: 30000
228-
}
229-
}}
230-
asChild
231-
play={async () => {
232-
// Phase 1: Stream reasoning content
233-
const thinkingContent =
234-
'Let me work through this problem systematically:\n\n1. First, I need to understand what the user is asking\n2. Then I should consider different approaches\n3. I need to evaluate the pros and cons\n4. Finally, I should provide a clear recommendation\n\nThis step-by-step approach will ensure accuracy.';
235-
236-
streamingThinkMessage.thinking = '';
237-
streamingThinkMessage.content = '';
238-
239-
for (let i = 0; i < thinkingContent.length; i++) {
240-
streamingThinkMessage.thinking += thinkingContent[i];
241-
await new Promise((resolve) => setTimeout(resolve, 5));
242-
}
243-
244-
await new Promise((resolve) => setTimeout(resolve, 200));
245-
246-
// Phase 2: Stream main response content
247-
const responseContent =
248-
"Based on my analysis above, here's the solution:\n\n**Key Points:**\n- The approach should be systematic\n- We need to consider all factors\n- Implementation should be step-by-step\n\nThis ensures the best possible outcome.";
249-
250-
let currentContent = '';
251-
252-
for (let i = 0; i < responseContent.length; i++) {
253-
currentContent += responseContent[i];
254-
streamingThinkMessage.content = currentContent;
255-
await new Promise((resolve) => setTimeout(resolve, 10));
256-
}
257-
258-
streamingThinkMessage.timestamp = Date.now();
259-
}}
260-
>
261-
<div class="w-[56rem]">
262-
<ChatMessage message={streamingThinkMessage} />
263-
</div>
264-
</Story>
265-
266-
<Story
267-
name="StreamingThinkBracket"
268-
args={{
269-
message: streamingBracketMessage
270-
}}
271-
parameters={{
272-
test: {
273-
timeout: 30000
274-
}
275-
}}
276-
asChild
277-
play={async () => {
278-
// Phase 1: Stream DeepSeek-style reasoning content
279-
const thinkingContent =
280-
'Using the DeepSeek format now:\n\n- This demonstrates the &#91;THINK&#93; bracket format\n- Should behave identically to the &lt;think&gt; format\n- The UI should display this in the thinking section\n- Main content should be separate\n\nBoth formats provide the same functionality.';
281-
282-
streamingBracketMessage.thinking = '';
283-
streamingBracketMessage.content = '';
284-
285-
for (let i = 0; i < thinkingContent.length; i++) {
286-
streamingBracketMessage.thinking += thinkingContent[i];
287-
await new Promise((resolve) => setTimeout(resolve, 5));
288-
}
289-
290-
await new Promise((resolve) => setTimeout(resolve, 200));
291-
292-
// Phase 2: Stream main response content
293-
const responseContent =
294-
"Here's my response after using the &#91;THINK&#93; format:\n\n**Observations:**\n- Both &lt;think&gt; and &#91;THINK&#93; formats work seamlessly\n- The helper logic handles both cases\n- UI display is consistent across formats\n\nThis demonstrates the enhanced thinking content support.";
295-
296-
let currentContent = '';
297-
298-
for (let i = 0; i < responseContent.length; i++) {
299-
currentContent += responseContent[i];
300-
streamingBracketMessage.content = currentContent;
301-
await new Promise((resolve) => setTimeout(resolve, 10));
302-
}
303-
304-
streamingBracketMessage.timestamp = Date.now();
305-
}}
306-
>
307-
<div class="w-[56rem]">
308-
<ChatMessage message={streamingBracketMessage} />
309-
</div>
310-
</Story>

0 commit comments

Comments
 (0)