chatflow / app.py
uumerrr684's picture
Update app.py
ea943de verified
raw
history blame
18.6 kB
import React, { useState, useEffect, useRef } from 'react';
import { Send, Plus, MessageSquare, Settings, Users, Download, Trash2, RefreshCw, Bot } from 'lucide-react';
const ChatFlowApp = () => {
const [messages, setMessages] = useState([]);
const [currentMessage, setCurrentMessage] = useState('');
const [selectedModel, setSelectedModel] = useState('openai/gpt-3.5-turbo');
const [isLoading, setIsLoading] = useState(false);
const [sessions, setSessions] = useState([]);
const [currentSessionId, setCurrentSessionId] = useState('default');
const [onlineUsers, setOnlineUsers] = useState(1);
const [apiStatus, setApiStatus] = useState('Connected');
const [autoSave, setAutoSave] = useState(true);
const messagesEndRef = useRef(null);
const [userId] = useState('User-' + Math.random().toString(36).substr(2, 8));
const models = [
{ name: "GPT-3.5 Turbo", id: "openai/gpt-3.5-turbo" },
{ name: "LLaMA 3.1 8B", id: "meta-llama/llama-3.1-8b-instruct" },
{ name: "LLaMA 3.1 70B", id: "meta-llama/llama-3.1-70b-instruct" },
{ name: "DeepSeek Chat v3", id: "deepseek/deepseek-chat-v3-0324:free" },
{ name: "DeepSeek R1", id: "deepseek/deepseek-r1-0528:free" },
{ name: "Qwen3 Coder", id: "qwen/qwen3-coder:free" },
{ name: "Microsoft MAI DS R1", id: "microsoft/mai-ds-r1:free" },
{ name: "Gemma 3 27B", id: "google/gemma-3-27b-it:free" },
{ name: "Gemma 3 4B", id: "google/gemma-3-4b-it:free" },
{ name: "Auto (Best Available)", id: "openrouter/auto" }
];
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
useEffect(() => {
// Simulate online users update
const interval = setInterval(() => {
setOnlineUsers(Math.floor(Math.random() * 5) + 1);
}, 10000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
// Check API status on component mount
const checkAPIStatus = async () => {
try {
const OPENROUTER_API_KEY = process.env.REACT_APP_OPENROUTER_API_KEY ||
window.OPENROUTER_API_KEY ||
process.env.OPENROUTER_API_KEY;
if (!OPENROUTER_API_KEY) {
setApiStatus('No API Key');
return;
}
const response = await fetch("https://openrouter.ai/api/v1/models", {
headers: { "Authorization": `Bearer ${OPENROUTER_API_KEY}` }
});
setApiStatus(response.ok ? 'Connected' : 'Error');
} catch {
setApiStatus('Error');
}
};
checkAPIStatus();
}, []);
const generateChatTitle = (msgs) => {
if (!msgs || msgs.length === 0) return "New Chat";
const firstUserMessage = msgs.find(m => m.role === 'user');
if (!firstUserMessage) return "New Chat";
const content = firstUserMessage.content;
return content.length > 30 ? content.substring(0, 30) + "..." : content;
};
const startNewChat = () => {
if (messages.length > 0) {
const newSession = {
id: 'session-' + Date.now(),
title: generateChatTitle(messages),
messages: [...messages],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
setSessions(prev => [newSession, ...prev]);
}
setMessages([]);
setCurrentSessionId('session-' + Date.now());
};
const loadSession = (session) => {
if (messages.length > 0) {
const currentSession = {
id: currentSessionId,
title: generateChatTitle(messages),
messages: [...messages],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
setSessions(prev => {
const filtered = prev.filter(s => s.id !== currentSessionId);
return [currentSession, ...filtered];
});
}
setMessages(session.messages);
setCurrentSessionId(session.id);
};
const deleteSession = (sessionId) => {
setSessions(prev => prev.filter(s => s.id !== sessionId));
if (sessionId === currentSessionId) {
startNewChat();
}
};
// OpenRouter API integration
const getAIResponse = async (userMessage) => {
setIsLoading(true);
try {
// Get API key from environment variables (Hugging Face Spaces secrets)
const OPENROUTER_API_KEY = process.env.REACT_APP_OPENROUTER_API_KEY ||
window.OPENROUTER_API_KEY ||
process.env.OPENROUTER_API_KEY;
if (!OPENROUTER_API_KEY) {
throw new Error("No API key found. Please add OPENROUTER_API_KEY to environment variables.");
}
const url = "https://openrouter.ai/api/v1/chat/completions";
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${OPENROUTER_API_KEY}`,
"HTTP-Referer": "https://huggingface.co/spaces",
"X-Title": "Chat Flow AI Assistant"
};
// Prepare messages for API
const apiMessages = [
{ role: "system", content: "You are a helpful AI assistant. Provide clear and helpful responses." },
...messages.map(msg => ({
role: msg.role,
content: msg.content.split('\n\n---\n*Response created by:')[0] // Remove attribution from content
})),
{ role: "user", content: userMessage }
];
const data = {
model: selectedModel,
messages: apiMessages,
stream: false, // Set to false for simpler handling in React
max_tokens: 2000,
temperature: 0.7,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0
};
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
});
if (!response.ok) {
let errorDetail = "";
try {
const errorData = await response.json();
errorDetail = errorData.error?.message || `HTTP ${response.status}`;
} catch {
errorDetail = `HTTP ${response.status}: ${response.statusText}`;
}
throw new Error(`API Error: ${errorDetail}. Please try a different model or check your API key.`);
}
const result = await response.json();
const aiResponse = result.choices[0].message.content;
const selectedModelName = models.find(m => m.id === selectedModel)?.name || "AI";
setIsLoading(false);
return aiResponse + `\n\n---\n*Response created by: **${selectedModelName}***`;
} catch (error) {
setIsLoading(false);
console.error('API Error:', error);
if (error.message.includes('timeout')) {
return "Request timed out. Please try again with a shorter message or different model.";
} else if (error.message.includes('Connection')) {
return "Connection error. Please check your internet connection and try again.";
} else {
return `Error: ${error.message}`;
}
}
};
const handleSendMessage = async (e) => {
e.preventDefault();
if (!currentMessage.trim() || isLoading) return;
const userMessage = {
role: 'user',
content: currentMessage.trim(),
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, userMessage]);
const messageToSend = currentMessage.trim();
setCurrentMessage('');
try {
const aiResponse = await getAIResponse(messageToSend);
const assistantMessage = {
role: 'assistant',
content: aiResponse,
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, assistantMessage]);
} catch (error) {
const errorMessage = {
role: 'assistant',
content: 'Sorry, I encountered an error. Please try again.',
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, errorMessage]);
}
};
const clearChat = () => {
setMessages([]);
};
const downloadHistory = () => {
const dataStr = JSON.stringify(messages, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `chat_history_${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
};
const getSelectedModelName = () => {
return models.find(m => m.id === selectedModel)?.name || "GPT-3.5 Turbo";
};
return (
<div className="flex h-screen bg-gray-900 text-white">
{/* Sidebar */}
<div className="w-80 bg-gray-800 border-r border-gray-700 flex flex-col">
{/* Header */}
<div className="p-4 border-b border-gray-700">
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<Bot className="w-5 h-5" />
</div>
<h1 className="text-xl font-semibold">Chat Flow</h1>
</div>
<button
onClick={startNewChat}
className="w-full flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
<Plus className="w-4 h-4" />
New Chat
</button>
</div>
{/* Chat History */}
<div className="flex-1 overflow-y-auto p-4">
<h3 className="text-sm font-medium text-gray-400 mb-3">💬 Chat History</h3>
{sessions.length > 0 ? (
<div className="space-y-2">
{sessions.map((session) => (
<div key={session.id} className="group flex items-center gap-2">
<button
onClick={() => loadSession(session)}
className={`flex-1 text-left px-3 py-2 rounded-lg transition-colors ${
session.id === currentSessionId
? 'bg-blue-600 text-white'
: 'bg-gray-700 hover:bg-gray-600 text-gray-300'
}`}
>
<div className="text-sm font-medium truncate">{session.title}</div>
<div className="text-xs text-gray-400">
{new Date(session.updatedAt).toLocaleDateString()}
</div>
</button>
<button
onClick={() => deleteSession(session.id)}
className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))}
</div>
) : (
<p className="text-gray-500 text-sm">No previous chats yet</p>
)}
</div>
{/* Settings */}
<div className="p-4 border-t border-gray-700 space-y-4">
{/* Online Users */}
<div>
<h3 className="text-sm font-medium text-gray-400 mb-2">👥 Who's Online</h3>
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span>{onlineUsers === 1 ? 'Just you online' : `${onlineUsers} people online`}</span>
</div>
<p className="text-xs text-gray-500 mt-1">You: {userId}</p>
</div>
{/* Model Selection */}
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">AI Model</label>
<select
value={selectedModel}
onChange={(e) => setSelectedModel(e.target.value)}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{models.map((model) => (
<option key={model.id} value={model.id}>
{model.name}
</option>
))}
</select>
<p className="text-xs text-green-400 mt-1 font-mono">{selectedModel}</p>
</div>
{/* API Status */}
<div className="flex items-center gap-2 text-sm">
<div className={`w-2 h-2 rounded-full ${apiStatus === 'Connected' ? 'bg-green-500' : 'bg-red-500'}`}></div>
<span>{apiStatus === 'Connected' ? '🟢 API Connected' : '🔴 Connection Issue'}</span>
</div>
{/* Controls */}
<div className="flex gap-2">
<button
onClick={downloadHistory}
className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
title="Download History"
>
<Download className="w-4 h-4" />
</button>
<button
onClick={clearChat}
className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
title="Clear Chat"
>
<Trash2 className="w-4 h-4" />
</button>
<button
onClick={() => window.location.reload()}
className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
title="Refresh"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* Main Chat Area */}
<div className="flex-1 flex flex-col">
{/* Chat Messages */}
<div className="flex-1 overflow-y-auto">
{messages.length === 0 ? (
<div className="h-full flex items-center justify-center">
<div className="text-center max-w-md mx-auto px-4">
<div className="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6">
<Bot className="w-8 h-8 text-blue-400" />
</div>
<h2 className="text-2xl font-semibold mb-4 text-gray-100">Your personal assistant</h2>
<p className="text-gray-400 leading-relaxed">
A personal assistant streamlines your life by managing tasks, schedules,
and communications efficiently.
</p>
</div>
</div>
) : (
<div className="p-6 space-y-6 max-w-4xl mx-auto">
{messages.map((message, index) => (
<div key={index} className="flex gap-4">
<div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
{message.role === 'user' ? (
<div className="w-6 h-6 bg-blue-600 rounded-full"></div>
) : (
<Bot className="w-5 h-5 text-blue-400" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="prose prose-invert max-w-none">
{message.role === 'assistant' && message.content.includes('---\n*Response created by:') ? (
<>
<div className="whitespace-pre-wrap text-gray-100">
{message.content.split('\n\n---\n*Response created by:')[0]}
</div>
<div className="text-xs text-gray-500 mt-2 italic">
Response created by: <strong>{message.content.split('**')[1]}</strong>
</div>
</>
) : (
<div className="whitespace-pre-wrap text-gray-100">{message.content}</div>
)}
</div>
</div>
</div>
))}
{isLoading && (
<div className="flex gap-4">
<div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
<Bot className="w-5 h-5 text-blue-400" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2 text-gray-400">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{animationDelay: '0.1s'}}></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{animationDelay: '0.2s'}}></div>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
)}
</div>
{/* Message Input */}
<div className="border-t border-gray-700 p-4">
<div className="max-w-4xl mx-auto">
<div className="flex gap-3">
<div className="flex-1 relative">
<input
type="text"
value={currentMessage}
onChange={(e) => setCurrentMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage(e);
}
}}
placeholder="Chat Smarter. Chat many Brains"
className="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
disabled={isLoading}
/>
</div>
<button
onClick={handleSendMessage}
disabled={!currentMessage.trim() || isLoading}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg transition-colors flex items-center gap-2"
>
<Send className="w-4 h-4" />
Send
</button>
</div>
<div className="mt-2 text-center">
<span className="text-xs text-gray-500">Currently using: <strong>{getSelectedModelName()}</strong></span>
</div>
</div>
</div>
</div>
</div>
);
};
export default ChatFlowApp;