Files
nexus-browser-pilot-extension/src/sidepanel/index.html
T
coreandClaude Opus 5 72d6a393fb feat(extension): make the side panel usable
The settings were 18 rows on one level: the broker URL, the pace in
milliseconds and the model all shouted equally loud, although one is set
once and the other is changed daily.

- Model picker moved into the header. It is the switch you touch every day
  and it sat at the end of the settings list. Source depends on driveMode:
  the verified gateway models in direct mode, the broker's list otherwise;
  hidden when there is nothing to choose.
- Settings grouped into five collapsible sections (Verbindung & Antrieb,
  Verhalten, Anzeige & Sicherheit, Modell & Zugang, Memory). Only "Modell &
  Zugang" starts open — that is what first-time setup needs.
- Existing rows were wrapped in place, not moved, so every element id and
  every binding stays intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 18:21:10 +02:00

1227 lines
41 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
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.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus Browser Pilot</title>
<style>
/* Keine externen Ressourcen, keine Webfonts — MV3-CSP verbietet sie. */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #101318;
--surface: #171b22;
--surface2: #1e242e;
--border: #272e3a;
--border-lt: #333c4a;
--text: #dfe4ec;
--text-dim: #8a94a6;
--text-mute: #626c7d;
--accent: #4ea3c8;
/* riskMode 'off' — ruhiges Gruen: der Agent laeuft durch */
--auto: #3fb27f;
--auto-bg: rgba(63, 178, 127, 0.13);
/* riskMode 'confirm' — Bernstein: es wird angehalten und gefragt */
--ask: #e0a338;
--ask-bg: rgba(224, 163, 56, 0.13);
--error: #e05c5c;
--error-bg: rgba(224, 92, 92, 0.12);
--success: #3fb27f;
--radius: 8px;
}
html, body { height: 100%; }
body {
font-family: system-ui, 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg);
color: var(--text);
display: flex;
flex-direction: column;
font-size: 13px;
line-height: 1.4;
overflow: hidden;
}
/* ── Kopfzeile ───────────────────────────────────────────────────────── */
.header {
display: flex;
align-items: center;
gap: 7px;
padding: 8px 10px;
background: var(--surface);
border-bottom: 1px solid var(--border);
flex: 0 0 auto;
}
.status-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--error);
flex: 0 0 auto;
transition: background 0.25s;
}
.status-dot.connected { background: var(--success); }
/* driveMode 'direct' braucht keinen Broker — kein Rot fuer einen Zustand, der in Ordnung ist. */
.status-dot.direct { background: var(--accent); }
.header h1 {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.2px;
white-space: nowrap;
}
.header .spacer { flex: 1; }
.badge {
font-size: 10px;
font-weight: 600;
padding: 2px 6px;
border-radius: 4px;
white-space: nowrap;
border: 1px solid transparent;
}
/* Moduskennung auch dann sichtbar, wenn der Schalter weggescrollt ist */
#modeBadge[data-mode="off"] { color: var(--auto); background: var(--auto-bg); border-color: var(--auto); }
#modeBadge[data-mode="confirm"] { color: var(--ask); background: var(--ask-bg); border-color: var(--ask); }
.round-counter {
font-size: 11px;
font-weight: 600;
color: var(--accent);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.mcp-status {
font-size: 10px;
color: var(--text-dim);
background: var(--surface2);
padding: 2px 5px;
border-radius: 4px;
white-space: nowrap;
}
.mcp-status:empty { display: none; }
/* ── Token-Usage-Bar ─────────────────────────────────────────────────── */
.usage-bar-wrap {
display: flex;
align-items: center;
gap: 5px;
font-size: 10px;
color: var(--text-mute);
white-space: nowrap;
}
.usage-bar-wrap:empty { display: none; }
.usage-bar {
width: 52px;
height: 4px;
background: var(--surface2);
border-radius: 2px;
overflow: hidden;
flex: 0 0 52px;
}
.usage-bar-fill {
height: 100%;
border-radius: 2px;
background: var(--accent);
transition: width 0.4s ease, background 0.4s;
width: 0%;
}
.usage-bar-fill.warn { background: var(--ask); }
.usage-bar-fill.crit { background: var(--error); }
.target-tab {
font-size: 10px;
color: var(--text-dim);
background: var(--surface2);
padding: 2px 6px;
border-radius: 4px;
white-space: nowrap;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
}
.target-tab:empty { display: none; }
.status-text { font-size: 10px; color: var(--text-mute); white-space: nowrap; }
.icon-btn {
background: transparent;
border: 1px solid var(--border-lt);
border-radius: 5px;
color: var(--text-dim);
cursor: pointer;
font-size: 12px;
line-height: 1;
padding: 4px 6px;
flex: 0 0 auto;
}
.icon-btn:hover { color: var(--text); border-color: var(--accent); }
/* ── Risikomodus-Schalter ────────────────────────────────────────────── */
.mode-bar {
display: flex;
gap: 6px;
padding: 8px 10px;
background: var(--surface);
border-bottom: 1px solid var(--border);
flex: 0 0 auto;
}
.mode-btn {
flex: 1 1 0;
min-width: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1px;
text-align: left;
padding: 6px 8px;
border-radius: 7px;
border: 1px solid var(--border-lt);
background: var(--bg);
color: var(--text-mute);
cursor: pointer;
font-family: inherit;
transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.mode-btn:hover { border-color: var(--text-dim); }
.mode-btn .t {
font-size: 12px;
font-weight: 650;
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
/* Ausgefuellter Punkt = aktiv, hohler Ring = inaktiv. Zweites Signal neben Farbe. */
.mode-btn .t::before {
content: '';
width: 8px; height: 8px;
border-radius: 50%;
border: 2px solid currentColor;
flex: 0 0 auto;
}
.mode-btn[aria-pressed="true"] .t::before { background: currentColor; }
.mode-btn .s {
font-size: 10px;
line-height: 1.25;
color: var(--text-mute);
}
#modeOff[aria-pressed="true"] {
border-color: var(--auto);
background: var(--auto-bg);
color: var(--auto);
}
#modeConfirm[aria-pressed="true"] {
border-color: var(--ask);
background: var(--ask-bg);
color: var(--ask);
}
.mode-btn[aria-pressed="true"] .s { color: var(--text-dim); }
/* ── Einstellungen ───────────────────────────────────────────────────── */
.settings {
background: var(--surface);
border-bottom: 1px solid var(--border);
flex: 0 0 auto;
max-height: 45vh;
overflow-y: auto;
}
.settings > summary {
padding: 6px 10px;
font-size: 11px;
font-weight: 600;
color: var(--text-dim);
cursor: pointer;
user-select: none;
outline: none;
list-style: none;
}
.settings > summary::-webkit-details-marker { display: none; }
.settings > summary::before { content: '▸ '; color: var(--text-mute); }
.settings[open] > summary::before { content: '▾ '; }
.settings > summary:hover { color: var(--text); }
.settings-body { padding: 2px 10px 10px; }
/* Klappbare Abschnitte: 18 Zeilen auf einer Ebene sind keine Bedienung,
sondern eine Liste. Was man taeglich anfasst, steht offen; der Rest
liegt zugeklappt darunter. */
.sgroup { border-top: 1px solid var(--border); margin-top: 6px; }
.sgroup > summary {
padding: 6px 0 5px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.5px;
text-transform: uppercase;
color: var(--text-mute);
cursor: pointer;
user-select: none;
list-style: none;
outline: none;
display: flex;
align-items: center;
gap: 5px;
}
.sgroup > summary::-webkit-details-marker { display: none; }
.sgroup > summary::before {
content: '';
width: 0; height: 0;
border-left: 4px solid currentColor;
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
transition: transform 0.15s;
flex: 0 0 auto;
}
.sgroup[open] > summary::before { transform: rotate(90deg); }
.sgroup > summary:hover { color: var(--text); }
.sgroup-body { padding: 1px 0 7px; }
/* Modellwahl in der Kopfzeile — der am haeufigsten benutzte Schalter
gehoert nicht ans Ende einer Einstellungsliste. */
#headerModel {
font-size: 10px;
max-width: 116px;
padding: 2px 4px;
background: var(--surface2);
color: var(--text-dim);
border: 1px solid var(--border-lt);
border-radius: 4px;
font-family: inherit;
cursor: pointer;
}
#headerModel:hover { color: var(--text); border-color: var(--accent); }
.row {
display: flex;
align-items: center;
gap: 8px;
padding: 3px 0;
}
.row > label {
flex: 0 0 88px;
font-size: 11px;
color: var(--text-dim);
white-space: nowrap;
}
.row input[type="text"],
.row input[type="password"],
.row input[type="number"],
.row select {
flex: 1 1 auto;
min-width: 0;
background: var(--bg);
border: 1px solid var(--border-lt);
border-radius: 5px;
color: var(--text);
padding: 4px 7px;
font-size: 11px;
font-family: inherit;
outline: none;
}
.row input[type="text"], .row input[type="password"] { font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace; }
.row input:focus, .row select:focus, .settings textarea:focus { border-color: var(--accent); }
.row.check { gap: 6px; }
.row.check > label { flex: 1 1 auto; order: 2; }
.row input[type="checkbox"] {
order: 1;
width: 14px; height: 14px;
accent-color: var(--accent);
cursor: pointer;
flex: 0 0 auto;
}
.settings .field-block { padding: 4px 0; }
.settings .field-block > label {
display: block;
font-size: 11px;
color: var(--text-dim);
margin-bottom: 3px;
}
.settings textarea {
width: 100%;
background: var(--bg);
border: 1px solid var(--border-lt);
border-radius: 5px;
color: var(--text);
padding: 6px 7px;
font-size: 11px;
font-family: inherit;
resize: vertical;
min-height: 70px;
outline: none;
}
.group-title {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.6px;
text-transform: uppercase;
color: var(--text-mute);
margin: 8px 0 2px;
padding-top: 7px;
border-top: 1px solid var(--border);
}
.hint { font-size: 10px; color: var(--text-mute); padding: 2px 0 0; }
/* Nexus-Modellauswahl */
.inline { display: flex; gap: 6px; flex: 1 1 auto; }
.mini {
background: var(--bg);
border: 1px solid var(--border-lt);
border-radius: 5px;
color: var(--text-dim);
padding: 3px 8px;
font-size: 10px;
font-family: inherit;
cursor: pointer;
}
.mini:hover { color: var(--text); border-color: var(--text-mute); }
.mini:disabled { opacity: .5; cursor: default; }
/* Zustand des gewaehlten Modells auf einen Blick — eine Drosselung ist der
haeufigste Grund, warum "nichts passiert". */
#nexusModel.throttled { border-color: var(--ask); color: var(--ask); }
#nexusModel.unavailable { border-color: var(--error); color: var(--error); }
#nexusModelHint.throttled { color: var(--ask); }
#nexusModelHint.err { color: var(--error); }
#nexusModelHint.ok { color: var(--auto); }
[hidden] { display: none !important; }
/* ── Diagnose-Panel ──────────────────────────────────────────────────── */
.diag-panel {
display: none;
flex-direction: column;
gap: 6px;
padding: 8px 10px;
background: var(--surface);
border-bottom: 1px solid var(--border);
flex: 0 0 auto;
max-height: 40vh;
overflow-y: auto;
}
.diag-panel.open { display: flex; }
.diag-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
}
.diag-label {
flex: 0 0 110px;
color: var(--text-dim);
white-space: nowrap;
}
.diag-status {
flex: 1 1 auto;
font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
font-size: 10px;
color: var(--text-mute);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.diag-status.ok { color: var(--auto); }
.diag-status.err { color: var(--error); }
.diag-status.warn { color: var(--ask); }
.diag-btn {
flex: 0 0 auto;
background: var(--bg);
border: 1px solid var(--border-lt);
border-radius: 4px;
color: var(--text-dim);
cursor: pointer;
font-size: 10px;
padding: 2px 7px;
font-family: inherit;
}
.diag-btn:hover { border-color: var(--accent); color: var(--text); }
.diag-section {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.5px;
text-transform: uppercase;
color: var(--text-mute);
padding-top: 4px;
border-top: 1px solid var(--border);
margin-top: 2px;
}
.diag-section:first-child { border-top: none; padding-top: 0; margin-top: 0; }
.conv-bar {
display: flex;
gap: 5px;
padding: 5px 10px;
background: var(--surface);
border-bottom: 1px solid var(--border);
flex: 0 0 auto;
align-items: center;
}
.conv-bar select {
flex: 1 1 auto;
min-width: 0;
background: var(--bg);
border: 1px solid var(--border-lt);
border-radius: 5px;
color: var(--text);
padding: 3px 6px;
font-size: 11px;
font-family: inherit;
outline: none;
}
.conv-bar select:focus { border-color: var(--accent); }
.conv-btn {
background: var(--bg);
border: 1px solid var(--border-lt);
border-radius: 5px;
color: var(--text-dim);
cursor: pointer;
font-size: 13px;
line-height: 1;
padding: 4px 7px;
flex: 0 0 auto;
}
.conv-btn:hover { color: var(--text); border-color: var(--accent); }
/* ── Chat ────────────────────────────────────────────────────────────── */
.chat-container {
flex: 1 1 auto;
overflow-y: auto;
overflow-x: hidden;
padding: 10px;
display: flex;
flex-direction: column;
gap: 7px;
}
.message {
padding: 7px 10px;
border-radius: var(--radius);
max-width: 96%;
overflow-wrap: anywhere;
white-space: pre-wrap;
font-size: 13px;
}
.message.user {
background: var(--surface2);
align-self: flex-end;
border-bottom-right-radius: 2px;
}
.message.assistant {
background: var(--surface);
align-self: flex-start;
border-bottom-left-radius: 2px;
position: relative;
}
.message.assistant:hover .copy-btn,
.message.assistant:focus-within .copy-btn {
opacity: 1;
}
.copy-btn {
position: absolute;
top: 5px;
right: 6px;
opacity: 0;
transition: opacity 0.15s;
background: var(--surface2);
border: 1px solid var(--border-lt);
border-radius: 5px;
color: var(--text-dim);
font-size: 11px;
padding: 2px 6px;
cursor: pointer;
line-height: 1.4;
user-select: none;
}
.copy-btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
.copy-btn.copied { background: var(--auto); color: #0d1014; border-color: var(--auto); opacity: 1; }
.message.tool {
background: var(--surface);
border-left: 3px solid var(--accent);
font-size: 11px;
color: var(--text-dim);
padding: 5px 8px;
max-width: 100%;
white-space: normal;
}
.message.error {
background: var(--error-bg);
border-left: 3px solid var(--error);
color: #f0a0a0;
max-width: 100%;
}
.message.system {
background: transparent;
color: var(--text-mute);
font-size: 11px;
text-align: center;
align-self: center;
padding: 2px 0;
max-width: 100%;
overflow-wrap: anywhere;
}
.message.system.warn { color: var(--ask); }
/* ── Markdown in Nachrichten ────────────────────────────────────────── */
.message.assistant.md { white-space: normal; }
.message.assistant.md p { margin: 0 0 0.5em; }
.message.assistant.md p:last-child { margin-bottom: 0; }
.message.assistant.md code {
background: var(--surface2);
padding: 1px 4px;
border-radius: 3px;
font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
font-size: 0.9em;
}
.message.assistant.md pre {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
padding: 8px;
overflow-x: auto;
margin: 0.5em 0;
font-size: 11px;
}
.message.assistant.md pre code {
background: none;
padding: 0;
font-size: inherit;
}
.message.assistant.md ul, .message.assistant.md ol {
margin: 0.4em 0;
padding-left: 1.4em;
}
.message.assistant.md li { margin: 0.15em 0; }
.message.assistant.md h1, .message.assistant.md h2,
.message.assistant.md h3, .message.assistant.md h4 {
margin: 0.6em 0 0.3em;
font-size: 1em;
font-weight: 700;
}
.message.assistant.md h1 { font-size: 1.15em; }
.message.assistant.md h2 { font-size: 1.05em; }
.message.assistant.md blockquote {
border-left: 3px solid var(--border-lt);
padding-left: 8px;
color: var(--text-dim);
margin: 0.4em 0;
}
.message.assistant.md table {
border-collapse: collapse;
margin: 0.4em 0;
font-size: 11px;
}
.message.assistant.md th, .message.assistant.md td {
border: 1px solid var(--border);
padding: 3px 6px;
}
.message.assistant.md th { background: var(--surface2); font-weight: 600; }
.message.assistant.md a { color: var(--accent); text-decoration: underline; }
.streaming-cursor::after {
content: '▊';
animation: blink 0.9s steps(1) infinite;
color: var(--accent);
}
@keyframes blink { 50% { opacity: 0; } }
/* Werkzeugereignisse eingeklappt — sonst friert der Renderer bei vielen Events ein. */
details.tool-collapsed > summary {
font-size: 11px;
cursor: pointer;
outline: none;
user-select: none;
list-style: none;
font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
}
details.tool-collapsed > summary::-webkit-details-marker { display: none; }
details.tool-collapsed > pre {
margin: 5px 0 0;
font-size: 10.5px;
font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
white-space: pre-wrap;
overflow-wrap: anywhere;
max-height: 190px;
overflow: auto;
color: var(--text-mute);
}
/* ── Spotlight-Viewer (was die KI gerade sieht) ─────────────────────── */
#spotlight {
position: fixed;
bottom: 80px;
right: 12px;
width: 220px;
background: rgba(16, 19, 24, 0.92);
border: 1px solid var(--border-lt);
border-radius: 10px;
box-shadow: 0 8px 32px rgba(0,0,0,0.55), 0 0 0 1px rgba(78,163,200,0.12);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
overflow: hidden;
z-index: 40;
opacity: 0;
transform: translateY(8px) scale(0.97);
transition: opacity 0.25s ease, transform 0.25s ease;
pointer-events: none;
}
#spotlight.visible {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
#spotlight.hidden-by-user { display: none; }
.spotlight-header {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px 4px;
font-size: 10px;
color: var(--text-mute);
border-bottom: 1px solid var(--border);
user-select: none;
}
.spotlight-io {
display: flex;
gap: 3px;
align-items: center;
}
.spotlight-io span {
width: 5px; height: 5px;
border-radius: 50%;
background: var(--accent);
opacity: 0.3;
animation: io-pulse 1.4s ease-in-out infinite;
}
.spotlight-io span:nth-child(2) { animation-delay: 0.2s; }
.spotlight-io span:nth-child(3) { animation-delay: 0.4s; }
@keyframes io-pulse {
0%, 100% { opacity: 0.2; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
.spotlight-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.spotlight-close {
background: none;
border: none;
color: var(--text-mute);
cursor: pointer;
font-size: 11px;
padding: 0 2px;
line-height: 1;
opacity: 0.6;
}
.spotlight-close:hover { opacity: 1; color: var(--text); }
.spotlight-img-wrap {
position: relative;
background: #000;
cursor: zoom-in;
}
.spotlight-img-wrap img {
display: block;
width: 100%;
height: auto;
max-height: 140px;
object-fit: cover;
object-position: top;
opacity: 0;
transition: opacity 0.3s;
}
.spotlight-img-wrap img.loaded { opacity: 1; }
.spotlight-action {
font-size: 10px;
color: var(--text-mute);
padding: 4px 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-top: 1px solid var(--border);
min-height: 22px;
}
/* ── Screenshot-Miniatur ─────────────────────────────────────────────── */
.shot {
align-self: stretch;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 5px;
max-width: 100%;
}
.shot .cap {
font-size: 10px;
color: var(--text-mute);
padding: 0 2px 4px;
display: flex;
justify-content: space-between;
gap: 6px;
}
.shot img {
display: block;
max-width: 100%;
height: auto;
border-radius: 4px;
cursor: zoom-in;
background: #000;
}
/* Aeltere Vorschauen geben ihre Base64-Daten frei, sonst waechst das Panel unbegrenzt. */
.shot.dropped img { display: none; }
/* Lightbox: im schmalen Panel ist die Miniatur zu klein zum Lesen. */
.lightbox {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.88);
z-index: 50;
overflow: auto;
cursor: zoom-out;
padding: 8px;
}
.lightbox img {
display: block;
max-width: none;
border-radius: 4px;
}
.lightbox .lb-hint {
position: sticky;
top: 0;
font-size: 10px;
color: #b9c2d0;
padding: 4px 2px;
}
/* ── Risiko-/Freigabe-Karte ──────────────────────────────────────────── */
.risk-card {
align-self: stretch;
max-width: 100%;
background: var(--ask-bg);
border: 1px solid var(--ask);
border-radius: var(--radius);
padding: 10px;
}
.risk-card .rc-title {
font-size: 12px;
font-weight: 700;
color: var(--ask);
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.risk-card .rc-reason {
font-size: 12px;
color: var(--text);
margin-bottom: 7px;
overflow-wrap: anywhere;
}
.risk-card dl {
display: grid;
grid-template-columns: 52px 1fr;
gap: 2px 8px;
font-size: 11px;
margin-bottom: 7px;
}
.risk-card dt { color: var(--text-mute); }
.risk-card dd {
color: var(--text-dim);
overflow-wrap: anywhere;
font-family: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
}
.chips { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
.chip {
font-size: 10px;
font-weight: 600;
color: var(--ask);
border: 1px solid var(--ask);
border-radius: 10px;
padding: 1px 7px;
white-space: nowrap;
}
.rc-actions { display: flex; gap: 7px; }
.rc-actions button {
flex: 1 1 0;
border: none;
border-radius: 6px;
padding: 7px 10px;
font-size: 12px;
font-weight: 650;
font-family: inherit;
cursor: pointer;
color: #0d1014;
}
.btn-go { background: var(--auto); }
.btn-go:hover { filter: brightness(1.1); }
.btn-cancel { background: var(--surface2); color: var(--text); border: 1px solid var(--border-lt); }
.btn-cancel:hover { border-color: var(--error); color: #f0a0a0; }
.rc-timer {
font-size: 10px;
color: var(--text-mute);
margin-top: 7px;
font-variant-numeric: tabular-nums;
}
.rc-timer b { color: var(--ask); font-weight: 700; font-variant-numeric: tabular-nums; }
.rc-timer.urgent b { color: var(--error); }
/* Entschiedene Karte tritt zurueck — offen ist nur, was noch eine Antwort braucht. */
.risk-card.resolved { background: var(--surface); border-color: var(--border); }
.risk-card.resolved .rc-title { color: var(--text-dim); }
.risk-card.resolved .chip { color: var(--text-mute); border-color: var(--border-lt); }
.rc-resolved { font-size: 11px; font-weight: 600; }
.rc-resolved.ok { color: var(--auto); }
.rc-resolved.no { color: var(--error); }
.rc-resolved.stale { color: var(--text-mute); }
/* ── Eingabe ─────────────────────────────────────────────────────────── */
.input-area {
flex: 0 0 auto;
padding: 8px 10px;
border-top: 1px solid var(--border);
background: var(--surface);
display: flex;
gap: 7px;
align-items: flex-end;
}
/* Bildanhänge: Streifen ÜBER der Eingabezeile, damit das Textfeld nicht springt. */
.attach-strip {
flex: 0 0 auto;
display: flex;
gap: 6px;
overflow-x: auto;
padding: 7px 10px 0;
background: var(--surface);
border-top: 1px solid var(--border);
}
.attach-chip { position: relative; flex: 0 0 auto; }
.attach-chip img {
height: 46px; width: 46px; object-fit: cover;
border-radius: var(--radius); border: 1px solid var(--border-lt); display: block;
}
.attach-chip button {
position: absolute; top: -5px; right: -5px;
width: 17px; height: 17px; padding: 0; line-height: 15px; font-size: 11px;
border-radius: 50%; border: 1px solid var(--border);
background: var(--surface); color: var(--text); cursor: pointer;
}
.attach-chip .attach-size {
position: absolute; bottom: 0; left: 0; right: 0;
font-size: 8px; text-align: center; color: var(--text-mute);
background: rgba(0,0,0,.55); border-radius: 0 0 var(--radius) var(--radius);
}
.attach-btn { background: var(--surface); border: 1px solid var(--border-lt); padding: 7px 9px; }
.input-area.drop-active { outline: 2px dashed var(--accent); outline-offset: -3px; }
.input-area textarea {
flex: 1 1 auto;
min-width: 0;
background: var(--bg);
border: 1px solid var(--border-lt);
border-radius: var(--radius);
color: var(--text);
padding: 8px 10px;
font-size: 13px;
font-family: inherit;
resize: none;
min-height: 36px;
max-height: 120px;
outline: none;
}
.input-area textarea:focus { border-color: var(--accent); }
.input-area button {
flex: 0 0 auto;
border: none;
border-radius: var(--radius);
padding: 9px 13px;
font-size: 12px;
font-weight: 650;
font-family: inherit;
cursor: pointer;
white-space: nowrap;
}
#sendBtn { background: var(--accent); color: #0d1014; }
#sendBtn:hover { filter: brightness(1.1); }
#sendBtn:disabled { opacity: 0.4; cursor: not-allowed; filter: none; }
#stopBtn { background: var(--error); color: #fff; }
#stopBtn:hover { filter: brightness(1.1); }
</style>
</head>
<body>
<div class="header">
<div class="status-dot" id="statusDot"></div>
<h1>Nexus Pilot</h1>
<span class="badge" id="modeBadge" data-mode="off">Autonom</span>
<span class="spacer"></span>
<span class="target-tab" id="targetTab" title="Aktiver Agent-Tab"></span>
<span class="round-counter" id="roundCounter"></span>
<div class="usage-bar-wrap" id="usageBarWrap" title="">
<div class="usage-bar"><div class="usage-bar-fill" id="usageBarFill"></div></div>
<span id="usageBarLabel"></span>
</div>
<span class="mcp-status" id="mcpStatus"></span>
<select id="headerModel" title="Aktives Modell"></select>
<span class="status-text" id="statusText">getrennt</span>
<button class="icon-btn" id="settingsToggle" type="button" title="Einstellungen ein-/ausklappen" aria-label="Einstellungen"></button>
<button class="icon-btn" id="diagToggle" type="button" title="Diagnose / Tests" aria-label="Diagnose">🔬</button>
</div>
<div class="diag-panel" id="diagPanel">
<div class="diag-section">Version</div>
<div class="diag-row">
<span class="diag-label">Panel / Worker</span>
<span class="diag-status" id="diagVersion"></span>
<button class="diag-btn" id="diagReloadBtn">Neu laden</button>
</div>
<div class="diag-section">Broker / Agent</div>
<div class="diag-row">
<span class="diag-label">Broker-WS</span>
<span class="diag-status" id="diagBroker"></span>
<button class="diag-btn" id="diagBrokerBtn">Test</button>
</div>
<div class="diag-row">
<span class="diag-label">Agent-Health</span>
<span class="diag-status" id="diagAgent"></span>
<button class="diag-btn" id="diagAgentBtn">Test</button>
</div>
<div class="diag-row">
<span class="diag-label">Verlauf (Turns)</span>
<span class="diag-status" id="diagConvTurns"></span>
<button class="diag-btn" id="diagResetConvBtn">Reset</button>
</div>
<div class="diag-section">MCP-Bridge</div>
<div class="diag-row">
<span class="diag-label">MCP-Bridge</span>
<span class="diag-status" id="diagMcp"></span>
<button class="diag-btn" id="diagMcpBtn">Test</button>
</div>
<div class="diag-row">
<span class="diag-label">MCP-Tools</span>
<span class="diag-status" id="diagMcpTools"></span>
</div>
<div class="diag-row" id="diagMcpStartRow" hidden>
<span class="diag-label" style="color:var(--ask)">▸ Starten</span>
<span class="diag-status" style="font-size:10px;color:var(--text-mute);flex:1">mcp-bridge.bat ausführen</span>
<button class="diag-btn" id="diagMcpCopyBtn" title="Startbefehl kopieren"></button>
</div>
<div class="diag-section">Memory</div>
<div class="diag-row">
<span class="diag-label">memory.cnull.net</span>
<span class="diag-status" id="diagMemory"></span>
<button class="diag-btn" id="diagMemoryBtn">Test</button>
</div>
<div class="diag-section">Extension</div>
<div class="diag-row">
<span class="diag-label">Service Worker</span>
<span class="diag-status" id="diagSw"></span>
<button class="diag-btn" id="diagSwBtn">Test</button>
</div>
<div class="diag-row">
<span class="diag-label">Aktiver Tab</span>
<span class="diag-status" id="diagTab"></span>
</div>
</div>
<div class="mode-bar" role="group" aria-label="Risikomodus">
<button class="mode-btn" id="modeOff" type="button" aria-pressed="true">
<span class="t">Autonom</span>
<span class="s">handelt ohne Rückfrage</span>
</button>
<button class="mode-btn" id="modeConfirm" type="button" aria-pressed="false">
<span class="t">Nachfragen</span>
<span class="s">fragt vor Absenden, Kauf, Löschen</span>
</button>
</div>
<div class="conv-bar" id="convBar">
<button class="conv-btn" id="newConvBtn" type="button" title="Neue Konversation"></button>
<select id="convSelect" title="Konversation wählen"></select>
<button class="conv-btn" id="syncMemoryBtn" type="button" title="Zu Memory synchronisieren"></button>
</div>
<details class="settings" id="settingsPanel">
<summary>Einstellungen</summary>
<div class="settings-body">
<details class="sgroup"><summary>Verbindung &amp; Antrieb</summary><div class="sgroup-body">
<div class="row">
<label for="brokerUrl">Broker</label>
<input type="text" id="brokerUrl" spellcheck="false" autocomplete="off" placeholder="ws://127.0.0.1:8765/ext/ws">
</div>
<div class="row">
<label for="driveMode">Antrieb</label>
<select id="driveMode">
<option value="broker">broker Python-Broker steuert</option>
<option value="direct">direct Loop im Worker</option>
</select>
</div>
<!-- Modelle des Compliance-Gateways. Nur im Broker-Modus sinnvoll: im
Modus 'direct' spricht der Worker direkt mit Anthropic/OpenAI. -->
<div id="nexusModelGroup">
<div class="row">
<label for="nexusModel">Nexus-Modell</label>
<select id="nexusModel">
<option value="">— Broker nicht erreichbar —</option>
</select>
</div>
<div class="row">
<label></label>
<div class="inline">
<button type="button" id="nexusModelRefresh" class="mini">Aktualisieren</button>
<button type="button" id="nexusModelProbe" class="mini" title="Prüft jede Modell-ID mit einem minimalen Aufruf gegen das Gateway">Verfügbarkeit prüfen</button>
</div>
</div>
<div class="row">
<label></label>
<div class="hint" id="nexusModelHint"></div>
</div>
</div>
</div></details>
<details class="sgroup"><summary>Verhalten</summary><div class="sgroup-body">
<div class="row">
<label for="inputMode">Eingabe</label>
<select id="inputMode">
<option value="trusted">trusted echte CDP-Events</option>
<option value="synthetic">synthetic nur DOM-Events</option>
<option value="auto">auto trusted, sonst synthetic</option>
</select>
</div>
<div class="row">
<label for="maxRounds">Max. Runden</label>
<input type="number" id="maxRounds" min="1" max="500" step="10">
</div>
<div class="row">
<label for="paceMs">Tempo (ms)</label>
<input type="number" id="paceMs" min="0" max="5000" step="100" title="Pause zwischen Aktionen in Millisekunden. 0 = keine Pause.">
</div>
</div></details>
<details class="sgroup"><summary>Anzeige &amp; Sicherheit</summary><div class="sgroup-body">
<div class="row">
<label for="pageOverlay">Anzeige</label>
<select id="pageOverlay" title="Sichtbare Rückmeldung auf der Seite, an der der Agent arbeitet.">
<option value="both">Glow + Spotlight</option>
<option value="spotlight">nur Spotlight (aktives Element)</option>
<option value="glow">nur Glow (aktiver Tab)</option>
<option value="off">aus</option>
</select>
</div>
<div class="row check">
<input type="checkbox" id="showCursor">
<label for="showCursor">Mauszeiger sichtbar einblenden</label>
</div>
<div class="row check">
<input type="checkbox" id="blockMailSend">
<label for="blockMailSend" title="Klicks auf Senden-Schaltflächen in Mail-Oberflächen werden abgelehnt. Der Agent legt nur Entwürfe an.">Senden in Mails blockieren (nur Entwürfe)</label>
</div>
</div></details>
<details class="sgroup" open id="directSection"><summary>Modell &amp; Zugang</summary><div class="sgroup-body">
<div id="directGroup" hidden>
<div class="row">
<label for="provider">Anbieter</label>
<select id="provider">
<option value="nexus">nexus (Mercedes)</option>
<option value="anthropic">anthropic</option>
<option value="openai">openai</option>
<option value="requesty">requesty</option>
</select>
</div>
<div class="row">
<label for="baseUrl">Basis-URL</label>
<input type="text" id="baseUrl" spellcheck="false" autocomplete="off" placeholder="https://api.anthropic.com/v1">
</div>
<div class="row" id="apiKeyRow">
<label for="apiKey">API-Key</label>
<input type="password" id="apiKey" spellcheck="false" autocomplete="off" placeholder="sk-…">
</div>
<div class="row" id="nexusKeyRow" hidden>
<label for="nexusApiKey">Nexus-Key</label>
<input type="password" id="nexusApiKey" spellcheck="false" autocomplete="off" placeholder="Nexus-API-Key (developer.corpinter.net)">
</div>
<div class="row" id="modelRow">
<label for="model">Modell</label>
<input type="text" id="model" spellcheck="false" autocomplete="off" placeholder="claude-sonnet-5">
</div>
<div class="row" id="directNexusModelRow" hidden>
<label for="directNexusModel">Modell</label>
<select id="directNexusModel"></select>
</div>
<div class="hint" id="directNexusHint" hidden></div>
<div class="field-block">
<label for="systemPrompt">System-Prompt</label>
<textarea id="systemPrompt" spellcheck="false"></textarea>
</div>
</div>
</div></details>
<details class="sgroup"><summary>Memory (memory.cnull.net)</summary><div class="sgroup-body">
<div class="row check">
<input type="checkbox" id="memoryEnabled">
<label for="memoryEnabled">Memory-Server anbinden</label>
</div>
<div class="row">
<label for="memoryUrl">URL</label>
<input type="text" id="memoryUrl" spellcheck="false" autocomplete="off" placeholder="https://memory.cnull.net">
</div>
<!-- Angemeldet: Token-Zeile + Abmelden -->
<div id="memoryLoggedIn" hidden>
<div class="row">
<label>Token</label>
<span id="memoryTokenPreview" style="font-size:10px;color:var(--auto);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:monospace"></span>
<button class="diag-btn" id="memoryLogoutBtn" type="button">Abmelden</button>
</div>
</div>
<!-- Nicht angemeldet: Login-Formular -->
<div id="memoryLoginForm">
<div class="row">
<label for="memoryUser">Benutzer</label>
<input type="text" id="memoryUser" spellcheck="false" autocomplete="username" placeholder="admin">
</div>
<div class="row">
<label for="memoryPass">Passwort</label>
<input type="password" id="memoryPass" spellcheck="false" autocomplete="current-password" placeholder="••••••">
</div>
<div class="row">
<label></label>
<button class="diag-btn" id="memoryLoginBtn" type="button" style="flex:1;padding:5px 8px;font-size:11px">Anmelden &amp; Token holen</button>
</div>
<div id="memoryLoginStatus" class="hint" style="color:var(--error);padding-left:96px"></div>
</div>
<!-- Manuell: Token direkt eintragen -->
<div class="row">
<label for="memoryToken">Token (manuell)</label>
<input type="password" id="memoryToken" spellcheck="false" autocomplete="off" placeholder="mcp-… oder leer lassen">
</div>
<div class="hint">Änderungen werden sofort gespeichert.</div>
</div></details>
</div>
</details>
<!-- Spotlight: zeigt was die KI gerade sieht -->
<div id="spotlight">
<div class="spotlight-header">
<div class="spotlight-io"><span></span><span></span><span></span></div>
<span class="spotlight-label" id="spotlightLabel">KI sieht…</span>
<button class="spotlight-close" id="spotlightClose" title="Ausblenden"></button>
</div>
<div class="spotlight-img-wrap" id="spotlightImgWrap">
<img id="spotlightImg" alt="Was die KI gerade sieht">
</div>
<div class="spotlight-action" id="spotlightAction"></div>
</div>
<div class="chat-container" id="chatContainer"></div>
<div class="attach-strip" id="attachStrip" hidden></div>
<div class="input-area" id="inputArea">
<button id="attachBtn" type="button" class="attach-btn"
title="Bild anhängen — oder Strg+V einfügen / hierher ziehen">📎</button>
<textarea id="chatInput" placeholder="Auftrag eingeben…" rows="1"></textarea>
<button id="sendBtn" type="button">Senden</button>
<button id="stopBtn" type="button" hidden>Stop</button>
<input type="file" id="imgFile" accept="image/png,image/jpeg,image/gif,image/webp" multiple hidden>
</div>
<!-- Pfad: diese Seite liegt unter /src/sidepanel/, das Bundle unter /dist/ -> zwei Ebenen hoch. -->
<script src="../../dist/sidepanel.js" type="module"></script>
</body>
</html>