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:
rwiegand
2025-07-14 12:49:19 +02:00
parent f893530471
commit a67ac0a8ad
2 changed files with 190 additions and 124 deletions

View File

@@ -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>

View File

@@ -1,166 +1,152 @@
'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)
return (
<div className="text-center space-y-6">
{/* Header */}
<div className="mb-8">
<h2 className="text-3xl font-bold text-amber-300 mb-2 animate-pulse-rainbow">
{(messages as any)[language].title}
</h2>
<p className="text-emerald-200 text-lg">{(messages as any)[language].subtitle}</p>
</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?')
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
askAI();
}
</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>
</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}
</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);
const clearChat = () => {
setChatMessages([]);
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)'
}}
textareaRef.current?.focus();
};
return (
<div className="flex flex-col h-full max-w-4xl mx-auto">
{/* Header */}
<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">
{(uiTexts as any)[language].title}
</h2>
<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"
>
{language === 'en' ? '🗑️ Clear Response' : '🗑️ Antwort löschen'}
🗑 {(uiTexts as any)[language].clearChat}
</button>
</div>
)}
</div>
{/* 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">Quick Learning Ideas:</h3>
<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">
{(messages as any)[language].examples.map((example: string, index: number) => (
{(uiTexts 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)'
}}
className="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-lg text-sm transition-colors"
disabled={isThinking}
>
{example}
@@ -169,5 +155,85 @@ export default function MissionControl() {
</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>
{/* 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>
<button
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'
}`}
>
{isThinking ? '⏳' : '🚀'} {(uiTexts as any)[language].send}
</button>
</div>
</div>
</div>
);
}