openclacky 1.5.21 → 1.5.22
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/lib/clacky/billing/platform_billing.rb +3 -0
- data/lib/clacky/default_extensions/git/panels/git/view.js +1911 -398
- data/lib/clacky/default_extensions/preview/panels/preview/view.js +199 -34
- data/lib/clacky/providers.rb +43 -17
- data/lib/clacky/search_config.rb +33 -11
- data/lib/clacky/server/git_panel.rb +360 -34
- data/lib/clacky/server/http_server.rb +145 -47
- data/lib/clacky/server/preview.rb +118 -17
- data/lib/clacky/server/scheduler.rb +81 -2
- data/lib/clacky/server/web_ui_controller.rb +35 -27
- data/lib/clacky/session_manager.rb +52 -5
- data/lib/clacky/utils/mac_app_detector.rb +8 -7
- data/lib/clacky/utils/model_pricing.rb +60 -2
- data/lib/clacky/utils/windows_app_detector.rb +3 -1
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +650 -96
- data/lib/clacky/web/app.js +59 -6
- data/lib/clacky/web/auth.js +1 -1
- data/lib/clacky/web/components/composer.js +107 -8
- data/lib/clacky/web/components/image-zoom.js +165 -0
- data/lib/clacky/web/components/model-picker.js +6 -4
- data/lib/clacky/web/components/notify.js +2 -2
- data/lib/clacky/web/core/aside.js +60 -3
- data/lib/clacky/web/features/billing/view.js +2 -2
- data/lib/clacky/web/features/channels/view.js +5 -2
- data/lib/clacky/web/features/extensions/view.js +2 -2
- data/lib/clacky/web/features/new-session/store.js +79 -2
- data/lib/clacky/web/features/new-session/view.js +496 -58
- data/lib/clacky/web/features/skills/store.js +1 -1
- data/lib/clacky/web/features/tasks/view.js +162 -51
- data/lib/clacky/web/features/workspace/store.js +13 -6
- data/lib/clacky/web/features/workspace/view.js +153 -163
- data/lib/clacky/web/i18n.js +238 -56
- data/lib/clacky/web/index.html +173 -157
- data/lib/clacky/web/projects.js +37 -4
- data/lib/clacky/web/sessions.js +181 -105
- data/lib/clacky/web/settings.js +26 -8
- data/lib/clacky/web/skills.js +2 -2
- data/lib/clacky/web/theme.js +17 -2
- data/lib/clacky/web/utils.js +17 -2
- data/lib/clacky/web/weixin-qr.html +2 -1
- data/lib/clacky/web/ws-dispatcher.js +8 -29
- metadata +2 -1
data/lib/clacky/web/app.js
CHANGED
|
@@ -1021,10 +1021,22 @@ window.bootAfterBrand = async function() {
|
|
|
1021
1021
|
|
|
1022
1022
|
// ── Image Lightbox ────────────────────────────────────────────────────────────
|
|
1023
1023
|
// Global lightbox: click a sent image or a pending attachment thumbnail to
|
|
1024
|
-
// open;
|
|
1024
|
+
// open; wheel and drag inspect details, while backdrop, ✕ and ESC close it.
|
|
1025
1025
|
(function () {
|
|
1026
1026
|
const IMAGE_SELECTOR = ".msg-image-thumb, .img-preview-thumb";
|
|
1027
1027
|
let _overlay = null;
|
|
1028
|
+
let _zoomController = null;
|
|
1029
|
+
let _backdropPointer = null;
|
|
1030
|
+
|
|
1031
|
+
function _zoomButton(label, content) {
|
|
1032
|
+
const button = document.createElement("button");
|
|
1033
|
+
button.type = "button";
|
|
1034
|
+
button.className = "img-lightbox-zoom-btn";
|
|
1035
|
+
button.title = label;
|
|
1036
|
+
button.setAttribute("aria-label", label);
|
|
1037
|
+
button.innerHTML = content;
|
|
1038
|
+
return button;
|
|
1039
|
+
}
|
|
1028
1040
|
|
|
1029
1041
|
function _open(src, alt) {
|
|
1030
1042
|
if (_overlay) return;
|
|
@@ -1039,21 +1051,62 @@ window.bootAfterBrand = async function() {
|
|
|
1039
1051
|
const image = document.createElement("img");
|
|
1040
1052
|
image.src = src;
|
|
1041
1053
|
image.alt = alt || "image";
|
|
1054
|
+
image.draggable = false;
|
|
1055
|
+
|
|
1056
|
+
const toolbar = document.createElement("div");
|
|
1057
|
+
toolbar.className = "img-lightbox-toolbar";
|
|
1058
|
+
const zoomOut = _zoomButton(I18n.t("workspace.zoomOut"), "−");
|
|
1059
|
+
const level = document.createElement("span");
|
|
1060
|
+
level.className = "img-lightbox-zoom-level";
|
|
1061
|
+
const zoomIn = _zoomButton(I18n.t("workspace.zoomIn"), "+");
|
|
1062
|
+
const fit = _zoomButton(
|
|
1063
|
+
I18n.t("workspace.zoomFit"),
|
|
1064
|
+
'<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M16 3h3a2 2 0 0 1 2 2v3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/><path d="M8 21H5a2 2 0 0 1-2-2v-3"/></svg>'
|
|
1065
|
+
);
|
|
1066
|
+
toolbar.append(zoomOut, level, zoomIn, fit);
|
|
1042
1067
|
|
|
1043
1068
|
_overlay.appendChild(close);
|
|
1044
1069
|
_overlay.appendChild(image);
|
|
1070
|
+
_overlay.appendChild(toolbar);
|
|
1045
1071
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1072
|
+
close.addEventListener("click", _close);
|
|
1073
|
+
|
|
1074
|
+
// A drag can finish outside the image and make the browser synthesize a
|
|
1075
|
+
// click on the backdrop. Close only when the whole pointer gesture belongs
|
|
1076
|
+
// to the backdrop, never from the click generated after image panning.
|
|
1077
|
+
_overlay.addEventListener("pointerdown", function(e) {
|
|
1078
|
+
_backdropPointer = e.target === _overlay ? e.pointerId : null;
|
|
1079
|
+
});
|
|
1080
|
+
_overlay.addEventListener("pointerup", function(e) {
|
|
1081
|
+
const closesBackdrop = e.pointerId === _backdropPointer && e.target === _overlay;
|
|
1082
|
+
_backdropPointer = null;
|
|
1083
|
+
if (closesBackdrop) _close();
|
|
1084
|
+
});
|
|
1085
|
+
_overlay.addEventListener("pointercancel", function(e) {
|
|
1086
|
+
if (e.pointerId === _backdropPointer) _backdropPointer = null;
|
|
1051
1087
|
});
|
|
1052
1088
|
|
|
1053
1089
|
document.body.appendChild(_overlay);
|
|
1090
|
+
|
|
1091
|
+
_zoomController = ImageZoom.create({
|
|
1092
|
+
viewport: _overlay,
|
|
1093
|
+
image,
|
|
1094
|
+
fitPadding: 0.9,
|
|
1095
|
+
panOnImageOnly: true,
|
|
1096
|
+
onChange(state) {
|
|
1097
|
+
level.textContent = Math.round(state.scale * 100) + "%";
|
|
1098
|
+
zoomOut.disabled = !state.canZoomOut;
|
|
1099
|
+
zoomIn.disabled = !state.canZoomIn;
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
1102
|
+
zoomOut.addEventListener("click", _zoomController.zoomOut);
|
|
1103
|
+
zoomIn.addEventListener("click", _zoomController.zoomIn);
|
|
1104
|
+
fit.addEventListener("click", _zoomController.reset);
|
|
1054
1105
|
}
|
|
1055
1106
|
|
|
1056
1107
|
function _close() {
|
|
1108
|
+
_backdropPointer = null;
|
|
1109
|
+
if (_zoomController) { _zoomController.destroy(); _zoomController = null; }
|
|
1057
1110
|
if (_overlay) { _overlay.remove(); _overlay = null; }
|
|
1058
1111
|
}
|
|
1059
1112
|
|
data/lib/clacky/web/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const Auth = (() => {
|
|
2
2
|
// ── Constants ──────────────────────────────────────────────────────────
|
|
3
3
|
const COOKIE_NAME = 'clacky_access_key';
|
|
4
|
-
const STORAGE_KEY = '
|
|
4
|
+
const STORAGE_KEY = 'clacky-access-key';
|
|
5
5
|
const PROBE_ENDPOINT = '/api/sessions?limit=1';
|
|
6
6
|
const MAX_PROMPT_TRIES = 3;
|
|
7
7
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
const Composer = (() => {
|
|
16
16
|
const CHIP_CLASS = "mention-chip";
|
|
17
17
|
const REFERENCE_DRAG_TYPE = "application/x-openclacky-reference";
|
|
18
|
+
const WORKSPACE_FILE_DRAG_TYPE = "application/x-openclacky-workspace-file";
|
|
18
19
|
const ELEMENT_NODE = 1;
|
|
19
20
|
const TEXT_NODE = 3;
|
|
20
21
|
|
|
@@ -86,8 +87,16 @@ const Composer = (() => {
|
|
|
86
87
|
return out.replace(/\u200B/g, "").replace(/\u00A0/g, " ");
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// ArrowUp/Down recall position per composer: { index, filled }.
|
|
91
|
+
const _recall = new WeakMap();
|
|
92
|
+
|
|
89
93
|
// Replace composer contents with plain text (newlines → <br>), caret at end.
|
|
90
94
|
function setText(el, value) {
|
|
95
|
+
_recall.delete(el);
|
|
96
|
+
_fill(el, value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function _fill(el, value) {
|
|
91
100
|
el.innerHTML = "";
|
|
92
101
|
const v = String(value == null ? "" : value);
|
|
93
102
|
if (v !== "") {
|
|
@@ -100,6 +109,7 @@ const Composer = (() => {
|
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
function clear(el) {
|
|
112
|
+
_recall.delete(el);
|
|
103
113
|
el.innerHTML = "";
|
|
104
114
|
}
|
|
105
115
|
|
|
@@ -284,10 +294,63 @@ const Composer = (() => {
|
|
|
284
294
|
return types.includes(REFERENCE_DRAG_TYPE);
|
|
285
295
|
}
|
|
286
296
|
|
|
287
|
-
function
|
|
297
|
+
function _normalizeDraggedWorkspaceFile(value) {
|
|
298
|
+
if (!value || typeof value !== "object" || value.type !== "file") return null;
|
|
299
|
+
const path = String(value.path || "").trim();
|
|
300
|
+
if (!path || path.length > 32768) return null;
|
|
301
|
+
const rawName = String(value.name || path.split(/[\\/]/).pop() || "").trim();
|
|
302
|
+
if (!rawName) return null;
|
|
303
|
+
const size = value.size == null ? NaN : Number(value.size);
|
|
304
|
+
return {
|
|
305
|
+
type: "file",
|
|
306
|
+
name: rawName.slice(0, 512),
|
|
307
|
+
path,
|
|
308
|
+
size: Number.isFinite(size) && size >= 0 ? size : null,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Start a drag from the Files tree without turning the workspace entry into
|
|
313
|
+
// a browser File. The path-backed attachment keeps using the same staging
|
|
314
|
+
// path as the Files tab's "Add to chat" action.
|
|
315
|
+
function beginWorkspaceFileDrag(dataTransfer, file) {
|
|
316
|
+
const normalized = _normalizeDraggedWorkspaceFile(file);
|
|
317
|
+
if (!dataTransfer || !normalized) return false;
|
|
318
|
+
dataTransfer.effectAllowed = "copy";
|
|
319
|
+
dataTransfer.setData(WORKSPACE_FILE_DRAG_TYPE, JSON.stringify(normalized));
|
|
320
|
+
dataTransfer.setData("text/plain", normalized.name);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function readDraggedWorkspaceFile(dataTransfer) {
|
|
325
|
+
if (!dataTransfer) return null;
|
|
326
|
+
let raw = "";
|
|
327
|
+
try {
|
|
328
|
+
raw = dataTransfer.getData(WORKSPACE_FILE_DRAG_TYPE) || "";
|
|
329
|
+
} catch (_) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
if (!raw || raw.length > 65536) return null;
|
|
333
|
+
try {
|
|
334
|
+
return _normalizeDraggedWorkspaceFile(JSON.parse(raw));
|
|
335
|
+
} catch (_) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function acceptsWorkspaceFileDrag(dataTransfer) {
|
|
341
|
+
if (!dataTransfer || !dataTransfer.types) return false;
|
|
342
|
+
return Array.from(dataTransfer.types).includes(WORKSPACE_FILE_DRAG_TYPE);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function _acceptsDrop(dataTransfer, allowWorkspaceFile) {
|
|
288
346
|
if (!dataTransfer || !dataTransfer.types) return false;
|
|
289
347
|
const types = Array.from(dataTransfer.types);
|
|
290
|
-
return acceptsSessionDrag(dataTransfer) || types.includes("Files")
|
|
348
|
+
return acceptsSessionDrag(dataTransfer) || types.includes("Files") ||
|
|
349
|
+
(allowWorkspaceFile && acceptsWorkspaceFileDrag(dataTransfer));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function acceptsDrop(dataTransfer) {
|
|
353
|
+
return _acceptsDrop(dataTransfer, true);
|
|
291
354
|
}
|
|
292
355
|
|
|
293
356
|
// Insert a valid dropped reference at the pointer position. Returns false
|
|
@@ -303,11 +366,14 @@ const Composer = (() => {
|
|
|
303
366
|
// Make a whole page/panel act as the drop target for its composer. Session
|
|
304
367
|
// references are inserted into the contenteditable; files are handed back
|
|
305
368
|
// to the owning view so its existing attachment pipeline remains in charge.
|
|
306
|
-
function bindDropZone({ zone, input, onFiles }) {
|
|
369
|
+
function bindDropZone({ zone, input, onFiles, onWorkspaceFile }) {
|
|
307
370
|
if (!zone || !input) return;
|
|
308
371
|
|
|
372
|
+
const acceptsBoundDrop = (dataTransfer) =>
|
|
373
|
+
_acceptsDrop(dataTransfer, typeof onWorkspaceFile === "function");
|
|
374
|
+
|
|
309
375
|
zone.addEventListener("dragover", (event) => {
|
|
310
|
-
if (!
|
|
376
|
+
if (!acceptsBoundDrop(event.dataTransfer)) return;
|
|
311
377
|
event.preventDefault();
|
|
312
378
|
event.dataTransfer.dropEffect = "copy";
|
|
313
379
|
zone.classList.add("drag-over");
|
|
@@ -316,11 +382,17 @@ const Composer = (() => {
|
|
|
316
382
|
if (!zone.contains(event.relatedTarget)) zone.classList.remove("drag-over");
|
|
317
383
|
});
|
|
318
384
|
zone.addEventListener("drop", (event) => {
|
|
319
|
-
if (!
|
|
385
|
+
if (!acceptsBoundDrop(event.dataTransfer)) return;
|
|
320
386
|
event.preventDefault();
|
|
321
387
|
zone.classList.remove("drag-over");
|
|
322
388
|
if (insertDroppedChip(input, event.dataTransfer, event.clientX, event.clientY)) return;
|
|
323
389
|
|
|
390
|
+
const workspaceFile = readDraggedWorkspaceFile(event.dataTransfer);
|
|
391
|
+
if (workspaceFile && typeof onWorkspaceFile === "function") {
|
|
392
|
+
onWorkspaceFile(workspaceFile);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
324
396
|
const files = Array.from(event.dataTransfer.files || []);
|
|
325
397
|
if (files.length > 0 && typeof onFiles === "function") onFiles(files);
|
|
326
398
|
});
|
|
@@ -419,12 +491,38 @@ const Composer = (() => {
|
|
|
419
491
|
el.setAttribute("data-placeholder", value || "");
|
|
420
492
|
}
|
|
421
493
|
|
|
422
|
-
//
|
|
423
|
-
|
|
494
|
+
// Shell-style recall: only from an empty box, or while the box still holds
|
|
495
|
+
// the last recalled entry untouched. Past the newest entry clears the box.
|
|
496
|
+
function _handleHistory(el, e, getHistory) {
|
|
497
|
+
if (!getHistory || (e.key !== "ArrowUp" && e.key !== "ArrowDown")) return false;
|
|
498
|
+
if (e.isComposing || e.shiftKey || e.altKey || e.metaKey || e.ctrlKey) return false;
|
|
499
|
+
let state = _recall.get(el);
|
|
500
|
+
if (state && (text(el) !== state.filled || el.querySelector("." + CHIP_CLASS))) {
|
|
501
|
+
_recall.delete(el);
|
|
502
|
+
state = null;
|
|
503
|
+
}
|
|
504
|
+
if (!state && (hasContent(el) || e.key === "ArrowDown")) return false;
|
|
505
|
+
const history = getHistory();
|
|
506
|
+
if (!history.length) return false;
|
|
507
|
+
e.preventDefault();
|
|
508
|
+
const index = Math.max(0, (state ? state.index : history.length) + (e.key === "ArrowUp" ? -1 : 1));
|
|
509
|
+
if (index >= history.length) {
|
|
510
|
+
clear(el);
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
_fill(el, history[index]);
|
|
514
|
+
_recall.set(el, { index, filled: text(el) });
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Wire up chip deletion, plain-text paste, Shift+Enter, and optional
|
|
519
|
+
// ArrowUp/Down recall of opts.getHistory() (oldest → newest) for one composer.
|
|
520
|
+
function init(el, opts = {}) {
|
|
424
521
|
if (!el || el.dataset.composerInit) return;
|
|
425
522
|
el.dataset.composerInit = "1";
|
|
426
523
|
el.addEventListener("keydown", (e) => {
|
|
427
524
|
if (_handleKeydown(el, e)) return;
|
|
525
|
+
if (_handleHistory(el, e, opts.getHistory)) return;
|
|
428
526
|
_handleEnter(el, e);
|
|
429
527
|
});
|
|
430
528
|
el.addEventListener("paste", (e) => _handlePaste(el, e));
|
|
@@ -441,7 +539,8 @@ const Composer = (() => {
|
|
|
441
539
|
return {
|
|
442
540
|
text, setText, clear, focus, focusEnd, hasContent,
|
|
443
541
|
chips, insertChip, insertDroppedChip, beginReferenceDrag, readDraggedChip,
|
|
444
|
-
acceptsSessionDrag,
|
|
542
|
+
acceptsSessionDrag, beginWorkspaceFileDrag, readDraggedWorkspaceFile,
|
|
543
|
+
acceptsWorkspaceFileDrag,
|
|
445
544
|
acceptsDrop, bindDropZone,
|
|
446
545
|
removeChip, setPlaceholder, init,
|
|
447
546
|
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ImageZoom — shared zoom and pan behavior for image viewers.
|
|
3
|
+
*
|
|
4
|
+
* The image keeps its natural dimensions and is transformed around the centre
|
|
5
|
+
* of the viewport, so 100% always means one image pixel per screen pixel.
|
|
6
|
+
*/
|
|
7
|
+
;(function(window) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MIN_SCALE = 0.1;
|
|
11
|
+
const DEFAULT_MAX_SCALE = 8;
|
|
12
|
+
const DEFAULT_STEP = 1.25;
|
|
13
|
+
|
|
14
|
+
function create(options) {
|
|
15
|
+
const viewport = options.viewport;
|
|
16
|
+
const image = options.image;
|
|
17
|
+
const minScale = options.minScale || DEFAULT_MIN_SCALE;
|
|
18
|
+
const maxScale = options.maxScale || DEFAULT_MAX_SCALE;
|
|
19
|
+
const step = options.step || DEFAULT_STEP;
|
|
20
|
+
const fitPadding = options.fitPadding || 1;
|
|
21
|
+
const onChange = options.onChange || function() {};
|
|
22
|
+
|
|
23
|
+
let scale = 1;
|
|
24
|
+
let fit = 1;
|
|
25
|
+
let x = 0;
|
|
26
|
+
let y = 0;
|
|
27
|
+
let drag = null;
|
|
28
|
+
|
|
29
|
+
function fitScale() {
|
|
30
|
+
const vw = viewport.clientWidth;
|
|
31
|
+
const vh = viewport.clientHeight;
|
|
32
|
+
if (!vw || !vh) return 1;
|
|
33
|
+
const nw = image.naturalWidth || vw;
|
|
34
|
+
const nh = image.naturalHeight || vh;
|
|
35
|
+
return Math.min(1, vw * fitPadding / nw, vh * fitPadding / nh);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pan(offsetX, offsetY) {
|
|
39
|
+
const maxX = Math.max(0, (image.naturalWidth * scale - viewport.clientWidth) / 2);
|
|
40
|
+
const maxY = Math.max(0, (image.naturalHeight * scale - viewport.clientHeight) / 2);
|
|
41
|
+
x = Math.max(-maxX, Math.min(maxX, offsetX));
|
|
42
|
+
y = Math.max(-maxY, Math.min(maxY, offsetY));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function state() {
|
|
46
|
+
return {
|
|
47
|
+
scale,
|
|
48
|
+
fit,
|
|
49
|
+
atFit: Math.abs(scale - fit) < 1e-6,
|
|
50
|
+
canZoomIn: scale < maxScale - 1e-6,
|
|
51
|
+
canZoomOut: scale > minScale + 1e-6,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function render() {
|
|
56
|
+
pan(x, y);
|
|
57
|
+
image.style.transform = "translate(" + x + "px, " + y + "px) scale(" + scale + ")";
|
|
58
|
+
onChange(state());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Zoom around a point relative to the viewport centre, so the image pixel
|
|
62
|
+
// beneath the pointer stays in place rather than drifting toward a corner.
|
|
63
|
+
function zoomAt(target, px, py) {
|
|
64
|
+
const next = Math.max(minScale, Math.min(maxScale, target));
|
|
65
|
+
if (next === scale) return;
|
|
66
|
+
const k = next / scale;
|
|
67
|
+
x = px - k * (px - x);
|
|
68
|
+
y = py - k * (py - y);
|
|
69
|
+
scale = next;
|
|
70
|
+
render();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function anchorOf(ev) {
|
|
74
|
+
const rect = viewport.getBoundingClientRect();
|
|
75
|
+
return [
|
|
76
|
+
ev.clientX - (rect.left + rect.width / 2),
|
|
77
|
+
ev.clientY - (rect.top + rect.height / 2),
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function reset() {
|
|
82
|
+
fit = fitScale();
|
|
83
|
+
scale = fit;
|
|
84
|
+
x = 0;
|
|
85
|
+
y = 0;
|
|
86
|
+
render();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function zoomIn() { zoomAt(scale * step, 0, 0); }
|
|
90
|
+
function zoomOut() { zoomAt(scale / step, 0, 0); }
|
|
91
|
+
|
|
92
|
+
function onWheel(ev) {
|
|
93
|
+
ev.preventDefault();
|
|
94
|
+
const anchor = anchorOf(ev);
|
|
95
|
+
zoomAt(scale * Math.exp(-ev.deltaY * 0.002), anchor[0], anchor[1]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function onDoubleClick(ev) {
|
|
99
|
+
ev.preventDefault();
|
|
100
|
+
if (state().atFit) {
|
|
101
|
+
const anchor = anchorOf(ev);
|
|
102
|
+
zoomAt(fit < 1 ? 1 : fit * 2, anchor[0], anchor[1]);
|
|
103
|
+
} else {
|
|
104
|
+
reset();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function onPointerDown(ev) {
|
|
109
|
+
if (ev.button || (options.panOnImageOnly && ev.target !== image)) return;
|
|
110
|
+
drag = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
111
|
+
viewport.classList.add("is-panning");
|
|
112
|
+
viewport.setPointerCapture(ev.pointerId);
|
|
113
|
+
ev.preventDefault();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function onPointerMove(ev) {
|
|
117
|
+
if (!drag || ev.pointerId !== drag.id) return;
|
|
118
|
+
pan(x + ev.clientX - drag.x, y + ev.clientY - drag.y);
|
|
119
|
+
drag.x = ev.clientX;
|
|
120
|
+
drag.y = ev.clientY;
|
|
121
|
+
image.style.transform = "translate(" + x + "px, " + y + "px) scale(" + scale + ")";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function endDrag(ev) {
|
|
125
|
+
if (!drag || ev.pointerId !== drag.id) return;
|
|
126
|
+
drag = null;
|
|
127
|
+
viewport.classList.remove("is-panning");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
viewport.addEventListener("wheel", onWheel, { passive: false });
|
|
131
|
+
viewport.addEventListener("dblclick", onDoubleClick);
|
|
132
|
+
viewport.addEventListener("pointerdown", onPointerDown);
|
|
133
|
+
viewport.addEventListener("pointermove", onPointerMove);
|
|
134
|
+
viewport.addEventListener("pointerup", endDrag);
|
|
135
|
+
viewport.addEventListener("pointercancel", endDrag);
|
|
136
|
+
image.addEventListener("load", reset);
|
|
137
|
+
|
|
138
|
+
if (image.complete) reset();
|
|
139
|
+
else render();
|
|
140
|
+
|
|
141
|
+
// Refit only while the image is still fitted. A deliberate user zoom must
|
|
142
|
+
// survive a viewport resize or a tab becoming visible.
|
|
143
|
+
const observer = typeof ResizeObserver === "function"
|
|
144
|
+
? new ResizeObserver(function() { if (state().atFit) reset(); })
|
|
145
|
+
: null;
|
|
146
|
+
if (observer) observer.observe(viewport);
|
|
147
|
+
|
|
148
|
+
function destroy() {
|
|
149
|
+
if (observer) observer.disconnect();
|
|
150
|
+
drag = null;
|
|
151
|
+
viewport.classList.remove("is-panning");
|
|
152
|
+
viewport.removeEventListener("wheel", onWheel);
|
|
153
|
+
viewport.removeEventListener("dblclick", onDoubleClick);
|
|
154
|
+
viewport.removeEventListener("pointerdown", onPointerDown);
|
|
155
|
+
viewport.removeEventListener("pointermove", onPointerMove);
|
|
156
|
+
viewport.removeEventListener("pointerup", endDrag);
|
|
157
|
+
viewport.removeEventListener("pointercancel", endDrag);
|
|
158
|
+
image.removeEventListener("load", reset);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { zoomIn, zoomOut, reset, destroy, state };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
window.ImageZoom = { create };
|
|
165
|
+
})(window);
|
|
@@ -32,17 +32,18 @@ const ModelPicker = (() => {
|
|
|
32
32
|
// tile's white ink; `brandSvg` keeps the official multi-color/gradient
|
|
33
33
|
// artwork, so those tiles take a light background and skip the currentColor
|
|
34
34
|
// fill. Unknown models fall back to their initial on a neutral tile.
|
|
35
|
+
// `tint` keeps a monochrome mark in brand color when drawn on a neutral tile.
|
|
35
36
|
const _VENDOR_RULES = [
|
|
36
37
|
// "auto" is the routing feature, not a vendor model: circular arrows
|
|
37
38
|
// wrapping a bold "A" mark the automatic model pick.
|
|
38
|
-
{ re: /^auto$/i, label: "A", color: "#4f46e5",
|
|
39
|
+
{ re: /^auto$/i, label: "A", color: "#4f46e5", tint: "#4f46e5",
|
|
39
40
|
svg: '<path d="M19.79 7.5A9 9 0 1 0 19.79 16.5" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"/>'
|
|
40
41
|
+ '<path d="M17.49 3.51 21.52 6.5 18.06 8.5Z"/>'
|
|
41
42
|
+ '<path d="M22.09 12.51 21.52 17.5 18.06 15.5Z"/>'
|
|
42
43
|
+ '<path fill-rule="evenodd" d="M12 5.8 16.4 17.6H14.35L13.5 15.1H10.5L9.65 17.6H7.6ZM12 8.8 13.05 12.8H10.95Z"/>' },
|
|
43
44
|
{ re: /claude|anthropic/i, label: "A", color: "#fff",
|
|
44
45
|
brandSvg: '<path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"/>' },
|
|
45
|
-
{ re: /deepseek/i, label: "D", color: "#4D6BFE", viewBox: "0 1.5 27 20.5",
|
|
46
|
+
{ re: /deepseek/i, label: "D", color: "#4D6BFE", tint: "#4D6BFE", viewBox: "0 1.5 27 20.5",
|
|
46
47
|
svg: '<path d="M26.5174 3.39471C26.235 3.2567 26.1137 3.52006 25.9487 3.65346C25.8923 3.69659 25.8446 3.75294 25.7969 3.80469C25.3846 4.24516 24.9027 4.53439 24.2737 4.49989C23.3536 4.44814 22.5682 4.73737 21.8735 5.44119C21.7258 4.57349 21.2353 4.0554 20.4889 3.72304C20.0985 3.55054 19.7034 3.37746 19.4297 3.00197C19.2388 2.73459 19.1865 2.43673 19.091 2.14289C19.0301 1.96579 18.9697 1.78466 18.7656 1.75418C18.5442 1.71968 18.4574 1.90541 18.3705 2.06067C18.0232 2.69549 17.8887 3.39471 17.9019 4.10313C17.9324 5.6965 18.6051 6.96556 19.9421 7.86834C20.0939 7.97184 20.133 8.07535 20.0852 8.22658C19.9938 8.53766 19.8857 8.83955 19.7903 9.15063C19.7293 9.34901 19.6384 9.39271 19.4257 9.30588C18.692 8.9994 18.0583 8.54571 17.4982 7.99772C16.5477 7.07827 15.6881 6.06336 14.6162 5.26869C14.3644 5.08296 14.1125 4.91045 13.8521 4.746C12.7584 3.68394 13.9952 2.81164 14.2816 2.70814C14.5812 2.60003 14.3857 2.22857 13.4179 2.23317C12.4502 2.2372 11.5646 2.56151 10.4359 2.99335C10.2708 3.05832 10.0972 3.10547 9.91951 3.14457C8.8954 2.95022 7.83162 2.90709 6.72069 3.03245C4.62877 3.26533 2.95777 4.25436 1.72954 5.94261C0.254043 7.97184 -0.0932678 10.2777 0.33167 12.6824C0.778458 15.2171 2.07225 17.3153 4.06008 18.9558C6.12152 20.6567 8.49577 21.4905 11.2047 21.3306C12.8498 21.2358 14.6812 21.0155 16.7473 19.2669C17.2682 19.5262 17.8151 19.6297 18.7219 19.7074C19.4205 19.7723 20.0933 19.6729 20.6143 19.5648C21.4302 19.3923 21.3739 18.6367 21.0789 18.4981C18.6874 17.3843 19.2124 17.8374 18.7351 17.4706C19.9501 16.033 21.8063 13.4776 22.379 9.99821C22.4353 9.61409 22.5072 9.073 22.4986 8.76192C22.494 8.57216 22.5377 8.49856 22.7545 8.47671C23.3536 8.40771 23.935 8.24383 24.4692 7.94999C26.0188 7.10357 26.6439 5.71318 26.7911 4.04678C26.8129 3.79204 26.7865 3.52869 26.5174 3.39471ZM13.0143 18.3946C10.6964 16.5724 9.5722 15.9726 9.10816 15.9985C8.67402 16.0244 8.75222 16.5212 8.84768 16.8449C8.94773 17.1646 9.07768 17.3849 9.25996 17.6655C9.38589 17.8512 9.47272 18.1272 9.13404 18.3348C8.38766 18.7965 7.08985 18.1796 7.0289 18.1491C5.51833 17.2595 4.25559 16.0853 3.36546 14.4793C2.50581 12.9337 2.0067 11.2753 1.92447 9.50542C1.90262 9.07818 2.02855 8.92695 2.45406 8.84932C3.01413 8.74582 3.59144 8.72397 4.15093 8.80619C6.51656 9.15178 8.53027 10.2092 10.2185 11.8848C11.1822 12.8388 11.9114 13.979 12.6623 15.0929C13.461 16.2757 14.3201 17.4027 15.4144 18.3268C15.8008 18.6505 16.109 18.8966 16.404 19.0783C15.5144 19.1778 14.0297 19.1991 13.0143 18.3958V18.3946ZM14.1252 11.2489C14.1252 11.0591 14.277 10.9079 14.4679 10.9079C14.511 10.9079 14.5501 10.9165 14.5852 10.9292C14.6329 10.9464 14.6766 10.9723 14.7111 11.0114C14.7721 11.0718 14.8066 11.158 14.8066 11.2489C14.8066 11.4386 14.6548 11.5899 14.4639 11.5899C14.273 11.5899 14.1252 11.4386 14.1252 11.2489ZM17.5759 13.0188C17.3545 13.1096 17.1331 13.1873 16.9203 13.1959C16.5903 13.2131 16.2303 13.0791 16.0348 12.9153C15.7312 12.6605 15.5139 12.5179 15.423 12.0734C15.3839 11.8837 15.4057 11.5899 15.4402 11.4214C15.5185 11.0585 15.4316 10.8257 15.1757 10.614C14.9676 10.4415 14.7025 10.3938 14.4115 10.3938C14.3029 10.3938 14.2034 10.3461 14.1292 10.3076C14.0079 10.2472 13.9078 10.096 14.0033 9.91023C14.0338 9.84985 14.1815 9.70322 14.216 9.67734C14.6111 9.45251 15.0665 9.52612 15.488 9.6946C15.8784 9.85445 16.174 10.1477 16.5989 10.5623C17.033 11.0631 17.1112 11.2011 17.3585 11.5772C17.554 11.871 17.7317 12.1729 17.8536 12.5185C17.9272 12.7341 17.8317 12.9107 17.5759 13.0188Z"/>' },
|
|
47
48
|
{ re: /glm|zhipu|bigmodel/i, label: "Z", color: "#131212", viewBox: "0 0 32 27",
|
|
48
49
|
svg: '<path d="M16.5376 0.0307374L14.3326 3.13223C14.1617 3.37577 13.9331 3.57415 13.6667 3.71001C13.4004 3.84586 13.1045 3.91504 12.8048 3.9115H0.787598V0.015152H16.5376V0.0307374Z"/><path d="M31.5 0.0321655L12.6 26.5273H0L18.9 0.0321655H31.5Z"/><path d="M14.9624 26.5272L17.1832 23.4101C17.3564 23.1689 17.5856 22.9723 17.8513 22.8368C18.1171 22.7012 18.4119 22.6306 18.7109 22.6308H30.7124V26.5272H14.9624Z"/>' },
|
|
@@ -64,7 +65,7 @@ const ModelPicker = (() => {
|
|
|
64
65
|
brandSvg: '<path d="M19.44 10.153l-2.936 11.586a.215.215 0 00.214.261h5.87a.215.215 0 00.214-.261l-2.95-11.586a.214.214 0 00-.412 0zM3.28 12.778l-2.275 8.96A.214.214 0 001.22 22h4.532a.212.212 0 00.214-.165.214.214 0 000-.097l-2.276-8.96a.214.214 0 00-.41 0z" fill="#00E5E5"/><path d="M7.29 5.359L3.148 21.738a.215.215 0 00.203.261h8.29a.214.214 0 00.215-.261L7.7 5.358a.214.214 0 00-.41 0z" fill="#006EFF"/><path d="M14.44.15a.214.214 0 00-.41 0L8.366 21.739a.214.214 0 00.214.261H19.9a.216.216 0 00.171-.078.214.214 0 00.044-.183L14.439.15z" fill="#006EFF"/><path d="M10.278 7.741L6.685 21.736a.214.214 0 00.214.264h7.17a.215.215 0 00.214-.264L10.688 7.741a.214.214 0 00-.41 0z" fill="#00E5E5"/>' },
|
|
65
66
|
{ re: /llama/i, label: "L", color: "#fff", ink: "#111827",
|
|
66
67
|
svg: '<path d="M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z"/>' },
|
|
67
|
-
{ re: /mistral/i, label: "M", color: "#F26B1D",
|
|
68
|
+
{ re: /mistral/i, label: "M", color: "#F26B1D", tint: "#F26B1D",
|
|
68
69
|
svg: '<path d="M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z"/>' },
|
|
69
70
|
{ re: /hunyuan/i, label: "H", color: "#fff",
|
|
70
71
|
brandSvg: '<circle cx="12" cy="12" fill="#0055E9" r="12"/><path d="M12 0c.518 0 1.028.033 1.528.096A6.188 6.188 0 0112.12 12.28l-.12.001c-2.99 0-5.242 2.179-5.554 5.11-.223 2.086.353 4.412 2.242 6.146C3.672 22.1 0 17.479 0 12 0 5.373 5.373 0 12 0z" fill="#A8DFF5"/><path d="M5.286 5a2.438 2.438 0 01.682 3.38c-3.962 5.966-3.215 10.743 2.648 15.136C3.636 22.056 0 17.452 0 12c0-1.787.39-3.482 1.09-5.006.253-.435.525-.872.817-1.311A2.438 2.438 0 015.286 5z" fill="#0055E9"/><path d="M12.98.04c.272.021.543.053.81.093.583.106 1.117.254 1.538.44 6.638 2.927 8.07 10.052 1.748 15.642a4.125 4.125 0 01-5.822-.358c-1.51-1.706-1.3-4.184.357-5.822.858-.848 3.108-1.223 4.045-2.441 1.257-1.634 2.122-6.009-2.523-7.506L12.98.039z" fill="#00BCFF"/><path d="M13.528.096A6.187 6.187 0 0112 12.281a5.75 5.75 0 00-1.71.255c.147-.905.595-1.784 1.321-2.501.858-.848 3.108-1.223 4.045-2.441 1.27-1.651 2.14-6.104-2.676-7.554.184.014.367.033.548.056z" fill="#ECECEE"/>' },
|
|
@@ -84,7 +85,8 @@ const ModelPicker = (() => {
|
|
|
84
85
|
const el = document.createElement("span");
|
|
85
86
|
el.className = "sib-vendor-badge";
|
|
86
87
|
el.style.setProperty("--vendor-color", rule ? rule.color : "#7A8B99");
|
|
87
|
-
if (rule && rule.ink) el.style.
|
|
88
|
+
if (rule && rule.ink) el.style.setProperty("--vendor-ink", rule.ink);
|
|
89
|
+
if (rule && rule.tint) el.style.setProperty("--vendor-tint", rule.tint);
|
|
88
90
|
if (rule && rule.color === "#fff") el.classList.add("is-light");
|
|
89
91
|
if (rule && rule.brandSvg) {
|
|
90
92
|
el.classList.add("is-brand");
|
|
@@ -230,9 +230,9 @@ const Notify = (() => {
|
|
|
230
230
|
btn.classList.toggle("notify-on", on);
|
|
231
231
|
if (typeof I18n !== "undefined") {
|
|
232
232
|
const tip = I18n.t(_tooltipKey(on));
|
|
233
|
-
btn.
|
|
233
|
+
btn.setAttribute("data-tooltip", tip);
|
|
234
234
|
btn.setAttribute("aria-label", tip);
|
|
235
|
-
btn.setAttribute("data-i18n-
|
|
235
|
+
btn.setAttribute("data-i18n-tooltip", _tooltipKey(on));
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
const OPEN_KEY = "clacky.aside.open";
|
|
24
24
|
const FULLSCREEN_KEY = "clacky.aside.fullscreen";
|
|
25
25
|
const MIN_W = 256;
|
|
26
|
+
// Narrower than this the chat column stops being readable — composer and
|
|
27
|
+
// message bubbles fold into a one-character-wide strip. Once the aside
|
|
28
|
+
// crosses the line it takes the whole workspace instead.
|
|
29
|
+
const CHAT_MIN_W = 300;
|
|
26
30
|
const maxW = () => Math.max(MIN_W, Math.round(window.innerWidth * 0.8));
|
|
27
31
|
// Matches the CSS breakpoint where the aside becomes a fixed overlay drawer.
|
|
28
32
|
const isNarrow = () => window.matchMedia("(max-width: 768px)").matches;
|
|
@@ -57,6 +61,19 @@
|
|
|
57
61
|
function setWidth(px) {
|
|
58
62
|
const aside = $("session-aside");
|
|
59
63
|
if (aside) aside.style.setProperty("--session-aside-width", px + "px");
|
|
64
|
+
syncChatCollapse();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The aside wins the whole workspace as soon as it would leave the chat
|
|
68
|
+
// column below CHAT_MIN_W. Width itself stays driven by the drag value, so
|
|
69
|
+
// pulling the handle back past the threshold restores the split at once.
|
|
70
|
+
function syncChatCollapse() {
|
|
71
|
+
const panel = $("chat-panel");
|
|
72
|
+
if (!panel) return;
|
|
73
|
+
const panelWidth = panel.getBoundingClientRect().width;
|
|
74
|
+
const collapsed = !isNarrow() && isOpen() && !slotEmpty() &&
|
|
75
|
+
panelWidth > CHAT_MIN_W && panelWidth - currentWidth() < CHAT_MIN_W;
|
|
76
|
+
panel.classList.toggle("chat-collapsed", collapsed);
|
|
60
77
|
}
|
|
61
78
|
|
|
62
79
|
// Reconcile temporary requests centrally so nested panels can close in any
|
|
@@ -144,6 +161,7 @@
|
|
|
144
161
|
// A fullscreen class with an empty slot would show an empty column over
|
|
145
162
|
// the whole workspace (the fullscreen rule outranks the empty-slot one).
|
|
146
163
|
if (empty) setFullscreen(false);
|
|
164
|
+
syncChatCollapse();
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
function isOpen() {
|
|
@@ -214,12 +232,29 @@
|
|
|
214
232
|
}
|
|
215
233
|
}
|
|
216
234
|
|
|
235
|
+
function shortcutLabel() {
|
|
236
|
+
const uaData = navigator.userAgentData && navigator.userAgentData.platform;
|
|
237
|
+
const platform = (uaData || navigator.platform || "").toString();
|
|
238
|
+
return /mac/i.test(platform) ? "⌘L" : "Ctrl+L";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Both hints carry the shortcut, so they are composed here rather than
|
|
242
|
+
// through data-i18n-tooltip (which would drop the suffix on a lang switch).
|
|
243
|
+
function applyAsideTitles() {
|
|
244
|
+
if (!window.Clacky || !Clacky.I18n) return;
|
|
245
|
+
const label = (key) => Clacky.I18n.t(key) + " (" + shortcutLabel() + ")";
|
|
246
|
+
const collapse = $("btn-aside-collapse");
|
|
247
|
+
if (collapse) collapse.setAttribute("data-tooltip", label("aside.collapse"));
|
|
248
|
+
const opener = $("btn-aside-open");
|
|
249
|
+
if (opener) opener.setAttribute("data-tooltip", label("aside.expand"));
|
|
250
|
+
}
|
|
251
|
+
|
|
217
252
|
function applyFullscreenState() {
|
|
218
253
|
const chatPanel = $("chat-panel");
|
|
219
254
|
if (chatPanel) chatPanel.classList.toggle("aside-fullscreen", isFullscreen());
|
|
220
255
|
const btn = $("btn-aside-fullscreen");
|
|
221
256
|
if (btn && window.Clacky && Clacky.I18n) {
|
|
222
|
-
btn.
|
|
257
|
+
btn.setAttribute("data-tooltip", Clacky.I18n.t(isFullscreen() ? "aside.exitFullscreen" : "aside.fullscreen"));
|
|
223
258
|
}
|
|
224
259
|
}
|
|
225
260
|
|
|
@@ -255,7 +290,7 @@
|
|
|
255
290
|
|
|
256
291
|
try {
|
|
257
292
|
const saved = parseFloat(localStorage.getItem(WIDTH_KEY));
|
|
258
|
-
if (saved >= MIN_W && saved <= maxW())
|
|
293
|
+
if (saved >= MIN_W && saved <= maxW()) setWidth(saved);
|
|
259
294
|
} catch (_e) { /* ignore */ }
|
|
260
295
|
|
|
261
296
|
let dragging = false;
|
|
@@ -277,7 +312,7 @@
|
|
|
277
312
|
if (!dragging) return;
|
|
278
313
|
const dx = startX - e.clientX;
|
|
279
314
|
const w = Math.min(maxW(), Math.max(MIN_W, startW + dx));
|
|
280
|
-
|
|
315
|
+
setWidth(w);
|
|
281
316
|
});
|
|
282
317
|
|
|
283
318
|
document.addEventListener("mouseup", () => {
|
|
@@ -320,6 +355,21 @@
|
|
|
320
355
|
if (opener) opener.addEventListener("click", () => setOpen(true));
|
|
321
356
|
if (overlay) overlay.addEventListener("click", () => setOpen(false));
|
|
322
357
|
if (fsBtn) fsBtn.addEventListener("click", () => setFullscreen(!isFullscreen()));
|
|
358
|
+
applyAsideTitles();
|
|
359
|
+
document.addEventListener("langchange", applyAsideTitles);
|
|
360
|
+
document.addEventListener("langchange", applyFullscreenState);
|
|
361
|
+
document.addEventListener("keydown", (e) => {
|
|
362
|
+
// ⌘L is the advertised shortcut; browsers keep it for the address bar,
|
|
363
|
+
// so ⌘\ stays as a working fallback wherever the keystroke is swallowed.
|
|
364
|
+
const isBackslash = e.key === "\\" || e.code === "Backslash";
|
|
365
|
+
const isL = e.key === "l" || e.key === "L" || e.code === "KeyL";
|
|
366
|
+
if (!isL && !isBackslash) return;
|
|
367
|
+
if (e.altKey || e.shiftKey) return;
|
|
368
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
369
|
+
if (slotEmpty()) return;
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
setOpen(!isOpen());
|
|
372
|
+
});
|
|
323
373
|
document.addEventListener("keydown", (e) => {
|
|
324
374
|
if (e.key !== "Escape" || !isFullscreen()) return;
|
|
325
375
|
// Let overlay panels (settings / skills) consume Esc first.
|
|
@@ -343,6 +393,13 @@
|
|
|
343
393
|
requestAnimationFrame(() => { clampScheduled = false; clampWidth(); });
|
|
344
394
|
});
|
|
345
395
|
|
|
396
|
+
// Sidebar collapse, session switches and window resizes all change how
|
|
397
|
+
// much room the chat panel has without going through setWidth.
|
|
398
|
+
const chatPanel = $("chat-panel");
|
|
399
|
+
if (chatPanel && window.ResizeObserver) {
|
|
400
|
+
new ResizeObserver(() => syncChatCollapse()).observe(chatPanel);
|
|
401
|
+
}
|
|
402
|
+
|
|
346
403
|
// Re-evaluate opener visibility whenever the slot content changes (panels
|
|
347
404
|
// re-render on session / agent switch).
|
|
348
405
|
const slot = $("ext-slot-session-aside");
|
|
@@ -219,8 +219,8 @@ const BillingView = (() => {
|
|
|
219
219
|
</div>
|
|
220
220
|
`;
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
btn.addEventListener("click", (
|
|
222
|
+
container.querySelectorAll(".segmented-control__btn").forEach(btn => {
|
|
223
|
+
btn.addEventListener("click", () => Billing.setPeriod(btn.dataset.period));
|
|
224
224
|
});
|
|
225
225
|
|
|
226
226
|
const billingModelWrap = document.getElementById("billing-model-filter");
|