Implemented cryptomining, although its extremely bad optimized.

This commit is contained in:
2025-12-03 18:20:02 +01:00
commit c2d6d0b096
308 changed files with 56964 additions and 0 deletions
+456
View File
@@ -0,0 +1,456 @@
import { useState, useRef, useEffect } from 'react';
import { motion } from 'framer-motion';
import { Send, Bot, User, Loader2, Trash2, AlertTriangle, Maximize2, Minimize2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { ScrollArea } from '@/components/ui/scroll-area';
import { useSettings } from '@/contexts/SettingsContext';
import { useToast } from '@/hooks/use-toast';
import GlitchText from '@/components/GlitchText';
import MessageContent from '@/components/MessageContent';
interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: Date;
}
const STORAGE_KEY = 'ai-chat-history';
const AIChat = () => {
const [messages, setMessages] = useState<Message[]>(() => {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
const parsed = JSON.parse(stored);
return parsed.map((msg: Message) => ({
...msg,
timestamp: new Date(msg.timestamp),
}));
} catch {
return [];
}
}
return [];
});
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);
const { playSound } = useSettings();
const { toast } = useToast();
// Persist messages to localStorage
useEffect(() => {
if (messages.length > 0) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
} else {
localStorage.removeItem(STORAGE_KEY);
}
}, [messages]);
// Exit fullscreen on Escape key
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isFullscreen) {
setIsFullscreen(false);
playSound('click');
}
};
window.addEventListener('keydown', handleEscape);
return () => window.removeEventListener('keydown', handleEscape);
}, [isFullscreen, playSound]);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages]);
const sendMessage = async () => {
if (!input.trim() || isLoading) return;
const userMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: input.trim(),
timestamp: new Date(),
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
playSound('click');
try {
// Build chat history, filtering out empty messages, system notices, and ensuring proper alternation
const validMessages = messages.filter(msg => msg.content.trim() !== '' && msg.role !== 'system');
let chatHistory: { role: 'user' | 'assistant'; content: string }[] = [];
for (const msg of validMessages) {
// Avoid consecutive same-role messages by merging or skipping
if (chatHistory.length > 0 && chatHistory[chatHistory.length - 1].role === msg.role) {
// Merge consecutive same-role messages
chatHistory[chatHistory.length - 1].content += '\n' + msg.content;
} else if (msg.role === 'user' || msg.role === 'assistant') {
chatHistory.push({ role: msg.role, content: msg.content });
}
}
// Add the new user message
if (chatHistory.length > 0 && chatHistory[chatHistory.length - 1].role === 'user') {
chatHistory[chatHistory.length - 1].content += '\n' + userMessage.content;
} else {
chatHistory.push({ role: 'user', content: userMessage.content });
}
// Truncate history to stay within API character limits (max ~5000 chars to be safe)
const MAX_CHARS = 5000;
const systemPromptLength = 100; // approximate system prompt length
let totalChars = systemPromptLength;
// Keep messages from the end (most recent) until we hit the limit
const truncatedHistory: typeof chatHistory = [];
const originalLength = chatHistory.length;
for (let i = chatHistory.length - 1; i >= 0; i--) {
const msgLength = chatHistory[i].content.length;
if (totalChars + msgLength > MAX_CHARS && truncatedHistory.length > 0) {
break;
}
totalChars += msgLength;
truncatedHistory.unshift(chatHistory[i]);
}
// Check if truncation occurred and notify user
const wasTruncated = truncatedHistory.length < originalLength;
if (wasTruncated) {
const systemNotice: Message = {
id: `system-${Date.now()}`,
role: 'system',
content: '⚠ Memory limit reached. Earlier conversation context has been cleared. The AI may not remember previous topics.',
timestamp: new Date(),
};
setMessages(prev => [...prev, systemNotice]);
}
chatHistory = truncatedHistory;
const assistantMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: '',
timestamp: new Date(),
};
setMessages(prev => [...prev, assistantMessage]);
// Helper function to make API request with retry logic
const makeRequest = async (retries = 3, delay = 1000): Promise<Response> => {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch('https://text.pollinations.ai/openai', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'openai',
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,
],
stream: true,
}),
});
if (response.ok) {
return response;
}
// If it's a 500 error and we have retries left, wait and try again
if (response.status >= 500 && attempt < retries) {
console.log(`API returned ${response.status}, retrying in ${delay}ms (attempt ${attempt}/${retries})`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff
continue;
}
throw new Error(`API error: ${response.status}`);
} catch (error) {
// Network errors (failed to fetch) - retry if we have attempts left
if (attempt < retries && error instanceof TypeError) {
console.log(`Network error, retrying in ${delay}ms (attempt ${attempt}/${retries})`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2;
continue;
}
throw error;
}
}
throw new Error('Failed after all retries');
};
const response = await makeRequest();
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (reader) {
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Process SSE format
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
const content = parsed.choices?.[0]?.delta?.content || '';
if (content) {
setMessages(prev =>
prev.map(msg =>
msg.id === assistantMessage.id
? { ...msg, content: msg.content + content }
: msg
)
);
}
} catch {
// Not valid JSON, might be plain text
if (data && data !== '[DONE]') {
setMessages(prev =>
prev.map(msg =>
msg.id === assistantMessage.id
? { ...msg, content: msg.content + data }
: msg
)
);
}
}
}
}
}
// Handle any remaining buffer content
if (buffer.trim()) {
setMessages(prev =>
prev.map(msg =>
msg.id === assistantMessage.id
? { ...msg, content: msg.content + buffer }
: msg
)
);
}
}
playSound('click');
} catch (error) {
console.error('AI Chat error:', error);
toast({
title: 'Error',
description: error instanceof Error ? error.message : 'Failed to get AI response',
variant: 'destructive',
});
// Remove the empty assistant message if error occurred
setMessages(prev => prev.filter(msg => msg.content !== ''));
} finally {
setIsLoading(false);
}
};
const clearChat = () => {
setMessages([]);
playSound('click');
toast({
title: 'Chat Cleared',
description: 'Conversation history has been erased.',
});
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
const toggleFullscreen = () => {
setIsFullscreen(!isFullscreen);
playSound('click');
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className={`flex flex-col ${
isFullscreen
? 'fixed inset-0 z-50 bg-background p-4 md:p-8'
: 'h-full'
}`}
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-4">
<GlitchText
text="AI Terminal"
className="font-minecraft text-2xl md:text-3xl text-primary text-glow"
/>
<div className="flex items-center gap-2 flex-wrap">
<Button
variant="ghost"
size="sm"
onClick={toggleFullscreen}
className="text-primary hover:bg-primary/20 order-first sm:order-last"
title={isFullscreen ? 'Exit fullscreen (Esc)' : 'Fullscreen'}
>
{isFullscreen ? (
<Minimize2 className="w-4 h-4" />
) : (
<Maximize2 className="w-4 h-4" />
)}
</Button>
{messages.length > 0 && (
<>
<div className="flex items-center gap-2 px-2 py-1 border border-primary/30 rounded text-xs font-pixel">
<span className="text-muted-foreground">Memory:</span>
<span className={`${
(() => {
const totalChars = messages.filter(m => m.role !== 'system').reduce((acc, m) => acc + m.content.length, 0);
const percent = (totalChars / 5000) * 100;
return percent > 80 ? 'text-red-400' : percent > 50 ? 'text-yellow-400' : 'text-green-400';
})()
}`}>
{Math.min(100, Math.round((messages.filter(m => m.role !== 'system').reduce((acc, m) => acc + m.content.length, 0) / 5000) * 100))}%
</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={clearChat}
className="text-primary hover:bg-primary/20"
>
<Trash2 className="w-4 h-4 mr-2" />
Clear
</Button>
</>
)}
</div>
</div>
<p className="text-muted-foreground mb-4 font-pixel text-sm">
{'>'} Free AI chat powered by Pollinations.ai - no login required
</p>
<ScrollArea className="flex-1 border border-primary/30 rounded-lg p-4 mb-4 bg-background/50" ref={scrollRef}>
{messages.length === 0 ? (
<div className="h-full flex items-center justify-center text-muted-foreground">
<div className="text-center">
<Bot className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p className="font-pixel text-sm">No messages yet.</p>
<p className="font-pixel text-xs mt-2">Start a conversation with the AI.</p>
</div>
</div>
) : (
<div className="space-y-4">
{messages.map((message, index) => (
message.role === 'system' ? (
<motion.div
key={message.id}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="flex justify-center"
>
<div className="flex items-center gap-2 px-4 py-2 rounded-lg bg-yellow-500/10 border border-yellow-500/30">
<AlertTriangle className="w-4 h-4 text-yellow-500" />
<p className="font-pixel text-xs text-yellow-500">{message.content}</p>
</div>
</motion.div>
) : (
<motion.div
key={message.id}
initial={{ opacity: 0, x: message.role === 'user' ? 20 : -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05 }}
className={`flex gap-3 ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
{message.role === 'assistant' && (
<div className="w-8 h-8 rounded border border-primary/50 flex items-center justify-center bg-primary/10 flex-shrink-0">
<Bot className="w-4 h-4 text-primary" />
</div>
)}
<div
className={`max-w-[80%] p-3 rounded-lg ${
message.role === 'user'
? 'bg-primary/20 border border-primary/50'
: 'bg-secondary/50 border border-primary/30'
}`}
>
<MessageContent
content={message.content}
isLoading={isLoading && message.role === 'assistant' && message.content === ''}
/>
</div>
{message.role === 'user' && (
<div className="w-8 h-8 rounded border border-primary/50 flex items-center justify-center bg-primary/10 flex-shrink-0">
<User className="w-4 h-4 text-primary" />
</div>
)}
</motion.div>
)
))}
{isLoading && messages[messages.length - 1]?.role === 'user' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="flex gap-3 justify-start"
>
<div className="w-8 h-8 rounded border border-primary/50 flex items-center justify-center bg-primary/10">
<Bot className="w-4 h-4 text-primary" />
</div>
<div className="p-3 rounded-lg bg-secondary/50 border border-primary/30">
<Loader2 className="w-4 h-4 text-primary animate-spin" />
</div>
</motion.div>
)}
</div>
)}
</ScrollArea>
<div className="flex gap-2">
<Textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Type your message..."
disabled={isLoading}
className="resize-none font-pixel text-sm bg-background/50 border-primary/50 text-primary placeholder:text-muted-foreground focus:border-primary"
rows={2}
/>
<Button
onClick={sendMessage}
disabled={!input.trim() || isLoading}
className="px-4 bg-primary/20 border border-primary hover:bg-primary/30 text-primary"
>
{isLoading ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<Send className="w-5 h-5" />
)}
</Button>
</div>
</motion.div>
);
};
export default AIChat;