From 865d1e6c2a87f25e8ecd959a3746c4db05431c95 Mon Sep 17 00:00:00 2001 From: zahlekhan Date: Fri, 19 Dec 2025 08:54:53 +0530 Subject: [PATCH] Implement versioning for artifacts: add version management in the API, update UI to select versions, and save versions upon artifact generation. --- c1-artifact/src/app/api/ask/route.ts | 40 +++-- .../app/api/versions/[artifactId]/route.ts | 28 +++ c1-artifact/src/app/page.tsx | 6 + c1-artifact/src/components/OutputPanel.tsx | 26 +++ c1-artifact/src/hooks/useArtifactStream.ts | 51 +++++- c1-chat-artifact/src/app/page.tsx | 166 +++++++++++++++++- 6 files changed, 297 insertions(+), 20 deletions(-) create mode 100644 c1-artifact/src/app/api/versions/[artifactId]/route.ts diff --git a/c1-artifact/src/app/api/ask/route.ts b/c1-artifact/src/app/api/ask/route.ts index 5ba0136..5e6da7a 100644 --- a/c1-artifact/src/app/api/ask/route.ts +++ b/c1-artifact/src/app/api/ask/route.ts @@ -2,6 +2,7 @@ import OpenAI from "openai"; import { NextRequest } from "next/server"; import { makeC1Response } from "@thesysai/genui-sdk/server"; import { transformStream } from "@crayonai/stream"; +import { saveVersion } from "../../../lib/versionStore"; export async function POST(req: NextRequest) { const apiKey = process.env.THESYS_API_KEY; @@ -9,17 +10,22 @@ export async function POST(req: NextRequest) { return new Response("Missing THESYS_API_KEY", { status: 500 }); } - const { prompt, artifactType, artifactId, artifactContent } = (await req.json()) as { - prompt?: string; - artifactType?: "slides" | "report"; - artifactId?: string; - artifactContent?: string; - }; + const { prompt, artifactType, artifactId, artifactContent } = + (await req.json()) as { + prompt?: string; + artifactType?: "slides" | "report"; + artifactId?: string; + artifactContent?: string; + }; if (!prompt || typeof prompt !== "string") { return new Response("Missing 'prompt'", { status: 400 }); } + if (!artifactId) { + return new Response("Missing 'artifactId'", { status: 400 }); + } + const client = new OpenAI({ apiKey, baseURL: "https://api.thesys.dev/v1/artifact", @@ -29,9 +35,12 @@ export async function POST(req: NextRequest) { role: "system" | "user" | "assistant"; content: string; }> = []; - + // Include previous artifact content if editing - if (typeof artifactContent === "string" && artifactContent.trim().length > 0) { + if ( + typeof artifactContent === "string" && + artifactContent.trim().length > 0 + ) { messages.push({ role: "assistant", content: artifactContent }); } messages.push({ role: "user", content: prompt }); @@ -47,7 +56,7 @@ export async function POST(req: NextRequest) { id: artifactId, c1_artifact_type: artifactType, }), - } + }, }); const c1Response = makeC1Response(); @@ -57,11 +66,15 @@ export async function POST(req: NextRequest) { isAborted = true; }); + // Accumulate content to save as a version + let accumulatedContent = ""; + transformStream( stream, (chunk) => { const content = chunk.choices?.[0]?.delta?.content; if (content) { + accumulatedContent += content; c1Response.writeContent(content); } }, @@ -74,8 +87,12 @@ export async function POST(req: NextRequest) { return; } c1Response.end(); - // if you want to store the response just call - // c1Response.getAssistantMessage() to get the assistant message + + // Save the completed artifact as a new version + if (accumulatedContent) { + const version = saveVersion(artifactId, accumulatedContent, prompt); + console.log(`Saved version ${version.id} for artifact ${artifactId}`); + } }, } ); @@ -84,6 +101,7 @@ export async function POST(req: NextRequest) { headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-cache", + "X-Artifact-Id": artifactId, }, }); } diff --git a/c1-artifact/src/app/api/versions/[artifactId]/route.ts b/c1-artifact/src/app/api/versions/[artifactId]/route.ts new file mode 100644 index 0000000..83e9863 --- /dev/null +++ b/c1-artifact/src/app/api/versions/[artifactId]/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getVersions, getVersion } from "../../../../lib/versionStore"; + +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ artifactId: string }> } +) { + const { artifactId } = await params; + + // Check if a specific version is requested + const versionId = req.nextUrl.searchParams.get("versionId"); + + if (versionId) { + const version = getVersion(artifactId, parseInt(versionId, 10)); + if (!version) { + return NextResponse.json( + { error: "Version not found" }, + { status: 404 } + ); + } + return NextResponse.json(version); + } + + // Return all versions for the artifact + const versions = getVersions(artifactId); + return NextResponse.json({ artifactId, versions }); +} + diff --git a/c1-artifact/src/app/page.tsx b/c1-artifact/src/app/page.tsx index 4ed6f46..6bef111 100644 --- a/c1-artifact/src/app/page.tsx +++ b/c1-artifact/src/app/page.tsx @@ -19,6 +19,9 @@ export default function Home() { send, stop, clear, + versions, + currentVersionIndex, + selectVersion, } = useArtifactStream(); const suggestions = [ @@ -38,6 +41,9 @@ export default function Home() { artifactType={artifactType} onClear={clear} isLoading={isLoading} + versions={versions} + currentVersionIndex={currentVersionIndex} + onSelectVersion={selectVersion} /> send(s)} /> diff --git a/c1-artifact/src/components/OutputPanel.tsx b/c1-artifact/src/components/OutputPanel.tsx index a09a58c..351226a 100644 --- a/c1-artifact/src/components/OutputPanel.tsx +++ b/c1-artifact/src/components/OutputPanel.tsx @@ -1,9 +1,14 @@ import { C1Component, ThemeProvider } from "@thesysai/genui-sdk"; +import type { Version } from "../hooks/useArtifactStream"; + type OutputPanelProps = { artifact: string; artifactType: "slides" | "report"; onClear: () => void; isLoading: boolean; + versions: Version[]; + currentVersionIndex: number; + onSelectVersion: (index: number) => void; }; export function OutputPanel({ @@ -11,12 +16,33 @@ export function OutputPanel({ artifactType, onClear, isLoading, + versions, + currentVersionIndex, + onSelectVersion, }: OutputPanelProps) { return (
{artifactType === "slides" ? "Slides" : "Report"} Output
+ {versions.length > 0 && ( + + )} + ) : ( + + )} +
+
+ + {c1Response && ( +
+ + setC1Response(message)} + onAction={({ llmFriendlyMessage, humanFriendlyMessage }) => { + console.log("Action triggered:", { + humanFriendlyMessage, + llmFriendlyMessage, + }); + setQuery(llmFriendlyMessage); + handleSubmit(llmFriendlyMessage); + }} + /> + +
+ )} + + + + {/* Artifact Side Panel */} + {isArtifactActive && ( +
+
{renderArtifact()}
+
+ )} + ); }