@cloudbase/agent-react-ui 1.0.1-alpha.32
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 +123 -0
- package/components.json +21 -0
- package/dist/index.css +4241 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +2169 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2182 -0
- package/dist/index.mjs.map +1 -0
- package/example/.env.sample +2 -0
- package/example/App.tsx +368 -0
- package/example/app.css +1 -0
- package/example/index.html +12 -0
- package/example/main.tsx +9 -0
- package/example/vite.config.ts +34 -0
- package/package.json +75 -0
- package/postcss.config.cjs +3 -0
- package/src/components/ai-elements/agent.tsx +140 -0
- package/src/components/ai-elements/artifact.tsx +147 -0
- package/src/components/ai-elements/attachments.tsx +421 -0
- package/src/components/ai-elements/audio-player.tsx +228 -0
- package/src/components/ai-elements/canvas.tsx +22 -0
- package/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/src/components/ai-elements/checkpoint.tsx +71 -0
- package/src/components/ai-elements/code-block.tsx +532 -0
- package/src/components/ai-elements/commit.tsx +448 -0
- package/src/components/ai-elements/confirmation.tsx +176 -0
- package/src/components/ai-elements/connection.tsx +28 -0
- package/src/components/ai-elements/context.tsx +408 -0
- package/src/components/ai-elements/controls.tsx +18 -0
- package/src/components/ai-elements/conversation.tsx +100 -0
- package/src/components/ai-elements/edge.tsx +140 -0
- package/src/components/ai-elements/environment-variables.tsx +295 -0
- package/src/components/ai-elements/file-tree.tsx +258 -0
- package/src/components/ai-elements/image.tsx +24 -0
- package/src/components/ai-elements/inline-citation.tsx +287 -0
- package/src/components/ai-elements/message.tsx +336 -0
- package/src/components/ai-elements/mic-selector.tsx +370 -0
- package/src/components/ai-elements/model-selector.tsx +211 -0
- package/src/components/ai-elements/node.tsx +71 -0
- package/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/src/components/ai-elements/package-info.tsx +233 -0
- package/src/components/ai-elements/panel.tsx +15 -0
- package/src/components/ai-elements/persona.tsx +270 -0
- package/src/components/ai-elements/plan.tsx +142 -0
- package/src/components/ai-elements/prompt-input.tsx +1263 -0
- package/src/components/ai-elements/queue.tsx +274 -0
- package/src/components/ai-elements/reasoning.tsx +193 -0
- package/src/components/ai-elements/sandbox.tsx +126 -0
- package/src/components/ai-elements/schema-display.tsx +458 -0
- package/src/components/ai-elements/shimmer.tsx +64 -0
- package/src/components/ai-elements/snippet.tsx +139 -0
- package/src/components/ai-elements/sources.tsx +77 -0
- package/src/components/ai-elements/speech-input.tsx +301 -0
- package/src/components/ai-elements/stack-trace.tsx +482 -0
- package/src/components/ai-elements/suggestion.tsx +53 -0
- package/src/components/ai-elements/task.tsx +87 -0
- package/src/components/ai-elements/terminal.tsx +261 -0
- package/src/components/ai-elements/test-results.tsx +485 -0
- package/src/components/ai-elements/tool.tsx +174 -0
- package/src/components/ai-elements/toolbar.tsx +16 -0
- package/src/components/ai-elements/transcription.tsx +124 -0
- package/src/components/ai-elements/voice-selector.tsx +479 -0
- package/src/components/ai-elements/web-preview.tsx +263 -0
- package/src/components/chat/Chat.tsx +178 -0
- package/src/components/chat/Input.tsx +98 -0
- package/src/components/chat/Message.tsx +276 -0
- package/src/components/chat/index.ts +2 -0
- package/src/components/index.ts +1 -0
- package/src/components/ui/accordion.tsx +64 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/avatar.tsx +107 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/carousel.tsx +239 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/hover-card.tsx +42 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +33 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/css/global.css +123 -0
- package/src/css/index.css +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +31 -0
- package/src/index.ts +4 -0
- package/src/lib/utils.ts +6 -0
- package/src/locales/context.ts +8 -0
- package/src/locales/hooks.ts +20 -0
- package/src/locales/index.ts +3 -0
- package/src/locales/langs/en.ts +17 -0
- package/src/locales/langs/index.ts +12 -0
- package/src/locales/langs/zh-cn.ts +18 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +21 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Switch } from "@/components/ui/switch";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
import { CheckIcon, CopyIcon, EyeIcon, EyeOffIcon } from "lucide-react";
|
|
8
|
+
import {
|
|
9
|
+
type ComponentProps,
|
|
10
|
+
createContext,
|
|
11
|
+
type HTMLAttributes,
|
|
12
|
+
useContext,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
15
|
+
|
|
16
|
+
interface EnvironmentVariablesContextType {
|
|
17
|
+
showValues: boolean;
|
|
18
|
+
setShowValues: (show: boolean) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const EnvironmentVariablesContext =
|
|
22
|
+
createContext<EnvironmentVariablesContextType>({
|
|
23
|
+
showValues: false,
|
|
24
|
+
setShowValues: () => undefined,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type EnvironmentVariablesProps = HTMLAttributes<HTMLDivElement> & {
|
|
28
|
+
showValues?: boolean;
|
|
29
|
+
defaultShowValues?: boolean;
|
|
30
|
+
onShowValuesChange?: (show: boolean) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const EnvironmentVariables = ({
|
|
34
|
+
showValues: controlledShowValues,
|
|
35
|
+
defaultShowValues = false,
|
|
36
|
+
onShowValuesChange,
|
|
37
|
+
className,
|
|
38
|
+
children,
|
|
39
|
+
...props
|
|
40
|
+
}: EnvironmentVariablesProps) => {
|
|
41
|
+
const [internalShowValues, setInternalShowValues] =
|
|
42
|
+
useState(defaultShowValues);
|
|
43
|
+
const showValues = controlledShowValues ?? internalShowValues;
|
|
44
|
+
|
|
45
|
+
const setShowValues = (show: boolean) => {
|
|
46
|
+
setInternalShowValues(show);
|
|
47
|
+
onShowValuesChange?.(show);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<EnvironmentVariablesContext.Provider value={{ showValues, setShowValues }}>
|
|
52
|
+
<div
|
|
53
|
+
className={cn("rounded-lg border bg-background", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
</div>
|
|
58
|
+
</EnvironmentVariablesContext.Provider>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type EnvironmentVariablesHeaderProps = HTMLAttributes<HTMLDivElement>;
|
|
63
|
+
|
|
64
|
+
export const EnvironmentVariablesHeader = ({
|
|
65
|
+
className,
|
|
66
|
+
children,
|
|
67
|
+
...props
|
|
68
|
+
}: EnvironmentVariablesHeaderProps) => (
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
"flex items-center justify-between border-b px-4 py-3",
|
|
72
|
+
className
|
|
73
|
+
)}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
export type EnvironmentVariablesTitleProps = HTMLAttributes<HTMLHeadingElement>;
|
|
81
|
+
|
|
82
|
+
export const EnvironmentVariablesTitle = ({
|
|
83
|
+
className,
|
|
84
|
+
children,
|
|
85
|
+
...props
|
|
86
|
+
}: EnvironmentVariablesTitleProps) => (
|
|
87
|
+
<h3 className={cn("font-medium text-sm", className)} {...props}>
|
|
88
|
+
{children ?? "Environment Variables"}
|
|
89
|
+
</h3>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
export type EnvironmentVariablesToggleProps = ComponentProps<typeof Switch>;
|
|
93
|
+
|
|
94
|
+
export const EnvironmentVariablesToggle = ({
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}: EnvironmentVariablesToggleProps) => {
|
|
98
|
+
const { showValues, setShowValues } = useContext(EnvironmentVariablesContext);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className={cn("flex items-center gap-2", className)}>
|
|
102
|
+
<span className="text-muted-foreground text-xs">
|
|
103
|
+
{showValues ? <EyeIcon size={14} /> : <EyeOffIcon size={14} />}
|
|
104
|
+
</span>
|
|
105
|
+
<Switch
|
|
106
|
+
aria-label="Toggle value visibility"
|
|
107
|
+
checked={showValues}
|
|
108
|
+
onCheckedChange={setShowValues}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type EnvironmentVariablesContentProps = HTMLAttributes<HTMLDivElement>;
|
|
116
|
+
|
|
117
|
+
export const EnvironmentVariablesContent = ({
|
|
118
|
+
className,
|
|
119
|
+
children,
|
|
120
|
+
...props
|
|
121
|
+
}: EnvironmentVariablesContentProps) => (
|
|
122
|
+
<div className={cn("divide-y", className)} {...props}>
|
|
123
|
+
{children}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
interface EnvironmentVariableContextType {
|
|
128
|
+
name: string;
|
|
129
|
+
value: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const EnvironmentVariableContext =
|
|
133
|
+
createContext<EnvironmentVariableContextType>({
|
|
134
|
+
name: "",
|
|
135
|
+
value: "",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export type EnvironmentVariableProps = HTMLAttributes<HTMLDivElement> & {
|
|
139
|
+
name: string;
|
|
140
|
+
value: string;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const EnvironmentVariable = ({
|
|
144
|
+
name,
|
|
145
|
+
value,
|
|
146
|
+
className,
|
|
147
|
+
children,
|
|
148
|
+
...props
|
|
149
|
+
}: EnvironmentVariableProps) => (
|
|
150
|
+
<EnvironmentVariableContext.Provider value={{ name, value }}>
|
|
151
|
+
<div
|
|
152
|
+
className={cn(
|
|
153
|
+
"flex items-center justify-between gap-4 px-4 py-3",
|
|
154
|
+
className
|
|
155
|
+
)}
|
|
156
|
+
{...props}
|
|
157
|
+
>
|
|
158
|
+
{children ?? (
|
|
159
|
+
<>
|
|
160
|
+
<div className="flex items-center gap-2">
|
|
161
|
+
<EnvironmentVariableName />
|
|
162
|
+
</div>
|
|
163
|
+
<EnvironmentVariableValue />
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
</EnvironmentVariableContext.Provider>
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
export type EnvironmentVariableGroupProps = HTMLAttributes<HTMLDivElement>;
|
|
171
|
+
|
|
172
|
+
export const EnvironmentVariableGroup = ({
|
|
173
|
+
className,
|
|
174
|
+
children,
|
|
175
|
+
...props
|
|
176
|
+
}: EnvironmentVariableGroupProps) => (
|
|
177
|
+
<div className={cn("flex items-center gap-2", className)} {...props}>
|
|
178
|
+
{children}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
export type EnvironmentVariableNameProps = HTMLAttributes<HTMLSpanElement>;
|
|
183
|
+
|
|
184
|
+
export const EnvironmentVariableName = ({
|
|
185
|
+
className,
|
|
186
|
+
children,
|
|
187
|
+
...props
|
|
188
|
+
}: EnvironmentVariableNameProps) => {
|
|
189
|
+
const { name } = useContext(EnvironmentVariableContext);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<span className={cn("font-mono text-sm", className)} {...props}>
|
|
193
|
+
{children ?? name}
|
|
194
|
+
</span>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export type EnvironmentVariableValueProps = HTMLAttributes<HTMLSpanElement>;
|
|
199
|
+
|
|
200
|
+
export const EnvironmentVariableValue = ({
|
|
201
|
+
className,
|
|
202
|
+
children,
|
|
203
|
+
...props
|
|
204
|
+
}: EnvironmentVariableValueProps) => {
|
|
205
|
+
const { value } = useContext(EnvironmentVariableContext);
|
|
206
|
+
const { showValues } = useContext(EnvironmentVariablesContext);
|
|
207
|
+
|
|
208
|
+
const displayValue = showValues
|
|
209
|
+
? value
|
|
210
|
+
: "•".repeat(Math.min(value.length, 20));
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<span
|
|
214
|
+
className={cn(
|
|
215
|
+
"font-mono text-muted-foreground text-sm",
|
|
216
|
+
!showValues && "select-none",
|
|
217
|
+
className
|
|
218
|
+
)}
|
|
219
|
+
{...props}
|
|
220
|
+
>
|
|
221
|
+
{children ?? displayValue}
|
|
222
|
+
</span>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export type EnvironmentVariableCopyButtonProps = ComponentProps<
|
|
227
|
+
typeof Button
|
|
228
|
+
> & {
|
|
229
|
+
onCopy?: () => void;
|
|
230
|
+
onError?: (error: Error) => void;
|
|
231
|
+
timeout?: number;
|
|
232
|
+
copyFormat?: "name" | "value" | "export";
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export const EnvironmentVariableCopyButton = ({
|
|
236
|
+
onCopy,
|
|
237
|
+
onError,
|
|
238
|
+
timeout = 2000,
|
|
239
|
+
copyFormat = "value",
|
|
240
|
+
children,
|
|
241
|
+
className,
|
|
242
|
+
...props
|
|
243
|
+
}: EnvironmentVariableCopyButtonProps) => {
|
|
244
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
245
|
+
const { name, value } = useContext(EnvironmentVariableContext);
|
|
246
|
+
|
|
247
|
+
const copyToClipboard = async () => {
|
|
248
|
+
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
|
|
249
|
+
onError?.(new Error("Clipboard API not available"));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let textToCopy = value;
|
|
254
|
+
if (copyFormat === "name") {
|
|
255
|
+
textToCopy = name;
|
|
256
|
+
} else if (copyFormat === "export") {
|
|
257
|
+
textToCopy = `export ${name}="${value}"`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
262
|
+
setIsCopied(true);
|
|
263
|
+
onCopy?.();
|
|
264
|
+
setTimeout(() => setIsCopied(false), timeout);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
onError?.(error as Error);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const Icon = isCopied ? CheckIcon : CopyIcon;
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<Button
|
|
274
|
+
className={cn("size-6 shrink-0", className)}
|
|
275
|
+
onClick={copyToClipboard}
|
|
276
|
+
size="icon"
|
|
277
|
+
variant="ghost"
|
|
278
|
+
{...props}
|
|
279
|
+
>
|
|
280
|
+
{children ?? <Icon size={12} />}
|
|
281
|
+
</Button>
|
|
282
|
+
);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export type EnvironmentVariableRequiredProps = ComponentProps<typeof Badge>;
|
|
286
|
+
|
|
287
|
+
export const EnvironmentVariableRequired = ({
|
|
288
|
+
className,
|
|
289
|
+
children,
|
|
290
|
+
...props
|
|
291
|
+
}: EnvironmentVariableRequiredProps) => (
|
|
292
|
+
<Badge className={cn("text-xs", className)} variant="secondary" {...props}>
|
|
293
|
+
{children ?? "Required"}
|
|
294
|
+
</Badge>
|
|
295
|
+
);
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Collapsible,
|
|
5
|
+
CollapsibleContent,
|
|
6
|
+
CollapsibleTrigger,
|
|
7
|
+
} from "@/components/ui/collapsible";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
import {
|
|
10
|
+
ChevronRightIcon,
|
|
11
|
+
FileIcon,
|
|
12
|
+
FolderIcon,
|
|
13
|
+
FolderOpenIcon,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import {
|
|
16
|
+
createContext,
|
|
17
|
+
type HTMLAttributes,
|
|
18
|
+
type ReactNode,
|
|
19
|
+
useContext,
|
|
20
|
+
useState,
|
|
21
|
+
} from "react";
|
|
22
|
+
|
|
23
|
+
interface FileTreeContextType {
|
|
24
|
+
expandedPaths: Set<string>;
|
|
25
|
+
togglePath: (path: string) => void;
|
|
26
|
+
selectedPath?: string;
|
|
27
|
+
onSelect?: (path: string) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const FileTreeContext = createContext<FileTreeContextType>({
|
|
31
|
+
expandedPaths: new Set(),
|
|
32
|
+
togglePath: () => undefined,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type FileTreeProps = HTMLAttributes<HTMLDivElement> & {
|
|
36
|
+
expanded?: Set<string>;
|
|
37
|
+
defaultExpanded?: Set<string>;
|
|
38
|
+
selectedPath?: string;
|
|
39
|
+
onSelect?: (path: string) => void;
|
|
40
|
+
onExpandedChange?: (expanded: Set<string>) => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const FileTree = ({
|
|
44
|
+
expanded: controlledExpanded,
|
|
45
|
+
defaultExpanded = new Set(),
|
|
46
|
+
selectedPath,
|
|
47
|
+
onSelect,
|
|
48
|
+
onExpandedChange,
|
|
49
|
+
className,
|
|
50
|
+
children,
|
|
51
|
+
...props
|
|
52
|
+
}: FileTreeProps) => {
|
|
53
|
+
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
|
|
54
|
+
const expandedPaths = controlledExpanded ?? internalExpanded;
|
|
55
|
+
|
|
56
|
+
const togglePath = (path: string) => {
|
|
57
|
+
const newExpanded = new Set(expandedPaths);
|
|
58
|
+
if (newExpanded.has(path)) {
|
|
59
|
+
newExpanded.delete(path);
|
|
60
|
+
} else {
|
|
61
|
+
newExpanded.add(path);
|
|
62
|
+
}
|
|
63
|
+
setInternalExpanded(newExpanded);
|
|
64
|
+
onExpandedChange?.(newExpanded);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<FileTreeContext.Provider
|
|
69
|
+
value={{ expandedPaths, togglePath, selectedPath, onSelect }}
|
|
70
|
+
>
|
|
71
|
+
<div
|
|
72
|
+
className={cn(
|
|
73
|
+
"rounded-lg border bg-background font-mono text-sm",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
role="tree"
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<div className="p-2">{children}</div>
|
|
80
|
+
</div>
|
|
81
|
+
</FileTreeContext.Provider>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
interface FileTreeFolderContextType {
|
|
86
|
+
path: string;
|
|
87
|
+
name: string;
|
|
88
|
+
isExpanded: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const FileTreeFolderContext = createContext<FileTreeFolderContextType>({
|
|
92
|
+
path: "",
|
|
93
|
+
name: "",
|
|
94
|
+
isExpanded: false,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export type FileTreeFolderProps = HTMLAttributes<HTMLDivElement> & {
|
|
98
|
+
path: string;
|
|
99
|
+
name: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const FileTreeFolder = ({
|
|
103
|
+
path,
|
|
104
|
+
name,
|
|
105
|
+
className,
|
|
106
|
+
children,
|
|
107
|
+
...props
|
|
108
|
+
}: FileTreeFolderProps) => {
|
|
109
|
+
const { expandedPaths, togglePath, selectedPath, onSelect } =
|
|
110
|
+
useContext(FileTreeContext);
|
|
111
|
+
const isExpanded = expandedPaths.has(path);
|
|
112
|
+
const isSelected = selectedPath === path;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<FileTreeFolderContext.Provider value={{ path, name, isExpanded }}>
|
|
116
|
+
<Collapsible onOpenChange={() => togglePath(path)} open={isExpanded}>
|
|
117
|
+
<div
|
|
118
|
+
className={cn("", className)}
|
|
119
|
+
role="treeitem"
|
|
120
|
+
tabIndex={0}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
<CollapsibleTrigger asChild>
|
|
124
|
+
<button
|
|
125
|
+
className={cn(
|
|
126
|
+
"flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
|
|
127
|
+
isSelected && "bg-muted"
|
|
128
|
+
)}
|
|
129
|
+
onClick={() => onSelect?.(path)}
|
|
130
|
+
type="button"
|
|
131
|
+
>
|
|
132
|
+
<ChevronRightIcon
|
|
133
|
+
className={cn(
|
|
134
|
+
"size-4 shrink-0 text-muted-foreground transition-transform",
|
|
135
|
+
isExpanded && "rotate-90"
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
<FileTreeIcon>
|
|
139
|
+
{isExpanded ? (
|
|
140
|
+
<FolderOpenIcon className="size-4 text-blue-500" />
|
|
141
|
+
) : (
|
|
142
|
+
<FolderIcon className="size-4 text-blue-500" />
|
|
143
|
+
)}
|
|
144
|
+
</FileTreeIcon>
|
|
145
|
+
<FileTreeName>{name}</FileTreeName>
|
|
146
|
+
</button>
|
|
147
|
+
</CollapsibleTrigger>
|
|
148
|
+
<CollapsibleContent>
|
|
149
|
+
<div className="ml-4 border-l pl-2">{children}</div>
|
|
150
|
+
</CollapsibleContent>
|
|
151
|
+
</div>
|
|
152
|
+
</Collapsible>
|
|
153
|
+
</FileTreeFolderContext.Provider>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
interface FileTreeFileContextType {
|
|
158
|
+
path: string;
|
|
159
|
+
name: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const FileTreeFileContext = createContext<FileTreeFileContextType>({
|
|
163
|
+
path: "",
|
|
164
|
+
name: "",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export type FileTreeFileProps = HTMLAttributes<HTMLDivElement> & {
|
|
168
|
+
path: string;
|
|
169
|
+
name: string;
|
|
170
|
+
icon?: ReactNode;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const FileTreeFile = ({
|
|
174
|
+
path,
|
|
175
|
+
name,
|
|
176
|
+
icon,
|
|
177
|
+
className,
|
|
178
|
+
children,
|
|
179
|
+
...props
|
|
180
|
+
}: FileTreeFileProps) => {
|
|
181
|
+
const { selectedPath, onSelect } = useContext(FileTreeContext);
|
|
182
|
+
const isSelected = selectedPath === path;
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<FileTreeFileContext.Provider value={{ path, name }}>
|
|
186
|
+
<div
|
|
187
|
+
className={cn(
|
|
188
|
+
"flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
|
|
189
|
+
isSelected && "bg-muted",
|
|
190
|
+
className
|
|
191
|
+
)}
|
|
192
|
+
onClick={() => onSelect?.(path)}
|
|
193
|
+
onKeyDown={(e) => {
|
|
194
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
195
|
+
onSelect?.(path);
|
|
196
|
+
}
|
|
197
|
+
}}
|
|
198
|
+
role="treeitem"
|
|
199
|
+
tabIndex={0}
|
|
200
|
+
{...props}
|
|
201
|
+
>
|
|
202
|
+
{children ?? (
|
|
203
|
+
<>
|
|
204
|
+
<span className="size-4" /> {/* Spacer for alignment */}
|
|
205
|
+
<FileTreeIcon>
|
|
206
|
+
{icon ?? <FileIcon className="size-4 text-muted-foreground" />}
|
|
207
|
+
</FileTreeIcon>
|
|
208
|
+
<FileTreeName>{name}</FileTreeName>
|
|
209
|
+
</>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</FileTreeFileContext.Provider>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type FileTreeIconProps = HTMLAttributes<HTMLSpanElement>;
|
|
217
|
+
|
|
218
|
+
export const FileTreeIcon = ({
|
|
219
|
+
className,
|
|
220
|
+
children,
|
|
221
|
+
...props
|
|
222
|
+
}: FileTreeIconProps) => (
|
|
223
|
+
<span className={cn("shrink-0", className)} {...props}>
|
|
224
|
+
{children}
|
|
225
|
+
</span>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
export type FileTreeNameProps = HTMLAttributes<HTMLSpanElement>;
|
|
229
|
+
|
|
230
|
+
export const FileTreeName = ({
|
|
231
|
+
className,
|
|
232
|
+
children,
|
|
233
|
+
...props
|
|
234
|
+
}: FileTreeNameProps) => (
|
|
235
|
+
<span className={cn("truncate", className)} {...props}>
|
|
236
|
+
{children}
|
|
237
|
+
</span>
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
export type FileTreeActionsProps = HTMLAttributes<HTMLDivElement>;
|
|
241
|
+
|
|
242
|
+
export const FileTreeActions = ({
|
|
243
|
+
className,
|
|
244
|
+
children,
|
|
245
|
+
...props
|
|
246
|
+
}: FileTreeActionsProps) => (
|
|
247
|
+
// biome-ignore lint/a11y/noNoninteractiveElementInteractions: stopPropagation required for nested interactions
|
|
248
|
+
// biome-ignore lint/a11y/useSemanticElements: fieldset doesn't fit this UI pattern
|
|
249
|
+
<div
|
|
250
|
+
className={cn("ml-auto flex items-center gap-1", className)}
|
|
251
|
+
onClick={(e) => e.stopPropagation()}
|
|
252
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
253
|
+
role="group"
|
|
254
|
+
{...props}
|
|
255
|
+
>
|
|
256
|
+
{children}
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import type { Experimental_GeneratedImage } from "ai";
|
|
3
|
+
|
|
4
|
+
export type ImageProps = Experimental_GeneratedImage & {
|
|
5
|
+
className?: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const Image = ({
|
|
10
|
+
base64,
|
|
11
|
+
uint8Array,
|
|
12
|
+
mediaType,
|
|
13
|
+
...props
|
|
14
|
+
}: ImageProps) => (
|
|
15
|
+
<img
|
|
16
|
+
{...props}
|
|
17
|
+
alt={props.alt}
|
|
18
|
+
className={cn(
|
|
19
|
+
"h-auto max-w-full overflow-hidden rounded-md",
|
|
20
|
+
props.className
|
|
21
|
+
)}
|
|
22
|
+
src={`data:${mediaType};base64,${base64}`}
|
|
23
|
+
/>
|
|
24
|
+
);
|