Complete redesign: Implement proper chat interface
- Replace single response layout with real chat window - Messages flow chronologically from top to bottom - User messages on right (blue), AI responses on left (gray) - Fixed input field always at bottom for continuous conversation - Auto-scroll to latest messages - Message timestamps and proper chat bubbles - Clear chat functionality and example questions - Keyboard shortcuts (Enter to send, Shift+Enter for new line) - Real-time chat experience like modern messaging apps
This commit is contained in:
@@ -23,9 +23,9 @@ export default function Home() {
|
||||
{/* Header */}
|
||||
<Header />
|
||||
|
||||
{/* Main Content - Clean Single Panel */}
|
||||
<div className="flex justify-center items-center h-[calc(100vh-120px)] p-6">
|
||||
<div className="w-full max-w-4xl">
|
||||
{/* Main Content - Full Height Chat Interface */}
|
||||
<div className="h-[calc(100vh-120px)] p-6">
|
||||
<div className="w-full h-full max-w-4xl mx-auto">
|
||||
<MissionControl />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,172 +1,238 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useLanguage } from './LanguageProvider';
|
||||
|
||||
interface ChatMessage {
|
||||
id: string;
|
||||
type: 'user' | 'ai';
|
||||
content: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export default function MissionControl() {
|
||||
const { language } = useLanguage();
|
||||
const [question, setQuestion] = useState('');
|
||||
const [isThinking, setIsThinking] = useState(false);
|
||||
const [response, setResponse] = useState<string | null>(null);
|
||||
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const messages = {
|
||||
const uiTexts = {
|
||||
en: {
|
||||
title: "AI Learning Center",
|
||||
subtitle: "Ask About Anything!",
|
||||
placeholder: "What would you like to explore today?",
|
||||
launch: "🚀 Start Learning",
|
||||
thinking: "🤔 Thinking...",
|
||||
title: "AI Learning Chat",
|
||||
subtitle: "Ask me anything!",
|
||||
placeholder: "Type your question here...",
|
||||
send: "Send",
|
||||
thinking: "AI is thinking...",
|
||||
examples: ["How do plants grow?", "What's gravity?", "Tell me about art!", "Why is the sky blue?"],
|
||||
askAnother: "Ask Another Question"
|
||||
clearChat: "Clear Chat",
|
||||
you: "You",
|
||||
ai: "AI Assistant"
|
||||
},
|
||||
de: {
|
||||
title: "KI-Lernzentrale",
|
||||
subtitle: "Frag nach allem!",
|
||||
placeholder: "Was möchtest du heute erforschen?",
|
||||
launch: "🚀 Lernen starten",
|
||||
thinking: "🤔 Denke nach...",
|
||||
title: "KI-Lern-Chat",
|
||||
subtitle: "Frag mich alles!",
|
||||
placeholder: "Stelle hier deine Frage...",
|
||||
send: "Senden",
|
||||
thinking: "KI denkt nach...",
|
||||
examples: ["Wie wachsen Pflanzen?", "Was ist Schwerkraft?", "Erzähl mir über Kunst!", "Warum ist der Himmel blau?"],
|
||||
askAnother: "Neue Frage stellen"
|
||||
clearChat: "Chat löschen",
|
||||
you: "Du",
|
||||
ai: "KI-Assistent"
|
||||
}
|
||||
};
|
||||
|
||||
// Simple function to ask AI
|
||||
const askAI = async (questionText: string) => {
|
||||
if (!questionText.trim()) return;
|
||||
// Auto-scroll to bottom when new messages arrive
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [chatMessages, isThinking]);
|
||||
|
||||
// Focus textarea when component mounts
|
||||
useEffect(() => {
|
||||
textareaRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const addMessage = (type: 'user' | 'ai', content: string) => {
|
||||
const newMessage: ChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
type,
|
||||
content,
|
||||
timestamp: new Date()
|
||||
};
|
||||
setChatMessages(prev => [...prev, newMessage]);
|
||||
};
|
||||
|
||||
const askAI = async () => {
|
||||
if (!question.trim() || isThinking) return;
|
||||
|
||||
const userQuestion = question.trim();
|
||||
|
||||
// Add user message to chat
|
||||
addMessage('user', userQuestion);
|
||||
|
||||
// Clear input and show thinking
|
||||
setQuestion('');
|
||||
setIsThinking(true);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ question: questionText.trim(), language }),
|
||||
body: JSON.stringify({ question: userQuestion, language }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const aiResponse = await res.json();
|
||||
setResponse(aiResponse.message || "Great question! Let me help you think about this...");
|
||||
addMessage('ai', aiResponse.message || "Great question! Let me help you think about this...");
|
||||
} else {
|
||||
setResponse(language === 'en' ? "Sorry, I had trouble with that. Try again!" : "Entschuldigung, da gab es ein Problem. Versuch es nochmal!");
|
||||
addMessage('ai', language === 'en' ? "Sorry, I had trouble with that. Try again!" : "Entschuldigung, da gab es ein Problem. Versuch es nochmal!");
|
||||
}
|
||||
} catch (err) {
|
||||
setResponse(language === 'en' ? "Oops! Something went wrong." : "Ups! Etwas ist schiefgelaufen.");
|
||||
addMessage('ai', language === 'en' ? "Oops! Something went wrong." : "Ups! Etwas ist schiefgelaufen.");
|
||||
} finally {
|
||||
setIsThinking(false);
|
||||
setQuestion(''); // Clear input after submitting
|
||||
// Focus back to input
|
||||
setTimeout(() => textareaRef.current?.focus(), 100);
|
||||
}
|
||||
};
|
||||
|
||||
// ALWAYS show both the input AND response (if exists)
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
askAI();
|
||||
}
|
||||
};
|
||||
|
||||
const clearChat = () => {
|
||||
setChatMessages([]);
|
||||
setQuestion('');
|
||||
textareaRef.current?.focus();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="text-center space-y-6">
|
||||
<div className="flex flex-col h-full max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="text-center py-6 border-b border-emerald-400/30">
|
||||
<h2 className="text-3xl font-bold text-amber-300 mb-2 animate-pulse-rainbow">
|
||||
{(messages as any)[language].title}
|
||||
{(uiTexts as any)[language].title}
|
||||
</h2>
|
||||
<p className="text-emerald-200 text-lg">{(messages as any)[language].subtitle}</p>
|
||||
<p className="text-emerald-200 text-lg">{(uiTexts as any)[language].subtitle}</p>
|
||||
{chatMessages.length > 0 && (
|
||||
<button
|
||||
onClick={clearChat}
|
||||
className="mt-3 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm transition-colors"
|
||||
>
|
||||
🗑️ {(uiTexts as any)[language].clearChat}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ALWAYS SHOW: Input area */}
|
||||
<div className="space-y-6">
|
||||
<div className="bg-black/40 rounded-2xl p-6 border-2 border-emerald-400 backdrop-blur-sm">
|
||||
<h3 className="text-lg font-semibold text-emerald-300 mb-4">
|
||||
{response ?
|
||||
(language === 'en' ? '🤔 Ask another question:' : '🤔 Stelle eine weitere Frage:') :
|
||||
(language === 'en' ? '🌟 What would you like to know?' : '🌟 Was möchtest du wissen?')
|
||||
}
|
||||
</h3>
|
||||
<textarea
|
||||
value={question}
|
||||
onChange={(e) => setQuestion(e.target.value)}
|
||||
placeholder={(messages as any)[language].placeholder}
|
||||
className="w-full h-32 bg-gray-900/80 text-emerald-100 rounded-xl p-4 border border-emerald-500 focus:border-emerald-300 focus:outline-none focus:ring-2 focus:ring-emerald-400/50 resize-none text-lg placeholder-emerald-600"
|
||||
disabled={isThinking}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => askAI(question)}
|
||||
disabled={!question.trim() || isThinking}
|
||||
style={{
|
||||
backgroundColor: isThinking ? '#6B7280' : '#EA580C',
|
||||
color: 'white',
|
||||
padding: '16px 32px',
|
||||
border: 'none',
|
||||
borderRadius: '12px',
|
||||
fontSize: '18px',
|
||||
fontWeight: 'bold',
|
||||
cursor: isThinking ? 'not-allowed' : 'pointer',
|
||||
display: 'block',
|
||||
margin: '0 auto',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
|
||||
}}
|
||||
>
|
||||
{isThinking ? (messages as any)[language].thinking : (messages as any)[language].launch}
|
||||
</button>
|
||||
|
||||
{/* Chat Messages Area */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 min-h-0">
|
||||
{chatMessages.length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-emerald-300 text-lg mb-6">
|
||||
{language === 'en' ? 'Start a conversation!' : 'Beginne ein Gespräch!'}
|
||||
</p>
|
||||
|
||||
{/* Example questions */}
|
||||
<div className="bg-black/20 rounded-xl p-4 border border-amber-400">
|
||||
<h3 className="text-amber-300 font-semibold mb-3">
|
||||
{language === 'en' ? 'Try asking:' : 'Frag zum Beispiel:'}
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-3 justify-center">
|
||||
{(uiTexts as any)[language].examples.map((example: string, index: number) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setQuestion(example)}
|
||||
className="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-lg text-sm transition-colors"
|
||||
disabled={isThinking}
|
||||
>
|
||||
{example}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chat Messages */}
|
||||
{chatMessages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-[80%] rounded-xl p-4 ${
|
||||
message.type === 'user'
|
||||
? 'bg-blue-600 text-white ml-auto'
|
||||
: 'bg-gray-800 text-emerald-100 border border-emerald-500'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="font-semibold text-sm">
|
||||
{message.type === 'user' ? (uiTexts as any)[language].you : (uiTexts as any)[language].ai}
|
||||
</span>
|
||||
<span className="text-xs opacity-70">
|
||||
{message.timestamp.toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
<p className="whitespace-pre-line leading-relaxed">{message.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Thinking indicator */}
|
||||
{isThinking && (
|
||||
<div className="flex justify-start">
|
||||
<div className="bg-gray-800 text-emerald-100 border border-emerald-500 rounded-xl p-4 max-w-[80%]">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="font-semibold text-sm">{(uiTexts as any)[language].ai}</span>
|
||||
</div>
|
||||
<p className="flex items-center gap-2">
|
||||
<span className="animate-pulse">🤔</span>
|
||||
{(uiTexts as any)[language].thinking}
|
||||
<span className="animate-bounce">...</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Scroll anchor */}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Show response IF it exists */}
|
||||
{response && (
|
||||
<div className="bg-black/40 rounded-2xl p-6 border-2 border-emerald-400 backdrop-blur-sm mt-6">
|
||||
<h3 className="text-2xl font-bold text-amber-300 mb-4">💡 AI Response:</h3>
|
||||
|
||||
<div className="bg-gray-900/80 rounded-xl p-4 border border-emerald-500 mb-4">
|
||||
<p className="text-emerald-100 text-lg leading-relaxed whitespace-pre-line">
|
||||
{response}
|
||||
|
||||
{/* Input Area - Always at bottom */}
|
||||
<div className="border-t border-emerald-400/30 p-4 bg-black/40 backdrop-blur-sm">
|
||||
<div className="flex gap-3 items-end">
|
||||
<div className="flex-1">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={question}
|
||||
onChange={(e) => setQuestion(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder={(uiTexts as any)[language].placeholder}
|
||||
className="w-full bg-gray-900/80 text-emerald-100 rounded-xl p-4 border border-emerald-500 focus:border-emerald-300 focus:outline-none focus:ring-2 focus:ring-emerald-400/50 resize-none text-lg placeholder-emerald-600"
|
||||
rows={2}
|
||||
disabled={isThinking}
|
||||
/>
|
||||
<p className="text-xs text-emerald-400 mt-1">
|
||||
{language === 'en' ? 'Press Enter to send, Shift+Enter for new line' : 'Enter zum Senden, Shift+Enter für neue Zeile'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-emerald-300 text-sm mb-4">
|
||||
{language === 'en' ? '↑ Ask another question above!' : '↑ Stelle eine weitere Frage oben!'}
|
||||
</p>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setResponse(null);
|
||||
setQuestion('');
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: '#8B5CF6',
|
||||
color: 'white',
|
||||
padding: '12px 24px',
|
||||
border: 'none',
|
||||
borderRadius: '10px',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
|
||||
}}
|
||||
onClick={askAI}
|
||||
disabled={!question.trim() || isThinking}
|
||||
className={`px-6 py-4 rounded-xl font-bold text-lg transition-all ${
|
||||
!question.trim() || isThinking
|
||||
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
|
||||
: 'bg-emerald-600 hover:bg-emerald-700 text-white shadow-lg hover:shadow-xl'
|
||||
}`}
|
||||
>
|
||||
{language === 'en' ? '🗑️ Clear Response' : '🗑️ Antwort löschen'}
|
||||
{isThinking ? '⏳' : '🚀'} {(uiTexts as any)[language].send}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Example questions */}
|
||||
<div className="bg-black/20 rounded-xl p-4 border border-amber-400">
|
||||
<h3 className="text-amber-300 font-semibold mb-3">Quick Learning Ideas:</h3>
|
||||
<div className="flex flex-wrap gap-3 justify-center">
|
||||
{(messages as any)[language].examples.map((example: string, index: number) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setQuestion(example)}
|
||||
style={{
|
||||
backgroundColor: '#D97706',
|
||||
color: 'white',
|
||||
padding: '8px 16px',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)'
|
||||
}}
|
||||
disabled={isThinking}
|
||||
>
|
||||
{example}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user