@tulipfarm/sprout 0.0.0-stage → 0.2.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 +21 -0
- package/README.md +183 -2
- package/dist/components/accordion.d.ts +28 -0
- package/dist/components/accordion.js +26 -0
- package/dist/components/alert-dialog.d.ts +60 -0
- package/dist/components/alert-dialog.js +69 -0
- package/dist/components/alert.d.ts +30 -0
- package/dist/components/alert.js +35 -0
- package/dist/components/aspect-ratio.d.ts +7 -0
- package/dist/components/aspect-ratio.js +6 -0
- package/dist/components/attachment.d.ts +118 -0
- package/dist/components/attachment.js +285 -0
- package/dist/components/avatar.d.ts +32 -0
- package/dist/components/avatar.js +73 -0
- package/dist/components/badge.d.ts +21 -0
- package/dist/components/badge.js +27 -0
- package/dist/components/brand-icon.d.ts +28 -0
- package/dist/components/brand-icon.js +82 -0
- package/dist/components/breadcrumb.d.ts +29 -0
- package/dist/components/breadcrumb.js +44 -0
- package/dist/components/button-group.d.ts +28 -0
- package/dist/components/button-group.js +36 -0
- package/dist/components/button.d.ts +79 -0
- package/dist/components/button.js +93 -0
- package/dist/components/calendar.d.ts +27 -0
- package/dist/components/calendar.js +107 -0
- package/dist/components/canvas.d.ts +78 -0
- package/dist/components/canvas.js +180 -0
- package/dist/components/card.d.ts +43 -0
- package/dist/components/card.js +42 -0
- package/dist/components/carousel.d.ts +57 -0
- package/dist/components/carousel.js +422 -0
- package/dist/components/chart.d.ts +112 -0
- package/dist/components/chart.js +172 -0
- package/dist/components/chat-confirmation.d.ts +42 -0
- package/dist/components/chat-confirmation.js +62 -0
- package/dist/components/chat-input.d.ts +94 -0
- package/dist/components/chat-input.js +279 -0
- package/dist/components/chat-plan.d.ts +21 -0
- package/dist/components/chat-plan.js +35 -0
- package/dist/components/chat-sources.d.ts +64 -0
- package/dist/components/chat-sources.js +66 -0
- package/dist/components/checkbox.d.ts +16 -0
- package/dist/components/checkbox.js +17 -0
- package/dist/components/choice-list.d.ts +53 -0
- package/dist/components/choice-list.js +36 -0
- package/dist/components/code-block.d.ts +35 -0
- package/dist/components/code-block.js +64 -0
- package/dist/components/collapsible.d.ts +24 -0
- package/dist/components/collapsible.js +27 -0
- package/dist/components/combobox.d.ts +95 -0
- package/dist/components/combobox.js +106 -0
- package/dist/components/command.d.ts +94 -0
- package/dist/components/command.js +414 -0
- package/dist/components/context-menu.d.ts +57 -0
- package/dist/components/context-menu.js +60 -0
- package/dist/components/dialog.d.ts +60 -0
- package/dist/components/dialog.js +79 -0
- package/dist/components/direction.d.ts +6 -0
- package/dist/components/direction.js +6 -0
- package/dist/components/dropdown-menu.d.ts +62 -0
- package/dist/components/dropdown-menu.js +59 -0
- package/dist/components/field.d.ts +117 -0
- package/dist/components/field.js +150 -0
- package/dist/components/file-explorer.d.ts +27 -0
- package/dist/components/file-explorer.js +300 -0
- package/dist/components/file-viewer.d.ts +137 -0
- package/dist/components/file-viewer.js +400 -0
- package/dist/components/form.d.ts +87 -0
- package/dist/components/form.js +55 -0
- package/dist/components/glass-banner.d.ts +27 -0
- package/dist/components/glass-banner.js +116 -0
- package/dist/components/gradient-banner.d.ts +37 -0
- package/dist/components/gradient-banner.js +43 -0
- package/dist/components/hover-card.d.ts +27 -0
- package/dist/components/hover-card.js +26 -0
- package/dist/components/input-group.d.ts +68 -0
- package/dist/components/input-group.js +88 -0
- package/dist/components/input-otp.d.ts +38 -0
- package/dist/components/input-otp.js +43 -0
- package/dist/components/input.d.ts +16 -0
- package/dist/components/input.js +18 -0
- package/dist/components/kbd.d.ts +12 -0
- package/dist/components/kbd.js +11 -0
- package/dist/components/label.d.ts +8 -0
- package/dist/components/label.js +10 -0
- package/dist/components/mention-list.d.ts +54 -0
- package/dist/components/mention-list.js +31 -0
- package/dist/components/menubar.d.ts +64 -0
- package/dist/components/menubar.js +68 -0
- package/dist/components/message-actions.d.ts +34 -0
- package/dist/components/message-actions.js +68 -0
- package/dist/components/message-response.d.ts +20 -0
- package/dist/components/message-response.js +46 -0
- package/dist/components/message-scroller.d.ts +71 -0
- package/dist/components/message-scroller.js +217 -0
- package/dist/components/message.d.ts +49 -0
- package/dist/components/message.js +59 -0
- package/dist/components/navigation-menu.d.ts +45 -0
- package/dist/components/navigation-menu.js +54 -0
- package/dist/components/notice-banner.d.ts +14 -0
- package/dist/components/notice-banner.js +11 -0
- package/dist/components/page-content.d.ts +7 -0
- package/dist/components/page-content.js +9 -0
- package/dist/components/page-header.d.ts +37 -0
- package/dist/components/page-header.js +53 -0
- package/dist/components/pagination.d.ts +29 -0
- package/dist/components/pagination.js +29 -0
- package/dist/components/plan-outline.d.ts +60 -0
- package/dist/components/plan-outline.js +108 -0
- package/dist/components/popover.d.ts +34 -0
- package/dist/components/popover.js +35 -0
- package/dist/components/progress.d.ts +27 -0
- package/dist/components/progress.js +35 -0
- package/dist/components/prompt-list.d.ts +23 -0
- package/dist/components/prompt-list.js +11 -0
- package/dist/components/questionnaire.d.ts +148 -0
- package/dist/components/questionnaire.js +1002 -0
- package/dist/components/radio-group.d.ts +16 -0
- package/dist/components/radio-group.js +21 -0
- package/dist/components/reasoning-effort.d.ts +24 -0
- package/dist/components/reasoning-effort.js +269 -0
- package/dist/components/resizable.d.ts +67 -0
- package/dist/components/resizable.js +536 -0
- package/dist/components/result-card.d.ts +58 -0
- package/dist/components/result-card.js +88 -0
- package/dist/components/review-card.d.ts +62 -0
- package/dist/components/review-card.js +49 -0
- package/dist/components/rich-text-editor.d.ts +51 -0
- package/dist/components/rich-text-editor.js +248 -0
- package/dist/components/schema-diagram.d.ts +52 -0
- package/dist/components/schema-diagram.js +226 -0
- package/dist/components/scroll-area.d.ts +22 -0
- package/dist/components/scroll-area.js +19 -0
- package/dist/components/search-dialog.d.ts +103 -0
- package/dist/components/search-dialog.js +65 -0
- package/dist/components/search-field.d.ts +17 -0
- package/dist/components/search-field.js +27 -0
- package/dist/components/select.d.ts +57 -0
- package/dist/components/select.js +57 -0
- package/dist/components/selection-slot.d.ts +18 -0
- package/dist/components/selection-slot.js +53 -0
- package/dist/components/separator.d.ts +48 -0
- package/dist/components/separator.js +54 -0
- package/dist/components/settings-list.d.ts +26 -0
- package/dist/components/settings-list.js +17 -0
- package/dist/components/sheet.d.ts +70 -0
- package/dist/components/sheet.js +77 -0
- package/dist/components/sidebar.d.ts +70 -0
- package/dist/components/sidebar.js +187 -0
- package/dist/components/skeleton.d.ts +31 -0
- package/dist/components/skeleton.js +32 -0
- package/dist/components/slider.d.ts +17 -0
- package/dist/components/slider.js +24 -0
- package/dist/components/spinner.d.ts +37 -0
- package/dist/components/spinner.js +43 -0
- package/dist/components/state-panel-samples.d.ts +25 -0
- package/dist/components/state-panel-samples.js +423 -0
- package/dist/components/state-panel.d.ts +35 -0
- package/dist/components/state-panel.js +29 -0
- package/dist/components/state-sample.d.ts +63 -0
- package/dist/components/state-sample.js +42 -0
- package/dist/components/switch.d.ts +12 -0
- package/dist/components/switch.js +14 -0
- package/dist/components/table-nuqs.d.ts +16 -0
- package/dist/components/table-nuqs.js +59 -0
- package/dist/components/table.d.ts +120 -0
- package/dist/components/table.js +663 -0
- package/dist/components/tabs.d.ts +37 -0
- package/dist/components/tabs.js +36 -0
- package/dist/components/textarea.d.ts +18 -0
- package/dist/components/textarea.js +19 -0
- package/dist/components/toast.d.ts +45 -0
- package/dist/components/toast.js +83 -0
- package/dist/components/toggle-group.d.ts +30 -0
- package/dist/components/toggle-group.js +39 -0
- package/dist/components/toggle.d.ts +27 -0
- package/dist/components/toggle.js +21 -0
- package/dist/components/tool-activity.d.ts +79 -0
- package/dist/components/tool-activity.js +226 -0
- package/dist/components/toolbar-status.d.ts +21 -0
- package/dist/components/toolbar-status.js +14 -0
- package/dist/components/tooltip.d.ts +31 -0
- package/dist/components/tooltip.js +99 -0
- package/dist/components/typed-text.d.ts +17 -0
- package/dist/components/typed-text.js +56 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +90 -0
- package/dist/internal/brand-glyphs.d.ts +370 -0
- package/dist/internal/brand-glyphs.js +262 -0
- package/dist/internal/classes.d.ts +3 -0
- package/dist/internal/classes.js +20 -0
- package/dist/internal/code-tokens.d.ts +4 -0
- package/dist/internal/code-tokens.js +34 -0
- package/dist/internal/fieldset.d.ts +6 -0
- package/dist/internal/fieldset.js +38 -0
- package/dist/internal/file-kind.d.ts +4 -0
- package/dist/internal/file-kind.js +62 -0
- package/dist/internal/languages.d.ts +7 -0
- package/dist/internal/languages.js +1010 -0
- package/dist/internal/message-scroller.d.ts +102 -0
- package/dist/internal/message-scroller.js +637 -0
- package/dist/internal/pressable.d.ts +2 -0
- package/dist/internal/pressable.js +29 -0
- package/dist/internal/syntax.d.ts +14 -0
- package/dist/internal/syntax.js +137 -0
- package/dist/styles/accordion.css +102 -0
- package/dist/styles/alert-dialog.css +198 -0
- package/dist/styles/alert.css +149 -0
- package/dist/styles/aspect-ratio.css +8 -0
- package/dist/styles/attachment.css +700 -0
- package/dist/styles/avatar.css +186 -0
- package/dist/styles/badge.css +178 -0
- package/dist/styles/brand-icon.css +104 -0
- package/dist/styles/breadcrumb.css +121 -0
- package/dist/styles/button-group.css +113 -0
- package/dist/styles/button.css +380 -0
- package/dist/styles/calendar.css +295 -0
- package/dist/styles/canvas.css +313 -0
- package/dist/styles/card.css +146 -0
- package/dist/styles/carousel.css +107 -0
- package/dist/styles/chart.css +227 -0
- package/dist/styles/chat-confirmation.css +29 -0
- package/dist/styles/chat-input.css +240 -0
- package/dist/styles/chat-plan.css +109 -0
- package/dist/styles/chat-sources.css +191 -0
- package/dist/styles/checkbox.css +122 -0
- package/dist/styles/choice-list.css +343 -0
- package/dist/styles/code-block.css +211 -0
- package/dist/styles/collapsible.css +72 -0
- package/dist/styles/combobox.css +347 -0
- package/dist/styles/command.css +233 -0
- package/dist/styles/context-menu.css +151 -0
- package/dist/styles/dialog.css +237 -0
- package/dist/styles/dropdown-menu.css +150 -0
- package/dist/styles/field.css +340 -0
- package/dist/styles/file-explorer.css +517 -0
- package/dist/styles/file-viewer.css +375 -0
- package/dist/styles/form.css +155 -0
- package/dist/styles/glass-banner.css +308 -0
- package/dist/styles/gradient-banner.css +256 -0
- package/dist/styles/hover-card.css +95 -0
- package/dist/styles/index.css +268 -0
- package/dist/styles/input-group.css +285 -0
- package/dist/styles/input-otp.css +138 -0
- package/dist/styles/input.css +98 -0
- package/dist/styles/kbd.css +59 -0
- package/dist/styles/label.css +89 -0
- package/dist/styles/mention-list.css +166 -0
- package/dist/styles/menubar.css +226 -0
- package/dist/styles/message-actions.css +83 -0
- package/dist/styles/message-response.css +176 -0
- package/dist/styles/message-scroller.css +136 -0
- package/dist/styles/message.css +310 -0
- package/dist/styles/navigation-menu.css +291 -0
- package/dist/styles/notice-banner.css +68 -0
- package/dist/styles/page-content.css +21 -0
- package/dist/styles/page-header.css +156 -0
- package/dist/styles/pagination.css +72 -0
- package/dist/styles/plan-outline.css +393 -0
- package/dist/styles/popover.css +140 -0
- package/dist/styles/progress.css +93 -0
- package/dist/styles/prompt-list.css +165 -0
- package/dist/styles/questionnaire.css +308 -0
- package/dist/styles/radio-group.css +119 -0
- package/dist/styles/reasoning-effort.css +157 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/result-card.css +215 -0
- package/dist/styles/review-card.css +235 -0
- package/dist/styles/rich-text-editor.css +169 -0
- package/dist/styles/schema-diagram.css +419 -0
- package/dist/styles/scroll-area.css +77 -0
- package/dist/styles/search-dialog.css +280 -0
- package/dist/styles/search-field.css +17 -0
- package/dist/styles/select.css +285 -0
- package/dist/styles/selection-slot.css +31 -0
- package/dist/styles/separator.css +206 -0
- package/dist/styles/settings-list.css +81 -0
- package/dist/styles/sheet.css +361 -0
- package/dist/styles/sidebar.css +363 -0
- package/dist/styles/skeleton.css +80 -0
- package/dist/styles/slider.css +142 -0
- package/dist/styles/spinner.css +39 -0
- package/dist/styles/state-panel-samples.css +41 -0
- package/dist/styles/state-panel.css +173 -0
- package/dist/styles/state-sample.css +285 -0
- package/dist/styles/switch.css +128 -0
- package/dist/styles/table.css +604 -0
- package/dist/styles/tabs.css +223 -0
- package/dist/styles/textarea.css +77 -0
- package/dist/styles/themes/chatgpt.css +125 -0
- package/dist/styles/themes/claude.css +125 -0
- package/dist/styles/themes/cloudflare.css +125 -0
- package/dist/styles/themes/discord.css +125 -0
- package/dist/styles/themes/github.css +125 -0
- package/dist/styles/themes/grok.css +125 -0
- package/dist/styles/themes/linear.css +126 -0
- package/dist/styles/themes/neutral.css +125 -0
- package/dist/styles/themes/notion.css +125 -0
- package/dist/styles/themes/perplexity.css +125 -0
- package/dist/styles/themes/raycast.css +125 -0
- package/dist/styles/themes/spotify.css +125 -0
- package/dist/styles/themes/stripe.css +125 -0
- package/dist/styles/themes/supabase.css +125 -0
- package/dist/styles/themes/tulipfarm.css +115 -0
- package/dist/styles/themes/vercel.css +126 -0
- package/dist/styles/toast.css +197 -0
- package/dist/styles/toggle-group.css +161 -0
- package/dist/styles/toggle.css +119 -0
- package/dist/styles/tool-activity.css +396 -0
- package/dist/styles/toolbar-status.css +52 -0
- package/dist/styles/tooltip.css +122 -0
- package/dist/styles/typed-text.css +17 -0
- package/package.json +452 -4
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
4
|
+
import { Extension, Node, mergeAttributes, } from "@tiptap/core";
|
|
5
|
+
import { PluginKey } from "@tiptap/pm/state";
|
|
6
|
+
import Suggestion, { exitSuggestion, } from "@tiptap/suggestion";
|
|
7
|
+
import { ArrowUp } from "reicon-react/icons/ArrowUp";
|
|
8
|
+
import { ChevronDown } from "reicon-react/icons/ChevronDown";
|
|
9
|
+
import { Folder } from "reicon-react/icons/Folder";
|
|
10
|
+
import { Plus } from "reicon-react/icons/Plus";
|
|
11
|
+
import { Sliders } from "reicon-react/icons/Sliders";
|
|
12
|
+
import { X } from "reicon-react/icons/X";
|
|
13
|
+
import { cn } from "../internal/classes.js";
|
|
14
|
+
import { FileAttachment } from "./attachment.js";
|
|
15
|
+
import { BrandIcon } from "./brand-icon.js";
|
|
16
|
+
import { Button } from "./button.js";
|
|
17
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "./command.js";
|
|
18
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
19
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, } from "./input-group.js";
|
|
20
|
+
import { MentionList, MentionOption, MentionStatus } from "./mention-list.js";
|
|
21
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
22
|
+
import { RichTextEditor, } from "./rich-text-editor.js";
|
|
23
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
24
|
+
export const chatInputReferenceKindNames = ["agent", "skill", "file"];
|
|
25
|
+
export const chatInputStatusNames = ["idle", "streaming"];
|
|
26
|
+
const triggers = {
|
|
27
|
+
agent: { char: "@", label: "Agents", empty: "No agents match." },
|
|
28
|
+
skill: { char: "/", label: "Skills", empty: "No skills match." },
|
|
29
|
+
file: { char: "#", label: "Files", empty: "No files match." },
|
|
30
|
+
};
|
|
31
|
+
const referenceNode = Node.create({
|
|
32
|
+
name: "chatReference",
|
|
33
|
+
inline: true,
|
|
34
|
+
group: "inline",
|
|
35
|
+
atom: true,
|
|
36
|
+
selectable: true,
|
|
37
|
+
addAttributes() {
|
|
38
|
+
return {
|
|
39
|
+
kind: {
|
|
40
|
+
default: "file",
|
|
41
|
+
rendered: false,
|
|
42
|
+
parseHTML: (el) => el.getAttribute("data-chat-reference"),
|
|
43
|
+
},
|
|
44
|
+
id: {
|
|
45
|
+
default: "",
|
|
46
|
+
rendered: false,
|
|
47
|
+
parseHTML: (el) => el.getAttribute("data-id"),
|
|
48
|
+
},
|
|
49
|
+
label: {
|
|
50
|
+
default: "",
|
|
51
|
+
rendered: false,
|
|
52
|
+
parseHTML: (el) => el.getAttribute("data-label"),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
parseHTML() {
|
|
57
|
+
return [{ tag: "span[data-chat-reference]" }];
|
|
58
|
+
},
|
|
59
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
60
|
+
const kind = node.attrs.kind;
|
|
61
|
+
return [
|
|
62
|
+
"span",
|
|
63
|
+
mergeAttributes(HTMLAttributes, {
|
|
64
|
+
"data-chat-reference": kind,
|
|
65
|
+
"data-id": node.attrs.id,
|
|
66
|
+
"data-label": node.attrs.label,
|
|
67
|
+
class: "sprout-chat-input-reference",
|
|
68
|
+
}),
|
|
69
|
+
`${triggers[kind]?.char ?? ""}${node.attrs.label}`,
|
|
70
|
+
];
|
|
71
|
+
},
|
|
72
|
+
renderText({ node }) {
|
|
73
|
+
const kind = node.attrs.kind;
|
|
74
|
+
return `${triggers[kind]?.char ?? ""}${node.attrs.label}`;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const linkShortcut = Extension.create({
|
|
78
|
+
name: "chatInputLinkShortcut",
|
|
79
|
+
addKeyboardShortcuts() {
|
|
80
|
+
return {
|
|
81
|
+
"Mod-k": ({ editor }) => {
|
|
82
|
+
if (editor.isActive("link"))
|
|
83
|
+
return editor.chain().focus().unsetLink().run();
|
|
84
|
+
const { from, to } = editor.state.selection;
|
|
85
|
+
const text = editor.state.doc.textBetween(from, to).trim();
|
|
86
|
+
const href = /^\S+@\S+\.\S+$/.test(text)
|
|
87
|
+
? `mailto:${text}`
|
|
88
|
+
: /^(https?:\/\/|www\.)\S+$/i.test(text)
|
|
89
|
+
? text.startsWith("www.")
|
|
90
|
+
? `https://${text}`
|
|
91
|
+
: text
|
|
92
|
+
: null;
|
|
93
|
+
if (!href)
|
|
94
|
+
return false;
|
|
95
|
+
return editor.chain().focus().setLink({ href }).run();
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
function triggerExtension(kind, source, onState) {
|
|
101
|
+
const key = new PluginKey(`chatInput-${kind}`);
|
|
102
|
+
return Extension.create({
|
|
103
|
+
name: `chatInput-${kind}`,
|
|
104
|
+
addProseMirrorPlugins() {
|
|
105
|
+
return [
|
|
106
|
+
Suggestion({
|
|
107
|
+
editor: this.editor,
|
|
108
|
+
pluginKey: key,
|
|
109
|
+
char: triggers[kind].char,
|
|
110
|
+
allowSpaces: false,
|
|
111
|
+
items: async ({ query }) => (await source.current?.(query)) ?? [],
|
|
112
|
+
command: ({ editor, range, props }) => editor
|
|
113
|
+
.chain()
|
|
114
|
+
.focus()
|
|
115
|
+
.insertContentAt(range, [
|
|
116
|
+
{
|
|
117
|
+
type: "chatReference",
|
|
118
|
+
attrs: { kind, id: props.id, label: props.label },
|
|
119
|
+
},
|
|
120
|
+
{ type: "text", text: " " },
|
|
121
|
+
])
|
|
122
|
+
.run(),
|
|
123
|
+
render: () => {
|
|
124
|
+
let current = null;
|
|
125
|
+
let active = 0;
|
|
126
|
+
const publish = () => {
|
|
127
|
+
if (!current)
|
|
128
|
+
return;
|
|
129
|
+
const items = current.items;
|
|
130
|
+
onState({
|
|
131
|
+
kind,
|
|
132
|
+
items,
|
|
133
|
+
active: Math.min(active, Math.max(0, items.length - 1)),
|
|
134
|
+
select: (index) => {
|
|
135
|
+
const item = items[index];
|
|
136
|
+
if (item)
|
|
137
|
+
current?.command(item);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
onStart: (props) => {
|
|
143
|
+
current = props;
|
|
144
|
+
active = 0;
|
|
145
|
+
publish();
|
|
146
|
+
},
|
|
147
|
+
onUpdate: (props) => {
|
|
148
|
+
current = props;
|
|
149
|
+
active = 0;
|
|
150
|
+
publish();
|
|
151
|
+
},
|
|
152
|
+
onKeyDown: ({ event, view }) => {
|
|
153
|
+
if (event.key === "Escape") {
|
|
154
|
+
exitSuggestion(view, key);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
const count = current?.items.length ?? 0;
|
|
158
|
+
if (!count)
|
|
159
|
+
return false;
|
|
160
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
161
|
+
active =
|
|
162
|
+
(active + (event.key === "ArrowDown" ? 1 : count - 1)) %
|
|
163
|
+
count;
|
|
164
|
+
publish();
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if ((event.key === "Enter" && !event.shiftKey) ||
|
|
168
|
+
event.key === "Tab") {
|
|
169
|
+
current.command(current.items[active]);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
},
|
|
174
|
+
onExit: () => {
|
|
175
|
+
current = null;
|
|
176
|
+
onState(null);
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
];
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function hasContent(node) {
|
|
186
|
+
if (node.type === "chatReference")
|
|
187
|
+
return true;
|
|
188
|
+
if (node.text?.trim())
|
|
189
|
+
return true;
|
|
190
|
+
return node.content?.some(hasContent) ?? false;
|
|
191
|
+
}
|
|
192
|
+
function textDoc(text) {
|
|
193
|
+
return {
|
|
194
|
+
type: "doc",
|
|
195
|
+
content: [{ type: "paragraph", content: [{ type: "text", text }] }],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function ModelMark({ model }) {
|
|
199
|
+
if (model.brand)
|
|
200
|
+
return _jsx(BrandIcon, { brand: model.brand, size: "inline" });
|
|
201
|
+
return model.icon ?? null;
|
|
202
|
+
}
|
|
203
|
+
function OptionPicker({ label, search, options, value, onChange, disabled, icon, align, }) {
|
|
204
|
+
const [open, setOpen] = useState(false);
|
|
205
|
+
const selected = options.find((option) => option.id === value);
|
|
206
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(InputGroupButton, { size: "sm", "aria-label": `${label}: ${selected?.label ?? "None"}`, disabled: disabled, className: "sprout-chat-input-picker" }), children: [icon(selected), _jsx("span", { className: "sprout-chat-input-picker-label", children: selected?.label ?? label }), _jsx(ChevronDown, { size: 12, "aria-hidden": "true" })] }), _jsx(PopoverContent, { size: "picker", side: "top", align: align, sideOffset: 8, className: "sprout-chat-input-menu", children: _jsxs(Command, { children: [_jsx(CommandInput, { "aria-label": search, placeholder: `${search}…` }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "No matches." }), _jsx(CommandGroup, { heading: label, children: options.map((option) => (_jsxs(CommandItem, { value: `${option.label} ${option.description ?? ""}`, "data-checked": option.id === value, disabled: "disabled" in option && !!option.disabled, onSelect: () => {
|
|
207
|
+
onChange(option.id);
|
|
208
|
+
setOpen(false);
|
|
209
|
+
}, children: [icon(option), _jsxs("span", { className: "sprout-chat-input-option-text", children: [_jsx("span", { children: option.label }), option.description && (_jsx("span", { className: "sprout-chat-input-option-description", children: option.description }))] })] }, option.id))) })] })] }) })] }));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* A chat message box on `RichTextEditor`. `@` tags agents, `/` adds skills,
|
|
213
|
+
* and `#` adds files. The bar holds attach and mode menus, a project picker,
|
|
214
|
+
* a model picker, and send or stop. Starter prompts sit under an empty box.
|
|
215
|
+
*/
|
|
216
|
+
export function ChatInput({ value, onValueChange, onSend, onStop, status = "idle", placeholder = "Ask anything, @agents, /skills, #files…", streamingPlaceholder = "Working on your request…", disabled = false, label = "Message", agents, skills, files, attachments = [], onRemoveAttachment, attachActions, modes, mode = null, onModeChange, projects, project, onProjectChange, models, model, onModelChange, prompts, onPromptSelect, className, }) {
|
|
217
|
+
const editor = useRef(null);
|
|
218
|
+
const form = useRef(null);
|
|
219
|
+
const listId = useId();
|
|
220
|
+
const [panel, setPanel] = useState(null);
|
|
221
|
+
const [panelBelow, setPanelBelow] = useState(false);
|
|
222
|
+
const sources = {
|
|
223
|
+
agent: useRef(agents),
|
|
224
|
+
skill: useRef(skills),
|
|
225
|
+
file: useRef(files),
|
|
226
|
+
};
|
|
227
|
+
sources.agent.current = agents;
|
|
228
|
+
sources.skill.current = skills;
|
|
229
|
+
sources.file.current = files;
|
|
230
|
+
const enabled = `${!!agents}${!!skills}${!!files}`;
|
|
231
|
+
const extensions = useMemo(() => [
|
|
232
|
+
referenceNode,
|
|
233
|
+
linkShortcut,
|
|
234
|
+
...chatInputReferenceKindNames
|
|
235
|
+
.filter((kind) => sources[kind].current)
|
|
236
|
+
.map((kind) => triggerExtension(kind, sources[kind], setPanel)),
|
|
237
|
+
],
|
|
238
|
+
// Sources are refs, so only adding or removing a trigger rebuilds the editor.
|
|
239
|
+
[enabled]);
|
|
240
|
+
const streaming = status === "streaming";
|
|
241
|
+
const filled = hasContent(value) || attachments.length > 0;
|
|
242
|
+
const canSend = filled && !disabled && !streaming;
|
|
243
|
+
const activeMode = modes?.find((option) => option.id === mode);
|
|
244
|
+
const optionId = (index) => `${listId}-option-${index}`;
|
|
245
|
+
const panelOpen = !!panel;
|
|
246
|
+
useLayoutEffect(() => {
|
|
247
|
+
const bounds = form.current?.getBoundingClientRect();
|
|
248
|
+
// Near the top of a short screen there's no room above, so the panel opens below.
|
|
249
|
+
if (panel && bounds)
|
|
250
|
+
setPanelBelow(bounds.top < 320 && window.innerHeight - bounds.bottom > bounds.top);
|
|
251
|
+
}, [panelOpen]);
|
|
252
|
+
const send = () => {
|
|
253
|
+
if (canSend)
|
|
254
|
+
onSend();
|
|
255
|
+
};
|
|
256
|
+
const fill = (prompt) => {
|
|
257
|
+
editor.current?.setContent(textDoc(prompt.prompt ?? prompt.label));
|
|
258
|
+
editor.current?.editor()?.commands.focus("end");
|
|
259
|
+
onPromptSelect?.(prompt);
|
|
260
|
+
};
|
|
261
|
+
return (_jsxs("div", { "data-slot": "chat-input", "data-status": status, "data-mode-color": activeMode?.color ? "" : undefined, style: activeMode?.color
|
|
262
|
+
? {
|
|
263
|
+
"--sprout-chat-input-mode": activeMode.color,
|
|
264
|
+
}
|
|
265
|
+
: undefined, className: cn("sprout-chat-input", className), children: [_jsxs("form", { ref: form, "aria-label": label, className: "sprout-chat-input-form", onSubmit: (event) => {
|
|
266
|
+
event.preventDefault();
|
|
267
|
+
send();
|
|
268
|
+
}, children: [_jsxs(InputGroup, { variant: "composer", className: "sprout-chat-input-box", children: [attachments.length > 0 && (_jsx(InputGroupAddon, { align: "block-start", className: "sprout-chat-input-attachments", children: attachments.map((file) => (_jsx(FileAttachment, { file: file, variant: "chip", viewable: true, preview: true, onRemove: onRemoveAttachment && !disabled
|
|
269
|
+
? () => onRemoveAttachment(file.id)
|
|
270
|
+
: undefined }, file.id))) })), _jsx(RichTextEditor, { ref: editor, value: value, onChange: onValueChange, onSend: send, extensions: extensions, placeholder: streaming ? streamingPlaceholder : placeholder, disabled: disabled, ariaLabel: label, ariaExpanded: !!panel, ariaControls: panel ? listId : undefined, ariaActiveDescendant: panel?.items.length ? optionId(panel.active) : undefined, className: "sprout-chat-input-editor", toolbarAddon: _jsxs("div", { className: "sprout-chat-input-bar", children: [_jsxs("div", { className: "sprout-chat-input-bar-start", children: [attachActions && attachActions.length > 0 && (_jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(DropdownMenuTrigger, { render: _jsx(InputGroupButton, { size: "icon-sm", "aria-label": "Add files and more", disabled: disabled }) }), children: _jsx(Plus, { size: 16, "aria-hidden": "true" }) }), _jsx(TooltipContent, { children: "Add files and more" })] }), _jsx(DropdownMenuContent, { size: "menu", side: "top", align: "start", className: "sprout-chat-input-menu", children: _jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: "Add" }), attachActions.map((action) => (_jsxs(DropdownMenuItem, { onClick: action.onSelect, children: [action.icon, _jsxs("span", { className: "sprout-chat-input-option-text", children: [_jsx("span", { children: action.label }), action.description && (_jsx("span", { className: "sprout-chat-input-option-description", children: action.description }))] })] }, action.id)))] }) })] })), modes && modes.length > 0 && (_jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(DropdownMenuTrigger, { render: _jsx(InputGroupButton, { size: "icon-sm", "aria-label": `Mode: ${activeMode?.label ?? "Default"}`, disabled: disabled }) }), children: _jsx(Sliders, { size: 16, "aria-hidden": "true" }) }), _jsx(TooltipContent, { children: "Modes" })] }), _jsx(DropdownMenuContent, { size: "menu", side: "top", align: "start", className: "sprout-chat-input-menu", children: _jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: "Mode" }), _jsx(DropdownMenuRadioGroup, { value: mode ?? "", onValueChange: (next) => onModeChange?.(next === mode || !next ? null : String(next)), children: modes.map((option) => (_jsxs(DropdownMenuRadioItem, { value: option.id, closeOnClick: true, style: option.color
|
|
271
|
+
? {
|
|
272
|
+
"--sprout-chat-input-mode": option.color,
|
|
273
|
+
}
|
|
274
|
+
: undefined, className: option.color
|
|
275
|
+
? "sprout-chat-input-mode-option"
|
|
276
|
+
: undefined, children: [option.icon, _jsxs("span", { className: "sprout-chat-input-option-text", children: [_jsx("span", { children: option.label }), option.description && (_jsx("span", { className: "sprout-chat-input-option-description", children: option.description }))] })] }, option.id))) })] }) })] })), activeMode && (_jsxs(InputGroupButton, { size: "sm", variant: "secondary", className: "sprout-chat-input-mode", "aria-label": `Turn off ${activeMode.label}`, disabled: disabled, onClick: () => onModeChange?.(null), children: [activeMode.icon, _jsx("span", { className: "sprout-chat-input-picker-label", children: activeMode.label }), _jsx(X, { size: 12, "aria-hidden": "true" })] })), projects && projects.length > 0 && (_jsxs(_Fragment, { children: [_jsx("span", { className: "sprout-chat-input-divider", "aria-hidden": "true" }), _jsx(OptionPicker, { label: "Project", search: "Search projects", options: projects, value: project, onChange: (id) => onProjectChange?.(id), disabled: disabled, icon: (option) => option?.icon ?? (_jsx(Folder, { size: 16, "aria-hidden": "true" })), align: "start" })] }))] }), _jsxs("div", { className: "sprout-chat-input-bar-end", children: [models && models.length > 0 && (_jsx(OptionPicker, { label: "Model", search: "Search models", options: models, value: model, onChange: (id) => onModelChange?.(id), disabled: disabled, icon: (option) => option && _jsx(ModelMark, { model: option }), align: "end" })), streaming ? (_jsx(InputGroupButton, { size: "icon-sm", variant: "default", className: "sprout-chat-input-send", "aria-label": "Stop", onClick: onStop, children: _jsx("span", { className: "sprout-chat-input-stop", "aria-hidden": "true" }) })) : (_jsx(InputGroupButton, { type: "submit", size: "icon-sm", variant: canSend ? "default" : "secondary", className: "sprout-chat-input-send", "aria-label": "Send message", disabled: !canSend, children: _jsx(ArrowUp, { size: 16, "aria-hidden": "true" }) }))] })] }) })] }), panel && (_jsx("div", { className: "sprout-chat-input-panel", "data-side": panelBelow ? "bottom" : "top", children: _jsx(MentionList, { id: listId, label: triggers[panel.kind].label, announcement: panel.items.length
|
|
277
|
+
? `${panel.items.length} ${triggers[panel.kind].label.toLowerCase()}. Use Up and Down to choose, Enter to add, Escape to close.`
|
|
278
|
+
: triggers[panel.kind].empty, status: panel.items.length ? undefined : (_jsx(MentionStatus, { tone: "empty", children: triggers[panel.kind].empty })), children: panel.items.map((item, index) => (_jsx(MentionOption, { id: optionId(index), active: index === panel.active, onSelect: () => panel.select(index), media: item.media, title: item.label, description: item.description }, item.id))) }) }))] }), prompts && prompts.length > 0 && !filled && !streaming && (_jsx("div", { role: "group", "aria-label": "Suggested prompts", className: "sprout-chat-input-prompts", children: prompts.map((prompt) => (_jsxs(Button, { size: "sm", variant: "outline", disabled: disabled, className: "sprout-chat-input-prompt", onClick: () => fill(prompt), children: [prompt.icon, prompt.label] }, prompt.id))) }))] }));
|
|
279
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
export declare const chatPlanStepStateNames: readonly ["pending", "running", "complete", "error"];
|
|
3
|
+
export type ChatPlanStepState = (typeof chatPlanStepStateNames)[number];
|
|
4
|
+
export type ChatPlanStep = {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
/** One line under the title, such as why a step failed. */
|
|
8
|
+
detail?: string | undefined;
|
|
9
|
+
state: ChatPlanStepState;
|
|
10
|
+
};
|
|
11
|
+
export interface ChatPlanProps extends Omit<ComponentProps<"section">, "title" | "children"> {
|
|
12
|
+
/** The plan name. It names the section and shows above the steps. */
|
|
13
|
+
title: string;
|
|
14
|
+
/** Steps in order. With none, the plan says no steps were proposed yet. */
|
|
15
|
+
steps: readonly ChatPlanStep[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A plan an agent proposed in chat, with each step’s progress: not started,
|
|
19
|
+
* in progress, complete, or failed. The header counts completed steps.
|
|
20
|
+
*/
|
|
21
|
+
export declare function ChatPlan({ title, steps, className, ...props }: ChatPlanProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check } from "reicon-react/icons/Check";
|
|
3
|
+
import { Clock } from "reicon-react/icons/Clock";
|
|
4
|
+
import { Warning } from "reicon-react/icons/Warning";
|
|
5
|
+
import { cn } from "../internal/classes.js";
|
|
6
|
+
import { Spinner } from "./spinner.js";
|
|
7
|
+
export const chatPlanStepStateNames = [
|
|
8
|
+
"pending",
|
|
9
|
+
"running",
|
|
10
|
+
"complete",
|
|
11
|
+
"error",
|
|
12
|
+
];
|
|
13
|
+
const stateLabels = {
|
|
14
|
+
pending: "Not started",
|
|
15
|
+
running: "In progress",
|
|
16
|
+
complete: "Complete",
|
|
17
|
+
error: "Failed",
|
|
18
|
+
};
|
|
19
|
+
function StepIcon({ state }) {
|
|
20
|
+
if (state === "running")
|
|
21
|
+
return _jsx(Spinner, { "aria-hidden": "true" });
|
|
22
|
+
if (state === "complete")
|
|
23
|
+
return _jsx(Check, { size: 16, "aria-hidden": "true" });
|
|
24
|
+
if (state === "error")
|
|
25
|
+
return _jsx(Warning, { size: 16, "aria-hidden": "true" });
|
|
26
|
+
return _jsx(Clock, { size: 16, "aria-hidden": "true" });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A plan an agent proposed in chat, with each step’s progress: not started,
|
|
30
|
+
* in progress, complete, or failed. The header counts completed steps.
|
|
31
|
+
*/
|
|
32
|
+
export function ChatPlan({ title, steps, className, ...props }) {
|
|
33
|
+
const complete = steps.filter((step) => step.state === "complete").length;
|
|
34
|
+
return (_jsxs("section", { "data-slot": "chat-plan", "aria-label": title, ...props, className: cn("sprout-chat-plan", className), children: [_jsxs("div", { className: "sprout-chat-plan-header", children: [_jsx("h4", { className: "sprout-chat-plan-title", children: title }), _jsxs("span", { className: "sprout-chat-plan-count", children: [complete, " of ", steps.length, " complete"] })] }), steps.length === 0 ? (_jsx("p", { className: "sprout-chat-plan-empty", children: "No steps have been proposed yet." })) : (_jsx("ol", { className: "sprout-chat-plan-steps", children: steps.map((step) => (_jsxs("li", { "data-slot": "chat-plan-step", "data-state": step.state, className: "sprout-chat-plan-step", children: [_jsx("span", { className: "sprout-chat-plan-icon", children: _jsx(StepIcon, { state: step.state }) }, step.state), _jsxs("div", { className: "sprout-chat-plan-text", children: [_jsxs("p", { className: "sprout-chat-plan-step-title", children: [step.title, _jsxs("span", { className: "sprout-chat-plan-sr-only", children: [" · ", stateLabels[step.state]] })] }), step.detail && (_jsx("p", { className: "sprout-chat-plan-detail", children: step.detail }))] })] }, step.id))) }))] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
2
|
+
export type ChatSource = {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
/** Where the passage lives, such as a collection or a heading. */
|
|
6
|
+
location: string;
|
|
7
|
+
excerpt: string;
|
|
8
|
+
/** Opens the source in a new tab. */
|
|
9
|
+
href: string;
|
|
10
|
+
};
|
|
11
|
+
export interface InlineCitationProps {
|
|
12
|
+
source: ChatSource;
|
|
13
|
+
/** The citation number shown in the reply and in the sources list. */
|
|
14
|
+
number: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A numbered citation inside a reply. Hovering, clicking, tapping, or pressing
|
|
18
|
+
* Enter shows the cited passage and where it comes from.
|
|
19
|
+
*/
|
|
20
|
+
export declare function InlineCitation({ source, number }: InlineCitationProps): import("react").JSX.Element;
|
|
21
|
+
export interface ChatSourcesProps extends Omit<ComponentProps<"div">, "children"> {
|
|
22
|
+
/** Sources in citation order. With none, it says no sources were used. */
|
|
23
|
+
sources: readonly ChatSource[];
|
|
24
|
+
/** Start with the list open. */
|
|
25
|
+
defaultOpen?: boolean | undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The sources a reply used. A count opens the numbered list; numbers match
|
|
29
|
+
* each `InlineCitation`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ChatSources({ sources, defaultOpen, className, ...props }: ChatSourcesProps): import("react").JSX.Element;
|
|
32
|
+
export declare const citationStateKindNames: readonly ["idle", "loading", "ready", "unavailable", "error"];
|
|
33
|
+
export type CitationStateKind = (typeof citationStateKindNames)[number];
|
|
34
|
+
export type CitationState = {
|
|
35
|
+
kind: "idle";
|
|
36
|
+
} | {
|
|
37
|
+
kind: "loading";
|
|
38
|
+
} | {
|
|
39
|
+
kind: "ready";
|
|
40
|
+
title: string;
|
|
41
|
+
location: string;
|
|
42
|
+
excerpt: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "unavailable";
|
|
45
|
+
message: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "error";
|
|
48
|
+
message: string;
|
|
49
|
+
};
|
|
50
|
+
export interface CheckedCitationProps {
|
|
51
|
+
/** The linked words in the reply. */
|
|
52
|
+
label: string;
|
|
53
|
+
state: CitationState;
|
|
54
|
+
/** Called each time the passage opens, to check access again. */
|
|
55
|
+
onOpen: () => void;
|
|
56
|
+
/** Called by “Load source again” after an error. */
|
|
57
|
+
onRetry: () => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A citation link whose passage is checked each time it opens, on hover or
|
|
61
|
+
* click. While checking it shows a spinner; an unavailable source never shows
|
|
62
|
+
* an old passage.
|
|
63
|
+
*/
|
|
64
|
+
export declare function CheckedCitation({ label, state, onOpen, onRetry, }: CheckedCitationProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import { ArrowUpRight } from "reicon-react/icons/ArrowUpRight";
|
|
5
|
+
import { ChevronDown } from "reicon-react/icons/ChevronDown";
|
|
6
|
+
import { FileText } from "reicon-react/icons/FileText";
|
|
7
|
+
import { cn } from "../internal/classes.js";
|
|
8
|
+
import { Button } from "./button.js";
|
|
9
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "./collapsible.js";
|
|
10
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
11
|
+
import { Spinner } from "./spinner.js";
|
|
12
|
+
const hoverDelay = 300;
|
|
13
|
+
const hoverCloseDelay = 150;
|
|
14
|
+
function hostOf(href) {
|
|
15
|
+
try {
|
|
16
|
+
return new URL(href).hostname.replace(/^www\./, "");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function SourceContent({ source, number, }) {
|
|
23
|
+
const host = number === undefined ? null : hostOf(source.href);
|
|
24
|
+
return (_jsxs("div", { "data-slot": "chat-source", className: "sprout-chat-sources-source", children: [number !== undefined && (_jsxs("p", { className: "sprout-chat-sources-preview-head", children: [_jsx("span", { className: "sprout-chat-sources-badge", "aria-hidden": "true", children: number }), _jsx("span", { children: host ?? `Source ${number}` })] })), _jsx("p", { className: "sprout-chat-sources-title", children: source.title }), _jsx("p", { className: "sprout-chat-sources-location", children: source.location }), _jsx("p", { className: "sprout-chat-sources-excerpt", children: source.excerpt }), _jsxs("a", { href: source.href, target: "_blank", rel: "noopener noreferrer", className: "sprout-chat-sources-link", children: ["Open source", _jsx(ArrowUpRight, { size: 14, "aria-hidden": "true" }), _jsx("span", { className: "sprout-chat-sources-sr-only", children: " in a new tab" })] })] }));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A numbered citation inside a reply. Hovering, clicking, tapping, or pressing
|
|
28
|
+
* Enter shows the cited passage and where it comes from.
|
|
29
|
+
*/
|
|
30
|
+
export function InlineCitation({ source, number }) {
|
|
31
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { openOnHover: true, delay: hoverDelay, closeDelay: hoverCloseDelay, "data-slot": "inline-citation", className: "sprout-chat-sources-citation", render: _jsx(Button, { variant: "secondary", size: "icon-xs", "aria-label": `Source ${number}: ${source.title}` }), children: number }), _jsx(PopoverContent, { className: "sprout-chat-sources-preview", children: _jsx(SourceContent, { source: source, number: number }) })] }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The sources a reply used. A count opens the numbered list; numbers match
|
|
35
|
+
* each `InlineCitation`.
|
|
36
|
+
*/
|
|
37
|
+
export function ChatSources({ sources, defaultOpen, className, ...props }) {
|
|
38
|
+
if (sources.length === 0)
|
|
39
|
+
return (_jsx("div", { "data-slot": "chat-sources", "data-empty": "", ...props, className: cn("sprout-chat-sources", className), children: _jsx("p", { className: "sprout-chat-sources-empty", children: "No sources were used for this response." }) }));
|
|
40
|
+
return (_jsxs(Collapsible, { "data-slot": "chat-sources", defaultOpen: defaultOpen, ...props, className: cn("sprout-chat-sources", className), children: [_jsxs(CollapsibleTrigger, { className: "sprout-chat-sources-trigger", children: [_jsx(FileText, { size: 16, "aria-hidden": "true" }), _jsxs("span", { children: [sources.length, " ", sources.length === 1 ? "source" : "sources"] }), _jsx(ChevronDown, { size: 14, "aria-hidden": "true", "data-slot": "collapsible-indicator" })] }), _jsx(CollapsibleContent, { variant: "sidebar", children: _jsx("ol", { className: "sprout-chat-sources-list", children: sources.map((source, index) => (_jsxs("li", { className: "sprout-chat-sources-item", children: [_jsx("span", { className: "sprout-chat-sources-number", children: index + 1 }), _jsx(SourceContent, { source: source })] }, source.id))) }) })] }));
|
|
41
|
+
}
|
|
42
|
+
export const citationStateKindNames = [
|
|
43
|
+
"idle",
|
|
44
|
+
"loading",
|
|
45
|
+
"ready",
|
|
46
|
+
"unavailable",
|
|
47
|
+
"error",
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* A citation link whose passage is checked each time it opens, on hover or
|
|
51
|
+
* click. While checking it shows a spinner; an unavailable source never shows
|
|
52
|
+
* an old passage.
|
|
53
|
+
*/
|
|
54
|
+
export function CheckedCitation({ label, state, onOpen, onRetry, }) {
|
|
55
|
+
const popup = useRef(null);
|
|
56
|
+
return (_jsxs(Popover, { onOpenChange: (open) => {
|
|
57
|
+
if (open)
|
|
58
|
+
onOpen();
|
|
59
|
+
}, children: [_jsx(PopoverTrigger, { openOnHover: true, delay: hoverDelay, closeDelay: hoverCloseDelay, "data-slot": "checked-citation", className: "sprout-chat-sources-checked", render: _jsx(Button, { variant: "link", size: "task-link", "aria-label": `Source: ${label}` }), children: label }), _jsx(PopoverContent, { ref: popup, "data-citation-state": state.kind, className: "sprout-chat-sources-preview", children: state.kind === "idle" || state.kind === "loading" ? (_jsxs("p", { role: "status", className: "sprout-chat-sources-checking", children: [_jsx(Spinner, { "aria-hidden": "true" }), "Checking access to this source\u2026"] })) : state.kind === "ready" ? (_jsxs("div", { "data-slot": "chat-source", className: "sprout-chat-sources-source", children: [_jsx("p", { className: "sprout-chat-sources-title", children: state.title }), _jsx("p", { className: "sprout-chat-sources-location", children: state.location }), _jsx("p", { className: "sprout-chat-sources-excerpt", "data-preserve-lines": "", children: state.excerpt })] })) : (_jsxs("div", { className: "sprout-chat-sources-source", children: [_jsx("p", { className: "sprout-chat-sources-title", children: state.kind === "unavailable"
|
|
60
|
+
? "Source unavailable"
|
|
61
|
+
: "Couldn’t load this source" }), _jsx("p", { role: "alert", className: "sprout-chat-sources-excerpt", children: state.message }), state.kind === "error" && (_jsx(Button, { variant: "outline", size: "sm", className: "sprout-chat-sources-retry", onClick: () => {
|
|
62
|
+
// The button leaves as loading starts; keep focus in the passage.
|
|
63
|
+
popup.current?.focus();
|
|
64
|
+
onRetry();
|
|
65
|
+
}, children: "Load source again" }))] })) })] }));
|
|
66
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
|
|
2
|
+
export declare const checkboxVariantNames: readonly ["default", "neutral"];
|
|
3
|
+
export type CheckboxVariant = (typeof checkboxVariantNames)[number];
|
|
4
|
+
export interface CheckboxProps extends BaseCheckbox.Root.Props {
|
|
5
|
+
/**
|
|
6
|
+
* `default` fills with the primary color. `neutral` fills with the text
|
|
7
|
+
* color, for filter lists that should not look branded.
|
|
8
|
+
*/
|
|
9
|
+
variant?: CheckboxVariant | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A 16px checkbox with a larger hit area. Name it with a wrapping `Label`,
|
|
13
|
+
* a `Label htmlFor` pointing at its `id`, or `aria-label`. Pass
|
|
14
|
+
* `indeterminate` to show a mixed state, such as a "select all" box.
|
|
15
|
+
*/
|
|
16
|
+
export declare function Checkbox({ variant, indeterminate, className, disabled, ref, ...props }: CheckboxProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
|
|
4
|
+
import { Check } from "reicon-react/icons/Check";
|
|
5
|
+
import { Minus } from "reicon-react/icons/Minus";
|
|
6
|
+
import { cn, stateClassName } from "../internal/classes.js";
|
|
7
|
+
import { useFieldsetDisabled } from "../internal/fieldset.js";
|
|
8
|
+
export const checkboxVariantNames = ["default", "neutral"];
|
|
9
|
+
/**
|
|
10
|
+
* A 16px checkbox with a larger hit area. Name it with a wrapping `Label`,
|
|
11
|
+
* a `Label htmlFor` pointing at its `id`, or `aria-label`. Pass
|
|
12
|
+
* `indeterminate` to show a mixed state, such as a "select all" box.
|
|
13
|
+
*/
|
|
14
|
+
export function Checkbox({ variant = "default", indeterminate = false, className, disabled, ref, ...props }) {
|
|
15
|
+
const [fieldsetDisabled, fieldsetRef] = useFieldsetDisabled(ref);
|
|
16
|
+
return (_jsx(BaseCheckbox.Root, { "data-slot": "checkbox", "data-variant": variant, indeterminate: indeterminate, ...props, ref: fieldsetRef, disabled: disabled || fieldsetDisabled, className: stateClassName(cn("sprout-checkbox", `sprout-checkbox-variant-${variant}`), className), children: _jsx(BaseCheckbox.Indicator, { "data-slot": "checkbox-indicator", className: "sprout-checkbox-indicator", children: indeterminate ? (_jsx(Minus, { "aria-hidden": "true" })) : (_jsx(Check, { "aria-hidden": "true" })) }) }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export declare const choiceListLayoutNames: readonly ["stacked", "compact"];
|
|
3
|
+
export type ChoiceListLayout = (typeof choiceListLayoutNames)[number];
|
|
4
|
+
export interface ChoiceListOption {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
/** Shows a "Recommended" badge. A label ending in "(Recommended)" does the same. */
|
|
8
|
+
recommended?: boolean | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface ChoiceListTextOption {
|
|
11
|
+
value: string;
|
|
12
|
+
/** True while the typed answer is the chosen one. */
|
|
13
|
+
active: boolean;
|
|
14
|
+
onChange: (value: string) => void;
|
|
15
|
+
onFocus: () => void;
|
|
16
|
+
/** Id of the answer box, for an "Other answer" toggle's `aria-controls`. */
|
|
17
|
+
id?: string | undefined;
|
|
18
|
+
error: string | null;
|
|
19
|
+
label?: string | undefined;
|
|
20
|
+
placeholder?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
export interface ChoiceListProps {
|
|
23
|
+
legend: string;
|
|
24
|
+
options: readonly ChoiceListOption[];
|
|
25
|
+
value: readonly string[];
|
|
26
|
+
onChange: (value: string[]) => void;
|
|
27
|
+
/** Checkboxes instead of radios. */
|
|
28
|
+
multiple?: boolean | undefined;
|
|
29
|
+
disabled?: boolean | undefined;
|
|
30
|
+
error?: string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* `stacked` draws bordered rows with radio or checkbox marks. `compact`
|
|
33
|
+
* draws numbered rows in a scrolling list, for questions inside a chat.
|
|
34
|
+
*/
|
|
35
|
+
layout?: ChoiceListLayout | undefined;
|
|
36
|
+
/** A free-text answer after the options. Compact lists show it only while active. */
|
|
37
|
+
textOption?: ChoiceListTextOption | undefined;
|
|
38
|
+
className?: string | undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A fieldset of radio or checkbox rows for answering a question, with an
|
|
42
|
+
* optional typed answer. Value is always an array of option ids.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ChoiceList({ legend, options, value, onChange, multiple, disabled, error, layout, textOption, className, }: ChoiceListProps): import("react").JSX.Element;
|
|
45
|
+
export interface ChoiceSummaryProps {
|
|
46
|
+
prompt: string;
|
|
47
|
+
/** The recorded answer. Omit it when the question expired or was cancelled. */
|
|
48
|
+
answer?: string | undefined;
|
|
49
|
+
className?: string | undefined;
|
|
50
|
+
children?: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
/** One line that shows a question and its recorded answer. */
|
|
53
|
+
export declare function ChoiceSummary({ prompt, answer, className, children, }: ChoiceSummaryProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { Check } from "reicon-react/icons/Check";
|
|
5
|
+
import { HelpCircle } from "reicon-react/icons/HelpCircle";
|
|
6
|
+
import { cn } from "../internal/classes.js";
|
|
7
|
+
import { Badge } from "./badge.js";
|
|
8
|
+
export const choiceListLayoutNames = ["stacked", "compact"];
|
|
9
|
+
const recommendedSuffix = /\s*\(recommended\)\s*$/i;
|
|
10
|
+
/**
|
|
11
|
+
* A fieldset of radio or checkbox rows for answering a question, with an
|
|
12
|
+
* optional typed answer. Value is always an array of option ids.
|
|
13
|
+
*/
|
|
14
|
+
export function ChoiceList({ legend, options, value, onChange, multiple = false, disabled = false, error, layout = "stacked", textOption, className, }) {
|
|
15
|
+
const name = useId();
|
|
16
|
+
const compact = layout === "compact";
|
|
17
|
+
const errorId = `${name}-choices-error`;
|
|
18
|
+
const textId = `${name}-text`;
|
|
19
|
+
const textErrorId = `${name}-text-error`;
|
|
20
|
+
return (_jsxs("fieldset", { "data-slot": "choice-list", "data-layout": layout, "data-multiple": multiple || undefined, disabled: disabled, className: cn("sprout-choice-list", className), children: [_jsx("legend", { className: compact && !multiple
|
|
21
|
+
? "sprout-choice-list-sr-only"
|
|
22
|
+
: "sprout-choice-list-legend", children: legend }), _jsx("div", { "data-slot": "choice-list-options", className: "sprout-choice-list-options", children: options.map((option, index) => {
|
|
23
|
+
const checked = value.includes(option.id);
|
|
24
|
+
const recommended = option.recommended === true || recommendedSuffix.test(option.label);
|
|
25
|
+
const label = option.label.replace(recommendedSuffix, "");
|
|
26
|
+
return (_jsxs("label", { "data-slot": "choice-list-option", "data-checked": checked || undefined, "data-disabled": disabled || undefined, "data-recommended": recommended || undefined, className: "sprout-choice-list-option", children: [_jsx("input", { type: multiple ? "checkbox" : "radio", name: name, value: option.id, checked: checked, "aria-invalid": error ? true : undefined, "aria-describedby": error ? errorId : undefined, onChange: (event) => onChange(multiple
|
|
27
|
+
? event.target.checked
|
|
28
|
+
? [...value, option.id]
|
|
29
|
+
: value.filter((id) => id !== option.id)
|
|
30
|
+
: [option.id]), className: "sprout-choice-list-input" }), _jsx("span", { "aria-hidden": "true", className: "sprout-choice-list-mark", children: checked ? _jsx(Check, {}) : compact ? index + 1 : null }), _jsxs("span", { className: "sprout-choice-list-label", children: [label, recommended && (_jsx(Badge, { variant: "info", className: "sprout-choice-list-badge", children: "Recommended" }))] })] }, option.id));
|
|
31
|
+
}) }), error && (_jsx("p", { id: errorId, className: "sprout-choice-list-error", children: error })), textOption && (!compact || textOption.active) && (_jsxs("div", { id: textOption.id ?? `${name}-custom`, "data-slot": "choice-list-text", "data-checked": textOption.active || undefined, "data-invalid": textOption.error ? true : undefined, className: "sprout-choice-list-text", children: [_jsx("span", { "aria-hidden": "true", className: "sprout-choice-list-mark", children: textOption.active && _jsx(Check, {}) }), _jsxs("div", { className: "sprout-choice-list-text-body", children: [_jsx("label", { htmlFor: textId, className: "sprout-choice-list-text-label", children: textOption.label ?? "Your answer" }), _jsx("textarea", { id: textId, autoFocus: compact, rows: 1, value: textOption.value, disabled: disabled, placeholder: textOption.placeholder ?? "Type a different answer…", "aria-describedby": textOption.error ? textErrorId : undefined, "aria-invalid": textOption.error ? true : undefined, onFocus: textOption.onFocus, onChange: (event) => textOption.onChange(event.target.value), className: "sprout-choice-list-textarea" }), textOption.error && (_jsx("p", { id: textErrorId, role: "alert", className: "sprout-choice-list-error", children: textOption.error }))] })] }))] }));
|
|
32
|
+
}
|
|
33
|
+
/** One line that shows a question and its recorded answer. */
|
|
34
|
+
export function ChoiceSummary({ prompt, answer, className, children, }) {
|
|
35
|
+
return (_jsxs("div", { "data-slot": "choice-summary", className: cn("sprout-choice-summary", className), children: [_jsx(HelpCircle, { "aria-hidden": "true", className: "sprout-choice-summary-icon" }), _jsx("span", { className: "sprout-choice-summary-prompt", children: prompt }), answer && (_jsxs("span", { className: "sprout-choice-summary-answer", children: [_jsx(Check, { "aria-hidden": "true", className: "sprout-choice-summary-check" }), _jsx("span", { className: "sprout-choice-summary-answer-text", children: answer })] })), children] }));
|
|
36
|
+
}
|