@elabs-ai/components-ai 4.0.0 → 4.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/README.md +62 -12
- package/dist/{_audio-player-media-chrome-KA5DY54G.js → _audio-player-media-chrome-T3XVXWRZ.js} +8 -4
- package/dist/_audio-player-media-chrome-T3XVXWRZ.js.map +1 -0
- package/dist/{_flow-boundary-D63PJ65S.js → _flow-boundary-SHNWLQG5.js} +32 -43
- package/dist/_flow-boundary-SHNWLQG5.js.map +1 -0
- package/dist/{_persona-rive-RFR2EUWP.js → _persona-rive-JEG44YHX.js} +9 -5
- package/dist/_persona-rive-JEG44YHX.js.map +1 -0
- package/dist/index.d.ts +1152 -218
- package/dist/index.js +5601 -3241
- package/dist/index.js.map +1 -1
- package/package.json +30 -15
- package/src/__contract__/audio-visualizer.contract.test.tsx +49 -0
- package/src/__contract__/chat-shell.contract.test.tsx +49 -0
- package/src/__contract__/grouped-parts.contract.test.tsx +49 -0
- package/src/__contract__/image.contract.test.tsx +49 -0
- package/src/__contract__/markdown-view.contract.test.tsx +49 -0
- package/src/__contract__/message-feedback.contract.test.tsx +49 -0
- package/src/__contract__/message-form.contract.test.tsx +49 -0
- package/src/__contract__/message-table.contract.test.tsx +49 -0
- package/src/__contract__/model-provider-logo.contract.test.tsx +49 -0
- package/src/__contract__/persona.contract.test.tsx +49 -0
- package/src/__contract__/prompt-input-effort.contract.test.tsx +49 -0
- package/src/__contract__/prompt-input-mode.contract.test.tsx +49 -0
- package/src/_audio-player-media-chrome.tsx +102 -15
- package/src/_chat-shell-rail.tsx +2 -2
- package/src/_flow-boundary.tsx +68 -49
- package/src/_lazy-boundary-conformance.ts +38 -0
- package/src/_lazy-cjk.test.ts +43 -0
- package/src/_lazy-cjk.ts +73 -0
- package/src/_lazy-engine-boundary.tsx +61 -0
- package/src/_lazy-math.test.ts +63 -0
- package/src/_lazy-math.ts +90 -0
- package/src/_lazy-mermaid-absent.test.ts +53 -0
- package/src/_lazy-mermaid.test.ts +15 -0
- package/src/_lazy-mermaid.ts +24 -1
- package/src/_mermaid-error-panel.test.tsx +50 -0
- package/src/_mermaid-error-panel.tsx +66 -0
- package/src/_persona-rive.tsx +62 -10
- package/src/_streamdown-i18n.ts +94 -22
- package/src/_streamdown-safety.ts +170 -0
- package/src/_theme-scope-store.test.ts +83 -0
- package/src/_theme-scope-store.ts +103 -0
- package/src/agent-event.stories.tsx +97 -0
- package/src/agent-event.test.tsx +145 -0
- package/src/agent-event.tsx +187 -0
- package/src/agent-timeline.stories.tsx +9 -1
- package/src/agent.test.tsx +47 -0
- package/src/agent.tsx +43 -24
- package/src/agentic-workspace.stories.tsx +27 -20
- package/src/artifact.tsx +17 -14
- package/src/asset-preview.test.tsx +40 -0
- package/src/asset-preview.tsx +83 -12
- package/src/attachments.tsx +7 -4
- package/src/audio-player.test.tsx +78 -0
- package/src/audio-player.tsx +243 -56
- package/src/audio-visualizer.stories.tsx +126 -0
- package/src/audio-visualizer.test.tsx +438 -0
- package/src/audio-visualizer.tsx +367 -0
- package/src/canvas.stories.tsx +150 -1
- package/src/chat-shell.stories.tsx +18 -3
- package/src/chat.stories.tsx +16 -2
- package/src/code-block.stories.tsx +9 -1
- package/src/code-block.test.tsx +100 -1
- package/src/code-block.tsx +166 -103
- package/src/commit.tsx +30 -41
- package/src/composer.stories.tsx +372 -9
- package/src/composer.test.tsx +357 -6
- package/src/composer.tsx +217 -35
- package/src/confirmation.stories.tsx +72 -1
- package/src/confirmation.test.tsx +216 -2
- package/src/confirmation.tsx +264 -4
- package/src/context-panel.stories.tsx +9 -1
- package/src/context-panel.tsx +3 -2
- package/src/conversation.stories.tsx +86 -2
- package/src/conversation.test.tsx +66 -0
- package/src/conversation.tsx +52 -9
- package/src/diff-view.stories.tsx +196 -0
- package/src/diff-view.test.tsx +238 -0
- package/src/diff-view.tsx +697 -0
- package/src/environment-variables.tsx +20 -37
- package/src/file-tree.test.tsx +21 -0
- package/src/file-tree.tsx +12 -2
- package/src/gallery.tsx +4 -2
- package/src/index.ts +14 -4
- package/src/inline-citation.tsx +5 -5
- package/src/jsx-preview.stories.tsx +2 -2
- package/src/jsx-preview.tsx +151 -43
- package/src/markdown-view.stories.tsx +92 -1
- package/src/markdown-view.test.tsx +237 -2
- package/src/markdown-view.tsx +157 -6
- package/src/message-compare.stories.tsx +175 -0
- package/src/message-compare.test.tsx +207 -0
- package/src/message-compare.tsx +453 -0
- package/src/message-form.stories.tsx +65 -4
- package/src/message-form.test.tsx +8 -2
- package/src/message-form.tsx +15 -7
- package/src/message-table.stories.tsx +2 -2
- package/src/message-table.test.tsx +7 -0
- package/src/message-table.tsx +8 -4
- package/src/message.stories.tsx +9 -1
- package/src/message.test.tsx +247 -1
- package/src/message.tsx +125 -15
- package/src/microcopy.test.tsx +40 -0
- package/src/{model-selector.stories.tsx → model-provider-logo.stories.tsx} +17 -8
- package/src/model-provider-logo.test.tsx +109 -0
- package/src/model-provider-logo.tsx +183 -0
- package/src/open-in-chat.tsx +50 -29
- package/src/package-info.tsx +12 -12
- package/src/permission-mode-select.stories.tsx +82 -0
- package/src/permission-mode-select.test.tsx +100 -0
- package/src/permission-mode-select.tsx +137 -0
- package/src/persona-missing-peer.test.tsx +54 -0
- package/src/persona.tsx +68 -22
- package/src/plan.stories.tsx +166 -0
- package/src/plan.test.tsx +267 -0
- package/src/plan.tsx +182 -20
- package/src/prompt-input-effort.stories.tsx +123 -0
- package/src/prompt-input-effort.test.tsx +83 -0
- package/src/prompt-input-effort.tsx +136 -0
- package/src/prompt-input-mode.stories.tsx +108 -0
- package/src/prompt-input-mode.test.tsx +99 -0
- package/src/prompt-input-mode.tsx +169 -0
- package/src/prompt-input-slash.stories.tsx +211 -0
- package/src/prompt-input-slash.test.tsx +262 -0
- package/src/prompt-input-slash.tsx +541 -0
- package/src/prompt-input.stories.tsx +2 -2
- package/src/prompt-input.test.tsx +67 -1
- package/src/prompt-input.tsx +42 -4
- package/src/queue.tsx +4 -4
- package/src/reasoning.tsx +42 -17
- package/src/sandbox.stories.tsx +9 -1
- package/src/sandbox.tsx +3 -3
- package/src/schema-display.test.tsx +56 -0
- package/src/schema-display.tsx +71 -37
- package/src/selection-toolbar.stories.tsx +9 -1
- package/src/session-header.stories.tsx +128 -0
- package/src/session-header.test.tsx +138 -0
- package/src/session-header.tsx +243 -0
- package/src/session-status-bar.stories.tsx +73 -0
- package/src/session-status-bar.test.tsx +94 -0
- package/src/session-status-bar.tsx +165 -0
- package/src/snippet.stories.tsx +9 -1
- package/src/snippet.test.tsx +6 -2
- package/src/snippet.tsx +14 -34
- package/src/speech-input.test.tsx +109 -0
- package/src/speech-input.tsx +31 -3
- package/src/stack-trace.tsx +24 -36
- package/src/streamdown-i18n.test.tsx +24 -1
- package/src/task.stories.tsx +13 -3
- package/src/templates-ai-assistant.stories.tsx +21 -1
- package/src/test-results.tsx +47 -27
- package/src/token-usage.stories.tsx +24 -0
- package/src/token-usage.test.tsx +92 -0
- package/src/{context.tsx → token-usage.tsx} +73 -59
- package/src/tool-result-card.stories.tsx +9 -1
- package/src/tool.stories.tsx +13 -3
- package/src/tool.test.tsx +65 -0
- package/src/tool.tsx +73 -30
- package/src/transcription.tsx +1 -1
- package/src/turn-status.stories.tsx +124 -0
- package/src/turn-status.test.tsx +74 -0
- package/src/turn-status.tsx +174 -0
- package/src/use-audio-level.ts +104 -0
- package/src/voice-selector.tsx +5 -5
- package/src/web-preview.test.tsx +51 -1
- package/src/web-preview.tsx +46 -10
- package/dist/_audio-player-media-chrome-KA5DY54G.js.map +0 -1
- package/dist/_flow-boundary-D63PJ65S.js.map +0 -1
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js +0 -11
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js.map +0 -1
- package/dist/_persona-rive-RFR2EUWP.js.map +0 -1
- package/src/_interactive-terminal-xterm.ts +0 -32
- package/src/blocks-ai-composer.stories.tsx +0 -83
- package/src/context.stories.tsx +0 -16
- package/src/interactive-terminal.stories.tsx +0 -165
- package/src/interactive-terminal.test.tsx +0 -448
- package/src/interactive-terminal.tsx +0 -444
- package/src/model-selector.test.tsx +0 -55
- package/src/model-selector.tsx +0 -225
- package/src/terminal.tsx +0 -244
package/src/model-selector.tsx
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Command,
|
|
3
|
-
CommandDialog,
|
|
4
|
-
CommandEmpty,
|
|
5
|
-
CommandGroup,
|
|
6
|
-
CommandInput,
|
|
7
|
-
CommandItem,
|
|
8
|
-
CommandList,
|
|
9
|
-
CommandSeparator,
|
|
10
|
-
CommandShortcut,
|
|
11
|
-
} from "@elabs-ai/components-ui";
|
|
12
|
-
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@elabs-ai/components-ui";
|
|
13
|
-
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
14
|
-
import { BotIcon } from "lucide-react";
|
|
15
|
-
import type { ComponentProps, ReactNode } from "react";
|
|
16
|
-
import { useState } from "react";
|
|
17
|
-
|
|
18
|
-
export type ModelSelectorProps = ComponentProps<typeof Dialog>;
|
|
19
|
-
|
|
20
|
-
export const ModelSelector = (props: ModelSelectorProps) => <Dialog {...props} />;
|
|
21
|
-
|
|
22
|
-
export type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;
|
|
23
|
-
|
|
24
|
-
export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => (
|
|
25
|
-
<DialogTrigger {...props} />
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
export type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
|
|
29
|
-
title?: ReactNode;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const ModelSelectorContent = ({
|
|
33
|
-
className,
|
|
34
|
-
children,
|
|
35
|
-
title = "Model Selector",
|
|
36
|
-
...props
|
|
37
|
-
}: ModelSelectorContentProps) => (
|
|
38
|
-
<DialogContent
|
|
39
|
-
aria-describedby={undefined}
|
|
40
|
-
className={cn("outline! border-none! p-0 outline-border! outline-solid!", className)}
|
|
41
|
-
{...props}
|
|
42
|
-
>
|
|
43
|
-
<DialogTitle className="sr-only">{title}</DialogTitle>
|
|
44
|
-
<Command className="**:data-[slot=command-input-wrapper]:h-auto">{children}</Command>
|
|
45
|
-
</DialogContent>
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
export type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;
|
|
49
|
-
|
|
50
|
-
export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => (
|
|
51
|
-
<CommandDialog {...props} />
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
export type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;
|
|
55
|
-
|
|
56
|
-
export const ModelSelectorInput = ({ className, ...props }: ModelSelectorInputProps) => (
|
|
57
|
-
<CommandInput className={cn("h-auto py-3.5", className)} {...props} />
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
export type ModelSelectorListProps = ComponentProps<typeof CommandList>;
|
|
61
|
-
|
|
62
|
-
export const ModelSelectorList = (props: ModelSelectorListProps) => <CommandList {...props} />;
|
|
63
|
-
|
|
64
|
-
export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
|
|
65
|
-
|
|
66
|
-
export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => <CommandEmpty {...props} />;
|
|
67
|
-
|
|
68
|
-
export type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;
|
|
69
|
-
|
|
70
|
-
export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => <CommandGroup {...props} />;
|
|
71
|
-
|
|
72
|
-
export type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;
|
|
73
|
-
|
|
74
|
-
export const ModelSelectorItem = (props: ModelSelectorItemProps) => <CommandItem {...props} />;
|
|
75
|
-
|
|
76
|
-
export type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;
|
|
77
|
-
|
|
78
|
-
export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => (
|
|
79
|
-
<CommandShortcut {...props} />
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
export type ModelSelectorSeparatorProps = ComponentProps<typeof CommandSeparator>;
|
|
83
|
-
|
|
84
|
-
export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => (
|
|
85
|
-
<CommandSeparator {...props} />
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Where provider logos are fetched from by default.
|
|
90
|
-
*
|
|
91
|
-
* This is a REMOTE origin: a deployment with a restrictive `img-src` blocks it.
|
|
92
|
-
* Self-host the SVGs and pass `src` (or set a `fallback`) — see
|
|
93
|
-
* `docs/CSP-AND-NETWORK.md`.
|
|
94
|
-
*/
|
|
95
|
-
export const MODEL_SELECTOR_LOGO_BASE_URL = "https://models.dev/logos";
|
|
96
|
-
|
|
97
|
-
export type ModelSelectorLogoProps = Omit<ComponentProps<"img">, "alt"> & {
|
|
98
|
-
/**
|
|
99
|
-
* Override the logo URL — point at a self-hosted or bundled asset when a CSP
|
|
100
|
-
* blocks `models.dev`. Defaults to `${MODEL_SELECTOR_LOGO_BASE_URL}/<provider>.svg`.
|
|
101
|
-
*/
|
|
102
|
-
src?: string;
|
|
103
|
-
/**
|
|
104
|
-
* Rendered when the logo fails to load (blocked, offline, or unknown
|
|
105
|
-
* provider). Defaults to a neutral Lucide glyph, so the row never collapses
|
|
106
|
-
* to a broken image.
|
|
107
|
-
*/
|
|
108
|
-
fallback?: ReactNode;
|
|
109
|
-
provider:
|
|
110
|
-
| "moonshotai-cn"
|
|
111
|
-
| "lucidquery"
|
|
112
|
-
| "moonshotai"
|
|
113
|
-
| "zai-coding-plan"
|
|
114
|
-
| "alibaba"
|
|
115
|
-
| "xai"
|
|
116
|
-
| "vultr"
|
|
117
|
-
| "nvidia"
|
|
118
|
-
| "upstage"
|
|
119
|
-
| "groq"
|
|
120
|
-
| "github-copilot"
|
|
121
|
-
| "mistral"
|
|
122
|
-
| "vercel"
|
|
123
|
-
| "nebius"
|
|
124
|
-
| "deepseek"
|
|
125
|
-
| "alibaba-cn"
|
|
126
|
-
| "google-vertex-anthropic"
|
|
127
|
-
| "venice"
|
|
128
|
-
| "chutes"
|
|
129
|
-
| "cortecs"
|
|
130
|
-
| "github-models"
|
|
131
|
-
| "togetherai"
|
|
132
|
-
| "azure"
|
|
133
|
-
| "baseten"
|
|
134
|
-
| "huggingface"
|
|
135
|
-
| "opencode"
|
|
136
|
-
| "fastrouter"
|
|
137
|
-
| "google"
|
|
138
|
-
| "google-vertex"
|
|
139
|
-
| "cloudflare-workers-ai"
|
|
140
|
-
| "inception"
|
|
141
|
-
| "wandb"
|
|
142
|
-
| "openai"
|
|
143
|
-
| "zhipuai-coding-plan"
|
|
144
|
-
| "perplexity"
|
|
145
|
-
| "openrouter"
|
|
146
|
-
| "zenmux"
|
|
147
|
-
| "v0"
|
|
148
|
-
| "iflowcn"
|
|
149
|
-
| "synthetic"
|
|
150
|
-
| "deepinfra"
|
|
151
|
-
| "zhipuai"
|
|
152
|
-
| "submodel"
|
|
153
|
-
| "zai"
|
|
154
|
-
| "inference"
|
|
155
|
-
| "requesty"
|
|
156
|
-
| "morph"
|
|
157
|
-
| "lmstudio"
|
|
158
|
-
| "anthropic"
|
|
159
|
-
| "aihubmix"
|
|
160
|
-
| "fireworks-ai"
|
|
161
|
-
| "modelscope"
|
|
162
|
-
| "llama"
|
|
163
|
-
| "scaleway"
|
|
164
|
-
| "amazon-bedrock"
|
|
165
|
-
| "cerebras"
|
|
166
|
-
// oxlint-disable-next-line typescript-eslint(ban-types) -- intentional pattern for autocomplete-friendly string union
|
|
167
|
-
| (string & {});
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export const ModelSelectorLogo = ({
|
|
171
|
-
provider,
|
|
172
|
-
className,
|
|
173
|
-
src,
|
|
174
|
-
fallback,
|
|
175
|
-
onError,
|
|
176
|
-
...props
|
|
177
|
-
}: ModelSelectorLogoProps) => {
|
|
178
|
-
const [failed, setFailed] = useState(false);
|
|
179
|
-
|
|
180
|
-
// A blocked/missing logo must not leave a broken-image glyph in the row.
|
|
181
|
-
if (failed) {
|
|
182
|
-
return (
|
|
183
|
-
<>
|
|
184
|
-
{fallback ?? (
|
|
185
|
-
<BotIcon aria-label={`${provider} logo`} className={cn("size-3", className)} role="img" />
|
|
186
|
-
)}
|
|
187
|
-
</>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
<img
|
|
193
|
-
{...props}
|
|
194
|
-
alt={`${provider} logo`}
|
|
195
|
-
className={cn("size-3 dark:invert", className)}
|
|
196
|
-
height={12}
|
|
197
|
-
onError={(event) => {
|
|
198
|
-
setFailed(true);
|
|
199
|
-
onError?.(event);
|
|
200
|
-
}}
|
|
201
|
-
// AFTER the spread, so the caller's `src` wins via the destructured
|
|
202
|
-
// default rather than being silently overwritten.
|
|
203
|
-
src={src ?? `${MODEL_SELECTOR_LOGO_BASE_URL}/${provider}.svg`}
|
|
204
|
-
width={12}
|
|
205
|
-
/>
|
|
206
|
-
);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
export type ModelSelectorLogoGroupProps = ComponentProps<"div">;
|
|
210
|
-
|
|
211
|
-
export const ModelSelectorLogoGroup = ({ className, ...props }: ModelSelectorLogoGroupProps) => (
|
|
212
|
-
<div
|
|
213
|
-
className={cn(
|
|
214
|
-
"flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:bg-background [&>img]:p-px [&>img]:ring-1 dark:[&>img]:bg-foreground",
|
|
215
|
-
className,
|
|
216
|
-
)}
|
|
217
|
-
{...props}
|
|
218
|
-
/>
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
export type ModelSelectorNameProps = ComponentProps<"span">;
|
|
222
|
-
|
|
223
|
-
export const ModelSelectorName = ({ className, ...props }: ModelSelectorNameProps) => (
|
|
224
|
-
<span className={cn("flex-1 truncate text-start", className)} {...props} />
|
|
225
|
-
);
|
package/src/terminal.tsx
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Button } from "@elabs-ai/components-ui";
|
|
4
|
-
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
5
|
-
import Ansi from "ansi-to-react";
|
|
6
|
-
import { CheckIcon, CopyIcon, TerminalIcon, Trash2Icon } from "lucide-react";
|
|
7
|
-
import type { ComponentProps, HTMLAttributes } from "react";
|
|
8
|
-
import {
|
|
9
|
-
createContext,
|
|
10
|
-
useCallback,
|
|
11
|
-
useContext,
|
|
12
|
-
useEffect,
|
|
13
|
-
useMemo,
|
|
14
|
-
useRef,
|
|
15
|
-
useState,
|
|
16
|
-
} from "react";
|
|
17
|
-
|
|
18
|
-
interface TerminalContextType {
|
|
19
|
-
output: string;
|
|
20
|
-
isStreaming: boolean;
|
|
21
|
-
autoScroll: boolean;
|
|
22
|
-
onClear?: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const TerminalContext = createContext<TerminalContextType>({
|
|
26
|
-
autoScroll: true,
|
|
27
|
-
isStreaming: false,
|
|
28
|
-
output: "",
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export type TerminalHeaderProps = HTMLAttributes<HTMLDivElement>;
|
|
32
|
-
|
|
33
|
-
export const TerminalHeader = ({ className, children, ...props }: TerminalHeaderProps) => (
|
|
34
|
-
<div
|
|
35
|
-
className={cn(
|
|
36
|
-
"flex items-center justify-between border-zinc-800 border-b px-4 py-2",
|
|
37
|
-
className,
|
|
38
|
-
)}
|
|
39
|
-
{...props}
|
|
40
|
-
>
|
|
41
|
-
{children}
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
export type TerminalTitleProps = HTMLAttributes<HTMLDivElement>;
|
|
46
|
-
|
|
47
|
-
export const TerminalTitle = ({ className, children, ...props }: TerminalTitleProps) => (
|
|
48
|
-
<div className={cn("flex items-center gap-2 text-sm text-zinc-400", className)} {...props}>
|
|
49
|
-
<TerminalIcon className="size-4" />
|
|
50
|
-
{children ?? "Terminal"}
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
export type TerminalStatusProps = HTMLAttributes<HTMLDivElement>;
|
|
55
|
-
|
|
56
|
-
export const TerminalStatus = ({ className, children, ...props }: TerminalStatusProps) => {
|
|
57
|
-
const { isStreaming } = useContext(TerminalContext);
|
|
58
|
-
|
|
59
|
-
if (!isStreaming) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div className={cn("flex items-center gap-2 text-xs text-zinc-400", className)} {...props}>
|
|
65
|
-
{children}
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export type TerminalActionsProps = HTMLAttributes<HTMLDivElement>;
|
|
71
|
-
|
|
72
|
-
export const TerminalActions = ({ className, children, ...props }: TerminalActionsProps) => (
|
|
73
|
-
<div className={cn("flex items-center gap-1", className)} {...props}>
|
|
74
|
-
{children}
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
export type TerminalCopyButtonProps = ComponentProps<typeof Button> & {
|
|
79
|
-
onCopy?: () => void;
|
|
80
|
-
onError?: (error: Error) => void;
|
|
81
|
-
timeout?: number;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export const TerminalCopyButton = ({
|
|
85
|
-
onCopy,
|
|
86
|
-
onError,
|
|
87
|
-
timeout = 2000,
|
|
88
|
-
children,
|
|
89
|
-
className,
|
|
90
|
-
...props
|
|
91
|
-
}: TerminalCopyButtonProps) => {
|
|
92
|
-
const [isCopied, setIsCopied] = useState(false);
|
|
93
|
-
const timeoutRef = useRef<number>(0);
|
|
94
|
-
const { output } = useContext(TerminalContext);
|
|
95
|
-
|
|
96
|
-
const copyToClipboard = useCallback(async () => {
|
|
97
|
-
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
|
|
98
|
-
onError?.(new Error("Clipboard API not available"));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
await navigator.clipboard.writeText(output);
|
|
104
|
-
setIsCopied(true);
|
|
105
|
-
onCopy?.();
|
|
106
|
-
timeoutRef.current = window.setTimeout(() => setIsCopied(false), timeout);
|
|
107
|
-
} catch (error) {
|
|
108
|
-
onError?.(error as Error);
|
|
109
|
-
}
|
|
110
|
-
}, [output, onCopy, onError, timeout]);
|
|
111
|
-
|
|
112
|
-
useEffect(
|
|
113
|
-
() => () => {
|
|
114
|
-
window.clearTimeout(timeoutRef.current);
|
|
115
|
-
},
|
|
116
|
-
[],
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const Icon = isCopied ? CheckIcon : CopyIcon;
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<Button
|
|
123
|
-
className={cn(
|
|
124
|
-
"size-7 shrink-0 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100",
|
|
125
|
-
className,
|
|
126
|
-
)}
|
|
127
|
-
onClick={copyToClipboard}
|
|
128
|
-
size="icon"
|
|
129
|
-
variant="ghost"
|
|
130
|
-
{...props}
|
|
131
|
-
>
|
|
132
|
-
{children ?? <Icon size={14} />}
|
|
133
|
-
</Button>
|
|
134
|
-
);
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
export type TerminalClearButtonProps = ComponentProps<typeof Button>;
|
|
138
|
-
|
|
139
|
-
export const TerminalClearButton = ({
|
|
140
|
-
children,
|
|
141
|
-
className,
|
|
142
|
-
...props
|
|
143
|
-
}: TerminalClearButtonProps) => {
|
|
144
|
-
const { onClear } = useContext(TerminalContext);
|
|
145
|
-
|
|
146
|
-
if (!onClear) {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<Button
|
|
152
|
-
className={cn(
|
|
153
|
-
"size-7 shrink-0 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100",
|
|
154
|
-
className,
|
|
155
|
-
)}
|
|
156
|
-
onClick={onClear}
|
|
157
|
-
size="icon"
|
|
158
|
-
variant="ghost"
|
|
159
|
-
{...props}
|
|
160
|
-
>
|
|
161
|
-
{children ?? <Trash2Icon size={14} />}
|
|
162
|
-
</Button>
|
|
163
|
-
);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export type TerminalContentProps = HTMLAttributes<HTMLDivElement>;
|
|
167
|
-
|
|
168
|
-
export const TerminalContent = ({ className, children, ...props }: TerminalContentProps) => {
|
|
169
|
-
const { output, isStreaming, autoScroll } = useContext(TerminalContext);
|
|
170
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
171
|
-
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (autoScroll && containerRef.current) {
|
|
174
|
-
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
175
|
-
}
|
|
176
|
-
}, [output, autoScroll]);
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<div
|
|
180
|
-
className={cn("max-h-96 overflow-auto p-4 font-mono text-sm leading-relaxed", className)}
|
|
181
|
-
ref={containerRef}
|
|
182
|
-
{...props}
|
|
183
|
-
>
|
|
184
|
-
{children ?? (
|
|
185
|
-
<pre className="whitespace-pre-wrap break-words">
|
|
186
|
-
<Ansi>{output}</Ansi>
|
|
187
|
-
{isStreaming && (
|
|
188
|
-
<span className="ml-0.5 inline-block h-4 w-2 animate-pulse bg-zinc-100" />
|
|
189
|
-
)}
|
|
190
|
-
</pre>
|
|
191
|
-
)}
|
|
192
|
-
</div>
|
|
193
|
-
);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
export type TerminalProps = HTMLAttributes<HTMLDivElement> & {
|
|
197
|
-
output: string;
|
|
198
|
-
isStreaming?: boolean;
|
|
199
|
-
autoScroll?: boolean;
|
|
200
|
-
onClear?: () => void;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
export const Terminal = ({
|
|
204
|
-
output,
|
|
205
|
-
isStreaming = false,
|
|
206
|
-
autoScroll = true,
|
|
207
|
-
onClear,
|
|
208
|
-
className,
|
|
209
|
-
children,
|
|
210
|
-
...props
|
|
211
|
-
}: TerminalProps) => {
|
|
212
|
-
const contextValue = useMemo(
|
|
213
|
-
() => ({ autoScroll, isStreaming, onClear, output }),
|
|
214
|
-
[autoScroll, isStreaming, onClear, output],
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
return (
|
|
218
|
-
<TerminalContext.Provider value={contextValue}>
|
|
219
|
-
<div
|
|
220
|
-
className={cn(
|
|
221
|
-
"flex flex-col overflow-hidden rounded-lg border bg-zinc-950 text-zinc-100",
|
|
222
|
-
className,
|
|
223
|
-
)}
|
|
224
|
-
{...props}
|
|
225
|
-
>
|
|
226
|
-
{children ?? (
|
|
227
|
-
<>
|
|
228
|
-
<TerminalHeader>
|
|
229
|
-
<TerminalTitle />
|
|
230
|
-
<div className="flex items-center gap-1">
|
|
231
|
-
<TerminalStatus />
|
|
232
|
-
<TerminalActions>
|
|
233
|
-
<TerminalCopyButton />
|
|
234
|
-
{onClear && <TerminalClearButton />}
|
|
235
|
-
</TerminalActions>
|
|
236
|
-
</div>
|
|
237
|
-
</TerminalHeader>
|
|
238
|
-
<TerminalContent />
|
|
239
|
-
</>
|
|
240
|
-
)}
|
|
241
|
-
</div>
|
|
242
|
-
</TerminalContext.Provider>
|
|
243
|
-
);
|
|
244
|
-
};
|