Files
New-folder/src/components/ChatInterface.tsx
Şahan Hasret 174f49f921 Push
2025-12-14 00:11:56 +03:00

191 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;