micro-models-agent 2.12.0 → 2.14.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 +35 -0
- package/dist/assets/app.js +22 -2
- package/dist/assets/index.html +39 -9
- package/dist/assets/js/dom.js +15 -0
- package/dist/assets/js/feed.js +199 -45
- package/dist/assets/js/sidebar.js +512 -0
- package/dist/assets/js/tabs.js +8 -2
- package/dist/assets/setup.js +267 -8
- package/dist/assets/styles.css +185 -28
- package/dist/i18n/en.json +92 -7
- package/dist/i18n/ru.json +92 -7
- package/dist/main.js +634 -101
- package/package.json +1 -1
package/dist/assets/setup.js
CHANGED
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
securityBashBlock: false,
|
|
104
104
|
securityFlagsBlock: false,
|
|
105
105
|
securityPathsRestrict: true,
|
|
106
|
+
securityDetail: { bash: {}, paths: {}, flags: {}, network: {}, advanced: {} },
|
|
106
107
|
},
|
|
107
108
|
};
|
|
108
109
|
|
|
@@ -118,6 +119,12 @@
|
|
|
118
119
|
state.answers.contextWindow = r.data.defaults.contextWindow || state.answers.contextWindow;
|
|
119
120
|
state.answers.maxToolIterations =
|
|
120
121
|
r.data.defaults.maxToolIterations || state.answers.maxToolIterations;
|
|
122
|
+
if (r.data.defaults.security) {
|
|
123
|
+
state.answers.securityDetail = Object.assign(
|
|
124
|
+
state.answers.securityDetail,
|
|
125
|
+
r.data.defaults.security,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
121
128
|
}
|
|
122
129
|
const first = (r.data.providers || [])[0];
|
|
123
130
|
if (first) {
|
|
@@ -137,6 +144,7 @@
|
|
|
137
144
|
// --- rendering -------------------------------------------------------------
|
|
138
145
|
|
|
139
146
|
function render() {
|
|
147
|
+
closePopup();
|
|
140
148
|
const wrap = h("div", "setup-wrap");
|
|
141
149
|
const title = h("h1", "setup-h");
|
|
142
150
|
title.appendChild(h("span", "glyph", "◇"));
|
|
@@ -258,6 +266,27 @@
|
|
|
258
266
|
});
|
|
259
267
|
box.appendChild(field(tr("web.setup.provider.base_url"), input));
|
|
260
268
|
|
|
269
|
+
const actions = h("div", "setup-row-inline");
|
|
270
|
+
const test = h("button", "setup-btn", tr("web.setup.provider.test"));
|
|
271
|
+
test.type = "button";
|
|
272
|
+
test.addEventListener("click", async () => {
|
|
273
|
+
if (!state.answers.apiBase) {
|
|
274
|
+
setMsg(box, "err", tr("web.setup.provider.required"));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
test.disabled = true;
|
|
278
|
+
setMsg(box, "", tr("web.setup.provider.testing"));
|
|
279
|
+
const r = await api("/api/setup/models", {
|
|
280
|
+
method: "POST",
|
|
281
|
+
body: { baseUrl: state.answers.apiBase, apiKey: state.answers.apiKey },
|
|
282
|
+
});
|
|
283
|
+
test.disabled = false;
|
|
284
|
+
const models = (r.ok && r.data && r.data.models) || [];
|
|
285
|
+
if (r.ok && models.length) setMsg(box, "ok", tr("web.setup.provider.test_ok"));
|
|
286
|
+
else setMsg(box, "warn", tr("web.setup.provider.test_fail"));
|
|
287
|
+
});
|
|
288
|
+
actions.appendChild(test);
|
|
289
|
+
|
|
261
290
|
if (state.answers.provider === "openai-compat" || state.answers.provider === "lmstudio") {
|
|
262
291
|
const scan = h("button", "setup-btn", tr("web.setup.provider.scan"));
|
|
263
292
|
scan.type = "button";
|
|
@@ -291,8 +320,11 @@
|
|
|
291
320
|
results.appendChild(b);
|
|
292
321
|
});
|
|
293
322
|
});
|
|
294
|
-
|
|
323
|
+
actions.appendChild(scan);
|
|
324
|
+
box.appendChild(actions);
|
|
295
325
|
box.appendChild(results);
|
|
326
|
+
} else {
|
|
327
|
+
box.appendChild(actions);
|
|
296
328
|
}
|
|
297
329
|
box.appendChild(messageEl());
|
|
298
330
|
return box;
|
|
@@ -393,6 +425,38 @@
|
|
|
393
425
|
});
|
|
394
426
|
box.appendChild(field(tr("web.setup.context.contextWindow"), cw));
|
|
395
427
|
|
|
428
|
+
const detect = h("button", "setup-btn", tr("web.setup.context.detect"));
|
|
429
|
+
detect.type = "button";
|
|
430
|
+
detect.addEventListener("click", async () => {
|
|
431
|
+
if (!state.answers.apiBase || !state.answers.model) {
|
|
432
|
+
setMsg(box, "err", tr("web.setup.context.detect_fail"));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
detect.disabled = true;
|
|
436
|
+
setMsg(box, "", tr("web.setup.context.detecting"));
|
|
437
|
+
const r = await api("/api/setup/context", {
|
|
438
|
+
method: "POST",
|
|
439
|
+
body: {
|
|
440
|
+
baseUrl: state.answers.apiBase,
|
|
441
|
+
apiKey: state.answers.apiKey,
|
|
442
|
+
model: state.answers.model,
|
|
443
|
+
providerType: state.answers.provider,
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
detect.disabled = false;
|
|
447
|
+
const n = r.ok && r.data && r.data.contextWindow;
|
|
448
|
+
if (r.ok && r.data && r.data.ok && typeof n === "number" && n > 0) {
|
|
449
|
+
state.answers.contextWindow = n;
|
|
450
|
+
cw.value = String(n);
|
|
451
|
+
setMsg(box, "ok", tr("web.setup.context.detect_ok", { count: n }));
|
|
452
|
+
} else {
|
|
453
|
+
setMsg(box, "warn", tr("web.setup.context.detect_fail"));
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
const row = h("div", "setup-row-inline");
|
|
457
|
+
row.appendChild(detect);
|
|
458
|
+
box.appendChild(row);
|
|
459
|
+
|
|
396
460
|
const it = h("input", "setup-input");
|
|
397
461
|
it.type = "number";
|
|
398
462
|
it.min = "1";
|
|
@@ -408,25 +472,219 @@
|
|
|
408
472
|
function renderSecurity() {
|
|
409
473
|
const box = stepBody("security");
|
|
410
474
|
const toggles = [
|
|
411
|
-
["securityPathsRestrict", "web.setup.security.paths"],
|
|
412
|
-
["securityBashBlock", "web.setup.security.bash"],
|
|
413
|
-
["securityFlagsBlock", "web.setup.security.flags"],
|
|
475
|
+
["securityPathsRestrict", "web.setup.security.paths", "web.setup.security.paths_desc", "paths"],
|
|
476
|
+
["securityBashBlock", "web.setup.security.bash", "web.setup.security.bash_desc", "bash"],
|
|
477
|
+
["securityFlagsBlock", "web.setup.security.flags", "web.setup.security.flags_desc", "flags"],
|
|
414
478
|
];
|
|
415
|
-
toggles.forEach(([key, labelKey]) => {
|
|
416
|
-
const row = h("
|
|
479
|
+
toggles.forEach(([key, labelKey, descKey, group]) => {
|
|
480
|
+
const row = h("div", "setup-sec-row");
|
|
481
|
+
const label = h("label", "setup-toggle");
|
|
417
482
|
const cb = h("input");
|
|
418
483
|
cb.type = "checkbox";
|
|
419
484
|
cb.checked = Boolean(state.answers[key]);
|
|
420
485
|
cb.addEventListener("change", () => {
|
|
421
486
|
state.answers[key] = cb.checked;
|
|
422
487
|
});
|
|
423
|
-
|
|
424
|
-
|
|
488
|
+
label.appendChild(cb);
|
|
489
|
+
const texts = h("span", "setup-toggle-texts");
|
|
490
|
+
texts.appendChild(h("span", "t", tr(labelKey)));
|
|
491
|
+
const desc = tr(descKey);
|
|
492
|
+
if (desc !== descKey) texts.appendChild(h("span", "d", desc));
|
|
493
|
+
label.appendChild(texts);
|
|
494
|
+
row.appendChild(label);
|
|
495
|
+
row.appendChild(detailButton(group, tr(labelKey)));
|
|
425
496
|
box.appendChild(row);
|
|
426
497
|
});
|
|
498
|
+
|
|
499
|
+
const advRow = h("div", "setup-sec-row");
|
|
500
|
+
const advTexts = h("span", "setup-toggle setup-adv-label");
|
|
501
|
+
advTexts.appendChild(h("span", "t", tr("web.setup.security.advanced")));
|
|
502
|
+
advRow.appendChild(advTexts);
|
|
503
|
+
advRow.appendChild(detailButton("advanced", tr("web.setup.security.advanced")));
|
|
504
|
+
box.appendChild(advRow);
|
|
427
505
|
return box;
|
|
428
506
|
}
|
|
429
507
|
|
|
508
|
+
// Описание поп-апов тонкой настройки: путь в securityDetail + тип поля.
|
|
509
|
+
const SECURITY_POPUPS = {
|
|
510
|
+
paths: [
|
|
511
|
+
["paths.denied", "list", "web.setup.security.denied"],
|
|
512
|
+
["paths.allowed", "list", "web.setup.security.allowed"],
|
|
513
|
+
],
|
|
514
|
+
bash: [
|
|
515
|
+
["bash.blacklist", "list", "web.setup.security.blacklist"],
|
|
516
|
+
["bash.whitelist", "list", "web.setup.security.whitelist"],
|
|
517
|
+
["bash.logCommands", "bool", "web.setup.security.log_commands"],
|
|
518
|
+
],
|
|
519
|
+
flags: [
|
|
520
|
+
["flags.dangerousFlags", "list", "web.setup.security.dangerous_flags"],
|
|
521
|
+
["flags.dangerousOperators", "list", "web.setup.security.dangerous_operators"],
|
|
522
|
+
],
|
|
523
|
+
advanced: [
|
|
524
|
+
["network.enabled", "bool", "web.setup.security.network_enabled"],
|
|
525
|
+
["network.deniedDomains", "list", "web.setup.security.denied_domains"],
|
|
526
|
+
["network.allowedDomains", "list", "web.setup.security.allowed_domains"],
|
|
527
|
+
["network.requestTimeout", "int", "web.setup.security.request_timeout"],
|
|
528
|
+
["network.maxResponseSize", "int", "web.setup.security.max_response_size"],
|
|
529
|
+
["advanced.maxRecursionDepth", "int", "web.setup.security.max_recursion"],
|
|
530
|
+
["advanced.maxFileOperations", "int", "web.setup.security.max_file_ops"],
|
|
531
|
+
["advanced.maxRequestsPerMinute", "int", "web.setup.security.rate_rpm"],
|
|
532
|
+
["advanced.maxParallelTasks", "int", "web.setup.security.rate_parallel"],
|
|
533
|
+
["advanced.contentScanEnabled", "bool", "web.setup.security.content_scan"],
|
|
534
|
+
["advanced.sessionEncryptionEnabled", "bool", "web.setup.security.session_encryption"],
|
|
535
|
+
["advanced.auditNotifierEnabled", "bool", "web.setup.security.audit_enabled"],
|
|
536
|
+
["advanced.auditNotifierMinSeverity", "enum", "web.setup.security.audit_severity"],
|
|
537
|
+
["advanced.auditNotifierEventTypes", "list", "web.setup.security.audit_events"],
|
|
538
|
+
["advanced.auditNotifierMaxRetries", "int", "web.setup.security.audit_retries"],
|
|
539
|
+
["advanced.auditNotifierWebhookTimeout", "int", "web.setup.security.audit_timeout"],
|
|
540
|
+
],
|
|
541
|
+
};
|
|
542
|
+
const SEVERITIES = ["low", "medium", "high", "critical"];
|
|
543
|
+
const GROUP_KEYS = {
|
|
544
|
+
paths: ["paths"],
|
|
545
|
+
bash: ["bash"],
|
|
546
|
+
flags: ["flags"],
|
|
547
|
+
advanced: ["network", "advanced"],
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
function getPath(obj, path) {
|
|
551
|
+
return path.split(".").reduce((o, k) => (o == null ? undefined : o[k]), obj);
|
|
552
|
+
}
|
|
553
|
+
function setPath(obj, path, value) {
|
|
554
|
+
const keys = path.split(".");
|
|
555
|
+
let cur = obj;
|
|
556
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
557
|
+
if (typeof cur[keys[i]] !== "object" || cur[keys[i]] === null) cur[keys[i]] = {};
|
|
558
|
+
cur = cur[keys[i]];
|
|
559
|
+
}
|
|
560
|
+
cur[keys[keys.length - 1]] = value;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function detailButton(group, title) {
|
|
564
|
+
const btn = h("button", "setup-detail-btn");
|
|
565
|
+
btn.type = "button";
|
|
566
|
+
btn.appendChild(h("span", "g", "⚙"));
|
|
567
|
+
btn.appendChild(h("span", "t", tr("web.setup.security.configure")));
|
|
568
|
+
btn.title = title;
|
|
569
|
+
btn.addEventListener("click", (e) => {
|
|
570
|
+
e.preventDefault();
|
|
571
|
+
openSecurityPopup(group, title);
|
|
572
|
+
});
|
|
573
|
+
return btn;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let popup = null;
|
|
577
|
+
|
|
578
|
+
function openSecurityPopup(group, title) {
|
|
579
|
+
const overlay = h("div", "setup-overlay");
|
|
580
|
+
const modal = h("div", "setup-modal");
|
|
581
|
+
modal.setAttribute("role", "dialog");
|
|
582
|
+
modal.setAttribute("aria-modal", "true");
|
|
583
|
+
|
|
584
|
+
const head = h("div", "setup-modal-head");
|
|
585
|
+
const hIcon = h("span", "glyph", "⚙");
|
|
586
|
+
head.appendChild(hIcon);
|
|
587
|
+
head.appendChild(h("span", "setup-modal-title", title));
|
|
588
|
+
const close = h("button", "setup-modal-x", "✕");
|
|
589
|
+
close.type = "button";
|
|
590
|
+
close.addEventListener("click", closePopup);
|
|
591
|
+
head.appendChild(close);
|
|
592
|
+
modal.appendChild(head);
|
|
593
|
+
|
|
594
|
+
const body = h("div", "setup-modal-body");
|
|
595
|
+
body.appendChild(h("p", "setup-hint", tr("web.setup.security.popup_hint")));
|
|
596
|
+
SECURITY_POPUPS[group].forEach(([path, type, labelKey]) => {
|
|
597
|
+
body.appendChild(popupField(path, type, tr(labelKey)));
|
|
598
|
+
});
|
|
599
|
+
modal.appendChild(body);
|
|
600
|
+
|
|
601
|
+
const foot = h("div", "setup-modal-foot");
|
|
602
|
+
const reset = h("button", "setup-btn link", tr("web.setup.security.reset"));
|
|
603
|
+
reset.type = "button";
|
|
604
|
+
reset.addEventListener("click", () => {
|
|
605
|
+
const defaults = (state.display && state.display.defaults && state.display.defaults.security) || {};
|
|
606
|
+
(GROUP_KEYS[group] || [group]).forEach((src) => {
|
|
607
|
+
const fresh = defaults[src] || {};
|
|
608
|
+
Object.keys(fresh).forEach((k) => setPath(state.answers.securityDetail, src + "." + k, fresh[k]));
|
|
609
|
+
});
|
|
610
|
+
closePopup();
|
|
611
|
+
openSecurityPopup(group, title);
|
|
612
|
+
});
|
|
613
|
+
foot.appendChild(reset);
|
|
614
|
+
foot.appendChild(h("span", "spacer"));
|
|
615
|
+
const done = h("button", "setup-btn primary", tr("web.setup.security.popup_done"));
|
|
616
|
+
done.type = "button";
|
|
617
|
+
done.addEventListener("click", closePopup);
|
|
618
|
+
foot.appendChild(done);
|
|
619
|
+
modal.appendChild(foot);
|
|
620
|
+
|
|
621
|
+
overlay.appendChild(modal);
|
|
622
|
+
overlay.addEventListener("click", (e) => {
|
|
623
|
+
if (e.target === overlay) closePopup();
|
|
624
|
+
});
|
|
625
|
+
popup = overlay;
|
|
626
|
+
gate.appendChild(overlay);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function popupField(path, type, label) {
|
|
630
|
+
const wrap = h("div", "setup-field");
|
|
631
|
+
if (type === "list") {
|
|
632
|
+
wrap.appendChild(h("label", "setup-label", label));
|
|
633
|
+
const ta = h("textarea", "setup-input setup-textarea");
|
|
634
|
+
const cur = getPath(state.answers.securityDetail, path);
|
|
635
|
+
ta.value = Array.isArray(cur) ? cur.join("\n") : "";
|
|
636
|
+
ta.rows = 5;
|
|
637
|
+
ta.spellcheck = false;
|
|
638
|
+
ta.addEventListener("input", () => {
|
|
639
|
+
const items = ta.value
|
|
640
|
+
.split("\n")
|
|
641
|
+
.map((s) => s.trim())
|
|
642
|
+
.filter(Boolean);
|
|
643
|
+
setPath(state.answers.securityDetail, path, items);
|
|
644
|
+
});
|
|
645
|
+
wrap.appendChild(ta);
|
|
646
|
+
} else if (type === "bool") {
|
|
647
|
+
const row = h("label", "setup-toggle");
|
|
648
|
+
const cb = h("input");
|
|
649
|
+
cb.type = "checkbox";
|
|
650
|
+
cb.checked = Boolean(getPath(state.answers.securityDetail, path));
|
|
651
|
+
cb.addEventListener("change", () => setPath(state.answers.securityDetail, path, cb.checked));
|
|
652
|
+
row.appendChild(cb);
|
|
653
|
+
row.appendChild(h("span", "t", label));
|
|
654
|
+
wrap.appendChild(row);
|
|
655
|
+
} else if (type === "int") {
|
|
656
|
+
wrap.appendChild(h("label", "setup-label", label));
|
|
657
|
+
const inp = h("input", "setup-input");
|
|
658
|
+
inp.type = "number";
|
|
659
|
+
inp.min = "1";
|
|
660
|
+
const cur = getPath(state.answers.securityDetail, path);
|
|
661
|
+
inp.value = typeof cur === "number" ? String(cur) : "";
|
|
662
|
+
inp.addEventListener("input", () => {
|
|
663
|
+
const n = parseInt(inp.value, 10);
|
|
664
|
+
setPath(state.answers.securityDetail, path, Number.isFinite(n) && n > 0 ? n : undefined);
|
|
665
|
+
});
|
|
666
|
+
wrap.appendChild(inp);
|
|
667
|
+
} else if (type === "enum") {
|
|
668
|
+
wrap.appendChild(h("label", "setup-label", label));
|
|
669
|
+
const sel = h("select", "setup-input");
|
|
670
|
+
const cur = getPath(state.answers.securityDetail, path);
|
|
671
|
+
SEVERITIES.forEach((sev) => {
|
|
672
|
+
const opt = h("option", null, tr("web.setup.security.severity." + sev));
|
|
673
|
+
opt.value = sev;
|
|
674
|
+
if (cur === sev) opt.selected = true;
|
|
675
|
+
sel.appendChild(opt);
|
|
676
|
+
});
|
|
677
|
+
sel.addEventListener("change", () => setPath(state.answers.securityDetail, path, sel.value));
|
|
678
|
+
wrap.appendChild(sel);
|
|
679
|
+
}
|
|
680
|
+
return wrap;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function closePopup() {
|
|
684
|
+
if (popup && popup.parentNode) popup.parentNode.removeChild(popup);
|
|
685
|
+
popup = null;
|
|
686
|
+
}
|
|
687
|
+
|
|
430
688
|
function securitySummary() {
|
|
431
689
|
const on = [];
|
|
432
690
|
if (state.answers.securityPathsRestrict) on.push(tr("web.setup.security.paths"));
|
|
@@ -534,6 +792,7 @@
|
|
|
534
792
|
securityBashBlock: state.answers.securityBashBlock,
|
|
535
793
|
securityFlagsBlock: state.answers.securityFlagsBlock,
|
|
536
794
|
securityPathsRestrict: state.answers.securityPathsRestrict,
|
|
795
|
+
securityDetail: state.answers.securityDetail,
|
|
537
796
|
},
|
|
538
797
|
});
|
|
539
798
|
if (!r.ok) {
|
package/dist/assets/styles.css
CHANGED
|
@@ -229,7 +229,7 @@ body[data-font="large"] { font-size: 17px; }
|
|
|
229
229
|
.layout { flex: 1; display: flex; min-height: 0; }
|
|
230
230
|
|
|
231
231
|
/* chat */
|
|
232
|
-
.chat { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
|
232
|
+
.chat { flex: 1; display: flex; flex-direction: column; min-width: 0; position: relative; }
|
|
233
233
|
.msgs { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 18px 22px; display: flex; flex-direction: column; gap: 11px; }
|
|
234
234
|
.msgs > * { flex-shrink: 0; min-height: fit-content; }
|
|
235
235
|
/* messages live inside a per-tab `.feed` wrapper; it must mirror the `.msgs`
|
|
@@ -377,31 +377,23 @@ body.light .codeblock { border: 1px solid var(--border); }
|
|
|
377
377
|
}
|
|
378
378
|
body.light .imgcard .ph { background: var(--ph-light); }
|
|
379
379
|
.imgcard .cap { padding: 6px 10px; color: var(--dim-a); font-size: 12px; }
|
|
380
|
-
.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
.
|
|
387
|
-
.
|
|
388
|
-
.
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
.
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
.
|
|
395
|
-
.
|
|
396
|
-
|
|
397
|
-
color: var(--accent); background: transparent; border: 1px solid var(--border-2);
|
|
398
|
-
border-radius: 4px; padding: 2px 10px; cursor: pointer; text-decoration: none;
|
|
399
|
-
}
|
|
400
|
-
.filecard .fc-open:hover { border-color: var(--accent); text-decoration: none; }
|
|
401
|
-
.filecard .fc-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
402
|
-
.filecard button.fc-open { font: inherit; }
|
|
403
|
-
.filecard .fc-actions { display: flex; gap: 6px; align-items: center; margin-left: auto; flex: 0 0 auto; }
|
|
404
|
-
.filecard .fc-hint { color: var(--dim-a); font-size: 12px; }
|
|
380
|
+
.filerow { margin-left: 26px; display: flex; gap: 8px; align-items: baseline; min-width: 0; font-size: 12.5px; color: var(--muted); }
|
|
381
|
+
.filerow .g { flex: 0 0 auto; }
|
|
382
|
+
.filerow.recon .g, .filerow.recon .lab { color: var(--cyan); }
|
|
383
|
+
.filerow.mutate .g, .filerow.mutate .lab { color: var(--magenta); }
|
|
384
|
+
.filerow .sep { color: var(--dim-b); }
|
|
385
|
+
.filerow a.path { color: var(--accent); text-decoration: none; overflow-wrap: anywhere; word-break: break-word; }
|
|
386
|
+
.filerow a.path:hover { text-decoration: underline; }
|
|
387
|
+
.filerow .tail { color: var(--dim-a); overflow-wrap: anywhere; word-break: break-word; }
|
|
388
|
+
.filerow .copy-btn { background: transparent; border: 0; padding: 2px; color: var(--dim-a); cursor: pointer; opacity: 0; }
|
|
389
|
+
.filerow:hover .copy-btn, .filerow:focus-within .copy-btn { opacity: 1; }
|
|
390
|
+
@media (pointer: coarse) { .filerow .copy-btn { opacity: 1; } }
|
|
391
|
+
.filerow .copy-btn:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
392
|
+
.filerow .copy-btn .svg-icon { width: 14px; height: 14px; }
|
|
393
|
+
.filegroup { margin-left: 26px; font-size: 12.5px; color: var(--muted); }
|
|
394
|
+
.filegroup .head { background: transparent; border: 0; color: inherit; font: inherit; cursor: pointer; display: flex; gap: 6px; align-items: baseline; padding: 0; }
|
|
395
|
+
.filegroup .items.hidden { display: none; }
|
|
396
|
+
.filegroup .items > .filerow { margin-left: 0; }
|
|
405
397
|
.linkline { margin-left: 26px; min-width: 0; overflow-wrap: anywhere; }
|
|
406
398
|
.linkline a { color: var(--accent); text-decoration: none; overflow-wrap: anywhere; word-break: break-word; }
|
|
407
399
|
.linkline a:hover { text-decoration: underline; }
|
|
@@ -452,6 +444,18 @@ body.light .imgcard .ph { background: var(--ph-light); }
|
|
|
452
444
|
}
|
|
453
445
|
.empty .g { color: var(--green); }
|
|
454
446
|
|
|
447
|
+
/* jump-to-latest pill: only visible when the reader scrolled up */
|
|
448
|
+
.to-latest {
|
|
449
|
+
position: absolute; left: 50%; bottom: 148px; transform: translateX(-50%);
|
|
450
|
+
z-index: 20; font: inherit; font-size: 12.5px; cursor: pointer;
|
|
451
|
+
color: var(--accent); background: var(--panel);
|
|
452
|
+
border: 1px solid var(--border-2); border-radius: 999px;
|
|
453
|
+
padding: 5px 14px; box-shadow: var(--shadow-menu);
|
|
454
|
+
white-space: nowrap;
|
|
455
|
+
}
|
|
456
|
+
.to-latest:hover { border-color: var(--accent); }
|
|
457
|
+
.to-latest[hidden] { display: none; }
|
|
458
|
+
|
|
455
459
|
/* ---- composer: selectors, attachments, input, autocomplete ---- */
|
|
456
460
|
.input-area { margin: 14px 22px 16px; position: relative; }
|
|
457
461
|
.selectors { display: flex; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; min-width: 0; }
|
|
@@ -590,6 +594,61 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
590
594
|
.input-area.dragover .composer { border-color: var(--accent); box-shadow: 0 0 0 2px var(--input-shadow); }
|
|
591
595
|
@keyframes blink { 50% { opacity: 0; } }
|
|
592
596
|
|
|
597
|
+
/* ---- chat motion ("magic"): entrances, caret, pulses, pops -----------------
|
|
598
|
+
Rules: only opacity/transform animations (never height/margin), 100–200ms,
|
|
599
|
+
calm single pulses. Killed globally by the `prefers-reduced-motion` block
|
|
600
|
+
below. Glyphs stay textual per STYLE.md — motion, not decoration. */
|
|
601
|
+
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
|
602
|
+
@keyframes pop { from { opacity: 0; transform: scale(.98) translateY(2px); } to { opacity: 1; transform: none; } }
|
|
603
|
+
@keyframes softPulse { 50% { opacity: .45; } }
|
|
604
|
+
@keyframes fadeIn { from { opacity: 0; } }
|
|
605
|
+
|
|
606
|
+
/* new rows rise in softly (a bulk history render animates once as a whole —
|
|
607
|
+
one shared fade, still cheap: opacity/transform only) */
|
|
608
|
+
.feed > :is(.u, .md, .act, .think, .q, .filerow, .filegroup, .planblock, .imgcard, .linkline,
|
|
609
|
+
.toolout, .metric, .detail, .metaln, .warnline, .errline, .cmdout, .sysline,
|
|
610
|
+
.startbanner, .grouped, .msg-actions, .busy) {
|
|
611
|
+
animation: rise .18s ease-out;
|
|
612
|
+
}
|
|
613
|
+
/* quiet-mode run folding fades instead of snapping away */
|
|
614
|
+
.fold-out { opacity: 0; transition: opacity .15s ease-out; }
|
|
615
|
+
|
|
616
|
+
/* streaming caret at the tail of live markdown */
|
|
617
|
+
.md.streaming::after {
|
|
618
|
+
content: "▌"; color: var(--accent); margin-left: 2px;
|
|
619
|
+
animation: blink 1s steps(2, start) infinite;
|
|
620
|
+
}
|
|
621
|
+
/* alive labels: in-feed spinner, the *live* think block, rail phase,
|
|
622
|
+
a running tool. Finished blocks stay static — a pulsing "думаю" after
|
|
623
|
+
the turn reads as hung. */
|
|
624
|
+
.busy .lab, .think.live .think-h .lab, .rail-l1 .lab, .act.running .lab {
|
|
625
|
+
animation: softPulse 1.6s ease-in-out infinite;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* menus, autocomplete and dialogs pop instead of snapping */
|
|
629
|
+
.dd.open .dd-menu, .sel.open .menu, .cmdk:not([hidden]) { animation: pop .12s ease-out; }
|
|
630
|
+
.to-latest:not([hidden]) { animation: pop .15s ease-out; }
|
|
631
|
+
.overlay.open { animation: fadeIn .15s ease-out; }
|
|
632
|
+
.overlay.open .modal { animation: rise .18s ease-out; }
|
|
633
|
+
|
|
634
|
+
/* hover/active blends */
|
|
635
|
+
.sel > button, .menu .mi, .stab, .q .qopt, .q .qsend, .filerow, .filegroup, .icon-btn,
|
|
636
|
+
.btn, .linkbtn, .chip, .cb-copy, .act-btn, .copy-btn, .composer .attach-btn,
|
|
637
|
+
.composer .send-btn, .dd-menu .si, .dd-menu .act, .cmdk-item, .toggle {
|
|
638
|
+
transition: background-color .12s, color .12s, border-color .12s;
|
|
639
|
+
}
|
|
640
|
+
.composer { transition: border-color .15s, box-shadow .15s; }
|
|
641
|
+
|
|
642
|
+
/* scroll edge fades on the transcript (classes from the feed scroll hook) */
|
|
643
|
+
.msgs::before, .msgs::after {
|
|
644
|
+
content: ""; position: sticky; display: block; height: 22px; flex: 0 0 auto;
|
|
645
|
+
pointer-events: none; z-index: 5; opacity: 0; transition: opacity .25s;
|
|
646
|
+
}
|
|
647
|
+
.msgs::before { top: 0; margin-bottom: -22px; background: linear-gradient(var(--bg), transparent); }
|
|
648
|
+
.msgs::after { bottom: 0; margin-top: -22px; background: linear-gradient(transparent, var(--bg)); }
|
|
649
|
+
.msgs.scrolled::before { opacity: 1; }
|
|
650
|
+
.msgs.unfollowed::after { opacity: 1; }
|
|
651
|
+
|
|
593
652
|
/* ---- activity rail — indicator above the composer while a turn runs ----
|
|
594
653
|
Reproduces docs/design/ui/demo-activity-rail-chat.html (variant №12).
|
|
595
654
|
`l-line` (default) = compact one-liner, `l-meta` = inside the hint row,
|
|
@@ -686,6 +745,66 @@ body.light .sel > button { border: 1px solid var(--border); }
|
|
|
686
745
|
.sec.plan-sec { flex: 1 1 auto; }
|
|
687
746
|
.sec.usage-sec { flex: 0 1 auto; max-height: 45%; }
|
|
688
747
|
|
|
748
|
+
/* ---- sidebar tabs ---- */
|
|
749
|
+
.sb-tabs { display: flex; gap: 6px; padding: 10px 14px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
|
750
|
+
.sb-tab {
|
|
751
|
+
position: relative; width: 34px; height: 34px; flex: 0 0 auto;
|
|
752
|
+
display: inline-flex; align-items: center; justify-content: center; padding: 0;
|
|
753
|
+
border: 1px solid var(--border-2); border-radius: 6px;
|
|
754
|
+
background: transparent; color: var(--stab-fg); cursor: pointer; font: inherit;
|
|
755
|
+
}
|
|
756
|
+
.sb-tab:hover { color: var(--fg); background: var(--hover-bg); }
|
|
757
|
+
.sb-tab.on { color: var(--accent); background: var(--accent-bg); border-color: transparent; }
|
|
758
|
+
.sb-tab .sb-ico svg { width: 17px; height: 17px; }
|
|
759
|
+
.sb-tab .sb-dot {
|
|
760
|
+
position: absolute; top: 4px; right: 4px; width: 6px; height: 6px; border-radius: 50%;
|
|
761
|
+
background: var(--accent); opacity: 0; transition: opacity .2s;
|
|
762
|
+
}
|
|
763
|
+
.sb-tab[data-indicator="live"] .sb-dot { opacity: 1; animation: sbPulse 1s ease-in-out infinite; }
|
|
764
|
+
.sb-tab[data-indicator="busy"] .sb-dot { opacity: 1; background: var(--accent); }
|
|
765
|
+
.sb-tab[data-indicator="stale"] .sb-dot { opacity: 1; background: var(--yellow); }
|
|
766
|
+
@keyframes sbPulse { 0%,100% { opacity: 1 } 50% { opacity: .25 } }
|
|
767
|
+
|
|
768
|
+
.sb-name {
|
|
769
|
+
display: flex; align-items: center; gap: 8px; flex-shrink: 0;
|
|
770
|
+
padding: 10px 16px; color: var(--fg); font-size: 12.5px; font-weight: 600;
|
|
771
|
+
letter-spacing: .03em; border-bottom: 1px solid var(--border);
|
|
772
|
+
}
|
|
773
|
+
.sb-name .sb-ico svg { width: 14px; height: 14px; color: var(--accent); }
|
|
774
|
+
|
|
775
|
+
.sb-pane { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
776
|
+
.sb-pane[hidden] { display: none; }
|
|
777
|
+
|
|
778
|
+
/* master-detail expansion */
|
|
779
|
+
.sb { transition: width .22s ease, min-width .22s ease; }
|
|
780
|
+
.sb.sb-expanded { width: 680px; min-width: 680px; }
|
|
781
|
+
#pane-trace { flex-direction: row; }
|
|
782
|
+
.sb-expanded #pane-trace { position: relative; }
|
|
783
|
+
.trace-list { flex: 1; min-width: 0; overflow-y: auto; padding: 6px 10px 12px; display: flex; flex-direction: column; gap: 1px; }
|
|
784
|
+
.trace-turn { color: var(--dim-c); font-size: 11px; margin: 8px 6px 2px; }
|
|
785
|
+
.trc { display: flex; gap: 8px; align-items: baseline; padding: 4px 6px; font-size: 12px; color: var(--muted); border-radius: 4px; cursor: pointer; }
|
|
786
|
+
.trc:hover { background: var(--hover-bg); }
|
|
787
|
+
.trc.sel { background: var(--accent-bg); }
|
|
788
|
+
.trc .g { flex: 0 0 auto; }
|
|
789
|
+
.trc.recon .g { color: var(--cyan); } .trc.mutate .g { color: var(--purple); }
|
|
790
|
+
.trc.execute .g { color: var(--accent); } .trc.meta .g { color: var(--meta); } .trc.phase .g { color: var(--think); }
|
|
791
|
+
.trc .lab { color: var(--fg); white-space: nowrap; }
|
|
792
|
+
.trc .args { color: var(--args); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
|
|
793
|
+
.trc .t { color: var(--dim-b); margin-left: auto; flex: 0 0 auto; }
|
|
794
|
+
.trc.warn .g, .trc.warn .lab { color: var(--yellow); }
|
|
795
|
+
.trc.phase .lab { color: var(--think); }
|
|
796
|
+
.trc-detail { display: flex; flex-direction: column; flex: 0 0 340px; min-width: 0; border-left: 1px solid var(--border); }
|
|
797
|
+
.trc-detail[hidden] { display: none; }
|
|
798
|
+
.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; }
|
|
799
|
+
.detail-h .g { color: var(--accent); }
|
|
800
|
+
.detail-h .x { margin-left: auto; color: var(--dim-b); cursor: pointer; }
|
|
801
|
+
.detail-b { padding: 10px 14px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; }
|
|
802
|
+
.detail-b h5 { margin: 0 0 4px; font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--sec-h); font-weight: 600; }
|
|
803
|
+
.map-sec { flex: 1 1 auto; }
|
|
804
|
+
.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; }
|
|
805
|
+
.map-reindex:disabled { color: var(--dim-b); cursor: default; }
|
|
806
|
+
.kv.wn b { color: var(--yellow); }
|
|
807
|
+
|
|
689
808
|
::-webkit-scrollbar { width: 8px; }
|
|
690
809
|
::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 4px; }
|
|
691
810
|
|
|
@@ -1212,6 +1331,7 @@ button.toggle:disabled { opacity: .5; cursor: default; }
|
|
|
1212
1331
|
.chat { min-height: 0; }
|
|
1213
1332
|
/* Phones: sidebar hidden, opens as an overlay drawer via body.sb-open. */
|
|
1214
1333
|
.sb { display: none; }
|
|
1334
|
+
.sb-tab[data-tab="trace"], #pane-trace { display: none; }
|
|
1215
1335
|
body.sb-open .sb {
|
|
1216
1336
|
display: flex; position: fixed; top: 41px; right: 0; bottom: 0; z-index: 45;
|
|
1217
1337
|
width: min(360px, 85vw); min-width: 0; max-height: none;
|
|
@@ -1349,8 +1469,45 @@ body.setup-active .layout { display: none; }
|
|
|
1349
1469
|
.setup-summary .k { color: var(--dim-a); width: 150px; flex: 0 0 auto; }
|
|
1350
1470
|
.setup-summary .v { color: var(--fg); word-break: break-all; }
|
|
1351
1471
|
|
|
1352
|
-
.setup-toggle { display: flex; align-items:
|
|
1353
|
-
.setup-toggle input { accent-color: var(--green-fill); width: 15px; height: 15px; }
|
|
1472
|
+
.setup-toggle { display: flex; align-items: flex-start; gap: 9px; padding: 7px 0; font-size: 13px; cursor: pointer; }
|
|
1473
|
+
.setup-toggle input { accent-color: var(--green-fill); width: 15px; height: 15px; margin-top: 2px; flex: 0 0 auto; }
|
|
1474
|
+
.setup-toggle-texts { display: flex; flex-direction: column; gap: 2px; }
|
|
1475
|
+
.setup-toggle-texts .t { font-weight: 500; }
|
|
1476
|
+
.setup-toggle-texts .d { color: var(--dim-a); font-size: 12px; line-height: 1.5; }
|
|
1477
|
+
|
|
1478
|
+
.setup-sec-row { display: flex; align-items: center; gap: 8px; }
|
|
1479
|
+
.setup-sec-row .setup-toggle { flex: 1; }
|
|
1480
|
+
.setup-adv-label { padding: 7px 0; }
|
|
1481
|
+
.setup-detail-btn {
|
|
1482
|
+
display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
|
|
1483
|
+
background: transparent; color: var(--dim-a);
|
|
1484
|
+
border: 1px solid var(--border); border-radius: 7px;
|
|
1485
|
+
padding: 5px 9px; font-size: 12px; font-family: inherit; cursor: pointer;
|
|
1486
|
+
}
|
|
1487
|
+
.setup-detail-btn:hover { background: var(--hover-bg); color: var(--fg); }
|
|
1488
|
+
.setup-detail-btn .g { color: var(--accent); }
|
|
1489
|
+
|
|
1490
|
+
.setup-textarea { resize: vertical; min-height: 84px; font-family: var(--mono, ui-monospace, monospace); font-size: 12.5px; line-height: 1.5; }
|
|
1491
|
+
|
|
1492
|
+
.setup-overlay {
|
|
1493
|
+
position: fixed; inset: 0; z-index: 60;
|
|
1494
|
+
background: rgba(0, 0, 0, .55);
|
|
1495
|
+
display: flex; align-items: center; justify-content: center; padding: 20px;
|
|
1496
|
+
}
|
|
1497
|
+
.setup-modal {
|
|
1498
|
+
width: 100%; max-width: 560px; max-height: 86vh; display: flex; flex-direction: column;
|
|
1499
|
+
background: var(--surface); border: 1px solid var(--border-2); border-radius: 10px;
|
|
1500
|
+
box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
|
|
1501
|
+
}
|
|
1502
|
+
.setup-modal-head { display: flex; align-items: center; gap: 8px; padding: 14px 16px; border-bottom: 1px solid var(--border-3); }
|
|
1503
|
+
.setup-modal-head .glyph { color: var(--accent); }
|
|
1504
|
+
.setup-modal-title { font-weight: 600; font-size: 14px; flex: 1; }
|
|
1505
|
+
.setup-modal-x { background: none; border: none; color: var(--dim-a); font-size: 15px; cursor: pointer; padding: 2px 6px; }
|
|
1506
|
+
.setup-modal-x:hover { color: var(--fg); }
|
|
1507
|
+
.setup-modal-body { padding: 14px 16px; overflow-y: auto; }
|
|
1508
|
+
.setup-modal-body .setup-field:last-child { margin-bottom: 0; }
|
|
1509
|
+
.setup-modal-foot { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-top: 1px solid var(--border-3); }
|
|
1510
|
+
.setup-modal-foot .spacer { flex: 1; }
|
|
1354
1511
|
|
|
1355
1512
|
@media (max-width: 640px) {
|
|
1356
1513
|
.setup-gate { padding: 24px 14px; }
|