@kitn.ai/ui 0.28.0 → 0.29.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/README.md +1 -1
- package/dist/agent-tooling/construct/public.d.ts +15 -0
- package/dist/agent-tooling/construct/schema.d.ts +111 -0
- package/dist/construct-cli.es.js +3 -3
- package/dist/construct.d.ts +5 -0
- package/dist/construct.js +327 -0
- package/dist/core-C8fzo39E.js +1 -12
- package/dist/engine-javascript-C1x7zo1_.js +1 -141
- package/dist/mcp.es.js +1 -1
- package/package.json +16 -16
- package/bin/route.test.js +0 -58
- package/src/agent-tooling/README.md +0 -192
- package/src/agent-tooling/archetypes.ts +0 -205
- package/src/agent-tooling/catalog/README.md +0 -676
- package/src/agent-tooling/catalog/catalog-types.ts +0 -231
- package/src/agent-tooling/catalog/fabrications.ts +0 -96
- package/src/agent-tooling/catalog/invariants.ts +0 -284
- package/src/agent-tooling/catalog/labs-titles.ts +0 -114
- package/src/agent-tooling/catalog/scenarios.ts +0 -87
- package/src/agent-tooling/catalog/surfaces.ts +0 -347
- package/src/agent-tooling/construct/cli-entry.ts +0 -8
- package/src/agent-tooling/construct/cli.ts +0 -147
- package/src/agent-tooling/construct/codegen.ts +0 -1635
- package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
- package/src/agent-tooling/construct/dev.ts +0 -148
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
- package/src/agent-tooling/construct/schema.ts +0 -452
- package/src/agent-tooling/integrations/anthropic.ts +0 -442
- package/src/agent-tooling/integrations/cloudflare.ts +0 -218
- package/src/agent-tooling/integrations/langgraph.ts +0 -133
- package/src/agent-tooling/integrations/mastra.ts +0 -182
- package/src/agent-tooling/integrations/mock.ts +0 -148
- package/src/agent-tooling/integrations/ollama.ts +0 -97
- package/src/agent-tooling/integrations/openai.ts +0 -95
- package/src/agent-tooling/integrations/openrouter.ts +0 -82
- package/src/agent-tooling/integrations/pi.ts +0 -176
- package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
- package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
- package/src/agent-tooling/mcp/manifest.ts +0 -422
- package/src/agent-tooling/mcp/server.ts +0 -116
- package/src/agent-tooling/mcp/stdio.ts +0 -18
- package/src/agent-tooling/mcp/tools/construct.ts +0 -130
- package/src/agent-tooling/mcp/tools/debug.ts +0 -621
- package/src/agent-tooling/mcp/tools/reference.ts +0 -980
- package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
- package/src/agent-tooling/mcp/tools/theme.ts +0 -456
- package/src/agent-tooling/mcp/tools/types.ts +0 -14
- package/src/agent-tooling/mcp/validate-args.ts +0 -141
- package/src/agent-tooling/recipes/composed-thread.ts +0 -714
- package/src/agent-tooling/recipes/index.ts +0 -22
- package/src/agent-tooling/recipes/types.ts +0 -32
- package/src/agent-tooling/registry.ts +0 -132
- package/src/agent-tooling/route-emit.ts +0 -305
- package/src/agent-tooling/types.ts +0 -390
- package/src/components/artifact-card.tsx +0 -120
- package/src/components/artifact.tsx +0 -914
- package/src/components/attachment-types.ts +0 -66
- package/src/components/attachments.tsx +0 -536
- package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
- package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
- package/src/components/audio-visualizer/fit-scale.ts +0 -112
- package/src/components/audio-visualizer/index.tsx +0 -528
- package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
- package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
- package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
- package/src/components/audio-visualizer/sizes.ts +0 -54
- package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
- package/src/components/audio-visualizer/variant-bar.tsx +0 -175
- package/src/components/audio-visualizer/variant-custom.tsx +0 -344
- package/src/components/audio-visualizer/variant-grid.tsx +0 -175
- package/src/components/audio-visualizer/variant-radial.tsx +0 -163
- package/src/components/audio-visualizer/variant-wave.tsx +0 -130
- package/src/components/audio-visualizer/wave.glsl.ts +0 -130
- package/src/components/card-fallback.tsx +0 -60
- package/src/components/card-renderer.tsx +0 -220
- package/src/components/card.tsx +0 -212
- package/src/components/chain-of-thought.tsx +0 -348
- package/src/components/chat-container.tsx +0 -89
- package/src/components/chat-scope-picker.tsx +0 -57
- package/src/components/chat-thread.tsx +0 -1014
- package/src/components/checkpoint.tsx +0 -88
- package/src/components/choice-card.tsx +0 -743
- package/src/components/coachmark.tsx +0 -249
- package/src/components/code-block.tsx +0 -181
- package/src/components/composer-dom.ts +0 -159
- package/src/components/composer-highlight.ts +0 -242
- package/src/components/composer-history.ts +0 -82
- package/src/components/composer.tsx +0 -1075
- package/src/components/confirm-card.tsx +0 -402
- package/src/components/context.tsx +0 -377
- package/src/components/conversation-item.tsx +0 -241
- package/src/components/conversation-list.tsx +0 -429
- package/src/components/conversation-panel.tsx +0 -118
- package/src/components/dismissed-stub.tsx +0 -80
- package/src/components/embed.tsx +0 -200
- package/src/components/empty.tsx +0 -177
- package/src/components/feedback-bar.tsx +0 -166
- package/src/components/file-tree.tsx +0 -530
- package/src/components/file-upload.tsx +0 -167
- package/src/components/form-widgets.tsx +0 -525
- package/src/components/form.tsx +0 -1296
- package/src/components/home-panel.tsx +0 -159
- package/src/components/image.tsx +0 -65
- package/src/components/link-preview.tsx +0 -195
- package/src/components/loader.tsx +0 -367
- package/src/components/markdown.tsx +0 -162
- package/src/components/message-skills.tsx +0 -40
- package/src/components/message.tsx +0 -765
- package/src/components/model-switcher.tsx +0 -132
- package/src/components/prompt-input.tsx +0 -188
- package/src/components/prompt-suggestion.tsx +0 -150
- package/src/components/reasoning.tsx +0 -293
- package/src/components/response-compare-types.ts +0 -152
- package/src/components/response-compare.tsx +0 -455
- package/src/components/response-stream.tsx +0 -109
- package/src/components/screen.tsx +0 -181
- package/src/components/scroll-button.tsx +0 -93
- package/src/components/source.tsx +0 -192
- package/src/components/tasks-card.tsx +0 -801
- package/src/components/text-shimmer.tsx +0 -37
- package/src/components/thinking-bar.tsx +0 -50
- package/src/components/thread.tsx +0 -228
- package/src/components/toast.tsx +0 -503
- package/src/components/tool-classify.ts +0 -26
- package/src/components/tool-types.ts +0 -49
- package/src/components/tool.tsx +0 -190
- package/src/components/use-card-resolution.ts +0 -44
- package/src/components/voice-input.tsx +0 -222
- package/src/components/voice-output.tsx +0 -242
- package/src/components/widget-tab-bar.tsx +0 -73
- package/src/components/workspace-shell.tsx +0 -285
- package/src/diagnostics/hook.ts +0 -352
- package/src/diagnostics/index.ts +0 -82
- package/src/diagnostics/report-request.ts +0 -296
- package/src/elements/agent-card.tsx +0 -67
- package/src/elements/artifact.tsx +0 -194
- package/src/elements/attachments.tsx +0 -140
- package/src/elements/audio-visualizer.tsx +0 -227
- package/src/elements/autoloader.ts +0 -89
- package/src/elements/avatar.tsx +0 -33
- package/src/elements/badge.tsx +0 -32
- package/src/elements/button.tsx +0 -149
- package/src/elements/card.tsx +0 -133
- package/src/elements/cards.tsx +0 -266
- package/src/elements/chain-of-thought.tsx +0 -141
- package/src/elements/chat-scope-picker.tsx +0 -65
- package/src/elements/chat-types.ts +0 -102
- package/src/elements/chat-workspace.tsx +0 -188
- package/src/elements/chat.tsx +0 -304
- package/src/elements/checkbox-group.tsx +0 -164
- package/src/elements/checkbox.tsx +0 -134
- package/src/elements/checkpoint.tsx +0 -47
- package/src/elements/choice.tsx +0 -100
- package/src/elements/coachmark.tsx +0 -90
- package/src/elements/code-block.tsx +0 -60
- package/src/elements/command.tsx +0 -197
- package/src/elements/compare.tsx +0 -111
- package/src/elements/compiled.css +0 -2
- package/src/elements/composer.tsx +0 -162
- package/src/elements/confirm-card.tsx +0 -76
- package/src/elements/context-meter.tsx +0 -107
- package/src/elements/conversation-item.tsx +0 -120
- package/src/elements/conversation-list.tsx +0 -203
- package/src/elements/css.ts +0 -5
- package/src/elements/default-input.tsx +0 -392
- package/src/elements/define-entry.ts +0 -12
- package/src/elements/define.tsx +0 -563
- package/src/elements/diagnostic-events.ts +0 -114
- package/src/elements/dialog.tsx +0 -145
- package/src/elements/disclosure.ts +0 -95
- package/src/elements/dock.tsx +0 -215
- package/src/elements/dropdown.tsx +0 -123
- package/src/elements/editable-label.tsx +0 -140
- package/src/elements/element-data-types.ts +0 -152
- package/src/elements/element-diagnostics.ts +0 -392
- package/src/elements/element-manifest.json +0 -354
- package/src/elements/element-nonscalar.json +0 -187
- package/src/elements/element-types.d.ts +0 -4353
- package/src/elements/embed.tsx +0 -35
- package/src/elements/empty.tsx +0 -29
- package/src/elements/feedback-bar.tsx +0 -66
- package/src/elements/file-tree.tsx +0 -62
- package/src/elements/file-upload.tsx +0 -44
- package/src/elements/form.tsx +0 -94
- package/src/elements/hover-card.tsx +0 -80
- package/src/elements/icon.tsx +0 -41
- package/src/elements/image.tsx +0 -32
- package/src/elements/input.tsx +0 -492
- package/src/elements/kbd.tsx +0 -45
- package/src/elements/link-preview.tsx +0 -34
- package/src/elements/loader.tsx +0 -25
- package/src/elements/markdown.tsx +0 -38
- package/src/elements/menu.tsx +0 -241
- package/src/elements/message-skills.tsx +0 -83
- package/src/elements/message.tsx +0 -361
- package/src/elements/model-switcher.tsx +0 -121
- package/src/elements/nav.tsx +0 -87
- package/src/elements/notice.tsx +0 -53
- package/src/elements/pane-grid.tsx +0 -111
- package/src/elements/pane-group.tsx +0 -119
- package/src/elements/pane.tsx +0 -129
- package/src/elements/popover.tsx +0 -81
- package/src/elements/progress-bar.tsx +0 -39
- package/src/elements/prompt-dock.tsx +0 -82
- package/src/elements/prompt-input.tsx +0 -240
- package/src/elements/prompt-suggestions.tsx +0 -113
- package/src/elements/radio-group.tsx +0 -129
- package/src/elements/reasoning.tsx +0 -71
- package/src/elements/register-impl.ts +0 -148
- package/src/elements/register.ts +0 -76
- package/src/elements/remote.tsx +0 -225
- package/src/elements/resizable.globals.d.ts +0 -36
- package/src/elements/resizable.tsx +0 -704
- package/src/elements/response-stream.tsx +0 -40
- package/src/elements/screen.tsx +0 -90
- package/src/elements/scroll-area.tsx +0 -33
- package/src/elements/scroll-button.tsx +0 -179
- package/src/elements/search.tsx +0 -185
- package/src/elements/segmented.tsx +0 -129
- package/src/elements/select.tsx +0 -165
- package/src/elements/separator.tsx +0 -36
- package/src/elements/setting-item.tsx +0 -50
- package/src/elements/settings-group.tsx +0 -37
- package/src/elements/skeleton.tsx +0 -45
- package/src/elements/slider.tsx +0 -171
- package/src/elements/slot-text.ts +0 -73
- package/src/elements/slots.ts +0 -787
- package/src/elements/source.tsx +0 -126
- package/src/elements/status.tsx +0 -42
- package/src/elements/styles.css +0 -328
- package/src/elements/switch.tsx +0 -118
- package/src/elements/tabs.tsx +0 -95
- package/src/elements/tasks.tsx +0 -103
- package/src/elements/text-shimmer.tsx +0 -28
- package/src/elements/thinking-bar.tsx +0 -43
- package/src/elements/thread.tsx +0 -139
- package/src/elements/toast.tsx +0 -106
- package/src/elements/tool.tsx +0 -78
- package/src/elements/tooltip.tsx +0 -85
- package/src/elements/validate-messages.ts +0 -76
- package/src/elements/voice-input.tsx +0 -100
- package/src/elements/voice-output.tsx +0 -82
- package/src/index.ts +0 -430
- package/src/primitives/audio-bands.ts +0 -174
- package/src/primitives/card-component-types.ts +0 -63
- package/src/primitives/card-contract.ts +0 -80
- package/src/primitives/card-data-types.ts +0 -361
- package/src/primitives/card-host.tsx +0 -37
- package/src/primitives/card-recovery.ts +0 -160
- package/src/primitives/card-registry.tsx +0 -78
- package/src/primitives/card-resolution.ts +0 -36
- package/src/primitives/card-routing.ts +0 -85
- package/src/primitives/card-schemas/artifact.schema.json +0 -92
- package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
- package/src/primitives/card-schemas/card-event.schema.json +0 -12
- package/src/primitives/card-schemas/choice.schema.json +0 -75
- package/src/primitives/card-schemas/confirm.schema.json +0 -65
- package/src/primitives/card-schemas/embed.schema.json +0 -63
- package/src/primitives/card-schemas/form.result.schema.json +0 -7
- package/src/primitives/card-schemas/form.schema.json +0 -53
- package/src/primitives/card-schemas/link.schema.json +0 -54
- package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
- package/src/primitives/card-schemas/tasks.schema.json +0 -83
- package/src/primitives/card-tags.ts +0 -61
- package/src/primitives/card-validate-cards.ts +0 -255
- package/src/primitives/card-validate-schemas.ts +0 -58
- package/src/primitives/card-validate.ts +0 -131
- package/src/primitives/chat-config.tsx +0 -103
- package/src/primitives/composer-model.ts +0 -49
- package/src/primitives/composer-triggers.ts +0 -35
- package/src/primitives/controllable.ts +0 -21
- package/src/primitives/conversation-store.ts +0 -248
- package/src/primitives/create-kai-chat.ts +0 -66
- package/src/primitives/create-tween.ts +0 -266
- package/src/primitives/embed-providers.ts +0 -254
- package/src/primitives/field-mask.ts +0 -256
- package/src/primitives/field-semantics.ts +0 -115
- package/src/primitives/highlighter.ts +0 -157
- package/src/primitives/input-mask.ts +0 -853
- package/src/primitives/link-preview.ts +0 -87
- package/src/primitives/message-feedback.ts +0 -124
- package/src/primitives/pdf-preview.ts +0 -121
- package/src/primitives/toast-store.ts +0 -338
- package/src/primitives/url-scheme-policy.ts +0 -70
- package/src/primitives/use-audio-analysis.ts +0 -340
- package/src/primitives/use-auto-resize.ts +0 -31
- package/src/primitives/use-resize-observer.ts +0 -45
- package/src/primitives/use-sequencer.ts +0 -72
- package/src/primitives/use-speech-recognition.ts +0 -146
- package/src/primitives/use-stick-to-bottom.ts +0 -75
- package/src/primitives/use-text-stream.ts +0 -143
- package/src/primitives/use-voice-recorder.ts +0 -62
- package/src/primitives/visualizer-sequences.ts +0 -350
- package/src/remote/host-embed.ts +0 -345
- package/src/remote/index.ts +0 -2
- package/src/remote/origin.ts +0 -30
- package/src/remote/provider-runtime.ts +0 -262
- package/src/remote/provider.ts +0 -2
- package/src/remote/validate.ts +0 -22
- package/src/remote/version.ts +0 -12
- package/src/remote/wire.ts +0 -48
- package/src/schemas/from-tool-call.ts +0 -151
- package/src/schemas/index.ts +0 -341
- package/src/schemas/provider-subsets.ts +0 -538
- package/src/schemas/registry.ts +0 -355
- package/src/schemas/tool-defs.ts +0 -793
- package/src/solid.ts +0 -173
- package/src/state/index.ts +0 -44
- package/src/state/messages.ts +0 -53
- package/src/state/mock.ts +0 -297
- package/src/state/parts.ts +0 -295
- package/src/state/persistence.ts +0 -180
- package/src/state/stream.ts +0 -246
- package/src/state/suggestions.ts +0 -9
- package/src/state/threads.ts +0 -142
- package/src/test-utils/fake-clock.ts +0 -88
- package/src/types.ts +0 -95
- package/src/ui/action-icons.ts +0 -53
- package/src/ui/agent-card.tsx +0 -218
- package/src/ui/avatar.tsx +0 -23
- package/src/ui/badge.tsx +0 -21
- package/src/ui/button.tsx +0 -49
- package/src/ui/card.tsx +0 -245
- package/src/ui/checkbox-group.tsx +0 -153
- package/src/ui/checkbox.tsx +0 -63
- package/src/ui/collapsible.tsx +0 -163
- package/src/ui/command.tsx +0 -103
- package/src/ui/dialog.tsx +0 -223
- package/src/ui/dock.tsx +0 -630
- package/src/ui/dropdown.tsx +0 -636
- package/src/ui/editable-label.tsx +0 -112
- package/src/ui/hover-card.tsx +0 -345
- package/src/ui/icon.tsx +0 -167
- package/src/ui/input.tsx +0 -391
- package/src/ui/kbd.tsx +0 -118
- package/src/ui/nav.tsx +0 -282
- package/src/ui/notice.tsx +0 -84
- package/src/ui/overlay.tsx +0 -277
- package/src/ui/pane-grid.tsx +0 -117
- package/src/ui/pane-group.tsx +0 -291
- package/src/ui/pane.tsx +0 -193
- package/src/ui/popover.tsx +0 -106
- package/src/ui/progress-bar.tsx +0 -83
- package/src/ui/prompt-dock.tsx +0 -145
- package/src/ui/radio.tsx +0 -150
- package/src/ui/resizable.tsx +0 -693
- package/src/ui/scroll-area.tsx +0 -26
- package/src/ui/segmented.tsx +0 -109
- package/src/ui/select.tsx +0 -168
- package/src/ui/separator.tsx +0 -10
- package/src/ui/settings-group.tsx +0 -67
- package/src/ui/skeleton.tsx +0 -73
- package/src/ui/slider.tsx +0 -178
- package/src/ui/status.tsx +0 -48
- package/src/ui/switch.tsx +0 -119
- package/src/ui/tabs.tsx +0 -168
- package/src/ui/textarea.tsx +0 -21
- package/src/ui/tooltip.tsx +0 -118
- package/src/utils/cn.ts +0 -30
- package/src/wire/chunk.ts +0 -248
- package/src/wire/consume.ts +0 -599
- package/src/wire/diagnostics.ts +0 -727
- package/src/wire/encode-probe.ts +0 -214
- package/src/wire/encode.ts +0 -947
- package/src/wire/files.ts +0 -342
- package/src/wire/formats/anthropic.ts +0 -274
- package/src/wire/formats/openai.ts +0 -228
- package/src/wire/index.ts +0 -95
- package/src/wire/media-types.ts +0 -344
- package/src/wire/read.ts +0 -396
- package/src/wire/sink-helpers.ts +0 -50
- package/src/wire/sse.ts +0 -153
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import { type JSX, Show, createContext, createMemo, useContext } from 'solid-js';
|
|
2
|
-
import { HoverCardRoot, HoverCardTrigger, HoverCardContent } from '../ui/hover-card';
|
|
3
|
-
import { cn } from '../utils/cn';
|
|
4
|
-
import { Button } from '../ui/button';
|
|
5
|
-
import { ProgressBar, type ProgressTone } from '../ui/progress-bar';
|
|
6
|
-
|
|
7
|
-
const ICON_RADIUS = 10;
|
|
8
|
-
const ICON_VIEWBOX = 24;
|
|
9
|
-
const ICON_CENTER = 12;
|
|
10
|
-
const ICON_STROKE_WIDTH = 2;
|
|
11
|
-
|
|
12
|
-
/** Default fraction (0–1) at which the meter transitions to the warning colour. */
|
|
13
|
-
export const DEFAULT_WARN_THRESHOLD = 0.7;
|
|
14
|
-
/** Default fraction (0–1) at which the meter transitions to the danger colour. */
|
|
15
|
-
export const DEFAULT_DANGER_THRESHOLD = 0.9;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Compute the severity level for a given usage fraction and thresholds.
|
|
19
|
-
* `pct` is a 0–1 fraction (e.g. `usedTokens / maxTokens`).
|
|
20
|
-
*/
|
|
21
|
-
export function computeSeverity(
|
|
22
|
-
pct: number,
|
|
23
|
-
warnThreshold = DEFAULT_WARN_THRESHOLD,
|
|
24
|
-
dangerThreshold = DEFAULT_DANGER_THRESHOLD,
|
|
25
|
-
): ContextSeverity {
|
|
26
|
-
if (pct > dangerThreshold) return 'danger';
|
|
27
|
-
if (pct > warnThreshold) return 'warn';
|
|
28
|
-
return 'ok';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type ContextSeverity = 'ok' | 'warn' | 'danger';
|
|
32
|
-
|
|
33
|
-
interface ContextSchema {
|
|
34
|
-
usedTokens: number;
|
|
35
|
-
maxTokens: number;
|
|
36
|
-
inputTokens?: number;
|
|
37
|
-
outputTokens?: number;
|
|
38
|
-
reasoningTokens?: number;
|
|
39
|
-
cacheTokens?: number;
|
|
40
|
-
estimatedCost?: number;
|
|
41
|
-
/** Fraction (0–1) above which the meter turns yellow. Default 0.7. */
|
|
42
|
-
warnThreshold: number;
|
|
43
|
-
/** Fraction (0–1) above which the meter turns red. Default 0.9. */
|
|
44
|
-
dangerThreshold: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const ContextCtx = createContext<ContextSchema>();
|
|
48
|
-
|
|
49
|
-
function useContextValue(): ContextSchema {
|
|
50
|
-
const ctx = useContext(ContextCtx);
|
|
51
|
-
if (!ctx) {
|
|
52
|
-
throw new Error('Context components must be used within Context');
|
|
53
|
-
}
|
|
54
|
-
return ctx;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const fmtCompact = new Intl.NumberFormat('en-US', { notation: 'compact' });
|
|
58
|
-
const fmtPercent = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1, style: 'percent' });
|
|
59
|
-
const fmtCurrency = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' });
|
|
60
|
-
|
|
61
|
-
// --- Root provider ---
|
|
62
|
-
|
|
63
|
-
export interface ContextProps {
|
|
64
|
-
usedTokens: number;
|
|
65
|
-
maxTokens: number;
|
|
66
|
-
inputTokens?: number;
|
|
67
|
-
outputTokens?: number;
|
|
68
|
-
reasoningTokens?: number;
|
|
69
|
-
cacheTokens?: number;
|
|
70
|
-
estimatedCost?: number;
|
|
71
|
-
/**
|
|
72
|
-
* Fraction (0–1) above which the progress bar turns yellow.
|
|
73
|
-
* Defaults to `0.7` (70%).
|
|
74
|
-
*/
|
|
75
|
-
warnThreshold?: number;
|
|
76
|
-
/**
|
|
77
|
-
* Fraction (0–1) above which the progress bar turns red.
|
|
78
|
-
* Defaults to `0.9` (90%).
|
|
79
|
-
*/
|
|
80
|
-
dangerThreshold?: number;
|
|
81
|
-
children?: JSX.Element;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function Context(props: ContextProps) {
|
|
85
|
-
const value = createMemo<ContextSchema>(() => ({
|
|
86
|
-
usedTokens: props.usedTokens,
|
|
87
|
-
maxTokens: props.maxTokens,
|
|
88
|
-
inputTokens: props.inputTokens,
|
|
89
|
-
outputTokens: props.outputTokens,
|
|
90
|
-
reasoningTokens: props.reasoningTokens,
|
|
91
|
-
cacheTokens: props.cacheTokens,
|
|
92
|
-
estimatedCost: props.estimatedCost,
|
|
93
|
-
warnThreshold: props.warnThreshold ?? DEFAULT_WARN_THRESHOLD,
|
|
94
|
-
dangerThreshold: props.dangerThreshold ?? DEFAULT_DANGER_THRESHOLD,
|
|
95
|
-
}));
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<ContextCtx.Provider value={value()}>
|
|
99
|
-
<HoverCardRoot openDelay={0}>
|
|
100
|
-
{props.children}
|
|
101
|
-
</HoverCardRoot>
|
|
102
|
-
</ContextCtx.Provider>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// --- Icon (internal) ---
|
|
107
|
-
|
|
108
|
-
function ContextIcon() {
|
|
109
|
-
const ctx = useContextValue();
|
|
110
|
-
const circumference = 2 * Math.PI * ICON_RADIUS;
|
|
111
|
-
const usedPercent = createMemo(() => ctx.usedTokens / ctx.maxTokens);
|
|
112
|
-
const dashOffset = createMemo(() => circumference * (1 - usedPercent()));
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<svg
|
|
116
|
-
aria-label="Model context usage"
|
|
117
|
-
height="20"
|
|
118
|
-
role="img"
|
|
119
|
-
style={{ color: 'currentcolor' }}
|
|
120
|
-
viewBox={`0 0 ${ICON_VIEWBOX} ${ICON_VIEWBOX}`}
|
|
121
|
-
width="20"
|
|
122
|
-
>
|
|
123
|
-
<circle
|
|
124
|
-
cx={ICON_CENTER}
|
|
125
|
-
cy={ICON_CENTER}
|
|
126
|
-
fill="none"
|
|
127
|
-
opacity="0.25"
|
|
128
|
-
r={ICON_RADIUS}
|
|
129
|
-
stroke="currentColor"
|
|
130
|
-
stroke-width={ICON_STROKE_WIDTH}
|
|
131
|
-
/>
|
|
132
|
-
<circle
|
|
133
|
-
cx={ICON_CENTER}
|
|
134
|
-
cy={ICON_CENTER}
|
|
135
|
-
fill="none"
|
|
136
|
-
opacity="0.7"
|
|
137
|
-
r={ICON_RADIUS}
|
|
138
|
-
stroke="currentColor"
|
|
139
|
-
stroke-dasharray={`${circumference} ${circumference}`}
|
|
140
|
-
stroke-dashoffset={dashOffset()}
|
|
141
|
-
stroke-linecap="round"
|
|
142
|
-
stroke-width={ICON_STROKE_WIDTH}
|
|
143
|
-
style={{ transform: 'rotate(-90deg)', 'transform-origin': 'center' }}
|
|
144
|
-
/>
|
|
145
|
-
</svg>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// --- Trigger ---
|
|
150
|
-
|
|
151
|
-
export interface ContextTriggerProps {
|
|
152
|
-
children?: JSX.Element;
|
|
153
|
-
class?: string;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function ContextTrigger(props: ContextTriggerProps) {
|
|
157
|
-
const ctx = useContextValue();
|
|
158
|
-
const usedPercent = createMemo(() => ctx.usedTokens / ctx.maxTokens);
|
|
159
|
-
const renderedPercent = createMemo(() => fmtPercent.format(usedPercent()));
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<HoverCardTrigger>
|
|
163
|
-
<Show
|
|
164
|
-
when={!props.children}
|
|
165
|
-
fallback={props.children}
|
|
166
|
-
>
|
|
167
|
-
<Button type="button" variant="ghost" class={props.class}>
|
|
168
|
-
<span class="font-medium text-muted-foreground">{renderedPercent()}</span>
|
|
169
|
-
<ContextIcon />
|
|
170
|
-
</Button>
|
|
171
|
-
</Show>
|
|
172
|
-
</HoverCardTrigger>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// --- Content ---
|
|
177
|
-
|
|
178
|
-
export interface ContextContentProps {
|
|
179
|
-
class?: string;
|
|
180
|
-
children?: JSX.Element;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function ContextContent(props: ContextContentProps) {
|
|
184
|
-
return (
|
|
185
|
-
<HoverCardContent
|
|
186
|
-
class={cn(
|
|
187
|
-
'min-w-60 divide-y divide-border overflow-hidden',
|
|
188
|
-
props.class
|
|
189
|
-
)}
|
|
190
|
-
>
|
|
191
|
-
{props.children}
|
|
192
|
-
</HoverCardContent>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// --- Content Header ---
|
|
197
|
-
|
|
198
|
-
export interface ContextContentHeaderProps {
|
|
199
|
-
class?: string;
|
|
200
|
-
children?: JSX.Element;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function ContextContentHeader(props: ContextContentHeaderProps) {
|
|
204
|
-
const ctx = useContextValue();
|
|
205
|
-
const usedPercent = createMemo(() => ctx.usedTokens / ctx.maxTokens);
|
|
206
|
-
const displayPct = createMemo(() => fmtPercent.format(usedPercent()));
|
|
207
|
-
const used = createMemo(() => fmtCompact.format(ctx.usedTokens));
|
|
208
|
-
const total = createMemo(() => fmtCompact.format(ctx.maxTokens));
|
|
209
|
-
|
|
210
|
-
const severity = createMemo<ContextSeverity>(() => {
|
|
211
|
-
const pct = usedPercent();
|
|
212
|
-
if (pct > ctx.dangerThreshold) return 'danger';
|
|
213
|
-
if (pct > ctx.warnThreshold) return 'warn';
|
|
214
|
-
return 'ok';
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// Severity used to pick a bare hue class and nothing else, so the meter was a
|
|
218
|
-
// pair of styled divs: no role, no value, invisible to assistive tech. Map it to
|
|
219
|
-
// the shared ProgressBar's semantic `tone` instead and let that primitive supply
|
|
220
|
-
// `role="progressbar"` + aria-valuenow/min/max, rather than hand-rolling those
|
|
221
|
-
// onto the divs.
|
|
222
|
-
const tone = createMemo<ProgressTone>(() => {
|
|
223
|
-
const s = severity();
|
|
224
|
-
if (s === 'danger') return 'error';
|
|
225
|
-
if (s === 'warn') return 'warning';
|
|
226
|
-
return 'primary';
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
return (
|
|
230
|
-
<div class={cn('w-full space-y-2 p-3', props.class)}>
|
|
231
|
-
<Show when={!props.children} fallback={props.children}>
|
|
232
|
-
<div class="flex items-center justify-between gap-3 text-xs">
|
|
233
|
-
<p>{displayPct()}</p>
|
|
234
|
-
<p class="font-mono text-muted-foreground">
|
|
235
|
-
{used()} / {total()}
|
|
236
|
-
</p>
|
|
237
|
-
</div>
|
|
238
|
-
<div class="space-y-2">
|
|
239
|
-
{/* Values are the raw token counts, so a screen reader announces
|
|
240
|
-
"190,000 of 200,000" rather than a bare percentage. */}
|
|
241
|
-
<ProgressBar
|
|
242
|
-
value={ctx.usedTokens}
|
|
243
|
-
max={ctx.maxTokens}
|
|
244
|
-
tone={tone()}
|
|
245
|
-
aria-label="Context usage"
|
|
246
|
-
/>
|
|
247
|
-
</div>
|
|
248
|
-
</Show>
|
|
249
|
-
</div>
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// --- Content Body ---
|
|
254
|
-
|
|
255
|
-
export interface ContextContentBodyProps {
|
|
256
|
-
class?: string;
|
|
257
|
-
children?: JSX.Element;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export function ContextContentBody(props: ContextContentBodyProps) {
|
|
261
|
-
return (
|
|
262
|
-
<div class={cn('w-full p-3', props.class)}>
|
|
263
|
-
{props.children}
|
|
264
|
-
</div>
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// --- Content Footer ---
|
|
269
|
-
|
|
270
|
-
export interface ContextContentFooterProps {
|
|
271
|
-
class?: string;
|
|
272
|
-
children?: JSX.Element;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export function ContextContentFooter(props: ContextContentFooterProps) {
|
|
276
|
-
const ctx = useContextValue();
|
|
277
|
-
const totalCost = createMemo(() =>
|
|
278
|
-
fmtCurrency.format(ctx.estimatedCost ?? 0)
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
return (
|
|
282
|
-
<div
|
|
283
|
-
class={cn(
|
|
284
|
-
'flex w-full items-center justify-between gap-3 bg-muted p-3 text-xs',
|
|
285
|
-
props.class
|
|
286
|
-
)}
|
|
287
|
-
>
|
|
288
|
-
<Show when={!props.children} fallback={props.children}>
|
|
289
|
-
<span class="text-muted-foreground">Total cost</span>
|
|
290
|
-
<span>{totalCost()}</span>
|
|
291
|
-
</Show>
|
|
292
|
-
</div>
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// --- Token row helper ---
|
|
297
|
-
|
|
298
|
-
function TokensDisplay(props: { tokens?: number }) {
|
|
299
|
-
return (
|
|
300
|
-
<span>
|
|
301
|
-
{props.tokens === undefined
|
|
302
|
-
? '\u2014'
|
|
303
|
-
: fmtCompact.format(props.tokens)}
|
|
304
|
-
</span>
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// --- Specialized usage rows ---
|
|
309
|
-
|
|
310
|
-
export interface ContextUsageRowProps {
|
|
311
|
-
class?: string;
|
|
312
|
-
children?: JSX.Element;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// The four rows share one shape, but each still ships a `<Name>Props` alias: a
|
|
316
|
-
// consumer typing a wrapper around one of them should not have to discover that
|
|
317
|
-
// the shared name exists.
|
|
318
|
-
export type ContextInputUsageProps = ContextUsageRowProps;
|
|
319
|
-
export type ContextOutputUsageProps = ContextUsageRowProps;
|
|
320
|
-
export type ContextReasoningUsageProps = ContextUsageRowProps;
|
|
321
|
-
export type ContextCacheUsageProps = ContextUsageRowProps;
|
|
322
|
-
|
|
323
|
-
export function ContextInputUsage(props: ContextInputUsageProps) {
|
|
324
|
-
const ctx = useContextValue();
|
|
325
|
-
return (
|
|
326
|
-
<Show when={props.children || ctx.inputTokens}>
|
|
327
|
-
<Show when={!props.children} fallback={props.children}>
|
|
328
|
-
<div class={cn('flex items-center justify-between text-xs', props.class)}>
|
|
329
|
-
<span class="text-muted-foreground">Input</span>
|
|
330
|
-
<TokensDisplay tokens={ctx.inputTokens} />
|
|
331
|
-
</div>
|
|
332
|
-
</Show>
|
|
333
|
-
</Show>
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export function ContextOutputUsage(props: ContextOutputUsageProps) {
|
|
338
|
-
const ctx = useContextValue();
|
|
339
|
-
return (
|
|
340
|
-
<Show when={props.children || ctx.outputTokens}>
|
|
341
|
-
<Show when={!props.children} fallback={props.children}>
|
|
342
|
-
<div class={cn('flex items-center justify-between text-xs', props.class)}>
|
|
343
|
-
<span class="text-muted-foreground">Output</span>
|
|
344
|
-
<TokensDisplay tokens={ctx.outputTokens} />
|
|
345
|
-
</div>
|
|
346
|
-
</Show>
|
|
347
|
-
</Show>
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export function ContextReasoningUsage(props: ContextReasoningUsageProps) {
|
|
352
|
-
const ctx = useContextValue();
|
|
353
|
-
return (
|
|
354
|
-
<Show when={props.children || ctx.reasoningTokens}>
|
|
355
|
-
<Show when={!props.children} fallback={props.children}>
|
|
356
|
-
<div class={cn('flex items-center justify-between text-xs', props.class)}>
|
|
357
|
-
<span class="text-muted-foreground">Reasoning</span>
|
|
358
|
-
<TokensDisplay tokens={ctx.reasoningTokens} />
|
|
359
|
-
</div>
|
|
360
|
-
</Show>
|
|
361
|
-
</Show>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export function ContextCacheUsage(props: ContextCacheUsageProps) {
|
|
366
|
-
const ctx = useContextValue();
|
|
367
|
-
return (
|
|
368
|
-
<Show when={props.children || ctx.cacheTokens}>
|
|
369
|
-
<Show when={!props.children} fallback={props.children}>
|
|
370
|
-
<div class={cn('flex items-center justify-between text-xs', props.class)}>
|
|
371
|
-
<span class="text-muted-foreground">Cache</span>
|
|
372
|
-
<TokensDisplay tokens={ctx.cacheTokens} />
|
|
373
|
-
</div>
|
|
374
|
-
</Show>
|
|
375
|
-
</Show>
|
|
376
|
-
);
|
|
377
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { Show, splitProps, createMemo, type JSX } from 'solid-js';
|
|
2
|
-
import { MessageSquare } from 'lucide-solid';
|
|
3
|
-
import { cn } from '../utils/cn';
|
|
4
|
-
import type { ConversationSummary } from '../types';
|
|
5
|
-
|
|
6
|
-
export interface ConversationItemProps {
|
|
7
|
-
conversation: ConversationSummary;
|
|
8
|
-
isActive: boolean;
|
|
9
|
-
onSelect: (id: string) => void;
|
|
10
|
-
/** Dense single-line row: a leading dot + title, no message count. */
|
|
11
|
-
compact?: boolean;
|
|
12
|
-
class?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Short relative time from an ISO date string: "just now", "5m ago", "3h ago",
|
|
17
|
-
* "2d ago", "24d ago". Pure — it snapshots `now` (defaults to `Date.now()`) at
|
|
18
|
-
* call time, so it re-derives whenever the list re-renders; there is no internal
|
|
19
|
-
* ticking clock. Returns '' for a missing or unparseable date.
|
|
20
|
-
*/
|
|
21
|
-
export function relativeTimeShort(iso?: string, now: number = Date.now()): string {
|
|
22
|
-
if (!iso) return '';
|
|
23
|
-
const then = Date.parse(iso);
|
|
24
|
-
if (Number.isNaN(then)) return '';
|
|
25
|
-
const secs = Math.max(0, Math.round((now - then) / 1000));
|
|
26
|
-
if (secs < 60) return 'just now';
|
|
27
|
-
const mins = Math.floor(secs / 60);
|
|
28
|
-
if (mins < 60) return `${mins}m ago`;
|
|
29
|
-
const hours = Math.floor(mins / 60);
|
|
30
|
-
if (hours < 24) return `${hours}h ago`;
|
|
31
|
-
return `${Math.floor(hours / 24)}d ago`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Whether a conversation should show an unread indicator (owner round,
|
|
36
|
-
* 2026-08-26). `lastReadAt`'s own doc (`types.ts`) has the full contract;
|
|
37
|
-
* this is the one place that reads it, so both list surfaces (this file's
|
|
38
|
-
* `ConversationItem` and the widget-box `ConversationPanel`) derive it
|
|
39
|
-
* identically rather than each restating the comparison.
|
|
40
|
-
*
|
|
41
|
-
* Absent `lastReadAt` reads as NOT unread — the decide-loudly default for a
|
|
42
|
-
* store that never implements `ConversationStore.markRead` at all (every
|
|
43
|
-
* summary it returns leaves the field undefined forever, so this always
|
|
44
|
-
* returns `false` for it) rather than guessing "probably unread" from a
|
|
45
|
-
* signal the store never actually provided. Defensive `Date.parse`, same
|
|
46
|
-
* pattern as `relativeTimeShort` above: an unparsable date reads as not
|
|
47
|
-
* unread rather than throwing.
|
|
48
|
-
*/
|
|
49
|
-
export function isConversationUnread(conv: Pick<ConversationSummary, 'updatedAt' | 'lastReadAt'>): boolean {
|
|
50
|
-
if (!conv.lastReadAt) return false;
|
|
51
|
-
const updated = Date.parse(conv.updatedAt);
|
|
52
|
-
const read = Date.parse(conv.lastReadAt);
|
|
53
|
-
if (Number.isNaN(updated) || Number.isNaN(read)) return false;
|
|
54
|
-
return updated > read;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The slotted-item shape rendered by `<kai-conversation-item>` — the composed
|
|
59
|
-
* row of the consumer-owned loop. Distinct from the
|
|
60
|
-
* data-mode `ConversationItem` above, which batteries mode keeps rendering
|
|
61
|
-
* unchanged: this one takes REGIONS, not a `ConversationSummary`.
|
|
62
|
-
*
|
|
63
|
-
* Regions map 1:1 to the element's slots: `children` = the default slot (the
|
|
64
|
-
* title), plus `leading`, `meta`, and `menu`. The `menu` region takes the
|
|
65
|
-
* consumer's OWN popover (rename / fork / archive live there); the component
|
|
66
|
-
* provides only the region plus focus and ARIA plumbing, never a declarative
|
|
67
|
-
* actions prop. Activation has two modes: inside `<kai-conversations>` it
|
|
68
|
-
* lives in the CONTAINER (`createConversationItemsController` in
|
|
69
|
-
* conversation-list.tsx — this row renders no handler and the
|
|
70
|
-
* `data-kai-item-menu` marker on the menu region is what the container's
|
|
71
|
-
* activation guard keys off so a click in the consumer's menu never also
|
|
72
|
-
* selects the row); STANDALONE, `onActivate` makes the row body
|
|
73
|
-
* its own tabbable button-role control — click / Enter / Space — with the menu
|
|
74
|
-
* still outside the control as the body's sibling.
|
|
75
|
-
*
|
|
76
|
-
* ARIA contract for direct Solid use: the row renders `role="listitem"` holding
|
|
77
|
-
* a `role="button"` body (`aria-current` marks the active row, the same dialect
|
|
78
|
-
* as the batteries-mode row above) with the menu as the body's tabbable SIBLING
|
|
79
|
-
* (axe `nested-interactive` bans focusable descendants of
|
|
80
|
-
* a control, and `aria-required-children` outlaws non-option content anywhere
|
|
81
|
-
* inside a listbox, which is why the vocabulary is not listbox/option). Render
|
|
82
|
-
* your loop's rows inside a `role="list"` ancestor; element mode has no such
|
|
83
|
-
* burden: `<kai-conversations>`' items region provides it for slotted items.
|
|
84
|
-
*/
|
|
85
|
-
export interface SlottedConversationItemProps {
|
|
86
|
-
/** The row's identity, handed to the container's selection contract. In the
|
|
87
|
-
* element this is the `conversation-id` attribute (host `id` is the fallback). */
|
|
88
|
-
conversationId?: string;
|
|
89
|
-
/** Selected state. Reflected as `aria-current` on the row body and a
|
|
90
|
-
* `data-active` styling hook on the row; the container drives it from its
|
|
91
|
-
* `activeId`. */
|
|
92
|
-
active?: boolean;
|
|
93
|
-
/** Dense single-line row padding. */
|
|
94
|
-
compact?: boolean;
|
|
95
|
-
/** Leading region before the title (an icon or avatar). */
|
|
96
|
-
leading?: JSX.Element;
|
|
97
|
-
/** Meta region under the title (a timestamp or status line). */
|
|
98
|
-
meta?: JSX.Element;
|
|
99
|
-
/** Your own row menu (a popover trigger). Never selects the row. */
|
|
100
|
-
menu?: JSX.Element;
|
|
101
|
-
/** The title (the element's default slot). */
|
|
102
|
-
children?: JSX.Element;
|
|
103
|
-
/** Set when the element HOST carries the row-group semantics: the inner row
|
|
104
|
-
* then renders `role="presentation"` so the accessibility tree sees one
|
|
105
|
-
* group (the host), never two. The `kai-conversation-item` facade sets it;
|
|
106
|
-
* Solid consumers rendering the component directly leave it off and the row
|
|
107
|
-
* itself is the group. */
|
|
108
|
-
hostSemantics?: boolean;
|
|
109
|
-
/** STANDALONE activation: when set, the row body is itself the
|
|
110
|
-
* activation control — `tabindex="0"` on the `role="button"` body, and
|
|
111
|
-
* click / Enter / Space call this. The menu region never triggers it (it is
|
|
112
|
-
* the body's sibling, outside the control). Inside `<kai-conversations>`
|
|
113
|
-
* leave it UNSET: the container's controller owns activation (its delegated
|
|
114
|
-
* click/keydown → `kai-conversation-select`) plus roving tabindex, and a
|
|
115
|
-
* handler here would double-fire. The `kai-conversation-item` facade passes
|
|
116
|
-
* it only when the item is standalone. */
|
|
117
|
-
onActivate?: () => void;
|
|
118
|
-
class?: string;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function SlottedConversationItem(props: SlottedConversationItemProps) {
|
|
122
|
-
const [local] = splitProps(props, ['conversationId', 'active', 'compact', 'leading', 'meta', 'menu', 'children', 'hostSemantics', 'onActivate', 'class']);
|
|
123
|
-
return (
|
|
124
|
-
// The sibling restructure: axe nested-interactive
|
|
125
|
-
// bans focusable descendants of an activation control, so the control role
|
|
126
|
-
// sits on the row BODY and the consumer's menu is its tabbable SIBLING
|
|
127
|
-
// (the nav.tsx TrailingActions precedent). The vocabulary is
|
|
128
|
-
// list/listitem/button + aria-current — NOT listbox/option, because axe
|
|
129
|
-
// aria-required-children lets a listbox subtree own nothing but options
|
|
130
|
-
// (and groups of options), which outlaws a sibling menu ANYWHERE inside
|
|
131
|
-
// it; button + aria-current is also exactly what the batteries-mode
|
|
132
|
-
// ConversationItem row above renders, so the two modes speak one dialect.
|
|
133
|
-
<div
|
|
134
|
-
part="row"
|
|
135
|
-
role={local.hostSemantics ? 'presentation' : 'listitem'}
|
|
136
|
-
data-active={local.active ? '' : undefined}
|
|
137
|
-
data-conversation-id={local.conversationId}
|
|
138
|
-
class={cn(
|
|
139
|
-
'flex w-full items-center gap-2.5 rounded-lg text-left transition-colors',
|
|
140
|
-
local.compact ? 'px-2.5 py-1.5' : 'px-2.5 py-2',
|
|
141
|
-
local.active ? 'bg-muted' : 'hover:bg-muted/50',
|
|
142
|
-
local.class,
|
|
143
|
-
)}
|
|
144
|
-
>
|
|
145
|
-
<div
|
|
146
|
-
part="body"
|
|
147
|
-
role="button"
|
|
148
|
-
data-kai-item-body
|
|
149
|
-
aria-current={local.active ? 'true' : 'false'}
|
|
150
|
-
// Standalone activation only: with `onActivate` unset —
|
|
151
|
-
// the inside-a-container case — no tabindex and no handlers render, so
|
|
152
|
-
// the container's delegated activation stays the single path.
|
|
153
|
-
tabindex={local.onActivate ? 0 : undefined}
|
|
154
|
-
onClick={() => local.onActivate?.()}
|
|
155
|
-
onKeyDown={(e: KeyboardEvent) => {
|
|
156
|
-
if (!local.onActivate) return;
|
|
157
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
158
|
-
e.preventDefault(); // Space must not scroll the page
|
|
159
|
-
local.onActivate();
|
|
160
|
-
}
|
|
161
|
-
}}
|
|
162
|
-
class="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
163
|
-
>
|
|
164
|
-
<Show when={local.leading}>
|
|
165
|
-
<span part="leading" class="flex shrink-0 items-center text-muted-foreground">{local.leading}</span>
|
|
166
|
-
</Show>
|
|
167
|
-
<div class="min-w-0 flex-1">
|
|
168
|
-
<div part="title" class={cn('truncate text-sm', local.active ? 'font-medium text-foreground' : 'text-foreground/80')}>
|
|
169
|
-
{local.children}
|
|
170
|
-
</div>
|
|
171
|
-
<Show when={local.meta}>
|
|
172
|
-
<div part="meta" class="mt-0.5 truncate text-xs text-muted-foreground">{local.meta}</div>
|
|
173
|
-
</Show>
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
<Show when={local.menu}>
|
|
177
|
-
<span part="menu" data-kai-item-menu class="ml-auto flex shrink-0 items-center">{local.menu}</span>
|
|
178
|
-
</Show>
|
|
179
|
-
</div>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function ConversationItem(props: ConversationItemProps) {
|
|
184
|
-
const [local] = splitProps(props, ['conversation', 'isActive', 'onSelect', 'compact', 'class']);
|
|
185
|
-
// The trailing text: the consumer's own `trailing` field, else an auto relative
|
|
186
|
-
// time from updatedAt (fallback lastMessageAt). Never an internal clock — it is a
|
|
187
|
-
// render-time snapshot.
|
|
188
|
-
//
|
|
189
|
-
// REACTIVITY, and the weaker version of this note is what #224 was filed against:
|
|
190
|
-
// a new `conversations` array reference is NOT sufficient. `ConversationList` renders
|
|
191
|
-
// these rows through a reference-keyed `<For>` that captures `conv` as a VALUE, so a
|
|
192
|
-
// row whose item object is unchanged is never re-invoked and never re-reads anything
|
|
193
|
-
// below. The consumer needs BOTH — a new array (which is what notifies at all) and a
|
|
194
|
-
// new object for the item that changed (which is what this row can see). Adds,
|
|
195
|
-
// removes and reorders are fine on a fresh array alone, since those rows' identities
|
|
196
|
-
// already differ. Pinned by `src/components/reactivity-contract.test.tsx`.
|
|
197
|
-
const trailing = createMemo(
|
|
198
|
-
() => local.conversation.trailing ?? relativeTimeShort(local.conversation.updatedAt ?? local.conversation.lastMessageAt),
|
|
199
|
-
);
|
|
200
|
-
return (
|
|
201
|
-
<button
|
|
202
|
-
data-conversation-id={local.conversation.id}
|
|
203
|
-
// `isActive` drives the selected LOOK below; it has to reach assistive tech
|
|
204
|
-
// too, or the active conversation is visible only to sighted users. `true`
|
|
205
|
-
// rather than `page` because this selects a conversation within the app, it
|
|
206
|
-
// is not page navigation — same call as ui/agent-card.tsx and ui/pane-group.tsx
|
|
207
|
-
// (ui/nav.tsx uses `page` because its items really are nav links).
|
|
208
|
-
aria-current={local.isActive ? 'true' : undefined}
|
|
209
|
-
onClick={() => local.onSelect(local.conversation.id)}
|
|
210
|
-
class={cn(
|
|
211
|
-
'w-full rounded-lg text-left transition-colors',
|
|
212
|
-
local.compact ? 'px-2.5 py-1.5' : 'px-2.5 py-2',
|
|
213
|
-
local.isActive ? 'bg-muted' : 'hover:bg-muted/50',
|
|
214
|
-
local.class,
|
|
215
|
-
)}
|
|
216
|
-
>
|
|
217
|
-
<Show
|
|
218
|
-
when={local.compact}
|
|
219
|
-
fallback={
|
|
220
|
-
<>
|
|
221
|
-
<div class="flex items-center gap-2">
|
|
222
|
-
<div class={cn('min-w-0 flex-1 truncate text-sm', local.isActive ? 'font-medium text-foreground' : 'text-foreground/80')}>{local.conversation.title}</div>
|
|
223
|
-
<Show when={trailing()}>
|
|
224
|
-
<span part="trailing" class="ml-auto shrink-0 text-xs text-muted-foreground">{trailing()}</span>
|
|
225
|
-
</Show>
|
|
226
|
-
</div>
|
|
227
|
-
<div class={cn('mt-0.5 truncate text-xs', local.isActive ? 'text-foreground/70' : 'text-muted-foreground')}>{local.conversation.messageCount} messages</div>
|
|
228
|
-
</>
|
|
229
|
-
}
|
|
230
|
-
>
|
|
231
|
-
<div class="flex items-center gap-2.5">
|
|
232
|
-
<MessageSquare class={cn('size-3.5 shrink-0', local.isActive ? 'text-foreground' : 'text-muted-foreground')} />
|
|
233
|
-
<span class={cn('min-w-0 flex-1 truncate text-sm', local.isActive ? 'font-medium text-foreground' : 'text-foreground/80')}>{local.conversation.title}</span>
|
|
234
|
-
<Show when={trailing()}>
|
|
235
|
-
<span part="trailing" class="ml-auto shrink-0 text-xs text-muted-foreground">{trailing()}</span>
|
|
236
|
-
</Show>
|
|
237
|
-
</div>
|
|
238
|
-
</Show>
|
|
239
|
-
</button>
|
|
240
|
-
);
|
|
241
|
-
}
|