import React, { useState, useEffect, useCallback } from "react"; import { useParams, useNavigate, Link } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { chatbotsAPI, documentsAPI, modelsAPI, uploadAPI, urlSourcesAPI, leadsAPI, channelsAPI, } from "@/services/api"; import { useAuthStore } from "@/store/authStore"; import { Button, Input, Textarea, Select, Card, Badge, StatusDot, Spinner, } from "@/components/ui"; import { CATEGORIES, INDUSTRIES, formatBytes, getFileIcon, cn, } from "@/lib/utils"; import { ChatInterface } from "@/components/ChatInterface"; import { useDropzone } from "react-dropzone"; import type { ChatbotFormData, AvailableModel, UrlSource, ChannelConnection, } from "@/types"; import { CHATBOT_TEMPLATES } from "@/data/templates"; import { ArrowLeft, Save, Eye, Upload, Trash2, FileText, Sliders, AlertCircle, CheckCircle, ChevronDown, ChevronRight, Settings2, ImagePlus, X, Link2, Copy, Globe, Webhook, Share2, Code, MessageSquare, Calendar, } from "lucide-react"; const DEFAULT_FORM: ChatbotFormData = { name: "", description: "", system_prompt: "", model: "accounts/fireworks/models/kimi-k2-instruct-0905", temperature: 0.7, max_tokens: 1000, primary_color: "#6366f1", welcome_message: "Hello! How can I help you today?", logo_url: "", category: "", industry: "", languages: ["en"], show_branding: true, lead_capture_enabled: false, lead_capture_fields: ["email"], lead_capture_trigger: "after_first_message", handoff_enabled: false, handoff_message: "I'll connect you with our team. Please wait.", handoff_email: "", handoff_keywords: ["human", "agent", "speak to someone"], booking_enabled: false, }; type Tab = "settings" | "documents" | "preview" | "deploy"; export const ChatbotBuilderPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const isNew = !id || id === "new"; const navigate = useNavigate(); const queryClient = useQueryClient(); const { user } = useAuthStore(); const [tab, setTab] = useState("settings"); const [form, setForm] = useState(DEFAULT_FORM); const [showTemplatePicker, setShowTemplatePicker] = useState(isNew); const [toast, setToast] = useState<{ msg: string; type: "success" | "error"; } | null>(null); const [chatbotId, setChatbotId] = useState( isNew ? null : id || null, ); // Load existing chatbot const { data: existingChatbot, isLoading: loadingChatbot } = useQuery({ queryKey: ["chatbot", id], queryFn: () => chatbotsAPI.get(id!), enabled: !isNew, }); useEffect(() => { if (existingChatbot) { // eslint-disable-next-line react-hooks/set-state-in-effect setForm({ name: existingChatbot.name, description: existingChatbot.description || "", system_prompt: existingChatbot.system_prompt || "", model: existingChatbot.model, temperature: existingChatbot.temperature, max_tokens: existingChatbot.max_tokens, primary_color: existingChatbot.primary_color, welcome_message: existingChatbot.welcome_message, logo_url: existingChatbot.logo_url || "", category: existingChatbot.category || "", industry: existingChatbot.industry || "", languages: existingChatbot.languages, show_branding: existingChatbot.show_branding, lead_capture_enabled: existingChatbot.lead_capture_enabled, lead_capture_fields: existingChatbot.lead_capture_fields, lead_capture_trigger: existingChatbot.lead_capture_trigger, handoff_enabled: existingChatbot.handoff_enabled, handoff_message: existingChatbot.handoff_message, handoff_email: existingChatbot.handoff_email || "", handoff_keywords: existingChatbot.handoff_keywords, booking_enabled: existingChatbot.booking_enabled ?? false, }); setChatbotId(existingChatbot.id); } }, [existingChatbot]); const createMutation = useMutation({ mutationFn: chatbotsAPI.create, onSuccess: (data) => { setChatbotId(data.id); queryClient.invalidateQueries({ queryKey: ["chatbots"] }); navigate(`/chatbots/${data.id}/edit`, { replace: true }); showToast("Chatbot created!", "success"); }, onError: (err: { response?: { data?: { detail?: string } } }) => showToast(err.response?.data?.detail || "Failed to create", "error"), }); const updateMutation = useMutation({ mutationFn: (data: Partial) => chatbotsAPI.update(chatbotId!, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["chatbot", chatbotId] }); queryClient.invalidateQueries({ queryKey: ["chatbots"] }); showToast("Settings saved!", "success"); }, onError: (err: { response?: { data?: { detail?: string } } }) => showToast(err.response?.data?.detail || "Save failed", "error"), }); const handleSave = () => { if (!form.name.trim()) { showToast("Chatbot name is required", "error"); return; } if (isNew || !chatbotId) { createMutation.mutate(form); } else { updateMutation.mutate(form); } }; const showToast = (msg: string, type: "success" | "error") => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); }; if (loadingChatbot) { return (

