diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..25201c7fe --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,234 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**Multi-Agent Chat Interface** - A Next.js-based AI chat platform that supports multiple agents configured via environment variables. Users select an agent from a menu on first visit, then chat with that agent. The application integrates with n8n workflows via webhooks, features markdown rendering with syntax highlighting, image uploads, and an interactive diff tool for code changes. + +## Development Commands + +```bash +npm run dev # Start Next.js development server (http://localhost:3000) +npm run build # Create production build +npm start # Run production server +npm run lint # Run ESLint checks +``` + +**Note:** No testing framework is currently configured. Tests should be added when needed. + +## Technology Stack + +- **Frontend:** Next.js 15.5.4 (App Router), React 19, TypeScript 5 +- **Styling:** Tailwind CSS 4.1.9 with PostCSS +- **UI Components:** shadcn/ui (Radix UI primitives), Lucide icons +- **Forms & Validation:** React Hook Form + Zod +- **Markdown:** react-markdown with remark-gfm and rehype-highlight for code syntax +- **Diffs:** Custom pipeline using `diff` library with Highlight.js for colored output +- **Deployment:** Cloudflare Workers/Pages via OpenNextJS + Wrangler + +## Project Structure + +``` +src/ +├── app/ +│ ├── api/ +│ │ ├── agents/route.ts # GET endpoint - returns available agents from env vars +│ │ └── chat/route.ts # POST endpoint - routes to selected agent's webhook +│ ├── layout.tsx # Root layout with theme provider +│ ├── page.tsx # Home page - conditionally renders AgentSelector or ChatInterface +│ └── globals.css # Tailwind global styles +├── components/ +│ ├── agent-selector.tsx # Agent selection menu (card-based UI) +│ ├── chat-interface.tsx # Main chat UI component (client-side, per-agent) +│ ├── markdown-renderer.tsx # Parses markdown + extracts diff-tool blocks +│ ├── diff-display.tsx # Renders diffs with syntax highlighting +│ ├── diff-tool.tsx # Diff tool wrapper component +│ ├── header.tsx # App header with agent name and switch button +│ ├── mode-toggle.tsx # Dark/light theme toggle +│ ├── theme-provider.tsx # Theme context setup +│ ├── DIFF_TOOL_USAGE.md # In-component documentation +│ └── ui/ # shadcn/ui component library +└── lib/ + ├── types.ts # TypeScript types and interfaces (Agent, Message, ChatRequest, etc.) + └── utils.ts # Utility functions (cn() for classname merging) +``` + +## Architecture & Data Flow + +### Agent Selection Flow +``` +User visits site + ↓ +page.tsx checks localStorage for selected agent + ↓ +If no agent: Show AgentSelector + │ - Fetches agents from GET /api/agents + │ - Displays agent cards with name + description + │ - On selection: saves agent to localStorage and shows ChatInterface + ↓ +If agent exists: Show ChatInterface with that agent +``` + +### Multi-Agent API Pattern +**GET /api/agents** +- Reads environment variables `AGENT_1_URL`, `AGENT_1_NAME`, `AGENT_1_DESCRIPTION`, `AGENT_2_URL`, etc. +- Returns array of available agents: `{ agents: Agent[] }` + +**POST /api/chat** +- **Request:** `{ message, timestamp, sessionId, agentId, images? }` +- **Processing:** + 1. Validates agentId is provided + 2. Looks up webhook URL using `getAgentWebhookUrl(agentId)` + 3. Proxies request to agent's specific n8n webhook + 4. Forwards images (base64) if provided +- **Response Format:** Newline-delimited JSON with two message types: + - `"item"` - Text content rendered directly + - `"tool_call"` - Structured tools like `{ name: "show_diff", input: {...} }` + +### Diff Tool Pipeline +``` +n8n webhook response (tool_call: show_diff) + ↓ +/api/chat/route.ts (converts to markdown code block format) + ↓ +MarkdownRenderer (regex extracts diff-tool code blocks) + ↓ +DiffDisplay (renders diffs with Highlight.js syntax highlighting) +``` + +### Client-Side Architecture +- **State Management:** React hooks (useState, useRef) + localStorage for persistence +- **Agent Persistence:** Selected agent stored in `selected-agent` and `selected-agent-id` +- **Session ID:** Per-agent format `chat-session-{agentId}` stored as `chat-session-{agentId}` +- **Message Storage:** Per-agent messages stored as `chat-messages-{agentId}` in localStorage +- **Image Handling:** Images converted to base64 and included in message payload +- **Auto-scroll:** Maintains scroll position at latest message + +### Markdown Processing Details +- Custom regex parser in `MarkdownRenderer` extracts ` ```diff-tool ... ``` ` code blocks +- Replaces diff-tool blocks with placeholders during markdown rendering +- After markdown is rendered, dynamically inserts `` components +- Supports remark-gfm (GitHub-flavored markdown) and rehype-highlight for syntax coloring + +## Key Files & Responsibilities + +| File | Purpose | +|------|---------| +| `src/lib/types.ts` | Centralized TypeScript types: Agent, Message, ChatRequest, ChatResponse, etc. | +| `src/app/api/agents/route.ts` | GET endpoint - parses env vars and returns available agents | +| `src/app/api/chat/route.ts` | POST endpoint - looks up agent webhook URL and proxies requests | +| `src/app/page.tsx` | Home page - manages agent selection state, conditionally renders AgentSelector or ChatInterface | +| `src/components/agent-selector.tsx` | Agent selection UI - fetches agents, displays cards, handles selection | +| `src/components/chat-interface.tsx` | Main chat UI - handles per-agent messages, image uploads, streaming | +| `src/components/header.tsx` | App header - displays agent name and "Switch Agent" button | +| `src/components/markdown-renderer.tsx` | Parses markdown and extracts diff-tool blocks for custom rendering | +| `src/components/diff-display.tsx` | Renders side-by-side diffs with syntax highlighting using Highlight.js | +| `AGENT_DIFF_TOOL_SETUP.md` | Comprehensive guide for configuring n8n agents to output diff tools | +| `src/components/DIFF_TOOL_USAGE.md` | In-component documentation for the diff tool feature | + +## Theme & Styling + +- **System:** Tailwind CSS 4 with CSS custom properties and OKLch color space +- **Colors:** Warm light mode (cream/beige) and pure black dark mode +- **Implementation:** `next-themes` with "light" and "dark" variants +- **Toggle:** Mode toggle button in header +- **Global Styles:** `src/app/globals.css` contains theme definitions and Tailwind imports + +## Configuration Files + +- `tsconfig.json` - TypeScript strict mode, bundler module resolution, `@/*` alias to `src/` +- `next.config.ts` - Next.js configuration +- `open-next.config.ts` - Cloudflare-specific Next.js configuration +- `wrangler.jsonc` - Wrangler (Cloudflare CLI) configuration +- `eslint.config.mjs` - ESLint with Next.js core-web-vitals and TypeScript support +- `components.json` - shadcn/ui component library configuration +- `postcss.config.mjs` - Tailwind CSS plugin configuration + +## n8n Webhook Integration + +The chat endpoint routes to an n8n workflow via webhook. The workflow should: + +1. Accept message input and context from the client +2. Return newline-delimited JSON with messages in one of two formats: + - `{ "type": "item", "content": "text content" }` + - `{ "type": "tool_call", "name": "show_diff", "input": { "before": "...", "after": "...", "language": "..." } }` + +**Note:** See `AGENT_DIFF_TOOL_SETUP.md` for detailed instructions on configuring n8n agents to use the diff tool feature. + +## Common Tasks + +### Configuring Agents via Environment Variables +**Local Development (.env.local):** +``` +AGENT_1_URL=https://n8n.example.com/webhook/agent-1-uuid +AGENT_1_NAME=Creative Writer +AGENT_1_DESCRIPTION=An AI assistant for creative writing and brainstorming + +AGENT_2_URL=https://n8n.example.com/webhook/agent-2-uuid +AGENT_2_NAME=Code Reviewer +AGENT_2_DESCRIPTION=An AI assistant for code review and refactoring +``` + +**Cloudflare Deployment:** +- Go to Cloudflare dashboard → Workers & Pages → your-project → Settings → Environment variables +- Add the same AGENT_* variables above +- Deploy to apply changes + +### Adding a New Agent +1. Add three environment variables: + - `AGENT_N_URL` - webhook URL for the agent + - `AGENT_N_NAME` - display name + - `AGENT_N_DESCRIPTION` - short description +2. On next page reload, new agent appears in AgentSelector +3. No code changes needed + +### Modifying Chat Messages or Display +- **Chat UI:** `src/components/chat-interface.tsx` +- **Rendering:** `src/components/markdown-renderer.tsx` +- **State:** Message list stored in component state, persisted to localStorage per agent +- **Per-agent persistence:** Messages saved as `chat-messages-{agentId}` + +### Changing Theme Colors +- Edit CSS custom properties in `src/app/globals.css` +- Uses OKLch color space (perceptually uniform) +- Dark mode variant defined with `@custom-variant dark` + +### Adding New Tool Types (beyond show_diff) +- **Step 1:** Handle in `/api/chat/route.ts` - convert tool_call to markdown format +- **Step 2:** Add detection logic in `markdown-renderer.tsx` to extract new code block type +- **Step 3:** Create new component similar to `DiffDisplay` and render dynamically + +### Switching Between Agents +- Users click "Switch Agent" button in header +- Returns to AgentSelector menu +- Previously selected agents/messages are preserved in localStorage per agent +- No data loss when switching + +## Notes for Future Development + +### Multi-Agent Features +- **Agent Switching:** Currently requires page reload to agent selector. Could add inline dropdown in header. +- **Agent Management:** UI for adding/editing agents without env vars could improve UX +- **Agent Verification:** Add health checks to verify agents' webhook URLs are reachable +- **Agent Categories:** Could group agents by category/type for better organization + +### Image Upload Enhancements +- **Image Storage:** Currently base64 in memory; consider Cloudflare R2 for large images +- **Image Preview:** Add full-screen image viewer for uploaded images +- **Multi-file Upload:** Support multiple file types beyond images + +### Performance & Scaling +- **Message Virtualization:** Chat interface uses `useRef` for scroll - consider virtualizing long message lists +- **Storage Size:** localStorage has 5-10MB limit; consider IndexedDB for longer conversations +- **Streaming:** Current implementation reads entire response before parsing; consider streaming HTML as it arrives + +### Testing & Quality +- **Testing:** Consider adding Jest or Vitest for component and API testing +- **Error Handling:** Enhance with retry logic for failed webhook calls and better error messages +- **Logging:** Add structured logging for debugging multi-agent interactions + +### Accessibility & UX +- **Keyboard Navigation:** Verify keyboard navigation on agent selector cards and custom diff tool +- **ARIA Labels:** Add aria-labels for screen readers on interactive elements +- **Mobile Responsive:** Test agent selector and chat interface on mobile devices diff --git a/src/app/api/agents/route.ts b/src/app/api/agents/route.ts new file mode 100644 index 000000000..691539d6f --- /dev/null +++ b/src/app/api/agents/route.ts @@ -0,0 +1,66 @@ +import { type NextRequest, NextResponse } from "next/server" +import type { Agent, AgentsResponse } from "@/lib/types" + +/** + * GET /api/agents + * Returns list of available agents configured via environment variables + * + * Expected environment variables format: + * - AGENT_1_URL, AGENT_1_NAME, AGENT_1_DESCRIPTION + * - AGENT_2_URL, AGENT_2_NAME, AGENT_2_DESCRIPTION + * - etc. + */ +export async function GET(request: NextRequest): Promise> { + try { + const agents: Agent[] = [] + + // Parse agent configurations from environment variables + // Look for AGENT_N_URL, AGENT_N_NAME, AGENT_N_DESCRIPTION patterns + let agentIndex = 1 + + while (true) { + const urlKey = `AGENT_${agentIndex}_URL` + const nameKey = `AGENT_${agentIndex}_NAME` + const descriptionKey = `AGENT_${agentIndex}_DESCRIPTION` + + const webhookUrl = process.env[urlKey] + const name = process.env[nameKey] + const description = process.env[descriptionKey] + + // Stop if we don't find a URL for this index + if (!webhookUrl) { + break + } + + // Require at least URL and name + if (!name) { + console.warn(`[agents] Agent ${agentIndex} missing name, skipping`) + agentIndex++ + continue + } + + agents.push({ + id: `agent-${agentIndex}`, + name, + description: description || "", + webhookUrl, + }) + + agentIndex++ + } + + if (agents.length === 0) { + console.warn("[agents] No agents configured in environment variables") + } + + console.log(`[agents] Loaded ${agents.length} agents`) + + return NextResponse.json({ agents }) + } catch (error) { + console.error("[agents] Error loading agents:", error) + return NextResponse.json( + { agents: [], error: "Failed to load agents" }, + { status: 500 }, + ) + } +} diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index 934c70ec3..bc66fd32e 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -1,6 +1,29 @@ import { type NextRequest, NextResponse } from "next/server" +import type { ChatRequest, ChatResponse } from "@/lib/types" -const WEBHOOK_URL = "https://n8n.biohazardvfx.com/webhook/d2ab4653-a107-412c-a905-ccd80e5b76cd" +/** + * Get webhook URL for a specific agent from environment variables + * Format: AGENT_{agentIndex}_URL + */ +function getAgentWebhookUrl(agentId: string): string | null { + // Extract agent index from agentId (format: "agent-1", "agent-2", etc.) + const match = agentId.match(/agent-(\d+)/) + if (!match) { + console.error("[chat] Invalid agentId format:", agentId) + return null + } + + const agentIndex = match[1] + const urlKey = `AGENT_${agentIndex}_URL` + const webhookUrl = process.env[urlKey] + + if (!webhookUrl) { + console.error(`[chat] No webhook URL configured for ${urlKey}`) + return null + } + + return webhookUrl +} // Helper function to convert diff tool call to markdown format function convertToDiffTool(args: any): string { @@ -25,28 +48,36 @@ function convertToDiffTool(args: any): string { } } -export async function POST(request: NextRequest) { +export async function POST(request: NextRequest): Promise> { try { const body = await request.json() if (typeof body !== "object" || body === null) { return NextResponse.json({ error: "Invalid request body" }, { status: 400 }) } - const { message, timestamp, sessionId } = body as { - message?: string - timestamp?: string - sessionId?: string - } + + const { message, timestamp, sessionId, agentId, images } = body as ChatRequest + + // Validate required fields if (!message || typeof message !== "string") { return NextResponse.json({ error: "Message is required" }, { status: 400 }) } - if (!message) { - return NextResponse.json({ error: "Message is required" }, { status: 400 }) + if (!agentId || typeof agentId !== "string") { + return NextResponse.json({ error: "Agent ID is required" }, { status: 400 }) } - console.log("[v0] Sending to webhook:", { message, timestamp, sessionId }) + // Get webhook URL for the selected agent + const webhookUrl = getAgentWebhookUrl(agentId) + if (!webhookUrl) { + return NextResponse.json( + { error: `Agent ${agentId} is not properly configured` }, + { status: 400 }, + ) + } - const response = await fetch(WEBHOOK_URL, { + console.log("[chat] Sending to webhook:", { agentId, message, timestamp, sessionId }) + + const response = await fetch(webhookUrl, { method: "POST", headers: { "Content-Type": "application/json", @@ -55,6 +86,8 @@ export async function POST(request: NextRequest) { message, timestamp, sessionId, + agentId, + images: images && images.length > 0 ? images : undefined, }), }) diff --git a/src/app/page.tsx b/src/app/page.tsx index c23f87032..f41e9953b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,13 +1,66 @@ +"use client" + +import { useState, useEffect } from "react" import { ChatInterface } from "@/components/chat-interface" +import { AgentSelector } from "@/components/agent-selector" import { Header } from "@/components/header" +import type { Agent } from "@/lib/types" export default function Home() { + const [selectedAgent, setSelectedAgent] = useState(null) + const [showAgentDialog, setShowAgentDialog] = useState(false) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + // Try to load previously selected agent from localStorage + const savedAgent = localStorage.getItem("selected-agent") + + if (savedAgent) { + try { + const agent = JSON.parse(savedAgent) + setSelectedAgent(agent) + } catch (err) { + console.error("[home] Failed to load saved agent:", err) + setShowAgentDialog(true) + } + } else { + // No agent selected, show dialog + setShowAgentDialog(true) + } + + setIsLoading(false) + }, []) + + const handleAgentSelected = (agent: Agent) => { + setSelectedAgent(agent) + } + + const handleSwitchAgent = () => { + setShowAgentDialog(true) + } + + if (isLoading) { + return null // Avoid hydration mismatch + } + return (
-
+
- + {selectedAgent ? ( + + ) : ( +
+

Select an agent to begin chatting

+
+ )}
+ +
) } diff --git a/src/components/agent-selector.tsx b/src/components/agent-selector.tsx new file mode 100644 index 000000000..53683bd17 --- /dev/null +++ b/src/components/agent-selector.tsx @@ -0,0 +1,115 @@ +"use client" + +import { useEffect, useState } from "react" +import { Loader2, Sparkles } from "lucide-react" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog" +import type { Agent } from "@/lib/types" + +interface AgentSelectorProps { + open: boolean + onOpenChange: (open: boolean) => void + onAgentSelected: (agent: Agent) => void +} + +export function AgentSelector({ + open, + onOpenChange, + onAgentSelected, +}: AgentSelectorProps) { + const [agents, setAgents] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + if (!open) return + + const fetchAgents = async () => { + try { + setIsLoading(true) + setError(null) + const response = await fetch("/api/agents") + const data = await response.json() + + if (!response.ok || !data.agents) { + throw new Error(data.error || "Failed to load agents") + } + + setAgents(data.agents) + + if (data.agents.length === 0) { + setError("No agents configured. Please add agents via environment variables.") + } + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to load agents", + ) + } finally { + setIsLoading(false) + } + } + + fetchAgents() + }, [open]) + + const handleSelectAgent = (agent: Agent) => { + // Store selected agent in localStorage + localStorage.setItem("selected-agent-id", agent.id) + localStorage.setItem("selected-agent", JSON.stringify(agent)) + // Close dialog and notify parent component + onOpenChange(false) + onAgentSelected(agent) + } + + return ( + + + +
+ +
+ + Select an Agent + + + Choose an agent to start chatting + +
+ + {isLoading ? ( +
+
+ +

Loading agents...

+
+
+ ) : error ? ( +
+

{error}

+
+ ) : ( +
+ {agents.map((agent) => ( + + ))} +
+ )} +
+
+ ) +} diff --git a/src/components/chat-interface.tsx b/src/components/chat-interface.tsx index 8a6cc04b2..92205e2c0 100644 --- a/src/components/chat-interface.tsx +++ b/src/components/chat-interface.tsx @@ -5,38 +5,54 @@ import type React from "react" import { useState, useRef, useEffect } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Send, Bot, Loader2, SquarePen, Sparkles, Paperclip, Mic } from "lucide-react" +import { Send, Bot, Loader2, SquarePen, Sparkles, Paperclip, Mic, X } from "lucide-react" import { MarkdownRenderer } from "./markdown-renderer" +import type { Message, Agent } from "@/lib/types" -interface Message { - id: string - role: "user" | "assistant" - content: string - timestamp: Date - isError?: boolean - hint?: string +interface ChatInterfaceProps { + agent: Agent } -export function ChatInterface() { +export function ChatInterface({ agent }: ChatInterfaceProps) { const [messages, setMessages] = useState([]) const [input, setInput] = useState("") const [isLoading, setIsLoading] = useState(false) const [sessionId, setSessionId] = useState("") + const [selectedImages, setSelectedImages] = useState([]) const messagesContainerRef = useRef(null) const inputRef = useRef(null) + const fileInputRef = useRef(null) useEffect(() => { - // Try to get existing sessionID from localStorage - let existingSessionId = localStorage.getItem("chat-session-id") + // Use agent-specific session ID: chat-session-{agentId} + const sessionKey = `chat-session-${agent.id}` + let existingSessionId = localStorage.getItem(sessionKey) if (!existingSessionId) { // Generate new sessionID using timestamp and random string - existingSessionId = `session-${Date.now()}-${Math.random().toString(36).substring(2, 15)}` - localStorage.setItem("chat-session-id", existingSessionId) + existingSessionId = `session-${agent.id}-${Date.now()}-${Math.random().toString(36).substring(2, 15)}` + localStorage.setItem(sessionKey, existingSessionId) } setSessionId(existingSessionId) - }, []) + + // Load existing messages for this agent + const messagesKey = `chat-messages-${agent.id}` + const savedMessages = localStorage.getItem(messagesKey) + if (savedMessages) { + try { + const parsed = JSON.parse(savedMessages) + // Ensure timestamps are Date objects + const messages = parsed.map((msg: any) => ({ + ...msg, + timestamp: new Date(msg.timestamp), + })) + setMessages(messages) + } catch (err) { + console.error("[chat] Failed to load saved messages:", err) + } + } + }, [agent.id]) useEffect(() => { if (messagesContainerRef.current) { @@ -44,6 +60,56 @@ export function ChatInterface() { } }, [messages, isLoading]) + // Save messages to localStorage whenever they change + useEffect(() => { + const messagesKey = `chat-messages-${agent.id}` + localStorage.setItem(messagesKey, JSON.stringify(messages)) + }, [messages, agent.id]) + + // Handle image file selection + const handleImageSelect = async (e: React.ChangeEvent) => { + const files = e.currentTarget.files + if (!files) return + + const newImages: string[] = [] + for (let i = 0; i < files.length; i++) { + const file = files[i] + // Only accept image files + if (!file.type.startsWith("image/")) { + console.warn("[chat] Skipping non-image file:", file.name) + continue + } + + try { + const base64 = await fileToBase64(file) + newImages.push(base64) + } catch (err) { + console.error("[chat] Failed to convert image:", err) + } + } + + setSelectedImages((prev) => [...prev, ...newImages]) + // Reset file input + if (fileInputRef.current) { + fileInputRef.current.value = "" + } + } + + // Convert file to base64 string + const fileToBase64 = (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = () => resolve(reader.result as string) + reader.onerror = reject + }) + } + + // Remove selected image + const removeImage = (index: number) => { + setSelectedImages((prev) => prev.filter((_, i) => i !== index)) + } + const sendMessage = async (e?: React.FormEvent) => { if (e) { e.preventDefault() @@ -56,10 +122,12 @@ export function ChatInterface() { role: "user", content: input.trim(), timestamp: new Date(), + images: selectedImages.length > 0 ? selectedImages : undefined, } setMessages((prev) => [...prev, userMessage]) setInput("") + setSelectedImages([]) setIsLoading(true) try { @@ -72,6 +140,8 @@ export function ChatInterface() { message: userMessage.content, timestamp: userMessage.timestamp.toISOString(), sessionId: sessionId, + agentId: agent.id, + images: selectedImages.length > 0 ? selectedImages : undefined, }), }) @@ -122,12 +192,14 @@ export function ChatInterface() { const startNewChat = () => { // Clear all messages setMessages([]) - // Generate new sessionID - const newSessionId = `session-${Date.now()}-${Math.random().toString(36).substring(2, 15)}` + // Generate new sessionID for this agent + const newSessionId = `session-${agent.id}-${Date.now()}-${Math.random().toString(36).substring(2, 15)}` setSessionId(newSessionId) - localStorage.setItem("chat-session-id", newSessionId) - // Clear input + const sessionKey = `chat-session-${agent.id}` + localStorage.setItem(sessionKey, newSessionId) + // Clear input and images setInput("") + setSelectedImages([]) // Focus input inputRef.current?.focus() } @@ -164,8 +236,8 @@ export function ChatInterface() {
-

Welcome to Inspiration Repo

-

Your AI-powered creative assistant is ready to help you brainstorm, create, and innovate.

+

Welcome to {agent.name}

+

{agent.description}

@@ -281,6 +353,28 @@ export function ChatInterface() {
+ {/* Image preview section */} + {selectedImages.length > 0 && ( +
+ {selectedImages.map((image, index) => ( +
+ {`Selected + +
+ ))} +
+ )} +
@@ -293,7 +387,7 @@ export function ChatInterface() { disabled={isLoading} rows={1} className="min-h-[20px] max-h-32 resize-none border-0 bg-transparent text-white placeholder:text-neutral-500 focus:outline-none focus:ring-0" - style={{ + style={{ overflow: 'hidden', height: 'auto' }} @@ -303,15 +397,26 @@ export function ChatInterface() { target.style.height = Math.min(target.scrollHeight, 128) + 'px'; }} /> - +
+ diff --git a/src/components/header.tsx b/src/components/header.tsx index 373675cf1..b729e4dd6 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,10 +1,16 @@ "use client" -import { Sparkles, Settings } from "lucide-react" +import { Sparkles, Settings, ChevronDown } from "lucide-react" import { Button } from "@/components/ui/button" import { ModeToggle } from "@/components/mode-toggle" +import type { Agent } from "@/lib/types" -export function Header() { +interface HeaderProps { + agent: Agent | null + onChangeAgent: () => void +} + +export function Header({ agent, onChangeAgent }: HeaderProps) { return (
@@ -13,11 +19,26 @@ export function Header() {
-

Inspiration Repo

-

AI Creative Assistant

+

+ {agent ? agent.name : "Inspiration Repo"} +

+

+ {agent ? "AI Agent" : "AI Creative Assistant"} +

+ {agent && ( + + )}
- +