diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
deleted file mode 100644
index d1d8d590..00000000
--- a/ARCHITECTURE.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Architecture
-
-This file exists to describe the principles of this code architecture.
-It's made for both the developer working on it and for AI models to read and apply when making changes.
-
-## Key Architecture Principles
-- The Frontend is a NextJS application in chartsmith-app
-- The's a single worker, written in go, run with `make run-worker`.
-- We have a Postres/pgvector database and Centrifugo for realtime notifications.
-- The intent is to keep this system design and avoid new databases, queues, components. Simplicity matters.
-
-## API Design Principles
-- Prefer consolidated data endpoints over granular ones to minimize API calls and database load
-- Structure API routes using Next.js's file-based routing with resource-oriented paths
-- Implement consistent authentication and error handling patterns across endpoints
-- Return complete data objects rather than fragments to reduce follow-up requests
-- Prioritize server-side data processing over client-side assembly of multiple API calls
-
-
-# Subprojects
-- See chartsmith-app/ARCHITECTURE.md for the architecture principles for the front end.
-
-
-## Workers
-- The go code is where we put all workers.
-- Jobs for workers are enqueued and scheduled using postgres notify and a work_queue table.
-- Status from the workers is communicated via Centrifugo messages to the client.
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index f9479fff..00000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,12 +0,0 @@
-See the following files for details:
-
-ARCHITECTURE.md: Our core design principles
-chartsmith-app/ARCHITECTURE.md: Our design principles for the frontend
-
-CONTRIBUTING.md: How to run and test this project
-chartsmith-app/CONTRIBUTING.md: How to run and test the frontend
-
-- `/chart.go:106:2: declared and not used: repoUrl
-pkg/workspace/chart.go:169:41: cannot use conn (variable of type *pgxpool.Conn) as *pgxpool.Pool value in argument to updatePublishStatus
-pkg/workspace/chart.go:178:40: cannot use conn (variable of type *pgxpool.Conn) as *pgxpool.Pool value in argument to updatePublishStatus
-make: *** [build] Error 1`
\ No newline at end of file
diff --git a/chartsmith-app/ARCHITECTURE.md b/chartsmith-app/ARCHITECTURE.md
deleted file mode 100644
index 93e7d7b4..00000000
--- a/chartsmith-app/ARCHITECTURE.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Architecture and Design for Chartsmith-app
-
-This is a next.js project that is the front end for chartsmith.
-
-## Monaco Editor Implementation
-- Avoid recreating editor instances
-- Use a single editor instance with model swapping for better performance
-- Properly clean up models to prevent memory leaks
-- We want to make sure that we don't show a "Loading..." state because it causes a lot of UI flashes.
-
-## State managemnet
-- Do not pass onChange and other callbacks through to child components
-- We use jotai for state, each component should be able to get or set the state it needs
-- Each component subscribes to the relevant atoms. This is preferred over callbacks.
-
-## SSR
-- We use server side rendering to avoid the "loading" state whenever possible.
-- Move code that requires "use client" into separate controls.
-
-## Database and functions
-- We aren't using Next.JS API routes, except when absolutely necessary.
-- Front end should call server actions, which call lib/* functions.
-- Database queries are not allowed in the server action. Server actions are just wrappers for which lib functions we expose.
diff --git a/chartsmith-app/CLAUDE.md b/chartsmith-app/CLAUDE.md
deleted file mode 100644
index 5f5fdb75..00000000
--- a/chartsmith-app/CLAUDE.md
+++ /dev/null
@@ -1,4 +0,0 @@
-See the following files for details:
-
-ARCHITECTURE.md: Our core design principles
-CONTRIBUTING.md: How to run and test this project
diff --git a/chartsmith-app/CONTRIBUTING.md b/chartsmith-app/CONTRIBUTING.md
deleted file mode 100644
index 69f18694..00000000
--- a/chartsmith-app/CONTRIBUTING.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Contributing to chartsmith-app
-
-## Commands
-- Build/start: `npm run dev` - Starts Next.js development server
-- Lint: `npm run lint` - Run ESLint
-- Typecheck: `npm run typecheck` - Check TypeScript types
-- Test: `npm test` - Run Jest tests
-- Single test: `npm test -- -t "test name"` - Run a specific test
-
-## Code Style
-- **Imports**: Group imports by type (React, components, utils, types)
-- **Components**: Use functional components with React hooks
-- **TypeScript**: Use explicit typing, avoid `any`
-- **State Management**: Use Jotai for global state
-- **Naming**: PascalCase for components, camelCase for variables/functions
-- **Styling**: Use Tailwind CSS with descriptive class names
-- **Error Handling**: Use try/catch blocks with consistent error logging
-- **File Organization**: Group related components in folders
-- **Editor**: Monaco editor instances should be carefully managed to prevent memory leaks
diff --git a/chartsmith-app/app/api/chat/conversational/route.ts b/chartsmith-app/app/api/chat/conversational/route.ts
new file mode 100644
index 00000000..84601be5
--- /dev/null
+++ b/chartsmith-app/app/api/chat/conversational/route.ts
@@ -0,0 +1,291 @@
+/**
+ * Vercel AI SDK Chat API Route
+ *
+ * This demonstrates how to migrate the conversational chat from Go to Next.js using Vercel AI SDK.
+ * This is a complete implementation showing all key features from pkg/llm/conversational.go
+ *
+ * Key features demonstrated:
+ * - Vercel AI SDK streamText() for streaming responses
+ * - Tool calling (latest_subchart_version, latest_kubernetes_version)
+ * - System prompts preservation
+ * - Context injection (chart structure, relevant files)
+ * - Centrifugo real-time publishing
+ * - Database integration for message persistence
+ */
+
+import { streamText, tool } from 'ai';
+import { createAnthropic } from '@ai-sdk/anthropic';
+import { NextRequest, NextResponse } from 'next/server';
+import { z } from 'zod';
+import { getChatMessage, getWorkspace } from '@/lib/workspace/workspace';
+import { appendChatMessageResponse, clearChatMessageResponse, markChatMessageComplete, getWorkspaceIdForChatMessage } from '@/lib/workspace/chat-helpers';
+import { publishChatMessageUpdate } from '@/lib/realtime/centrifugo-publish';
+import { getChartStructure, chooseRelevantFiles, getPreviousChatHistory } from '@/lib/workspace/context';
+import { getLatestSubchartVersion } from '@/lib/recommendations/subchart';
+
+export const runtime = 'nodejs';
+export const maxDuration = 300; // 5 minutes
+
+// System prompts from pkg/llm/system.go
+const CHAT_SYSTEM_PROMPT = `You are ChartSmith, an expert AI assistant and a highly skilled senior software developer specializing in the creation, improvement, and maintenance of Helm charts.
+ Your primary responsibility is to help users transform, refine, and optimize Helm charts based on a variety of inputs, including:
+
+- Existing Helm charts that need adjustments, improvements, or best-practice refinements.
+
+Your guidance should be exhaustive, thorough, and precisely tailored to the user's needs.
+Always ensure that your output is a valid, production-ready Helm chart setup adhering to Helm best practices.
+If the user provides partial information (e.g., a single Deployment manifest, a partial Chart.yaml, or just an image and port configuration), you must integrate it into a coherent chart.
+Requests will always be based on a existing Helm chart and you must incorporate modifications while preserving and improving the chart's structure (do not rewrite the chart for each request).
+
+Below are guidelines and constraints you must always follow:
+
+
+ - Focus exclusively on tasks related to Helm charts and Kubernetes manifests. Do not address topics outside of Kubernetes, Helm, or their associated configurations.
+ - Assume a standard Kubernetes environment, where Helm is available.
+ - Do not assume any external services (e.g., cloud-hosted registries or databases) unless the user's scenario explicitly includes them.
+ - Do not rely on installing arbitrary tools; you are guiding and generating Helm chart files and commands only.
+ - Incorporate changes into the most recent version of files. Make sure to provide complete updated file contents.
+
+
+
+ - Use 2 spaces for indentation in all YAML files.
+ - Ensure YAML and Helm templates are valid, syntactically correct, and adhere to Kubernetes resource definitions.
+ - Use proper Helm templating expressions ({{ ... }}) where appropriate. For example, parameterize image tags, resource counts, ports, and labels.
+ - Keep the chart well-structured and maintainable.
+
+
+
+ - Use only valid Markdown for your responses unless required by the instructions below.
+ - Do not use HTML elements.
+ - Communicate in plain Markdown. Inside these tags, produce only the required YAML, shell commands, or file contents.
+
+
+NEVER use the word "artifact" in your final messages to the user.
+
+
+ - You will be asked to answer a question.
+ - You will be given the question and the context of the question.
+ - You will be given the current chat history.
+ - You will be asked to answer the question based on the context and the chat history.
+ - You can provide small examples of code, but just use markdown.
+`;
+
+const CHAT_INSTRUCTIONS = `- You will be asked to answer a question.
+- You will be given the question and the context of the question.
+- You will be given the current chat history.
+- You will be asked to answer the question based on the context and the chat history.
+- You can be technical in your response and include inline code snippets identifed with Markdown when appropriate.
+- Never use the tag in your response.`;
+
+export async function POST(req: NextRequest) {
+ const startTime = Date.now();
+ let chatMessageId: string | undefined;
+
+ try {
+ // Validate Authorization header using dedicated internal API token
+ // This prevents leaking the Anthropic API key via request headers
+ const authHeader = req.headers.get('authorization');
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
+ console.error('[CHAT API] Missing or invalid Authorization header');
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const token = authHeader.substring(7); // Remove 'Bearer ' prefix
+ const expectedToken = process.env.INTERNAL_API_TOKEN;
+
+ if (!expectedToken) {
+ console.error('[CHAT API] INTERNAL_API_TOKEN not configured');
+ return NextResponse.json({ error: 'Server misconfigured' }, { status: 500 });
+ }
+
+ if (token !== expectedToken) {
+ console.error('[CHAT API] Invalid Bearer token');
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ // Get chat message ID from request
+ const body = await req.json();
+ chatMessageId = body.chatMessageId;
+
+ console.log(`[CHAT API] Starting request for chatMessageId=${chatMessageId}`);
+
+ if (!chatMessageId) {
+ console.error('[CHAT API] Missing chatMessageId in request');
+ return NextResponse.json({ error: 'chatMessageId is required' }, { status: 400 });
+ }
+
+ // Fetch the chat message from database
+ console.log(`[CHAT API] Fetching chat message from database...`);
+ const chatMessage = await getChatMessage(chatMessageId);
+ if (!chatMessage) {
+ console.error(`[CHAT API] Chat message not found: ${chatMessageId}`);
+ return NextResponse.json({ error: 'Chat message not found' }, { status: 404 });
+ }
+ console.log(`[CHAT API] Found chat message. Prompt: "${chatMessage.prompt.substring(0, 100)}..."`);
+
+ // Get workspace ID and user ID for Centrifugo publishing
+ const workspaceId = await getWorkspaceIdForChatMessage(chatMessageId);
+ const userId = chatMessage.userId;
+
+ if (!userId) {
+ console.error(`[CHAT API] Chat message missing userId: ${chatMessageId}`);
+ return NextResponse.json({ error: 'Chat message missing userId' }, { status: 400 });
+ }
+
+ console.log(`[CHAT API] workspaceId=${workspaceId}, userId=${userId}`);
+
+ // Clear any existing response to prevent duplication on retry
+ await clearChatMessageResponse(chatMessageId);
+ console.log(`[CHAT API] Cleared existing response`);
+
+ // Initialize Anthropic with Vercel AI SDK
+ const anthropic = createAnthropic({
+ apiKey: process.env.ANTHROPIC_API_KEY,
+ });
+ console.log(`[CHAT API] Initialized Anthropic client`);
+
+ // Define tools (from pkg/llm/conversational.go)
+ const tools = {
+ latest_subchart_version: tool({
+ description: 'Return the latest version of a subchart from name',
+ inputSchema: z.object({
+ chart_name: z.string().describe('The subchart name to get the latest version of'),
+ }),
+ execute: async ({ chart_name }) => {
+ try {
+ const version = await getLatestSubchartVersion(chart_name);
+ return version;
+ } catch (error) {
+ console.error(`Failed to get subchart version for ${chart_name}:`, error);
+ return 'unknown';
+ }
+ },
+ }),
+ latest_kubernetes_version: tool({
+ description: 'Return the latest version of Kubernetes',
+ inputSchema: z.object({
+ semver_field: z.enum(['major', 'minor', 'patch']).describe('One of major, minor, or patch'),
+ }),
+ execute: async ({ semver_field }) => {
+ switch (semver_field) {
+ case 'major':
+ return '1';
+ case 'minor':
+ return '1.32';
+ case 'patch':
+ return '1.32.1';
+ default:
+ return '1.32.1';
+ }
+ },
+ }),
+ };
+
+ // Get workspace and chart context
+ console.log(`[CHAT API] Loading workspace context...`);
+ const workspace = await getWorkspace(workspaceId);
+ if (!workspace) {
+ console.error(`[CHAT API] Workspace not found: ${workspaceId}`);
+ return NextResponse.json({ error: 'Workspace not found' }, { status: 404 });
+ }
+
+ const chartStructure = await getChartStructure(workspace);
+ const relevantFiles = await chooseRelevantFiles(workspace, chatMessage.prompt, undefined, 10);
+ const chatHistory = await getPreviousChatHistory(workspaceId, chatMessageId);
+ console.log(`[CHAT API] Context loaded: ${relevantFiles.length} files, ${chatHistory.length} history messages`);
+
+ // Build system prompt (includes instructions and chart context)
+ const systemPrompt = `${CHAT_SYSTEM_PROMPT}
+
+${CHAT_INSTRUCTIONS}
+
+I am working on a Helm chart that has the following structure: ${chartStructure}
+
+${relevantFiles.map(file => `File: ${file.filePath}, Content: ${file.content}`).join('\n\n')}`;
+
+ // Build messages array (conversation history + current message)
+ const messages = [
+ // Add conversation history
+ ...chatHistory,
+ // User's current message
+ { role: 'user' as const, content: chatMessage.prompt },
+ ];
+
+ console.log(`[CHAT API] Built system prompt and ${messages.length} messages, calling streamText()...`);
+
+ let chunkCount = 0;
+ let totalChars = 0;
+
+ // Stream the response using Vercel AI SDK
+ const result = streamText({
+ model: anthropic('claude-3-5-sonnet-20241022'),
+ system: systemPrompt,
+ messages,
+ tools,
+ onChunk: async ({ chunk }) => {
+ chunkCount++;
+ console.log(`[CHAT API] onChunk called (chunk #${chunkCount}): type=${chunk.type}`);
+
+ // Handle text delta chunks
+ if (chunk.type === 'text-delta') {
+ const textChunk = chunk.text;
+ totalChars += textChunk.length;
+ console.log(`[CHAT API] text-delta chunk: ${textChunk.length} chars (total: ${totalChars})`);
+
+ try {
+ // 1. Append to database
+ await appendChatMessageResponse(chatMessageId!, textChunk);
+ console.log(`[CHAT API] Saved chunk to database`);
+
+ // 2. Publish to Centrifugo for real-time updates
+ await publishChatMessageUpdate(workspaceId, userId, chatMessageId!, textChunk, false);
+ console.log(`[CHAT API] Published chunk to Centrifugo`);
+ } catch (err) {
+ console.error(`[CHAT API] Error processing chunk:`, err);
+ throw err;
+ }
+ }
+ },
+ onFinish: async () => {
+ console.log(`[CHAT API] onFinish called. Total chunks: ${chunkCount}, total chars: ${totalChars}`);
+
+ try {
+ // Mark message as complete
+ await markChatMessageComplete(chatMessageId!);
+ console.log(`[CHAT API] Marked message complete in database`);
+
+ // Publish final completion event
+ await publishChatMessageUpdate(workspaceId, userId, chatMessageId!, '', true);
+ console.log(`[CHAT API] Published completion to Centrifugo`);
+ } catch (err) {
+ console.error(`[CHAT API] Error in onFinish:`, err);
+ throw err;
+ }
+ },
+ });
+
+ console.log(`[CHAT API] Waiting for streamText to complete...`);
+
+ // Wait for completion
+ const fullText = await result.text;
+
+ const duration = Date.now() - startTime;
+ console.log(`[CHAT API] Completed successfully in ${duration}ms. Response length: ${fullText.length} chars`);
+
+ return NextResponse.json({ success: true });
+
+ } catch (error) {
+ const duration = Date.now() - startTime;
+ console.error(`[CHAT API] Error after ${duration}ms:`, error);
+ console.error(`[CHAT API] Error stack:`, error instanceof Error ? error.stack : 'No stack trace');
+
+ return NextResponse.json(
+ {
+ error: 'Internal Server Error',
+ details: error instanceof Error ? error.message : String(error),
+ chatMessageId
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/chartsmith-app/components/types.ts b/chartsmith-app/components/types.ts
index f4a12fed..a6ba4d3a 100644
--- a/chartsmith-app/components/types.ts
+++ b/chartsmith-app/components/types.ts
@@ -107,6 +107,10 @@ export interface CentrifugoMessageData {
status?: string;
completedAt?: string;
isAutorender?: boolean;
+ // Conversational chat streaming fields
+ id?: string;
+ chunk?: string;
+ isComplete?: boolean;
}
export interface RawRevision {
diff --git a/chartsmith-app/hooks/useCentrifugo.ts b/chartsmith-app/hooks/useCentrifugo.ts
index 2fcc7fd8..ddab030a 100644
--- a/chartsmith-app/hooks/useCentrifugo.ts
+++ b/chartsmith-app/hooks/useCentrifugo.ts
@@ -46,6 +46,10 @@ export function useCentrifugo({
const [isReconnecting, setIsReconnecting] = useState(false);
+ // Buffer for chunks that arrive before the message is loaded
+ // Store both chunks and completion state
+ const pendingChunksRef = useRef