Skip to content

Commit 3517660

Browse files
committed
test: UI/Unit tests
1 parent 935fa01 commit 3517660

File tree

3 files changed

+133
-23
lines changed

3 files changed

+133
-23
lines changed

tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActionFileAttachments.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
<Tooltip.Trigger class="w-full" >
5555
<DropdownMenu.Item
5656
class="images-button flex items-center gap-2 cursor-pointer"
57-
onclick={() => handleFileUpload('image')}
5857
disabled={!supportsVision()}
58+
onclick={() => handleFileUpload('image')}
5959
>
6060
<Image class="h-4 w-4" />
6161

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
name="Default"
4949
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
5050
play={async ({ canvas, userEvent }) => {
51+
mockServerProps(mockConfigs.noModalities);
52+
5153
const textarea = await canvas.findByRole('textbox');
5254
const submitButton = await canvas.findByRole('button', { name: 'Send' });
5355

@@ -71,13 +73,13 @@
7173
await expect(acceptAttr).not.toContain('audio/');
7274

7375

74-
const fileUploadButton = canvas.getByText('Attach files');
75-
76-
await userEvent.click(fileUploadButton);
76+
const fileUploadButton = canvas.getByText('Attach files');
77+
78+
await userEvent.click(fileUploadButton);
7779

7880
const recordButton = canvas.getAllByRole('button', { name: 'Start recording' })[1];
79-
const imagesButton = document.querySelector('.images-button');
80-
const audioButton = document.querySelector('.audio-button');
81+
const imagesButton = document.querySelector('.images-button');
82+
const audioButton = document.querySelector('.audio-button');
8183

8284

8385
await expect(recordButton).toBeDisabled();
@@ -122,7 +124,7 @@
122124
/>
123125

124126

125-
<Story
127+
<!-- <Story
126128
name="AudioModality"
127129
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
128130
play={async ({ canvas, userEvent }) => {
@@ -150,7 +152,7 @@
150152
151153
console.log('✅ Audio modality: Audio/Recording enabled, Images disabled');
152154
}}
153-
/>
155+
/> -->
154156

155157
<Story
156158
name="FileAttachments"

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

Lines changed: 123 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script module lang="ts">
22
import { defineMeta } from '@storybook/addon-svelte-csf';
33
import ChatMessage from '$lib/components/app/chat/ChatMessages/ChatMessage.svelte';
4+
import { expect } from 'storybook/internal/test';
45
56
const { Story } = defineMeta({
67
title: 'Components/ChatScreen/ChatMessage',
@@ -47,17 +48,29 @@
4748
children: []
4849
};
4950
50-
const processingMessage: DatabaseMessage = {
51+
let processingMessage = $state({
5152
id: '4',
5253
convId: 'conv-1',
5354
type: 'message',
54-
timestamp: Date.now(),
55+
timestamp: 0, // No timestamp = processing
5556
role: 'assistant',
5657
content: '',
5758
parent: '1',
5859
thinking: '',
5960
children: []
60-
};
61+
});
62+
63+
let streamingMessage = $state({
64+
id: '5',
65+
convId: 'conv-1',
66+
type: 'message',
67+
timestamp: 0, // No timestamp = streaming
68+
role: 'assistant',
69+
content: '',
70+
parent: '1',
71+
thinking: '',
72+
children: []
73+
});
6174
</script>
6275

6376
<Story
@@ -70,32 +83,127 @@
7083
<Story
7184
name="Assistant"
7285
args={{
86+
class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
7387
message: assistantMessage
7488
}}
7589
/>
7690

7791
<Story
78-
name="ThinkingBlock"
92+
name="WithThinkingBlock"
93+
args={{
94+
message: streamingMessage
95+
}}
96+
asChild
97+
play={async ({ canvas }) => {
98+
// Phase 1: Stream reasoning content in chunks
99+
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.";
100+
101+
102+
let reasoningChunk = 'I';
103+
let i = 0;
104+
while (i < reasoningText.length) {
105+
const chunkSize = Math.floor(Math.random() * 5) + 3; // Random 3-7 characters
106+
const chunk = reasoningText.slice(i, i + chunkSize);
107+
reasoningChunk += chunk;
108+
109+
// Update the reactive state directly
110+
streamingMessage.thinking = reasoningChunk;
111+
112+
i += chunkSize;
113+
await new Promise(resolve => setTimeout(resolve, 50));
114+
}
115+
116+
const regularText = "Based on my analysis, here's the solution:\n\n**Step 1:** First, we need to understand the requirements clearly.\n\n**Step 2:** Then we can implement the solution systematically.\n\n**Step 3:** Finally, we test and validate the results.\n\nThis approach ensures we cover all aspects of the problem effectively.";
117+
118+
let contentChunk = '';
119+
i = 0;
120+
121+
while (i < regularText.length) {
122+
const chunkSize = Math.floor(Math.random() * 5) + 3; // Random 3-7 characters
123+
const chunk = regularText.slice(i, i + chunkSize);
124+
contentChunk += chunk;
125+
126+
// Update the reactive state directly
127+
streamingMessage.content = contentChunk;
128+
129+
i += chunkSize;
130+
await new Promise(resolve => setTimeout(resolve, 50));
131+
}
132+
133+
streamingMessage.timestamp = Date.now();
134+
135+
136+
// const collapsibleTrigger = canvas.getByText('Reasoning');
137+
// expect(collapsibleTrigger).not.toHaveAttribute('data-state', 'open');
138+
}}
139+
>
140+
141+
<div class="w-[56rem]">
142+
<ChatMessage
143+
message={streamingMessage}
144+
/>
145+
</div>
146+
</Story>
147+
148+
<Story
149+
name="Processing"
79150
args={{
80-
message: thinkingMessage
151+
message: processingMessage
152+
}}
153+
play={async ({ canvas }) => {
154+
// Import the chat store to simulate loading state
155+
const { chatStore } = await import('$lib/stores/chat.svelte');
156+
157+
// Set loading state to true to trigger the processing UI
158+
chatStore.isLoading = true;
159+
160+
// Simulate the processing state hook behavior
161+
// This will show the "Generating..." text and parameter details
162+
await new Promise(resolve => setTimeout(resolve, 100));
81163
}}
82164
/>
83165

84166
<Story
85-
name="ProcessingState"
167+
name="Processing with Slots"
86168
args={{
87169
message: processingMessage
88170
}}
89-
play={({ canvasElement }) => {
90-
// Simulate processing state by setting up mock processing data
91-
const processingState = {
92-
slots: {
93-
'slot-1': { content: 'Processing your request...', timestamp: Date.now() },
94-
'slot-2': { content: 'Analyzing data...', timestamp: Date.now() + 1000 }
95-
}
171+
play={async () => {
172+
173+
// Import the chat store and slots service to simulate loading state with slots data
174+
const { chatStore } = await import('$lib/stores/chat.svelte');
175+
const { slotsService } = await import('$lib/services/slots');
176+
177+
// Set loading state to true to trigger the processing UI
178+
chatStore.isLoading = true;
179+
180+
// Mock the slots service to provide realistic slot data
181+
const mockProcessingState = {
182+
status: 'generating' as const,
183+
tokensDecoded: 410,
184+
tokensRemaining: 1000,
185+
contextUsed: 429,
186+
contextTotal: 4096,
187+
temperature: 0.8,
188+
topP: 0.95,
189+
speculative: false,
190+
hasNextToken: true
96191
};
97192

98-
// This would normally be handled by the useProcessingState hook
99-
// but for Storybook we can simulate the visual state
193+
// Override the parseProcessingState method to return our mock data
194+
const originalParseProcessingState = slotsService['parseProcessingState'];
195+
slotsService['parseProcessingState'] = () => mockProcessingState;
196+
197+
// Trigger the processing state callbacks manually
198+
slotsService['callbacks'].forEach(callback => {
199+
try {
200+
callback(mockProcessingState);
201+
} catch (error) {
202+
console.error('Error in slots callback:', error);
203+
}
204+
});
205+
206+
// Restore original method
207+
slotsService['parseProcessingState'] = originalParseProcessingState;
100208
}}
101209
/>

0 commit comments

Comments
 (0)