This commit is contained in:
Şahan Hasret
2025-12-14 00:11:56 +03:00
parent 448cc1cc17
commit 174f49f921
36 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,191 @@
import React, { useState, useRef, useEffect } from 'react';
import { Message } from '@/types';
import { streamChatResponse } from '@/services/geminiService';
const ChatInterface: React.FC = () => {
const [input, setInput] = useState('');
const [messages, setMessages] = useState<Message[]>([
{
id: 'welcome',
role: 'model',
text: "Sistem aktif. ZeroSixLab AI arayüzüne hoş geldiniz. Araştırmanız veya projeniz için nasıl yardımcı olabilirim?",
timestamp: new Date(),
}
]);
const [isLoading, setIsLoading] = useState(false);
const [isInitialLoad, setIsInitialLoad] = useState(true);
const messagesEndRef = useRef<HTMLDivElement>(null);
const messagesContainerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const scrollToBottom = () => {
// Only scroll inside the messages container, not the whole page
if (messagesContainerRef.current) {
messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
}
};
useEffect(() => {
// Skip auto-scroll on initial load
if (isInitialLoad) {
setIsInitialLoad(false);
return;
}
scrollToBottom();
}, [messages, isInitialLoad]);
// Focus input on load (without scrolling page)
useEffect(() => {
inputRef.current?.focus({ preventScroll: true });
}, []);
const handleSend = async () => {
if (!input.trim() || isLoading) return;
const userMsg: Message = {
id: Date.now().toString(),
role: 'user',
text: input,
timestamp: new Date(),
};
setMessages(prev => [...prev, userMsg]);
setInput('');
setIsLoading(true);
// Prepare history for API
const history = messages.map(m => ({
role: m.role,
parts: [{ text: m.text }]
}));
try {
const stream = await streamChatResponse(userMsg.text, history);
const modelMsgId = (Date.now() + 1).toString();
const modelMsg: Message = {
id: modelMsgId,
role: 'model',
text: '',
timestamp: new Date(),
};
setMessages(prev => [...prev, modelMsg]);
let fullText = '';
for await (const chunk of stream) {
fullText += chunk;
setMessages(prev =>
prev.map(msg => msg.id === modelMsgId ? { ...msg, text: fullText } : msg)
);
}
} catch (error) {
setMessages(prev => [...prev, {
id: Date.now().toString(),
role: 'model',
text: "Hata: Nöral bağlantı kararsız. Lütfen API anahtarını kontrol edin.",
timestamp: new Date(),
isError: true
}]);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col h-162.5 w-full max-w-5xl mx-auto glass-panel rounded-xl shadow-2xl overflow-hidden relative border border-zsl-primary/20 group">
{/* Decorative Corner Lines */}
<div className="absolute top-0 left-0 w-8 h-8 border-t-2 border-l-2 border-zsl-primary/50 rounded-tl-xl z-20"></div>
<div className="absolute top-0 right-0 w-8 h-8 border-t-2 border-r-2 border-zsl-primary/50 rounded-tr-xl z-20"></div>
<div className="absolute bottom-0 left-0 w-8 h-8 border-b-2 border-l-2 border-zsl-primary/50 rounded-bl-xl z-20"></div>
<div className="absolute bottom-0 right-0 w-8 h-8 border-b-2 border-r-2 border-zsl-primary/50 rounded-br-xl z-20"></div>
{/* HUD Header */}
<div className="bg-black/40 p-3 border-b border-zsl-primary/20 flex justify-between items-center backdrop-blur-md z-10">
<div className="flex items-center gap-3">
<div className="relative">
<div className="w-2.5 h-2.5 bg-zsl-accent rounded-full animate-pulse shadow-[0_0_10px_#FFAA00]"></div>
<div className="absolute inset-0 bg-zsl-accent rounded-full animate-ping opacity-20"></div>
</div>
<span className="font-mono text-xs text-zsl-primary tracking-widest font-bold">CANLI_BAĞLANTI // GEMINI-2.5-FLASH</span>
</div>
<div className="font-mono text-[10px] text-zsl-muted flex gap-4 uppercase tracking-wider">
<span className="hidden sm:inline">MEM: <span className="text-zsl-primary">32GB</span></span>
<span className="hidden sm:inline">NET: <span className="text-green-400">SECURE</span></span>
<span>LATENCY: <span className="text-zsl-primary">12ms</span></span>
</div>
</div>
{/* Background Effect inside Chat */}
<div className="absolute inset-0 bg-[linear-gradient(rgba(10,25,47,0.8)_2px,transparent_2px)] bg-size-[100%_4px] pointer-events-none opacity-20"></div>
{/* Messages Area */}
<div ref={messagesContainerRef} className="flex-1 overflow-y-auto p-6 space-y-6 scrollbar-thin scrollbar-thumb-zsl-card relative z-10">
{messages.map((msg) => (
<div key={msg.id} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[85%] rounded-xl p-5 relative transition-all duration-300 ${
msg.role === 'user'
? 'bg-zsl-primary/10 border border-zsl-primary/30 text-white rounded-tr-none shadow-[0_0_15px_rgba(0,212,255,0.05)]'
: 'bg-[#0f1d35] border border-slate-700/50 text-zsl-text rounded-tl-none shadow-lg'
} ${msg.isError ? 'border-red-500/50 text-red-200 bg-red-900/10' : ''}`}>
<div className="font-mono text-[10px] uppercase opacity-60 mb-2 flex justify-between gap-4 border-b border-white/5 pb-1 select-none">
<span className={`font-bold ${msg.role === 'user' ? 'text-zsl-primary' : 'text-zsl-accent'}`}>
{msg.role === 'user' ? '>> OPERATOR' : '>> ZERO_AI'}
</span>
<span>{msg.timestamp.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
</div>
<div className="whitespace-pre-wrap leading-relaxed text-sm md:text-base font-light tracking-wide">
{msg.text}
{msg.role === 'model' && msg.id === messages[messages.length - 1].id && isLoading && (
<span className="inline-block w-2 h-4 bg-zsl-accent ml-1 animate-pulse align-middle"></span>
)}
</div>
{/* Decorative side accent for model */}
{msg.role === 'model' && (
<div className="absolute left-0 top-6 w-0.5 h-8 bg-zsl-accent shadow-[0_0_10px_#FFAA00]"></div>
)}
</div>
</div>
))}
{/* Loading Indicator */}
{isLoading && messages[messages.length - 1].role === 'user' && (
<div className="flex justify-start animate-in fade-in duration-300">
<div className="bg-[#0f1d35] p-3 rounded-lg rounded-tl-none border border-slate-700/50 flex items-center gap-2">
<span className="font-mono text-xs text-zsl-accent animate-pulse">PROCESSING...</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input Area */}
<div className="p-4 bg-black/40 border-t border-zsl-primary/20 backdrop-blur relative z-10">
<div className="relative flex items-center gap-3">
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-zsl-primary font-mono text-lg animate-pulse">{'>'}</div>
<input
ref={inputRef}
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
placeholder="Komut girişi yapın..."
className="flex-1 bg-zsl-bg/50 border border-slate-700 focus:border-zsl-primary text-white pl-8 pr-5 py-4 rounded-lg font-mono text-sm outline-none transition-all placeholder-slate-600 focus:shadow-[0_0_15px_rgba(0,212,255,0.1)] focus:bg-zsl-bg/80"
/>
<button
onClick={handleSend}
disabled={isLoading || !input.trim()}
className="bg-zsl-primary hover:bg-white text-black px-6 py-4 rounded-lg font-mono text-sm font-bold uppercase transition-all hover:shadow-[0_0_20px_rgba(0,212,255,0.4)] disabled:opacity-50 disabled:cursor-not-allowed group relative overflow-hidden active:scale-95"
>
<span className="relative z-10">GÖNDER</span>
</button>
</div>
</div>
</div>
);
};
export default ChatInterface;