feat: ops & UX batch — jobs survive restarts, logs/system in the console, media queue, gallery, chat, exact stream tokens

Fifteen changes aimed at the first day of real use:

- Import and media jobs persist to <data>/jobs-*.json and reload on start
  (running ones settle as errors); the Register button no longer vanishes
  because the gateway restarted.
- Docker ships the cortiq binary (built in the builder stage) — local .cmf
  chat models and image/video generation now work in containers, where
  auto-install never could (no cargo in the runtime image). libvulkan1 added
  for optional GPU offload.
- Open admin console on 0.0.0.0 without a token now warns in the log and
  shows a dismissible banner — the VPS footgun is at least labeled.
- "Restart gateway" button in Settings: stops managed model servers, spawns
  a fresh copy (bind retried while the port frees), Docker relies on the
  container restart policy instead.
- Dashboard "System" panel: CPU sparkline + load, RAM (with the gateway's
  own RSS), free disk at the models dir — sampled by sysinfo every 2 s.
- Featured model cards warn "not enough disk" / "may not fit in RAM" before
  the download, from the same live snapshot.
- Log viewer in the sidebar: a 500-line in-memory ring fed by a second
  ANSI-free tracing layer; filter, level, pause, copy.
- Update check: crates.io polled twice a day, the console shows an
  "update available" notice.
- Media generations queue up (with position shown) instead of being
  rejected; progress gained an honest ETA from measured throughput.
- Generation gallery under the image/video tabs: lazy PPM thumbnails,
  click to reopen with seed/steps, delete.
- Playground chat is a real dialogue now: history is resent, bubbles,
  Clear, Cmd/Ctrl+Enter.
- "Connect your app" snippets (curl / Python / JS) on the keys page, with
  the real key inlined right after creation.
- One-click benchmark on the Models page via `cortiq bench --json`.
- Config export now downloads the actual gateway.toml (comments intact);
  import accepts TOML (validated, hot-reloaded) and the legacy JSON.
- Streaming token counts are exact when the model server provides usage
  (cortiq-server patch, shipped separately); the gateway fallback now
  counts content chunks (1/token on cortiq) instead of chars/4.

Wizard's Finish lands on the Playground. 37 new strings in all 7 languages.
This commit is contained in:
infosave2007
2026-08-07 17:07:48 +03:00
parent a77beeabab
commit 15de36be3b
32 changed files with 1876 additions and 143 deletions
+60
View File
@@ -585,6 +585,66 @@ body.modal-open { overflow: hidden; }
/* Featured gallery: the list grows as new models are published, so give it more
room than the search grid before it starts scrolling. */
.featured-section .hf-grid { max-height: min(62vh, 760px); }
/* Shell notices (open admin warning, update available) */
.notice {
display: flex; align-items: center; gap: 8px;
padding: 8px 18px; font-size: 13px;
border-bottom: 1px solid var(--border-soft, #232833);
}
.notice.warn { background: color-mix(in srgb, #e5a54b 12%, var(--bg)); }
.notice.info { background: color-mix(in srgb, var(--accent) 10%, var(--bg)); }
.notice .icon-btn { margin-left: auto; }
/* Dashboard host metrics */
.sysgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
.sysitem .k { color: var(--muted); font-size: 12px; }
.sysitem .v { font-weight: 600; }
.sysbar {
height: 8px; border-radius: 6px; overflow: hidden; margin: 6px 0;
background: var(--bg-2, #12151c); border: 1px solid var(--border, #2a2f3a);
}
.sysbar i { display: block; height: 100%; background: var(--accent, #5b8def); }
.sysbar i.bad { background: #e5534b; }
/* Log viewer */
.logs-view { max-height: 68vh; }
/* Chat dialogue bubbles */
.chat-log { max-height: 56vh; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.chat-msg { display: flex; }
.chat-msg.user { justify-content: flex-end; }
.chat-bubble {
max-width: 85%; padding: 8px 12px; border-radius: 12px;
white-space: pre-wrap; word-break: break-word; font-size: 13.5px;
background: var(--bg-2, #12151c); border: 1px solid var(--border-soft, #232833);
}
.chat-msg.user .chat-bubble {
background: color-mix(in srgb, var(--accent) 16%, var(--bg-2));
border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}
/* Generation gallery */
.gal-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px; }
.gal-item {
position: relative; cursor: pointer; border: 1px solid var(--border, #2a2f3a);
border-radius: 8px; overflow: hidden; background: var(--bg-2, #12151c);
}
.gal-item:hover { border-color: var(--accent, #5b8def); }
.gal-thumb { width: 100%; display: block; min-height: 64px; }
.gal-video { display: flex; align-items: center; justify-content: center; font-size: 28px; height: 72px; }
.gal-cap { font-size: 10px; color: var(--muted); padding: 3px 6px; }
.gal-del {
position: absolute; top: 2px; right: 2px; z-index: 2;
border: 0; border-radius: 50%; width: 18px; height: 18px; line-height: 16px;
background: rgba(0,0,0,.55); color: #fff; cursor: pointer; font-size: 12px;
}
/* Bench + badges */
.bench-row { gap: 10px; padding: 6px 0; border-top: 1px solid var(--border-soft, #232833); }
.bench-row:first-of-type { border-top: 0; }
.badge.warn { background: rgba(229,165,75,.15); color: #e5a54b; }
.snippet { max-height: 260px; }
/* Playground media tabs: generated image / video frame */
.media-out { display: flex; flex-direction: column; gap: 8px; }
.media-canvas {