mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
style: adopt agent-market design language for web UI (#282)
* fix: move Logs to status bar and fix chat history ordering after restart
Move the Logs tab out of the main tab bar and into the right-side status
area as a compact pill button next to "Connected". Remove it from the
Ctrl+1-N shortcut order (now Ctrl+1-5).
Fix chat message ordering in libSQL backend: datetime('now') has only
second precision, so back-to-back user+assistant inserts got identical
timestamps causing non-deterministic ORDER BY. Now passes explicit
millisecond-precision timestamps and uses rowid as tiebreaker for
existing data.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: separate WASM extensions from MCP servers on Extensions page
Reorganize the Extensions tab into 5 distinct sections: Installed
Extensions, Available WASM Extensions, Install WASM Extension (by
tar.gz URL), MCP Servers (with Add Custom form), and Registered Tools.
Registry entries are now filtered client-side by kind so WASM tools/
channels and MCP servers each have dedicated UI sections.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: adopt agent-market design language for web UI
Refresh the web gateway visual identity with a cleaner, modern aesthetic:
deeper blacks, green accent palette, DM Sans + IBM Plex Mono typography,
larger border-radii, glassmorphic navigation, refined hover effects,
pill badges, and green focus rings. CSS-only change plus Google Fonts.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Update src/channels/web/static/style.css
Co-authored-by: Copilot <[email protected]>
* Update src/channels/web/static/style.css
Co-authored-by: Copilot <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Copilot <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
Copilot
parent
c038c7705b
commit
250551799b
+217
-15
@@ -1204,15 +1204,18 @@ function loadServerLogLevel() {
|
||||
|
||||
function loadExtensions() {
|
||||
const extList = document.getElementById('extensions-list');
|
||||
const wasmList = document.getElementById('available-wasm-list');
|
||||
const mcpList = document.getElementById('mcp-servers-list');
|
||||
const toolsTbody = document.getElementById('tools-tbody');
|
||||
const toolsEmpty = document.getElementById('tools-empty');
|
||||
|
||||
// Fetch both in parallel
|
||||
// Fetch all three in parallel
|
||||
Promise.all([
|
||||
apiFetch('/api/extensions').catch(() => ({ extensions: [] })),
|
||||
apiFetch('/api/extensions/tools').catch(() => ({ tools: [] })),
|
||||
]).then(([extData, toolData]) => {
|
||||
// Render extensions
|
||||
apiFetch('/api/extensions/registry').catch(function(err) { console.warn('registry fetch failed:', err); return { entries: [] }; }),
|
||||
]).then(([extData, toolData, registryData]) => {
|
||||
// Render installed extensions
|
||||
if (extData.extensions.length === 0) {
|
||||
extList.innerHTML = '<div class="empty-state">No extensions installed</div>';
|
||||
} else {
|
||||
@@ -1222,6 +1225,31 @@ function loadExtensions() {
|
||||
}
|
||||
}
|
||||
|
||||
// Split registry entries by kind
|
||||
var wasmEntries = registryData.entries.filter(function(e) { return e.kind !== 'mcp_server' && !e.installed; });
|
||||
var mcpEntries = registryData.entries.filter(function(e) { return e.kind === 'mcp_server'; });
|
||||
|
||||
// Available WASM extensions
|
||||
if (wasmEntries.length === 0) {
|
||||
wasmList.innerHTML = '<div class="empty-state">No additional WASM extensions available</div>';
|
||||
} else {
|
||||
wasmList.innerHTML = '';
|
||||
for (const entry of wasmEntries) {
|
||||
wasmList.appendChild(renderAvailableExtensionCard(entry));
|
||||
}
|
||||
}
|
||||
|
||||
// MCP servers (show both installed and uninstalled)
|
||||
if (mcpEntries.length === 0) {
|
||||
mcpList.innerHTML = '<div class="empty-state">No MCP servers available</div>';
|
||||
} else {
|
||||
mcpList.innerHTML = '';
|
||||
for (const entry of mcpEntries) {
|
||||
var installedExt = extData.extensions.find(function(e) { return e.name === entry.name; });
|
||||
mcpList.appendChild(renderMcpServerCard(entry, installedExt));
|
||||
}
|
||||
}
|
||||
|
||||
// Render tools
|
||||
if (toolData.tools.length === 0) {
|
||||
toolsTbody.innerHTML = '';
|
||||
@@ -1235,6 +1263,148 @@ function loadExtensions() {
|
||||
});
|
||||
}
|
||||
|
||||
function renderAvailableExtensionCard(entry) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'ext-card ext-available';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'ext-header';
|
||||
|
||||
const name = document.createElement('span');
|
||||
name.className = 'ext-name';
|
||||
name.textContent = entry.display_name;
|
||||
header.appendChild(name);
|
||||
|
||||
const kind = document.createElement('span');
|
||||
kind.className = 'ext-kind kind-' + entry.kind;
|
||||
kind.textContent = entry.kind;
|
||||
header.appendChild(kind);
|
||||
|
||||
card.appendChild(header);
|
||||
|
||||
const desc = document.createElement('div');
|
||||
desc.className = 'ext-desc';
|
||||
desc.textContent = entry.description;
|
||||
card.appendChild(desc);
|
||||
|
||||
if (entry.keywords && entry.keywords.length > 0) {
|
||||
const kw = document.createElement('div');
|
||||
kw.className = 'ext-keywords';
|
||||
kw.textContent = entry.keywords.join(', ');
|
||||
card.appendChild(kw);
|
||||
}
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'ext-actions';
|
||||
|
||||
const installBtn = document.createElement('button');
|
||||
installBtn.className = 'btn-ext install';
|
||||
installBtn.textContent = 'Install';
|
||||
installBtn.addEventListener('click', function() {
|
||||
installBtn.disabled = true;
|
||||
installBtn.textContent = 'Installing...';
|
||||
apiFetch('/api/extensions/install', {
|
||||
method: 'POST',
|
||||
body: { name: entry.name, kind: entry.kind },
|
||||
}).then(function(res) {
|
||||
if (res.success) {
|
||||
showToast('Installed ' + entry.display_name, 'success');
|
||||
} else {
|
||||
showToast('Install: ' + (res.message || 'unknown error'), 'error');
|
||||
}
|
||||
loadExtensions();
|
||||
}).catch(function(err) {
|
||||
showToast('Install failed: ' + err.message, 'error');
|
||||
loadExtensions();
|
||||
});
|
||||
});
|
||||
actions.appendChild(installBtn);
|
||||
|
||||
card.appendChild(actions);
|
||||
return card;
|
||||
}
|
||||
|
||||
function renderMcpServerCard(entry, installedExt) {
|
||||
var card = document.createElement('div');
|
||||
card.className = 'ext-card' + (installedExt ? '' : ' ext-available');
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.className = 'ext-header';
|
||||
|
||||
var name = document.createElement('span');
|
||||
name.className = 'ext-name';
|
||||
name.textContent = entry.display_name;
|
||||
header.appendChild(name);
|
||||
|
||||
var kind = document.createElement('span');
|
||||
kind.className = 'ext-kind kind-mcp_server';
|
||||
kind.textContent = 'mcp_server';
|
||||
header.appendChild(kind);
|
||||
|
||||
if (installedExt) {
|
||||
var authDot = document.createElement('span');
|
||||
authDot.className = 'ext-auth-dot ' + (installedExt.authenticated ? 'authed' : 'unauthed');
|
||||
authDot.title = installedExt.authenticated ? 'Authenticated' : 'Not authenticated';
|
||||
header.appendChild(authDot);
|
||||
}
|
||||
|
||||
card.appendChild(header);
|
||||
|
||||
var desc = document.createElement('div');
|
||||
desc.className = 'ext-desc';
|
||||
desc.textContent = entry.description;
|
||||
card.appendChild(desc);
|
||||
|
||||
var actions = document.createElement('div');
|
||||
actions.className = 'ext-actions';
|
||||
|
||||
if (installedExt) {
|
||||
if (!installedExt.active) {
|
||||
var activateBtn = document.createElement('button');
|
||||
activateBtn.className = 'btn-ext activate';
|
||||
activateBtn.textContent = 'Activate';
|
||||
activateBtn.addEventListener('click', function() { activateExtension(installedExt.name); });
|
||||
actions.appendChild(activateBtn);
|
||||
} else {
|
||||
var activeLabel = document.createElement('span');
|
||||
activeLabel.className = 'ext-active-label';
|
||||
activeLabel.textContent = 'Active';
|
||||
actions.appendChild(activeLabel);
|
||||
}
|
||||
var removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'btn-ext remove';
|
||||
removeBtn.textContent = 'Remove';
|
||||
removeBtn.addEventListener('click', function() { removeExtension(installedExt.name); });
|
||||
actions.appendChild(removeBtn);
|
||||
} else {
|
||||
var installBtn = document.createElement('button');
|
||||
installBtn.className = 'btn-ext install';
|
||||
installBtn.textContent = 'Install';
|
||||
installBtn.addEventListener('click', function() {
|
||||
installBtn.disabled = true;
|
||||
installBtn.textContent = 'Installing...';
|
||||
apiFetch('/api/extensions/install', {
|
||||
method: 'POST',
|
||||
body: { name: entry.name, kind: entry.kind },
|
||||
}).then(function(res) {
|
||||
if (res.success) {
|
||||
showToast('Installed ' + entry.display_name, 'success');
|
||||
} else {
|
||||
showToast('Install: ' + (res.message || 'unknown error'), 'error');
|
||||
}
|
||||
loadExtensions();
|
||||
}).catch(function(err) {
|
||||
showToast('Install failed: ' + err.message, 'error');
|
||||
loadExtensions();
|
||||
});
|
||||
});
|
||||
actions.appendChild(installBtn);
|
||||
}
|
||||
|
||||
card.appendChild(actions);
|
||||
return card;
|
||||
}
|
||||
|
||||
function renderExtensionCard(ext) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'ext-card';
|
||||
@@ -2195,32 +2365,64 @@ document.getElementById('tee-shield').addEventListener('mouseleave', function()
|
||||
|
||||
// --- Extension install ---
|
||||
|
||||
function installExtension() {
|
||||
const name = document.getElementById('ext-install-name').value.trim();
|
||||
function installWasmExtension() {
|
||||
var name = document.getElementById('wasm-install-name').value.trim();
|
||||
if (!name) {
|
||||
showToast('Extension name is required', 'error');
|
||||
return;
|
||||
}
|
||||
const url = document.getElementById('ext-install-url').value.trim();
|
||||
const kind = document.getElementById('ext-install-kind').value;
|
||||
var url = document.getElementById('wasm-install-url').value.trim();
|
||||
if (!url) {
|
||||
showToast('URL to .tar.gz bundle is required', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
apiFetch('/api/extensions/install', {
|
||||
method: 'POST',
|
||||
body: { name, url: url || undefined, kind },
|
||||
}).then((res) => {
|
||||
body: { name: name, url: url, kind: 'wasm_tool' },
|
||||
}).then(function(res) {
|
||||
if (res.success) {
|
||||
showToast('Installed ' + name, 'success');
|
||||
document.getElementById('ext-install-name').value = '';
|
||||
document.getElementById('ext-install-url').value = '';
|
||||
document.getElementById('wasm-install-name').value = '';
|
||||
document.getElementById('wasm-install-url').value = '';
|
||||
loadExtensions();
|
||||
} else {
|
||||
showToast('Install failed: ' + (res.message || 'unknown error'), 'error');
|
||||
}
|
||||
}).catch((err) => {
|
||||
}).catch(function(err) {
|
||||
showToast('Install failed: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function addMcpServer() {
|
||||
var name = document.getElementById('mcp-install-name').value.trim();
|
||||
if (!name) {
|
||||
showToast('Server name is required', 'error');
|
||||
return;
|
||||
}
|
||||
var url = document.getElementById('mcp-install-url').value.trim();
|
||||
if (!url) {
|
||||
showToast('MCP server URL is required', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
apiFetch('/api/extensions/install', {
|
||||
method: 'POST',
|
||||
body: { name: name, url: url, kind: 'mcp_server' },
|
||||
}).then(function(res) {
|
||||
if (res.success) {
|
||||
showToast('Added MCP server ' + name, 'success');
|
||||
document.getElementById('mcp-install-name').value = '';
|
||||
document.getElementById('mcp-install-url').value = '';
|
||||
loadExtensions();
|
||||
} else {
|
||||
showToast('Failed to add MCP server: ' + (res.message || 'unknown error'), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
showToast('Failed to add MCP server: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// --- Keyboard shortcuts ---
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
@@ -2228,10 +2430,10 @@ document.addEventListener('keydown', (e) => {
|
||||
const tag = (e.target.tagName || '').toLowerCase();
|
||||
const inInput = tag === 'input' || tag === 'textarea';
|
||||
|
||||
// Mod+1-6: switch tabs
|
||||
if (mod && e.key >= '1' && e.key <= '6') {
|
||||
// Mod+1-5: switch tabs
|
||||
if (mod && e.key >= '1' && e.key <= '5') {
|
||||
e.preventDefault();
|
||||
const tabs = ['chat', 'memory', 'jobs', 'routines', 'logs', 'extensions'];
|
||||
const tabs = ['chat', 'memory', 'jobs', 'routines', 'extensions'];
|
||||
const idx = parseInt(e.key) - 1;
|
||||
if (tabs[idx]) switchTab(tabs[idx]);
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IronClaw</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.min.js"
|
||||
@@ -36,10 +39,10 @@
|
||||
<button class="active" data-tab="chat">Chat</button>
|
||||
<button data-tab="memory">Memory</button>
|
||||
<button data-tab="jobs">Jobs</button>
|
||||
<button data-tab="logs">Logs</button>
|
||||
<button data-tab="routines">Routines</button>
|
||||
<button data-tab="extensions">Extensions</button>
|
||||
<div class="spacer"></div>
|
||||
<button class="status-logs-btn" data-tab="logs" title="Logs">Logs</button>
|
||||
<div class="tee-shield" id="tee-shield" style="display:none" title="Running in a Trusted Execution Environment">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
@@ -185,34 +188,42 @@
|
||||
<!-- Extensions Tab -->
|
||||
<div class="tab-panel" id="tab-extensions">
|
||||
<div class="extensions-container">
|
||||
<div class="extensions-section">
|
||||
<h3>Install Extension</h3>
|
||||
<div class="ext-install-form" id="ext-install-form">
|
||||
<input type="text" id="ext-install-name" placeholder="Extension name (required)">
|
||||
<input type="text" id="ext-install-url" placeholder="URL (optional)">
|
||||
<select id="ext-install-kind">
|
||||
<option value="mcp_server">MCP Server</option>
|
||||
<option value="wasm_tool">WASM Tool</option>
|
||||
<option value="wasm_channel">WASM Channel</option>
|
||||
</select>
|
||||
<button onclick="installExtension()">Install</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extensions-section">
|
||||
<h3>Installed Extensions</h3>
|
||||
<div class="extensions-list" id="extensions-list">
|
||||
<div class="empty-state">Loading extensions...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extensions-section" id="available-wasm-section">
|
||||
<h3>Available WASM Extensions</h3>
|
||||
<div class="extensions-list" id="available-wasm-list">
|
||||
<div class="empty-state">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extensions-section">
|
||||
<h3>Install WASM Extension</h3>
|
||||
<div class="ext-install-form">
|
||||
<input type="text" id="wasm-install-name" placeholder="Extension name">
|
||||
<input type="text" id="wasm-install-url" placeholder="URL to .tar.gz bundle">
|
||||
<button onclick="installWasmExtension()">Install</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extensions-section">
|
||||
<h3>MCP Servers</h3>
|
||||
<div class="extensions-list" id="mcp-servers-list">
|
||||
<div class="empty-state">Loading...</div>
|
||||
</div>
|
||||
<h4>Add Custom MCP Server</h4>
|
||||
<div class="ext-install-form">
|
||||
<input type="text" id="mcp-install-name" placeholder="Server name">
|
||||
<input type="text" id="mcp-install-url" placeholder="MCP server URL (https://...)">
|
||||
<button onclick="addMcpServer()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extensions-section">
|
||||
<h3>Registered Tools</h3>
|
||||
<table class="tools-table" id="tools-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<thead><tr><th>Name</th><th>Description</th></tr></thead>
|
||||
<tbody id="tools-tbody"></tbody>
|
||||
</table>
|
||||
<div class="empty-state" id="tools-empty" style="display:none">No tools registered</div>
|
||||
|
||||
+232
-113
@@ -1,20 +1,22 @@
|
||||
/* IronClaw Web Gateway */
|
||||
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--bg-secondary: #161b22;
|
||||
--bg-tertiary: #21262d;
|
||||
--border: #30363d;
|
||||
--text: #e6edf3;
|
||||
--text-secondary: #8b949e;
|
||||
--accent: #58a6ff;
|
||||
--accent-hover: #79c0ff;
|
||||
--success: #3fb950;
|
||||
--warning: #d29922;
|
||||
--danger: #f85149;
|
||||
--code-bg: #1b2028;
|
||||
--radius: 6px;
|
||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
--bg: #09090b;
|
||||
--bg-secondary: #0f0f11;
|
||||
--bg-tertiary: #1a1a1e;
|
||||
--border: rgba(255, 255, 255, 0.08);
|
||||
--text: #fafafa;
|
||||
--text-secondary: #a1a1aa;
|
||||
--accent: #34d399;
|
||||
--accent-hover: #2fc48d;
|
||||
--success: #34d399;
|
||||
--warning: #F5A623;
|
||||
--danger: #E64C4C;
|
||||
--code-bg: #111113;
|
||||
--radius: 8px;
|
||||
--radius-lg: 12px;
|
||||
--shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
--font-mono: 'IBM Plex Mono', 'SF Mono', 'Fira Code', Consolas, monospace;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -24,7 +26,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||
font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
height: 100vh;
|
||||
@@ -44,14 +46,14 @@ body {
|
||||
.auth-card-login {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
border-radius: 16px;
|
||||
padding: 40px 36px 32px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.auth-brand {
|
||||
@@ -95,22 +97,29 @@ body {
|
||||
#auth-screen input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.3);
|
||||
}
|
||||
|
||||
#auth-screen button {
|
||||
padding: 10px 16px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
margin-top: 4px;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
#auth-screen button:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#auth-screen button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
#auth-error {
|
||||
@@ -137,14 +146,17 @@ body {
|
||||
/* Tab Bar */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background: var(--bg-secondary);
|
||||
background: rgba(9, 9, 11, 0.75);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
will-change: backdrop-filter;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0 16px;
|
||||
gap: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-bar button {
|
||||
.tab-bar button:not(.status-logs-btn) {
|
||||
padding: 10px 20px;
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -152,14 +164,15 @@ body {
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.tab-bar button:hover {
|
||||
.tab-bar button:not(.status-logs-btn):hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tab-bar button.active {
|
||||
.tab-bar button:not(.status-logs-btn).active {
|
||||
color: var(--accent);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
@@ -168,6 +181,30 @@ body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tab-bar .status-logs-btn {
|
||||
padding: 4px 10px;
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
align-self: center;
|
||||
margin-right: 8px;
|
||||
transition: color 0.2s, border-color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.tab-bar .status-logs-btn:hover {
|
||||
color: var(--text);
|
||||
border-color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.tab-bar .status-logs-btn.active {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
background: rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.tab-bar .status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -198,16 +235,16 @@ body {
|
||||
color: var(--success);
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(63, 185, 80, 0.1);
|
||||
border: 1px solid rgba(63, 185, 80, 0.25);
|
||||
background: rgba(52, 211, 153, 0.1);
|
||||
border: 1px solid rgba(52, 211, 153, 0.25);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
margin-right: 8px;
|
||||
transition: background 0.15s;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.tee-shield:hover {
|
||||
background: rgba(63, 185, 80, 0.18);
|
||||
background: rgba(52, 211, 153, 0.18);
|
||||
}
|
||||
|
||||
.tee-shield svg {
|
||||
@@ -225,9 +262,11 @@ body {
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
background: var(--bg-secondary);
|
||||
background: rgba(15, 15, 17, 0.9);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 16px;
|
||||
min-width: 340px;
|
||||
max-width: 420px;
|
||||
@@ -272,12 +311,12 @@ body {
|
||||
|
||||
.tee-field-value {
|
||||
font-size: 12px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text);
|
||||
word-break: break-all;
|
||||
background: var(--bg);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
@@ -295,7 +334,7 @@ body {
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.tee-btn-copy:hover {
|
||||
@@ -350,7 +389,7 @@ body {
|
||||
.message.user {
|
||||
align-self: flex-end;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
border-bottom-right-radius: 2px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -469,11 +508,12 @@ body {
|
||||
max-width: 80%;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--warning);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.approval-header {
|
||||
@@ -488,7 +528,7 @@ body {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.approval-description {
|
||||
@@ -516,7 +556,7 @@ body {
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 12px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1.4;
|
||||
overflow-x: auto;
|
||||
color: var(--text-secondary);
|
||||
@@ -549,13 +589,15 @@ body {
|
||||
.approval-card .approval-actions button.approve {
|
||||
background: var(--success);
|
||||
border-color: var(--success);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.approval-card .approval-actions button.always {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.approval-card .approval-actions button.deny {
|
||||
@@ -577,12 +619,13 @@ body {
|
||||
max-width: 80%;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 12px 16px;
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.auth-card .auth-header {
|
||||
@@ -623,12 +666,13 @@ body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.auth-card .auth-token-input input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.auth-card .auth-actions {
|
||||
@@ -655,7 +699,8 @@ body {
|
||||
.auth-card .auth-actions button.auth-submit {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auth-card .auth-actions button.auth-cancel {
|
||||
@@ -666,7 +711,8 @@ body {
|
||||
.auth-card .auth-actions button.auth-oauth {
|
||||
background: var(--success);
|
||||
border-color: var(--success);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auth-card .auth-error {
|
||||
@@ -700,21 +746,29 @@ body {
|
||||
.chat-input textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.chat-input button {
|
||||
padding: 8px 20px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
align-self: flex-end;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.chat-input button:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.chat-input button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.chat-input button:disabled {
|
||||
@@ -755,6 +809,7 @@ body {
|
||||
.memory-sidebar input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.memory-tree {
|
||||
@@ -888,7 +943,7 @@ body {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.memory-viewer .empty {
|
||||
@@ -942,8 +997,13 @@ body {
|
||||
padding: 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-lg);
|
||||
text-align: center;
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.summary-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.summary-card .count {
|
||||
@@ -987,24 +1047,24 @@ body {
|
||||
}
|
||||
|
||||
.jobs-table tr:hover td {
|
||||
background: var(--bg-secondary);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 9999px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge.pending { background: var(--bg-tertiary); color: var(--text-secondary); }
|
||||
.badge.in_progress { background: rgba(88, 166, 255, 0.15); color: var(--accent); }
|
||||
.badge.completed { background: rgba(63, 185, 80, 0.15); color: var(--success); }
|
||||
.badge.failed { background: rgba(248, 81, 73, 0.15); color: var(--danger); }
|
||||
.badge.stuck { background: rgba(210, 153, 34, 0.15); color: var(--warning); }
|
||||
.badge.in_progress { background: rgba(52, 211, 153, 0.15); color: var(--accent); }
|
||||
.badge.completed { background: rgba(52, 211, 153, 0.15); color: var(--success); }
|
||||
.badge.failed { background: rgba(230, 76, 76, 0.15); color: var(--danger); }
|
||||
.badge.stuck { background: rgba(245, 166, 35, 0.15); color: var(--warning); }
|
||||
.badge.cancelled { background: var(--bg-tertiary); color: var(--text-secondary); }
|
||||
.badge.interrupted { background: rgba(210, 153, 34, 0.15); color: var(--warning); }
|
||||
.badge.interrupted { background: rgba(245, 166, 35, 0.15); color: var(--warning); }
|
||||
.badge.source-sandbox { background: rgba(136, 132, 216, 0.15); color: #b4b0e8; }
|
||||
.badge.source-direct { background: var(--bg-tertiary); color: var(--text-secondary); }
|
||||
|
||||
@@ -1019,7 +1079,7 @@ body {
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: rgba(248, 81, 73, 0.15);
|
||||
background: rgba(230, 76, 76, 0.15);
|
||||
}
|
||||
|
||||
.btn-restart {
|
||||
@@ -1033,7 +1093,7 @@ body {
|
||||
}
|
||||
|
||||
.btn-restart:hover {
|
||||
background: rgba(88, 166, 255, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
.btn-browse {
|
||||
@@ -1048,7 +1108,7 @@ body {
|
||||
}
|
||||
|
||||
.btn-browse:hover {
|
||||
background: rgba(63, 185, 80, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
/* Job started card in chat */
|
||||
@@ -1060,8 +1120,13 @@ body {
|
||||
margin: 8px 0;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-lg);
|
||||
border-left: 3px solid var(--accent);
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.job-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.job-card-icon {
|
||||
@@ -1080,7 +1145,7 @@ body {
|
||||
.job-card-id {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
font-family: monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.job-card-view, .job-card-browse {
|
||||
@@ -1098,7 +1163,7 @@ body {
|
||||
}
|
||||
|
||||
.job-card-view:hover {
|
||||
background: rgba(88, 166, 255, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
.job-card-browse {
|
||||
@@ -1108,7 +1173,7 @@ body {
|
||||
}
|
||||
|
||||
.job-card-browse:hover {
|
||||
background: rgba(63, 185, 80, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
/* Clickable job rows */
|
||||
@@ -1314,7 +1379,7 @@ body {
|
||||
.action-tool {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.action-seq {
|
||||
@@ -1359,7 +1424,7 @@ body {
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 12px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1.4;
|
||||
overflow-x: auto;
|
||||
color: var(--text-secondary);
|
||||
@@ -1371,11 +1436,11 @@ body {
|
||||
}
|
||||
|
||||
.action-error {
|
||||
background: rgba(248, 81, 73, 0.1);
|
||||
background: rgba(230, 76, 76, 0.1);
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 12px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1.4;
|
||||
color: var(--danger);
|
||||
margin: 0;
|
||||
@@ -1413,8 +1478,8 @@ body {
|
||||
.conv-system .conv-body { color: var(--text-secondary); font-size: 13px; }
|
||||
|
||||
.conv-user {
|
||||
background: rgba(88, 166, 255, 0.08);
|
||||
border: 1px solid rgba(88, 166, 255, 0.2);
|
||||
background: rgba(52, 211, 153, 0.08);
|
||||
border: 1px solid rgba(52, 211, 153, 0.2);
|
||||
}
|
||||
|
||||
.conv-user .conv-role { color: var(--accent); }
|
||||
@@ -1429,7 +1494,7 @@ body {
|
||||
.conv-tool {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -1440,7 +1505,7 @@ body {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 4px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.conv-tool-calls {
|
||||
@@ -1457,7 +1522,7 @@ body {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.conv-tc-args {
|
||||
@@ -1465,7 +1530,7 @@ body {
|
||||
padding: 6px 10px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 11px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1.4;
|
||||
margin: 4px 0 0;
|
||||
color: var(--text-secondary);
|
||||
@@ -1505,12 +1570,12 @@ body {
|
||||
font-size: 12px;
|
||||
color: var(--accent);
|
||||
margin-bottom: 8px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.job-files-content {
|
||||
font-size: 13px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
@@ -1560,7 +1625,7 @@ body {
|
||||
}
|
||||
|
||||
.routines-table tr:hover td {
|
||||
background: var(--bg-secondary);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.routine-row {
|
||||
@@ -1571,9 +1636,9 @@ body {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.badge.enabled { background: rgba(63, 185, 80, 0.15); color: var(--success); }
|
||||
.badge.enabled { background: rgba(52, 211, 153, 0.15); color: var(--success); }
|
||||
.badge.disabled { background: var(--bg-tertiary); color: var(--text-secondary); }
|
||||
.badge.failing { background: rgba(248, 81, 73, 0.15); color: var(--danger); }
|
||||
.badge.failing { background: rgba(230, 76, 76, 0.15); color: var(--danger); }
|
||||
|
||||
.btn-trigger {
|
||||
padding: 4px 10px;
|
||||
@@ -1586,7 +1651,7 @@ body {
|
||||
}
|
||||
|
||||
.btn-trigger:hover {
|
||||
background: rgba(88, 166, 255, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
.btn-toggle {
|
||||
@@ -1600,7 +1665,7 @@ body {
|
||||
}
|
||||
|
||||
.btn-toggle:hover {
|
||||
background: rgba(210, 153, 34, 0.15);
|
||||
background: rgba(245, 166, 35, 0.15);
|
||||
}
|
||||
|
||||
/* Logs Tab */
|
||||
@@ -1644,6 +1709,7 @@ body {
|
||||
.logs-toolbar input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.logs-checkbox {
|
||||
@@ -1673,7 +1739,7 @@ body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
background: var(--bg);
|
||||
@@ -1758,6 +1824,13 @@ body {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.extensions-section h4 {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin: 16px 0 8px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.extensions-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
@@ -1767,11 +1840,16 @@ body {
|
||||
.ext-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.ext-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.ext-header {
|
||||
@@ -1796,17 +1874,17 @@ body {
|
||||
}
|
||||
|
||||
.ext-kind.kind-mcp_server {
|
||||
background: rgba(88, 166, 255, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.ext-kind.kind-wasm_tool {
|
||||
background: rgba(63, 185, 80, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.ext-kind.kind-wasm_channel {
|
||||
background: rgba(210, 153, 34, 0.15);
|
||||
background: rgba(245, 166, 35, 0.15);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
@@ -1834,7 +1912,7 @@ body {
|
||||
.ext-url {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -1878,7 +1956,7 @@ body {
|
||||
}
|
||||
|
||||
.btn-ext.activate:hover {
|
||||
background: rgba(88, 166, 255, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
.btn-ext.remove {
|
||||
@@ -1887,7 +1965,31 @@ body {
|
||||
}
|
||||
|
||||
.btn-ext.remove:hover {
|
||||
background: rgba(248, 81, 73, 0.15);
|
||||
background: rgba(230, 76, 76, 0.15);
|
||||
}
|
||||
|
||||
.btn-ext.install {
|
||||
border-color: var(--success);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.btn-ext.install:hover {
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
.btn-ext.install:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ext-available {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.ext-keywords {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.tools-table {
|
||||
@@ -1912,7 +2014,7 @@ body {
|
||||
}
|
||||
|
||||
.tools-table tr:hover td {
|
||||
background: var(--bg-secondary);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* --- Activity tab (unified sandbox job events) --- */
|
||||
@@ -1921,12 +2023,12 @@ body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
font-family: monospace;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 8px;
|
||||
max-height: calc(100vh - 320px);
|
||||
}
|
||||
@@ -2020,7 +2122,7 @@ body {
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -2028,20 +2130,28 @@ body {
|
||||
.activity-input-bar input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.activity-input-bar button {
|
||||
padding: 8px 16px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.activity-input-bar button:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.activity-input-bar button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
#activity-done-btn {
|
||||
@@ -2131,7 +2241,7 @@ body {
|
||||
/* --- Memory search highlighting --- */
|
||||
|
||||
mark {
|
||||
background: rgba(88, 166, 255, 0.3);
|
||||
background: rgba(52, 211, 153, 0.3);
|
||||
color: inherit;
|
||||
border-radius: 2px;
|
||||
padding: 0 1px;
|
||||
@@ -2197,7 +2307,7 @@ mark {
|
||||
}
|
||||
|
||||
.thread-new-btn:hover {
|
||||
background: rgba(88, 166, 255, 0.15);
|
||||
background: rgba(52, 211, 153, 0.15);
|
||||
}
|
||||
|
||||
.assistant-item {
|
||||
@@ -2218,7 +2328,7 @@ mark {
|
||||
}
|
||||
|
||||
.assistant-item.active {
|
||||
background: rgba(88, 166, 255, 0.08);
|
||||
background: rgba(52, 211, 153, 0.08);
|
||||
color: var(--accent);
|
||||
border-left: 2px solid var(--accent);
|
||||
}
|
||||
@@ -2285,7 +2395,7 @@ mark {
|
||||
}
|
||||
|
||||
.thread-label {
|
||||
font-family: monospace;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -2332,7 +2442,7 @@ mark {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text);
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
resize: none;
|
||||
@@ -2341,6 +2451,7 @@ mark {
|
||||
.memory-editor textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.memory-editor-actions {
|
||||
@@ -2351,15 +2462,22 @@ mark {
|
||||
.btn-save {
|
||||
padding: 6px 16px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-save:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.btn-cancel-edit {
|
||||
@@ -2425,9 +2543,11 @@ mark {
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
background: var(--bg-secondary);
|
||||
background: rgba(15, 15, 17, 0.9);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 12px;
|
||||
min-width: 180px;
|
||||
box-shadow: var(--shadow);
|
||||
@@ -2472,29 +2592,28 @@ mark {
|
||||
.ext-install-form input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.ext-install-form select {
|
||||
padding: 6px 10px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
.ext-install-form button {
|
||||
padding: 6px 16px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: #09090b;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.ext-install-form button:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.ext-install-form button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* --- Activity toolbar --- */
|
||||
@@ -2518,6 +2637,7 @@ mark {
|
||||
.activity-toolbar select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
|
||||
}
|
||||
|
||||
/* --- Mobile responsive --- */
|
||||
@@ -2530,7 +2650,7 @@ mark {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.tab-bar button {
|
||||
.tab-bar button:not(.status-logs-btn) {
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
@@ -2616,8 +2736,7 @@ mark {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.ext-install-form input,
|
||||
.ext-install-form select {
|
||||
.ext-install-form input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,10 @@ impl ConversationStore for LibSqlBackend {
|
||||
) -> Result<Uuid, DatabaseError> {
|
||||
let conn = self.connect().await?;
|
||||
let id = Uuid::new_v4();
|
||||
let now = fmt_ts(&Utc::now());
|
||||
conn.execute(
|
||||
"INSERT INTO conversation_messages (id, conversation_id, role, content) VALUES (?1, ?2, ?3, ?4)",
|
||||
params![id.to_string(), conversation_id.to_string(), role, content],
|
||||
"INSERT INTO conversation_messages (id, conversation_id, role, content, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
params![id.to_string(), conversation_id.to_string(), role, content, now],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| DatabaseError::Query(e.to_string()))?;
|
||||
@@ -100,7 +101,7 @@ impl ConversationStore for LibSqlBackend {
|
||||
(SELECT substr(m2.content, 1, 100)
|
||||
FROM conversation_messages m2
|
||||
WHERE m2.conversation_id = c.id AND m2.role = 'user'
|
||||
ORDER BY m2.created_at ASC
|
||||
ORDER BY m2.created_at ASC, m2.rowid ASC
|
||||
LIMIT 1
|
||||
) AS title
|
||||
FROM conversations c
|
||||
@@ -216,7 +217,7 @@ impl ConversationStore for LibSqlBackend {
|
||||
SELECT id, role, content, created_at
|
||||
FROM conversation_messages
|
||||
WHERE conversation_id = ?1 AND created_at < ?2
|
||||
ORDER BY created_at DESC
|
||||
ORDER BY created_at DESC, rowid DESC
|
||||
LIMIT ?3
|
||||
"#,
|
||||
params![cid, fmt_ts(&before_ts), fetch_limit],
|
||||
@@ -228,7 +229,7 @@ impl ConversationStore for LibSqlBackend {
|
||||
SELECT id, role, content, created_at
|
||||
FROM conversation_messages
|
||||
WHERE conversation_id = ?1
|
||||
ORDER BY created_at DESC
|
||||
ORDER BY created_at DESC, rowid DESC
|
||||
LIMIT ?2
|
||||
"#,
|
||||
params![cid, fetch_limit],
|
||||
@@ -309,7 +310,7 @@ impl ConversationStore for LibSqlBackend {
|
||||
SELECT id, role, content, created_at
|
||||
FROM conversation_messages
|
||||
WHERE conversation_id = ?1
|
||||
ORDER BY created_at ASC
|
||||
ORDER BY created_at ASC, rowid ASC
|
||||
"#,
|
||||
params![conversation_id.to_string()],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user