From 7218d570616b4e7a8e6865989971299acda6fc97 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 00:43:38 +0000 Subject: [PATCH] Changes --- src/pages/AIChat.tsx | 162 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 153 insertions(+), 9 deletions(-) diff --git a/src/pages/AIChat.tsx b/src/pages/AIChat.tsx index 8f56f58..3601aaa 100644 --- a/src/pages/AIChat.tsx +++ b/src/pages/AIChat.tsx @@ -1,6 +1,6 @@ import { useState, useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; -import { Send, Bot, User, Loader2, Trash2, AlertTriangle, Maximize2, Minimize2, ChevronDown } from 'lucide-react'; +import { Send, Bot, User, Loader2, Trash2, AlertTriangle, Maximize2, Minimize2, ChevronDown, Settings } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { ScrollArea } from '@/components/ui/scroll-area'; @@ -8,13 +8,22 @@ import { useSettings } from '@/contexts/SettingsContext'; import { useToast } from '@/hooks/use-toast'; import GlitchText from '@/components/GlitchText'; import MessageContent from '@/components/MessageContent'; -import { AI_PROVIDERS, AIProvider, getProvider } from '@/lib/aiProviders'; +import { AI_PROVIDERS, AIProvider, getProvider, CUSTOM_API_STORAGE_KEY } from '@/lib/aiProviders'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; interface Message { id: string; @@ -23,6 +32,12 @@ interface Message { timestamp: Date; } +interface CustomApiConfig { + endpoint: string; + apiKey: string; + model: string; +} + const STORAGE_KEY = 'ai-chat-history'; const PROVIDER_KEY = 'ai-chat-provider'; @@ -49,6 +64,19 @@ const AIChat = () => { const stored = localStorage.getItem(PROVIDER_KEY); return (stored as AIProvider) || 'pollinations'; }); + const [customApiConfig, setCustomApiConfig] = useState(() => { + const stored = localStorage.getItem(CUSTOM_API_STORAGE_KEY); + if (stored) { + try { + return JSON.parse(stored); + } catch { + return { endpoint: '', apiKey: '', model: '' }; + } + } + return { endpoint: '', apiKey: '', model: '' }; + }); + const [showCustomApiDialog, setShowCustomApiDialog] = useState(false); + const [tempCustomConfig, setTempCustomConfig] = useState({ endpoint: '', apiKey: '', model: '' }); const scrollRef = useRef(null); const { playSound } = useSettings(); const { toast } = useToast(); @@ -67,6 +95,13 @@ const AIChat = () => { localStorage.setItem(PROVIDER_KEY, selectedProvider); }, [selectedProvider]); + // Persist custom API config + useEffect(() => { + if (customApiConfig.endpoint || customApiConfig.apiKey) { + localStorage.setItem(CUSTOM_API_STORAGE_KEY, JSON.stringify(customApiConfig)); + } + }, [customApiConfig]); + // Exit fullscreen on Escape key useEffect(() => { const handleEscape = (e: KeyboardEvent) => { @@ -164,6 +199,23 @@ const AIChat = () => { // Helper function to make API request with retry logic const provider = getProvider(selectedProvider); + + // For custom provider, use user's config + const apiEndpoint = selectedProvider === 'custom' ? customApiConfig.endpoint : provider.endpoint; + const apiKey = selectedProvider === 'custom' ? customApiConfig.apiKey : provider.apiKey; + const apiModel = selectedProvider === 'custom' ? customApiConfig.model : provider.model; + + if (selectedProvider === 'custom' && (!customApiConfig.endpoint || !customApiConfig.apiKey)) { + toast({ + title: 'Custom API not configured', + description: 'Please configure your custom API settings first.', + variant: 'destructive', + }); + setMessages(prev => prev.filter(msg => msg.id !== assistantMessage.id)); + setIsLoading(false); + return; + } + const makeRequest = async (retries = 3, delay = 1000): Promise => { for (let attempt = 1; attempt <= retries; attempt++) { try { @@ -171,15 +223,15 @@ const AIChat = () => { 'Content-Type': 'application/json', }; - if (provider.requiresAuth && provider.apiKey) { - headers['Authorization'] = `Bearer ${provider.apiKey}`; + if ((provider.requiresAuth || selectedProvider === 'custom') && apiKey) { + headers['Authorization'] = `Bearer ${apiKey}`; } - const response = await fetch(provider.endpoint, { + const response = await fetch(apiEndpoint, { method: 'POST', headers, body: JSON.stringify({ - model: provider.model, + model: apiModel, messages: [ { role: 'system', content: 'You are a helpful AI assistant with a hacker/cyberpunk personality. Keep responses concise and engaging. IMPORTANT: When sharing code examples, ALWAYS wrap them in markdown code blocks with the language specified, like ```python\ncode here\n``` or ```javascript\ncode here\n```. Never show code without proper markdown code block formatting.' }, ...chatHistory, @@ -390,6 +442,10 @@ const AIChat = () => { { + if (provider.id === 'custom') { + setTempCustomConfig(customApiConfig); + setShowCustomApiDialog(true); + } setSelectedProvider(provider.id); playSound('click'); }} @@ -397,14 +453,34 @@ const AIChat = () => { selectedProvider === provider.id ? 'text-primary' : 'text-foreground' }`} > -
-
{provider.name}
-
{provider.description}
+
+
+
{provider.name}
+
{provider.description}
+
+ {provider.id === 'custom' && ( + + )}
))} + {selectedProvider === 'custom' && ( + + )}
@@ -504,6 +580,74 @@ const AIChat = () => { )} + + {/* Custom API Configuration Dialog */} + + + + Custom API Configuration + +
+
+ + setTempCustomConfig(prev => ({ ...prev, endpoint: e.target.value }))} + className="font-pixel text-sm bg-background/50 border-primary/50" + /> +
+
+ + setTempCustomConfig(prev => ({ ...prev, apiKey: e.target.value }))} + className="font-pixel text-sm bg-background/50 border-primary/50" + /> +
+
+ + setTempCustomConfig(prev => ({ ...prev, model: e.target.value }))} + className="font-pixel text-sm bg-background/50 border-primary/50" + /> +
+

+ Your API key is stored locally in your browser and never sent to our servers. +

+
+ + + + +
+
); };