Skip to content

Commit 84e4980

Browse files
authored
feat: clean up code for artifacts (#129)
2 parents 9946b15 + 48f5c42 commit 84e4980

File tree

2 files changed

+93
-10
lines changed

2 files changed

+93
-10
lines changed

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,67 @@ return { messages: [result] };
123123

124124
This approach guarantees the message remains completely hidden from the user interface.
125125

126+
## Rendering Artifacts
127+
128+
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:
129+
130+
```tsx
131+
export function useArtifact<TContext = Record<string, unknown>>() {
132+
type Component = (props: {
133+
children: React.ReactNode;
134+
title?: React.ReactNode;
135+
}) => React.ReactNode;
136+
137+
type Context = TContext | undefined;
138+
139+
type Bag = {
140+
open: boolean;
141+
setOpen: (value: boolean | ((prev: boolean) => boolean)) => void;
142+
143+
context: Context;
144+
setContext: (value: Context | ((prev: Context) => Context)) => void;
145+
};
146+
147+
const thread = useStreamContext<
148+
{ messages: Message[]; ui: UIMessage[] },
149+
{ MetaType: { artifact: [Component, Bag] } }
150+
>();
151+
152+
return thread.meta?.artifact;
153+
}
154+
```
155+
156+
After which you can render additional content using the `Artifact` component from the `useArtifact` hook:
157+
158+
```tsx
159+
import { useArtifact } from "../utils/use-artifact";
160+
import { LoaderIcon } from "lucide-react";
161+
162+
export function Writer(props: {
163+
title?: string;
164+
content?: string;
165+
description?: string;
166+
}) {
167+
const [Artifact, { open, setOpen }] = useArtifact();
168+
169+
return (
170+
<>
171+
<div
172+
onClick={() => setOpen(!open)}
173+
className="cursor-pointer rounded-lg border p-4"
174+
>
175+
<p className="font-medium">{props.title}</p>
176+
<p className="text-sm text-gray-500">{props.description}</p>
177+
</div>
178+
179+
<Artifact title={props.title}>
180+
<p className="p-4 whitespace-pre-wrap">{props.content}</p>
181+
</Artifact>
182+
</>
183+
);
184+
}
185+
```
186+
126187
## Going to Production
127188

128189
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.

src/components/thread/artifact.tsx

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ const ArtifactSlotContext = createContext<{
2424
context: [Record<string, unknown>, Setter<Record<string, unknown>>];
2525
}>(null!);
2626

27-
const ArtifactFill = (props: {
27+
/**
28+
* Headless component that will obtain the title and content of the artifact
29+
* and render them in place of the `ArtifactContent` and `ArtifactTitle` components via
30+
* React Portals.
31+
*/
32+
const ArtifactSlot = (props: {
2833
id: string;
2934
children?: ReactNode;
3035
title?: ReactNode;
@@ -107,6 +112,11 @@ export function ArtifactProvider(props: { children?: ReactNode }) {
107112
);
108113
}
109114

115+
/**
116+
* Provides a value to be passed into `meta.artifact` field
117+
* of the `LoadExternalComponent` component, to be consumed by the `useArtifact` hook
118+
* on the generative UI side.
119+
*/
110120
export function useArtifact() {
111121
const id = useId();
112122
const context = useContext(ArtifactSlotContext);
@@ -131,26 +141,34 @@ export function useArtifact() {
131141
const ArtifactContent = useCallback(
132142
(props: { title?: React.ReactNode; children: React.ReactNode }) => {
133143
return (
134-
<ArtifactFill
144+
<ArtifactSlot
135145
id={id}
136146
title={props.title}
137147
>
138148
{props.children}
139-
</ArtifactFill>
149+
</ArtifactSlot>
140150
);
141151
},
142152
[id],
143153
);
144154

145-
return {
146-
open,
147-
setOpen,
148-
context: ctxContext,
149-
setContext: ctxSetContext,
150-
content: ArtifactContent,
151-
};
155+
return [
156+
ArtifactContent,
157+
{ open, setOpen, context: ctxContext, setContext: ctxSetContext },
158+
] as [
159+
typeof ArtifactContent,
160+
{
161+
open: typeof open;
162+
setOpen: typeof setOpen;
163+
context: typeof ctxContext;
164+
setContext: typeof ctxSetContext;
165+
},
166+
];
152167
}
153168

169+
/**
170+
* General hook for detecting if any artifact is open.
171+
*/
154172
export function useArtifactOpen() {
155173
const context = useContext(ArtifactSlotContext);
156174
const [ctxOpen, setCtxOpen] = context.open;
@@ -161,6 +179,10 @@ export function useArtifactOpen() {
161179
return [open, onClose] as const;
162180
}
163181

182+
/**
183+
* Artifacts may at their discretion provide additional context
184+
* that will be used when creating a new run.
185+
*/
164186
export function useArtifactContext() {
165187
const context = useContext(ArtifactSlotContext);
166188
return context.context;

0 commit comments

Comments
 (0)