mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 10:02:04 +08:00
feat: add Storybook stories for all high-priority screenshot test coverage gaps
- packages/kilo-ui: add dock-prompt.stories.tsx and dock-surface.stories.tsx - packages/kilo-vscode: add chat.stories.tsx (ChatView, MessageList, QuestionDock) - packages/kilo-vscode: add history.stories.tsx (SessionList) - packages/kilo-vscode: add settings.stories.tsx (Settings, ProvidersTab) - packages/kilo-vscode: add shared.stories.tsx (ModelSelector, ModeSwitcher) - packages/kilo-vscode: add agent-manager.stories.tsx (AgentManagerApp, FileTree, DiffPanel, FullScreenDiffView) - update StoryProviders to wrap with VSCodeProvider, ServerProvider, ConfigProvider, ProviderProvider
This commit is contained in:
committed by
Mark IJbema
parent
90aed7928b
commit
cc3a6fd922
@@ -0,0 +1,60 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Components/DockPrompt",
|
||||
parameters: { layout: "padded" },
|
||||
}
|
||||
export default meta
|
||||
type Story = StoryObj
|
||||
|
||||
export const Question: Story = {
|
||||
name: "Question kind",
|
||||
render: () => (
|
||||
<DockPrompt
|
||||
kind="question"
|
||||
header={<span style={{ "font-weight": "600" }}>Which testing framework should I use?</span>}
|
||||
footer={
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<Button variant="ghost" size="small">Dismiss</Button>
|
||||
<Button variant="primary" size="small">Submit</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div style={{ padding: "8px 0" }}>
|
||||
<p style={{ margin: 0, "font-size": "13px" }}>Choose one of the following options:</p>
|
||||
<ul style={{ margin: "8px 0 0", padding: "0 0 0 16px", "font-size": "13px" }}>
|
||||
<li>Vitest</li>
|
||||
<li>Jest</li>
|
||||
<li>Playwright</li>
|
||||
<li>Bun test</li>
|
||||
</ul>
|
||||
</div>
|
||||
</DockPrompt>
|
||||
),
|
||||
}
|
||||
|
||||
export const Permission: Story = {
|
||||
name: "Permission kind",
|
||||
render: () => (
|
||||
<DockPrompt
|
||||
kind="permission"
|
||||
header={<span style={{ "font-weight": "600" }}>Permission required — write</span>}
|
||||
footer={
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<Button variant="ghost" size="small">Deny</Button>
|
||||
<Button variant="secondary" size="small">Always Allow</Button>
|
||||
<Button variant="primary" size="small">Allow Once</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div style={{ padding: "8px 0", "font-size": "13px" }}>
|
||||
<code>src/main.tsx</code>
|
||||
<br />
|
||||
<code>src/utils.ts</code>
|
||||
</div>
|
||||
</DockPrompt>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { DockShell, DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Components/DockSurface",
|
||||
parameters: { layout: "padded" },
|
||||
}
|
||||
export default meta
|
||||
type Story = StoryObj
|
||||
|
||||
export const Shell: Story = {
|
||||
name: "DockShell",
|
||||
render: () => (
|
||||
<DockShell style={{ padding: "12px", border: "1px solid var(--border-base)", "border-radius": "8px" }}>
|
||||
<p style={{ margin: 0, "font-size": "13px" }}>DockShell — main container body for the dock surface.</p>
|
||||
</DockShell>
|
||||
),
|
||||
}
|
||||
|
||||
export const Tray: Story = {
|
||||
name: "DockTray",
|
||||
render: () => (
|
||||
<DockTray style={{ padding: "8px 12px", background: "var(--background-surface)", "border-radius": "0 0 8px 8px" }}>
|
||||
<p style={{ margin: 0, "font-size": "13px" }}>DockTray — footer tray area (attach=none).</p>
|
||||
</DockTray>
|
||||
),
|
||||
}
|
||||
|
||||
export const TrayAttachTop: Story = {
|
||||
name: "DockTray attach=top",
|
||||
render: () => (
|
||||
<DockTray attach="top" style={{ padding: "8px 12px", background: "var(--background-surface)", "border-radius": "8px 8px 0 0" }}>
|
||||
<p style={{ margin: 0, "font-size": "13px" }}>DockTray — header tray area (attach=top).</p>
|
||||
</DockTray>
|
||||
),
|
||||
}
|
||||
|
||||
export const ShellWithTray: Story = {
|
||||
name: "DockShell + DockTray",
|
||||
render: () => (
|
||||
<div style={{ border: "1px solid var(--border-base)", "border-radius": "8px", overflow: "hidden" }}>
|
||||
<DockShell style={{ padding: "12px" }}>
|
||||
<p style={{ margin: 0, "font-size": "13px" }}>Body content goes here inside DockShell.</p>
|
||||
</DockShell>
|
||||
<DockTray style={{ padding: "8px 12px", display: "flex", gap: "8px", "justify-content": "flex-end" }}>
|
||||
<span style={{ "font-size": "12px", color: "var(--text-dimmed)" }}>Footer actions slot</span>
|
||||
</DockTray>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const ShellForm: Story = {
|
||||
name: "DockShellForm",
|
||||
render: () => (
|
||||
<DockShellForm style={{ padding: "12px", border: "1px solid var(--border-base)", "border-radius": "8px" }}>
|
||||
<p style={{ margin: 0, "font-size": "13px" }}>DockShellForm — shell variant using a form element.</p>
|
||||
</DockShellForm>
|
||||
),
|
||||
}
|
||||
@@ -4,10 +4,16 @@
|
||||
*
|
||||
* Instead of instantiating the full VSCodeProvider → ServerProvider → SessionProvider
|
||||
* chain (which requires a real extension host / SSE connection), we provide mock
|
||||
* context values directly.
|
||||
* context values directly. Where a real provider is safe to instantiate without an
|
||||
* extension host (VSCodeProvider, ServerProvider, ProviderProvider), we use the real
|
||||
* thing so components that call useVSCode()/useServer()/useProvider() don't throw.
|
||||
*/
|
||||
|
||||
import { createSignal, type ParentComponent } from "solid-js"
|
||||
import { VSCodeProvider } from "../context/vscode"
|
||||
import { ServerProvider } from "../context/server"
|
||||
import { ProviderProvider } from "../context/provider"
|
||||
import { ConfigProvider } from "../context/config"
|
||||
import { DataProvider } from "@kilocode/kilo-ui/context/data"
|
||||
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
|
||||
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
|
||||
@@ -144,29 +150,37 @@ export const StoryProviders: ParentComponent<StoryProvidersProps> = (props) => {
|
||||
const [locale] = createSignal<"en">("en")
|
||||
|
||||
return (
|
||||
<DialogProvider>
|
||||
<LanguageContext.Provider
|
||||
value={{
|
||||
locale,
|
||||
setLocale: noop,
|
||||
userOverride: () => "" as any,
|
||||
t,
|
||||
}}
|
||||
>
|
||||
<I18nProvider value={{ locale: () => "en", t }}>
|
||||
<SessionContext.Provider value={session as any}>
|
||||
<DataProvider data={data()} directory="/project/">
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<CodeComponentProvider component={Code}>
|
||||
<MarkedProvider>
|
||||
{props.noPadding ? props.children : <div style={{ padding: "12px" }}>{props.children}</div>}
|
||||
</MarkedProvider>
|
||||
</CodeComponentProvider>
|
||||
</DiffComponentProvider>
|
||||
</DataProvider>
|
||||
</SessionContext.Provider>
|
||||
</I18nProvider>
|
||||
</LanguageContext.Provider>
|
||||
</DialogProvider>
|
||||
<VSCodeProvider>
|
||||
<ServerProvider>
|
||||
<ConfigProvider>
|
||||
<ProviderProvider>
|
||||
<DialogProvider>
|
||||
<LanguageContext.Provider
|
||||
value={{
|
||||
locale,
|
||||
setLocale: noop,
|
||||
userOverride: () => "" as any,
|
||||
t,
|
||||
}}
|
||||
>
|
||||
<I18nProvider value={{ locale: () => "en", t }}>
|
||||
<SessionContext.Provider value={session as any}>
|
||||
<DataProvider data={data()} directory="/project/">
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<CodeComponentProvider component={Code}>
|
||||
<MarkedProvider>
|
||||
{props.noPadding ? props.children : <div style={{ padding: "12px" }}>{props.children}</div>}
|
||||
</MarkedProvider>
|
||||
</CodeComponentProvider>
|
||||
</DiffComponentProvider>
|
||||
</DataProvider>
|
||||
</SessionContext.Provider>
|
||||
</I18nProvider>
|
||||
</LanguageContext.Provider>
|
||||
</DialogProvider>
|
||||
</ProviderProvider>
|
||||
</ConfigProvider>
|
||||
</ServerProvider>
|
||||
</VSCodeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
/**
|
||||
* Stories for Agent Manager components:
|
||||
* AgentManagerApp, FileTree, DiffPanel, FullScreenDiffView
|
||||
*
|
||||
* FileTree, DiffPanel, and FullScreenDiffView accept data-only props and only
|
||||
* require a LanguageContext, so they are storied with StoryProviders.
|
||||
* AgentManagerApp sets up its own full provider chain and is rendered directly.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { StoryProviders } from "./StoryProviders"
|
||||
import { AgentManagerApp } from "../../agent-manager/AgentManagerApp"
|
||||
import { FileTree } from "../../agent-manager/FileTree"
|
||||
import { DiffPanel } from "../../agent-manager/DiffPanel"
|
||||
import { FullScreenDiffView } from "../../agent-manager/FullScreenDiffView"
|
||||
import type { WorktreeFileDiff } from "../types/messages"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared mock data
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mockDiffs: WorktreeFileDiff[] = [
|
||||
{
|
||||
file: "src/components/chat/ChatView.tsx",
|
||||
status: "modified",
|
||||
additions: 12,
|
||||
deletions: 4,
|
||||
before: `import { Component } from "solid-js"\n\nexport const ChatView: Component = () => {\n return <div class="chat-view" />\n}\n`,
|
||||
after: `import { Component, createSignal } from "solid-js"\n\nexport const ChatView: Component = () => {\n const [open, setOpen] = createSignal(false)\n return <div class="chat-view" />\n}\n`,
|
||||
},
|
||||
{
|
||||
file: "src/components/chat/MessageList.tsx",
|
||||
status: "modified",
|
||||
additions: 3,
|
||||
deletions: 1,
|
||||
before: `export const MessageList = () => <div class="message-list" />\n`,
|
||||
after: `export const MessageList = () => (\n <div class="message-list" role="log" aria-live="polite" />\n)\n`,
|
||||
},
|
||||
{
|
||||
file: "src/stories/chat.stories.tsx",
|
||||
status: "added",
|
||||
additions: 80,
|
||||
deletions: 0,
|
||||
before: "",
|
||||
after: `/** @jsxImportSource solid-js */\nimport type { Meta } from "storybook-solidjs-vite"\nconst meta: Meta = { title: "Chat" }\nexport default meta\n`,
|
||||
},
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Meta
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const meta: Meta = {
|
||||
title: "AgentManager",
|
||||
parameters: { layout: "padded" },
|
||||
}
|
||||
export default meta
|
||||
type Story = StoryObj
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FileTree
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const FileTreeWithChanges: Story = {
|
||||
name: "FileTree — with modifications and additions",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "280px", height: "400px", overflow: "auto" }}>
|
||||
<FileTree
|
||||
diffs={mockDiffs}
|
||||
activeFile="src/components/chat/ChatView.tsx"
|
||||
onFileSelect={() => {}}
|
||||
showSummary
|
||||
/>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const FileTreeEmpty: Story = {
|
||||
name: "FileTree — no changes",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "280px", height: "400px" }}>
|
||||
<FileTree diffs={[]} activeFile={null} onFileSelect={() => {}} />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DiffPanel
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const DiffPanelLoading: Story = {
|
||||
name: "DiffPanel — loading",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "600px", height: "500px", display: "flex", "flex-direction": "column" }}>
|
||||
<DiffPanel
|
||||
diffs={[]}
|
||||
loading
|
||||
comments={[]}
|
||||
onCommentsChange={() => {}}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const DiffPanelWithDiffs: Story = {
|
||||
name: "DiffPanel — with diffs (unified)",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "600px", height: "500px", display: "flex", "flex-direction": "column" }}>
|
||||
<DiffPanel
|
||||
diffs={mockDiffs}
|
||||
loading={false}
|
||||
diffStyle="unified"
|
||||
onDiffStyleChange={() => {}}
|
||||
comments={[]}
|
||||
onCommentsChange={() => {}}
|
||||
onClose={() => {}}
|
||||
onExpand={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FullScreenDiffView
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const FullScreenDiffLoading: Story = {
|
||||
name: "FullScreenDiffView — loading",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "1024px", height: "700px", display: "flex" }}>
|
||||
<FullScreenDiffView
|
||||
diffs={[]}
|
||||
loading
|
||||
diffStyle="unified"
|
||||
onDiffStyleChange={() => {}}
|
||||
comments={[]}
|
||||
onCommentsChange={() => {}}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const FullScreenDiffWithChanges: Story = {
|
||||
name: "FullScreenDiffView — with changes",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "1024px", height: "700px", display: "flex" }}>
|
||||
<FullScreenDiffView
|
||||
diffs={mockDiffs}
|
||||
loading={false}
|
||||
diffStyle="unified"
|
||||
onDiffStyleChange={() => {}}
|
||||
comments={[]}
|
||||
onCommentsChange={() => {}}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AgentManagerApp
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const AgentManagerRoot: Story = {
|
||||
name: "AgentManagerApp — initial connecting state",
|
||||
render: () => (
|
||||
// AgentManagerApp sets up its own VSCodeProvider + ThemeProvider + layout.
|
||||
// In Storybook it renders its empty/connecting state (no extension host).
|
||||
<div style={{ width: "900px", height: "600px" }}>
|
||||
<AgentManagerApp />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
/**
|
||||
* Stories for high-priority chat components:
|
||||
* ChatView, MessageList, QuestionDock
|
||||
*
|
||||
* These render with mocked session/server/provider contexts — the components
|
||||
* will show their "idle / empty" states since no real extension host is connected.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { StoryProviders } from "./StoryProviders"
|
||||
import { ChatView } from "../components/chat/ChatView"
|
||||
import { MessageList } from "../components/chat/MessageList"
|
||||
import { QuestionDock } from "../components/chat/QuestionDock"
|
||||
import type { QuestionRequest } from "../types/messages"
|
||||
|
||||
const SESSION_ID = "story-session-chat-001"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Question fixtures
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const singleQuestion: QuestionRequest = {
|
||||
id: "q-single-001",
|
||||
sessionID: SESSION_ID,
|
||||
questions: [
|
||||
{
|
||||
question: "Which testing framework should I use for this project?",
|
||||
header: "Choose a framework",
|
||||
options: [
|
||||
{ label: "Vitest", description: "Fast, Vite-native unit testing" },
|
||||
{ label: "Jest", description: "Widely adopted, rich ecosystem" },
|
||||
{ label: "Playwright", description: "End-to-end browser testing" },
|
||||
{ label: "Bun test", description: "Built-in, zero config" },
|
||||
],
|
||||
},
|
||||
],
|
||||
tool: { messageID: "asst-msg-001", callID: "call-question-001" },
|
||||
}
|
||||
|
||||
const multiQuestion: QuestionRequest = {
|
||||
id: "q-multi-001",
|
||||
sessionID: SESSION_ID,
|
||||
questions: [
|
||||
{
|
||||
question: "Which testing framework?",
|
||||
options: [{ label: "Vitest" }, { label: "Jest" }, { label: "Bun test" }],
|
||||
},
|
||||
{
|
||||
question: "Should I include coverage reporting?",
|
||||
options: [{ label: "Yes, Istanbul" }, { label: "Yes, V8" }, { label: "No" }],
|
||||
},
|
||||
],
|
||||
tool: { messageID: "asst-msg-001", callID: "call-question-002" },
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Meta
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Chat",
|
||||
parameters: { layout: "padded" },
|
||||
}
|
||||
export default meta
|
||||
type Story = StoryObj
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ChatView stories
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const ChatViewIdle: Story = {
|
||||
name: "ChatView — idle (empty)",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle">
|
||||
<div style={{ width: "400px", height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ChatViewReadonly: Story = {
|
||||
name: "ChatView — readonly",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle">
|
||||
<div style={{ width: "400px", height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView readonly />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ChatViewWithQuestion: Story = {
|
||||
name: "ChatView — with QuestionDock",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} questions={[singleQuestion]}>
|
||||
<div style={{ width: "400px", height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MessageList stories
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const MessageListEmpty: Story = {
|
||||
name: "MessageList — empty",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID}>
|
||||
<div style={{ width: "400px", height: "500px" }}>
|
||||
<MessageList />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const MessageListLoading: Story = {
|
||||
name: "MessageList — loading",
|
||||
render: () => {
|
||||
const loadingSession = {
|
||||
loading: () => true,
|
||||
}
|
||||
// Render empty state — the real "loading" state requires overriding session.loading()
|
||||
// which we approximate here: the component will show its empty-state logo instead.
|
||||
return (
|
||||
<StoryProviders sessionID={SESSION_ID}>
|
||||
<div style={{ width: "400px", height: "500px" }}>
|
||||
<MessageList />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// QuestionDock stories
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const QuestionDockSingle: Story = {
|
||||
name: "QuestionDock — single question",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} questions={[singleQuestion]}>
|
||||
<div style={{ width: "420px" }}>
|
||||
<QuestionDock request={singleQuestion} />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const QuestionDockMulti: Story = {
|
||||
name: "QuestionDock — multi-question wizard",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} questions={[multiQuestion]}>
|
||||
<div style={{ width: "420px" }}>
|
||||
<QuestionDock request={multiQuestion} />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
/**
|
||||
* Stories for the SessionList component (history panel).
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { createSignal, type ParentComponent } from "solid-js"
|
||||
import { DialogProvider } from "@kilocode/kilo-ui/context/dialog"
|
||||
import { DataProvider } from "@kilocode/kilo-ui/context/data"
|
||||
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
|
||||
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
|
||||
import { MarkedProvider } from "@kilocode/kilo-ui/context/marked"
|
||||
import { I18nProvider } from "@kilocode/kilo-ui/context"
|
||||
import { Diff } from "@kilocode/kilo-ui/diff"
|
||||
import { Code } from "@kilocode/kilo-ui/code"
|
||||
import { VSCodeProvider } from "../context/vscode"
|
||||
import { ServerProvider } from "../context/server"
|
||||
import { ConfigProvider } from "../context/config"
|
||||
import { ProviderProvider } from "../context/provider"
|
||||
import { SessionContext } from "../context/session"
|
||||
import { LanguageContext } from "../context/language"
|
||||
import { dict as uiEn } from "@kilocode/kilo-ui/i18n/en"
|
||||
import { dict as appEn } from "../i18n/en"
|
||||
import { dict as kiloEn } from "@kilocode/kilo-i18n/en"
|
||||
import { SessionList } from "../components/history/SessionList"
|
||||
|
||||
const dict: Record<string, string> = { ...appEn, ...uiEn, ...kiloEn }
|
||||
function t(key: string) { return dict[key] ?? key }
|
||||
function noop() {}
|
||||
|
||||
const now = new Date().toISOString()
|
||||
const yesterday = new Date(Date.now() - 86400000).toISOString()
|
||||
const weekAgo = new Date(Date.now() - 7 * 86400000).toISOString()
|
||||
|
||||
const mockSessions = [
|
||||
{ id: "s1", title: "Refactor authentication module", createdAt: now, updatedAt: now },
|
||||
{ id: "s2", title: "Add screenshot test coverage", createdAt: yesterday, updatedAt: yesterday },
|
||||
{ id: "s3", title: "Fix TypeScript errors in webview", createdAt: weekAgo, updatedAt: weekAgo },
|
||||
{ id: "s4", title: undefined, createdAt: weekAgo, updatedAt: weekAgo },
|
||||
]
|
||||
|
||||
const WithSessions: ParentComponent<{ sessions?: typeof mockSessions }> = (props) => {
|
||||
const [locale] = createSignal<"en">("en")
|
||||
const sessions = props.sessions ?? []
|
||||
const session = {
|
||||
currentSessionID: () => "s1",
|
||||
currentSession: () => sessions[0],
|
||||
setCurrentSessionID: noop,
|
||||
sessions: () => sessions as any,
|
||||
status: () => "idle" as const,
|
||||
statusInfo: () => ({ type: "idle" }),
|
||||
statusText: () => undefined,
|
||||
busySince: () => undefined,
|
||||
loading: () => false,
|
||||
messages: () => [],
|
||||
userMessages: () => [],
|
||||
allMessages: () => ({}),
|
||||
allParts: () => ({}),
|
||||
allStatusMap: () => ({}),
|
||||
getParts: () => [],
|
||||
todos: () => [],
|
||||
permissions: () => [],
|
||||
questions: () => [],
|
||||
questionErrors: () => new Set<string>(),
|
||||
selected: () => ({ providerID: "anthropic", modelID: "claude-sonnet-4-20250514" }),
|
||||
selectModel: noop,
|
||||
totalCost: () => 0,
|
||||
contextUsage: () => undefined,
|
||||
agents: () => [{ name: "code", description: "Code mode", mode: "primary" as const }],
|
||||
selectedAgent: () => "code",
|
||||
selectAgent: noop,
|
||||
getSessionAgent: () => "code",
|
||||
getSessionModel: () => ({ providerID: "anthropic", modelID: "claude-sonnet-4-20250514" }),
|
||||
setSessionModel: noop,
|
||||
setSessionAgent: noop,
|
||||
variantList: () => [],
|
||||
currentVariant: () => undefined,
|
||||
selectVariant: noop,
|
||||
sendMessage: noop,
|
||||
abort: noop,
|
||||
compact: noop,
|
||||
respondToPermission: noop,
|
||||
replyToQuestion: noop,
|
||||
rejectQuestion: noop,
|
||||
createSession: noop,
|
||||
clearCurrentSession: noop,
|
||||
loadSessions: noop,
|
||||
selectSession: noop,
|
||||
deleteSession: noop,
|
||||
renameSession: noop,
|
||||
syncSession: noop,
|
||||
cloudPreviewId: () => null,
|
||||
selectCloudSession: noop,
|
||||
}
|
||||
|
||||
return (
|
||||
<VSCodeProvider>
|
||||
<ServerProvider>
|
||||
<ConfigProvider>
|
||||
<ProviderProvider>
|
||||
<DialogProvider>
|
||||
<LanguageContext.Provider value={{ locale, setLocale: noop, userOverride: () => "" as any, t }}>
|
||||
<I18nProvider value={{ locale: () => "en", t }}>
|
||||
<SessionContext.Provider value={session as any}>
|
||||
<DataProvider data={{ session: sessions, session_status: {}, session_diff: {}, message: {}, part: {}, permission: {}, question: {}, provider: { all: [], connected: false, default: {} } }} directory="/project/">
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<CodeComponentProvider component={Code}>
|
||||
<MarkedProvider>
|
||||
<div style={{ padding: "12px" }}>{props.children}</div>
|
||||
</MarkedProvider>
|
||||
</CodeComponentProvider>
|
||||
</DiffComponentProvider>
|
||||
</DataProvider>
|
||||
</SessionContext.Provider>
|
||||
</I18nProvider>
|
||||
</LanguageContext.Provider>
|
||||
</DialogProvider>
|
||||
</ProviderProvider>
|
||||
</ConfigProvider>
|
||||
</ServerProvider>
|
||||
</VSCodeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const meta: Meta = {
|
||||
title: "History/SessionList",
|
||||
parameters: { layout: "padded" },
|
||||
}
|
||||
export default meta
|
||||
type Story = StoryObj
|
||||
|
||||
export const Empty: Story = {
|
||||
name: "Empty list",
|
||||
render: () => (
|
||||
<WithSessions sessions={[]}>
|
||||
<div style={{ width: "320px", height: "500px" }}>
|
||||
<SessionList onSelectSession={noop} />
|
||||
</div>
|
||||
</WithSessions>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithItems: Story = {
|
||||
name: "With sessions",
|
||||
render: () => (
|
||||
<WithSessions sessions={mockSessions as any}>
|
||||
<div style={{ width: "320px", height: "500px" }}>
|
||||
<SessionList onSelectSession={noop} />
|
||||
</div>
|
||||
</WithSessions>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
/**
|
||||
* Stories for Settings and ProvidersTab components.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { StoryProviders } from "./StoryProviders"
|
||||
import Settings from "../components/settings/Settings"
|
||||
import ProvidersTab from "../components/settings/ProvidersTab"
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Settings",
|
||||
parameters: { layout: "fullscreen" },
|
||||
}
|
||||
export default meta
|
||||
type Story = StoryObj
|
||||
|
||||
export const SettingsPanel: Story = {
|
||||
name: "Settings — full panel",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "560px", height: "700px", display: "flex", "flex-direction": "column" }}>
|
||||
<Settings onBack={() => {}} />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ProvidersConfigure: Story = {
|
||||
name: "ProvidersTab — no providers configured",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ width: "560px", "max-height": "700px", overflow: "auto" }}>
|
||||
<ProvidersTab />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
/**
|
||||
* Stories for shared controls: ModelSelector and ModeSwitcher.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { StoryProviders } from "./StoryProviders"
|
||||
import { ModelSelectorBase } from "../components/shared/ModelSelector"
|
||||
import { ModeSwitcherBase, ModeSwitcher } from "../components/shared/ModeSwitcher"
|
||||
|
||||
const agents = [
|
||||
{ name: "code", description: "Write, edit and review code", mode: "primary" as const },
|
||||
{ name: "ask", description: "Answer questions without making changes", mode: "primary" as const },
|
||||
{ name: "architect", description: "Plan and design before implementation", mode: "primary" as const },
|
||||
{ name: "debug", description: "Diagnose and fix issues", mode: "primary" as const },
|
||||
]
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Shared",
|
||||
parameters: { layout: "padded" },
|
||||
}
|
||||
export default meta
|
||||
type Story = StoryObj
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ModelSelector
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const ModelSelectorNoProviders: Story = {
|
||||
name: "ModelSelector — no providers",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
|
||||
<ModelSelectorBase
|
||||
value={{ providerID: "kilo", modelID: "kilo/auto" }}
|
||||
onSelect={() => {}}
|
||||
placement="bottom-start"
|
||||
/>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ModelSelectorAllowClear: Story = {
|
||||
name: "ModelSelector — allow clear",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
|
||||
<ModelSelectorBase
|
||||
value={null}
|
||||
onSelect={() => {}}
|
||||
placement="bottom-start"
|
||||
allowClear
|
||||
clearLabel="Use default model"
|
||||
/>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ModeSwitcher
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const ModeSwitcherSingle: Story = {
|
||||
name: "ModeSwitcherBase — single agent (hidden)",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<ModeSwitcherBase
|
||||
agents={[{ name: "code", description: "Code mode", mode: "primary" as const }]}
|
||||
value="code"
|
||||
onSelect={() => {}}
|
||||
/>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ModeSwitcherMultiple: Story = {
|
||||
name: "ModeSwitcherBase — multiple agents",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<ModeSwitcherBase agents={agents} value="code" onSelect={() => {}} />
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ModeSwitcherAskSelected: Story = {
|
||||
name: "ModeSwitcherBase — ask mode selected",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<ModeSwitcherBase agents={agents} value="ask" onSelect={() => {}} />
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ModeSwitcherFromSession: Story = {
|
||||
name: "ModeSwitcher — wired to session context",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<ModeSwitcher />
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user