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 */}
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
{/* Main Content - Clean Single Panel */}
|
{/* Main Content - Full Height Chat Interface */}
|
||||||
<div className="flex justify-center items-center h-[calc(100vh-120px)] p-6">
|
<div className="h-[calc(100vh-120px)] p-6">
|
||||||
<div className="w-full max-w-4xl">
|
<div className="w-full h-full max-w-4xl mx-auto">
|
||||||
<MissionControl />
|
<MissionControl />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,166 +1,152 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { useLanguage } from './LanguageProvider';
|
import { useLanguage } from './LanguageProvider';
|
||||||
|
|
||||||
|
interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
type: 'user' | 'ai';
|
||||||
|
content: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export default function MissionControl() {
|
export default function MissionControl() {
|
||||||
const { language } = useLanguage();
|
const { language } = useLanguage();
|
||||||
const [question, setQuestion] = useState('');
|
const [question, setQuestion] = useState('');
|
||||||
const [isThinking, setIsThinking] = useState(false);
|
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: {
|
en: {
|
||||||
title: "AI Learning Center",
|
title: "AI Learning Chat",
|
||||||
subtitle: "Ask About Anything!",
|
subtitle: "Ask me anything!",
|
||||||
placeholder: "What would you like to explore today?",
|
placeholder: "Type your question here...",
|
||||||
launch: "🚀 Start Learning",
|
send: "Send",
|
||||||
thinking: "🤔 Thinking...",
|
thinking: "AI is thinking...",
|
||||||
examples: ["How do plants grow?", "What's gravity?", "Tell me about art!", "Why is the sky blue?"],
|
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: {
|
de: {
|
||||||
title: "KI-Lernzentrale",
|
title: "KI-Lern-Chat",
|
||||||
subtitle: "Frag nach allem!",
|
subtitle: "Frag mich alles!",
|
||||||
placeholder: "Was möchtest du heute erforschen?",
|
placeholder: "Stelle hier deine Frage...",
|
||||||
launch: "🚀 Lernen starten",
|
send: "Senden",
|
||||||
thinking: "🤔 Denke nach...",
|
thinking: "KI denkt nach...",
|
||||||
examples: ["Wie wachsen Pflanzen?", "Was ist Schwerkraft?", "Erzähl mir über Kunst!", "Warum ist der Himmel blau?"],
|
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
|
// Auto-scroll to bottom when new messages arrive
|
||||||
const askAI = async (questionText: string) => {
|
useEffect(() => {
|
||||||
if (!questionText.trim()) return;
|
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);
|
setIsThinking(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/chat', {
|
const res = await fetch('/api/chat', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ question: questionText.trim(), language }),
|
body: JSON.stringify({ question: userQuestion, language }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const aiResponse = await res.json();
|
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 {
|
} 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) {
|
} 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 {
|
} finally {
|
||||||
setIsThinking(false);
|
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) => {
|
||||||
return (
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
<div className="text-center space-y-6">
|
e.preventDefault();
|
||||||
{/* Header */}
|
askAI();
|
||||||
<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?')
|
|
||||||
}
|
}
|
||||||
</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
|
const clearChat = () => {
|
||||||
onClick={() => askAI(question)}
|
setChatMessages([]);
|
||||||
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);
|
|
||||||
setQuestion('');
|
setQuestion('');
|
||||||
}}
|
textareaRef.current?.focus();
|
||||||
style={{
|
};
|
||||||
backgroundColor: '#8B5CF6',
|
|
||||||
color: 'white',
|
return (
|
||||||
padding: '12px 24px',
|
<div className="flex flex-col h-full max-w-4xl mx-auto">
|
||||||
border: 'none',
|
{/* Header */}
|
||||||
borderRadius: '10px',
|
<div className="text-center py-6 border-b border-emerald-400/30">
|
||||||
fontSize: '16px',
|
<h2 className="text-3xl font-bold text-amber-300 mb-2 animate-pulse-rainbow">
|
||||||
cursor: 'pointer',
|
{(uiTexts as any)[language].title}
|
||||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
|
</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>
|
</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 */}
|
{/* Example questions */}
|
||||||
<div className="bg-black/20 rounded-xl p-4 border border-amber-400">
|
<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">
|
<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
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => setQuestion(example)}
|
onClick={() => setQuestion(example)}
|
||||||
style={{
|
className="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-lg text-sm transition-colors"
|
||||||
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}
|
disabled={isThinking}
|
||||||
>
|
>
|
||||||
{example}
|
{example}
|
||||||
@@ -169,5 +155,85 @@ export default function MissionControl() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user