feat: clean up code for artifacts (#129)
This commit is contained in:
61
README.md
61
README.md
@@ -123,6 +123,67 @@ return { messages: [result] };
|
|||||||
|
|
||||||
This approach guarantees the message remains completely hidden from the user interface.
|
This approach guarantees the message remains completely hidden from the user interface.
|
||||||
|
|
||||||
|
## Rendering Artifacts
|
||||||
|
|
||||||
|
The Agent Chat UI supports rendering artifacts in the chat. Artifacts are rendered in a side panel to the right of the chat. To render an artifact, you can obtain the artifact context from the `thread.meta.artifact` field. Here's a sample utility hook for obtaining the artifact context:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export function useArtifact<TContext = Record<string, unknown>>() {
|
||||||
|
type Component = (props: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
title?: React.ReactNode;
|
||||||
|
}) => React.ReactNode;
|
||||||
|
|
||||||
|
type Context = TContext | undefined;
|
||||||
|
|
||||||
|
type Bag = {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (value: boolean | ((prev: boolean) => boolean)) => void;
|
||||||
|
|
||||||
|
context: Context;
|
||||||
|
setContext: (value: Context | ((prev: Context) => Context)) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const thread = useStreamContext<
|
||||||
|
{ messages: Message[]; ui: UIMessage[] },
|
||||||
|
{ MetaType: { artifact: [Component, Bag] } }
|
||||||
|
>();
|
||||||
|
|
||||||
|
return thread.meta?.artifact;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After which you can render additional content using the `Artifact` component from the `useArtifact` hook:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useArtifact } from "../utils/use-artifact";
|
||||||
|
import { LoaderIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export function Writer(props: {
|
||||||
|
title?: string;
|
||||||
|
content?: string;
|
||||||
|
description?: string;
|
||||||
|
}) {
|
||||||
|
const [Artifact, { open, setOpen }] = useArtifact();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
className="cursor-pointer rounded-lg border p-4"
|
||||||
|
>
|
||||||
|
<p className="font-medium">{props.title}</p>
|
||||||
|
<p className="text-sm text-gray-500">{props.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Artifact title={props.title}>
|
||||||
|
<p className="p-4 whitespace-pre-wrap">{props.content}</p>
|
||||||
|
</Artifact>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Going to Production
|
## Going to Production
|
||||||
|
|
||||||
Once you're ready to go to production, you'll need to update how you connect, and authenticate requests to your deployment. By default, the Agent Chat UI is setup for local development, and connects to your LangGraph server directly from the client. This is not possible if you want to go to production, because it requires every user to have their own LangSmith API key, and set the LangGraph configuration themselves.
|
Once you're ready to go to production, you'll need to update how you connect, and authenticate requests to your deployment. By default, the Agent Chat UI is setup for local development, and connects to your LangGraph server directly from the client. This is not possible if you want to go to production, because it requires every user to have their own LangSmith API key, and set the LangGraph configuration themselves.
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ const ArtifactSlotContext = createContext<{
|
|||||||
context: [Record<string, unknown>, Setter<Record<string, unknown>>];
|
context: [Record<string, unknown>, Setter<Record<string, unknown>>];
|
||||||
}>(null!);
|
}>(null!);
|
||||||
|
|
||||||
const ArtifactFill = (props: {
|
/**
|
||||||
|
* Headless component that will obtain the title and content of the artifact
|
||||||
|
* and render them in place of the `ArtifactContent` and `ArtifactTitle` components via
|
||||||
|
* React Portals.
|
||||||
|
*/
|
||||||
|
const ArtifactSlot = (props: {
|
||||||
id: string;
|
id: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
@@ -107,6 +112,11 @@ export function ArtifactProvider(props: { children?: ReactNode }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a value to be passed into `meta.artifact` field
|
||||||
|
* of the `LoadExternalComponent` component, to be consumed by the `useArtifact` hook
|
||||||
|
* on the generative UI side.
|
||||||
|
*/
|
||||||
export function useArtifact() {
|
export function useArtifact() {
|
||||||
const id = useId();
|
const id = useId();
|
||||||
const context = useContext(ArtifactSlotContext);
|
const context = useContext(ArtifactSlotContext);
|
||||||
@@ -131,26 +141,34 @@ export function useArtifact() {
|
|||||||
const ArtifactContent = useCallback(
|
const ArtifactContent = useCallback(
|
||||||
(props: { title?: React.ReactNode; children: React.ReactNode }) => {
|
(props: { title?: React.ReactNode; children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<ArtifactFill
|
<ArtifactSlot
|
||||||
id={id}
|
id={id}
|
||||||
title={props.title}
|
title={props.title}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</ArtifactFill>
|
</ArtifactSlot>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[id],
|
[id],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return [
|
||||||
open,
|
ArtifactContent,
|
||||||
setOpen,
|
{ open, setOpen, context: ctxContext, setContext: ctxSetContext },
|
||||||
context: ctxContext,
|
] as [
|
||||||
setContext: ctxSetContext,
|
typeof ArtifactContent,
|
||||||
content: ArtifactContent,
|
{
|
||||||
};
|
open: typeof open;
|
||||||
|
setOpen: typeof setOpen;
|
||||||
|
context: typeof ctxContext;
|
||||||
|
setContext: typeof ctxSetContext;
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General hook for detecting if any artifact is open.
|
||||||
|
*/
|
||||||
export function useArtifactOpen() {
|
export function useArtifactOpen() {
|
||||||
const context = useContext(ArtifactSlotContext);
|
const context = useContext(ArtifactSlotContext);
|
||||||
const [ctxOpen, setCtxOpen] = context.open;
|
const [ctxOpen, setCtxOpen] = context.open;
|
||||||
@@ -161,6 +179,10 @@ export function useArtifactOpen() {
|
|||||||
return [open, onClose] as const;
|
return [open, onClose] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Artifacts may at their discretion provide additional context
|
||||||
|
* that will be used when creating a new run.
|
||||||
|
*/
|
||||||
export function useArtifactContext() {
|
export function useArtifactContext() {
|
||||||
const context = useContext(ArtifactSlotContext);
|
const context = useContext(ArtifactSlotContext);
|
||||||
return context.context;
|
return context.context;
|
||||||
|
|||||||
Reference in New Issue
Block a user