mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-25 23:14:46 +08:00
feat: integrated with artifacts in states
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
|||||||
import { useSidebar } from "@/components/ui/sidebar";
|
import { useSidebar } from "@/components/ui/sidebar";
|
||||||
import {
|
import {
|
||||||
ArtifactFileDetail,
|
ArtifactFileDetail,
|
||||||
|
ArtifactFileList,
|
||||||
useArtifacts,
|
useArtifacts,
|
||||||
} from "@/components/workspace/artifacts";
|
} from "@/components/workspace/artifacts";
|
||||||
import { FlipDisplay } from "@/components/workspace/flip-display";
|
import { FlipDisplay } from "@/components/workspace/flip-display";
|
||||||
@@ -32,8 +33,10 @@ export default function ChatPage() {
|
|||||||
const [settings, setSettings] = useLocalSettings();
|
const [settings, setSettings] = useLocalSettings();
|
||||||
const { setOpen: setSidebarOpen } = useSidebar();
|
const { setOpen: setSidebarOpen } = useSidebar();
|
||||||
const {
|
const {
|
||||||
|
artifacts,
|
||||||
open: artifactsOpen,
|
open: artifactsOpen,
|
||||||
setOpen: setArtifactsOpen,
|
setOpen: setArtifactsOpen,
|
||||||
|
setArtifacts,
|
||||||
selectedArtifact,
|
selectedArtifact,
|
||||||
} = useArtifacts();
|
} = useArtifacts();
|
||||||
|
|
||||||
@@ -65,6 +68,10 @@ export default function ChatPage() {
|
|||||||
return result;
|
return result;
|
||||||
}, [thread, isNewThread]);
|
}, [thread, isNewThread]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setArtifacts(thread.values.artifacts);
|
||||||
|
}, [setArtifacts, thread.values.artifacts]);
|
||||||
|
|
||||||
const handleSubmit = useSubmitThread({
|
const handleSubmit = useSubmitThread({
|
||||||
isNewThread,
|
isNewThread,
|
||||||
threadId,
|
threadId,
|
||||||
@@ -96,17 +103,17 @@ export default function ChatPage() {
|
|||||||
</FlipDisplay>
|
</FlipDisplay>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{!artifactsOpen && (
|
{artifacts?.length && !artifactsOpen && (
|
||||||
<Tooltip content="Show artifacts">
|
<Tooltip content="Show artifacts of this conversation">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setArtifactsOpen(true);
|
setArtifactsOpen(true);
|
||||||
setSidebarOpen(false);
|
setSidebarOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilesIcon />
|
<FilesIcon />
|
||||||
|
Artifacts
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
@@ -161,7 +168,7 @@ export default function ChatPage() {
|
|||||||
threadId={threadId!}
|
threadId={threadId!}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative flex size-full items-center justify-center">
|
<div className="relative flex size-full justify-center">
|
||||||
<div className="absolute top-1 right-1 z-30">
|
<div className="absolute top-1 right-1 z-30">
|
||||||
<Button
|
<Button
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -173,11 +180,26 @@ export default function ChatPage() {
|
|||||||
<XIcon />
|
<XIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ConversationEmptyState
|
{thread.values.artifacts?.length === 0 ? (
|
||||||
icon={<FilesIcon />}
|
<ConversationEmptyState
|
||||||
title="No artifact selected"
|
icon={<FilesIcon />}
|
||||||
description="Select an artifact to view its details"
|
title="No artifact selected"
|
||||||
/>
|
description="Select an artifact to view its details"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center p-4 pt-8">
|
||||||
|
<header className="shrink-0">
|
||||||
|
<h2 className="text-lg font-medium">Artifacts</h2>
|
||||||
|
</header>
|
||||||
|
<main className="min-h-0 grow">
|
||||||
|
<ArtifactFileList
|
||||||
|
className="max-w-(--container-width-sm) p-4 pt-12"
|
||||||
|
files={thread.values.artifacts ?? []}
|
||||||
|
threadId={threadId!}
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export type ArtifactProps = HTMLAttributes<HTMLDivElement>;
|
|||||||
export const Artifact = ({ className, ...props }: ArtifactProps) => (
|
export const Artifact = ({ className, ...props }: ArtifactProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col overflow-hidden rounded-lg border bg-background shadow-sm",
|
"bg-background flex flex-col overflow-hidden rounded-lg border shadow-sm",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -31,8 +31,8 @@ export const ArtifactHeader = ({
|
|||||||
}: ArtifactHeaderProps) => (
|
}: ArtifactHeaderProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-between border-b bg-muted/50 px-4 py-3",
|
"bg-muted/50 flex items-center justify-between border-b px-4 py-3",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -49,8 +49,8 @@ export const ArtifactClose = ({
|
|||||||
}: ArtifactCloseProps) => (
|
}: ArtifactCloseProps) => (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-8 p-0 text-muted-foreground hover:text-foreground",
|
"text-muted-foreground hover:text-foreground size-8 p-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
size={size}
|
size={size}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -65,8 +65,8 @@ export const ArtifactClose = ({
|
|||||||
export type ArtifactTitleProps = HTMLAttributes<HTMLParagraphElement>;
|
export type ArtifactTitleProps = HTMLAttributes<HTMLParagraphElement>;
|
||||||
|
|
||||||
export const ArtifactTitle = ({ className, ...props }: ArtifactTitleProps) => (
|
export const ArtifactTitle = ({ className, ...props }: ArtifactTitleProps) => (
|
||||||
<p
|
<div
|
||||||
className={cn("font-medium text-foreground text-sm", className)}
|
className={cn("text-foreground text-sm font-medium", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -108,8 +108,8 @@ export const ArtifactAction = ({
|
|||||||
const button = (
|
const button = (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-8 p-0 text-muted-foreground hover:text-foreground",
|
"text-muted-foreground hover:text-foreground size-8 p-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
size={size}
|
size={size}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Select({
|
function Select({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectGroup({
|
function SelectGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectValue({
|
function SelectValue({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectTrigger({
|
function SelectTrigger({
|
||||||
@@ -30,15 +30,15 @@ function SelectTrigger({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
size?: "sm" | "default"
|
size?: "sm" | "default";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -47,7 +47,7 @@ function SelectTrigger({
|
|||||||
<ChevronDownIcon className="size-4 opacity-50" />
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectContent({
|
function SelectContent({
|
||||||
@@ -65,7 +65,7 @@ function SelectContent({
|
|||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
position={position}
|
position={position}
|
||||||
align={align}
|
align={align}
|
||||||
@@ -76,7 +76,7 @@ function SelectContent({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
"p-1",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -84,7 +84,7 @@ function SelectContent({
|
|||||||
<SelectScrollDownButton />
|
<SelectScrollDownButton />
|
||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Content>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectLabel({
|
function SelectLabel({
|
||||||
@@ -97,7 +97,7 @@ function SelectLabel({
|
|||||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectItem({
|
function SelectItem({
|
||||||
@@ -109,8 +109,8 @@ function SelectItem({
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -124,7 +124,7 @@ function SelectItem({
|
|||||||
</span>
|
</span>
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectSeparator({
|
function SelectSeparator({
|
||||||
@@ -137,7 +137,7 @@ function SelectSeparator({
|
|||||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollUpButton({
|
function SelectScrollUpButton({
|
||||||
@@ -149,13 +149,13 @@ function SelectScrollUpButton({
|
|||||||
data-slot="select-scroll-up-button"
|
data-slot="select-scroll-up-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"flex cursor-default items-center justify-center py-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronUpIcon className="size-4" />
|
<ChevronUpIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollUpButton>
|
</SelectPrimitive.ScrollUpButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollDownButton({
|
function SelectScrollDownButton({
|
||||||
@@ -167,13 +167,13 @@ function SelectScrollDownButton({
|
|||||||
data-slot="select-scroll-down-button"
|
data-slot="select-scroll-down-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"flex cursor-default items-center justify-center py-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon className="size-4" />
|
<ChevronDownIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -187,4 +187,4 @@ export {
|
|||||||
SelectSeparator,
|
SelectSeparator,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -12,17 +12,19 @@ import {
|
|||||||
ArtifactAction,
|
ArtifactAction,
|
||||||
ArtifactActions,
|
ArtifactActions,
|
||||||
ArtifactContent,
|
ArtifactContent,
|
||||||
ArtifactDescription,
|
|
||||||
ArtifactHeader,
|
ArtifactHeader,
|
||||||
ArtifactTitle,
|
ArtifactTitle,
|
||||||
} from "@/components/ai-elements/artifact";
|
} from "@/components/ai-elements/artifact";
|
||||||
|
import { Select, SelectItem } from "@/components/ui/select";
|
||||||
|
import {
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||||
import {
|
import { checkCodeFile, getFileName } from "@/core/utils/files";
|
||||||
checkCodeFile,
|
|
||||||
getFileExtensionDisplayName,
|
|
||||||
getFileName,
|
|
||||||
} from "@/core/utils/files";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { useArtifacts } from "./context";
|
import { useArtifacts } from "./context";
|
||||||
@@ -37,7 +39,7 @@ export function ArtifactFileDetail({
|
|||||||
filepath: string;
|
filepath: string;
|
||||||
threadId: string;
|
threadId: string;
|
||||||
}) {
|
}) {
|
||||||
const { setOpen } = useArtifacts();
|
const { artifacts, setOpen, select } = useArtifacts();
|
||||||
const { isCodeFile } = useMemo(() => checkCodeFile(filepath), [filepath]);
|
const { isCodeFile } = useMemo(() => checkCodeFile(filepath), [filepath]);
|
||||||
const { content } = useArtifactContent({
|
const { content } = useArtifactContent({
|
||||||
threadId,
|
threadId,
|
||||||
@@ -46,12 +48,24 @@ export function ArtifactFileDetail({
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Artifact className={cn("rounded-none", className)}>
|
<Artifact className={cn("rounded-none", className)}>
|
||||||
<ArtifactHeader>
|
<ArtifactHeader className="px-2">
|
||||||
<div>
|
<div>
|
||||||
<ArtifactTitle>{getFileName(filepath)}</ArtifactTitle>
|
<ArtifactTitle>
|
||||||
<ArtifactDescription className="mt-1 text-xs">
|
<Select value={filepath} onValueChange={select}>
|
||||||
{getFileExtensionDisplayName(filepath)} file
|
<SelectTrigger className="border-none bg-transparent! select-none focus:outline-0 active:outline-0">
|
||||||
</ArtifactDescription>
|
<SelectValue placeholder="Select a file" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="select-none">
|
||||||
|
<SelectGroup>
|
||||||
|
{(artifacts ?? []).map((filepath) => (
|
||||||
|
<SelectItem key={filepath} value={filepath}>
|
||||||
|
{getFileName(filepath)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</ArtifactTitle>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ArtifactActions>
|
<ArtifactActions>
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ export function ArtifactFileList({
|
|||||||
files: string[];
|
files: string[];
|
||||||
threadId: string;
|
threadId: string;
|
||||||
}) {
|
}) {
|
||||||
const { openArtifact } = useArtifacts();
|
const { select: selectArtifact, setOpen } = useArtifacts();
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(filepath: string) => {
|
(filepath: string) => {
|
||||||
openArtifact(filepath);
|
selectArtifact(filepath);
|
||||||
|
setOpen(true);
|
||||||
},
|
},
|
||||||
[openArtifact],
|
[selectArtifact, setOpen],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<ul className={cn("flex w-full flex-col gap-4", className)}>
|
<ul className={cn("flex w-full flex-col gap-4", className)}>
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ import { useSidebar } from "@/components/ui/sidebar";
|
|||||||
|
|
||||||
export interface ArtifactsContextType {
|
export interface ArtifactsContextType {
|
||||||
artifacts: string[];
|
artifacts: string[];
|
||||||
|
setArtifacts: (artifacts: string[]) => void;
|
||||||
|
|
||||||
selectedArtifact: string | null;
|
selectedArtifact: string | null;
|
||||||
|
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
|
deselect: () => void;
|
||||||
|
|
||||||
addArtifacts: (artifacts: string[]) => void;
|
select: (artifact: string) => void;
|
||||||
openArtifact: (artifact: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ArtifactsContext = createContext<ArtifactsContextType | undefined>(
|
const ArtifactsContext = createContext<ArtifactsContextType | undefined>(
|
||||||
@@ -27,23 +29,25 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { setOpen: setSidebarOpen } = useSidebar();
|
const { setOpen: setSidebarOpen } = useSidebar();
|
||||||
|
|
||||||
const addArtifacts = (newArtifacts: string[]) => {
|
const select = (artifact: string) => {
|
||||||
setArtifacts((prev) => [...prev, ...newArtifacts]);
|
setSelectedArtifact(artifact);
|
||||||
|
setSidebarOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openArtifact = (artifact: string) => {
|
const deselect = () => {
|
||||||
setSelectedArtifact(artifact);
|
setSelectedArtifact(null);
|
||||||
setOpen(true);
|
|
||||||
setSidebarOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const value: ArtifactsContextType = {
|
const value: ArtifactsContextType = {
|
||||||
artifacts,
|
artifacts,
|
||||||
selectedArtifact,
|
setArtifacts,
|
||||||
|
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
addArtifacts,
|
|
||||||
openArtifact,
|
selectedArtifact,
|
||||||
|
select,
|
||||||
|
deselect,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AgentThread } from "../threads";
|
||||||
|
|
||||||
export function urlOfArtifact({
|
export function urlOfArtifact({
|
||||||
filepath,
|
filepath,
|
||||||
threadId,
|
threadId,
|
||||||
@@ -9,3 +11,7 @@ export function urlOfArtifact({
|
|||||||
}) {
|
}) {
|
||||||
return `http://localhost:8000/api/threads/${threadId}/artifacts${filepath}${download ? "?download=true" : ""}`;
|
return `http://localhost:8000/api/threads/${threadId}/artifacts${filepath}${download ? "?download=true" : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractArtifactsFromThread(thread: AgentThread) {
|
||||||
|
return thread.values.artifacts ?? [];
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { Thread } from "@langchain/langgraph-sdk";
|
|||||||
export interface AgentThreadState extends Record<string, unknown> {
|
export interface AgentThreadState extends Record<string, unknown> {
|
||||||
title: string;
|
title: string;
|
||||||
messages: BaseMessage[];
|
messages: BaseMessage[];
|
||||||
|
artifacts: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentThread extends Thread<AgentThreadState> {}
|
export interface AgentThread extends Thread<AgentThreadState> {}
|
||||||
|
|||||||
Reference in New Issue
Block a user