MCPcopy Create free account
hub / github.com/UsefulSoftwareCo/executor / ArtifactsPage

Function ArtifactsPage

packages/react/src/pages/artifacts.tsx:166–250  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

164}
165
166export function ArtifactsPage() {
167 useExecutorDocumentTitle("Artifacts");
168 // Opening a card is the only thing this page is for, and the renderer chunk is
169 // the slowest part of doing so. Fetching it while the user is still choosing
170 // means the detail page never has to wait for it — the renderer's own
171 // placeholder simply stops existing for anyone arriving from here.
172 usePreloadArtifactRenderer();
173 const artifacts = useAtomValue(artifactsOptimisticAtom);
174 const refreshArtifacts = useAtomRefresh(artifactsOptimisticAtom);
175 const doRename = useAtomSet(renameArtifactOptimistic, { mode: "promiseExit" });
176 const doRemove = useAtomSet(removeArtifactOptimistic, { mode: "promiseExit" });
177
178 const handleRename = async (artifactId: ArtifactId, title: string) => {
179 const exit = await doRename({
180 params: { artifactId },
181 payload: { title },
182 reactivityKeys: artifactWriteKeys,
183 });
184 trackEvent("artifact_renamed", { success: Exit.isSuccess(exit) });
185 if (Exit.isFailure(exit)) toast.error("Couldn't rename the artifact. Try again.");
186 };
187
188 const handleRemove = async (artifactId: ArtifactId) => {
189 const exit = await doRemove({
190 params: { artifactId },
191 reactivityKeys: artifactWriteKeys,
192 });
193 trackEvent("artifact_removed", { success: Exit.isSuccess(exit) });
194 if (Exit.isFailure(exit)) toast.error("Couldn't delete the artifact. Try again.");
195 };
196
197 return (
198 // Wider than the settings column: three preview cards need the room, and
199 // this page is a gallery rather than a form.
200 <PageContainer className="max-w-6xl">
201 <PageHeader
202 title="Artifacts"
203 description="Interactive components your agents generated. Ask an agent to render a UI and it is saved here — reopen it any time, or have the agent bring it back by name."
204 />
205
206 {isAsyncResultLoading(artifacts) ? (
207 <LoadingState />
208 ) : (
209 AsyncResult.match(artifacts, {
210 onInitial: () => <LoadingState />,
211 onFailure: () => (
212 <ErrorState message="Failed to load artifacts" onRetry={refreshArtifacts} />
213 ),
214 onSuccess: ({ value }) => {
215 // Most-recently-updated first: an artifact just generated by an
216 // agent is the one the user is most likely coming here to open.
217 const rows = [...value].sort((a, b) => b.updatedAt - a.updatedAt);
218 return (
219 <section>
220 <h2 className="mb-3 flex items-center font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground">
221 Saved artifacts
222 {rows.length > 0 ? (
223 <span className="ml-2 font-normal tabular-nums">{rows.length}</span>

Callers

nothing calls this directly

Calls 5

useExecutorDocumentTitleFunction · 0.90
isAsyncResultLoadingFunction · 0.90
handleRenameFunction · 0.70
handleRemoveFunction · 0.70

Tested by

no test coverage detected