summaryrefslogtreecommitdiff
path: root/frontend/src/components/ChatInterface.jsx
diff options
context:
space:
mode:
authorkarpathy <andrej.karpathy@gmail.com>2025-11-22 14:27:53 -0800
committerkarpathy <andrej.karpathy@gmail.com>2025-11-22 14:27:53 -0800
commiteb0eb26f4cefa4880c895ff017f312e8674f9b73 (patch)
treeea20b736519a5b4149b0356fec93447eef950e6b /frontend/src/components/ChatInterface.jsx
v0
Diffstat (limited to 'frontend/src/components/ChatInterface.jsx')
-rw-r--r--frontend/src/components/ChatInterface.jsx117
1 files changed, 117 insertions, 0 deletions
diff --git a/frontend/src/components/ChatInterface.jsx b/frontend/src/components/ChatInterface.jsx
new file mode 100644
index 0000000..951183f
--- /dev/null
+++ b/frontend/src/components/ChatInterface.jsx
@@ -0,0 +1,117 @@
+import { useState, useEffect, useRef } from 'react';
+import ReactMarkdown from 'react-markdown';
+import Stage1 from './Stage1';
+import Stage2 from './Stage2';
+import Stage3 from './Stage3';
+import './ChatInterface.css';
+
+export default function ChatInterface({
+ conversation,
+ onSendMessage,
+ isLoading,
+}) {
+ const [input, setInput] = useState('');
+ const messagesEndRef = useRef(null);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [conversation]);
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (input.trim() && !isLoading) {
+ onSendMessage(input);
+ setInput('');
+ }
+ };
+
+ const handleKeyDown = (e) => {
+ // Submit on Enter (without Shift)
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSubmit(e);
+ }
+ };
+
+ if (!conversation) {
+ return (
+ <div className="chat-interface">
+ <div className="empty-state">
+ <h2>Welcome to LLM Council</h2>
+ <p>Create a new conversation to get started</p>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div className="chat-interface">
+ <div className="messages-container">
+ {conversation.messages.length === 0 ? (
+ <div className="empty-state">
+ <h2>Start a conversation</h2>
+ <p>Ask a question to consult the LLM Council</p>
+ </div>
+ ) : (
+ conversation.messages.map((msg, index) => (
+ <div key={index} className="message-group">
+ {msg.role === 'user' ? (
+ <div className="user-message">
+ <div className="message-label">You</div>
+ <div className="message-content">
+ <div className="markdown-content">
+ <ReactMarkdown>{msg.content}</ReactMarkdown>
+ </div>
+ </div>
+ </div>
+ ) : (
+ <div className="assistant-message">
+ <div className="message-label">LLM Council</div>
+ <Stage1 responses={msg.stage1} />
+ <Stage2
+ rankings={msg.stage2}
+ labelToModel={msg.metadata?.label_to_model}
+ aggregateRankings={msg.metadata?.aggregate_rankings}
+ />
+ <Stage3 finalResponse={msg.stage3} />
+ </div>
+ )}
+ </div>
+ ))
+ )}
+
+ {isLoading && (
+ <div className="loading-indicator">
+ <div className="spinner"></div>
+ <span>Consulting the council...</span>
+ </div>
+ )}
+
+ <div ref={messagesEndRef} />
+ </div>
+
+ <form className="input-form" onSubmit={handleSubmit}>
+ <textarea
+ className="message-input"
+ placeholder="Ask your question... (Shift+Enter for new line, Enter to send)"
+ value={input}
+ onChange={(e) => setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ disabled={isLoading}
+ rows={3}
+ />
+ <button
+ type="submit"
+ className="send-button"
+ disabled={!input.trim() || isLoading}
+ >
+ Send
+ </button>
+ </form>
+ </div>
+ );
+}