"use client" import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import { useUser } from '@stackframe/stack' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Badge } from '@/components/ui/badge' import { Loader2, Copy, CheckCircle2, Cloud, Server, Share2 } from 'lucide-react' import { ToolDefinition, ToolInput } from '@/lib/tools' import { AI_MODELS, DEFAULT_MODEL, isOllamaModel } from '@/types/prompt' import { useOllama } from '@/contexts/ollama-context' import { SaveRunPanel } from '@/components/saved-runs/save-run-panel' import { ShareMenu } from '@/components/share/share-menu' interface ToolExecutorProps { tool: ToolDefinition } interface SavedRun { id: string name: string | null inputs: Record | null output: string model: string createdAt: string } export function ToolExecutor({ tool }: ToolExecutorProps) { const router = useRouter() const user = useUser() const { settings: ollamaSettings } = useOllama() const [inputs, setInputs] = useState>({}) const [output, setOutput] = useState('') const [isLoading, setIsLoading] = useState(false) const [isCopied, setIsCopied] = useState(false) const [selectedModel, setSelectedModel] = useState(DEFAULT_MODEL) // Saved runs state const [savedRuns, setSavedRuns] = useState([]) const [loadingRuns, setLoadingRuns] = useState(false) // Fetch saved runs useEffect(() => { const fetchRuns = async () => { if (!tool.slug || !user?.id) return setLoadingRuns(true) try { const res = await fetch(`/api/tools/${tool.slug}/saved-runs?userId=${user.id}`) if (res.ok) { const data = await res.json() setSavedRuns(data.runs || []) } } catch (err) { console.error("Failed to fetch runs:", err) } finally { setLoadingRuns(false) } } fetchRuns() }, [tool.slug, user?.id]) const handleInputChange = (name: string, value: string) => { setInputs(prev => ({ ...prev, [name]: value })) } const handleExecute = async () => { setIsLoading(true) setOutput('') try { if (isOllamaModel(selectedModel)) { // Ollama: call directly from browser instead of through server const ollamaUrl = (ollamaSettings.apiUrl || "http://localhost:11434").replace(/\/+$/, "") // Build the prompt from tool's system prompt + inputs let finalPrompt = tool.systemPrompt for (const [key, value] of Object.entries(inputs)) { finalPrompt = finalPrompt.replace(new RegExp(`{{${key}}}`, 'g'), value) } const res = await fetch(`${ollamaUrl}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: selectedModel, prompt: finalPrompt, stream: false, }), }) if (!res.ok) { if (res.status === 0 || res.type === "opaque") { throw new Error( `Cannot reach Ollama. Make sure CORS is enabled:\n` + `OLLAMA_ORIGINS=${window.location.origin} ollama serve` ) } throw new Error(`Ollama error: ${res.status}. Make sure Ollama is running.`) } const data = await res.json() setOutput(data.response) } else { // Cloud models: call server API const res = await fetch(`/api/tools/${tool.slug}/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inputs, model: selectedModel, }), }) const data = await res.json() if (res.ok) { setOutput(data.output) } else { setOutput(`Error: ${data.error || 'Failed to execute tool'}`) } } } catch (error) { console.error('Execute error:', error) setOutput( error instanceof Error ? `Error: ${error.message}` : 'Error: Failed to execute tool. Please try again.' ) } finally { setIsLoading(false) } } const handleCopy = async () => { if (output) { await navigator.clipboard.writeText(output) setIsCopied(true) setTimeout(() => setIsCopied(false), 2000) } } const handleSaveRun = async (name: string) => { if (!output) return const res = await fetch(`/api/tools/${tool.slug}/saved-runs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: user?.id, name: name || null, inputs, output, model: selectedModel, }), }) if (res.ok) { const newRun = await res.json() setSavedRuns(prev => [newRun, ...prev]) } else { throw new Error("Failed to save") } } const getModelName = (modelId: string) => { if (isOllamaModel(modelId)) { return modelId.split(':')[0] || modelId } return AI_MODELS[modelId as keyof typeof AI_MODELS]?.name || modelId } const renderInput = (input: ToolInput) => { const value = inputs[input.name] || '' switch (input.type) { case 'textarea': return (