orca.ai/internal/web/html.go

366 lines
12 KiB
Go

package web
const indexHTML = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>orca.agent</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1b26;
color: #a9b1d6;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
height: 100vh;
overflow: hidden;
}
.header {
background: #1a1b26;
border-bottom: 1px solid #414868;
padding: 8px 16px;
display: flex;
align-items: center;
gap: 8px;
}
.header h1 {
font-size: 16px;
color: #7aa2f7;
font-weight: 600;
}
.header .version {
font-size: 12px;
color: #565f89;
}
.container {
display: flex;
height: calc(100vh - 37px);
}
.left-panel {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.chat-box {
flex: 1;
overflow-y: auto;
padding: 16px;
border-right: 1px solid #414868;
}
.message {
margin-bottom: 16px;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.message-user .label {
color: #7dcfff;
font-weight: 600;
font-size: 13px;
margin-bottom: 4px;
}
.message-assistant .label {
color: #9ece6a;
font-weight: 600;
font-size: 13px;
margin-bottom: 4px;
}
.message-system .label {
color: #e0af68;
font-weight: 600;
font-size: 13px;
margin-bottom: 4px;
}
.message-content {
color: #c0caf5;
line-height: 1.6;
font-size: 14px;
white-space: pre-wrap;
word-break: break-word;
}
.input-box {
padding: 12px 16px;
border-right: 1px solid #414868;
border-top: 1px solid #414868;
display: flex;
gap: 8px;
}
.input-box input {
flex: 1;
background: #24283b;
border: 1px solid #414868;
border-radius: 6px;
padding: 8px 12px;
color: #c0caf5;
font-size: 14px;
outline: none;
}
.input-box input:focus {
border-color: #7aa2f7;
}
.input-box input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.input-box button {
background: #7aa2f7;
color: #1a1b26;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.input-box button:hover {
background: #bb9af7;
}
.input-box button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.right-panel {
width: 280px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
overflow-y: auto;
}
.panel-box {
background: #24283b;
border: 1px solid #414868;
border-radius: 8px;
padding: 12px;
}
.panel-box h3 {
color: #7aa2f7;
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #414868;
}
.stat-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
font-size: 13px;
}
.stat-label { color: #565f89; }
.stat-value { color: #7aa2f7; font-weight: 600; }
.agent-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
font-size: 13px;
border-bottom: 1px solid #2f3349;
}
.agent-row:last-child { border-bottom: none; }
.agent-name { color: #c0caf5; }
.agent-status {
font-size: 11px;
padding: 2px 8px;
border-radius: 4px;
font-weight: 600;
}
.status-idle { background: #2f3349; color: #565f89; }
.status-running { background: #2b3a2b; color: #9ece6a; }
.typing-indicator {
display: none;
padding: 8px 0;
color: #e0af68;
font-size: 13px;
font-style: italic;
}
.typing-indicator.active { display: block; }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #414868; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #565f89; }
</style>
</head>
<body>
<div class="header">
<h1>orca.agent</h1>
<span class="version">v0.1.0</span>
</div>
<div class="container">
<div class="left-panel">
<div class="chat-box" id="chatBox"></div>
<div class="typing-indicator" id="typingIndicator">Processing...</div>
<div class="input-box">
<input type="text" id="messageInput" placeholder="Type a message and press Enter..." autocomplete="off">
<button id="sendBtn" onclick="sendMessage()">Send</button>
</div>
</div>
<div class="right-panel">
<div class="panel-box">
<h3>Statistics</h3>
<div class="stat-row">
<span class="stat-label">Tools:</span>
<span class="stat-value" id="statTools">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Skills:</span>
<span class="stat-value" id="statSkills">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Agents:</span>
<span class="stat-value" id="statAgents">0</span>
</div>
</div>
<div class="panel-box">
<h3>Active Agents</h3>
<div id="agentsList"></div>
</div>
</div>
</div>
<script>
const chatBox = document.getElementById('chatBox');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const typingIndicator = document.getElementById('typingIndicator');
const statTools = document.getElementById('statTools');
const statSkills = document.getElementById('statSkills');
const statAgents = document.getElementById('statAgents');
const agentsList = document.getElementById('agentsList');
let currentAssistantDiv = null;
let eventSource = null;
function connectSSE() {
eventSource = new EventSource('/api/stream');
eventSource.addEventListener('connected', function(e) {
console.log('SSE connected:', e.data);
});
eventSource.onmessage = function(e) {
if (currentAssistantDiv) {
const content = currentAssistantDiv.querySelector('.message-content');
content.textContent += e.data;
chatBox.scrollTop = chatBox.scrollHeight;
}
};
eventSource.onerror = function(e) {
console.log('SSE error, reconnecting...');
setTimeout(connectSSE, 3000);
};
}
connectSSE();
function addMessage(role, content, agent) {
const div = document.createElement('div');
div.className = 'message message-' + role;
const label = document.createElement('div');
label.className = 'label';
if (role === 'user') {
label.textContent = 'You';
} else if (role === 'assistant' && agent) {
label.textContent = '[' + agent + ']';
} else if (role === 'assistant') {
label.textContent = 'Assistant';
} else {
label.textContent = 'System';
}
const text = document.createElement('div');
text.className = 'message-content';
text.textContent = content;
div.appendChild(label);
div.appendChild(text);
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
return div;
}
async function sendMessage() {
const message = messageInput.value.trim();
if (!message) return;
messageInput.value = '';
messageInput.disabled = true;
sendBtn.disabled = true;
typingIndicator.classList.add('active');
addMessage('user', message);
currentAssistantDiv = addMessage('assistant', '', '');
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: message })
});
const data = await response.json();
if (data.error) {
addMessage('system', 'Error: ' + data.error);
}
} catch (err) {
addMessage('system', 'Error: ' + err.message);
} finally {
messageInput.disabled = false;
sendBtn.disabled = false;
typingIndicator.classList.remove('active');
messageInput.focus();
currentAssistantDiv = null;
updateStats();
updateAgents();
}
}
async function updateStats() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
statTools.textContent = data.tools;
statSkills.textContent = data.skills;
statAgents.textContent = data.agents;
} catch (e) {
console.log('Failed to update stats');
}
}
async function updateAgents() {
try {
const response = await fetch('/api/agents');
const data = await response.json();
agentsList.innerHTML = '';
data.forEach(agent => {
const div = document.createElement('div');
div.className = 'agent-row';
const statusClass = agent.status === 'running' ? 'status-running' : 'status-idle';
div.innerHTML = '<span class="agent-name">' + agent.id + '</span><span class="agent-status ' + statusClass + '">' + agent.status + '</span>';
agentsList.appendChild(div);
});
} catch (e) {
console.log('Failed to update agents');
}
}
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
updateStats();
updateAgents();
setInterval(updateStats, 5000);
setInterval(updateAgents, 2000);
</script>
</body>
</html>`