feat: Add session management and global Claude terminal
Phase 1 - Accurate study status detection: - Add is_optimization_running() to check for active processes - Add get_accurate_study_status() with proper status logic - Status now: not_started, running, paused, completed - Add "paused" status styling (orange) to Home page Phase 2 - Global Claude terminal: - Create ClaudeTerminalContext for app-level state - Create GlobalClaudeTerminal floating component - Terminal persists across page navigation - Shows green indicator when connected - Remove inline terminal from Dashboard 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
FolderOpen
|
||||
} from 'lucide-react';
|
||||
import { useStudy } from '../context/StudyContext';
|
||||
import { useClaudeTerminal } from '../context/ClaudeTerminalContext';
|
||||
|
||||
interface ClaudeTerminalProps {
|
||||
isExpanded?: boolean;
|
||||
@@ -26,17 +27,24 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
|
||||
onClose
|
||||
}) => {
|
||||
const { selectedStudy } = useStudy();
|
||||
const { setIsConnected: setGlobalConnected } = useClaudeTerminal();
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
const xtermRef = useRef<Terminal | null>(null);
|
||||
const fitAddonRef = useRef<FitAddon | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isConnected, setIsConnectedLocal] = useState(false);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [_error, setError] = useState<string | null>(null);
|
||||
const [cliAvailable, setCliAvailable] = useState<boolean | null>(null);
|
||||
const [contextSet, setContextSet] = useState(false);
|
||||
const [settingContext, setSettingContext] = useState(false);
|
||||
|
||||
// Sync local connection state to global context
|
||||
const setIsConnected = useCallback((connected: boolean) => {
|
||||
setIsConnectedLocal(connected);
|
||||
setGlobalConnected(connected);
|
||||
}, [setGlobalConnected]);
|
||||
|
||||
// Check CLI availability
|
||||
useEffect(() => {
|
||||
fetch('/api/terminal/status')
|
||||
@@ -251,9 +259,13 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
|
||||
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN || !selectedStudy?.id) return;
|
||||
|
||||
setSettingContext(true);
|
||||
// Send context message - Claude should use CLAUDE.md and .claude/skills/ for guidance
|
||||
const contextMessage = `Context: Working on study "${selectedStudy.id}" at studies/${selectedStudy.id}/. ` +
|
||||
`Read .claude/skills/ for task protocols. Use atomizer conda env. Acknowledge briefly.`;
|
||||
// Send context message with POS bootstrap instructions and study context
|
||||
const contextMessage =
|
||||
`You are helping with Atomizer optimization. ` +
|
||||
`First read: .claude/skills/00_BOOTSTRAP.md for task routing. ` +
|
||||
`Then follow the Protocol Execution Framework. ` +
|
||||
`Study context: Working on "${selectedStudy.id}" at studies/${selectedStudy.id}/. ` +
|
||||
`Use atomizer conda env. Acknowledge briefly.`;
|
||||
wsRef.current.send(JSON.stringify({ type: 'input', data: contextMessage + '\n' }));
|
||||
|
||||
// Mark as done after Claude has had time to process
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { useClaudeTerminal } from '../context/ClaudeTerminalContext';
|
||||
import { ClaudeTerminal } from './ClaudeTerminal';
|
||||
import { Terminal } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* GlobalClaudeTerminal - A floating terminal that persists across page navigation
|
||||
*
|
||||
* This component renders at the App level and maintains the Claude Code session
|
||||
* even when the user navigates between pages. It can be minimized to a floating
|
||||
* button or expanded to a side panel.
|
||||
*/
|
||||
export const GlobalClaudeTerminal: React.FC = () => {
|
||||
const { isOpen, setIsOpen, isExpanded, setIsExpanded, isConnected } = useClaudeTerminal();
|
||||
|
||||
// Floating button when terminal is closed
|
||||
if (!isOpen) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className={`fixed bottom-6 right-6 p-4 rounded-full shadow-lg transition-all z-50 ${
|
||||
isConnected
|
||||
? 'bg-green-600 hover:bg-green-500'
|
||||
: 'bg-primary-600 hover:bg-primary-500'
|
||||
}`}
|
||||
title={isConnected ? 'Claude Terminal (Connected)' : 'Open Claude Terminal'}
|
||||
>
|
||||
<Terminal className="w-6 h-6 text-white" />
|
||||
{isConnected && (
|
||||
<span className="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full border-2 border-dark-900 animate-pulse" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Terminal panel
|
||||
return (
|
||||
<div className={`fixed z-50 transition-all duration-200 ${
|
||||
isExpanded
|
||||
? 'inset-4'
|
||||
: 'bottom-6 right-6 w-[650px] h-[500px]'
|
||||
}`}>
|
||||
<ClaudeTerminal
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={() => setIsExpanded(!isExpanded)}
|
||||
onClose={() => setIsOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user