Files
sitemente/app/mission-control/claude/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

284 lines
9.8 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 } from "react";
interface Tab {
id: string;
name: string;
icon: string;
color: string;
projectPath?: string;
status?: "inactive" | "starting" | "active";
sessionKey?: string;
}
const TABS_STORAGE_KEY = "mc_claude_tabs";
export default function ClaudePage() {
const [tabs, setTabs] = useState<Tab[]>([
{ id: "main", name: "Claude Code", icon: "🤖", color: "#ff6154", status: "inactive" },
]);
const [activeTab, setActiveTab] = useState("main");
const [showSettings, setShowSettings] = useState(false);
const [apiKey, setApiKey] = useState("");
const [message, setMessage] = useState("");
useEffect(() => {
const saved = localStorage.getItem(TABS_STORAGE_KEY);
if (saved) {
try {
setTabs(JSON.parse(saved));
} catch (e) {}
}
const savedKey = localStorage.getItem("anthropic_api_key") || "";
setApiKey(savedKey);
}, []);
const saveTabs = (newTabs: Tab[]) => {
localStorage.setItem(TABS_STORAGE_KEY, JSON.stringify(newTabs));
setTabs(newTabs);
};
const spawnSession = async (tabId: string) => {
const tab = tabs.find(t => t.id === tabId);
if (!tab) return;
// Update status to starting
const newTabs = tabs.map(t =>
t.id === tabId ? { ...t, status: "starting" as const } : t
);
saveTabs(newTabs);
try {
const response = await fetch("/api/claude/spawn", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tabId, tabName: tab.name, apiKey }),
});
const data = await response.json();
if (data.success) {
// Update with session info
const finalTabs = tabs.map(t =>
t.id === tabId
? { ...t, status: "active" as const, sessionKey: data.sessionKey }
: t
);
saveTabs(finalTabs);
setMessage(`Session spawned! Ask Horus to connect.`);
} else {
setMessage(`Error: ${data.error}`);
}
} catch (e) {
setMessage(`Connection failed. Try asking Horus directly.`);
}
};
const addTab = () => {
const name = prompt("Project name:");
if (!name) return;
const colors = ["#ff6154", "#3b82f6", "#22c55e", "#f59e0b", "#8b5cf6", "#ec4899"];
const newTab: Tab = {
id: `tab-${Date.now()}`,
name,
icon: "📁",
color: colors[Math.floor(Math.random() * colors.length)],
status: "inactive",
};
saveTabs([...tabs, newTab]);
setActiveTab(newTab.id);
};
const removeTab = (tabId: string) => {
if (tabs.length <= 1) return;
const newTabs = tabs.filter(t => t.id !== tabId);
saveTabs(newTabs);
if (activeTab === tabId) {
setActiveTab(newTabs[0].id);
}
};
const activeTabData = tabs.find(t => t.id === activeTab);
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">
<div>
<h1 className="text-2xl font-bold text-white">🤖 Claude Code Sessions</h1>
<p className="text-slate-400 text-sm">Spawn coding agents powered by Claude</p>
</div>
<button
onClick={() => setShowSettings(true)}
className="bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-sm"
>
Settings
</button>
</div>
{/* Tabs Bar */}
<div className="bg-slate-900/50 border-b border-slate-800 px-4 py-2 flex items-center gap-2 overflow-x-auto">
{tabs.map((tab) => (
<div
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`
flex items-center gap-2 px-4 py-2 rounded-lg cursor-pointer transition-all text-sm font-medium whitespace-nowrap
${activeTab === tab.id
? "bg-slate-800 text-white border border-slate-600"
: "bg-slate-800/50 text-slate-400 border border-transparent hover:bg-slate-800 hover:text-white"
}
`}
>
<span>{tab.icon}</span>
<span>{tab.name}</span>
{tab.status === "active" && (
<span className="w-2 h-2 rounded-full bg-green-500" title="Active" />
)}
{tab.status === "starting" && (
<span className="w-2 h-2 rounded-full bg-yellow-500 animate-pulse" title="Starting..." />
)}
{tabs.length > 1 && (
<button
onClick={(e) => { e.stopPropagation(); removeTab(tab.id); }}
className="ml-1 text-slate-500 hover:text-red-400 text-xs"
>
</button>
)}
</div>
))}
<button
onClick={addTab}
className="bg-slate-800/50 hover:bg-slate-800 text-slate-400 hover:text-white px-3 py-2 rounded-lg text-sm border border-dashed border-slate-600"
>
+ Add Project
</button>
</div>
{/* Content */}
<div className="flex-1 flex items-center justify-center p-8">
<div className="text-center max-w-lg">
{activeTabData?.status === "inactive" && (
<>
<div className="text-6xl mb-6" style={{ color: activeTabData.color }}>
{activeTabData.icon}
</div>
<h2 className="text-2xl font-bold text-white mb-2">{activeTabData.name}</h2>
<p className="text-slate-400 mb-6">
Start a Claude Code session for this project
</p>
<button
onClick={() => spawnSession(activeTab)}
className="bg-[#ff6154] hover:bg-[#ff4f3a] text-white px-8 py-4 rounded-xl font-bold text-lg transition-all transform hover:scale-105 shadow-lg"
style={{ boxShadow: "0 4px 20px rgba(255, 97, 84, 0.4)" }}
>
🚀 Start Claude Session
</button>
</>
)}
{activeTabData?.status === "starting" && (
<>
<div className="text-6xl mb-6 animate-pulse"></div>
<h2 className="text-2xl font-bold text-white mb-2">Starting Session...</h2>
<p className="text-slate-400">Connecting to Claude Code</p>
</>
)}
{activeTabData?.status === "active" && (
<>
<div className="text-6xl mb-6"></div>
<h2 className="text-2xl font-bold text-white mb-2">Session Active!</h2>
<p className="text-slate-400 mb-4">
Session ID: <code className="text-amber-400">{activeTabData.sessionKey?.slice(0, 12)}...</code>
</p>
<div className="bg-slate-800 rounded-lg p-4 text-left text-sm">
<p className="text-slate-300 mb-2">
💡 <strong>To use this session:</strong>
</p>
<p className="text-slate-400">
Ask Horus to connect to this session for coding tasks.
</p>
</div>
<button
onClick={() => spawnSession(activeTab)}
className="mt-4 bg-slate-700 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-sm"
>
Restart Session
</button>
</>
)}
{message && (
<div className="mt-6 bg-slate-800 rounded-lg p-4 text-left">
<p className="text-slate-300 text-sm">{message}</p>
</div>
)}
</div>
</div>
{/* Instructions */}
<div className="bg-slate-900/50 border-t border-slate-800 px-6 py-4">
<div className="flex items-center justify-between text-sm">
<div className="text-slate-500">
💡 Claude Code sessions run as separate agents. Ask Horus to delegate coding tasks to them.
</div>
<div className="flex items-center gap-2">
{tabs.filter(t => t.status === "active").length > 0 && (
<span className="text-green-400">
{tabs.filter(t => t.status === "active").length} active
</span>
)}
</div>
</div>
</div>
{/* Settings Modal */}
{showSettings && (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50">
<div className="bg-slate-800 rounded-xl p-6 w-96 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"
/>
<p className="text-slate-500 text-xs mt-1">
Get key from console.anthropic.com
</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-blue-600 text-white rounded-lg text-sm hover:bg-blue-500"
>
Save
</button>
</div>
</div>
</div>
)}
</div>
);
}