From 2fb1cd706148419b011b05bfa31a7fea96abd5fe 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:34:29 +0000 Subject: [PATCH] Changes --- src/lib/aiProviders.ts | 35 ++++++++++++++++++++ src/pages/AIChat.tsx | 73 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 src/lib/aiProviders.ts diff --git a/src/lib/aiProviders.ts b/src/lib/aiProviders.ts new file mode 100644 index 0000000..161e385 --- /dev/null +++ b/src/lib/aiProviders.ts @@ -0,0 +1,35 @@ +export type AIProvider = 'pollinations' | 'gpt-oss'; + +export interface AIProviderConfig { + id: AIProvider; + name: string; + description: string; + endpoint: string; + requiresAuth: boolean; + apiKey?: string; + model?: string; +} + +export const AI_PROVIDERS: AIProviderConfig[] = [ + { + id: 'pollinations', + name: 'Pollinations.ai', + description: 'Free, no login required', + endpoint: 'https://text.pollinations.ai/openai', + requiresAuth: false, + model: 'openai', + }, + { + id: 'gpt-oss', + name: 'GPT-OSS-20B', + description: 'Alternative model (IP-locked)', + endpoint: 'https://api.pawan.krd/gpt-oss-20b/v1/chat/completions', + requiresAuth: true, + apiKey: 'pk-AvSqFEAjTobfSGEGTnqKVuSwcfcDDMeEZSkeFqnlrGFJNVAC', + model: 'gpt-oss-20b', + }, +]; + +export const getProvider = (id: AIProvider): AIProviderConfig => { + return AI_PROVIDERS.find(p => p.id === id) || AI_PROVIDERS[0]; +}; diff --git a/src/pages/AIChat.tsx b/src/pages/AIChat.tsx index 7fe3e1f..8f56f58 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 } from 'lucide-react'; +import { Send, Bot, User, Loader2, Trash2, AlertTriangle, Maximize2, Minimize2, ChevronDown } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { ScrollArea } from '@/components/ui/scroll-area'; @@ -8,6 +8,13 @@ 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 { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; interface Message { id: string; @@ -17,6 +24,7 @@ interface Message { } const STORAGE_KEY = 'ai-chat-history'; +const PROVIDER_KEY = 'ai-chat-provider'; const AIChat = () => { const [messages, setMessages] = useState(() => { @@ -37,6 +45,10 @@ const AIChat = () => { const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); + const [selectedProvider, setSelectedProvider] = useState(() => { + const stored = localStorage.getItem(PROVIDER_KEY); + return (stored as AIProvider) || 'pollinations'; + }); const scrollRef = useRef(null); const { playSound } = useSettings(); const { toast } = useToast(); @@ -50,6 +62,11 @@ const AIChat = () => { } }, [messages]); + // Persist provider selection + useEffect(() => { + localStorage.setItem(PROVIDER_KEY, selectedProvider); + }, [selectedProvider]); + // Exit fullscreen on Escape key useEffect(() => { const handleEscape = (e: KeyboardEvent) => { @@ -146,16 +163,23 @@ const AIChat = () => { setMessages(prev => [...prev, assistantMessage]); // Helper function to make API request with retry logic + const provider = getProvider(selectedProvider); const makeRequest = async (retries = 3, delay = 1000): Promise => { for (let attempt = 1; attempt <= retries; attempt++) { try { - const response = await fetch('https://text.pollinations.ai/openai', { + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (provider.requiresAuth && provider.apiKey) { + headers['Authorization'] = `Bearer ${provider.apiKey}`; + } + + const response = await fetch(provider.endpoint, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers, body: JSON.stringify({ - model: 'openai', + model: provider.model, 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, @@ -348,9 +372,40 @@ const AIChat = () => { -

- {'>'} Free AI chat powered by Pollinations.ai - no login required -

+
+ {'>'} Model: + + + + + + {AI_PROVIDERS.map((provider) => ( + { + setSelectedProvider(provider.id); + playSound('click'); + }} + className={`font-pixel text-xs cursor-pointer ${ + selectedProvider === provider.id ? 'text-primary' : 'text-foreground' + }`} + > +
+
{provider.name}
+
{provider.description}
+
+
+ ))} +
+
+
{messages.length === 0 ? (