import React from 'react';
import { useChatStore } from './stores/chatStore';
import { useNavigation } from './hooks/useNavigation';
import { OpenChamberLogo } from './components/OpenChamberLogo';
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
function ConnectionStatusBanner({ status, error, onRetry }: {
status: ConnectionStatus;
error?: string;
onRetry: () => void;
}) {
if (status === 'connected') return null;
// Show full-screen logo for connecting state
if (status === 'connecting') {
return (
);
}
const messages: Record = {
disconnected: 'Not connected to OpenCode API',
connecting: '',
connected: '',
error: error || 'Connection error',
};
return (
{messages[status]}
{(status === 'disconnected' || status === 'error') && (
)}
);
}
function SessionsList() {
const { sessions, currentSessionId, selectSession, createSession, isLoadingSessions } = useChatStore();
const { goToChat } = useNavigation();
const [isCreating, setIsCreating] = React.useState(false);
const handleSelectSession = async (sessionId: string) => {
await selectSession(sessionId);
goToChat();
};
const handleNewSession = async () => {
setIsCreating(true);
const sessionId = await createSession();
setIsCreating(false);
if (sessionId) {
goToChat();
}
};
const formatTime = (timestamp?: number) => {
if (!timestamp) return '';
const date = new Date(timestamp);
const now = new Date();
const diffDays = Math.floor((now.getTime() - date.getTime()) / 86400000);
if (diffDays === 0) return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
if (diffDays === 1) return 'Yesterday';
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
};
return (
Sessions
{isLoadingSessions ? (
Loading...
) : sessions.length === 0 ? (
No sessions yet
) : (
{sessions.map((session) => (
))}
)}
);
}
function ChatPanel() {
const { currentSessionId, sessions, messages, sendMessage, abortMessage, isSending, streamingSessionId } = useChatStore();
const { goToSessions } = useNavigation();
const [input, setInput] = React.useState('');
const messagesEndRef = React.useRef(null);
const currentSession = sessions.find((s) => s.id === currentSessionId);
const sessionMessages = currentSessionId ? messages.get(currentSessionId) || [] : [];
const isStreaming = streamingSessionId === currentSessionId;
React.useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [sessionMessages.length]);
const handleSend = async () => {
if (!input.trim() || isSending) return;
const text = input.trim();
setInput('');
await sendMessage(text);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
};
return (
{currentSession?.title || 'New Chat'}
{sessionMessages.length === 0 ? (
Start a conversation
) : (
{sessionMessages.map((msg, idx) => (
))}
{isStreaming && (
)}
)}
);
}
function MessageBubble({ message }: { message: { info: { role: string }; parts: Array<{ type: string; text?: string }> } }) {
const isUser = message.info.role === 'user';
const text = message.parts.filter((p) => p.type === 'text').map((p) => p.text).join('\n');
return (
);
}
export function VSCodeApp() {
const { initialize, isConnected } = useChatStore();
const { currentView } = useNavigation();
const [status, setStatus] = React.useState('connecting');
const [error, setError] = React.useState();
const connect = React.useCallback(async () => {
setStatus('connecting');
setError(undefined);
try {
await initialize();
setStatus('connected');
} catch (err) {
setStatus('error');
setError(err instanceof Error ? err.message : 'Failed to connect');
}
}, [initialize]);
React.useEffect(() => {
connect();
}, [connect]);
React.useEffect(() => {
if (isConnected) setStatus('connected');
}, [isConnected]);
// Listen for extension messages
React.useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const msg = event.data;
if (msg.type === 'connectionStatus') {
if (msg.status === 'connected') setStatus('connected');
else if (msg.status === 'error') {
setStatus('error');
setError(msg.error);
}
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
return (
{currentView === 'sessions' ? (
) : (
)}
);
}
export default VSCodeApp;