@elabs-ai/components-ai 4.0.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 +133 -0
- package/dist/_audio-player-media-chrome-KA5DY54G.js +81 -0
- package/dist/_audio-player-media-chrome-KA5DY54G.js.map +1 -0
- package/dist/_flow-boundary-D63PJ65S.js +186 -0
- package/dist/_flow-boundary-D63PJ65S.js.map +1 -0
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js +11 -0
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js.map +1 -0
- package/dist/_persona-rive-RFR2EUWP.js +164 -0
- package/dist/_persona-rive-RFR2EUWP.js.map +1 -0
- package/dist/index.d.ts +4079 -0
- package/dist/index.js +11770 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
- package/src/_audio-player-media-chrome.tsx +146 -0
- package/src/_chat-shell-rail.tsx +18 -0
- package/src/_code-block-theme.test.ts +134 -0
- package/src/_code-block-theme.ts +184 -0
- package/src/_flow-boundary.tsx +237 -0
- package/src/_flow-lazy.ts +34 -0
- package/src/_interactive-terminal-xterm.ts +32 -0
- package/src/_lazy-mermaid.test.ts +101 -0
- package/src/_lazy-mermaid.ts +117 -0
- package/src/_persona-rive.tsx +252 -0
- package/src/_streamdown-i18n.ts +119 -0
- package/src/agent-timeline.stories.tsx +66 -0
- package/src/agent-timeline.test.tsx +74 -0
- package/src/agent-timeline.tsx +106 -0
- package/src/agent.tsx +113 -0
- package/src/agentic-workspace.stories.tsx +1099 -0
- package/src/artifact.stories.tsx +62 -0
- package/src/artifact.test.tsx +112 -0
- package/src/artifact.tsx +167 -0
- package/src/asset-preview.stories.tsx +100 -0
- package/src/asset-preview.test.tsx +126 -0
- package/src/asset-preview.tsx +218 -0
- package/src/attachments.tsx +393 -0
- package/src/audio-player.stories.tsx +99 -0
- package/src/audio-player.tsx +170 -0
- package/src/blocks-ai-composer.stories.tsx +83 -0
- package/src/canvas.stories.tsx +142 -0
- package/src/canvas.tsx +29 -0
- package/src/chain-of-thought.tsx +193 -0
- package/src/chat-greeting.stories.tsx +96 -0
- package/src/chat-greeting.test.tsx +91 -0
- package/src/chat-greeting.tsx +91 -0
- package/src/chat-shell.stories.tsx +123 -0
- package/src/chat-shell.test.tsx +113 -0
- package/src/chat-shell.tsx +97 -0
- package/src/chat.stories.tsx +99 -0
- package/src/checkpoint.tsx +54 -0
- package/src/code-block.stories.tsx +81 -0
- package/src/code-block.test.tsx +191 -0
- package/src/code-block.tsx +673 -0
- package/src/commit.tsx +343 -0
- package/src/composer.stories.tsx +188 -0
- package/src/composer.test.tsx +182 -0
- package/src/composer.tsx +202 -0
- package/src/confirmation.stories.tsx +61 -0
- package/src/confirmation.test.tsx +136 -0
- package/src/confirmation.tsx +273 -0
- package/src/connection.tsx +23 -0
- package/src/context-panel.stories.tsx +137 -0
- package/src/context-panel.test.tsx +198 -0
- package/src/context-panel.tsx +609 -0
- package/src/context.stories.tsx +16 -0
- package/src/context.tsx +363 -0
- package/src/controls.tsx +20 -0
- package/src/conversation.stories.tsx +26 -0
- package/src/conversation.test.tsx +123 -0
- package/src/conversation.tsx +143 -0
- package/src/dark-theme-variant.test.ts +47 -0
- package/src/edge.tsx +33 -0
- package/src/environment-variables.tsx +304 -0
- package/src/file-tree.stories.tsx +55 -0
- package/src/file-tree.test.tsx +101 -0
- package/src/file-tree.tsx +390 -0
- package/src/gallery.stories.tsx +182 -0
- package/src/gallery.test.tsx +150 -0
- package/src/gallery.tsx +737 -0
- package/src/grouped-parts.stories.tsx +104 -0
- package/src/grouped-parts.test.tsx +167 -0
- package/src/grouped-parts.tsx +247 -0
- package/src/image.stories.tsx +67 -0
- package/src/image.test.tsx +105 -0
- package/src/image.tsx +115 -0
- package/src/index.ts +93 -0
- package/src/inline-citation.stories.tsx +91 -0
- package/src/inline-citation.test.tsx +96 -0
- package/src/inline-citation.tsx +314 -0
- package/src/interactive-terminal.stories.tsx +165 -0
- package/src/interactive-terminal.test.tsx +448 -0
- package/src/interactive-terminal.tsx +444 -0
- package/src/jsx-preview.stories.tsx +265 -0
- package/src/jsx-preview.test.tsx +75 -0
- package/src/jsx-preview.tsx +409 -0
- package/src/markdown-view.stories.tsx +57 -0
- package/src/markdown-view.test.tsx +36 -0
- package/src/markdown-view.tsx +113 -0
- package/src/message-edit.stories.tsx +150 -0
- package/src/message-edit.test.tsx +115 -0
- package/src/message-edit.tsx +336 -0
- package/src/message-feedback.stories.tsx +53 -0
- package/src/message-feedback.test.tsx +62 -0
- package/src/message-feedback.tsx +117 -0
- package/src/message-form-spec.ts +349 -0
- package/src/message-form.stories.tsx +182 -0
- package/src/message-form.test.tsx +227 -0
- package/src/message-form.tsx +845 -0
- package/src/message-table-spec.ts +281 -0
- package/src/message-table.stories.tsx +176 -0
- package/src/message-table.test.tsx +144 -0
- package/src/message-table.tsx +366 -0
- package/src/message.stories.tsx +262 -0
- package/src/message.test.tsx +508 -0
- package/src/message.tsx +619 -0
- package/src/mic-selector.tsx +339 -0
- package/src/microcopy.test.tsx +123 -0
- package/src/model-selector.stories.tsx +73 -0
- package/src/model-selector.test.tsx +55 -0
- package/src/model-selector.tsx +225 -0
- package/src/motion-config.tsx +49 -0
- package/src/node.tsx +71 -0
- package/src/open-in-chat.tsx +340 -0
- package/src/package-info.tsx +205 -0
- package/src/panel.tsx +20 -0
- package/src/part-groups.ts +258 -0
- package/src/persona-sources.ts +59 -0
- package/src/persona.stories.tsx +111 -0
- package/src/persona.test.tsx +67 -0
- package/src/persona.tsx +148 -0
- package/src/plan.tsx +135 -0
- package/src/prompt-input.stories.tsx +261 -0
- package/src/prompt-input.test.tsx +392 -0
- package/src/prompt-input.tsx +1668 -0
- package/src/queue.tsx +237 -0
- package/src/reasoning.stories.tsx +81 -0
- package/src/reasoning.test.tsx +50 -0
- package/src/reasoning.tsx +229 -0
- package/src/sandbox.stories.tsx +62 -0
- package/src/sandbox.test.tsx +42 -0
- package/src/sandbox.tsx +149 -0
- package/src/schema-display.tsx +412 -0
- package/src/selection-toolbar.stories.tsx +86 -0
- package/src/selection-toolbar.test.tsx +89 -0
- package/src/selection-toolbar.tsx +192 -0
- package/src/shimmer.stories.tsx +10 -0
- package/src/shimmer.tsx +81 -0
- package/src/snippet.stories.tsx +19 -0
- package/src/snippet.test.tsx +116 -0
- package/src/snippet.tsx +138 -0
- package/src/sources.stories.tsx +34 -0
- package/src/sources.test.tsx +129 -0
- package/src/sources.tsx +93 -0
- package/src/speech-input.tsx +312 -0
- package/src/stack-trace.tsx +479 -0
- package/src/streamdown-i18n.test.tsx +163 -0
- package/src/suggestion.stories.tsx +75 -0
- package/src/suggestion.test.tsx +72 -0
- package/src/suggestion.tsx +120 -0
- package/src/task.stories.tsx +22 -0
- package/src/task.test.tsx +36 -0
- package/src/task.tsx +89 -0
- package/src/templates-ai-assistant.stories.tsx +142 -0
- package/src/terminal.tsx +244 -0
- package/src/test-results.stories.tsx +177 -0
- package/src/test-results.test.tsx +241 -0
- package/src/test-results.tsx +408 -0
- package/src/tool-result-card.stories.tsx +143 -0
- package/src/tool-result-card.test.tsx +72 -0
- package/src/tool-result-card.tsx +86 -0
- package/src/tool.stories.tsx +60 -0
- package/src/tool.test.tsx +63 -0
- package/src/tool.tsx +235 -0
- package/src/toolbar.tsx +44 -0
- package/src/transcription.tsx +118 -0
- package/src/voice-selector.tsx +469 -0
- package/src/web-preview.stories.tsx +67 -0
- package/src/web-preview.test.tsx +117 -0
- package/src/web-preview.tsx +283 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@elabs-ai/components-ui";
|
|
4
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@elabs-ai/components-ui";
|
|
5
|
+
import { Input } from "@elabs-ai/components-ui";
|
|
6
|
+
import { Skeleton } from "@elabs-ai/components-ui";
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@elabs-ai/components-ui";
|
|
8
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
9
|
+
import { useLocale } from "@elabs-ai/components-ui";
|
|
10
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
11
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
12
|
+
import { createContext, useCallback, useContext, useMemo, useState } from "react";
|
|
13
|
+
|
|
14
|
+
export interface WebPreviewContextValue {
|
|
15
|
+
url: string;
|
|
16
|
+
setUrl: (url: string) => void;
|
|
17
|
+
consoleOpen: boolean;
|
|
18
|
+
setConsoleOpen: (open: boolean) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const WebPreviewContext = createContext<WebPreviewContextValue | null>(null);
|
|
22
|
+
|
|
23
|
+
const useWebPreview = () => {
|
|
24
|
+
const context = useContext(WebPreviewContext);
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error("WebPreview components must be used within a WebPreview");
|
|
27
|
+
}
|
|
28
|
+
return context;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type WebPreviewProps = ComponentProps<"div"> & {
|
|
32
|
+
defaultUrl?: string;
|
|
33
|
+
onUrlChange?: (url: string) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const WebPreview = ({
|
|
37
|
+
className,
|
|
38
|
+
children,
|
|
39
|
+
defaultUrl = "",
|
|
40
|
+
onUrlChange,
|
|
41
|
+
...props
|
|
42
|
+
}: WebPreviewProps) => {
|
|
43
|
+
const [url, setUrl] = useState(defaultUrl);
|
|
44
|
+
const [consoleOpen, setConsoleOpen] = useState(false);
|
|
45
|
+
|
|
46
|
+
const handleUrlChange = useCallback(
|
|
47
|
+
(newUrl: string) => {
|
|
48
|
+
setUrl(newUrl);
|
|
49
|
+
onUrlChange?.(newUrl);
|
|
50
|
+
},
|
|
51
|
+
[onUrlChange],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const contextValue = useMemo<WebPreviewContextValue>(
|
|
55
|
+
() => ({
|
|
56
|
+
consoleOpen,
|
|
57
|
+
setConsoleOpen,
|
|
58
|
+
setUrl: handleUrlChange,
|
|
59
|
+
url,
|
|
60
|
+
}),
|
|
61
|
+
[consoleOpen, handleUrlChange, url],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<WebPreviewContext.Provider value={contextValue}>
|
|
66
|
+
<div
|
|
67
|
+
className={cn("flex size-full flex-col rounded-lg border bg-card", className)}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</div>
|
|
72
|
+
</WebPreviewContext.Provider>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type WebPreviewNavigationProps = ComponentProps<"div">;
|
|
77
|
+
|
|
78
|
+
export const WebPreviewNavigation = ({
|
|
79
|
+
className,
|
|
80
|
+
children,
|
|
81
|
+
...props
|
|
82
|
+
}: WebPreviewNavigationProps) => (
|
|
83
|
+
<div className={cn("flex items-center gap-1 border-b p-2", className)} {...props}>
|
|
84
|
+
{children}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {
|
|
89
|
+
tooltip?: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WebPreviewNavigationButton = ({
|
|
93
|
+
onClick,
|
|
94
|
+
disabled,
|
|
95
|
+
tooltip,
|
|
96
|
+
children,
|
|
97
|
+
...props
|
|
98
|
+
}: WebPreviewNavigationButtonProps) => (
|
|
99
|
+
<TooltipProvider>
|
|
100
|
+
<Tooltip>
|
|
101
|
+
<TooltipTrigger asChild>
|
|
102
|
+
<Button
|
|
103
|
+
// These buttons are icon-only, and a Radix tooltip only contributes
|
|
104
|
+
// `aria-describedby` while it is OPEN — so at rest the control had no
|
|
105
|
+
// accessible name at all and axe's `button-name` red every story that
|
|
106
|
+
// mounted one (#386). The tooltip text IS the name; spreading
|
|
107
|
+
// `...props` afterwards keeps an explicit `aria-label` winning, and a
|
|
108
|
+
// button that ships real text children keeps its own content as the
|
|
109
|
+
// name because `tooltip` is then undefined. Mirrors `MessageAction`'s
|
|
110
|
+
// `sr-only` label in message.tsx.
|
|
111
|
+
aria-label={tooltip}
|
|
112
|
+
className="h-8 w-8 p-0 hover:text-foreground"
|
|
113
|
+
disabled={disabled}
|
|
114
|
+
onClick={onClick}
|
|
115
|
+
size="sm"
|
|
116
|
+
variant="ghost"
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</Button>
|
|
121
|
+
</TooltipTrigger>
|
|
122
|
+
<TooltipContent>
|
|
123
|
+
<p>{tooltip}</p>
|
|
124
|
+
</TooltipContent>
|
|
125
|
+
</Tooltip>
|
|
126
|
+
</TooltipProvider>
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
export type WebPreviewUrlProps = ComponentProps<typeof Input>;
|
|
130
|
+
|
|
131
|
+
export const WebPreviewUrl = ({ value, onChange, onKeyDown, ...props }: WebPreviewUrlProps) => {
|
|
132
|
+
const { t } = useLocale();
|
|
133
|
+
const { url, setUrl } = useWebPreview();
|
|
134
|
+
const [prevUrl, setPrevUrl] = useState(url);
|
|
135
|
+
const [inputValue, setInputValue] = useState(url);
|
|
136
|
+
|
|
137
|
+
// Sync input value with context URL when it changes externally (derived state pattern)
|
|
138
|
+
if (url !== prevUrl) {
|
|
139
|
+
setPrevUrl(url);
|
|
140
|
+
setInputValue(url);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
144
|
+
setInputValue(event.target.value);
|
|
145
|
+
onChange?.(event);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleKeyDown = useCallback(
|
|
149
|
+
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
150
|
+
if (event.key === "Enter") {
|
|
151
|
+
const target = event.target as HTMLInputElement;
|
|
152
|
+
setUrl(target.value);
|
|
153
|
+
}
|
|
154
|
+
onKeyDown?.(event);
|
|
155
|
+
},
|
|
156
|
+
[setUrl, onKeyDown],
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Input
|
|
161
|
+
className="h-8 flex-1 text-sm"
|
|
162
|
+
onChange={onChange ?? handleChange}
|
|
163
|
+
onKeyDown={handleKeyDown}
|
|
164
|
+
placeholder={t("ai.webPreview.urlPlaceholder")}
|
|
165
|
+
value={value ?? inputValue}
|
|
166
|
+
{...props}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export type WebPreviewBodyProps = Omit<ComponentProps<"iframe">, "loading"> & {
|
|
172
|
+
/**
|
|
173
|
+
* Not-ready placeholder over the iframe (loading-states.md `loading`
|
|
174
|
+
* signal). Pass `true` for the default layout-shaped Skeleton filling the
|
|
175
|
+
* iframe box, or a ReactNode to override with custom placeholder content.
|
|
176
|
+
* `false`/`undefined` (default) renders nothing. Shadows (rather than
|
|
177
|
+
* forwards) the native `<iframe loading="eager"|"lazy">` attribute — this
|
|
178
|
+
* component's box is always fully reserved, so lazy-loading the iframe
|
|
179
|
+
* itself is not a use case here.
|
|
180
|
+
* @default false
|
|
181
|
+
*/
|
|
182
|
+
loading?: boolean | ReactNode;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const WebPreviewBody = ({ className, loading, src, ...props }: WebPreviewBodyProps) => {
|
|
186
|
+
const { url } = useWebPreview();
|
|
187
|
+
const { t } = useLocale();
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div className="relative flex-1">
|
|
191
|
+
<iframe
|
|
192
|
+
className={cn("size-full", className)}
|
|
193
|
+
// oxlint-disable-next-line eslint-plugin-react(iframe-missing-sandbox)
|
|
194
|
+
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
|
|
195
|
+
src={(src ?? url) || undefined}
|
|
196
|
+
title="Preview"
|
|
197
|
+
{...props}
|
|
198
|
+
/>
|
|
199
|
+
{loading === true ? (
|
|
200
|
+
<div
|
|
201
|
+
className="absolute inset-0 flex flex-col gap-2 bg-card p-4"
|
|
202
|
+
role="status"
|
|
203
|
+
aria-live="polite"
|
|
204
|
+
>
|
|
205
|
+
<span className="sr-only">{t("loading")}</span>
|
|
206
|
+
<Skeleton className="h-4 w-1/3" />
|
|
207
|
+
<Skeleton className="flex-1" />
|
|
208
|
+
</div>
|
|
209
|
+
) : (
|
|
210
|
+
loading
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type WebPreviewConsoleProps = ComponentProps<"div"> & {
|
|
217
|
+
logs?: {
|
|
218
|
+
level: "log" | "warn" | "error";
|
|
219
|
+
message: string;
|
|
220
|
+
timestamp: Date;
|
|
221
|
+
}[];
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const WebPreviewConsole = ({
|
|
225
|
+
className,
|
|
226
|
+
logs = [],
|
|
227
|
+
children,
|
|
228
|
+
...props
|
|
229
|
+
}: WebPreviewConsoleProps) => {
|
|
230
|
+
const { consoleOpen, setConsoleOpen } = useWebPreview();
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<Collapsible
|
|
234
|
+
className={cn("border-t bg-muted/50 font-mono text-sm", className)}
|
|
235
|
+
onOpenChange={setConsoleOpen}
|
|
236
|
+
open={consoleOpen}
|
|
237
|
+
{...props}
|
|
238
|
+
>
|
|
239
|
+
<CollapsibleTrigger asChild>
|
|
240
|
+
<Button
|
|
241
|
+
className="flex w-full items-center justify-between p-4 text-start font-medium hover:bg-muted/50"
|
|
242
|
+
variant="ghost"
|
|
243
|
+
>
|
|
244
|
+
Console
|
|
245
|
+
<ChevronDownIcon
|
|
246
|
+
className={cn(
|
|
247
|
+
"h-4 w-4 transition-transform duration-base ease-standard",
|
|
248
|
+
consoleOpen && "rotate-180",
|
|
249
|
+
)}
|
|
250
|
+
/>
|
|
251
|
+
</Button>
|
|
252
|
+
</CollapsibleTrigger>
|
|
253
|
+
<CollapsibleContent
|
|
254
|
+
className={cn(
|
|
255
|
+
"px-4 pb-4",
|
|
256
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
<div className="max-h-48 space-y-1 overflow-y-auto">
|
|
260
|
+
{logs.length === 0 ? (
|
|
261
|
+
<p className="text-muted-foreground">No console output</p>
|
|
262
|
+
) : (
|
|
263
|
+
logs.map((log) => (
|
|
264
|
+
<div
|
|
265
|
+
className={cn(
|
|
266
|
+
"text-xs",
|
|
267
|
+
log.level === "error" && "text-destructive",
|
|
268
|
+
log.level === "warn" && "text-yellow-600",
|
|
269
|
+
log.level === "log" && "text-foreground",
|
|
270
|
+
)}
|
|
271
|
+
key={`${log.timestamp.getTime()}-${log.level}-${log.message}`}
|
|
272
|
+
>
|
|
273
|
+
<span className="text-muted-foreground">{log.timestamp.toLocaleTimeString()}</span>{" "}
|
|
274
|
+
{log.message}
|
|
275
|
+
</div>
|
|
276
|
+
))
|
|
277
|
+
)}
|
|
278
|
+
{children}
|
|
279
|
+
</div>
|
|
280
|
+
</CollapsibleContent>
|
|
281
|
+
</Collapsible>
|
|
282
|
+
);
|
|
283
|
+
};
|