Loading chatbot…

); } if (isNew && showTemplatePicker) { return (
{/* Template picker header */}

Choose a template

Start from a template or build from scratch

{CHATBOT_TEMPLATES.map((template) => ( ))}
); } const TABS: { key: Tab; label: string; icon: React.ElementType }[] = [ { key: "settings", label: "Settings", icon: Sliders }, { key: "documents", label: "Documents", icon: FileText }, { key: "preview", label: "Preview", icon: Eye }, { key: "deploy", label: "Deploy", icon: Share2 }, ]; return (
{/* Top bar */}
{/* Chatbot name + status */}

{isNew ? "Create Chatbot" : form.name || "Untitled Chatbot"}

{!isNew && existingChatbot && (

{existingChatbot.is_published ? ( Published ) : ( Draft )}

)}
{/* Pill tab switcher */}
{TABS.map((t) => ( ))}
{/* Tab content with fade transition */}
{tab === "settings" && ( )} {tab === "documents" && chatbotId && ( )} {tab === "documents" && !chatbotId && ( } message="Save your chatbot first to upload documents." accent="amber" /> )} {tab === "preview" && chatbotId && (
)} {tab === "preview" && !chatbotId && ( } message="Save your chatbot first to preview it." accent="gray" /> )} {tab === "deploy" && chatbotId && ( )} {tab === "deploy" && !chatbotId && ( } message="Save your chatbot first to access deployment options." accent="gray" /> )}
{/* Toast notification */} {toast && (
{toast.type === "success" ? ( ) : ( )} {toast.msg}
)}
); }; // ═══════════════════════════════════════════════════════════════════════════════ // SAVE FIRST PROMPT — shown when chatbot not yet saved // ═══════════════════════════════════════════════════════════════════════════════ const SaveFirstPrompt: React.FC<{ icon: React.ReactNode; message: string; accent: "amber" | "gray"; }> = ({ icon, message, accent }) => (
{icon}

{message}

Fill in the Settings tab and click Save to continue.

); // ═══════════════════════════════════════════════════════════════════════════════ // SECTION HEADER — reusable divider with title // ═══════════════════════════════════════════════════════════════════════════════ const SectionHeader: React.FC<{ icon?: React.ReactNode; title: string; description?: string; }> = ({ icon, title, description }) => (
{icon && (
{icon}
)}

{title}

{description && (

{description}

)}
); // ═══════════════════════════════════════════════════════════════════════════════ // SETTINGS TAB // ═══════════════════════════════════════════════════════════════════════════════ interface SettingsTabProps { form: ChatbotFormData; setForm: React.Dispatch>; userPlan: string; } const SettingsTab: React.FC = ({ form, setForm }) => { const [advancedOpen, setAdvancedOpen] = useState(false); const set = (field: K) => (value: ChatbotFormData[K]) => { setForm((prev) => ({ ...prev, [field]: value })); }; // ── IMPROVEMENT #3: Load models dynamically from backend ────────────────── const { data: modelsData, isLoading: modelsLoading } = useQuery({ queryKey: ["available-models"], queryFn: modelsAPI.available, staleTime: 5 * 60 * 1000, // cache for 5 minutes }); const availableModels = modelsData?.models || []; const upgradeLabel = modelsData?.upgrade_label || null; return (
{/* ── Basic Info ──────────────────────────────────────────────────────── */}
} title="Basic Info" description="Name, description, and greeting message for your chatbot" />
set("name")(e.target.value)} placeholder="e.g. Customer Support Bot" />