Initial commit

This commit is contained in:
Jordan
2026-04-01 23:16:45 +01:00
commit bc4199aed2
201 changed files with 25612 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
/**
* Chat — message input + conversation view + streaming
*/
import { sendMessage } from '../app.js';
let messagesEl;
let inputEl;
let sendBtn;
let streamBtn;
let indicatorEl;
let streamingBubble = null;
// Minimal markdown renderer
function renderMarkdown(text) {
let html = text
// Code blocks
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
// Inline code
.replace(/`([^`]+)`/g, '<code>$1</code>')
// Headers
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
// Bold
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
// Lists
.replace(/^\- (.+)$/gm, '<li>$1</li>')
.replace(/^\* (.+)$/gm, '<li>$1</li>')
// Paragraphs (double newlines)
.replace(/\n\n/g, '</p><p>')
// Single newlines
.replace(/\n/g, '<br>');
// Wrap loose <li> in <ul>
html = html.replace(/(<li>.*?<\/li>)/gs, '<ul>$1</ul>');
html = html.replace(/<\/ul>\s*<ul>/g, '');
return `<p>${html}</p>`;
}
export function initChat() {
const main = document.getElementById('main');
main.innerHTML = `
<div class="chat-messages" id="chat-messages">
<div class="chat-empty">
<div class="icon">&#9881;</div>
<p>Select or create a session to start</p>
</div>
</div>
<div class="event-log-panel" id="event-log-panel"></div>
<div class="chat-input-area">
<div class="chat-input-wrapper">
<textarea id="chat-input" placeholder="Send a message..." rows="1" disabled></textarea>
<button class="btn btn-primary" id="btn-send" disabled>Send</button>
<button class="btn" id="btn-stream" disabled title="Send with SSE streaming">Stream</button>
</div>
</div>
`;
messagesEl = document.getElementById('chat-messages');
inputEl = document.getElementById('chat-input');
sendBtn = document.getElementById('btn-send');
streamBtn = document.getElementById('btn-stream');
// Auto-resize textarea
inputEl.addEventListener('input', () => {
inputEl.style.height = 'auto';
inputEl.style.height = Math.min(inputEl.scrollHeight, 160) + 'px';
});
// Send
sendBtn.addEventListener('click', () => doSend(false));
streamBtn.addEventListener('click', () => doSend(true));
// Keyboard shortcuts
inputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
doSend(false);
}
if (e.key === 'Enter' && e.ctrlKey) {
e.preventDefault();
doSend(true);
}
});
}
function doSend(stream) {
const text = inputEl.value.trim();
if (!text) return;
inputEl.value = '';
inputEl.style.height = 'auto';
sendMessage(text, stream);
}
export function addMessage(role, content) {
// Remove empty state
const empty = messagesEl.querySelector('.chat-empty');
if (empty) empty.remove();
// Remove streaming bubble if finalizing
if (role === 'assistant' && streamingBubble) {
streamingBubble.remove();
streamingBubble = null;
}
const div = document.createElement('div');
div.className = `message ${role}`;
if (role === 'assistant') {
div.innerHTML = renderMarkdown(content);
} else if (role === 'system') {
div.textContent = content;
} else {
div.textContent = content;
}
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
export function setStreamingContent(content, agent) {
// Remove empty state
const empty = messagesEl.querySelector('.chat-empty');
if (empty) empty.remove();
if (!streamingBubble) {
streamingBubble = document.createElement('div');
streamingBubble.className = 'message assistant';
messagesEl.appendChild(streamingBubble);
}
let badgeHtml = '';
if (agent) {
badgeHtml = `<span class="agent-badge role-badge ${agent}">${agent}</span><br>`;
}
streamingBubble.innerHTML = badgeHtml + renderMarkdown(content);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
export function clearChat() {
if (!messagesEl) return;
streamingBubble = null;
messagesEl.innerHTML = `
<div class="chat-empty">
<div class="icon">&#9881;</div>
<p>Select or create a session to start</p>
</div>
`;
}
export function setInputEnabled(enabled) {
if (!inputEl) return;
inputEl.disabled = !enabled;
sendBtn.disabled = !enabled;
streamBtn.disabled = !enabled;
if (enabled) inputEl.focus();
}
export function showExecutionIndicator(text) {
hideExecutionIndicator();
const empty = messagesEl.querySelector('.chat-empty');
if (empty) empty.remove();
indicatorEl = document.createElement('div');
indicatorEl.className = 'execution-indicator';
indicatorEl.innerHTML = `<div class="spinner"></div><span>${text || 'Executing...'}</span>`;
messagesEl.appendChild(indicatorEl);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
export function hideExecutionIndicator() {
if (indicatorEl) {
indicatorEl.remove();
indicatorEl = null;
}
}