@kitn.ai/ui 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +394 -0
- package/dist/bash-InADTalH.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -0
- package/dist/css-M7EaDHN_.js +1 -0
- package/dist/custom-elements.json +4739 -0
- package/dist/engine-javascript-vq0WuIJl.js +141 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -0
- package/dist/github-light-JYsPkUQd.js +1 -0
- package/dist/host-embed.d.ts +33 -0
- package/dist/html-CPZ3oZQ7.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/javascript-C25yR2R2.js +1 -0
- package/dist/json-DxJze_jm.js +1 -0
- package/dist/kitn-chat-provider.d.ts +2 -0
- package/dist/kitn-chat-provider.es.js +240 -0
- package/dist/kitn-chat.es.js +77 -0
- package/dist/llms/llms-full.txt +1018 -0
- package/dist/llms/llms.txt +104 -0
- package/dist/origin.d.ts +5 -0
- package/dist/provider-runtime.d.ts +27 -0
- package/dist/schemas/card-envelope.schema.json +14 -0
- package/dist/schemas/card-event.schema.json +12 -0
- package/dist/schemas/choice.schema.json +70 -0
- package/dist/schemas/confirm.schema.json +65 -0
- package/dist/schemas/embed.schema.json +65 -0
- package/dist/schemas/form.result.schema.json +7 -0
- package/dist/schemas/form.schema.json +33 -0
- package/dist/schemas/link.schema.json +56 -0
- package/dist/schemas/tasks.result.schema.json +16 -0
- package/dist/schemas/tasks.schema.json +78 -0
- package/dist/svelte--5p79yCD.js +1 -0
- package/dist/theme.tokens.css +176 -0
- package/dist/tsx-B8rCNbgL.js +1 -0
- package/dist/typescript-RycA9KXf.js +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/version.d.ts +4 -0
- package/dist/vue-BmIZj4XD.js +1 -0
- package/dist/wire.d.ts +53 -0
- package/frameworks/react/index.tsx +948 -0
- package/frameworks/react/runtime.tsx +94 -0
- package/llms-full.txt +1018 -0
- package/llms.txt +104 -0
- package/package.json +141 -0
- package/src/components/artifact.stories.tsx +138 -0
- package/src/components/artifact.tsx +743 -0
- package/src/components/attachments.stories.tsx +303 -0
- package/src/components/attachments.test.tsx +64 -0
- package/src/components/attachments.tsx +399 -0
- package/src/components/card-fallback.tsx +28 -0
- package/src/components/card-renderer.tsx +52 -0
- package/src/components/card.tsx +110 -0
- package/src/components/chain-of-thought.stories.tsx +211 -0
- package/src/components/chain-of-thought.tsx +139 -0
- package/src/components/chat-container.stories.tsx +187 -0
- package/src/components/chat-container.tsx +82 -0
- package/src/components/chat-scope-picker.tsx +47 -0
- package/src/components/chat-thread.test.tsx +58 -0
- package/src/components/chat-thread.tsx +249 -0
- package/src/components/checkpoint.stories.tsx +102 -0
- package/src/components/checkpoint.tsx +88 -0
- package/src/components/choice-card.tsx +569 -0
- package/src/components/code-block.stories.tsx +150 -0
- package/src/components/code-block.tsx +105 -0
- package/src/components/component-meta.json +3735 -0
- package/src/components/confirm-card.stories.tsx +74 -0
- package/src/components/confirm-card.tsx +315 -0
- package/src/components/context.stories.tsx +229 -0
- package/src/components/context.test.tsx +133 -0
- package/src/components/context.tsx +363 -0
- package/src/components/conversation-item.stories.tsx +125 -0
- package/src/components/conversation-item.tsx +16 -0
- package/src/components/conversation-list.stories.tsx +133 -0
- package/src/components/conversation-list.tsx +101 -0
- package/src/components/default-input.test.tsx +111 -0
- package/src/components/embed.tsx +196 -0
- package/src/components/empty.stories.tsx +434 -0
- package/src/components/empty.tsx +166 -0
- package/src/components/feedback-bar.stories.tsx +138 -0
- package/src/components/feedback-bar.test.tsx +103 -0
- package/src/components/feedback-bar.tsx +166 -0
- package/src/components/file-tree.stories.tsx +73 -0
- package/src/components/file-tree.tsx +383 -0
- package/src/components/file-upload.stories.tsx +156 -0
- package/src/components/file-upload.tsx +161 -0
- package/src/components/form-widgets.tsx +461 -0
- package/src/components/form.tsx +873 -0
- package/src/components/image.stories.tsx +89 -0
- package/src/components/image.tsx +67 -0
- package/src/components/link-preview.tsx +194 -0
- package/src/components/loader.stories.tsx +181 -0
- package/src/components/loader.tsx +333 -0
- package/src/components/markdown.stories.tsx +180 -0
- package/src/components/markdown.tsx +81 -0
- package/src/components/message-action-bar.test.tsx +128 -0
- package/src/components/message-narrow.stories.tsx +329 -0
- package/src/components/message-skills.stories.tsx +211 -0
- package/src/components/message-skills.tsx +36 -0
- package/src/components/message.stories.tsx +281 -0
- package/src/components/message.tsx +227 -0
- package/src/components/model-switcher.stories.tsx +123 -0
- package/src/components/model-switcher.test.tsx +65 -0
- package/src/components/model-switcher.tsx +87 -0
- package/src/components/prompt-input.stories.tsx +222 -0
- package/src/components/prompt-input.tsx +211 -0
- package/src/components/prompt-suggestion.stories.tsx +142 -0
- package/src/components/prompt-suggestion.tsx +115 -0
- package/src/components/reasoning.stories.tsx +140 -0
- package/src/components/reasoning.tsx +156 -0
- package/src/components/resizable.test.tsx +226 -0
- package/src/components/response-stream.tsx +103 -0
- package/src/components/scroll-button.stories.tsx +100 -0
- package/src/components/scroll-button.tsx +34 -0
- package/src/components/slash-command.stories.tsx +163 -0
- package/src/components/slash-command.tsx +232 -0
- package/src/components/source-list.test.tsx +152 -0
- package/src/components/source.stories.tsx +124 -0
- package/src/components/source.tsx +129 -0
- package/src/components/tasks-card.stories.tsx +78 -0
- package/src/components/tasks-card.tsx +432 -0
- package/src/components/text-shimmer.stories.tsx +87 -0
- package/src/components/text-shimmer.tsx +37 -0
- package/src/components/thinking-bar.stories.tsx +87 -0
- package/src/components/thinking-bar.tsx +50 -0
- package/src/components/tool.stories.tsx +153 -0
- package/src/components/tool.tsx +184 -0
- package/src/components/use-card-resolution.ts +28 -0
- package/src/components/voice-input.stories.tsx +83 -0
- package/src/components/voice-input.tsx +107 -0
- package/src/elements/artifact.stories.tsx +506 -0
- package/src/elements/artifact.tsx +137 -0
- package/src/elements/attachments.stories.tsx +190 -0
- package/src/elements/attachments.tsx +136 -0
- package/src/elements/card.stories.tsx +113 -0
- package/src/elements/card.tsx +40 -0
- package/src/elements/cards.stories.tsx +59 -0
- package/src/elements/cards.tsx +97 -0
- package/src/elements/catalog.stories.tsx +491 -0
- package/src/elements/chain-of-thought.declarative.test.tsx +153 -0
- package/src/elements/chain-of-thought.stories.tsx +113 -0
- package/src/elements/chain-of-thought.tsx +92 -0
- package/src/elements/chat-scope-picker.stories.tsx +73 -0
- package/src/elements/chat-scope-picker.tsx +36 -0
- package/src/elements/chat-types.ts +42 -0
- package/src/elements/chat-workspace.stories.tsx +288 -0
- package/src/elements/chat-workspace.tsx +161 -0
- package/src/elements/chat.stories.tsx +151 -0
- package/src/elements/chat.tsx +80 -0
- package/src/elements/checkpoint.stories.tsx +72 -0
- package/src/elements/checkpoint.tsx +47 -0
- package/src/elements/choice.stories.tsx +240 -0
- package/src/elements/choice.tsx +48 -0
- package/src/elements/code-block.stories.tsx +83 -0
- package/src/elements/code-block.tsx +42 -0
- package/src/elements/compiled.css +2 -0
- package/src/elements/composed-shell.stories.tsx +316 -0
- package/src/elements/confirm-card.stories.tsx +227 -0
- package/src/elements/confirm-card.tsx +51 -0
- package/src/elements/context-meter.stories.tsx +151 -0
- package/src/elements/context-meter.tsx +121 -0
- package/src/elements/conversation-list.declarative.test.tsx +137 -0
- package/src/elements/conversation-list.stories.tsx +243 -0
- package/src/elements/conversation-list.tsx +80 -0
- package/src/elements/css.ts +5 -0
- package/src/elements/default-input.tsx +247 -0
- package/src/elements/define.tsx +171 -0
- package/src/elements/element-meta.json +3314 -0
- package/src/elements/element-types.d.ts +710 -0
- package/src/elements/embed.stories.tsx +213 -0
- package/src/elements/embed.tsx +35 -0
- package/src/elements/empty.stories.tsx +111 -0
- package/src/elements/empty.tsx +29 -0
- package/src/elements/feedback-bar.stories.tsx +114 -0
- package/src/elements/feedback-bar.tsx +61 -0
- package/src/elements/file-tree.stories.tsx +134 -0
- package/src/elements/file-tree.tsx +52 -0
- package/src/elements/file-upload.stories.tsx +82 -0
- package/src/elements/file-upload.tsx +44 -0
- package/src/elements/form.stories.tsx +248 -0
- package/src/elements/form.tsx +43 -0
- package/src/elements/framework-usage.json +563 -0
- package/src/elements/image.stories.tsx +71 -0
- package/src/elements/image.tsx +32 -0
- package/src/elements/link-preview.stories.tsx +209 -0
- package/src/elements/link-preview.tsx +34 -0
- package/src/elements/loader.stories.tsx +88 -0
- package/src/elements/loader.tsx +25 -0
- package/src/elements/markdown.stories.tsx +76 -0
- package/src/elements/markdown.tsx +38 -0
- package/src/elements/message-skills.declarative.test.tsx +104 -0
- package/src/elements/message-skills.stories.tsx +114 -0
- package/src/elements/message-skills.tsx +85 -0
- package/src/elements/message.stories.tsx +218 -0
- package/src/elements/message.tsx +162 -0
- package/src/elements/model-switcher.declarative.test.tsx +130 -0
- package/src/elements/model-switcher.stories.tsx +198 -0
- package/src/elements/model-switcher.tsx +93 -0
- package/src/elements/popover.stories.tsx +80 -0
- package/src/elements/popover.tsx +59 -0
- package/src/elements/prompt-input-slash-command.test.tsx +128 -0
- package/src/elements/prompt-input-stoppable.test.tsx +88 -0
- package/src/elements/prompt-input-toolbar-actions.test.tsx +120 -0
- package/src/elements/prompt-input.stories.tsx +331 -0
- package/src/elements/prompt-input.tsx +185 -0
- package/src/elements/prompt-suggestions.declarative.test.tsx +164 -0
- package/src/elements/prompt-suggestions.stories.tsx +213 -0
- package/src/elements/prompt-suggestions.tsx +94 -0
- package/src/elements/reasoning.stories.tsx +77 -0
- package/src/elements/reasoning.tsx +50 -0
- package/src/elements/register.ts +56 -0
- package/src/elements/remote.stories.tsx +314 -0
- package/src/elements/remote.tsx +221 -0
- package/src/elements/resizable.d.ts +27 -0
- package/src/elements/resizable.stories.tsx +426 -0
- package/src/elements/resizable.tsx +553 -0
- package/src/elements/response-stream.stories.tsx +79 -0
- package/src/elements/response-stream.tsx +40 -0
- package/src/elements/scroll-button.stories.tsx +202 -0
- package/src/elements/scroll-button.test.tsx +148 -0
- package/src/elements/scroll-button.tsx +126 -0
- package/src/elements/source-list.stories.tsx +143 -0
- package/src/elements/source.stories.tsx +88 -0
- package/src/elements/source.tsx +120 -0
- package/src/elements/styles.css +199 -0
- package/src/elements/switch.stories.tsx +56 -0
- package/src/elements/switch.tsx +43 -0
- package/src/elements/tasks.stories.tsx +237 -0
- package/src/elements/tasks.tsx +46 -0
- package/src/elements/text-shimmer.stories.tsx +64 -0
- package/src/elements/text-shimmer.tsx +28 -0
- package/src/elements/thinking-bar.stories.tsx +72 -0
- package/src/elements/thinking-bar.tsx +34 -0
- package/src/elements/tool.stories.tsx +89 -0
- package/src/elements/tool.tsx +23 -0
- package/src/elements/voice-input.stories.tsx +88 -0
- package/src/elements/voice-input.tsx +41 -0
- package/src/index.ts +253 -0
- package/src/primitives/card-contract.ts +71 -0
- package/src/primitives/card-host.tsx +35 -0
- package/src/primitives/card-registry.tsx +67 -0
- package/src/primitives/card-resolution.ts +36 -0
- package/src/primitives/card-routing.ts +79 -0
- package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
- package/src/primitives/card-schemas/card-event.schema.json +12 -0
- package/src/primitives/card-schemas/choice.schema.json +70 -0
- package/src/primitives/card-schemas/confirm.schema.json +65 -0
- package/src/primitives/card-schemas/embed.schema.json +65 -0
- package/src/primitives/card-schemas/form.result.schema.json +7 -0
- package/src/primitives/card-schemas/form.schema.json +33 -0
- package/src/primitives/card-schemas/link.schema.json +56 -0
- package/src/primitives/card-schemas/tasks.result.schema.json +16 -0
- package/src/primitives/card-schemas/tasks.schema.json +78 -0
- package/src/primitives/card-validate.ts +95 -0
- package/src/primitives/chat-config.tsx +76 -0
- package/src/primitives/controllable.test.ts +47 -0
- package/src/primitives/controllable.ts +21 -0
- package/src/primitives/embed-providers.ts +254 -0
- package/src/primitives/highlighter.ts +157 -0
- package/src/primitives/link-preview.ts +87 -0
- package/src/primitives/pdf-preview.ts +121 -0
- package/src/primitives/use-auto-resize.ts +31 -0
- package/src/primitives/use-resize-observer.ts +12 -0
- package/src/primitives/use-stick-to-bottom.ts +43 -0
- package/src/primitives/use-text-stream.ts +112 -0
- package/src/primitives/use-voice-recorder.ts +50 -0
- package/src/remote/host-embed.ts +345 -0
- package/src/remote/index.ts +2 -0
- package/src/remote/origin.ts +30 -0
- package/src/remote/provider-runtime.ts +259 -0
- package/src/remote/provider.ts +2 -0
- package/src/remote/validate.ts +22 -0
- package/src/remote/version.ts +12 -0
- package/src/remote/wire.ts +48 -0
- package/src/stories/chat-panel-layout.stories.tsx +145 -0
- package/src/stories/chat-scene.tsx +571 -0
- package/src/stories/checkpoint-restore.stories.tsx +256 -0
- package/src/stories/context-usage.stories.tsx +212 -0
- package/src/stories/conversation-with-reasoning.stories.tsx +182 -0
- package/src/stories/conversation-with-sources.stories.tsx +471 -0
- package/src/stories/docs/Accessibility.mdx +119 -0
- package/src/stories/docs/ChildElements.mdx +269 -0
- package/src/stories/docs/ForAIAgents.mdx +162 -0
- package/src/stories/docs/GettingStarted.mdx +65 -0
- package/src/stories/docs/Installation.mdx +83 -0
- package/src/stories/docs/Introduction.mdx +50 -0
- package/src/stories/docs/SolidJsAdvanced.mdx +17 -0
- package/src/stories/docs/Theming.mdx +85 -0
- package/src/stories/docs/element-controls.ts +121 -0
- package/src/stories/docs/frameworks/Angular.mdx +245 -0
- package/src/stories/docs/frameworks/Html.mdx +227 -0
- package/src/stories/docs/frameworks/Overview.mdx +47 -0
- package/src/stories/docs/frameworks/React.mdx +227 -0
- package/src/stories/docs/frameworks/Solid.mdx +173 -0
- package/src/stories/docs/frameworks/Svelte.mdx +194 -0
- package/src/stories/docs/frameworks/Vue.mdx +238 -0
- package/src/stories/docs/generative-ui-overview.mdx +186 -0
- package/src/stories/docs/recipes/SpeechToText.mdx +7 -0
- package/src/stories/docs/recipes/Streaming.mdx +68 -0
- package/src/stories/docs/recipes/TextToSpeech.mdx +35 -0
- package/src/stories/docs/theme-editor/canvas.tsx +32 -0
- package/src/stories/docs/theme-editor/inspector.tsx +66 -0
- package/src/stories/docs/theme-editor/presets.test.ts +32 -0
- package/src/stories/docs/theme-editor/presets.ts +64 -0
- package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
- package/src/stories/docs/theme-editor/theme-css.ts +15 -0
- package/src/stories/docs/theme-editor/theme-editor.tsx +146 -0
- package/src/stories/docs/theme-tokens.tsx +174 -0
- package/src/stories/examples/ChoosingComponents.mdx +106 -0
- package/src/stories/examples/sample-data.ts +79 -0
- package/src/stories/examples/usage/checkpoint-restore.ts +156 -0
- package/src/stories/examples/usage/context-usage.ts +591 -0
- package/src/stories/examples/usage/conversation-with-reasoning.ts +224 -0
- package/src/stories/examples/usage/conversation-with-sources.ts +589 -0
- package/src/stories/examples/usage/empty-state.ts +144 -0
- package/src/stories/examples/usage/full-chat-app.ts +277 -0
- package/src/stories/examples/usage/index.ts +63 -0
- package/src/stories/examples/usage/message-actions.ts +704 -0
- package/src/stories/examples/usage/pattern-centered-conversation.ts +446 -0
- package/src/stories/examples/usage/pattern-chat-panel-layout.ts +442 -0
- package/src/stories/examples/usage/pattern-docked-widget.ts +479 -0
- package/src/stories/examples/usage/prompt-input-variants.ts +1356 -0
- package/src/stories/examples/usage/streaming-response.ts +687 -0
- package/src/stories/examples/usage/types.ts +33 -0
- package/src/stories/full-chat.stories.tsx +18 -0
- package/src/stories/message-actions.stories.tsx +230 -0
- package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
- package/src/stories/pattern-docked-widget.stories.tsx +93 -0
- package/src/stories/pattern-empty-state.stories.tsx +76 -0
- package/src/stories/prompt-input-variants.stories.tsx +456 -0
- package/src/stories/streaming-response.stories.tsx +438 -0
- package/src/stories/theme-editor.stories.tsx +16 -0
- package/src/stories/token-reference.stories.tsx +18 -0
- package/src/stories/typography.stories.tsx +78 -0
- package/src/types.ts +47 -0
- package/src/ui/action-icons.ts +52 -0
- package/src/ui/avatar.stories.tsx +103 -0
- package/src/ui/avatar.tsx +23 -0
- package/src/ui/badge.stories.tsx +86 -0
- package/src/ui/badge.tsx +21 -0
- package/src/ui/button.stories.tsx +145 -0
- package/src/ui/button.tsx +38 -0
- package/src/ui/collapsible.stories.tsx +69 -0
- package/src/ui/collapsible.tsx +125 -0
- package/src/ui/dropdown.stories.tsx +59 -0
- package/src/ui/dropdown.tsx +196 -0
- package/src/ui/hover-card.stories.tsx +77 -0
- package/src/ui/hover-card.test.tsx +30 -0
- package/src/ui/hover-card.tsx +172 -0
- package/src/ui/overlay.stories.tsx +115 -0
- package/src/ui/overlay.tsx +158 -0
- package/src/ui/popover.stories.tsx +81 -0
- package/src/ui/popover.test.tsx +99 -0
- package/src/ui/popover.tsx +92 -0
- package/src/ui/resizable.stories.tsx +236 -0
- package/src/ui/resizable.tsx +576 -0
- package/src/ui/scroll-area.stories.tsx +50 -0
- package/src/ui/scroll-area.tsx +15 -0
- package/src/ui/separator.stories.tsx +81 -0
- package/src/ui/separator.tsx +10 -0
- package/src/ui/skeleton.stories.tsx +337 -0
- package/src/ui/skeleton.tsx +16 -0
- package/src/ui/switch.stories.tsx +51 -0
- package/src/ui/switch.test.tsx +59 -0
- package/src/ui/switch.tsx +62 -0
- package/src/ui/textarea.stories.tsx +76 -0
- package/src/ui/textarea.tsx +21 -0
- package/src/ui/tooltip.stories.tsx +74 -0
- package/src/ui/tooltip.tsx +68 -0
- package/src/utils/cn.ts +24 -0
- package/theme.css +187 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { createSignal } from 'solid-js';
|
|
3
|
+
import {
|
|
4
|
+
ChatContainer, ChatContainerContent, ChatContainerScrollAnchor,
|
|
5
|
+
Message, MessageAvatar, MessageContent, MessageActions,
|
|
6
|
+
PromptInput, PromptInputTextarea, PromptInputActions,
|
|
7
|
+
Checkpoint, CheckpointIcon, CheckpointTrigger,
|
|
8
|
+
Button, Separator,
|
|
9
|
+
} from '../index';
|
|
10
|
+
import { Copy, ThumbsUp, ThumbsDown, ArrowUp, RotateCcw } from 'lucide-solid';
|
|
11
|
+
|
|
12
|
+
const meta: Meta = {
|
|
13
|
+
title: 'Examples/Checkpoint & Restore',
|
|
14
|
+
parameters: {
|
|
15
|
+
layout: 'padded',
|
|
16
|
+
docs: {
|
|
17
|
+
description: {
|
|
18
|
+
component: [
|
|
19
|
+
'Mark named restore points in a conversation. `<kai-checkpoint>` (or the SolidJS `Checkpoint` primitive) renders a bookmark button that fires `kai-select` (no detail payload) when clicked.',
|
|
20
|
+
'',
|
|
21
|
+
'**"Restoring" is your responsibility — the element just emits an event.** When `kai-select` fires, slice your message array back to the messages that existed at that checkpoint. The element has no internal state management.',
|
|
22
|
+
'',
|
|
23
|
+
'**Where checkpoints come from:** create a checkpoint entry in your message list after each AI response you want to mark as a save point. The `label` / `tooltip` props are plain strings passed to the element.',
|
|
24
|
+
'',
|
|
25
|
+
'**`<kai-checkpoint>` WC props:** `label` (optional visible text), `tooltip` (hover text), `variant` (`ghost` | `default` | `outline`, default `ghost`), `size` (`sm` | `md` | `lg` | `icon` | `icon-sm`, default `sm`). The icon is always the built-in `CheckpointIcon` — there is no `icon` prop on the WC (planned gap in the capability-gaps spec).',
|
|
26
|
+
'',
|
|
27
|
+
'**Custom icon in the trigger:** compose the SolidJS `Checkpoint` + `CheckpointIcon` + `CheckpointTrigger` primitives. The demo places a `<RotateCcw>` icon inside `CheckpointTrigger` — that works because `CheckpointTrigger` accepts arbitrary children.',
|
|
28
|
+
].join('\n'),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj;
|
|
36
|
+
|
|
37
|
+
export const Default: Story = {
|
|
38
|
+
name: 'API Design Session with Restore Points',
|
|
39
|
+
parameters: {
|
|
40
|
+
docs: {
|
|
41
|
+
description: {
|
|
42
|
+
story: [
|
|
43
|
+
'Three-exchange API design session with two checkpoints. Clicking a checkpoint updates the header label — in a real app you would also slice your message array back to the messages that existed at that point.',
|
|
44
|
+
'',
|
|
45
|
+
'**`kai-select` carries no detail payload** — you identify which checkpoint was clicked by which handler you wired up, not from the event itself. Confirmed in `src/elements/checkpoint.tsx`.',
|
|
46
|
+
'',
|
|
47
|
+
'**The `<RotateCcw>` icon in the trigger is a SolidJS-only touch** — the `<kai-checkpoint>` WC always uses the built-in `CheckpointIcon` (no `icon` prop). Compose `CheckpointTrigger` with custom children only when using the SolidJS primitives.',
|
|
48
|
+
].join('\n'),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
render: () => {
|
|
53
|
+
const [restoredTo, setRestoredTo] = createSignal<string | null>(null);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div class="flex flex-col h-[700px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
|
|
57
|
+
<div class="flex items-center justify-between px-4 py-3">
|
|
58
|
+
<h2 class="text-sm font-semibold text-foreground">API Design Session</h2>
|
|
59
|
+
{restoredTo() && (
|
|
60
|
+
<span class="text-xs text-muted-foreground bg-muted px-2 py-1 rounded-md">
|
|
61
|
+
Restored to checkpoint: {restoredTo()}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
<Separator />
|
|
66
|
+
|
|
67
|
+
<ChatContainer class="flex-1 p-4">
|
|
68
|
+
<ChatContainerContent class="space-y-6 py-4">
|
|
69
|
+
|
|
70
|
+
{/* Initial exchange */}
|
|
71
|
+
<Message>
|
|
72
|
+
<MessageAvatar src="" fallback="U" alt="User" />
|
|
73
|
+
<MessageContent>
|
|
74
|
+
I'm designing a REST API for a task management app. What should the core endpoints look like?
|
|
75
|
+
</MessageContent>
|
|
76
|
+
</Message>
|
|
77
|
+
|
|
78
|
+
<Message>
|
|
79
|
+
<MessageAvatar src="" fallback="AI" alt="Assistant" />
|
|
80
|
+
<div class="flex-1 space-y-2">
|
|
81
|
+
<MessageContent markdown>
|
|
82
|
+
{`Here's a clean REST resource design for task management:
|
|
83
|
+
|
|
84
|
+
\`\`\`
|
|
85
|
+
GET /api/tasks # List tasks (with filters)
|
|
86
|
+
POST /api/tasks # Create task
|
|
87
|
+
GET /api/tasks/:id # Get single task
|
|
88
|
+
PATCH /api/tasks/:id # Update task
|
|
89
|
+
DELETE /api/tasks/:id # Delete task
|
|
90
|
+
|
|
91
|
+
GET /api/projects # List projects
|
|
92
|
+
POST /api/projects # Create project
|
|
93
|
+
GET /api/projects/:id/tasks # Tasks within a project
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
Key design decisions:
|
|
97
|
+
- Use **PATCH** over PUT for partial updates
|
|
98
|
+
- Nest tasks under projects for scoped queries
|
|
99
|
+
- Support filtering via query params: \`?status=active&assignee=me\``}
|
|
100
|
+
</MessageContent>
|
|
101
|
+
<MessageActions>
|
|
102
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
103
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
104
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
105
|
+
</MessageActions>
|
|
106
|
+
</div>
|
|
107
|
+
</Message>
|
|
108
|
+
|
|
109
|
+
{/* First checkpoint */}
|
|
110
|
+
<Checkpoint>
|
|
111
|
+
<CheckpointIcon />
|
|
112
|
+
<CheckpointTrigger
|
|
113
|
+
tooltip="Restore to this point"
|
|
114
|
+
onClick={() => setRestoredTo('API structure defined')}
|
|
115
|
+
>
|
|
116
|
+
<div class="flex items-center gap-1.5 text-xs">
|
|
117
|
+
<RotateCcw class="size-3" />
|
|
118
|
+
<span>API structure defined</span>
|
|
119
|
+
</div>
|
|
120
|
+
</CheckpointTrigger>
|
|
121
|
+
</Checkpoint>
|
|
122
|
+
|
|
123
|
+
{/* Second exchange */}
|
|
124
|
+
<Message>
|
|
125
|
+
<MessageAvatar src="" fallback="U" alt="User" />
|
|
126
|
+
<MessageContent>
|
|
127
|
+
What about authentication? Should I use JWT or session cookies?
|
|
128
|
+
</MessageContent>
|
|
129
|
+
</Message>
|
|
130
|
+
|
|
131
|
+
<Message>
|
|
132
|
+
<MessageAvatar src="" fallback="AI" alt="Assistant" />
|
|
133
|
+
<div class="flex-1 space-y-2">
|
|
134
|
+
<MessageContent markdown>
|
|
135
|
+
{`For a task management API, I'd recommend **JWT with refresh tokens**:
|
|
136
|
+
|
|
137
|
+
\`\`\`typescript
|
|
138
|
+
// Auth endpoints
|
|
139
|
+
POST /api/auth/login // Returns access + refresh tokens
|
|
140
|
+
POST /api/auth/refresh // Exchanges refresh for new access token
|
|
141
|
+
POST /api/auth/logout // Invalidates refresh token
|
|
142
|
+
|
|
143
|
+
// Middleware
|
|
144
|
+
function authMiddleware(req, res, next) {
|
|
145
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
146
|
+
try {
|
|
147
|
+
req.user = jwt.verify(token, SECRET);
|
|
148
|
+
next();
|
|
149
|
+
} catch {
|
|
150
|
+
res.status(401).json({ error: 'Invalid token' });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
**Why JWT over sessions:**
|
|
156
|
+
- Stateless -- no server-side session store needed
|
|
157
|
+
- Works across subdomains and mobile apps
|
|
158
|
+
- Access tokens expire in 15min, limiting damage from theft
|
|
159
|
+
|
|
160
|
+
**The tradeoff:** you can't instantly revoke JWTs. Mitigate this with short expiry + a blocklist for critical cases (password change, account compromise).`}
|
|
161
|
+
</MessageContent>
|
|
162
|
+
<MessageActions>
|
|
163
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
164
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
165
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
166
|
+
</MessageActions>
|
|
167
|
+
</div>
|
|
168
|
+
</Message>
|
|
169
|
+
|
|
170
|
+
{/* Second checkpoint */}
|
|
171
|
+
<Checkpoint>
|
|
172
|
+
<CheckpointIcon />
|
|
173
|
+
<CheckpointTrigger
|
|
174
|
+
tooltip="Restore to this point"
|
|
175
|
+
onClick={() => setRestoredTo('Auth strategy chosen')}
|
|
176
|
+
>
|
|
177
|
+
<div class="flex items-center gap-1.5 text-xs">
|
|
178
|
+
<RotateCcw class="size-3" />
|
|
179
|
+
<span>Auth strategy chosen</span>
|
|
180
|
+
</div>
|
|
181
|
+
</CheckpointTrigger>
|
|
182
|
+
</Checkpoint>
|
|
183
|
+
|
|
184
|
+
{/* Third exchange after checkpoint */}
|
|
185
|
+
<Message>
|
|
186
|
+
<MessageAvatar src="" fallback="U" alt="User" />
|
|
187
|
+
<MessageContent>
|
|
188
|
+
Now help me think about error handling. What's a good error response format?
|
|
189
|
+
</MessageContent>
|
|
190
|
+
</Message>
|
|
191
|
+
|
|
192
|
+
<Message>
|
|
193
|
+
<MessageAvatar src="" fallback="AI" alt="Assistant" />
|
|
194
|
+
<div class="flex-1 space-y-2">
|
|
195
|
+
<MessageContent markdown>
|
|
196
|
+
{`Use a consistent error envelope inspired by the **RFC 7807 Problem Details** spec:
|
|
197
|
+
|
|
198
|
+
\`\`\`typescript
|
|
199
|
+
interface ApiError {
|
|
200
|
+
status: number; // HTTP status code
|
|
201
|
+
code: string; // Machine-readable error code
|
|
202
|
+
message: string; // Human-readable description
|
|
203
|
+
details?: unknown[]; // Validation errors, field-level info
|
|
204
|
+
requestId: string; // For debugging/support
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Example responses
|
|
208
|
+
// 400 Bad Request
|
|
209
|
+
{
|
|
210
|
+
"status": 400,
|
|
211
|
+
"code": "VALIDATION_ERROR",
|
|
212
|
+
"message": "Invalid task data",
|
|
213
|
+
"details": [
|
|
214
|
+
{ "field": "title", "error": "Required field missing" },
|
|
215
|
+
{ "field": "dueDate", "error": "Must be a future date" }
|
|
216
|
+
],
|
|
217
|
+
"requestId": "req_abc123"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 404 Not Found
|
|
221
|
+
{
|
|
222
|
+
"status": 404,
|
|
223
|
+
"code": "RESOURCE_NOT_FOUND",
|
|
224
|
+
"message": "Task not found",
|
|
225
|
+
"requestId": "req_def456"
|
|
226
|
+
}
|
|
227
|
+
\`\`\`
|
|
228
|
+
|
|
229
|
+
Always include the \`requestId\` -- it's invaluable for correlating client errors with server logs.`}
|
|
230
|
+
</MessageContent>
|
|
231
|
+
<MessageActions>
|
|
232
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
233
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
234
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
235
|
+
</MessageActions>
|
|
236
|
+
</div>
|
|
237
|
+
</Message>
|
|
238
|
+
|
|
239
|
+
<ChatContainerScrollAnchor />
|
|
240
|
+
</ChatContainerContent>
|
|
241
|
+
</ChatContainer>
|
|
242
|
+
|
|
243
|
+
<div class="px-4 pb-4">
|
|
244
|
+
<PromptInput>
|
|
245
|
+
<PromptInputTextarea placeholder="Continue the API design..." />
|
|
246
|
+
<PromptInputActions class="justify-end">
|
|
247
|
+
<Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
|
|
248
|
+
<ArrowUp class="size-4" />
|
|
249
|
+
</Button>
|
|
250
|
+
</PromptInputActions>
|
|
251
|
+
</PromptInput>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
},
|
|
256
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import {
|
|
3
|
+
Context, ContextTrigger, ContextContent,
|
|
4
|
+
ContextContentHeader, ContextContentBody, ContextContentFooter,
|
|
5
|
+
ContextInputUsage, ContextOutputUsage, ContextReasoningUsage, ContextCacheUsage,
|
|
6
|
+
ModelSwitcher, Button, Separator,
|
|
7
|
+
} from '../index';
|
|
8
|
+
import type { ModelOption } from '../types';
|
|
9
|
+
import { createSignal } from 'solid-js';
|
|
10
|
+
|
|
11
|
+
const meta: Meta = {
|
|
12
|
+
title: 'Examples/Context & Token Usage',
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'padded',
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component: [
|
|
18
|
+
'Show how much of the model\'s context window is consumed. `<kai-context>` (or its SolidJS `Context` primitive) takes token counts and renders a circular meter trigger + hover-card breakdown.',
|
|
19
|
+
'',
|
|
20
|
+
'**Color thresholds** are configurable via `warnThreshold` (default `0.7`) and `dangerThreshold` (default `0.9`) props on both `<Context>` and `<kai-context>`:',
|
|
21
|
+
'- Green (`bg-primary`): `usedTokens / maxTokens` ≤ `warnThreshold`',
|
|
22
|
+
'- Yellow (`bg-yellow-400`): above `warnThreshold`',
|
|
23
|
+
'- Red (`bg-red-400`): above `dangerThreshold`',
|
|
24
|
+
'',
|
|
25
|
+
'When the computed severity level changes, `<kai-context>` fires a **`kai-threshold-change`** CustomEvent with `detail.level` set to `\'ok\'`, `\'warn\'`, or `\'danger\'`.',
|
|
26
|
+
'',
|
|
27
|
+
'**Where token counts come from:** read them from the API response `usage` object (`input_tokens`, `output_tokens`, `cache_read_input_tokens`, etc.) after each turn and pass them as props. The element fires no data-fetching events.',
|
|
28
|
+
'',
|
|
29
|
+
'**Live-update pattern:** for SolidJS, drive the `Context` props from reactive signals that you update after each streaming response completes. The element re-renders automatically when props change.',
|
|
30
|
+
'',
|
|
31
|
+
'**`<kai-context>` web-component route:** pass a single `context` object as a **property** (not an attribute — attributes only accept strings). Set `warnThreshold` / `dangerThreshold` as numeric properties to override defaults. The WC renders the full trigger + popover for you with no composition needed.',
|
|
32
|
+
].join('\n'),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj;
|
|
40
|
+
|
|
41
|
+
export const LowUsage: Story = {
|
|
42
|
+
name: 'Low Usage (Green)',
|
|
43
|
+
parameters: {
|
|
44
|
+
docs: {
|
|
45
|
+
description: {
|
|
46
|
+
story: 'Early in a conversation. `usedTokens / maxTokens` ≤ `warnThreshold` (default 70%) → green meter. Only input + output rows shown; omit `reasoningTokens` / `cacheTokens` to hide those rows.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
render: () => (
|
|
51
|
+
<div class="p-8">
|
|
52
|
+
<p class="text-sm text-muted-foreground mb-4">Early in a conversation -- minimal token usage.</p>
|
|
53
|
+
<Context usedTokens={4200} maxTokens={200000} inputTokens={2800} outputTokens={1400} estimatedCost={0.012}>
|
|
54
|
+
<ContextTrigger />
|
|
55
|
+
<ContextContent>
|
|
56
|
+
<ContextContentHeader />
|
|
57
|
+
<ContextContentBody>
|
|
58
|
+
<div class="space-y-1.5">
|
|
59
|
+
<ContextInputUsage />
|
|
60
|
+
<ContextOutputUsage />
|
|
61
|
+
</div>
|
|
62
|
+
</ContextContentBody>
|
|
63
|
+
<ContextContentFooter />
|
|
64
|
+
</ContextContent>
|
|
65
|
+
</Context>
|
|
66
|
+
</div>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const MediumUsage: Story = {
|
|
71
|
+
name: 'Medium Usage (Yellow)',
|
|
72
|
+
parameters: {
|
|
73
|
+
docs: {
|
|
74
|
+
description: {
|
|
75
|
+
story: '`usedTokens / maxTokens` > `warnThreshold` (default 70%) → yellow meter. Add `reasoningTokens` to surface a reasoning row in the breakdown. Pass `warnThreshold` to `<Context>` / `<kai-context>` to override the 70% default.',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
render: () => (
|
|
80
|
+
<div class="p-8">
|
|
81
|
+
<p class="text-sm text-muted-foreground mb-4">Extended conversation with reasoning -- approaching 75% usage.</p>
|
|
82
|
+
<Context usedTokens={150000} maxTokens={200000} inputTokens={85000} outputTokens={42000} reasoningTokens={23000} estimatedCost={0.89}>
|
|
83
|
+
<ContextTrigger />
|
|
84
|
+
<ContextContent>
|
|
85
|
+
<ContextContentHeader />
|
|
86
|
+
<ContextContentBody>
|
|
87
|
+
<div class="space-y-1.5">
|
|
88
|
+
<ContextInputUsage />
|
|
89
|
+
<ContextOutputUsage />
|
|
90
|
+
<ContextReasoningUsage />
|
|
91
|
+
</div>
|
|
92
|
+
</ContextContentBody>
|
|
93
|
+
<ContextContentFooter />
|
|
94
|
+
</ContextContent>
|
|
95
|
+
</Context>
|
|
96
|
+
</div>
|
|
97
|
+
),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const HighUsage: Story = {
|
|
101
|
+
name: 'High Usage (Red)',
|
|
102
|
+
parameters: {
|
|
103
|
+
docs: {
|
|
104
|
+
description: {
|
|
105
|
+
story: '`usedTokens / maxTokens` > `dangerThreshold` (default 90%) → red meter. Pass `dangerThreshold` to `<Context>` / `<kai-context>` to override the 90% default. Signal to the user to start a new conversation.',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
render: () => (
|
|
110
|
+
<div class="p-8">
|
|
111
|
+
<p class="text-sm text-muted-foreground mb-4">Near the context limit -- user should consider starting a new conversation.</p>
|
|
112
|
+
<Context usedTokens={189000} maxTokens={200000} inputTokens={110000} outputTokens={54000} reasoningTokens={25000} estimatedCost={1.42}>
|
|
113
|
+
<ContextTrigger />
|
|
114
|
+
<ContextContent>
|
|
115
|
+
<ContextContentHeader />
|
|
116
|
+
<ContextContentBody>
|
|
117
|
+
<div class="space-y-1.5">
|
|
118
|
+
<ContextInputUsage />
|
|
119
|
+
<ContextOutputUsage />
|
|
120
|
+
<ContextReasoningUsage />
|
|
121
|
+
</div>
|
|
122
|
+
</ContextContentBody>
|
|
123
|
+
<ContextContentFooter />
|
|
124
|
+
</ContextContent>
|
|
125
|
+
</Context>
|
|
126
|
+
</div>
|
|
127
|
+
),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const WithCacheBreakdown: Story = {
|
|
131
|
+
name: 'Full Breakdown with Cache',
|
|
132
|
+
parameters: {
|
|
133
|
+
docs: {
|
|
134
|
+
description: {
|
|
135
|
+
story: 'Add `cacheTokens` to expose the cache row. From the API: cache-hit tokens come from `usage.cache_read_input_tokens` in the response; cache-write tokens from `usage.cache_creation_input_tokens`. Both count toward `usedTokens`.',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
render: () => (
|
|
140
|
+
<div class="p-8">
|
|
141
|
+
<p class="text-sm text-muted-foreground mb-4">Detailed usage including cache hit tokens.</p>
|
|
142
|
+
<Context usedTokens={82000} maxTokens={200000} inputTokens={45000} outputTokens={22000} reasoningTokens={15000} cacheTokens={32000} estimatedCost={0.38}>
|
|
143
|
+
<ContextTrigger />
|
|
144
|
+
<ContextContent>
|
|
145
|
+
<ContextContentHeader />
|
|
146
|
+
<ContextContentBody>
|
|
147
|
+
<div class="space-y-1.5">
|
|
148
|
+
<ContextInputUsage />
|
|
149
|
+
<ContextOutputUsage />
|
|
150
|
+
<ContextReasoningUsage />
|
|
151
|
+
<ContextCacheUsage />
|
|
152
|
+
</div>
|
|
153
|
+
</ContextContentBody>
|
|
154
|
+
<ContextContentFooter />
|
|
155
|
+
</ContextContent>
|
|
156
|
+
</Context>
|
|
157
|
+
</div>
|
|
158
|
+
),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const InHeaderBar: Story = {
|
|
162
|
+
name: 'In a Header Bar',
|
|
163
|
+
parameters: {
|
|
164
|
+
docs: {
|
|
165
|
+
description: {
|
|
166
|
+
story: '`Context` (or `<kai-context>`) is a plain inline element — place it anywhere in your header. Here it sits next to a `ModelSwitcher`. Drive both from reactive signals that you update after each API response.',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
render: () => {
|
|
171
|
+
const [modelId, setModelId] = createSignal('claude-4');
|
|
172
|
+
const models: ModelOption[] = [
|
|
173
|
+
{ id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
|
|
174
|
+
{ id: 'claude-4-sonnet', name: 'Claude 4 Sonnet', provider: 'Anthropic' },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div class="w-full max-w-3xl">
|
|
179
|
+
<p class="text-sm text-muted-foreground mb-4 px-4">Context usage as it appears in the app header, alongside model switcher.</p>
|
|
180
|
+
|
|
181
|
+
<div class="bg-background rounded-xl shadow-lg overflow-hidden">
|
|
182
|
+
<div class="flex items-center justify-between px-4 py-2.5">
|
|
183
|
+
<div class="flex items-center gap-3">
|
|
184
|
+
<h2 class="text-sm font-semibold text-foreground">Database indexing strategies</h2>
|
|
185
|
+
<span class="text-xs text-muted-foreground">24 messages</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="flex items-center gap-2">
|
|
188
|
+
<ModelSwitcher models={models} currentModelId={modelId()} onModelChange={setModelId} />
|
|
189
|
+
<Context usedTokens={67000} maxTokens={200000} inputTokens={38000} outputTokens={29000} estimatedCost={0.31}>
|
|
190
|
+
<ContextTrigger />
|
|
191
|
+
<ContextContent>
|
|
192
|
+
<ContextContentHeader />
|
|
193
|
+
<ContextContentBody>
|
|
194
|
+
<div class="space-y-1.5">
|
|
195
|
+
<ContextInputUsage />
|
|
196
|
+
<ContextOutputUsage />
|
|
197
|
+
</div>
|
|
198
|
+
</ContextContentBody>
|
|
199
|
+
<ContextContentFooter />
|
|
200
|
+
</ContextContent>
|
|
201
|
+
</Context>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<Separator />
|
|
205
|
+
<div class="h-48 flex items-center justify-center text-muted-foreground text-sm">
|
|
206
|
+
Chat content area
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
},
|
|
212
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import {
|
|
3
|
+
ChatContainer, ChatContainerContent, ChatContainerScrollAnchor,
|
|
4
|
+
Message, MessageAvatar, MessageContent, MessageActions,
|
|
5
|
+
PromptInput, PromptInputTextarea, PromptInputActions,
|
|
6
|
+
ChainOfThought, ChainOfThoughtStep, ChainOfThoughtTrigger, ChainOfThoughtContent, ChainOfThoughtItem,
|
|
7
|
+
Button, Separator,
|
|
8
|
+
} from '../index';
|
|
9
|
+
import { Copy, ThumbsUp, ThumbsDown, ArrowUp, Lightbulb, Search, Calculator } from 'lucide-solid';
|
|
10
|
+
|
|
11
|
+
const meta: Meta = {
|
|
12
|
+
title: 'Examples/Conversation with Reasoning',
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'padded',
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component: [
|
|
18
|
+
'Show a model\'s chain-of-thought trace above its answer. Two elements cover this:',
|
|
19
|
+
'',
|
|
20
|
+
'**`<kai-chain-of-thought>`** — the flat, data-driven route. Pass a `steps` array of `{ label, content }` objects as a **property** (not an attribute — attributes only accept strings). The element renders collapsible steps with connector lines automatically.',
|
|
21
|
+
'',
|
|
22
|
+
'**Gotcha — no per-step icons in the element:** the `steps` shape has no `icon` field. The Search/Calculator/Lightbulb icons in the live demo are `ChainOfThoughtTrigger leftIcon` props, a SolidJS-only touch. If you need branded step icons, compose the SolidJS primitives directly (see the Solid tab).',
|
|
23
|
+
'',
|
|
24
|
+
'**`<kai-reasoning>`** — a single collapsible reasoning block (text + label). Better for raw LLM thinking output: pass `text` as a property, add `streaming` while the stream is live (auto-expands then re-collapses), and `markdown` (defaults to `true`). It fires `kai-open-change: { open }` when toggled.',
|
|
25
|
+
'',
|
|
26
|
+
'The live demo composes the SolidJS `ChainOfThought` + step primitives for full icon control.',
|
|
27
|
+
].join('\n'),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
name: 'Reasoning + Answer',
|
|
38
|
+
parameters: {
|
|
39
|
+
docs: {
|
|
40
|
+
description: {
|
|
41
|
+
story: [
|
|
42
|
+
'Four-step chain-of-thought trace above a technical answer. Key points:',
|
|
43
|
+
'',
|
|
44
|
+
'- The `ChainOfThought` steps here use `leftIcon` (Search/Calculator/Lightbulb) — a **SolidJS-only** prop on `ChainOfThoughtTrigger`. The `<kai-chain-of-thought>` element\'s `steps` array has no icon field.',
|
|
45
|
+
'- Each step is independently collapsible — `ChainOfThoughtStep isLast` removes the trailing connector line.',
|
|
46
|
+
'- The copy/like/dislike bar is a separate `MessageActions` composition; it is not part of the chain-of-thought model.',
|
|
47
|
+
].join('\n'),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
render: () => (
|
|
52
|
+
<div class="flex flex-col h-[700px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
|
|
53
|
+
<div class="flex items-center px-4 py-3">
|
|
54
|
+
<h2 class="text-sm font-semibold text-foreground">Architecture Decision</h2>
|
|
55
|
+
</div>
|
|
56
|
+
<Separator />
|
|
57
|
+
|
|
58
|
+
<ChatContainer class="flex-1 p-4">
|
|
59
|
+
<ChatContainerContent class="space-y-6 py-4">
|
|
60
|
+
|
|
61
|
+
<Message>
|
|
62
|
+
<MessageAvatar src="" fallback="U" alt="User" />
|
|
63
|
+
<MessageContent>
|
|
64
|
+
I have a Chrome extension that needs to sync user data across devices. Should I use Chrome's built-in storage.sync API or implement my own sync layer with a backend? The data is about 50KB per user, updated frequently (every few minutes), and includes nested objects with arrays.
|
|
65
|
+
</MessageContent>
|
|
66
|
+
</Message>
|
|
67
|
+
|
|
68
|
+
<Message>
|
|
69
|
+
<MessageAvatar src="" fallback="AI" alt="Assistant" />
|
|
70
|
+
<div class="flex-1 space-y-3">
|
|
71
|
+
|
|
72
|
+
{/* Chain of thought reasoning */}
|
|
73
|
+
<ChainOfThought>
|
|
74
|
+
<ChainOfThoughtStep>
|
|
75
|
+
<ChainOfThoughtTrigger leftIcon={<Search class="size-3.5" />}>
|
|
76
|
+
Analyzing data size and sync limits
|
|
77
|
+
</ChainOfThoughtTrigger>
|
|
78
|
+
<ChainOfThoughtContent>
|
|
79
|
+
<ChainOfThoughtItem>
|
|
80
|
+
chrome.storage.sync has a total limit of 102,400 bytes and 512 items max. At 50KB per user, that's within the total limit but leaves little headroom. Each item is capped at 8,192 bytes, so nested objects would need splitting. Write operations are throttled to 120/minute in normal mode, 1,800/hour.
|
|
81
|
+
</ChainOfThoughtItem>
|
|
82
|
+
</ChainOfThoughtContent>
|
|
83
|
+
</ChainOfThoughtStep>
|
|
84
|
+
|
|
85
|
+
<ChainOfThoughtStep>
|
|
86
|
+
<ChainOfThoughtTrigger leftIcon={<Calculator class="size-3.5" />}>
|
|
87
|
+
Evaluating update frequency constraints
|
|
88
|
+
</ChainOfThoughtTrigger>
|
|
89
|
+
<ChainOfThoughtContent>
|
|
90
|
+
<ChainOfThoughtItem>
|
|
91
|
+
"Updated every few minutes" means roughly 20-30 writes per hour. That's well within the 1,800/hour limit. But if the data has nested objects with arrays, each update might need to write multiple storage items (due to the 8KB per-item limit), multiplying the write count. 50KB / 8KB = ~7 items minimum, so 7 * 30 = 210 writes/hour. Still within limits.
|
|
92
|
+
</ChainOfThoughtItem>
|
|
93
|
+
</ChainOfThoughtContent>
|
|
94
|
+
</ChainOfThoughtStep>
|
|
95
|
+
|
|
96
|
+
<ChainOfThoughtStep>
|
|
97
|
+
<ChainOfThoughtTrigger leftIcon={<Lightbulb class="size-3.5" />}>
|
|
98
|
+
Considering conflict resolution
|
|
99
|
+
</ChainOfThoughtTrigger>
|
|
100
|
+
<ChainOfThoughtContent>
|
|
101
|
+
<ChainOfThoughtItem>
|
|
102
|
+
Chrome storage.sync uses last-write-wins. For nested objects updated from multiple devices simultaneously, this could cause data loss. If a user has the extension open on laptop and desktop, and both modify different array elements, the last write overwrites the other's changes. A custom backend could use CRDTs or operational transforms for proper merging.
|
|
103
|
+
</ChainOfThoughtItem>
|
|
104
|
+
</ChainOfThoughtContent>
|
|
105
|
+
</ChainOfThoughtStep>
|
|
106
|
+
|
|
107
|
+
<ChainOfThoughtStep isLast>
|
|
108
|
+
<ChainOfThoughtTrigger leftIcon={<Lightbulb class="size-3.5" />}>
|
|
109
|
+
Weighing implementation complexity
|
|
110
|
+
</ChainOfThoughtTrigger>
|
|
111
|
+
<ChainOfThoughtContent>
|
|
112
|
+
<ChainOfThoughtItem>
|
|
113
|
+
Custom backend means: server costs, auth, API design, offline handling, retry logic, and maintenance. Chrome storage.sync is free, built-in, handles offline automatically, and requires zero infrastructure. For a personal tool, the operational burden of a backend is significant.
|
|
114
|
+
</ChainOfThoughtItem>
|
|
115
|
+
</ChainOfThoughtContent>
|
|
116
|
+
</ChainOfThoughtStep>
|
|
117
|
+
</ChainOfThought>
|
|
118
|
+
|
|
119
|
+
{/* Final answer */}
|
|
120
|
+
<MessageContent markdown>
|
|
121
|
+
{`Given your constraints, I'd recommend a **hybrid approach**:
|
|
122
|
+
|
|
123
|
+
### Use \`chrome.storage.sync\` as primary, with a chunking strategy
|
|
124
|
+
|
|
125
|
+
1. **Split your 50KB into logical chunks** -- group related data into objects under 8KB each. Use a manifest key that tracks which chunks exist and their versions.
|
|
126
|
+
|
|
127
|
+
2. **Implement delta syncing** -- instead of writing the full dataset every time, only write the chunks that changed. This keeps you well under rate limits.
|
|
128
|
+
|
|
129
|
+
3. **Add a simple conflict guard** -- store a \`lastModifiedBy\` device ID and timestamp in each chunk. On read, if you detect a conflict, merge arrays by union rather than overwrite.
|
|
130
|
+
|
|
131
|
+
\`\`\`typescript
|
|
132
|
+
interface SyncChunk {
|
|
133
|
+
version: number;
|
|
134
|
+
deviceId: string;
|
|
135
|
+
updatedAt: number;
|
|
136
|
+
data: Record<string, unknown>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Write only changed chunks
|
|
140
|
+
async function syncChunks(changes: Map<string, SyncChunk>) {
|
|
141
|
+
const batch: Record<string, SyncChunk> = {};
|
|
142
|
+
for (const [key, chunk] of changes) {
|
|
143
|
+
batch[\`chunk_\${key}\`] = { ...chunk, version: chunk.version + 1 };
|
|
144
|
+
}
|
|
145
|
+
await chrome.storage.sync.set(batch);
|
|
146
|
+
}
|
|
147
|
+
\`\`\`
|
|
148
|
+
|
|
149
|
+
### When to upgrade to a backend
|
|
150
|
+
|
|
151
|
+
Move to a custom sync layer if:
|
|
152
|
+
- Data grows beyond **80KB** (hitting limits)
|
|
153
|
+
- You need **real-time collaboration** (multiple devices editing simultaneously)
|
|
154
|
+
- You want **version history** or undo across devices
|
|
155
|
+
- You add team/sharing features`}
|
|
156
|
+
</MessageContent>
|
|
157
|
+
|
|
158
|
+
<MessageActions>
|
|
159
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
160
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
161
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
162
|
+
</MessageActions>
|
|
163
|
+
</div>
|
|
164
|
+
</Message>
|
|
165
|
+
|
|
166
|
+
<ChatContainerScrollAnchor />
|
|
167
|
+
</ChatContainerContent>
|
|
168
|
+
</ChatContainer>
|
|
169
|
+
|
|
170
|
+
<div class="px-4 pb-4">
|
|
171
|
+
<PromptInput>
|
|
172
|
+
<PromptInputTextarea placeholder="Ask a follow-up..." />
|
|
173
|
+
<PromptInputActions class="justify-end">
|
|
174
|
+
<Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
|
|
175
|
+
<ArrowUp class="size-4" />
|
|
176
|
+
</Button>
|
|
177
|
+
</PromptInputActions>
|
|
178
|
+
</PromptInput>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
),
|
|
182
|
+
};
|