micro-models-agent 2.15.0 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/assets/app.js +6 -2
- package/dist/assets/index.html +1 -0
- package/dist/assets/js/feed.js +24 -47
- package/dist/assets/js/format.js +8 -0
- package/dist/assets/js/settings.js +74 -8
- package/dist/assets/js/sidebar.js +198 -5
- package/dist/assets/js/tabs.js +105 -18
- package/dist/assets/styles.css +73 -30
- package/dist/i18n/en.json +20 -1
- package/dist/i18n/ru.json +20 -1
- package/dist/main.js +355 -134
- package/package.json +1 -1
package/dist/assets/js/tabs.js
CHANGED
|
@@ -8,7 +8,7 @@ el, icon,
|
|
|
8
8
|
import { api } from "./api.js";
|
|
9
9
|
import { t } from "./i18n.js";
|
|
10
10
|
import { S, MAX_IMAGES, MAX_IMAGE_BYTES } from "./store.js";
|
|
11
|
-
import { relTime, contextLabel } from "./format.js";
|
|
11
|
+
import { relTime, contextLabel, matchesQuery } from "./format.js";
|
|
12
12
|
import {
|
|
13
13
|
makeFeed, showEmptyFeed, activeFeed, liveFeed, appendTo,
|
|
14
14
|
clearEmpty, showBusy, clearFeed, finishMd, resetTurnText,
|
|
@@ -204,18 +204,70 @@ document.getElementById("sessRefresh").addEventListener("click", () => {
|
|
|
204
204
|
loadSessions();
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
+
// Live filter text of the sessions dropdown. Module-level so the 2s
|
|
208
|
+
// `renderState` poll and the 15s `loadSessions` poll (both re-render the lists)
|
|
209
|
+
// never wipe an in-progress query: the `<input id="sessSearch">` itself lives
|
|
210
|
+
// statically in `index.html` and only the row lists are redrawn.
|
|
211
|
+
let sessQuery = "";
|
|
212
|
+
let sessSearchWired = false;
|
|
213
|
+
|
|
214
|
+
// One-time wiring for the static `#sessSearch` input: placeholder follows the
|
|
215
|
+
// locale on every render, listeners attach once. Returns the input or null.
|
|
216
|
+
function ensureSessSearch() {
|
|
217
|
+
const search = document.getElementById("sessSearch");
|
|
218
|
+
if (!search) return null;
|
|
219
|
+
search.placeholder = t("web.sessions.search");
|
|
220
|
+
search.setAttribute("aria-label", t("web.sessions.search"));
|
|
221
|
+
if (!sessSearchWired) {
|
|
222
|
+
sessSearchWired = true;
|
|
223
|
+
search.addEventListener("click", (e) => e.stopPropagation());
|
|
224
|
+
search.addEventListener("input", () => {
|
|
225
|
+
sessQuery = search.value;
|
|
226
|
+
renderSessionsDropdown();
|
|
227
|
+
// Re-render must not steal the caret: the input node itself survives
|
|
228
|
+
// (only the lists redraw), but re-query it to be safe.
|
|
229
|
+
const again = document.getElementById("sessSearch");
|
|
230
|
+
if (again && document.activeElement !== again) again.focus();
|
|
231
|
+
});
|
|
232
|
+
search.addEventListener("keydown", (e) => {
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
if (e.key === "Escape") {
|
|
235
|
+
if (search.value) {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
search.value = "";
|
|
238
|
+
sessQuery = "";
|
|
239
|
+
renderSessionsDropdown();
|
|
240
|
+
search.focus();
|
|
241
|
+
} else {
|
|
242
|
+
setSessOpen(false);
|
|
243
|
+
}
|
|
244
|
+
} else if (e.key === "ArrowDown") {
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
const first = document.querySelector("#sessActiveList .si-row .dd-row-main, #sessRecentList .si-row .dd-row-main");
|
|
247
|
+
if (first) first.focus();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return search;
|
|
252
|
+
}
|
|
253
|
+
|
|
207
254
|
export function renderSessionsDropdown() {
|
|
255
|
+
ensureSessSearch();
|
|
208
256
|
const activeList = document.getElementById("sessActiveList");
|
|
209
257
|
const recentList = document.getElementById("sessRecentList");
|
|
258
|
+
const activeHeader = document.getElementById("sessActiveHeader");
|
|
259
|
+
const recentHeader = document.getElementById("sessRecentHeader");
|
|
210
260
|
activeList.textContent = "";
|
|
211
261
|
recentList.textContent = "";
|
|
262
|
+
const q = sessQuery.trim();
|
|
212
263
|
const activeId = S.state.activeSessionId;
|
|
213
264
|
const sorted = S.state.sessions.slice().sort((a, b) => {
|
|
214
265
|
const at = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
215
266
|
const bt = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
216
267
|
return bt - at;
|
|
217
|
-
});
|
|
268
|
+
}).filter((s) => matchesQuery((s.name || "") + " " + String(s.id || ""), q));
|
|
218
269
|
let activePlaced = false;
|
|
270
|
+
let recentPlaced = false;
|
|
219
271
|
sorted.forEach((s) => {
|
|
220
272
|
const isActive = s.id === activeId;
|
|
221
273
|
const row = sessionRow(s, isActive);
|
|
@@ -224,13 +276,27 @@ export function renderSessionsDropdown() {
|
|
|
224
276
|
activePlaced = true;
|
|
225
277
|
} else {
|
|
226
278
|
recentList.appendChild(row);
|
|
279
|
+
recentPlaced = true;
|
|
227
280
|
}
|
|
228
281
|
});
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
282
|
+
if (q) {
|
|
283
|
+
// Filtered view: empty sections hide with their headers; zero hits total
|
|
284
|
+
// show a single no-matches row instead of the unfiltered placeholders.
|
|
285
|
+
if (activeHeader) activeHeader.hidden = !activePlaced;
|
|
286
|
+
if (recentHeader) recentHeader.hidden = !recentPlaced;
|
|
287
|
+
if (!activePlaced && !recentPlaced) {
|
|
288
|
+
if (recentHeader) recentHeader.hidden = true;
|
|
289
|
+
recentList.appendChild(el("div", "si dim", t("web.settings.no_matches")));
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
if (activeHeader) activeHeader.hidden = false;
|
|
293
|
+
if (recentHeader) recentHeader.hidden = false;
|
|
294
|
+
if (!activePlaced) {
|
|
295
|
+
activeList.appendChild(el("div", "si", t("web.sessions.none_active")));
|
|
296
|
+
}
|
|
297
|
+
if (!recentPlaced) {
|
|
298
|
+
recentList.appendChild(el("div", "si", t("web.sessions.none")));
|
|
299
|
+
}
|
|
234
300
|
}
|
|
235
301
|
if (S.state.sessionsError) {
|
|
236
302
|
recentList.appendChild(el("div", "dd-err", S.state.sessionsError));
|
|
@@ -304,11 +370,28 @@ const sessBtn = document.getElementById("sessBtn");
|
|
|
304
370
|
export function setSessOpen(open) {
|
|
305
371
|
sessDD.classList.toggle("open", !!open);
|
|
306
372
|
sessBtn.setAttribute("aria-expanded", open ? "true" : "false");
|
|
373
|
+
if (!open && sessQuery) {
|
|
374
|
+
// Closing wipes the filter so the next opening starts from the full list.
|
|
375
|
+
sessQuery = "";
|
|
376
|
+
const search = document.getElementById("sessSearch");
|
|
377
|
+
if (search && search.value) {
|
|
378
|
+
search.value = "";
|
|
379
|
+
renderSessionsDropdown();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function focusSessSearch() {
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
const search = document.getElementById("sessSearch");
|
|
386
|
+
if (search) search.focus();
|
|
387
|
+
}, 0);
|
|
307
388
|
}
|
|
308
389
|
sessBtn.addEventListener("click", (e) => {
|
|
309
390
|
e.stopPropagation();
|
|
310
391
|
document.querySelectorAll("[data-sel]").forEach((s) => s.classList.remove("open"));
|
|
311
|
-
|
|
392
|
+
const willOpen = !sessDD.classList.contains("open");
|
|
393
|
+
setSessOpen(willOpen);
|
|
394
|
+
if (willOpen) focusSessSearch();
|
|
312
395
|
});
|
|
313
396
|
|
|
314
397
|
// --- mobile session sub-header -------------------------------------------
|
|
@@ -370,7 +453,9 @@ if (sessCloseBtn) sessCloseBtn.addEventListener("click", () => {
|
|
|
370
453
|
if (sessNameBtn) sessNameBtn.addEventListener("click", (e) => {
|
|
371
454
|
e.stopPropagation();
|
|
372
455
|
document.querySelectorAll("[data-sel]").forEach((s) => s.classList.remove("open"));
|
|
373
|
-
|
|
456
|
+
const willOpen = !sessDD.classList.contains("open");
|
|
457
|
+
setSessOpen(willOpen);
|
|
458
|
+
if (willOpen) focusSessSearch();
|
|
374
459
|
});
|
|
375
460
|
if (typeof window.matchMedia === "function") {
|
|
376
461
|
const narrowMq = window.matchMedia("(max-width: 760px)");
|
|
@@ -627,11 +712,8 @@ async function send() {
|
|
|
627
712
|
const images = S.attachments.slice();
|
|
628
713
|
if (!text && !images.length) return;
|
|
629
714
|
const feed = liveFeed() || activeFeed();
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (feed) appendTo(feed, el("div", "warnline", "⚠ " + t("web.input.busy")));
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
715
|
+
// No client-side busy gate: a message sent mid-turn is queued server-side
|
|
716
|
+
// (`202 {queued: n}`) and runs after the current turn, in order.
|
|
635
717
|
clearEmpty(feed);
|
|
636
718
|
finishMd();
|
|
637
719
|
resetTurnText();
|
|
@@ -672,13 +754,18 @@ async function send() {
|
|
|
672
754
|
if (images.length) body.images = images;
|
|
673
755
|
const r = await api("/api/message", { method: "POST", body });
|
|
674
756
|
if (!r.ok) {
|
|
675
|
-
const
|
|
757
|
+
const err = (r.data && r.data.error) || r.status;
|
|
758
|
+
const msg = err === "queue_full" ? t("web.input.queue_full") : err;
|
|
676
759
|
appendTo(feed, el("div", "warnline", "⚠ " + msg));
|
|
677
760
|
} else {
|
|
678
761
|
if (images.length) clearAttachments();
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
762
|
+
if (r.data && r.data.queued) {
|
|
763
|
+
appendTo(feed, el("div", "detail", "↳ " + t("web.input.queued", { n: r.data.queued })));
|
|
764
|
+
} else {
|
|
765
|
+
// Delay the spinner so fast first tokens never flash it: `showBusy`
|
|
766
|
+
// itself skips when a stream/think block already started.
|
|
767
|
+
window.setTimeout(() => showBusy(feed), 180);
|
|
768
|
+
}
|
|
682
769
|
}
|
|
683
770
|
}
|
|
684
771
|
|
package/dist/assets/styles.css
CHANGED
|
@@ -244,11 +244,14 @@ body[data-font="large"] { font-size: 17px; }
|
|
|
244
244
|
|
|
245
245
|
/* chat */
|
|
246
246
|
.chat { flex: 1; display: flex; flex-direction: column; min-width: 0; position: relative; }
|
|
247
|
-
.msgs { flex: 1; overflow-y: auto; overflow-x: hidden; padding:
|
|
247
|
+
.msgs { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 0 22px; display: flex; flex-direction: column; gap: 11px; }
|
|
248
248
|
.msgs > * { flex-shrink: 0; min-height: fit-content; }
|
|
249
249
|
/* messages live inside a per-tab `.feed` wrapper; it must mirror the `.msgs`
|
|
250
|
-
column gap so turns are separated instead of running together
|
|
251
|
-
|
|
250
|
+
column gap so turns are separated instead of running together.
|
|
251
|
+
Vertical breathing lives on `.feed`, NOT on the scroller: sticky edge fades
|
|
252
|
+
are constrained to the content box, so scroller padding would offset them
|
|
253
|
+
from the header/composer edges. */
|
|
254
|
+
.feed { display: flex; flex-direction: column; gap: 11px; padding: 18px 0; }
|
|
252
255
|
.feed > * { flex-shrink: 0; min-height: fit-content; }
|
|
253
256
|
.u {
|
|
254
257
|
color: var(--u-fg); background: var(--u-bg); border-left: 2px solid var(--u-border);
|
|
@@ -270,7 +273,7 @@ body[data-font="large"] { font-size: 17px; }
|
|
|
270
273
|
font-size: 12.5px; overflow-wrap: anywhere;
|
|
271
274
|
}
|
|
272
275
|
.metaln.warn { color: var(--yellow); }
|
|
273
|
-
.act { display: flex; gap: 8px; min-width: 0; }
|
|
276
|
+
.act { display: flex; gap: 8px; min-width: 0; line-height: 1.35; }
|
|
274
277
|
.act .lab { white-space: nowrap; }
|
|
275
278
|
.act .args { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
276
279
|
.act.recon .g, .act.recon .lab { color: var(--cyan); }
|
|
@@ -311,7 +314,15 @@ body.light .code { border: 1px solid var(--border); }
|
|
|
311
314
|
.toolout .more { color: var(--dim-b); cursor: pointer; font-size: 12px; }
|
|
312
315
|
.toolout .more:hover { color: var(--dim-a); }
|
|
313
316
|
|
|
314
|
-
/* markdown answers (streamed by md.js) — lines and glyphs, no boxes
|
|
317
|
+
/* markdown answers (streamed by md.js) — lines and glyphs, no boxes.
|
|
318
|
+
Top-level answers carry a quiet accent rail (mirrors the user's `> ` block),
|
|
319
|
+
so the finished message reads instantly apart from the dim work process
|
|
320
|
+
(tool/think rows stay plain). Scoped to `.feed >` — code blocks inside keep
|
|
321
|
+
their own styling. */
|
|
322
|
+
.feed > .md, .feed > .ans {
|
|
323
|
+
border-left: 2px solid var(--accent);
|
|
324
|
+
padding-left: 12px;
|
|
325
|
+
}
|
|
315
326
|
.md { color: var(--ans); overflow-wrap: anywhere; }
|
|
316
327
|
.md > * { flex-shrink: 0; }
|
|
317
328
|
.md p { margin: 4px 0; white-space: pre-wrap; }
|
|
@@ -391,7 +402,14 @@ body.light .codeblock { border: 1px solid var(--border); }
|
|
|
391
402
|
}
|
|
392
403
|
body.light .imgcard .ph { background: var(--ph-light); }
|
|
393
404
|
.imgcard .cap { padding: 6px 10px; color: var(--dim-a); font-size: 12px; }
|
|
394
|
-
.filerow { margin-left: 26px; display: flex; gap: 8px; align-items: baseline; min-width: 0; font-size: 12.5px; color: var(--muted); }
|
|
405
|
+
.filerow { margin-left: 26px; display: flex; gap: 8px; align-items: baseline; min-width: 0; font-size: 12.5px; line-height: 1.35; color: var(--muted); }
|
|
406
|
+
/* Compact tool lists: consecutive action/file rows sit tight (~4px visual),
|
|
407
|
+
// while turns keep the airy 11px feed gap. Negative margin compensates the
|
|
408
|
+
// `.feed { gap: 11px }` only between siblings in a run. */
|
|
409
|
+
.feed > .filerow + .filerow,
|
|
410
|
+
.feed > .act + .act,
|
|
411
|
+
.feed > .filerow + .act,
|
|
412
|
+
.feed > .act + .filerow { margin-top: -7px; }
|
|
395
413
|
.filerow .g { flex: 0 0 auto; }
|
|
396
414
|
.filerow.recon .g, .filerow.recon .lab { color: var(--cyan); }
|
|
397
415
|
.filerow.mutate .g, .filerow.mutate .lab { color: var(--magenta); }
|
|
@@ -404,10 +422,6 @@ body.light .imgcard .ph { background: var(--ph-light); }
|
|
|
404
422
|
@media (pointer: coarse) { .filerow .copy-btn { opacity: 1; } }
|
|
405
423
|
.filerow .copy-btn:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
406
424
|
.filerow .copy-btn .svg-icon { width: 14px; height: 14px; }
|
|
407
|
-
.filegroup { margin-left: 26px; font-size: 12.5px; color: var(--muted); }
|
|
408
|
-
.filegroup .head { background: transparent; border: 0; color: inherit; font: inherit; cursor: pointer; display: flex; gap: 6px; align-items: baseline; padding: 0; }
|
|
409
|
-
.filegroup .items.hidden { display: none; }
|
|
410
|
-
.filegroup .items > .filerow { margin-left: 0; }
|
|
411
425
|
.linkline { margin-left: 26px; min-width: 0; overflow-wrap: anywhere; }
|
|
412
426
|
.linkline a { color: var(--accent); text-decoration: none; overflow-wrap: anywhere; word-break: break-word; }
|
|
413
427
|
.linkline a:hover { text-decoration: underline; }
|
|
@@ -522,6 +536,16 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
522
536
|
color: var(--fg); font: inherit; font-size: 12.5px; padding: 4px 8px;
|
|
523
537
|
}
|
|
524
538
|
.menu .ctx-custom .linkbtn { flex: 0 0 auto; }
|
|
539
|
+
/* Dropdown search fields (composer model menu, sessions menu): sticky on top
|
|
540
|
+
so the query stays visible while the list scrolls underneath. */
|
|
541
|
+
.menu .searchbox {
|
|
542
|
+
margin-bottom: 4px; position: sticky; top: 0; z-index: 1;
|
|
543
|
+
}
|
|
544
|
+
.model-menu-list { display: contents; }
|
|
545
|
+
.dd-menu .searchbox {
|
|
546
|
+
margin: 2px 2px 6px; width: calc(100% - 4px);
|
|
547
|
+
position: sticky; top: 0; z-index: 1;
|
|
548
|
+
}
|
|
525
549
|
|
|
526
550
|
.inp {
|
|
527
551
|
width: 100%; padding: 12px 14px; resize: none; display: block;
|
|
@@ -619,7 +643,7 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
619
643
|
|
|
620
644
|
/* new rows rise in softly (a bulk history render animates once as a whole —
|
|
621
645
|
one shared fade, still cheap: opacity/transform only) */
|
|
622
|
-
.feed > :is(.u, .md, .act, .think, .q, .filerow, .
|
|
646
|
+
.feed > :is(.u, .md, .act, .think, .q, .filerow, .planblock, .imgcard, .linkline,
|
|
623
647
|
.toolout, .metric, .detail, .metaln, .warnline, .errline, .cmdout, .sysline,
|
|
624
648
|
.startbanner, .grouped, .msg-actions, .busy) {
|
|
625
649
|
animation: rise .18s ease-out;
|
|
@@ -646,20 +670,25 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
646
670
|
.overlay.open .modal { animation: rise .18s ease-out; }
|
|
647
671
|
|
|
648
672
|
/* hover/active blends */
|
|
649
|
-
.sel > button, .menu .mi, .stab, .q .qopt, .q .qsend, .filerow, .
|
|
673
|
+
.sel > button, .menu .mi, .stab, .q .qopt, .q .qsend, .filerow, .icon-btn,
|
|
650
674
|
.btn, .linkbtn, .chip, .cb-copy, .act-btn, .copy-btn, .composer .attach-btn,
|
|
651
675
|
.composer .send-btn, .dd-menu .si, .dd-menu .act, .cmdk-item, .toggle {
|
|
652
676
|
transition: background-color .12s, color .12s, border-color .12s;
|
|
653
677
|
}
|
|
654
678
|
.composer { transition: border-color .15s, box-shadow .15s; }
|
|
655
679
|
|
|
656
|
-
/* scroll edge fades on the transcript (classes from the feed scroll hook)
|
|
680
|
+
/* scroll edge fades on the transcript (classes from the feed scroll hook).
|
|
681
|
+
Sticky in-flow pseudos glued exactly to the scrollport edges: they sit in
|
|
682
|
+
the scroller's padding-free content box (breathing lives on `.feed`), so
|
|
683
|
+
`top: 0` / `bottom: 0` meet the header above and the composer below with
|
|
684
|
+
no visible offset. Negative margins neutralize their flow height plus the
|
|
685
|
+
flex gap, so hidden fades take no space. */
|
|
657
686
|
.msgs::before, .msgs::after {
|
|
658
687
|
content: ""; position: sticky; display: block; height: 22px; flex: 0 0 auto;
|
|
659
688
|
pointer-events: none; z-index: 5; opacity: 0; transition: opacity .25s;
|
|
660
689
|
}
|
|
661
|
-
.msgs::before { top: 0; margin-bottom: -
|
|
662
|
-
.msgs::after { bottom: 0; margin-top: -
|
|
690
|
+
.msgs::before { top: 0; margin-bottom: -33px; background: linear-gradient(var(--bg), transparent); }
|
|
691
|
+
.msgs::after { bottom: 0; margin-top: -33px; background: linear-gradient(transparent, var(--bg)); }
|
|
663
692
|
.msgs.scrolled::before { opacity: 1; }
|
|
664
693
|
.msgs.unfollowed::after { opacity: 1; }
|
|
665
694
|
|
|
@@ -734,12 +763,16 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
734
763
|
@keyframes rlFlash { 0% { filter: brightness(1.9); } 100% { filter: brightness(1); } }
|
|
735
764
|
|
|
736
765
|
/* ---- sidebar — plan / usage / environment sections ---- */
|
|
766
|
+
/* Sidebar never scrolls horizontally, in any tab: long paths / model names /
|
|
767
|
+
ids wrap or ellipsize inside their rows, never widening the pane. */
|
|
737
768
|
.sb {
|
|
738
769
|
width: 340px; min-width: 340px; display: flex; flex-direction: column;
|
|
739
770
|
border-left: 1px solid var(--border); background: var(--sb-bg); min-height: 0;
|
|
771
|
+
overflow-x: hidden;
|
|
740
772
|
}
|
|
741
773
|
.sec {
|
|
742
|
-
display: flex; flex-direction: column; min-height: 0;
|
|
774
|
+
display: flex; flex-direction: column; min-height: 0; min-width: 0;
|
|
775
|
+
max-width: 100%; overflow-x: hidden;
|
|
743
776
|
border-bottom: 1px solid var(--border);
|
|
744
777
|
}
|
|
745
778
|
.sec:last-child { border-bottom: none; }
|
|
@@ -751,8 +784,8 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
751
784
|
.sec-h:hover { color: var(--sec-h-hover); }
|
|
752
785
|
.sec-h .n { margin-left: auto; color: var(--dim-b); }
|
|
753
786
|
.sec-body {
|
|
754
|
-
overflow-y: auto; padding: 0 16px 12px;
|
|
755
|
-
min-height: 0;
|
|
787
|
+
overflow-y: auto; overflow-x: hidden; padding: 0 16px 12px;
|
|
788
|
+
min-height: 0; min-width: 0; max-width: 100%;
|
|
756
789
|
}
|
|
757
790
|
.sec-body.collapsed { display: none; }
|
|
758
791
|
/* plan section takes the lion's share and scrolls */
|
|
@@ -786,7 +819,7 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
786
819
|
}
|
|
787
820
|
.sb-name .sb-ico svg { width: 14px; height: 14px; color: var(--accent); }
|
|
788
821
|
|
|
789
|
-
.sb-pane { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
822
|
+
.sb-pane { display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0; max-width: 100%; overflow-x: hidden; }
|
|
790
823
|
.sb-pane[hidden] { display: none; }
|
|
791
824
|
|
|
792
825
|
/* master-detail expansion */
|
|
@@ -794,25 +827,26 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
794
827
|
.sb.sb-expanded { width: 680px; min-width: 680px; }
|
|
795
828
|
#pane-trace { flex-direction: row; }
|
|
796
829
|
.sb-expanded #pane-trace { position: relative; }
|
|
797
|
-
.trace-list { flex: 1; min-width: 0; overflow-y: auto; padding: 6px 10px 12px; display: flex; flex-direction: column; gap: 1px; }
|
|
798
|
-
.trace-turn { color: var(--dim-c); font-size: 11px; margin: 8px 6px 2px; }
|
|
799
|
-
.trc { display: flex; gap: 8px; align-items: baseline; padding: 4px 6px; font-size: 12px; color: var(--muted); border-radius: 4px; cursor: pointer; }
|
|
830
|
+
.trace-list { flex: 1; min-width: 0; max-width: 100%; overflow-y: auto; overflow-x: hidden; padding: 6px 10px 12px; display: flex; flex-direction: column; gap: 1px; }
|
|
831
|
+
.trace-turn { color: var(--dim-c); font-size: 11px; margin: 8px 6px 2px; overflow-wrap: anywhere; }
|
|
832
|
+
.trc { display: flex; gap: 8px; align-items: baseline; padding: 4px 6px; font-size: 12px; color: var(--muted); border-radius: 4px; cursor: pointer; min-width: 0; max-width: 100%; overflow: hidden; }
|
|
800
833
|
.trc:hover { background: var(--hover-bg); }
|
|
801
834
|
.trc.sel { background: var(--accent-bg); }
|
|
802
835
|
.trc .g { flex: 0 0 auto; }
|
|
803
836
|
.trc.recon .g { color: var(--cyan); } .trc.mutate .g { color: var(--purple); }
|
|
804
837
|
.trc.execute .g { color: var(--accent); } .trc.meta .g { color: var(--meta); } .trc.phase .g { color: var(--think); }
|
|
805
|
-
.trc .lab { color: var(--fg); white-space: nowrap; }
|
|
838
|
+
.trc .lab { color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 0 1 auto; }
|
|
806
839
|
.trc .args { color: var(--args); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
|
|
807
840
|
.trc .t { color: var(--dim-b); margin-left: auto; flex: 0 0 auto; }
|
|
808
841
|
.trc.warn .g, .trc.warn .lab { color: var(--yellow); }
|
|
809
842
|
.trc.phase .lab { color: var(--think); }
|
|
810
|
-
.trc-detail { display: flex; flex-direction: column; flex: 0 0 340px; min-width: 0; border-left: 1px solid var(--border); }
|
|
843
|
+
.trc-detail { display: flex; flex-direction: column; flex: 0 0 340px; min-width: 0; max-width: 100%; overflow-x: hidden; border-left: 1px solid var(--border); }
|
|
811
844
|
.trc-detail[hidden] { display: none; }
|
|
812
|
-
.detail-h { display: flex; gap: 8px; align-items: center; padding: 10px 14px; border-bottom: 1px solid var(--border); color: var(--fg); font-size: 12.5px; flex-shrink: 0; }
|
|
845
|
+
.detail-h { display: flex; gap: 8px; align-items: center; padding: 10px 14px; border-bottom: 1px solid var(--border); color: var(--fg); font-size: 12.5px; flex-shrink: 0; min-width: 0; max-width: 100%; overflow: hidden; }
|
|
813
846
|
.detail-h .g { color: var(--accent); }
|
|
814
847
|
.detail-h .x { margin-left: auto; color: var(--dim-b); cursor: pointer; }
|
|
815
|
-
.detail-b { padding: 10px 14px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; }
|
|
848
|
+
.detail-b { padding: 10px 14px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; overflow-x: hidden; min-width: 0; max-width: 100%; }
|
|
849
|
+
.detail-b .code { max-width: 100%; }
|
|
816
850
|
.detail-b h5 { margin: 0 0 4px; font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--sec-h); font-weight: 600; }
|
|
817
851
|
.map-sec { flex: 1 1 auto; }
|
|
818
852
|
.map-reindex { margin-top: 10px; font: inherit; font-size: 12px; color: var(--accent); background: transparent; border: 1px solid var(--border-2); border-radius: 5px; padding: 4px 10px; cursor: pointer; width: fit-content; }
|
|
@@ -832,8 +866,15 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
832
866
|
.plan-step::before { content: ""; position: absolute; left: 4px; top: 0; bottom: 0; width: 1px; background: var(--plan-line); }
|
|
833
867
|
.plan-step .sub { display: block; color: var(--dim-b); font-size: 11.5px; padding-left: 0; }
|
|
834
868
|
|
|
835
|
-
.kv { display: flex; justify-content: space-between; padding: 2px 0; color: var(--muted); gap: 10px; }
|
|
836
|
-
.kv
|
|
869
|
+
.kv { display: flex; justify-content: space-between; padding: 2px 0; color: var(--muted); gap: 10px; min-width: 0; max-width: 100%; }
|
|
870
|
+
.kv > * { min-width: 0; }
|
|
871
|
+
/* Подпись строки никогда не сжимается — иначе её текст наезжает на значение. */
|
|
872
|
+
.kv > span:first-child { flex-shrink: 0; }
|
|
873
|
+
.kv b { font-weight: 400; color: var(--fg); text-align: right; overflow-wrap: anywhere; word-break: break-word; min-width: 0; max-width: 100%; }
|
|
874
|
+
/* Корень карты: подпись сверху, длинный путь снизу на всю ширину. */
|
|
875
|
+
.map-path { display: flex; flex-direction: column; gap: 2px; padding: 2px 0; min-width: 0; max-width: 100%; color: var(--muted); }
|
|
876
|
+
.map-path .dim { color: var(--dim-a); }
|
|
877
|
+
.map-path b { font-weight: 400; color: var(--fg); overflow-wrap: anywhere; word-break: break-word; min-width: 0; max-width: 100%; }
|
|
837
878
|
body.light .kv b { color: var(--fg); }
|
|
838
879
|
.kv .dim { color: var(--dim-a); }
|
|
839
880
|
.kv .wn { color: var(--yellow); }
|
|
@@ -1372,7 +1413,8 @@ button.toggle:disabled { opacity: .5; cursor: default; }
|
|
|
1372
1413
|
border-left: 1px solid var(--border); border-top: none;
|
|
1373
1414
|
box-shadow: var(--shadow-dd);
|
|
1374
1415
|
}
|
|
1375
|
-
.msgs { padding: 14px; }
|
|
1416
|
+
.msgs { padding: 0 14px; }
|
|
1417
|
+
.feed { padding: 14px 0; }
|
|
1376
1418
|
.input-area { margin: 10px 14px 12px; }
|
|
1377
1419
|
.inp-meta { flex-wrap: wrap; gap: 8px; }
|
|
1378
1420
|
}
|
|
@@ -1395,7 +1437,8 @@ button.toggle:disabled { opacity: .5; cursor: default; }
|
|
|
1395
1437
|
.content-col { padding: 14px; }
|
|
1396
1438
|
.row { font-size: 12.5px; }
|
|
1397
1439
|
.input-area { margin: 8px 10px 10px; }
|
|
1398
|
-
.msgs { padding:
|
|
1440
|
+
.msgs { padding: 0 10px; }
|
|
1441
|
+
.feed { padding: 12px 0; }
|
|
1399
1442
|
/* narrow phones: rail stacks vertically, track takes the full row */
|
|
1400
1443
|
.rail-wrap.l-line, .rail-wrap.l-meta {
|
|
1401
1444
|
flex-direction: column; align-items: stretch; gap: 6px;
|
package/dist/i18n/en.json
CHANGED
|
@@ -1087,6 +1087,18 @@
|
|
|
1087
1087
|
"web.sidebar.trace_text": "composing… {count} chars",
|
|
1088
1088
|
"web.sidebar.trace_plan": "plan update",
|
|
1089
1089
|
"web.sidebar.trace_question": "question",
|
|
1090
|
+
"web.sidebar.trace_status": "status",
|
|
1091
|
+
"web.sidebar.trace_log": "log · {level}",
|
|
1092
|
+
"web.sidebar.trace_retract": "draft retracted",
|
|
1093
|
+
"web.sidebar.trace_index": "index · {phase}",
|
|
1094
|
+
"web.sidebar.trace_critic": "critic · {status}",
|
|
1095
|
+
"web.sidebar.trace_moe": "moe",
|
|
1096
|
+
"web.sidebar.trace_compaction": "compaction",
|
|
1097
|
+
"web.sidebar.trace_probe": "context probe",
|
|
1098
|
+
"web.sidebar.trace_usage": "llm usage",
|
|
1099
|
+
"web.sidebar.trace_audit": "audit",
|
|
1100
|
+
"web.sidebar.trace_claim": "claim retry · {kind}",
|
|
1101
|
+
"web.sidebar.trace_output": "output",
|
|
1090
1102
|
"web.sidebar.detail_metadata": "metadata",
|
|
1091
1103
|
"web.sidebar.detail_arguments": "arguments",
|
|
1092
1104
|
"web.sidebar.detail_output": "output",
|
|
@@ -1101,6 +1113,10 @@
|
|
|
1101
1113
|
"web.sidebar.detail_time": "time",
|
|
1102
1114
|
"web.sidebar.detail_tool_call": "tool call",
|
|
1103
1115
|
"web.sidebar.detail_session": "session",
|
|
1116
|
+
"web.sidebar.detail_turn": "turn",
|
|
1117
|
+
"web.sidebar.detail_iteration": "iteration",
|
|
1118
|
+
"web.sidebar.detail_provenance": "session-log provenance",
|
|
1119
|
+
"web.sidebar.detail_provenance_count": "{n} assistant messages in log",
|
|
1104
1120
|
"web.env.dir": "dir",
|
|
1105
1121
|
"web.env.agents": "AGENTS.md",
|
|
1106
1122
|
"web.env.map": "map",
|
|
@@ -1420,9 +1436,10 @@
|
|
|
1420
1436
|
"web.input.shift_enter": "Shift+Enter — newline",
|
|
1421
1437
|
"web.input.esc": "Esc — stop",
|
|
1422
1438
|
"web.input.attach": "Ctrl+V — attach",
|
|
1423
|
-
"web.input.busy": "still working — Esc to stop",
|
|
1424
1439
|
"web.input.send": "send",
|
|
1425
1440
|
"web.input.stop": "stop",
|
|
1441
|
+
"web.input.queued": "queued · position {n}",
|
|
1442
|
+
"web.input.queue_full": "message queue is full — wait for the turn to end",
|
|
1426
1443
|
"web.empty.title": "new chat",
|
|
1427
1444
|
"web.empty.hint": "type a message below · / — commands · model and context are chosen above the input",
|
|
1428
1445
|
"web.attach.title": "attach image",
|
|
@@ -1439,6 +1456,7 @@
|
|
|
1439
1456
|
"web.sessions.none_active": "— no active session —",
|
|
1440
1457
|
"web.sessions.none": "— none —",
|
|
1441
1458
|
"web.sessions.delete": "delete",
|
|
1459
|
+
"web.sessions.search": "search sessions…",
|
|
1442
1460
|
"web.sessions.create_failed": "session create failed",
|
|
1443
1461
|
"web.sessions.delete_failed": "delete failed",
|
|
1444
1462
|
"web.sessions.confirm_title": "Delete session?",
|
|
@@ -1451,6 +1469,7 @@
|
|
|
1451
1469
|
"web.selectors.switch_busy": "switch in progress, please wait",
|
|
1452
1470
|
"web.selectors.reasoning_off": "off (backend ignores it)",
|
|
1453
1471
|
"web.selectors.no_models": "no models",
|
|
1472
|
+
"web.selectors.search_models": "search models…",
|
|
1454
1473
|
"web.selectors.refresh_list": "↻ refresh list",
|
|
1455
1474
|
"web.selectors.custom": "custom…",
|
|
1456
1475
|
"web.selectors.current": "current",
|
package/dist/i18n/ru.json
CHANGED
|
@@ -1090,6 +1090,18 @@
|
|
|
1090
1090
|
"web.sidebar.trace_text": "печатает… {count} симв.",
|
|
1091
1091
|
"web.sidebar.trace_plan": "обновление плана",
|
|
1092
1092
|
"web.sidebar.trace_question": "вопрос",
|
|
1093
|
+
"web.sidebar.trace_status": "статус",
|
|
1094
|
+
"web.sidebar.trace_log": "лог · {level}",
|
|
1095
|
+
"web.sidebar.trace_retract": "черновик отозван",
|
|
1096
|
+
"web.sidebar.trace_index": "индекс · {phase}",
|
|
1097
|
+
"web.sidebar.trace_critic": "критик · {status}",
|
|
1098
|
+
"web.sidebar.trace_moe": "moe",
|
|
1099
|
+
"web.sidebar.trace_compaction": "компакция",
|
|
1100
|
+
"web.sidebar.trace_probe": "проба контекста",
|
|
1101
|
+
"web.sidebar.trace_usage": "расход llm",
|
|
1102
|
+
"web.sidebar.trace_audit": "аудит",
|
|
1103
|
+
"web.sidebar.trace_claim": "перепроверка · {kind}",
|
|
1104
|
+
"web.sidebar.trace_output": "вывод",
|
|
1093
1105
|
"web.sidebar.detail_metadata": "метаданные",
|
|
1094
1106
|
"web.sidebar.detail_arguments": "аргументы",
|
|
1095
1107
|
"web.sidebar.detail_output": "вывод",
|
|
@@ -1104,6 +1116,10 @@
|
|
|
1104
1116
|
"web.sidebar.detail_time": "время",
|
|
1105
1117
|
"web.sidebar.detail_tool_call": "вызов тула",
|
|
1106
1118
|
"web.sidebar.detail_session": "сессия",
|
|
1119
|
+
"web.sidebar.detail_turn": "ход",
|
|
1120
|
+
"web.sidebar.detail_iteration": "итерация",
|
|
1121
|
+
"web.sidebar.detail_provenance": "сверка с логом",
|
|
1122
|
+
"web.sidebar.detail_provenance_count": "assistant-сообщений в логе: {n}",
|
|
1107
1123
|
"web.env.dir": "директория",
|
|
1108
1124
|
"web.env.agents": "AGENTS.md",
|
|
1109
1125
|
"web.env.map": "карта",
|
|
@@ -1396,9 +1412,10 @@
|
|
|
1396
1412
|
"web.input.shift_enter": "Shift+Enter — новая строка",
|
|
1397
1413
|
"web.input.esc": "Esc — стоп",
|
|
1398
1414
|
"web.input.attach": "Ctrl+V — вложить",
|
|
1399
|
-
"web.input.busy": "ещё работаю — Esc для остановки",
|
|
1400
1415
|
"web.input.send": "отправить",
|
|
1401
1416
|
"web.input.stop": "остановить",
|
|
1417
|
+
"web.input.queued": "в очереди · позиция {n}",
|
|
1418
|
+
"web.input.queue_full": "очередь сообщений полна — дождись конца хода",
|
|
1402
1419
|
"web.empty.title": "новый чат",
|
|
1403
1420
|
"web.empty.hint": "введи сообщение ниже · / — команды · модель и контекст выбираются над полем",
|
|
1404
1421
|
"web.attach.title": "прикрепить изображение",
|
|
@@ -1415,6 +1432,7 @@
|
|
|
1415
1432
|
"web.sessions.none_active": "— нет активной сессии —",
|
|
1416
1433
|
"web.sessions.none": "— нет —",
|
|
1417
1434
|
"web.sessions.delete": "удалить",
|
|
1435
|
+
"web.sessions.search": "поиск сессий…",
|
|
1418
1436
|
"web.sessions.create_failed": "не удалось создать сессию",
|
|
1419
1437
|
"web.sessions.delete_failed": "не удалось удалить",
|
|
1420
1438
|
"web.sessions.confirm_title": "Удалить сессию?",
|
|
@@ -1427,6 +1445,7 @@
|
|
|
1427
1445
|
"web.selectors.switch_busy": "переключение уже идёт, подождите",
|
|
1428
1446
|
"web.selectors.reasoning_off": "выкл (бэкенд игнорирует)",
|
|
1429
1447
|
"web.selectors.no_models": "нет моделей",
|
|
1448
|
+
"web.selectors.search_models": "поиск моделей…",
|
|
1430
1449
|
"web.selectors.refresh_list": "↻ обновить список",
|
|
1431
1450
|
"web.selectors.custom": "другое…",
|
|
1432
1451
|
"web.selectors.current": "текущее",
|