ui: Linting & Formatting scripts (#26819)
This commit is contained in:
@@ -3,86 +3,86 @@
|
||||
import ChatMessage from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessage.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/ChatScreen/ChatMessage',
|
||||
component: ChatMessage,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
},
|
||||
title: 'Components/ChatScreen/ChatMessage'
|
||||
});
|
||||
|
||||
// Mock messages for different scenarios
|
||||
const userMessage: DatabaseMessage = {
|
||||
id: '1',
|
||||
convId: 'conv-1',
|
||||
type: 'message',
|
||||
timestamp: Date.now() - 1000 * 60 * 5,
|
||||
role: 'user',
|
||||
children: [],
|
||||
content: 'What is the meaning of life, the universe, and everything?',
|
||||
convId: 'conv-1',
|
||||
id: '1',
|
||||
parent: '',
|
||||
role: 'user',
|
||||
thinking: '',
|
||||
children: []
|
||||
timestamp: Date.now() - 1000 * 60 * 5,
|
||||
type: 'message'
|
||||
};
|
||||
|
||||
const assistantMessage: DatabaseMessage = {
|
||||
id: '2',
|
||||
convId: 'conv-1',
|
||||
type: 'message',
|
||||
timestamp: Date.now() - 1000 * 60 * 3,
|
||||
role: 'assistant',
|
||||
children: [],
|
||||
content:
|
||||
'The answer to the ultimate question of life, the universe, and everything is **42**.\n\nThis comes from Douglas Adams\' "The Hitchhiker\'s Guide to the Galaxy," where a supercomputer named Deep Thought calculated this answer over 7.5 million years. However, the question itself was never properly formulated, which is why the answer seems meaningless without context.',
|
||||
convId: 'conv-1',
|
||||
id: '2',
|
||||
parent: '1',
|
||||
role: 'assistant',
|
||||
thinking: '',
|
||||
children: []
|
||||
timestamp: Date.now() - 1000 * 60 * 3,
|
||||
type: 'message'
|
||||
};
|
||||
|
||||
const assistantWithReasoning: DatabaseMessage = {
|
||||
id: '3',
|
||||
convId: 'conv-1',
|
||||
type: 'message',
|
||||
timestamp: Date.now() - 1000 * 60 * 2,
|
||||
role: 'assistant',
|
||||
children: [],
|
||||
content: "Here's the concise answer, now that I've thought it through carefully for you.",
|
||||
convId: 'conv-1',
|
||||
id: '3',
|
||||
parent: '1',
|
||||
role: 'assistant',
|
||||
thinking:
|
||||
"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.",
|
||||
children: []
|
||||
timestamp: Date.now() - 1000 * 60 * 2,
|
||||
type: 'message'
|
||||
};
|
||||
const rawOutputMessage: DatabaseMessage = {
|
||||
id: '6',
|
||||
convId: 'conv-1',
|
||||
type: 'message',
|
||||
timestamp: Date.now() - 1000 * 60,
|
||||
role: 'assistant',
|
||||
children: [],
|
||||
content:
|
||||
'<|channel|>analysis<|message|>User greeted me. Initiating overcomplicated analysis: Is this a trap? No, just a normal hello. Respond calmly, act like a helpful assistant, and do not start explaining quantum physics again. Confidence 0.73. Engaging socially acceptable greeting protocol...<|end|>Hello there! How can I help you today?',
|
||||
convId: 'conv-1',
|
||||
id: '6',
|
||||
parent: '1',
|
||||
role: 'assistant',
|
||||
thinking: '',
|
||||
children: []
|
||||
timestamp: Date.now() - 1000 * 60,
|
||||
type: 'message'
|
||||
};
|
||||
|
||||
let processingMessage = $state({
|
||||
id: '4',
|
||||
convId: 'conv-1',
|
||||
type: 'message',
|
||||
timestamp: 0, // No timestamp = processing
|
||||
role: 'assistant',
|
||||
children: [],
|
||||
content: '',
|
||||
convId: 'conv-1',
|
||||
id: '4',
|
||||
parent: '1',
|
||||
role: 'assistant',
|
||||
thinking: '',
|
||||
children: []
|
||||
timestamp: 0, // No timestamp = processing
|
||||
type: 'message'
|
||||
});
|
||||
|
||||
let streamingMessage = $state({
|
||||
id: '5',
|
||||
convId: 'conv-1',
|
||||
type: 'message',
|
||||
timestamp: 0, // No timestamp = streaming
|
||||
role: 'assistant',
|
||||
children: [],
|
||||
content: '',
|
||||
convId: 'conv-1',
|
||||
id: '5',
|
||||
parent: '1',
|
||||
role: 'assistant',
|
||||
thinking: '',
|
||||
children: []
|
||||
timestamp: 0, // No timestamp = streaming
|
||||
type: 'message'
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
}}
|
||||
play={async () => {
|
||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||
|
||||
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||
}}
|
||||
/>
|
||||
@@ -105,6 +106,7 @@
|
||||
}}
|
||||
play={async () => {
|
||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||
|
||||
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||
}}
|
||||
/>
|
||||
@@ -117,6 +119,7 @@
|
||||
}}
|
||||
play={async () => {
|
||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||
|
||||
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||
}}
|
||||
/>
|
||||
@@ -129,6 +132,7 @@
|
||||
}}
|
||||
play={async () => {
|
||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||
|
||||
settingsStore.updateConfig('showRawOutputSwitch', true);
|
||||
}}
|
||||
/>
|
||||
@@ -141,16 +145,18 @@
|
||||
asChild
|
||||
play={async () => {
|
||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||
|
||||
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||
// Phase 1: Stream reasoning content in chunks
|
||||
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.';
|
||||
|
||||
let reasoningChunk = 'I';
|
||||
let i = 0;
|
||||
|
||||
while (i < reasoningText.length) {
|
||||
const chunkSize = Math.floor(Math.random() * 5) + 3; // Random 3-7 characters
|
||||
const chunk = reasoningText.slice(i, i + chunkSize);
|
||||
|
||||
reasoningChunk += chunk;
|
||||
|
||||
// Update the reactive state directly
|
||||
@@ -164,11 +170,13 @@
|
||||
"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.";
|
||||
|
||||
let contentChunk = '';
|
||||
|
||||
i = 0;
|
||||
|
||||
while (i < regularText.length) {
|
||||
const chunkSize = Math.floor(Math.random() * 5) + 3; // Random 3-7 characters
|
||||
const chunk = regularText.slice(i, i + chunkSize);
|
||||
|
||||
contentChunk += chunk;
|
||||
|
||||
// Update the reactive state directly
|
||||
@@ -193,6 +201,7 @@
|
||||
}}
|
||||
play={async () => {
|
||||
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||
|
||||
settingsStore.updateConfig('showRawOutputSwitch', false);
|
||||
// Import the chat store to simulate loading state
|
||||
const { chatStore } = await import('$lib/stores/chat.svelte');
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
<script module lang="ts">
|
||||
import jpgAsset from './fixtures/assets/1.jpg?url';
|
||||
import pdfAsset from './fixtures/assets/example.pdf?raw';
|
||||
import svgAsset from './fixtures/assets/hf-logo.svg?url';
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import ChatScreenForm from '$lib/components/app/chat/ChatScreen/ChatScreenForm.svelte';
|
||||
import { expect } from 'storybook/test';
|
||||
import jpgAsset from './fixtures/assets/1.jpg?url';
|
||||
import svgAsset from './fixtures/assets/hf-logo.svg?url';
|
||||
import pdfAsset from './fixtures/assets/example.pdf?raw';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/ChatScreen/ChatScreenForm',
|
||||
component: ChatScreenForm,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
},
|
||||
title: 'Components/ChatScreen/ChatScreenForm'
|
||||
});
|
||||
|
||||
let fileAttachments = $state([
|
||||
{
|
||||
file: new File([''], '1.jpg', { type: 'image/jpeg' }),
|
||||
id: '1',
|
||||
name: '1.jpg',
|
||||
type: 'image/jpeg',
|
||||
size: 44891,
|
||||
preview: jpgAsset,
|
||||
file: new File([''], '1.jpg', { type: 'image/jpeg' })
|
||||
size: 44891,
|
||||
type: 'image/jpeg'
|
||||
},
|
||||
{
|
||||
file: new File([''], 'hf-logo.svg', { type: 'image/svg+xml' }),
|
||||
id: '2',
|
||||
name: 'hf-logo.svg',
|
||||
type: 'image/svg+xml',
|
||||
size: 1234,
|
||||
preview: svgAsset,
|
||||
file: new File([''], 'hf-logo.svg', { type: 'image/svg+xml' })
|
||||
size: 1234,
|
||||
type: 'image/svg+xml'
|
||||
},
|
||||
{
|
||||
file: new File([pdfAsset], 'example.pdf', { type: 'application/pdf' }),
|
||||
id: '3',
|
||||
name: 'example.pdf',
|
||||
type: 'application/pdf',
|
||||
size: 351048,
|
||||
file: new File([pdfAsset], 'example.pdf', { type: 'application/pdf' })
|
||||
type: 'application/pdf'
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
@@ -62,6 +62,7 @@
|
||||
await expect(textarea).toHaveValue(text);
|
||||
|
||||
const fileInput = document.querySelector('input[type="file"]');
|
||||
|
||||
await expect(fileInput).not.toHaveAttribute('accept');
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,59 +1,60 @@
|
||||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { expect } from 'storybook/test';
|
||||
import { MarkdownContent } from '$lib/components/app';
|
||||
import { AI_TUTORIAL_MD } from './fixtures/ai-tutorial.js';
|
||||
import { API_DOCS_MD } from './fixtures/api-docs.js';
|
||||
import { BLOG_POST_MD } from './fixtures/blog-post.js';
|
||||
import { DATA_ANALYSIS_MD } from './fixtures/data-analysis.js';
|
||||
import { README_MD } from './fixtures/readme.js';
|
||||
import { MATH_FORMULAS_MD } from './fixtures/math-formulas.js';
|
||||
import { EMPTY_MD } from './fixtures/empty.js';
|
||||
import { MATH_FORMULAS_MD } from './fixtures/math-formulas.js';
|
||||
import { README_MD } from './fixtures/readme.js';
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { MarkdownContent } from '$lib/components/app';
|
||||
import { expect } from 'storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/MarkdownContent',
|
||||
component: MarkdownContent,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
},
|
||||
title: 'Components/MarkdownContent'
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Empty" args={{ content: EMPTY_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }} />
|
||||
<Story name="Empty" args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', content: EMPTY_MD }} />
|
||||
|
||||
<Story
|
||||
name="AI Tutorial"
|
||||
args={{ content: AI_TUTORIAL_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', content: AI_TUTORIAL_MD }}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="API Documentation"
|
||||
args={{ content: API_DOCS_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', content: API_DOCS_MD }}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Technical Blog"
|
||||
args={{ content: BLOG_POST_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', content: BLOG_POST_MD }}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Data Analysis"
|
||||
args={{ content: DATA_ANALYSIS_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', content: DATA_ANALYSIS_MD }}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="README file"
|
||||
args={{ content: README_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', content: README_MD }}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Math Formulas"
|
||||
args={{ content: MATH_FORMULAS_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', content: MATH_FORMULAS_MD }}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="URL Links"
|
||||
args={{
|
||||
class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
|
||||
content: `# URL Links Test
|
||||
|
||||
Here are some example URLs that should open in new tabs:
|
||||
@@ -65,11 +66,11 @@ Here are some example URLs that should open in new tabs:
|
||||
|
||||
You can also test inline links like https://example.com or https://docs.python.org.
|
||||
|
||||
All links should have \`target="_blank"\` and \`rel="noopener noreferrer"\` attributes for security.`,
|
||||
class: 'max-w-[56rem] w-[calc(100vw-2rem)]'
|
||||
All links should have \`target="_blank"\` and \`rel="noopener noreferrer"\` attributes for security.`
|
||||
}}
|
||||
play={async (context) => {
|
||||
const { canvasElement } = context;
|
||||
|
||||
// Wait for component to render
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
@@ -97,22 +98,26 @@ All links should have \`target="_blank"\` and \`rel="noopener noreferrer"\` attr
|
||||
const hugginFaceLink = linkList.find(
|
||||
(link) => link.getAttribute('href') === 'https://huggingface.co'
|
||||
);
|
||||
|
||||
expect(hugginFaceLink).toBeTruthy();
|
||||
expect(hugginFaceLink?.textContent).toBe('Hugging Face Homepage');
|
||||
|
||||
const githubLink = linkList.find(
|
||||
(link) => link.getAttribute('href') === 'https://github.com/ggml-org/llama.cpp'
|
||||
);
|
||||
|
||||
expect(githubLink).toBeTruthy();
|
||||
expect(githubLink?.textContent).toBe('GitHub Repository');
|
||||
|
||||
const openaiLink = linkList.find((link) => link.getAttribute('href') === 'https://openai.com');
|
||||
|
||||
expect(openaiLink).toBeTruthy();
|
||||
expect(openaiLink?.textContent).toBe('OpenAI Website');
|
||||
|
||||
const googleLink = linkList.find(
|
||||
(link) => link.getAttribute('href') === 'https://www.google.com'
|
||||
);
|
||||
|
||||
expect(googleLink).toBeTruthy();
|
||||
expect(googleLink?.textContent).toBe('Google Search');
|
||||
|
||||
@@ -120,11 +125,13 @@ All links should have \`target="_blank"\` and \`rel="noopener noreferrer"\` attr
|
||||
const exampleLink = linkList.find(
|
||||
(link) => link.getAttribute('href') === 'https://example.com'
|
||||
);
|
||||
|
||||
expect(exampleLink).toBeTruthy();
|
||||
|
||||
const pythonDocsLink = linkList.find(
|
||||
(link) => link.getAttribute('href') === 'https://docs.python.org'
|
||||
);
|
||||
|
||||
expect(pythonDocsLink).toBeTruthy();
|
||||
|
||||
console.log(`✅ URL Links test passed - Found ${links.length} links with proper attributes`);
|
||||
|
||||
@@ -3,39 +3,39 @@
|
||||
import ModelsSelectorList from '$lib/components/app/models/ModelsSelectorList.svelte';
|
||||
import ModelsSelectorOption from '$lib/components/app/models/ModelsSelectorOption.svelte';
|
||||
import type { GroupedModelOptions, ModelItem } from '$lib/components/app/models/utils';
|
||||
import { modelsStore } from '$lib/stores/models.svelte';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { modelsStore } from '$lib/stores/models.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/ModelsSelector',
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
},
|
||||
title: 'Components/ModelsSelector'
|
||||
});
|
||||
|
||||
const mockModel = (id: string, name: string, orgName?: string, tags?: string[]): ModelOption => ({
|
||||
id,
|
||||
name,
|
||||
model: orgName ? `${orgName}/${name}` : name,
|
||||
capabilities: [],
|
||||
id,
|
||||
model: orgName ? `${orgName}/${name}` : name,
|
||||
name,
|
||||
parsedId: {
|
||||
raw: orgName ? `${orgName}/${name}` : name,
|
||||
orgName: orgName ?? null,
|
||||
modelName: name,
|
||||
params: null,
|
||||
activatedParams: null,
|
||||
modelName: name,
|
||||
orgName: orgName ?? null,
|
||||
params: null,
|
||||
quantization: null,
|
||||
raw: orgName ? `${orgName}/${name}` : name,
|
||||
tags: tags ?? []
|
||||
},
|
||||
tags
|
||||
});
|
||||
|
||||
const mockRouterEntry = (modelName: string, status: ServerModelStatus): ApiModelDataEntry => ({
|
||||
created: Date.now(),
|
||||
id: modelName,
|
||||
in_cache: true,
|
||||
object: 'model',
|
||||
owned_by: 'llamacpp',
|
||||
created: Date.now(),
|
||||
in_cache: true,
|
||||
path: `/models/${modelName}`,
|
||||
status: { value: status }
|
||||
});
|
||||
@@ -60,57 +60,58 @@
|
||||
mockModelsStore();
|
||||
|
||||
const loadedModels: ModelItem[] = [
|
||||
{ option: mockModel('llama3.1-8b', 'Llama-3.1-8B-Instruct', 'meta'), flatIndex: 0 },
|
||||
{ option: mockModel('mistral-7b', 'Mistral-7B-v0.3', 'mistralai'), flatIndex: 1 }
|
||||
{ flatIndex: 0, option: mockModel('llama3.1-8b', 'Llama-3.1-8B-Instruct', 'meta') },
|
||||
{ flatIndex: 1, option: mockModel('mistral-7b', 'Mistral-7B-v0.3', 'mistralai') }
|
||||
];
|
||||
|
||||
const favoriteModels: ModelItem[] = [
|
||||
{ option: mockModel('qwen2.5-7b', 'Qwen2.5-7B-Instruct', 'Qwen'), flatIndex: 2 },
|
||||
{ option: mockModel('llama3.2-3b', 'Llama-3.2-3B-Instruct', 'meta'), flatIndex: 3 }
|
||||
{ flatIndex: 2, option: mockModel('qwen2.5-7b', 'Qwen2.5-7B-Instruct', 'Qwen') },
|
||||
{ flatIndex: 3, option: mockModel('llama3.2-3b', 'Llama-3.2-3B-Instruct', 'meta') }
|
||||
];
|
||||
|
||||
const availableModels: ModelItem[] = [
|
||||
{
|
||||
option: mockModel('deepseek-coder-6.7b', 'DeepSeek-Coder-6.7B', 'deepseek', ['coding']),
|
||||
flatIndex: 4
|
||||
flatIndex: 4,
|
||||
option: mockModel('deepseek-coder-6.7b', 'DeepSeek-Coder-6.7B', 'deepseek', ['coding'])
|
||||
},
|
||||
{ option: mockModel('gemma-2-9b', 'Gemma-2-9B-IT', 'google'), flatIndex: 5 },
|
||||
{ option: mockModel('phi-3-mini', 'Phi-3-mini-4k', 'microsoft'), flatIndex: 6 },
|
||||
{ option: mockModel('codellama-7b', 'CodeLlama-7B', 'codellama', ['coding']), flatIndex: 7 },
|
||||
{ option: mockModel('neural-chat-7b', 'Neural-Chat-7B-v3-3', 'intel'), flatIndex: 8 }
|
||||
{ flatIndex: 5, option: mockModel('gemma-2-9b', 'Gemma-2-9B-IT', 'google') },
|
||||
{ flatIndex: 6, option: mockModel('phi-3-mini', 'Phi-3-mini-4k', 'microsoft') },
|
||||
{ flatIndex: 7, option: mockModel('codellama-7b', 'CodeLlama-7B', 'codellama', ['coding']) },
|
||||
{ flatIndex: 8, option: mockModel('neural-chat-7b', 'Neural-Chat-7B-v3-3', 'intel') }
|
||||
];
|
||||
|
||||
const groupedOptions: GroupedModelOptions = {
|
||||
loaded: loadedModels,
|
||||
favorites: favoriteModels,
|
||||
available: [
|
||||
{
|
||||
orgName: 'deepseek',
|
||||
items: [availableModels[0]]
|
||||
items: [availableModels[0]],
|
||||
orgName: 'deepseek'
|
||||
},
|
||||
{
|
||||
orgName: 'google',
|
||||
items: [availableModels[1]]
|
||||
items: [availableModels[1]],
|
||||
orgName: 'google'
|
||||
},
|
||||
{
|
||||
orgName: 'microsoft',
|
||||
items: [availableModels[2]]
|
||||
items: [availableModels[2]],
|
||||
orgName: 'microsoft'
|
||||
},
|
||||
{
|
||||
orgName: 'codellama',
|
||||
items: [availableModels[3]]
|
||||
items: [availableModels[3]],
|
||||
orgName: 'codellama'
|
||||
},
|
||||
{
|
||||
orgName: 'intel',
|
||||
items: [availableModels[4]]
|
||||
items: [availableModels[4]],
|
||||
orgName: 'intel'
|
||||
}
|
||||
]
|
||||
],
|
||||
favorites: favoriteModels,
|
||||
loaded: loadedModels
|
||||
};
|
||||
|
||||
function handleSelect(modelId: string) {
|
||||
const opt = [...loadedModels, ...favoriteModels, ...availableModels].find(
|
||||
(m) => m.option.id === modelId
|
||||
);
|
||||
|
||||
if (opt) {
|
||||
selectedModel = opt.option.model;
|
||||
activeId = modelId;
|
||||
@@ -134,9 +135,9 @@
|
||||
<div class="w-80 rounded-lg border border-border bg-popover p-2 shadow-md">
|
||||
<ModelsSelectorList
|
||||
groups={{
|
||||
loaded: [loadedModels[0]],
|
||||
available: [],
|
||||
favorites: [],
|
||||
available: []
|
||||
loaded: [loadedModels[0]]
|
||||
}}
|
||||
currentModel={null}
|
||||
activeId={null}
|
||||
@@ -150,9 +151,9 @@
|
||||
<div class="w-80 rounded-lg border border-border bg-popover p-2 shadow-md">
|
||||
<ModelsSelectorList
|
||||
groups={{
|
||||
loaded: [],
|
||||
available: [],
|
||||
favorites: favoriteModels,
|
||||
available: []
|
||||
loaded: []
|
||||
}}
|
||||
currentModel={null}
|
||||
activeId={null}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
import { expect } from 'storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/PwaRefreshAlert',
|
||||
component: PwaRefreshAlert,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
},
|
||||
title: 'Components/PwaRefreshAlert'
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
args={{ needRefresh: true, updateServiceWorker: () => console.log('reload') }}
|
||||
play={async ({ canvas }) => {
|
||||
const title = canvas.getByText('Update available');
|
||||
|
||||
await expect(title).toBeInTheDocument();
|
||||
|
||||
const description = canvas.getByText(/A new version is available/);
|
||||
|
||||
await expect(description).toBeInTheDocument();
|
||||
|
||||
const button = canvas.getByRole('button', { name: 'Reload' });
|
||||
|
||||
await expect(button).toBeInTheDocument();
|
||||
}}
|
||||
/>
|
||||
@@ -32,6 +35,7 @@
|
||||
args={{ needRefresh: false, updateServiceWorker: () => console.log('reload') }}
|
||||
play={async ({ canvas }) => {
|
||||
const title = canvas.queryByText('Update available');
|
||||
|
||||
await expect(title).not.toBeInTheDocument();
|
||||
}}
|
||||
/>
|
||||
@@ -44,14 +48,17 @@
|
||||
}}
|
||||
play={async ({ canvas, userEvent }) => {
|
||||
const button = canvas.getByRole('button', { name: 'Reload' });
|
||||
|
||||
await expect(button).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(button);
|
||||
|
||||
const title = canvas.queryByText('Update available');
|
||||
|
||||
await expect(title).not.toBeInTheDocument();
|
||||
|
||||
const reloadBtn = canvas.queryByRole('button', { name: 'Reload' });
|
||||
|
||||
await expect(reloadBtn).not.toBeInTheDocument();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
import { screen } from 'storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/SidebarNavigation',
|
||||
component: SidebarNavigation,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
},
|
||||
title: 'Components/SidebarNavigation'
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -17,34 +17,34 @@
|
||||
// Mock conversations for the sidebar
|
||||
const mockConversations: DatabaseConversation[] = [
|
||||
{
|
||||
currNode: 'msg-1',
|
||||
id: 'conv-1',
|
||||
name: 'Getting Started with AI',
|
||||
lastModified: Date.now() - 1000 * 60 * 5, // 5 minutes ago
|
||||
currNode: 'msg-1'
|
||||
name: 'Getting Started with AI'
|
||||
},
|
||||
{
|
||||
currNode: 'msg-2',
|
||||
id: 'conv-2',
|
||||
name: 'Python Programming Help',
|
||||
lastModified: Date.now() - 1000 * 60 * 60 * 2, // 2 hours ago
|
||||
currNode: 'msg-2'
|
||||
name: 'Python Programming Help'
|
||||
},
|
||||
{
|
||||
currNode: 'msg-3',
|
||||
id: 'conv-3',
|
||||
name: 'Creative Writing Ideas',
|
||||
lastModified: Date.now() - 1000 * 60 * 60 * 24, // 1 day ago
|
||||
currNode: 'msg-3'
|
||||
name: 'Creative Writing Ideas'
|
||||
},
|
||||
{
|
||||
currNode: 'msg-4',
|
||||
id: 'conv-4',
|
||||
name: 'This is a very long conversation title that should be truncated properly when displayed',
|
||||
lastModified: Date.now() - 1000 * 60 * 60 * 24 * 3, // 3 days ago
|
||||
currNode: 'msg-4'
|
||||
name: 'This is a very long conversation title that should be truncated properly when displayed'
|
||||
},
|
||||
{
|
||||
currNode: 'msg-5',
|
||||
id: 'conv-5',
|
||||
name: 'Math Problem Solving',
|
||||
lastModified: Date.now() - 1000 * 60 * 60 * 24 * 7, // 1 week ago
|
||||
currNode: 'msg-5'
|
||||
name: 'Math Problem Solving'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
@@ -81,8 +81,10 @@
|
||||
|
||||
// Expand sidebar first, then click Search in the expanded button list
|
||||
const logoTrigger = screen.getByRole('button', { name: /expand navigation/i });
|
||||
|
||||
await userEvent.click(logoTrigger);
|
||||
const searchTrigger = screen.getByText('Search');
|
||||
|
||||
userEvent.click(searchTrigger);
|
||||
}}
|
||||
>
|
||||
@@ -97,6 +99,7 @@
|
||||
play={async () => {
|
||||
// Mock empty conversations store
|
||||
const { conversationsStore } = await import('$lib/stores/conversations.svelte');
|
||||
|
||||
conversationsStore.conversations = [];
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { Copy } from '@lucide/svelte';
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import ActionIcon from '$lib/components/app/actions/ActionIcon.svelte';
|
||||
import { expect } from 'storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/ActionIcon/Accessibility',
|
||||
component: ActionIcon,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['!dev']
|
||||
tags: ['!dev'],
|
||||
title: 'Components/ActionIcon/Accessibility'
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,30 +4,30 @@
|
||||
import { expect } from 'storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/ChatMessageStatistics/Accessibility',
|
||||
component: ChatMessageStatistics,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['!dev']
|
||||
tags: ['!dev'],
|
||||
title: 'Components/ChatMessageStatistics/Accessibility'
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story
|
||||
name="ViewButtonsSingleTabStop"
|
||||
args={{
|
||||
promptTokens: 100,
|
||||
promptMs: 500,
|
||||
predictedTokens: 200,
|
||||
predictedMs: 1000,
|
||||
agenticTimings: {
|
||||
turns: 1,
|
||||
llm: { predicted_ms: 1000, predicted_n: 200, prompt_ms: 500, prompt_n: 100 },
|
||||
toolCallsCount: 1,
|
||||
toolsMs: 500,
|
||||
llm: { predicted_n: 200, predicted_ms: 1000, prompt_n: 100, prompt_ms: 500 }
|
||||
turns: 1
|
||||
},
|
||||
hideSummary: false,
|
||||
isLive: false
|
||||
isLive: false,
|
||||
predictedMs: 1000,
|
||||
predictedTokens: 200,
|
||||
promptMs: 500,
|
||||
promptTokens: 100
|
||||
}}
|
||||
play={async ({ canvas, userEvent }) => {
|
||||
const reading = await canvas.findByRole('button', { name: 'Reading' });
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import ChatScreenForm from '$lib/components/app/chat/ChatScreen/ChatScreenForm.svelte';
|
||||
import { expect, screen, waitFor } from 'storybook/test';
|
||||
import { ATTACHMENT_TOOLTIP_TEXT } from '$lib/constants';
|
||||
import { expect, screen, waitFor } from 'storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/ChatScreen/ChatScreenForm/Accessibility',
|
||||
component: ChatScreenForm,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['!dev']
|
||||
tags: ['!dev'],
|
||||
title: 'Components/ChatScreen/ChatScreenForm/Accessibility'
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||
play={async ({ canvas, userEvent }) => {
|
||||
const textarea = await canvas.findByRole('textbox');
|
||||
|
||||
await userEvent.clear(textarea);
|
||||
await userEvent.type(textarea, 'What is the meaning of life?');
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
import { expect, waitFor } from 'storybook/test';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/HorizontalScrollCarousel/Accessibility',
|
||||
component: HorizontalScrollCarousel,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['!dev']
|
||||
tags: ['!dev'],
|
||||
title: 'Components/HorizontalScrollCarousel/Accessibility'
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
import { expect } from 'storybook/test';
|
||||
|
||||
const mockForkedConversation: DatabaseConversation = {
|
||||
id: 'conv-2',
|
||||
name: 'Forked Conversation',
|
||||
lastModified: Date.now(),
|
||||
currNode: 'msg-2',
|
||||
forkedFromConversationId: 'conv-1'
|
||||
forkedFromConversationId: 'conv-1',
|
||||
id: 'conv-2',
|
||||
lastModified: Date.now(),
|
||||
name: 'Forked Conversation'
|
||||
};
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Components/SidebarNavigationConversationItem/Accessibility',
|
||||
component: SidebarNavigationConversationItem,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['!dev']
|
||||
tags: ['!dev'],
|
||||
title: 'Components/SidebarNavigationConversationItem/Accessibility'
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
import { modelsStore } from '$lib/stores/models.svelte';
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
|
||||
/**
|
||||
* Mock server properties for Storybook testing
|
||||
@@ -8,16 +8,17 @@ import { modelsStore } from '$lib/stores/models.svelte';
|
||||
export function mockServerProps(props: Partial<ApiLlamaCppServerProps>): void {
|
||||
// Reset any pointer-events from previous tests (dropdown cleanup)
|
||||
const body = document.querySelector('body');
|
||||
|
||||
if (body) body.style.pointerEvents = '';
|
||||
|
||||
// Directly set the props for testing purposes
|
||||
(serverStore as unknown as { props: ApiLlamaCppServerProps }).props = {
|
||||
model_path: props.model_path || 'test-model',
|
||||
modalities: {
|
||||
vision: props.modalities?.vision ?? false,
|
||||
audio: props.modalities?.audio ?? false,
|
||||
video: props.modalities?.video ?? false
|
||||
video: props.modalities?.video ?? false,
|
||||
vision: props.modalities?.vision ?? false
|
||||
},
|
||||
model_path: props.model_path || 'test-model',
|
||||
...props
|
||||
} as ApiLlamaCppServerProps;
|
||||
|
||||
@@ -41,8 +42,8 @@ export function mockServerProps(props: Partial<ApiLlamaCppServerProps>): void {
|
||||
(modelsStore as any).models = [
|
||||
{
|
||||
id: 'test-model',
|
||||
name: 'Test Model',
|
||||
model: 'test-model'
|
||||
model: 'test-model',
|
||||
name: 'Test Model'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -56,12 +57,12 @@ export function mockServerProps(props: Partial<ApiLlamaCppServerProps>): void {
|
||||
*/
|
||||
export function resetServerStore(): void {
|
||||
(serverStore as unknown as { props: ApiLlamaCppServerProps }).props = {
|
||||
model_path: '',
|
||||
modalities: {
|
||||
vision: false,
|
||||
audio: false,
|
||||
video: false
|
||||
}
|
||||
video: false,
|
||||
vision: false
|
||||
},
|
||||
model_path: ''
|
||||
} as ApiLlamaCppServerProps;
|
||||
(serverStore as unknown as { error: string }).error = '';
|
||||
(serverStore as unknown as { loading: boolean }).loading = false;
|
||||
@@ -71,16 +72,16 @@ export function resetServerStore(): void {
|
||||
* Common mock configurations for Storybook stories
|
||||
*/
|
||||
export const mockConfigs = {
|
||||
visionOnly: {
|
||||
modalities: { vision: true, audio: false }
|
||||
},
|
||||
audioOnly: {
|
||||
modalities: { vision: false, audio: true }
|
||||
modalities: { audio: true, vision: false }
|
||||
},
|
||||
bothModalities: {
|
||||
modalities: { vision: true, audio: true }
|
||||
modalities: { audio: true, vision: true }
|
||||
},
|
||||
noModalities: {
|
||||
modalities: { vision: false, audio: false, video: false }
|
||||
modalities: { audio: false, video: false, vision: false }
|
||||
},
|
||||
visionOnly: {
|
||||
modalities: { audio: false, vision: true }
|
||||
}
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user