Files
sitemente/app/mission-control/claude-chat/page.tsx
T
horus 45af56d9cf feat(mission-control): restore MC tabs - temple, office, memory, claude, pdf-viewer, resume, resume-upload, temple-3d, demos
Also added:
- Memory API endpoints
- Briefs API endpoints
- AnveVoice stats API
- Claude spawn API
- TTS proxy
- Cleopatra voice widget
- api-auth middleware
2026-03-23 16:30:44 +01:00

264 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect, useRef } from "react";
interface Message {
role: "user" | "assistant";
content: string;
timestamp: Date;
}
export default function ClaudeChatPage() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [apiKey, setApiKey] = useState("");
const [showSettings, setShowSettings] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const savedKey = localStorage.getItem("anthropic_api_key") || "";
setApiKey(savedKey);
if (!savedKey) {
setShowSettings(true);
}
}, []);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const sendMessage = async () => {
if (!input.trim() || !apiKey) return;
const userMessage: Message = {
role: "user",
content: input,
timestamp: new Date(),
};
setMessages(prev => [...prev, userMessage]);
setInput("");
setLoading(true);
try {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
"anthropic-dangerous-direct-browser-access": "true",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
messages: [
...messages.map(m => ({ role: m.role, content: m.content })),
{ role: "user", content: input }
],
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || "API Error");
}
const data = await response.json();
const assistantMessage: Message = {
role: "assistant",
content: data.content[0].text,
timestamp: new Date(),
};
setMessages(prev => [...prev, assistantMessage]);
} catch (error: any) {
const errorMessage: Message = {
role: "assistant",
content: `❌ Error: ${error.message}`,
timestamp: new Date(),
};
setMessages(prev => [...prev, errorMessage]);
}
setLoading(false);
};
const clearChat = () => {
setMessages([]);
};
const activeTab = messages.length === 0;
return (
<div className="min-h-screen bg-slate-950 flex flex-col">
{/* Header */}
<div className="bg-slate-900 border-b border-slate-800 px-6 py-4 flex items-center justify-between flex-shrink-0">
<div>
<h1 className="text-2xl font-bold text-white flex items-center gap-2">
<span style={{ color: "#ff6154" }}></span>
Claude Code Chat
</h1>
<p className="text-slate-400 text-sm">Direct chat with Claude AI</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={clearChat}
className="bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-sm"
disabled={messages.length === 0}
>
🗑 Clear
</button>
<button
onClick={() => setShowSettings(true)}
className="bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-sm"
>
</button>
</div>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-6">
<div className="max-w-4xl mx-auto space-y-4">
{messages.length === 0 && !loading && (
<div className="text-center py-12">
<div className="text-6xl mb-4">💬</div>
<h2 className="text-xl font-bold text-white mb-2">Chat with Claude</h2>
<p className="text-slate-400 max-w-md mx-auto">
Send a message to start a conversation. Claude will respond directly.
</p>
{!apiKey && (
<button
onClick={() => setShowSettings(true)}
className="mt-4 bg-[#ff6154] hover:bg-[#ff4f3a] text-white px-6 py-3 rounded-lg font-medium"
>
Add API Key to Start
</button>
)}
</div>
)}
{messages.map((msg, i) => (
<div
key={i}
className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
>
<div
className={`max-w-[80%] rounded-2xl px-4 py-3 ${
msg.role === "user"
? "bg-[#ff6154] text-white"
: "bg-slate-800 text-slate-100"
}`}
>
<div className="text-sm whitespace-pre-wrap font-mono leading-relaxed">
{msg.content}
</div>
<div className={`text-xs mt-1 ${msg.role === "user" ? "text-red-200" : "text-slate-500"}`}>
{msg.timestamp.toLocaleTimeString()}
</div>
</div>
</div>
))}
{loading && (
<div className="flex justify-start">
<div className="bg-slate-800 rounded-2xl px-4 py-3">
<div className="flex items-center gap-2 text-slate-400">
<div className="animate-spin"></div>
<span>Claude is thinking...</span>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</div>
{/* Input */}
<div className="bg-slate-900 border-t border-slate-800 p-4 flex-shrink-0">
<div className="max-w-4xl mx-auto">
<div className="flex gap-3">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}}
placeholder={apiKey ? "Message Claude..." : "Add API key in settings to start"}
disabled={!apiKey}
className="flex-1 bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 text-white placeholder-slate-500 resize-none focus:outline-none focus:border-slate-500 disabled:opacity-50"
rows={1}
/>
<button
onClick={sendMessage}
disabled={!input.trim() || loading || !apiKey}
className="bg-[#ff6154] hover:bg-[#ff4f3a] disabled:opacity-50 disabled:cursor-not-allowed text-white px-6 py-3 rounded-xl font-medium transition-colors"
>
Send
</button>
</div>
<p className="text-slate-500 text-xs mt-2 text-center">
Press Enter to send, Shift+Enter for new line
</p>
</div>
</div>
{/* Settings Modal */}
{showSettings && (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
<div className="bg-slate-800 rounded-xl p-6 w-full max-w-md border border-slate-700">
<h3 className="text-white font-bold text-lg mb-4"> Settings</h3>
<div className="mb-4">
<label className="block text-slate-400 text-sm mb-2">Anthropic API Key</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="sk-ant-..."
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-slate-500"
/>
<p className="text-slate-500 text-xs mt-1">
Get your key from <span className="text-blue-400">console.anthropic.com</span>
</p>
</div>
<div className="bg-slate-900 rounded-lg p-3 mb-4">
<p className="text-slate-400 text-xs">
<strong>Note:</strong> This chat uses your API key directly.
Your conversations are processed by Anthropic's Claude AI.
</p>
</div>
<div className="flex gap-2 justify-end">
<button
onClick={() => setShowSettings(false)}
className="px-4 py-2 bg-slate-700 text-white rounded-lg text-sm hover:bg-slate-600"
>
Cancel
</button>
<button
onClick={() => {
localStorage.setItem("anthropic_api_key", apiKey);
setShowSettings(false);
}}
className="px-4 py-2 bg-[#ff6154] text-white rounded-lg text-sm hover:bg-[#ff4f3a]"
>
Save
</button>
</div>
</div>
</div>
)}
</div>
);
}