Files
SuperCharged-Claude-Code-Up…/public/claude-ide/chat-functions.js
uroma 0dd2083556 Initial commit: Obsidian Web Interface for Claude Code
- Full IDE with terminal integration using xterm.js
- Session management with local and web sessions
- HTML preview functionality
- Multi-terminal support with session picker

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 16:29:44 +00:00

500 lines
16 KiB
JavaScript
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.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================
// Chat Interface Functions
// ============================================
let chatSessionId = null;
let chatMessages = [];
let attachedSessionId = null;
// Reset all chat state
function resetChatState() {
console.log('Resetting chat state...');
chatSessionId = null;
chatMessages = [];
attachedSessionId = null;
console.log('Chat state reset complete');
}
// Load Chat View
async function loadChatView() {
console.log('[loadChatView] Loading chat view...');
// Reset state on view load to prevent stale session references
resetChatState();
// Load chat sessions
try {
console.log('[loadChatView] Fetching sessions...');
const res = await fetch('/claude/api/claude/sessions');
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
const data = await res.json();
console.log('[loadChatView] Sessions data received:', data);
const sessionsListEl = document.getElementById('chat-history-list');
if (!sessionsListEl) {
console.error('[loadChatView] chat-history-list element not found!');
return;
}
// ONLY show active sessions - no historical sessions in chat view
// Historical sessions are read-only and can't receive new messages
const activeSessions = (data.active || []).filter(s => s.status === 'running');
console.log('Active sessions (can receive messages):', activeSessions.length);
if (activeSessions.length > 0) {
sessionsListEl.innerHTML = activeSessions.map(session => {
const projectName = session.metadata && session.metadata.project ?
session.metadata.project :
session.id.substring(0, 20);
return `
<div class="chat-history-item ${session.id === attachedSessionId ? 'active' : ''}"
onclick="attachToSession('${session.id}')">
<div class="chat-history-icon">💬</div>
<div class="chat-history-content">
<div class="chat-history-title">${projectName}</div>
<div class="chat-history-meta">
<span class="chat-history-date">${new Date(session.createdAt).toLocaleDateString()}</span>
<span class="chat-history-status active">Running</span>
</div>
</div>
</div>
`;
}).join('');
} else {
sessionsListEl.innerHTML = `
<div class="chat-history-empty">
<p>No active sessions</p>
<button class="btn-primary" onclick="startNewChat()" style="margin-top: 12px;">+ Start New Chat</button>
</div>
`;
}
console.log('[loadChatView] Chat view loaded successfully');
} catch (error) {
console.error('[loadChatView] Error loading chat sessions:', error);
const sessionsListEl = document.getElementById('chat-history-list');
if (sessionsListEl) {
sessionsListEl.innerHTML = `
<div class="chat-history-empty">
<p>Error: ${error.message}</p>
<button class="btn-secondary" onclick="loadChatView()" style="margin-top: 12px;">Retry</button>
</div>
`;
}
}
}
// Start New Chat
async function startNewChat() {
// Reset all state first
resetChatState();
// Clear current chat
clearChatDisplay();
appendSystemMessage('Creating new chat session...');
// Create new session
try {
console.log('Creating new Claude Code session...');
const res = await fetch('/claude/api/claude/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
workingDir: '/home/uroma/obsidian-vault',
metadata: { type: 'chat', source: 'web-ide' }
})
});
const data = await res.json();
console.log('Session creation response:', data);
if (data.success) {
attachedSessionId = data.session.id;
chatSessionId = data.session.id;
console.log('New session created:', data.session.id);
// Update UI
document.getElementById('current-session-id').textContent = data.session.id;
document.getElementById('chat-title').textContent = 'New Chat';
// Subscribe to session via WebSocket
subscribeToSession(data.session.id);
// Reload sessions list
loadChatView();
// Show success message
appendSystemMessage('✅ New chat session started! You can now chat with Claude Code.');
} else {
console.error('Session creation failed:', data);
appendSystemMessage('❌ Failed to create session: ' + (data.error || 'Unknown error'));
}
} catch (error) {
console.error('Error starting new chat:', error);
appendSystemMessage('❌ Failed to start new chat session: ' + error.message);
}
}
// Attach to Existing Session
function attachToSession(sessionId) {
attachedSessionId = sessionId;
chatSessionId = sessionId;
// Update UI
document.getElementById('current-session-id').textContent = sessionId;
// Load session messages
loadSessionMessages(sessionId);
// Subscribe to session via WebSocket
subscribeToSession(sessionId);
// Update active state in sidebar
document.querySelectorAll('.chat-session-item').forEach(item => {
item.classList.remove('active');
if (item.getAttribute('onclick') && item.getAttribute('onclick').includes(sessionId)) {
item.classList.add('active');
}
});
appendSystemMessage('Attached to session: ' + sessionId);
}
// Subscribe to session via WebSocket
function subscribeToSession(sessionId) {
if (window.ws && window.ws.readyState === WebSocket.OPEN) {
window.ws.send(JSON.stringify({
type: 'subscribe',
sessionId: sessionId
}));
console.log('Subscribed to session:', sessionId);
}
}
// Load Session Messages
async function loadSessionMessages(sessionId) {
try {
const res = await fetch('/claude/api/claude/sessions/' + sessionId);
const data = await res.json();
if (data.session) {
clearChatDisplay();
// Add existing messages from output buffer
data.session.outputBuffer.forEach(entry => {
appendMessage('assistant', entry.content, false);
});
}
} catch (error) {
console.error('Error loading session messages:', error);
}
}
// Handle Chat Key Press
function handleChatKeypress(event) {
const input = document.getElementById('chat-input');
// Update character count
const charCount = input.value.length;
document.getElementById('char-count').textContent = charCount + ' characters';
// Send on Enter (but allow Shift+Enter for new line)
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
sendChatMessage();
}
// Auto-resize textarea
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, 150) + 'px';
}
// Send Chat Message
async function sendChatMessage() {
const input = document.getElementById('chat-input');
const message = input.value.trim();
if (!message) return;
if (!attachedSessionId) {
appendSystemMessage('Please start or attach to a session first.');
return;
}
// Add user message to chat
appendMessage('user', message);
clearInput();
// Show streaming indicator
showStreamingIndicator();
try {
// Check WebSocket state
if (!window.ws) {
console.error('WebSocket is null/undefined');
appendSystemMessage('WebSocket not initialized. Please refresh the page.');
hideStreamingIndicator();
return;
}
const state = window.ws.readyState;
const stateName = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][state] || 'UNKNOWN';
console.log('WebSocket state:', state, stateName);
if (state !== WebSocket.OPEN) {
console.error('WebSocket not in OPEN state:', stateName);
appendSystemMessage(`WebSocket not ready (state: ${stateName}). Retrying...`);
hideStreamingIndicator();
// Trigger reconnection if closed
if (state === WebSocket.CLOSED) {
console.log('WebSocket closed, triggering reconnection...');
if (typeof connectWebSocket === 'function') {
connectWebSocket();
}
}
return;
}
// Send command via WebSocket
window.ws.send(JSON.stringify({
type: 'command',
sessionId: attachedSessionId,
command: message
}));
console.log('Sent command via WebSocket:', message.substring(0, 50));
} catch (error) {
console.error('Error sending message:', error);
hideStreamingIndicator();
appendSystemMessage('Failed to send message: ' + error.message);
}
}
// Append Message to Chat
function appendMessage(role, content, scroll) {
const messagesContainer = document.getElementById('chat-messages');
// Remove welcome message if present
const welcome = messagesContainer.querySelector('.chat-welcome');
if (welcome) {
welcome.remove();
}
// Remove streaming indicator if present
const streaming = messagesContainer.querySelector('.streaming-indicator');
if (streaming) {
streaming.remove();
}
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message ' + role;
const avatar = document.createElement('div');
avatar.className = 'chat-message-avatar';
avatar.textContent = role === 'user' ? '👤' : '🤖';
const contentDiv = document.createElement('div');
contentDiv.className = 'chat-message-content';
const bubble = document.createElement('div');
bubble.className = 'chat-message-bubble';
// Format content (handle code blocks, etc.)
bubble.innerHTML = formatMessage(content);
const timestamp = document.createElement('div');
timestamp.className = 'chat-message-timestamp';
timestamp.textContent = new Date().toLocaleTimeString();
contentDiv.appendChild(bubble);
contentDiv.appendChild(timestamp);
messageDiv.appendChild(avatar);
messageDiv.appendChild(contentDiv);
messagesContainer.appendChild(messageDiv);
// Scroll to bottom
if (scroll) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Update token usage (estimated)
updateTokenUsage(content.length);
}
// Append System Message
function appendSystemMessage(text) {
const messagesContainer = document.getElementById('chat-messages');
// Remove welcome message if present
const welcome = messagesContainer.querySelector('.chat-welcome');
if (welcome) {
welcome.remove();
}
const systemDiv = document.createElement('div');
systemDiv.className = 'chat-message assistant';
systemDiv.style.opacity = '0.8';
const avatar = document.createElement('div');
avatar.className = 'chat-message-avatar';
avatar.textContent = '';
const contentDiv = document.createElement('div');
contentDiv.className = 'chat-message-content';
const bubble = document.createElement('div');
bubble.className = 'chat-message-bubble';
bubble.innerHTML = '<em>' + escapeHtml(text) + '</em>';
contentDiv.appendChild(bubble);
systemDiv.appendChild(avatar);
systemDiv.appendChild(contentDiv);
messagesContainer.appendChild(systemDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Format Message (handle code blocks, markdown, etc.)
function formatMessage(content) {
// Escape HTML first
let formatted = escapeHtml(content);
// Handle code blocks
formatted = formatted.replace(/```(\w+)?\n([\s\S]*?)```/g, function(match, lang, code) {
return '<pre><code class="language-' + (lang || 'text') + '">' + code.trim() + '</code></pre>';
});
// Handle inline code
formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>');
// Handle line breaks
formatted = formatted.replace(/\n/g, '<br>');
return formatted;
}
// Show Streaming Indicator
function showStreamingIndicator() {
const messagesContainer = document.getElementById('chat-messages');
// Remove existing streaming indicator
const existing = messagesContainer.querySelector('.streaming-indicator');
if (existing) {
existing.remove();
}
const streamingDiv = document.createElement('div');
streamingDiv.className = 'streaming-indicator';
streamingDiv.innerHTML = '<div class="streaming-dot"></div><div class="streaming-dot"></div><div class="streaming-dot"></div>';
messagesContainer.appendChild(streamingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Hide Streaming Indicator
function hideStreamingIndicator() {
const streaming = document.querySelector('.streaming-indicator');
if (streaming) {
streaming.remove();
}
}
// Clear Chat Display
function clearChatDisplay() {
const messagesContainer = document.getElementById('chat-messages');
messagesContainer.innerHTML = '';
// Reset token usage
document.getElementById('token-usage').textContent = '0 tokens used';
}
// Clear Chat
function clearChat() {
if (confirm('Clear all messages in this chat?')) {
clearChatDisplay();
// Show welcome message again
const messagesContainer = document.getElementById('chat-messages');
messagesContainer.innerHTML = `
<div class="chat-welcome">
<h2>👋 Chat Cleared</h2>
<p>Start a new conversation with Claude Code.</p>
</div>
`;
}
}
// Clear Input
function clearInput() {
const input = document.getElementById('chat-input');
input.value = '';
input.style.height = 'auto';
document.getElementById('char-count').textContent = '0 characters';
}
// Update Token Usage
function updateTokenUsage(charCount) {
// Rough estimation: ~4 characters per token
const estimatedTokens = Math.ceil(charCount / 4);
const currentUsage = parseInt(document.getElementById('token-usage').textContent) || 0;
document.getElementById('token-usage').textContent = (currentUsage + estimatedTokens) + ' tokens used';
}
// Show Attach CLI Modal
function showAttachCliModal() {
document.getElementById('modal-overlay').classList.remove('hidden');
document.getElementById('attach-cli-modal').classList.remove('hidden');
}
// Submit Attach CLI Session
async function submitAttachCliSession() {
const sessionId = document.getElementById('cli-session-id').value.trim();
if (!sessionId) {
alert('Please enter a session ID');
return;
}
attachToSession(sessionId);
closeModal();
}
// Attach File (placeholder for now)
function attachFile() {
appendSystemMessage('File attachment feature coming soon! For now, use @filename to reference files.');
}
// Show Chat Settings (placeholder)
function showChatSettings() {
appendSystemMessage('Chat settings coming soon!');
}
// Export variables to window for global access
if (typeof window !== 'undefined') {
window.attachedSessionId = attachedSessionId;
window.chatSessionId = chatSessionId;
window.chatMessages = chatMessages;
// Create a proxy to keep window vars in sync
Object.defineProperty(window, 'attachedSessionId', {
get: function() { return attachedSessionId; },
set: function(value) { attachedSessionId = value; }
});
Object.defineProperty(window, 'chatSessionId', {
get: function() { return chatSessionId; },
set: function(value) { chatSessionId = value; }
});
}