@kitn.ai/ui 0.18.2 → 0.20.0
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 +6 -0
- package/NOTICE +54 -0
- package/dist/components/audio-visualizer/audio-visualizer.voice-fixture.d.ts +59 -0
- package/dist/components/audio-visualizer/aurora.glsl.d.ts +114 -0
- package/dist/components/audio-visualizer/index.d.ts +155 -0
- package/dist/components/audio-visualizer/shader-canvas.d.ts +130 -0
- package/dist/components/audio-visualizer/sizes.d.ts +25 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +89 -0
- package/dist/components/audio-visualizer/variant-bar.d.ts +46 -0
- package/dist/components/audio-visualizer/variant-custom.d.ts +73 -0
- package/dist/components/audio-visualizer/variant-grid.d.ts +22 -0
- package/dist/components/audio-visualizer/variant-radial.d.ts +13 -0
- package/dist/components/audio-visualizer/variant-wave.d.ts +11 -0
- package/dist/components/audio-visualizer/wave.glsl.d.ts +16 -0
- package/dist/components/thread.d.ts +54 -0
- package/dist/create-tween-hvpmSuwW.js +7 -0
- package/dist/custom-elements.json +477 -0
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.d.ts +1 -0
- package/dist/elements/audio-visualizer.js +1 -0
- package/dist/elements/autoloader.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-C2LTALdM.js → Icon-Tj58ES_d.js} +1 -1
- package/dist/elements/chunks/action-icons-ns1Fnedj.js +1 -0
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +1 -0
- package/dist/elements/chunks/{attachments-DLJcWCDV.js → attachments-mCB5F1Fq.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +1 -0
- package/dist/elements/chunks/{badge-BTNZTODL.js → badge-B4Zuajl2.js} +1 -1
- package/dist/elements/chunks/{button-DBDZebGr.js → button-ZWNif3--.js} +1 -1
- package/dist/elements/chunks/{card-YfCyI4fH.js → card-BkHbp4BA.js} +1 -1
- package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +1 -0
- package/dist/elements/chunks/check-B9-gn6SS.js +1 -0
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +1 -0
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +1 -0
- package/dist/elements/chunks/circle-0e3KJTDr.js +1 -0
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +1 -0
- package/dist/elements/chunks/{circle-x-pDtzlghQ.js → circle-x-B7faJFlY.js} +1 -1
- package/dist/elements/chunks/{code-block-DZ6mz6vs.js → code-block-BFUHn6Va.js} +1 -1
- package/dist/elements/chunks/collapsible-DM1Ckd4e.js +1 -0
- package/dist/elements/chunks/{composer-BZyPU6gD.js → composer-BguS0QL_.js} +7 -3
- package/dist/elements/chunks/{context-Cx6eQA_G.js → context-Cb7k4F-v.js} +1 -1
- package/dist/elements/chunks/{conversation-list-C4xTQSpT.js → conversation-list-CvoAkWhN.js} +1 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +7 -0
- package/dist/elements/chunks/{default-input-BZcwyKcP.js → default-input-Cqqilg9t.js} +1 -1
- package/dist/elements/chunks/define-D1din7Ld.js +1 -0
- package/dist/elements/chunks/{disclosure-BcBs--Jf.js → disclosure-CIZ5c8nO.js} +1 -1
- package/dist/elements/chunks/{download-BDQwcFav.js → download-Dsbg3ste.js} +1 -1
- package/dist/elements/chunks/{dropdown-wgs8xCJZ.js → dropdown-BDd1-Zgi.js} +1 -1
- package/dist/elements/chunks/{ellipsis-CUiEJ6R1.js → ellipsis-Dw6qrss-.js} +1 -1
- package/dist/elements/chunks/{external-link-DJUmC7g2.js → external-link-Vt74zAoL.js} +1 -1
- package/dist/elements/chunks/{file-text-DOYfYvrh.js → file-text-CZrZF6_v.js} +1 -1
- package/dist/elements/chunks/{file-tree-DQzNQrbr.js → file-tree-DF4okKyo.js} +1 -1
- package/dist/elements/chunks/{folder-DBDUBANz.js → folder-glP5rha4.js} +1 -1
- package/dist/elements/chunks/hover-card-Cmm6Q53m.js +1 -0
- package/dist/elements/chunks/icon-BLb8beoi.js +1 -0
- package/dist/elements/chunks/{info-DBQl-m6e.js → info-CIsv92JK.js} +1 -1
- package/dist/elements/chunks/{input-DZD67EvW.js → input-Dds1kFyt.js} +1 -1
- package/dist/elements/chunks/{kbd-DbyVrlnU.js → kbd-De_xbgi9.js} +1 -1
- package/dist/elements/chunks/{link-vko7AttA.js → link-i4r_k7z4.js} +1 -1
- package/dist/elements/chunks/{loader-Cg6rO8tQ.js → loader-BmQH4s3V.js} +1 -1
- package/dist/elements/chunks/{markdown-n3Xbj3PR.js → markdown-Zi21WhUz.js} +7 -7
- package/dist/elements/chunks/{message-CgKytiSB.js → message-BqyXDN3-.js} +1 -1
- package/dist/elements/chunks/{message-feedback-Ds3-US0e.js → message-feedback-Djr3z6Yb.js} +1 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +1 -0
- package/dist/elements/chunks/{minimize-2-3kCdE0q3.js → minimize-2--ifeCGrl.js} +1 -1
- package/dist/elements/chunks/{model-switcher-BMtXGwLU.js → model-switcher-B94YAKsM.js} +1 -1
- package/dist/elements/chunks/{overlay-B1-MBI6h.js → overlay-kD8dwRv_.js} +1 -1
- package/dist/elements/chunks/{paperclip-DDEfmkHc.js → paperclip-q21dYOSK.js} +1 -1
- package/dist/elements/chunks/{progress-bar-Dwbiyv3s.js → progress-bar-BrAIIaaJ.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-9lvgFcEa.js → prompt-suggestion-Bjn2K-fR.js} +1 -1
- package/dist/elements/chunks/{reasoning-DzHWsdAJ.js → reasoning-DTpCGtnY.js} +1 -1
- package/dist/elements/chunks/resizable-Dp4JExql.js +1 -0
- package/dist/elements/chunks/{rotate-cw-jpisOdOV.js → rotate-cw-CFWWkpj2.js} +1 -1
- package/dist/elements/chunks/{scroll-area-g5Ze3K6f.js → scroll-area-Bb0kHH1j.js} +1 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +1 -0
- package/dist/elements/chunks/{separator-Bmjc5XyX.js → separator-9kRgr1X9.js} +1 -1
- package/dist/elements/chunks/{settings-Cz1bMJP9.js → settings-B8lmec4D.js} +1 -1
- package/dist/elements/chunks/{settings-group-9imY3kRA.js → settings-group-I0hEuXiN.js} +1 -1
- package/dist/elements/chunks/{share-BdenF0wQ.js → share-C3lTiYGt.js} +1 -1
- package/dist/elements/chunks/skeleton-DSXW5Yya.js +1 -0
- package/dist/elements/chunks/{slots-DkZ-Cmha.js → slots--V19HwUb.js} +1 -1
- package/dist/elements/chunks/{star-DY6z1UUH.js → star-CSJtGxgv.js} +1 -1
- package/dist/elements/chunks/{store-th4VyJXM.js → store-CHyW95TP.js} +1 -1
- package/dist/elements/chunks/{text-shimmer-CEi-tlXr.js → text-shimmer-Cn9TmNpI.js} +1 -1
- package/dist/elements/chunks/textarea-CUwqAX0G.js +1 -0
- package/dist/elements/chunks/{thumbs-up-BR33KLfA.js → thumbs-up-PNVVThEF.js} +1 -1
- package/dist/elements/chunks/{toast-store-BEMda0fv.js → toast-store-DKbQn3Wn.js} +1 -1
- package/dist/elements/chunks/{tool-TMued3WO.js → tool-DjTRpIpl.js} +1 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +1 -0
- package/dist/elements/chunks/{triangle-alert-B07fX3Q8.js → triangle-alert-DsHTHCPm.js} +1 -1
- package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +1 -0
- package/dist/elements/chunks/variant-aurora-Dc4nQUia.js +218 -0
- package/dist/elements/chunks/variant-custom-y_3kVQhS.js +1 -0
- package/dist/elements/chunks/variant-wave-D5OR1-9Z.js +116 -0
- package/dist/elements/chunks/{video-MKp3LmNT.js → video-B7vmenoC.js} +1 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +1 -0
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/define.d.ts +11 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +1 -1
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slots.d.ts +15 -0
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.d.ts +1 -0
- package/dist/elements/thread.js +1 -0
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +62 -0
- package/dist/index.js +3411 -3328
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +70 -4
- package/dist/llms/llms.txt +3 -3
- package/dist/primitives/audio-bands.d.ts +106 -0
- package/dist/primitives/create-tween.d.ts +33 -0
- package/dist/primitives/use-audio-analysis.d.ts +149 -0
- package/dist/primitives/use-sequencer.d.ts +13 -0
- package/dist/primitives/use-voice-recorder.d.ts +1 -0
- package/dist/primitives/visualizer-sequences.d.ts +39 -0
- package/dist/react/index.d.ts +105 -0
- package/dist/react/use-voice-input.d.ts +15 -0
- package/dist/react.js +269 -240
- package/dist/register-impl-DQyejdfb.js +145 -0
- package/dist/test-utils/fake-clock.d.ts +45 -0
- package/dist/ui/resizable.d.ts +9 -3
- package/dist/variant-aurora-DVtGqVH6.js +218 -0
- package/dist/variant-custom-Dif-IV4u.js +1 -0
- package/dist/variant-wave-DbfNdvSi.js +116 -0
- package/frameworks/react/index.tsx +74 -1
- package/frameworks/react/use-voice-input.tsx +59 -0
- package/llms-full.txt +70 -4
- package/llms.txt +3 -3
- package/package.json +32 -7
- package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +257 -0
- package/src/components/audio-visualizer/aurora.glsl.ts +330 -0
- package/src/components/audio-visualizer/index.tsx +442 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +846 -0
- package/src/components/audio-visualizer/sizes.ts +54 -0
- package/src/components/audio-visualizer/variant-aurora.tsx +299 -0
- package/src/components/audio-visualizer/variant-bar.tsx +143 -0
- package/src/components/audio-visualizer/variant-custom.tsx +338 -0
- package/src/components/audio-visualizer/variant-grid.tsx +170 -0
- package/src/components/audio-visualizer/variant-radial.tsx +158 -0
- package/src/components/audio-visualizer/variant-wave.tsx +125 -0
- package/src/components/audio-visualizer/wave.glsl.ts +130 -0
- package/src/components/component-meta.json +34 -32
- package/src/components/composer.tsx +5 -0
- package/src/components/thread.tsx +177 -0
- package/src/elements/audio-visualizer.tsx +183 -0
- package/src/elements/chat-workspace.tsx +1 -1
- package/src/elements/compiled.css +1 -1
- package/src/elements/define.tsx +12 -1
- package/src/elements/element-manifest.json +8 -0
- package/src/elements/element-meta.json +287 -0
- package/src/elements/element-types.d.ts +62 -0
- package/src/elements/icon-names.json +2 -0
- package/src/elements/menu.tsx +7 -4
- package/src/elements/register-impl.ts +3 -0
- package/src/elements/resizable.tsx +78 -2
- package/src/elements/slots.ts +42 -0
- package/src/elements/thread.tsx +105 -0
- package/src/primitives/audio-bands.ts +174 -0
- package/src/primitives/create-tween.ts +214 -0
- package/src/primitives/use-audio-analysis.ts +321 -0
- package/src/primitives/use-sequencer.ts +53 -0
- package/src/primitives/use-voice-recorder.ts +16 -4
- package/src/primitives/visualizer-sequences.ts +350 -0
- package/src/test-utils/fake-clock.ts +88 -0
- package/src/ui/action-icons.ts +2 -1
- package/src/ui/icon.tsx +4 -0
- package/src/ui/overlay.tsx +8 -1
- package/src/ui/resizable.tsx +47 -9
- package/dist/elements/chunks/action-icons-Dtl8Ox5D.js +0 -1
- package/dist/elements/chunks/arrow-left-GbBceGBE.js +0 -1
- package/dist/elements/chunks/chat-thread-Dz2qbzvI.js +0 -1
- package/dist/elements/chunks/check-fhmRksq_.js +0 -1
- package/dist/elements/chunks/chevron-down-DcRg1aRU.js +0 -1
- package/dist/elements/chunks/chevron-right-ChzBcNJ-.js +0 -1
- package/dist/elements/chunks/circle-B4fTe9v6.js +0 -1
- package/dist/elements/chunks/circle-check-CJV-3Rem.js +0 -1
- package/dist/elements/chunks/collapsible-CWUAShMC.js +0 -1
- package/dist/elements/chunks/define-CNSKFZCd.js +0 -1
- package/dist/elements/chunks/hover-card-BQGdGAQ3.js +0 -1
- package/dist/elements/chunks/icon-CAyRqVrg.js +0 -1
- package/dist/elements/chunks/message-square-MmRkvc-M.js +0 -1
- package/dist/elements/chunks/resizable-Bq_eHxUm.js +0 -1
- package/dist/elements/chunks/skeleton-Pjc5jiaY.js +0 -1
- package/dist/elements/chunks/textarea-CboigarR.js +0 -1
- package/dist/elements/chunks/tooltip-Cft_Rxiw.js +0 -1
- package/dist/elements/chunks/use-card-resolution-M36zVciB.js +0 -1
- package/dist/elements/chunks/x-Ckwicf6g.js +0 -1
- package/dist/register-impl-DcjZDELj.js +0 -141
|
@@ -855,6 +855,10 @@ export function Composer(props: ComposerProps): JSX.Element {
|
|
|
855
855
|
.kai-composer-pill-glyph svg { width: 1em; height: 1em; display: block; }
|
|
856
856
|
/* The editable is the containing block for the placeholder pseudo-element. */
|
|
857
857
|
[data-kai-composer-editable] { position: relative; }
|
|
858
|
+
/* While a pill is arrow-selected the pill IS the selection, so hide the text
|
|
859
|
+
caret so a blinking cursor doesn't sit beside the highlight box. Returns
|
|
860
|
+
the moment the pill selection clears (arrow off, type, click, backspace). */
|
|
861
|
+
[data-kai-composer-editable][data-pill-selected] { caret-color: transparent; }
|
|
858
862
|
/* Placeholder via pseudo-element — exempt from axe color-contrast like a
|
|
859
863
|
native <textarea> placeholder. position:absolute (with auto offsets =
|
|
860
864
|
its static text-origin spot) takes it OUT of flow, so the caret sits at
|
|
@@ -874,6 +878,7 @@ export function Composer(props: ComposerProps): JSX.Element {
|
|
|
874
878
|
<div
|
|
875
879
|
ref={(el) => { editable = el; props.editableRef?.(el); }}
|
|
876
880
|
data-kai-composer-editable
|
|
881
|
+
data-pill-selected={selectedPill() ? '' : undefined}
|
|
877
882
|
data-placeholder={props.placeholder ?? ''}
|
|
878
883
|
contentEditable={props.disabled ? false : ('plaintext-only' as unknown as boolean)}
|
|
879
884
|
role="textbox"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { For, Show, onMount, type JSX } from 'solid-js';
|
|
2
|
+
import { ChatConfig, useChatConfig } from '../primitives/chat-config';
|
|
3
|
+
import { ChatContainer, ChatContainerContent, ChatContainerScrollAnchor } from './chat-container';
|
|
4
|
+
import { Message, MessageAvatar, MessageBody } from './message';
|
|
5
|
+
import { createMessageFeedback, type MessageActionDetail } from '../primitives/message-feedback';
|
|
6
|
+
import { ScrollButton } from './scroll-button';
|
|
7
|
+
import { Loader } from './loader';
|
|
8
|
+
import type { ChatMessage } from '../elements/chat-types';
|
|
9
|
+
import type { ProseSize } from '../primitives/chat-config';
|
|
10
|
+
|
|
11
|
+
/** Imperative handle exposed via `controllerRef` — the thread's scroll control,
|
|
12
|
+
* forwarded onto `<kai-thread>` as the `scrollToBottom()` instance method. */
|
|
13
|
+
export interface ThreadController {
|
|
14
|
+
/** Scroll the message list to the bottom (default `'smooth'`). */
|
|
15
|
+
scrollToBottom(behavior?: ScrollBehavior): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ThreadProps {
|
|
19
|
+
/** Extra classes for the thread root (e.g. `rounded-xl`). */
|
|
20
|
+
class?: string;
|
|
21
|
+
/** The full message thread to render, newest last. Each entry carries its role,
|
|
22
|
+
* content, and optional reasoning/tools/attachments/actions/avatar. A new array
|
|
23
|
+
* reference per streaming chunk re-renders (mutating in place does not). */
|
|
24
|
+
messages: ChatMessage[];
|
|
25
|
+
/** Show a typing indicator on the pending assistant turn — use while awaiting
|
|
26
|
+
* the assistant's reply. */
|
|
27
|
+
loading?: boolean;
|
|
28
|
+
/** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`).
|
|
29
|
+
* Defaults to `'sm'`. */
|
|
30
|
+
proseSize?: ProseSize;
|
|
31
|
+
/** Shiki theme name for syntax-highlighted code blocks (e.g.
|
|
32
|
+
* `'github-dark-dimmed'`). */
|
|
33
|
+
codeTheme?: string;
|
|
34
|
+
/** Enable Shiki syntax highlighting in code blocks. Turn off to render plain
|
|
35
|
+
* `<pre>` blocks (lighter, no highlighter load). Default true. */
|
|
36
|
+
codeHighlight?: boolean;
|
|
37
|
+
/** Whether each message's action bar is always visible (`'always'`, default) or
|
|
38
|
+
* only revealed on hover of that message row (`'hover'`). */
|
|
39
|
+
actionsReveal?: 'always' | 'hover';
|
|
40
|
+
/** Show the scroll-to-bottom button inside the scroll area. Default true. */
|
|
41
|
+
scrollButton?: boolean;
|
|
42
|
+
/** Custom zero-state shown while the thread is empty. A sensible default renders
|
|
43
|
+
* when omitted (the `<kai-thread>` facade wires this to `slot="empty"`). */
|
|
44
|
+
empty?: JSX.Element;
|
|
45
|
+
/** Fired when a message's action button is clicked (copy / vote / regenerate /
|
|
46
|
+
* edit / custom). The facade re-dispatches this as `kai-message-action`. */
|
|
47
|
+
onMessageAction?: (detail: MessageActionDetail) => void;
|
|
48
|
+
/** Receive the imperative controller once mounted — the `<kai-thread>` facade
|
|
49
|
+
* forwards `scrollToBottom` onto the host. */
|
|
50
|
+
controllerRef?: (controller: ThreadController) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Built-in zero-state, shown when the thread is empty and no `empty` is supplied. */
|
|
54
|
+
function DefaultEmpty() {
|
|
55
|
+
return (
|
|
56
|
+
<div class="flex min-h-[10rem] flex-1 flex-col items-center justify-center gap-1 py-12 text-center">
|
|
57
|
+
<p class="text-sm font-medium text-foreground">No messages yet</p>
|
|
58
|
+
<p class="text-xs text-muted-foreground">
|
|
59
|
+
Messages will appear here as the conversation starts.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `Thread` — the message-list slice of a chat, as a standalone composable: the
|
|
67
|
+
* scrolling list of messages + per-message rendering (markdown, code highlight,
|
|
68
|
+
* reasoning + tool panels, avatars, and the action row), stick-to-bottom scroll
|
|
69
|
+
* with a scroll-to-bottom button, an optional typing indicator, and an empty
|
|
70
|
+
* state. No composer, header, suggestions, or sidebar — those are sibling/preset
|
|
71
|
+
* concerns. This is the source of truth behind the `<kai-thread>` facade; it is
|
|
72
|
+
* composed from the same internal pieces `ChatThread` uses for its message list.
|
|
73
|
+
*/
|
|
74
|
+
export function Thread(props: ThreadProps) {
|
|
75
|
+
const outer = useChatConfig();
|
|
76
|
+
const reveal = () => (props.actionsReveal === 'hover' ? 'hover' : 'always');
|
|
77
|
+
// Feedback (copy + vote) state lives ABOVE the per-message <For>, so streaming
|
|
78
|
+
// re-renders (a fresh `messages` array ref per chunk) don't wipe it. The
|
|
79
|
+
// copy/feedback toasts scope to this thread's root so they appear in-thread
|
|
80
|
+
// rather than at the page top.
|
|
81
|
+
let rootEl: HTMLElement | undefined;
|
|
82
|
+
const feedback = createMessageFeedback({
|
|
83
|
+
emit: (detail) => props.onMessageAction?.(detail),
|
|
84
|
+
target: () => rootEl,
|
|
85
|
+
});
|
|
86
|
+
const showScrollButton = () => props.scrollButton !== false;
|
|
87
|
+
const showEmpty = () => props.messages.length === 0 && !props.loading;
|
|
88
|
+
|
|
89
|
+
// Hand the imperative controller to the facade once mounted (rootEl is set).
|
|
90
|
+
onMount(() => {
|
|
91
|
+
props.controllerRef?.({
|
|
92
|
+
scrollToBottom: (behavior) => {
|
|
93
|
+
const vp = rootEl?.querySelector<HTMLElement>('.overflow-y-auto');
|
|
94
|
+
vp?.scrollTo({ top: vp.scrollHeight, behavior: behavior ?? 'smooth' });
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<ChatConfig
|
|
101
|
+
proseSize={props.proseSize}
|
|
102
|
+
codeTheme={props.codeTheme}
|
|
103
|
+
codeHighlight={props.codeHighlight !== false}
|
|
104
|
+
portalMount={outer.portalMount()}
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
ref={(e) => (rootEl = e as HTMLElement)}
|
|
108
|
+
class={`relative flex h-full min-h-0 flex-col bg-background ${props.class ?? ''}`}
|
|
109
|
+
>
|
|
110
|
+
<ChatContainer class="h-full px-4 py-3">
|
|
111
|
+
<ChatContainerContent class="mx-auto w-full max-w-3xl space-y-4">
|
|
112
|
+
{/* Zero-state: the consumer owns WHAT it looks like (`empty`); the
|
|
113
|
+
component owns WHEN it shows (empty + not loading). */}
|
|
114
|
+
<Show when={showEmpty()}>
|
|
115
|
+
<Show when={props.empty} fallback={<DefaultEmpty />}>
|
|
116
|
+
{props.empty}
|
|
117
|
+
</Show>
|
|
118
|
+
</Show>
|
|
119
|
+
<For each={props.messages}>
|
|
120
|
+
{(m) => {
|
|
121
|
+
const body = (
|
|
122
|
+
<MessageBody
|
|
123
|
+
content={m.content}
|
|
124
|
+
reasoning={m.reasoning}
|
|
125
|
+
tools={m.tools}
|
|
126
|
+
attachments={m.attachments}
|
|
127
|
+
isUser={m.role === 'user'}
|
|
128
|
+
markdown={m.role === 'assistant'}
|
|
129
|
+
actions={m.actions}
|
|
130
|
+
actionsReveal={reveal()}
|
|
131
|
+
activeFeedback={feedback.resolveFeedback(m)}
|
|
132
|
+
copied={feedback.isCopied(m.id)}
|
|
133
|
+
onAction={(action) => feedback.handleAction(m, action)}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
const rowGroup = reveal() === 'hover' ? 'group ' : '';
|
|
137
|
+
return (
|
|
138
|
+
<Show
|
|
139
|
+
when={m.avatar}
|
|
140
|
+
fallback={
|
|
141
|
+
<Message class={`${rowGroup}${m.role === 'user' ? 'flex-col items-end' : 'flex-col items-start'}`}>
|
|
142
|
+
{body}
|
|
143
|
+
</Message>
|
|
144
|
+
}
|
|
145
|
+
>
|
|
146
|
+
{(av) => (
|
|
147
|
+
<Message class={rowGroup}>
|
|
148
|
+
<MessageAvatar src={av().src ?? ''} alt={av().alt ?? ''} fallback={av().fallback} />
|
|
149
|
+
<div class={`flex min-w-0 flex-1 flex-col ${m.role === 'user' ? 'items-end' : 'items-start'}`}>
|
|
150
|
+
{body}
|
|
151
|
+
</div>
|
|
152
|
+
</Message>
|
|
153
|
+
)}
|
|
154
|
+
</Show>
|
|
155
|
+
);
|
|
156
|
+
}}
|
|
157
|
+
</For>
|
|
158
|
+
{/* Typing indicator on the pending assistant turn. */}
|
|
159
|
+
<Show when={props.loading}>
|
|
160
|
+
<Message class="flex-col items-start">
|
|
161
|
+
<div class="rounded-lg px-1 py-2">
|
|
162
|
+
<Loader variant="typing" />
|
|
163
|
+
</div>
|
|
164
|
+
</Message>
|
|
165
|
+
</Show>
|
|
166
|
+
<ChatContainerScrollAnchor />
|
|
167
|
+
</ChatContainerContent>
|
|
168
|
+
<Show when={showScrollButton()}>
|
|
169
|
+
<div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">
|
|
170
|
+
<ScrollButton class="shadow-sm" />
|
|
171
|
+
</div>
|
|
172
|
+
</Show>
|
|
173
|
+
</ChatContainer>
|
|
174
|
+
</div>
|
|
175
|
+
</ChatConfig>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js';
|
|
2
|
+
import { AudioVisualizer, type ShaderSpec, type VisualizerVariant } from '../components/audio-visualizer';
|
|
3
|
+
import type { VisualizerSize } from '../components/audio-visualizer/sizes';
|
|
4
|
+
import { defineWebComponent, type WebComponentContext } from './define';
|
|
5
|
+
|
|
6
|
+
interface Props extends Record<string, unknown> {
|
|
7
|
+
/** Look to render: `bar` (default), `grid`, `radial`, `wave`, `aurora`, `custom`.
|
|
8
|
+
* `aura` is accepted as a LiveKit-markup alias for `aurora`. Attribute: `variant`. */
|
|
9
|
+
variant?: string;
|
|
10
|
+
/** `idle` (default), `connecting`, `listening`, `thinking`, `speaking`,
|
|
11
|
+
* `disconnected` (connection down: the dead, flat look). LiveKit's
|
|
12
|
+
* room-lifecycle state names are accepted as aliases. Attribute: `state`. */
|
|
13
|
+
state?: string;
|
|
14
|
+
/** `icon` | `sm` | `md` (default) | `lg` | `xl`. Attribute: `size`. */
|
|
15
|
+
size?: string;
|
|
16
|
+
/** Bars to draw. Bar and radial only. Attribute: `bar-count`. */
|
|
17
|
+
barCount?: number;
|
|
18
|
+
/** Grid only: rows and columns of the (always square) grid. Attribute: `count`. */
|
|
19
|
+
count?: number;
|
|
20
|
+
/** Radial only: ring distance from center, in px. Attribute: `radius`. */
|
|
21
|
+
radius?: number;
|
|
22
|
+
/** Grid only: ring distance for the connecting animation, in cells. Attribute: `spread`. */
|
|
23
|
+
spread?: number;
|
|
24
|
+
/** Grid only: ms between scripted frames. Attribute: `interval`. */
|
|
25
|
+
interval?: number;
|
|
26
|
+
/** CSS color for the geometry, overriding the inherited `currentColor`. Attribute: `color`. */
|
|
27
|
+
color?: string;
|
|
28
|
+
/** Shader variants only: pattern density, 0..1. Attribute: `complexity`. */
|
|
29
|
+
complexity?: number;
|
|
30
|
+
/** Setting this makes the element an announced image (`role="img"`) instead of
|
|
31
|
+
* decorative (`aria-hidden`). Attribute: `label`. */
|
|
32
|
+
label?: string;
|
|
33
|
+
/** Live microphone or WebRTC audio to analyze. JS property only. */
|
|
34
|
+
stream?: MediaStream;
|
|
35
|
+
/** An `<audio>` or `<video>` element to tap for its audio. JS property only. */
|
|
36
|
+
audioElement?: HTMLMediaElement;
|
|
37
|
+
/** Pre-computed levels, 0..1. Set this and no AudioContext is ever built, which is
|
|
38
|
+
* what keeps headless/SSR rendering and browser-speech-synthesis playback (which
|
|
39
|
+
* exposes no audio node) free of Web Audio entirely. JS property only. A new
|
|
40
|
+
* array reference is required for each update; mutating the existing array in
|
|
41
|
+
* place will not re-render. */
|
|
42
|
+
bands?: number[];
|
|
43
|
+
/** Custom fragment shader for `variant="custom"`. JS property only. */
|
|
44
|
+
shader?: ShaderSpec;
|
|
45
|
+
/** Shader variants only: keep animating while scrolled off screen. Off by default,
|
|
46
|
+
* which stops drawing and releases the WebGL context until the element comes back
|
|
47
|
+
* (browsers ration contexts to roughly 16 a page). Does not override
|
|
48
|
+
* `prefers-reduced-motion`. Attribute: `animate-when-not-visible`. */
|
|
49
|
+
animateWhenNotVisible?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse a numeric attribute or JS property value. Attribute values arrive as
|
|
54
|
+
* strings, so a missing or non-numeric one must resolve to `undefined` (which
|
|
55
|
+
* the composed component then defaults itself), never `NaN`, which would
|
|
56
|
+
* poison the downstream geometry (bar counts, radii, grid dimensions).
|
|
57
|
+
*/
|
|
58
|
+
export function num(value: unknown): number | undefined {
|
|
59
|
+
if (value == null || value === '') return undefined;
|
|
60
|
+
const n = Number(value);
|
|
61
|
+
return Number.isFinite(n) ? n : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The facade's render logic, extracted to a named function so it can be
|
|
66
|
+
* exercised directly in tests (a plain Solid component call, no shadow DOM
|
|
67
|
+
* involved) without needing `defineWebComponent`'s browser-only custom-element
|
|
68
|
+
* upgrade path. Identical to what was previously an inline arrow passed to
|
|
69
|
+
* `defineWebComponent` below, plus forwarding the resolved theme (see `dark`
|
|
70
|
+
* below) once that context field existed to forward.
|
|
71
|
+
*
|
|
72
|
+
* `ctx` is optional so the existing declarative test (which calls this
|
|
73
|
+
* directly with just `props`, the way it did before `dark` existed) keeps
|
|
74
|
+
* working unchanged. `defineWebComponent` always supplies a real one; the
|
|
75
|
+
* `false` fallback only matters for that reduced, no-shadow-DOM call path.
|
|
76
|
+
*/
|
|
77
|
+
export function AudioVisualizerFacade(
|
|
78
|
+
props: Props,
|
|
79
|
+
ctx?: Partial<Pick<WebComponentContext, 'dark' | 'flag'>>,
|
|
80
|
+
): JSX.Element {
|
|
81
|
+
// Every element already resolves `theme='light'|'dark'|'auto'` against a
|
|
82
|
+
// live `prefers-color-scheme` listener in `defineWebComponent` (see
|
|
83
|
+
// `createDarkMode`), to drive the `.dark` class every element's content
|
|
84
|
+
// sits inside. A WebGL shader cannot read that class or a CSS custom
|
|
85
|
+
// property, so `<kai-audio-visualizer variant="aurora">` needs the ALREADY
|
|
86
|
+
// -RESOLVED boolean forwarded explicitly instead. Passing it through as a
|
|
87
|
+
// definite 'light'/'dark' (never 'auto') means the resolution RULE itself
|
|
88
|
+
// -- explicit wins, 'auto' follows the media query -- lives in exactly one
|
|
89
|
+
// place (`createDarkMode`); this is a plain relay, not a second
|
|
90
|
+
// implementation of that rule.
|
|
91
|
+
const dark = ctx?.dark?.() ?? false;
|
|
92
|
+
|
|
93
|
+
// `flag` is what makes a BARE attribute
|
|
94
|
+
// (`<kai-audio-visualizer animate-when-not-visible>`) mean true:
|
|
95
|
+
// component-register parses a valueless boolean attribute to `undefined`, so
|
|
96
|
+
// the prop alone cannot tell it apart from an absent one. Same helper every
|
|
97
|
+
// other boolean-attribute element uses, so the coercion rule lives in one
|
|
98
|
+
// place. The `props` fallback only covers the ctx-less direct-call path the
|
|
99
|
+
// declarative test uses, where there is no host element carrying an
|
|
100
|
+
// attribute at all.
|
|
101
|
+
const animateWhenNotVisible = ctx?.flag
|
|
102
|
+
? ctx.flag('animateWhenNotVisible')
|
|
103
|
+
: props.animateWhenNotVisible === true;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<AudioVisualizer
|
|
107
|
+
variant={props.variant as VisualizerVariant | undefined}
|
|
108
|
+
state={props.state as string | undefined}
|
|
109
|
+
size={props.size as VisualizerSize | undefined}
|
|
110
|
+
barCount={num(props.barCount)}
|
|
111
|
+
count={num(props.count)}
|
|
112
|
+
radius={num(props.radius)}
|
|
113
|
+
spread={num(props.spread)}
|
|
114
|
+
interval={num(props.interval)}
|
|
115
|
+
color={props.color as string | undefined}
|
|
116
|
+
complexity={num(props.complexity)}
|
|
117
|
+
label={props.label as string | undefined}
|
|
118
|
+
stream={props.stream as MediaStream | undefined}
|
|
119
|
+
audioElement={props.audioElement as HTMLMediaElement | undefined}
|
|
120
|
+
bands={props.bands as number[] | undefined}
|
|
121
|
+
shader={props.shader as ShaderSpec | undefined}
|
|
122
|
+
animateWhenNotVisible={animateWhenNotVisible}
|
|
123
|
+
theme={dark ? 'dark' : 'light'}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* `<kai-audio-visualizer>` renders live audio as bars, a grid, a ring, a wave,
|
|
130
|
+
* or a glowing aurora. It also animates from `state` alone with no audio at
|
|
131
|
+
* all, which is what you want when the source cannot be tapped (browser
|
|
132
|
+
* speech synthesis exposes no audio node).
|
|
133
|
+
*
|
|
134
|
+
* ```html
|
|
135
|
+
* <kai-audio-visualizer variant="bar" state="speaking" size="md"></kai-audio-visualizer>
|
|
136
|
+
* <kai-audio-visualizer variant="radial" size="lg" bar-count="24"></kai-audio-visualizer>
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* Audio sources and rich data are JS properties, never attributes:
|
|
140
|
+
* ```js
|
|
141
|
+
* el.stream = micStream // MediaStream
|
|
142
|
+
* el.audioElement = audioRef // HTMLMediaElement
|
|
143
|
+
* el.bands = [0.2, 0.8, 0.4] // pre-computed, skips Web Audio; new array each update
|
|
144
|
+
* el.shader = { fragment: glsl } // variant="custom" only
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* The `wave`, `aurora`, and `custom` variants render through WebGL, and a
|
|
148
|
+
* browser only allows about 16 live WebGL contexts per page. So a shader
|
|
149
|
+
* variant scrolled off screen stops drawing and hands its context back,
|
|
150
|
+
* picking up where it left off when it returns. Opt out per element when a
|
|
151
|
+
* visualizer must keep running unseen:
|
|
152
|
+
*
|
|
153
|
+
* ```html
|
|
154
|
+
* <kai-audio-visualizer variant="wave" animate-when-not-visible></kai-audio-visualizer>
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* This is a display element: no methods, no events.
|
|
158
|
+
*
|
|
159
|
+
* Restyle from outside via `::part(bar)` / `::part(cell)` / `::part(canvas)`,
|
|
160
|
+
* the bar and cell parts each carrying `data-kai-index` and `data-kai-highlighted`.
|
|
161
|
+
*/
|
|
162
|
+
defineWebComponent<Props>('kai-audio-visualizer', {
|
|
163
|
+
variant: 'bar',
|
|
164
|
+
state: 'idle',
|
|
165
|
+
size: 'md',
|
|
166
|
+
barCount: undefined,
|
|
167
|
+
count: undefined,
|
|
168
|
+
radius: undefined,
|
|
169
|
+
spread: undefined,
|
|
170
|
+
interval: undefined,
|
|
171
|
+
color: undefined,
|
|
172
|
+
complexity: undefined,
|
|
173
|
+
label: undefined,
|
|
174
|
+
stream: undefined,
|
|
175
|
+
audioElement: undefined,
|
|
176
|
+
bands: undefined,
|
|
177
|
+
shader: undefined,
|
|
178
|
+
// `undefined`, not `false`: `resolveFlag` short-circuits on an explicit
|
|
179
|
+
// `false` prop value, so a `false` default would beat a bare
|
|
180
|
+
// `animate-when-not-visible` attribute if component-register ever left the
|
|
181
|
+
// default in place instead of writing `undefined` for a valueless attribute.
|
|
182
|
+
animateWhenNotVisible: undefined,
|
|
183
|
+
}, AudioVisualizerFacade);
|