diff --git a/INSTALL.md b/INSTALL.md index d46f787af..6c0a8a276 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -176,30 +176,6 @@ CREATE TABLE users ( CONSTRAINT users_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE -- Explicit FK definition ); --- Agents table -CREATE TABLE agents ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - name TEXT NOT NULL, - slug TEXT NOT NULL, - description TEXT NOT NULL, - avatar_url TEXT, - system_prompt TEXT NOT NULL, - model_preference TEXT, - is_public BOOLEAN DEFAULT false NOT NULL, - remixable BOOLEAN DEFAULT false NOT NULL, - tools_enabled BOOLEAN DEFAULT false NOT NULL, - example_inputs TEXT[], - tags TEXT[], - category TEXT, - creator_id UUID, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ, - tools TEXT[], - max_steps INTEGER, - mcp_config JSONB, -- Representing the object structure as JSONB - CONSTRAINT agents_creator_id_fkey FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL -- Changed to SET NULL based on schema, could also be CASCADE -); - -- Projects table CREATE TABLE projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -217,12 +193,10 @@ CREATE TABLE chats ( title TEXT, model TEXT, system_prompt TEXT, - agent_id UUID, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), public BOOLEAN DEFAULT FALSE NOT NULL, CONSTRAINT chats_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT chats_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL, CONSTRAINT chats_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE ); @@ -281,7 +255,7 @@ CREATE TABLE user_keys ( -- ALTER TABLE users ENABLE ROW LEVEL SECURITY; -- CREATE POLICY "Users can view their own data." ON users FOR SELECT USING (auth.uid() = id); -- CREATE POLICY "Users can update their own data." ON users FOR UPDATE USING (auth.uid() = id); --- ... add policies for other tables (agents, chats, messages, etc.) ... +-- ... add policies for other tables (chats, messages, etc.) ... ``` ### Storage Setup @@ -292,25 +266,6 @@ Create the buckets `chat-attachments` and `avatars` in your Supabase dashboard: 2. Click "New bucket" and create two buckets: `chat-attachments` and `avatars` 3. Configure public access permissions for both buckets -#### Agent Avatar Configuration - -For agent profile pictures to work properly: - -1. Create an `agents` folder inside your `avatars` bucket: - - - Navigate to the `avatars` bucket - - Click "Create folder" and name it `agents` - -2. Upload agent avatar images - -3. Set up public access for the avatars bucket: - - Go to "Configuration" tab for the `avatars` bucket - - Under "Row Level Security (RLS)" ensure it's disabled or create a policy: - ```sql - CREATE POLICY "Public Read Access" ON storage.objects - FOR SELECT USING (bucket_id = 'avatars'); - ``` - ## Ollama Setup (Local AI Models) Ollama allows you to run AI models locally on your machine. Zola has built-in support for Ollama with automatic model detection. diff --git a/README.md b/README.md index 1b737b74f..c3dd8d6ef 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ - Open-source and self-hostable - Customizable: user system prompt, multiple layout options - Local AI with Ollama: Run models locally with automatic model detection -- Basic agent (wip) - Full MCP support (wip) ## Quick Start @@ -57,7 +56,7 @@ docker-compose -f docker-compose.ollama.yml up [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/ibelick/zola) -To unlock features like auth, file uploads, and agents, see [INSTALL.md](./INSTALL.md). +To unlock features like auth, file uploads, see [INSTALL.md](./INSTALL.md). ## Built with diff --git a/app/agents/[...agentSlug]/page.tsx b/app/agents/[...agentSlug]/page.tsx deleted file mode 100644 index cbd276e4e..000000000 --- a/app/agents/[...agentSlug]/page.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { AgentDetail } from "@/app/components/agents/agent-detail" -import { LayoutApp } from "@/app/components/layout/layout-app" -import { MessagesProvider } from "@/lib/chat-store/messages/provider" -import { isSupabaseEnabled } from "@/lib/supabase/config" -import { createClient } from "@/lib/supabase/server" -import { notFound } from "next/navigation" - -export default async function AgentIdPage({ - params, -}: { - params: Promise<{ agentSlug: string | string[] }> -}) { - if (!isSupabaseEnabled) { - notFound() - } - - const { agentSlug: slugParts } = await params - const agentSlug = Array.isArray(slugParts) ? slugParts.join("/") : slugParts - - const supabase = await createClient() - - if (!supabase) { - notFound() - } - - const { data: agent, error: agentError } = await supabase - .from("agents") - .select("*") - .eq("slug", agentSlug) - .single() - - if (agentError) { - throw new Error(agentError.message) - } - - const { data: agents, error: agentsError } = await supabase - .from("agents") - .select("*") - .not("slug", "eq", agentSlug) - .limit(4) - - if (agentsError) { - throw new Error(agentsError.message) - } - - return ( - - -
- -
-
-
- ) -} diff --git a/app/agents/page.tsx b/app/agents/page.tsx deleted file mode 100644 index d1a990cb7..000000000 --- a/app/agents/page.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { AgentsPage } from "@/app/components/agents/agents-page" -import { LayoutApp } from "@/app/components/layout/layout-app" -import { MessagesProvider } from "@/lib/chat-store/messages/provider" -import { CURATED_AGENTS_SLUGS } from "@/lib/config" -import { isSupabaseEnabled } from "@/lib/supabase/config" -import { createClient } from "@/lib/supabase/server" -import { notFound } from "next/navigation" - -export const dynamic = "force-dynamic" - -export default async function Page() { - if (!isSupabaseEnabled) { - notFound() - } - - const supabase = await createClient() - - if (!supabase) { - notFound() - } - - const { data: userData } = await supabase.auth.getUser() - - const { data: curatedAgents, error: agentsError } = await supabase - .from("agents") - .select("*") - .in("slug", CURATED_AGENTS_SLUGS) - - const { data: userAgents, error: userAgentsError } = userData?.user?.id - ? await supabase - .from("agents") - .select("*") - .eq("creator_id", userData?.user?.id) - : { data: [], error: null } - - if (agentsError) { - console.error(agentsError) - return
Error loading agents
- } - - if (userAgentsError) { - console.error(userAgentsError) - return
Error loading user agents
- } - - return ( - - - - - - ) -} diff --git a/app/api/chat/api.ts b/app/api/chat/api.ts index b6bfbc8c4..c84757056 100644 --- a/app/api/chat/api.ts +++ b/app/api/chat/api.ts @@ -1,14 +1,13 @@ import { saveFinalAssistantMessage } from "@/app/api/chat/db" -import { checkSpecialAgentUsage, incrementSpecialAgentUsage } from "@/lib/api" +import type { + ChatApiParams, + LogUserMessageParams, + StoreAssistantMessageParams, + SupabaseClientType, +} from "@/app/types/api.types" import { sanitizeUserInput } from "@/lib/sanitize" import { validateUserIdentity } from "@/lib/server/api" import { checkUsageByModel, incrementUsageByModel } from "@/lib/usage" -import type { - SupabaseClientType, - ChatApiParams, - LogUserMessageParams, - StoreAssistantMessageParams -} from "@/app/types/api.types" export async function validateAndTrackUsage({ userId, @@ -48,12 +47,6 @@ export async function logUserMessage({ } } -export async function trackSpecialAgentUsage(supabase: SupabaseClientType, userId: string): Promise { - if (!supabase) return - await checkSpecialAgentUsage(supabase, userId) - await incrementSpecialAgentUsage(supabase, userId) -} - export async function storeAssistantMessage({ supabase, chatId, diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 4732c4049..cf06d917e 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,6 +1,4 @@ -import { loadAgent } from "@/lib/agents/load-agent" import { SYSTEM_PROMPT_DEFAULT } from "@/lib/config" -import { loadMCPToolsFromURL } from "@/lib/mcp/load-mcp-from-url" import { getAllModels } from "@/lib/models" import { getProviderForModel } from "@/lib/openproviders/provider-map" import type { ProviderWithoutOllama } from "@/lib/user-keys" @@ -9,14 +7,9 @@ import { Message as MessageAISDK, streamText, ToolSet } from "ai" import { logUserMessage, storeAssistantMessage, - trackSpecialAgentUsage, validateAndTrackUsage, } from "./api" -import { - cleanMessagesForTools, - createErrorResponse, - extractErrorMessage, -} from "./utils" +import { createErrorResponse, extractErrorMessage } from "./utils" export const maxDuration = 60 @@ -27,7 +20,6 @@ type ChatRequest = { model: string isAuthenticated: boolean systemPrompt: string - agentId: string | null enableSearch: boolean } @@ -40,7 +32,6 @@ export async function POST(req: Request) { model, isAuthenticated, systemPrompt, - agentId, enableSearch, } = (await req.json()) as ChatRequest @@ -71,12 +62,6 @@ export async function POST(req: Request) { }) } - let agentConfig = null - - if (agentId) { - agentConfig = await loadAgent(agentId) - } - const allModels = await getAllModels() const modelConfig = allModels.find((m) => m.id === model) @@ -84,24 +69,7 @@ export async function POST(req: Request) { throw new Error(`Model ${model} not found`) } - const effectiveSystemPrompt = - agentConfig?.systemPrompt || systemPrompt || SYSTEM_PROMPT_DEFAULT - - let toolsToUse = undefined - - if (agentConfig?.mcpConfig) { - const { tools } = await loadMCPToolsFromURL(agentConfig.mcpConfig.server) - toolsToUse = tools - } else if (agentConfig?.tools) { - toolsToUse = agentConfig.tools - if (supabase) { - await trackSpecialAgentUsage(supabase, userId) - } - } - - // Clean messages when switching between agents with different tool capabilities - const hasTools = !!toolsToUse && Object.keys(toolsToUse).length > 0 - const cleanedMessages = cleanMessagesForTools(messages, hasTools) + const effectiveSystemPrompt = systemPrompt || SYSTEM_PROMPT_DEFAULT let apiKey: string | undefined if (isAuthenticated && userId) { @@ -115,8 +83,8 @@ export async function POST(req: Request) { const result = streamText({ model: modelConfig.apiSdk(apiKey, { enableSearch }), system: effectiveSystemPrompt, - messages: cleanedMessages, - tools: toolsToUse as ToolSet, + messages: messages, + tools: {} as ToolSet, maxSteps: 10, onError: (err: unknown) => { console.error("Streaming error occurred:", err) diff --git a/app/api/create-agent/route.ts b/app/api/create-agent/route.ts deleted file mode 100644 index b474e4530..000000000 --- a/app/api/create-agent/route.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { createClient } from "@/lib/supabase/server" -import { nanoid } from "nanoid" -import slugify from "slugify" - -function generateAgentSlug(title: string) { - const base = slugify(title, { lower: true, strict: true, trim: true }) - const id = nanoid(6) - return `${base}-${id}` -} - -export async function POST(request: Request) { - try { - const { - name, - description, - systemPrompt, - mcp_config, - example_inputs, - avatar_url, - tools = [], - remixable = false, - is_public = true, - max_steps = 5, - } = await request.json() - - if (!name || !description || !systemPrompt) { - return new Response( - JSON.stringify({ error: "Missing required fields" }), - { - status: 400, - } - ) - } - - const supabase = await createClient() - - if (!supabase) { - return new Response( - JSON.stringify({ error: "Supabase not available in this deployment." }), - { status: 200 } - ) - } - - const { data: authData } = await supabase.auth.getUser() - - if (!authData?.user?.id) { - return new Response(JSON.stringify({ error: "Missing userId" }), { - status: 400, - }) - } - - const { data: agent, error: supabaseError } = await supabase - .from("agents") - .insert({ - slug: generateAgentSlug(name), - name, - description, - avatar_url, - mcp_config, - example_inputs, - tools, - remixable, - is_public, - system_prompt: systemPrompt, - max_steps, - creator_id: authData.user.id, - }) - .select() - .single() - - if (supabaseError) { - return new Response(JSON.stringify({ error: supabaseError.message }), { - status: 500, - }) - } - - return new Response(JSON.stringify({ agent }), { status: 201 }) - } catch (err: unknown) { - console.error("Error in create-agent endpoint:", err) - - return new Response( - JSON.stringify({ error: (err as Error).message || "Internal server error" }), - { status: 500 } - ) - } -} diff --git a/app/api/create-chat/api.ts b/app/api/create-chat/api.ts index 5de3cb349..6e7e9db81 100644 --- a/app/api/create-chat/api.ts +++ b/app/api/create-chat/api.ts @@ -1,4 +1,3 @@ -import { filterLocalAgentId } from "@/lib/agents/utils" import { validateUserIdentity } from "@/lib/server/api" import { checkUsageByModel } from "@/lib/usage" @@ -7,7 +6,6 @@ type CreateChatInput = { title?: string model: string isAuthenticated: boolean - agentId?: string projectId?: string } @@ -16,12 +14,8 @@ export async function createChatInDb({ title, model, isAuthenticated, - agentId, projectId, }: CreateChatInput) { - // Filter out local agent IDs for database operations - const dbAgentId = filterLocalAgentId(agentId) - const supabase = await validateUserIdentity(userId, isAuthenticated) if (!supabase) { return { @@ -31,7 +25,6 @@ export async function createChatInDb({ model, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), - agent_id: dbAgentId, } } @@ -41,7 +34,6 @@ export async function createChatInDb({ user_id: string title: string model: string - agent_id?: string project_id?: string } = { user_id: userId, @@ -49,10 +41,6 @@ export async function createChatInDb({ model, } - if (dbAgentId) { - insertData.agent_id = dbAgentId - } - if (projectId) { insertData.project_id = projectId } diff --git a/app/api/create-chat/route.ts b/app/api/create-chat/route.ts index 45ff91df8..ca68d869e 100644 --- a/app/api/create-chat/route.ts +++ b/app/api/create-chat/route.ts @@ -2,7 +2,7 @@ import { createChatInDb } from "./api" export async function POST(request: Request) { try { - const { userId, title, model, isAuthenticated, agentId, projectId } = + const { userId, title, model, isAuthenticated, projectId } = await request.json() if (!userId) { @@ -16,7 +16,6 @@ export async function POST(request: Request) { title, model, isAuthenticated, - agentId, projectId, }) diff --git a/app/api/delete-agent/route.ts b/app/api/delete-agent/route.ts deleted file mode 100644 index 7e98a6a4a..000000000 --- a/app/api/delete-agent/route.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { createClient } from "@/lib/supabase/server" - -export async function DELETE(request: Request) { - try { - const { slug } = await request.json() - - if (!slug) { - return new Response(JSON.stringify({ error: "Missing agent slug" }), { - status: 400, - }) - } - - const supabase = await createClient() - - if (!supabase) { - return new Response( - JSON.stringify({ error: "Supabase not available in this deployment." }), - { status: 500 } - ) - } - - // Get the authenticated user - const { data: authData, error: authError } = await supabase.auth.getUser() - - if (authError || !authData?.user?.id) { - return new Response( - JSON.stringify({ error: "Authentication required" }), - { status: 401 } - ) - } - - // First, check if the agent exists and the user owns it - const { data: agent, error: fetchError } = await supabase - .from("agents") - .select("id, creator_id, name") - .eq("slug", slug) - .single() - - if (fetchError || !agent) { - return new Response(JSON.stringify({ error: "Agent not found" }), { - status: 404, - }) - } - - if (agent.creator_id !== authData.user.id) { - return new Response( - JSON.stringify({ - error: "You can only delete agents that you created", - }), - { status: 403 } - ) - } - - // Delete the agent - const { error: deleteError } = await supabase - .from("agents") - .delete() - .eq("slug", slug) - .eq("creator_id", authData.user.id) // Extra safety check - - if (deleteError) { - console.error("Error deleting agent:", deleteError) - return new Response( - JSON.stringify({ - error: "Failed to delete agent", - details: deleteError.message, - }), - { status: 500 } - ) - } - - return new Response( - JSON.stringify({ message: "Agent deleted successfully" }), - { status: 200 } - ) - } catch (err: unknown) { - console.error("Error in delete-agent endpoint:", err) - - return new Response( - JSON.stringify({ error: (err as Error).message || "Internal server error" }), - { status: 500 } - ) - } -} diff --git a/app/api/developer-tools/route.ts b/app/api/developer-tools/route.ts deleted file mode 100644 index 389306b15..000000000 --- a/app/api/developer-tools/route.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { NextResponse } from "next/server" - -export async function GET() { - // Only allow in development - if (process.env.NODE_ENV !== "development") { - return NextResponse.json( - { error: "Not available in production" }, - { status: 403 } - ) - } - - const getMaskedKey = (key: string | undefined) => { - if (!key || key.length < 4) return null - return `${"*".repeat(8)}${key.slice(-3)}` - } - - const tools = [ - { - id: "exa", - name: "Exa", - icon: "🧠", - description: "Use Exa to power search-based agents.", - envKeys: ["EXA_API_KEY"], - connected: Boolean(process.env.EXA_API_KEY), - maskedKey: getMaskedKey(process.env.EXA_API_KEY), - sampleEnv: `EXA_API_KEY=your_key_here`, - }, - { - id: "github", - name: "GitHub", - icon: "🐙", - description: "Use GitHub Search in your agents.", - envKeys: ["GITHUB_TOKEN"], - connected: Boolean(process.env.GITHUB_TOKEN), - maskedKey: getMaskedKey(process.env.GITHUB_TOKEN), - sampleEnv: `GITHUB_TOKEN=your_token_here`, - }, - ] - - return NextResponse.json({ tools }) -} diff --git a/app/api/tools-available/route.ts b/app/api/tools-available/route.ts deleted file mode 100644 index 022a3ff6f..000000000 --- a/app/api/tools-available/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TOOL_REGISTRY } from "@/lib/tools" -import { NextResponse } from "next/server" - -export async function GET() { - const availableToolIds = Object.entries(TOOL_REGISTRY) - .filter(([, tool]) => tool?.isAvailable?.()) - .map(([id]) => id) - - return NextResponse.json({ available: availableToolIds }) -} diff --git a/app/api/update-chat-agent/route.ts b/app/api/update-chat-agent/route.ts deleted file mode 100644 index 50ba74813..000000000 --- a/app/api/update-chat-agent/route.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { filterLocalAgentId } from "@/lib/agents/utils" -import { validateUserIdentity } from "@/lib/server/api" - -export async function POST(request: Request) { - try { - const { userId, chatId, agentId, isAuthenticated } = await request.json() - - if (!userId || !chatId) { - return new Response( - JSON.stringify({ error: "Missing userId or chatId" }), - { status: 400 } - ) - } - - // Filter out local agent IDs for database operations - const dbAgentId = filterLocalAgentId(agentId) - - const supabase = await validateUserIdentity(userId, isAuthenticated) - - if (!supabase) { - console.log("Supabase not enabled, skipping agent update") - return new Response( - JSON.stringify({ chat: { id: chatId, agent_id: dbAgentId } }), - { - status: 200, - } - ) - } - - const { data, error: updateError } = await supabase - .from("chats") - .update({ agent_id: dbAgentId || null }) - .eq("id", chatId) - .select() - .single() - - if (updateError) { - return new Response(JSON.stringify({ error: "Failed to update chat" }), { - status: 500, - }) - } - - return new Response(JSON.stringify({ chat: data }), { - status: 200, - }) - } catch (error) { - console.error("Error updating chat agent:", error) - return new Response( - JSON.stringify({ error: "Failed to update chat agent" }), - { status: 500 } - ) - } -} diff --git a/app/components/agents/agent-card.tsx b/app/components/agents/agent-card.tsx deleted file mode 100644 index 6fe149964..000000000 --- a/app/components/agents/agent-card.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Tables } from "@/app/types/database.types" -import { Avatar, AvatarImage } from "@/components/ui/avatar" -import { cn } from "@/lib/utils" - -type AgentCardProps = { - id: string - name: string - description: string - avatar_url?: string | null - className?: string - isAvailable: boolean - onClick?: () => void - system_prompt?: string - tools?: string[] | null - mcp_config?: Tables<"agents">["mcp_config"] | null - isLight?: boolean -} - -export function AgentCard({ - name, - description, - avatar_url, - className, - isAvailable, - onClick, - system_prompt, - tools, - mcp_config, - isLight = false, -}: AgentCardProps) { - return ( - - ) -} diff --git a/app/components/agents/agent-detail.tsx b/app/components/agents/agent-detail.tsx deleted file mode 100644 index 203092f1f..000000000 --- a/app/components/agents/agent-detail.tsx +++ /dev/null @@ -1,442 +0,0 @@ -"use client" - -import { AgentSummary } from "@/app/types/agent" -import type { Tables } from "@/app/types/database.types" -import { ButtonCopy } from "@/components/common/button-copy" -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog" -import { Avatar, AvatarImage } from "@/components/ui/avatar" -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { toast } from "@/components/ui/toast" -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip" -import { fetchClient } from "@/lib/fetch" -import { API_ROUTE_DELETE_AGENT } from "@/lib/routes" -import { useUser } from "@/lib/user-store/provider" -import { cn } from "@/lib/utils" -import { - ChatCircle, - Check, - CopySimple, - Cube, - DotsThree, - Trash, - X, -} from "@phosphor-icons/react" -import { useRouter } from "next/navigation" -import { useRef, useState } from "react" - -function SystemPromptDisplay({ prompt }: { prompt: string }) { - const [expanded, setExpanded] = useState(false) - const isLong = prompt.length > 300 - - const displayText = - isLong && !expanded ? prompt.slice(0, 300) + "..." : prompt - - return ( -
-
- -
-
- {displayText} -
- {isLong && ( - - )} -
- ) -} - -type AgentDetailProps = { - slug: string - name: string - description: string - example_inputs: string[] - creator_id?: string | null - avatar_url?: string | null - onAgentClick?: (agentId: string) => void - randomAgents: AgentSummary[] - isFullPage?: boolean - system_prompt?: string | null - tools?: string[] | null - mcp_config?: Tables<"agents">["mcp_config"] | null -} - -export function AgentDetail({ - slug, - name, - description, - example_inputs, - creator_id, - avatar_url, - onAgentClick, - randomAgents, - isFullPage, - system_prompt, - tools, - mcp_config, -}: AgentDetailProps) { - const [copied, setCopied] = useState(false) - const [showDeleteDialog, setShowDeleteDialog] = useState(false) - const [isDeleting, setIsDeleting] = useState(false) - const router = useRouter() - const { user } = useUser() - const didPrefetch = useRef(false) - - if (!didPrefetch.current && isFullPage && randomAgents.length > 0) { - randomAgents.forEach((agent) => { - router.prefetch(`/agents/${agent.slug}`) - }) - didPrefetch.current = true - } - - const copyToClipboard = () => { - navigator.clipboard.writeText(`${window.location.origin}/agents/${slug}`) - setCopied(true) - setTimeout(() => setCopied(false), 1000) - } - - const handleAgentClick = (agent: AgentSummary) => { - if (onAgentClick) { - onAgentClick(agent.id) - } else { - router.push(`/agents/${agent.slug}`) - } - } - - const tryAgentWithPrompt = async (prompt: string) => { - router.push(`/?agent=${slug}&prompt=${encodeURIComponent(prompt)}`) - } - - const tryAgent = async () => { - router.push(`/?agent=${slug}`) - } - - const handleDelete = async () => { - if (!user?.id) { - toast({ - title: "Error", - description: "You must be logged in to delete an agent.", - status: "error", - }) - return - } - - if (creator_id !== user.id) { - toast({ - title: "Error", - description: "You can only delete agents that you created.", - status: "error", - }) - return - } - - setIsDeleting(true) - try { - const response = await fetchClient(API_ROUTE_DELETE_AGENT, { - method: "DELETE", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ slug }), - }) - - const data = await response.json() - - if (!response.ok) { - throw new Error(data.error || `HTTP ${response.status}`) - } - - toast({ - title: "Success", - description: "Agent deleted successfully.", - status: "success", - }) - - setShowDeleteDialog(false) - - // If we're in a dialog (not full page), close it first - if (!isFullPage && onAgentClick) { - onAgentClick("") - } - - // Navigate to agents page - router.push("/agents") - } catch (error) { - console.error("Failed to delete agent:", error) - toast({ - title: "Error", - description: - error instanceof Error - ? error.message - : "Failed to delete agent. Please try again.", - status: "error", - }) - } finally { - setIsDeleting(false) - } - } - - const canDelete = user?.id && creator_id === user.id - - return ( -
-
-
-
- {avatar_url ? ( - - - - ) : ( -
- -
- )} -

{name}

-
- -
- {isFullPage && canDelete && ( - - - - - - setShowDeleteDialog(true)} - > - - Delete agent - - - - )} - {!isFullPage && ( - - )} -
-
- -
-

{description}

-
- - {system_prompt && ( -
-

System Prompt

- -
- )} - - {(tools || mcp_config) && ( -
- {tools && ( -
-

Tools

-

{tools}

-
- )} - {mcp_config && ( -
-

MCP

-

- {JSON.stringify(mcp_config)} -

-
- )} -
- )} - - {example_inputs && example_inputs.length > 0 && ( -
-

What can I ask?

-
- {example_inputs.map((example_input) => ( - - ))} -
-
- )} - - {randomAgents && randomAgents.length > 0 && ( -
-

- More agents -

-
- {randomAgents.map((agent, index) => ( -
handleAgentClick(agent)} - className={cn( - "bg-secondary hover:bg-accent h-full cursor-pointer rounded-xl p-4 transition-colors", - isFullPage ? "w-full" : "min-w-[280px]", - index === randomAgents.length - 1 && "mr-6" - )} - > -
-
- {agent.avatar_url ? ( -
- - - -
- ) : ( -
- -
- )} -
-
-

- {agent.name} -

-

- {agent.description} -

-
-
-
- ))} -
-
- )} -
- -
- - - - - - {copied ? "Copied to clipboard" : "Copy link to clipboard"} - - - -
- - {/* Only show AlertDialog in full page mode to avoid nesting issues */} - {isFullPage && ( - - - - Delete Agent - - Are you sure you want to delete "{name}"? This action - cannot be undone. - - - - Cancel - - {isDeleting ? "Deleting..." : "Delete"} - - - - - )} -
- ) -} diff --git a/app/components/agents/agent-featured-section.tsx b/app/components/agents/agent-featured-section.tsx deleted file mode 100644 index e0ca938cf..000000000 --- a/app/components/agents/agent-featured-section.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Agent } from "@/app/types/agent" -import React from "react" -import { DialogAgent } from "./dialog-agent" - -type AgentFeaturedSectionProps = { - agents: Agent[] - handleAgentClick: (agentId: string | null) => void - openAgentId: string | null - setOpenAgentId: (agentId: string | null) => void - moreAgents: Agent[] -} - -export function AgentFeaturedSection({ - agents, - moreAgents, - handleAgentClick, - openAgentId, - setOpenAgentId, -}: AgentFeaturedSectionProps) { - if (!agents || agents.length === 0) { - return null - } - - return ( -
-

Featured

-
- {agents.map((agent) => ( - setOpenAgentId(open ? agent.id : null)} - randomAgents={moreAgents} - /> - ))} -
-
- ) -} diff --git a/app/components/agents/agents-page.tsx b/app/components/agents/agents-page.tsx deleted file mode 100644 index 1fb5153de..000000000 --- a/app/components/agents/agents-page.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client" - -import { Agent } from "@/app/types/agent" -import { Button } from "@/components/ui/button" -import { useMemo, useState } from "react" -import { AgentFeaturedSection } from "./agent-featured-section" -import { DialogCreateAgentTrigger } from "./dialog-create-agent/dialog-trigger-create-agent" -import { UserAgentsSection } from "./user-agent-section" - -type AgentsPageProps = { - curatedAgents: Agent[] - userAgents: Agent[] | null - userId: string | null -} - -export function AgentsPage({ - curatedAgents, - userAgents, - userId, -}: AgentsPageProps) { - const [openAgentId, setOpenAgentId] = useState(null) - - const randomAgents = useMemo(() => { - return curatedAgents - .filter((agent) => agent.id !== openAgentId) - .sort(() => Math.random() - 0.5) - .slice(0, 4) - }, [curatedAgents, openAgentId]) - - const handleAgentClick = (agentId: string | null) => { - setOpenAgentId(agentId) - } - - return ( -
-
-
-

- Agents (experimental) -

-
- Your every day AI assistant -
-

- a growing set of personal AI agents, built for ideas, writing, and - product work. -

- - Create an agent - - } - /> -
- - - -
-
- ) -} diff --git a/app/components/agents/dialog-agent.tsx b/app/components/agents/dialog-agent.tsx deleted file mode 100644 index 9f25135ee..000000000 --- a/app/components/agents/dialog-agent.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { useBreakpoint } from "@/app/hooks/use-breakpoint" -import { AgentSummary } from "@/app/types/agent" -import type { Tables } from "@/app/types/database.types" -import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog" -import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer" -import { AgentCard } from "./agent-card" -import { AgentDetail } from "./agent-detail" - -type DialogAgentProps = { - id: string - name: string - description: string - avatar_url?: string | null - example_inputs: string[] - className?: string - isAvailable: boolean - slug: string - onAgentClick?: (agentId: string | null) => void - isOpen: boolean - onOpenChange: (open: boolean) => void - randomAgents: AgentSummary[] - trigger?: React.ReactNode - system_prompt?: string | null - tools?: string[] | null - mcp_config?: Tables<"agents">["mcp_config"] | null - isCardLight?: boolean - creator_id?: string | null -} - -export function DialogAgent({ - id, - name, - description, - avatar_url, - example_inputs, - slug, - system_prompt, - className, - isAvailable, - onAgentClick, - isOpen, - onOpenChange, - randomAgents, - trigger = null, - tools, - mcp_config, - isCardLight = false, - creator_id, -}: DialogAgentProps) { - const isMobile = useBreakpoint(768) - - const handleOpenChange = (open: boolean) => { - if (!isAvailable) { - return - } - - window.history.replaceState(null, "", `/agents/${slug}`) - onOpenChange(open) - } - - const defaultTrigger = ( - handleOpenChange(true)} - tools={tools} - mcp_config={mcp_config} - isLight={isCardLight} - /> - ) - - const renderContent = () => ( - - ) - - if (isMobile) { - return ( - - {trigger || defaultTrigger} - - {renderContent()} - - - ) - } - - return ( - - {trigger || defaultTrigger} - e.preventDefault()} - > - {renderContent()} - - - ) -} diff --git a/app/components/agents/dialog-create-agent/create-agent-form.tsx b/app/components/agents/dialog-create-agent/create-agent-form.tsx deleted file mode 100644 index 1d2c1520d..000000000 --- a/app/components/agents/dialog-create-agent/create-agent-form.tsx +++ /dev/null @@ -1,211 +0,0 @@ -"use client" - -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" -import { Textarea } from "@/components/ui/textarea" -import { AlertCircle, Check, Github, X } from "lucide-react" -import type React from "react" -import { ToolsSection } from "./tools-section" - -export type AgentFormData = { - name: string - description: string - systemPrompt: string - mcp: "none" | "git-mcp" - repository?: string - tools: string[] -} - -type CreateAgentFormProps = { - formData: AgentFormData - repository: string - setRepository: (e: React.ChangeEvent) => void - error: { [key: string]: string } - isLoading: boolean - handleInputChange: ( - e: React.ChangeEvent - ) => void - handleSelectChange: (value: string) => void - handleToolsChange: (selectedTools: string[]) => void - handleSubmit: (e: React.FormEvent) => Promise - onClose: () => void - isDrawer?: boolean -} - -export function CreateAgentForm({ - formData, - repository, - setRepository, - error, - isLoading, - handleInputChange, - handleSelectChange, - handleToolsChange, - handleSubmit, - onClose, - isDrawer = false, -}: CreateAgentFormProps) { - return ( -
- {isDrawer && ( -
-

Create agent (experimental)

- -
- )} - -
-
-

- Agents can use a system prompt and optionally connect to GitHub - repos via git-mcp. More tools and MCP integrations are coming soon. -

-
- -
- {/* Agent Name */} -
- - - - {error.name && ( -
- - {error.name} -
- )} -
- - {/* Description */} -
- - -

- Short sentence, used in list/search -

- {error.description && ( -
- - {error.description} -
- )} -
- - - - {/* MCP Dropdown */} -
- - -
- - {/* Repository (only shown if git-mcp is selected) */} - {formData.mcp === "git-mcp" && ( -
- -
- - -
- - {error.repository && ( -
- - {error.repository} -
- )} -
- )} - - {/* System Prompt */} -
- -