@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,333 @@
|
|
|
1
|
+
import { Switch, Match, For, Show } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export type LoaderVariant =
|
|
5
|
+
| 'circular'
|
|
6
|
+
| 'classic'
|
|
7
|
+
| 'pulse'
|
|
8
|
+
| 'pulse-dot'
|
|
9
|
+
| 'dots'
|
|
10
|
+
| 'typing'
|
|
11
|
+
| 'wave'
|
|
12
|
+
| 'bars'
|
|
13
|
+
| 'terminal'
|
|
14
|
+
| 'text-blink'
|
|
15
|
+
| 'text-shimmer'
|
|
16
|
+
| 'loading-dots';
|
|
17
|
+
|
|
18
|
+
export type LoaderSize = 'sm' | 'md' | 'lg';
|
|
19
|
+
|
|
20
|
+
export interface LoaderProps {
|
|
21
|
+
variant?: LoaderVariant;
|
|
22
|
+
size?: LoaderSize;
|
|
23
|
+
text?: string;
|
|
24
|
+
class?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// --- CircularLoader ---
|
|
28
|
+
|
|
29
|
+
export function CircularLoader(props: { class?: string; size?: LoaderSize }) {
|
|
30
|
+
const size = () => props.size ?? 'md';
|
|
31
|
+
const sizeClasses = { sm: 'size-4', md: 'size-5', lg: 'size-6' };
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
class={cn(
|
|
35
|
+
'border-primary animate-spin rounded-full border-2 border-t-transparent',
|
|
36
|
+
sizeClasses[size()],
|
|
37
|
+
props.class
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
<span class="sr-only">Loading</span>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// --- ClassicLoader ---
|
|
46
|
+
|
|
47
|
+
export function ClassicLoader(props: { class?: string; size?: LoaderSize }) {
|
|
48
|
+
const size = () => props.size ?? 'md';
|
|
49
|
+
const sizeClasses = { sm: 'size-4', md: 'size-5', lg: 'size-6' };
|
|
50
|
+
const barSizes = {
|
|
51
|
+
sm: { height: '6px', width: '1.5px' },
|
|
52
|
+
md: { height: '8px', width: '2px' },
|
|
53
|
+
lg: { height: '10px', width: '2.5px' },
|
|
54
|
+
};
|
|
55
|
+
return (
|
|
56
|
+
<div class={cn('relative', sizeClasses[size()], props.class)}>
|
|
57
|
+
<div class="absolute h-full w-full">
|
|
58
|
+
<For each={Array.from({ length: 12 }, (_, i) => i)}>
|
|
59
|
+
{(i) => (
|
|
60
|
+
<div
|
|
61
|
+
class="bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full"
|
|
62
|
+
style={{
|
|
63
|
+
top: '0',
|
|
64
|
+
left: '50%',
|
|
65
|
+
'margin-left': size() === 'sm' ? '-0.75px' : size() === 'lg' ? '-1.25px' : '-1px',
|
|
66
|
+
'transform-origin': `${size() === 'sm' ? '0.75px' : size() === 'lg' ? '1.25px' : '1px'} ${size() === 'sm' ? '10px' : size() === 'lg' ? '14px' : '12px'}`,
|
|
67
|
+
transform: `rotate(${i * 30}deg)`,
|
|
68
|
+
opacity: 0,
|
|
69
|
+
'animation-delay': `${i * 0.1}s`,
|
|
70
|
+
height: barSizes[size()].height,
|
|
71
|
+
width: barSizes[size()].width,
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</For>
|
|
76
|
+
</div>
|
|
77
|
+
<span class="sr-only">Loading</span>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- PulseLoader ---
|
|
83
|
+
|
|
84
|
+
export function PulseLoader(props: { class?: string; size?: LoaderSize }) {
|
|
85
|
+
const size = () => props.size ?? 'md';
|
|
86
|
+
const sizeClasses = { sm: 'size-4', md: 'size-5', lg: 'size-6' };
|
|
87
|
+
return (
|
|
88
|
+
<div class={cn('relative', sizeClasses[size()], props.class)}>
|
|
89
|
+
<div class="border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2" />
|
|
90
|
+
<span class="sr-only">Loading</span>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- PulseDotLoader ---
|
|
96
|
+
|
|
97
|
+
export function PulseDotLoader(props: { class?: string; size?: LoaderSize }) {
|
|
98
|
+
const size = () => props.size ?? 'md';
|
|
99
|
+
const sizeClasses = { sm: 'size-1', md: 'size-2', lg: 'size-3' };
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
class={cn(
|
|
103
|
+
'bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full',
|
|
104
|
+
sizeClasses[size()],
|
|
105
|
+
props.class
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
<span class="sr-only">Loading</span>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- DotsLoader ---
|
|
114
|
+
|
|
115
|
+
export function DotsLoader(props: { class?: string; size?: LoaderSize }) {
|
|
116
|
+
const size = () => props.size ?? 'md';
|
|
117
|
+
const dotSizes = { sm: 'h-1.5 w-1.5', md: 'h-2 w-2', lg: 'h-2.5 w-2.5' };
|
|
118
|
+
const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
|
|
119
|
+
return (
|
|
120
|
+
<div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
|
|
121
|
+
<For each={[0, 1, 2]}>
|
|
122
|
+
{(i) => (
|
|
123
|
+
<div
|
|
124
|
+
class={cn(
|
|
125
|
+
'bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full',
|
|
126
|
+
dotSizes[size()]
|
|
127
|
+
)}
|
|
128
|
+
style={{ 'animation-delay': `${i * 160}ms` }}
|
|
129
|
+
/>
|
|
130
|
+
)}
|
|
131
|
+
</For>
|
|
132
|
+
<span class="sr-only">Loading</span>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- TypingLoader ---
|
|
138
|
+
|
|
139
|
+
export function TypingLoader(props: { class?: string; size?: LoaderSize }) {
|
|
140
|
+
const size = () => props.size ?? 'md';
|
|
141
|
+
const dotSizes = { sm: 'h-1 w-1', md: 'h-1.5 w-1.5', lg: 'h-2 w-2' };
|
|
142
|
+
const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
|
|
143
|
+
return (
|
|
144
|
+
<div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
|
|
145
|
+
<For each={[0, 1, 2]}>
|
|
146
|
+
{(i) => (
|
|
147
|
+
<div
|
|
148
|
+
class={cn(
|
|
149
|
+
'bg-primary animate-[typing_1s_infinite] rounded-full',
|
|
150
|
+
dotSizes[size()]
|
|
151
|
+
)}
|
|
152
|
+
style={{ 'animation-delay': `${i * 250}ms` }}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
</For>
|
|
156
|
+
<span class="sr-only">Loading</span>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- WaveLoader ---
|
|
162
|
+
|
|
163
|
+
export function WaveLoader(props: { class?: string; size?: LoaderSize }) {
|
|
164
|
+
const size = () => props.size ?? 'md';
|
|
165
|
+
const barWidths = { sm: 'w-0.5', md: 'w-0.5', lg: 'w-1' };
|
|
166
|
+
const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
|
|
167
|
+
const heights: Record<LoaderSize, string[]> = {
|
|
168
|
+
sm: ['6px', '9px', '12px', '9px', '6px'],
|
|
169
|
+
md: ['8px', '12px', '16px', '12px', '8px'],
|
|
170
|
+
lg: ['10px', '15px', '20px', '15px', '10px'],
|
|
171
|
+
};
|
|
172
|
+
return (
|
|
173
|
+
<div class={cn('flex items-center gap-0.5', containerSizes[size()], props.class)}>
|
|
174
|
+
<For each={[0, 1, 2, 3, 4]}>
|
|
175
|
+
{(i) => (
|
|
176
|
+
<div
|
|
177
|
+
class={cn(
|
|
178
|
+
'bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full',
|
|
179
|
+
barWidths[size()]
|
|
180
|
+
)}
|
|
181
|
+
style={{
|
|
182
|
+
'animation-delay': `${i * 100}ms`,
|
|
183
|
+
height: heights[size()][i],
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
</For>
|
|
188
|
+
<span class="sr-only">Loading</span>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- BarsLoader ---
|
|
194
|
+
|
|
195
|
+
export function BarsLoader(props: { class?: string; size?: LoaderSize }) {
|
|
196
|
+
const size = () => props.size ?? 'md';
|
|
197
|
+
const barWidths = { sm: 'w-1', md: 'w-1.5', lg: 'w-2' };
|
|
198
|
+
const containerSizes = { sm: 'h-4 gap-1', md: 'h-5 gap-1.5', lg: 'h-6 gap-2' };
|
|
199
|
+
return (
|
|
200
|
+
<div class={cn('flex', containerSizes[size()], props.class)}>
|
|
201
|
+
<For each={[0, 1, 2]}>
|
|
202
|
+
{(i) => (
|
|
203
|
+
<div
|
|
204
|
+
class={cn(
|
|
205
|
+
'bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]',
|
|
206
|
+
barWidths[size()]
|
|
207
|
+
)}
|
|
208
|
+
style={{ 'animation-delay': `${i * 0.2}s` }}
|
|
209
|
+
/>
|
|
210
|
+
)}
|
|
211
|
+
</For>
|
|
212
|
+
<span class="sr-only">Loading</span>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// --- TerminalLoader ---
|
|
218
|
+
|
|
219
|
+
export function TerminalLoader(props: { class?: string; size?: LoaderSize }) {
|
|
220
|
+
const size = () => props.size ?? 'md';
|
|
221
|
+
const cursorSizes = { sm: 'h-3 w-1.5', md: 'h-4 w-2', lg: 'h-5 w-2.5' };
|
|
222
|
+
const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
|
|
223
|
+
const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
|
|
224
|
+
return (
|
|
225
|
+
<div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
|
|
226
|
+
<span class={cn('text-primary font-mono', textSizes[size()])}>{'>'}</span>
|
|
227
|
+
<div class={cn('bg-primary animate-[blink_1s_step-end_infinite]', cursorSizes[size()])} />
|
|
228
|
+
<span class="sr-only">Loading</span>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// --- TextBlinkLoader ---
|
|
234
|
+
|
|
235
|
+
export function TextBlinkLoader(props: { text?: string; class?: string; size?: LoaderSize }) {
|
|
236
|
+
const size = () => props.size ?? 'md';
|
|
237
|
+
const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
|
|
238
|
+
return (
|
|
239
|
+
<div
|
|
240
|
+
class={cn(
|
|
241
|
+
'animate-[text-blink_2s_ease-in-out_infinite] font-medium',
|
|
242
|
+
textSizes[size()],
|
|
243
|
+
props.class
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
246
|
+
{props.text ?? 'Thinking'}
|
|
247
|
+
</div>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// --- TextShimmerLoader ---
|
|
252
|
+
|
|
253
|
+
export function TextShimmerLoader(props: { text?: string; class?: string; size?: LoaderSize }) {
|
|
254
|
+
const size = () => props.size ?? 'md';
|
|
255
|
+
const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
|
|
256
|
+
return (
|
|
257
|
+
<div
|
|
258
|
+
class={cn(
|
|
259
|
+
'bg-[linear-gradient(to_right,var(--color-muted-foreground)_40%,var(--color-foreground)_60%,var(--color-muted-foreground)_80%)]',
|
|
260
|
+
'bg-size-[200%_auto] bg-clip-text font-medium text-transparent',
|
|
261
|
+
'animate-[shimmer_4s_infinite_linear]',
|
|
262
|
+
textSizes[size()],
|
|
263
|
+
props.class
|
|
264
|
+
)}
|
|
265
|
+
>
|
|
266
|
+
{props.text ?? 'Thinking'}
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --- TextDotsLoader ---
|
|
272
|
+
|
|
273
|
+
export function TextDotsLoader(props: { text?: string; class?: string; size?: LoaderSize }) {
|
|
274
|
+
const size = () => props.size ?? 'md';
|
|
275
|
+
const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
|
|
276
|
+
return (
|
|
277
|
+
<div class={cn('inline-flex items-center', props.class)}>
|
|
278
|
+
<span class={cn('text-primary font-medium', textSizes[size()])}>
|
|
279
|
+
{props.text ?? 'Thinking'}
|
|
280
|
+
</span>
|
|
281
|
+
<span class="inline-flex">
|
|
282
|
+
<span class="text-primary animate-[loading-dots_1.4s_infinite_0.2s]">.</span>
|
|
283
|
+
<span class="text-primary animate-[loading-dots_1.4s_infinite_0.4s]">.</span>
|
|
284
|
+
<span class="text-primary animate-[loading-dots_1.4s_infinite_0.6s]">.</span>
|
|
285
|
+
</span>
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- Loader (variant dispatcher) ---
|
|
291
|
+
|
|
292
|
+
export function Loader(props: LoaderProps) {
|
|
293
|
+
return (
|
|
294
|
+
<Switch fallback={<CircularLoader size={props.size} class={props.class} />}>
|
|
295
|
+
<Match when={props.variant === 'circular'}>
|
|
296
|
+
<CircularLoader size={props.size} class={props.class} />
|
|
297
|
+
</Match>
|
|
298
|
+
<Match when={props.variant === 'classic'}>
|
|
299
|
+
<ClassicLoader size={props.size} class={props.class} />
|
|
300
|
+
</Match>
|
|
301
|
+
<Match when={props.variant === 'pulse'}>
|
|
302
|
+
<PulseLoader size={props.size} class={props.class} />
|
|
303
|
+
</Match>
|
|
304
|
+
<Match when={props.variant === 'pulse-dot'}>
|
|
305
|
+
<PulseDotLoader size={props.size} class={props.class} />
|
|
306
|
+
</Match>
|
|
307
|
+
<Match when={props.variant === 'dots'}>
|
|
308
|
+
<DotsLoader size={props.size} class={props.class} />
|
|
309
|
+
</Match>
|
|
310
|
+
<Match when={props.variant === 'typing'}>
|
|
311
|
+
<TypingLoader size={props.size} class={props.class} />
|
|
312
|
+
</Match>
|
|
313
|
+
<Match when={props.variant === 'wave'}>
|
|
314
|
+
<WaveLoader size={props.size} class={props.class} />
|
|
315
|
+
</Match>
|
|
316
|
+
<Match when={props.variant === 'bars'}>
|
|
317
|
+
<BarsLoader size={props.size} class={props.class} />
|
|
318
|
+
</Match>
|
|
319
|
+
<Match when={props.variant === 'terminal'}>
|
|
320
|
+
<TerminalLoader size={props.size} class={props.class} />
|
|
321
|
+
</Match>
|
|
322
|
+
<Match when={props.variant === 'text-blink'}>
|
|
323
|
+
<TextBlinkLoader text={props.text} size={props.size} class={props.class} />
|
|
324
|
+
</Match>
|
|
325
|
+
<Match when={props.variant === 'text-shimmer'}>
|
|
326
|
+
<TextShimmerLoader text={props.text} size={props.size} class={props.class} />
|
|
327
|
+
</Match>
|
|
328
|
+
<Match when={props.variant === 'loading-dots'}>
|
|
329
|
+
<TextDotsLoader text={props.text} size={props.size} class={props.class} />
|
|
330
|
+
</Match>
|
|
331
|
+
</Switch>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { Markdown } from './markdown';
|
|
3
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Solid (Advanced)/Elements/Markdown',
|
|
7
|
+
component: Markdown,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'padded',
|
|
11
|
+
docs: {
|
|
12
|
+
controls: { exclude: ['use:eventListener'] },
|
|
13
|
+
description: componentDescription([
|
|
14
|
+
'Renders a Markdown string to styled HTML (GFM enabled — tables, lists, blockquotes), splitting fenced code into highlighted `CodeBlock`s.',
|
|
15
|
+
'**When to use:** to display assistant message content, documentation, or any rich text authored in Markdown.',
|
|
16
|
+
'**How to use:** pass the Markdown string as `content`; optionally set `codeTheme` for code fences and `class` for the prose container. Prose sizing follows the surrounding `ChatConfig`.',
|
|
17
|
+
'**Placement:** inside `MessageContent`, response panels, or anywhere formatted text is shown.',
|
|
18
|
+
]),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
argTypes: {
|
|
22
|
+
content: {
|
|
23
|
+
control: 'text',
|
|
24
|
+
description: 'The Markdown source string to render.',
|
|
25
|
+
},
|
|
26
|
+
id: {
|
|
27
|
+
control: 'text',
|
|
28
|
+
description: 'Optional stable id used for block keys (auto-generated if omitted).',
|
|
29
|
+
},
|
|
30
|
+
codeTheme: {
|
|
31
|
+
control: 'text',
|
|
32
|
+
description: 'Shiki theme name applied to fenced code blocks (falls back to the chat config theme).',
|
|
33
|
+
},
|
|
34
|
+
class: {
|
|
35
|
+
control: 'text',
|
|
36
|
+
description: 'Additional CSS classes for the prose container.',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
args: {
|
|
40
|
+
content: 'This is a simple paragraph of text rendered through the **Markdown** component.',
|
|
41
|
+
},
|
|
42
|
+
render: (args) => <Markdown {...args} />,
|
|
43
|
+
} satisfies Meta<typeof Markdown>;
|
|
44
|
+
|
|
45
|
+
export default meta;
|
|
46
|
+
type Story = StoryObj<typeof meta>;
|
|
47
|
+
|
|
48
|
+
const IMPORT = `import { Markdown } from '@kitn.ai/ui';`;
|
|
49
|
+
const src = (code: string) => ({
|
|
50
|
+
parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** Interactive playground — edit the `content` control to render any Markdown. */
|
|
54
|
+
export const Playground: Story = {
|
|
55
|
+
...src(`<Markdown content="This is a simple paragraph of **Markdown** text." />`),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const PlainText: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
content: 'This is a simple paragraph of text rendered through the Markdown component.',
|
|
61
|
+
},
|
|
62
|
+
...src(`<Markdown content="This is a simple paragraph of text rendered through the Markdown component." />`),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Headings: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
content: `# Heading 1
|
|
68
|
+
## Heading 2
|
|
69
|
+
### Heading 3
|
|
70
|
+
#### Heading 4
|
|
71
|
+
|
|
72
|
+
Some text below the headings.`,
|
|
73
|
+
},
|
|
74
|
+
...src(`<Markdown content={\`# Heading 1
|
|
75
|
+
## Heading 2
|
|
76
|
+
### Heading 3
|
|
77
|
+
|
|
78
|
+
Some text below the headings.\`} />`),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const CodeBlocks: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
content: `Here is some inline \`code\` in a paragraph.
|
|
84
|
+
|
|
85
|
+
\`\`\`typescript
|
|
86
|
+
const x: number = 42;
|
|
87
|
+
console.log(x);
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
And another block:
|
|
91
|
+
|
|
92
|
+
\`\`\`python
|
|
93
|
+
print("hello world")
|
|
94
|
+
\`\`\``,
|
|
95
|
+
},
|
|
96
|
+
...src(`<Markdown content={\`Here is some inline \\\`code\\\` in a paragraph.
|
|
97
|
+
|
|
98
|
+
\\\`\\\`\\\`typescript
|
|
99
|
+
const x: number = 42;
|
|
100
|
+
console.log(x);
|
|
101
|
+
\\\`\\\`\\\`\`} />`),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const Lists: Story = {
|
|
105
|
+
args: {
|
|
106
|
+
content: `### Unordered List
|
|
107
|
+
- First item
|
|
108
|
+
- Second item
|
|
109
|
+
- Nested item
|
|
110
|
+
- Another nested
|
|
111
|
+
- Third item
|
|
112
|
+
|
|
113
|
+
### Ordered List
|
|
114
|
+
1. Step one
|
|
115
|
+
2. Step two
|
|
116
|
+
3. Step three`,
|
|
117
|
+
},
|
|
118
|
+
...src(`<Markdown content={\`### Unordered List
|
|
119
|
+
- First item
|
|
120
|
+
- Second item
|
|
121
|
+
- Nested item
|
|
122
|
+
|
|
123
|
+
### Ordered List
|
|
124
|
+
1. Step one
|
|
125
|
+
2. Step two\`} />`),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const GFMTable: Story = {
|
|
129
|
+
args: {
|
|
130
|
+
content: `### Comparison Table
|
|
131
|
+
|
|
132
|
+
| Feature | SolidJS | React | Svelte |
|
|
133
|
+
|---------|---------|-------|--------|
|
|
134
|
+
| Reactivity | Fine-grained | Virtual DOM | Compiler |
|
|
135
|
+
| Bundle Size | ~7KB | ~40KB | ~2KB |
|
|
136
|
+
| Performance | Excellent | Good | Excellent |
|
|
137
|
+
| Learning Curve | Moderate | Moderate | Easy |`,
|
|
138
|
+
},
|
|
139
|
+
...src(`<Markdown content={\`| Feature | SolidJS | React |
|
|
140
|
+
|---------|---------|-------|
|
|
141
|
+
| Reactivity | Fine-grained | Virtual DOM |
|
|
142
|
+
| Bundle Size | ~7KB | ~40KB |\`} />`),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const RichContent: Story = {
|
|
146
|
+
args: {
|
|
147
|
+
class: 'max-w-2xl',
|
|
148
|
+
content: `# Project Overview
|
|
149
|
+
|
|
150
|
+
This is a **comprehensive** guide to building modern web applications.
|
|
151
|
+
|
|
152
|
+
## Key Technologies
|
|
153
|
+
|
|
154
|
+
- **SolidJS** -- Reactive UI framework
|
|
155
|
+
- **TypeScript** -- Type-safe JavaScript
|
|
156
|
+
- **Tailwind CSS** -- Utility-first styling
|
|
157
|
+
|
|
158
|
+
## Getting Started
|
|
159
|
+
|
|
160
|
+
\`\`\`bash
|
|
161
|
+
pnpm create solid
|
|
162
|
+
cd my-app
|
|
163
|
+
pnpm install
|
|
164
|
+
pnpm dev
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
> **Note:** Make sure you have Node.js 18+ installed.
|
|
168
|
+
|
|
169
|
+
## Architecture
|
|
170
|
+
|
|
171
|
+
| Layer | Technology | Purpose |
|
|
172
|
+
|-------|-----------|---------|
|
|
173
|
+
| UI | SolidJS | Components |
|
|
174
|
+
| State | Signals | Reactivity |
|
|
175
|
+
| Styling | Tailwind | Design |
|
|
176
|
+
|
|
177
|
+
For more info, visit [solidjs.com](https://solidjs.com).`,
|
|
178
|
+
},
|
|
179
|
+
...src(`<Markdown class="max-w-2xl" content={richMarkdown} />`),
|
|
180
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { splitProps, createMemo, createUniqueId, For, Show, Switch, Match } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
import { CodeBlock, CodeBlockCode } from './code-block';
|
|
5
|
+
import { useChatConfig, textClass } from '../primitives/chat-config';
|
|
6
|
+
|
|
7
|
+
marked.setOptions({ gfm: true, breaks: true });
|
|
8
|
+
|
|
9
|
+
export interface MarkdownProps {
|
|
10
|
+
content: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
codeTheme?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ParsedBlock {
|
|
17
|
+
type: 'markdown' | 'code';
|
|
18
|
+
content: string;
|
|
19
|
+
language?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseMarkdownIntoBlocks(markdown: string): ParsedBlock[] {
|
|
23
|
+
const tokens = marked.lexer(markdown);
|
|
24
|
+
return tokens.map((token) => {
|
|
25
|
+
if (token.type === 'code') {
|
|
26
|
+
return {
|
|
27
|
+
type: 'code' as const,
|
|
28
|
+
content: token.text,
|
|
29
|
+
language: token.lang || undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
type: 'markdown' as const,
|
|
34
|
+
content: token.raw,
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function MarkdownBlock(props: { content: string }) {
|
|
40
|
+
const html = createMemo(() => {
|
|
41
|
+
try {
|
|
42
|
+
return marked.parse(props.content, { async: false }) as string;
|
|
43
|
+
} catch {
|
|
44
|
+
return props.content;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return <div innerHTML={html()} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Markdown(props: MarkdownProps) {
|
|
52
|
+
const [local] = splitProps(props, ['content', 'id', 'class', 'codeTheme']);
|
|
53
|
+
const config = useChatConfig();
|
|
54
|
+
const blockId = () => local.id ?? createUniqueId();
|
|
55
|
+
const blocks = createMemo(() => parseMarkdownIntoBlocks(local.content));
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div class={cn('chat-markdown max-w-none break-words whitespace-normal [&>div:first-child>p:first-child]:mt-0 [&>div:last-child>p:last-child]:mb-0', textClass(config.proseSize()), local.class)}>
|
|
59
|
+
<For each={blocks()}>
|
|
60
|
+
{(block) => (
|
|
61
|
+
<Switch>
|
|
62
|
+
<Match when={block.type === 'code'}>
|
|
63
|
+
<CodeBlock class="my-4">
|
|
64
|
+
<CodeBlockCode
|
|
65
|
+
code={block.content}
|
|
66
|
+
language={block.language}
|
|
67
|
+
theme={local.codeTheme ?? config.codeTheme()}
|
|
68
|
+
/>
|
|
69
|
+
</CodeBlock>
|
|
70
|
+
</Match>
|
|
71
|
+
<Match when={block.type === 'markdown'}>
|
|
72
|
+
<MarkdownBlock content={block.content} />
|
|
73
|
+
</Match>
|
|
74
|
+
</Switch>
|
|
75
|
+
)}
|
|
76
|
+
</For>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { Markdown };
|