openclacky 1.3.2 → 1.3.3
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 +28 -0
- data/Dockerfile +3 -0
- data/README.md +1 -1
- data/README_JA.md +237 -0
- data/lib/clacky/agent/session_serializer.rb +49 -5
- data/lib/clacky/agent/time_machine.rb +247 -26
- data/lib/clacky/agent.rb +12 -1
- data/lib/clacky/agent_config.rb +14 -2
- data/lib/clacky/default_agents/_panels/git/panel.js +201 -0
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +640 -0
- data/lib/clacky/default_agents/coding/profile.yml +3 -0
- data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
- data/lib/clacky/default_skills/cron-task-creator/SKILL.md +1 -1
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +6 -4
- data/lib/clacky/default_skills/media-gen/SKILL.md +30 -6
- data/lib/clacky/media/openai_compat.rb +64 -1
- data/lib/clacky/media/output_dir.rb +43 -0
- data/lib/clacky/message_history.rb +9 -0
- data/lib/clacky/server/channel/channel_manager.rb +26 -0
- data/lib/clacky/server/git_panel.rb +115 -0
- data/lib/clacky/server/http_server.rb +497 -12
- data/lib/clacky/server/server_master.rb +6 -4
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +473 -60
- data/lib/clacky/web/app.js +30 -7
- data/lib/clacky/web/components/code-editor.js +197 -0
- data/lib/clacky/web/{notify.js → components/notify.js} +1 -1
- data/lib/clacky/web/core/aside.js +112 -0
- data/lib/clacky/web/core/ext.js +387 -0
- data/lib/clacky/web/features/backup/store.js +92 -0
- data/lib/clacky/web/features/backup/view.js +94 -0
- data/lib/clacky/web/features/billing/store.js +163 -0
- data/lib/clacky/web/{billing.js → features/billing/view.js} +132 -240
- data/lib/clacky/web/features/brand/store.js +110 -0
- data/lib/clacky/web/{brand.js → features/brand/view.js} +49 -199
- data/lib/clacky/web/features/channels/store.js +103 -0
- data/lib/clacky/web/{channels.js → features/channels/view.js} +50 -127
- data/lib/clacky/web/features/creator/store.js +81 -0
- data/lib/clacky/web/{creator.js → features/creator/view.js} +53 -102
- data/lib/clacky/web/features/mcp/store.js +158 -0
- data/lib/clacky/web/{mcp.js → features/mcp/view.js} +57 -134
- data/lib/clacky/web/features/model-tester/store.js +77 -0
- data/lib/clacky/web/features/model-tester/view.js +7 -0
- data/lib/clacky/web/features/profile/store.js +170 -0
- data/lib/clacky/web/{profile.js → features/profile/view.js} +94 -144
- data/lib/clacky/web/features/share/store.js +145 -0
- data/lib/clacky/web/{share.js → features/share/view.js} +66 -202
- data/lib/clacky/web/features/skills/store.js +303 -0
- data/lib/clacky/web/features/skills/view.js +550 -0
- data/lib/clacky/web/features/tasks/store.js +135 -0
- data/lib/clacky/web/features/tasks/view.js +241 -0
- data/lib/clacky/web/features/trash/store.js +242 -0
- data/lib/clacky/web/{trash.js → features/trash/view.js} +102 -293
- data/lib/clacky/web/features/version/store.js +165 -0
- data/lib/clacky/web/features/version/view.js +323 -0
- data/lib/clacky/web/features/workspace/store.js +99 -0
- data/lib/clacky/web/features/workspace/view.js +305 -0
- data/lib/clacky/web/i18n.js +56 -6
- data/lib/clacky/web/index.html +117 -58
- data/lib/clacky/web/sessions.js +221 -25
- data/lib/clacky/web/settings.js +118 -22
- data/lib/clacky/web/skills.js +3 -863
- data/lib/clacky/web/vendor/codemirror/codemirror.min.js +29 -0
- data/lib/clacky.rb +1 -0
- metadata +45 -20
- data/lib/clacky/web/backup.js +0 -119
- data/lib/clacky/web/model-tester.js +0 -66
- data/lib/clacky/web/tasks.js +0 -373
- data/lib/clacky/web/version.js +0 -449
- data/lib/clacky/web/workspace.js +0 -316
- /data/lib/clacky/web/{notify.mp3 → assets/notify.mp3} +0 -0
- /data/lib/clacky/web/{datepicker.js → components/datepicker.js} +0 -0
- /data/lib/clacky/web/{onboard.js → components/onboard.js} +0 -0
- /data/lib/clacky/web/{sidebar.js → components/sidebar.js} +0 -0
- /data/lib/clacky/web/{marked.min.js → vendor/marked/marked.min.js} +0 -0
data/lib/clacky/web/app.js
CHANGED
|
@@ -116,7 +116,10 @@ const Router = (() => {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Core: apply a view change. Called both from navigate() and hashchange.
|
|
119
|
-
|
|
119
|
+
// Async because the "session" case may need to fetch /api/sessions/:id when
|
|
120
|
+
// the target session isn't in the paged sidebar list (search clicks, URL
|
|
121
|
+
// deep links, share links, browser back/forward, notification jumps).
|
|
122
|
+
async function _apply(view, params = {}) {
|
|
120
123
|
_current = view;
|
|
121
124
|
_params = params;
|
|
122
125
|
|
|
@@ -135,6 +138,12 @@ const Router = (() => {
|
|
|
135
138
|
|
|
136
139
|
_hideAll();
|
|
137
140
|
|
|
141
|
+
// Leaving a session view → clear agent scope so agent panels don't linger
|
|
142
|
+
// over non-session views. The session case re-sets it below.
|
|
143
|
+
if (view !== "session" && window.Clacky && Clacky.ext && Clacky.ext.context.agentProfile) {
|
|
144
|
+
Clacky.ext.setContext({ agentProfile: null, sessionId: null });
|
|
145
|
+
}
|
|
146
|
+
|
|
138
147
|
// Reveal #app on first navigation — ensures the correct view (and language)
|
|
139
148
|
// is already in place before the user sees anything.
|
|
140
149
|
// #app covers sidebar + main, so data-i18n elements in the sidebar are also
|
|
@@ -150,10 +159,14 @@ const Router = (() => {
|
|
|
150
159
|
|
|
151
160
|
case "session": {
|
|
152
161
|
const id = params.id;
|
|
153
|
-
|
|
162
|
+
// findOrFetch falls back to the backend when the session isn't in the
|
|
163
|
+
// sidebar's paged `_sessions` (search results, URL deep links, share
|
|
164
|
+
// links, browser back/forward). On success it caches the row in the
|
|
165
|
+
// local `_extraSessions` pool so subsequent sync `find` calls hit too.
|
|
166
|
+
const s = await Sessions.findOrFetch(id);
|
|
154
167
|
if (!s) {
|
|
155
|
-
//
|
|
156
|
-
_apply("welcome");
|
|
168
|
+
// Truly not found (deleted, or never existed) — fall back to welcome.
|
|
169
|
+
await _apply("welcome");
|
|
157
170
|
return;
|
|
158
171
|
}
|
|
159
172
|
_setHash(`session/${id}`);
|
|
@@ -163,6 +176,13 @@ const Router = (() => {
|
|
|
163
176
|
Sessions.updateInfoBar(s);
|
|
164
177
|
Sessions._restoreMessagesPublic(id);
|
|
165
178
|
Sessions._setActiveId(id);
|
|
179
|
+
// Scope agent UI / official panels to this session's agent profile, then
|
|
180
|
+
// re-render every slot so the right panels appear (and a previous
|
|
181
|
+
// agent's panels are cleared).
|
|
182
|
+
if (window.Clacky && Clacky.ext) {
|
|
183
|
+
Clacky.ext.setContext({ agentProfile: s.agent_profile || "general", sessionId: id });
|
|
184
|
+
Clacky.ext.emit("session:agent-changed", { sessionId: id, agentProfile: s.agent_profile || "general" });
|
|
185
|
+
}
|
|
166
186
|
// Immediately re-attach saved progress UI (timer + spinner) so it appears
|
|
167
187
|
// instantly without waiting for the async history fetch or WS replay.
|
|
168
188
|
Sessions._attachProgressUI(id);
|
|
@@ -277,7 +297,7 @@ const Router = (() => {
|
|
|
277
297
|
return;
|
|
278
298
|
}
|
|
279
299
|
const { view, params } = _parseHash(location.hash);
|
|
280
|
-
_apply(view, params);
|
|
300
|
+
_apply(view, params).catch(err => console.error("Router._apply failed:", err));
|
|
281
301
|
});
|
|
282
302
|
|
|
283
303
|
return {
|
|
@@ -286,13 +306,16 @@ const Router = (() => {
|
|
|
286
306
|
|
|
287
307
|
/** Navigate to a view. This is the only way panels should change. */
|
|
288
308
|
navigate(view, params = {}) {
|
|
289
|
-
_apply(
|
|
309
|
+
// Fire-and-forget: _apply is async (may fetch /api/sessions/:id), but
|
|
310
|
+
// navigate() keeps a sync signature so all existing call sites are
|
|
311
|
+
// unaffected. Errors are logged; UI falls back to welcome on missing id.
|
|
312
|
+
_apply(view, params).catch(err => console.error("Router._apply failed:", err));
|
|
290
313
|
},
|
|
291
314
|
|
|
292
315
|
/** Restore state from current URL hash (called once on boot after data loads). */
|
|
293
316
|
restoreFromHash() {
|
|
294
317
|
const { view, params } = _parseHash(location.hash);
|
|
295
|
-
_apply(view, params);
|
|
318
|
+
_apply(view, params).catch(err => console.error("Router._apply failed:", err));
|
|
296
319
|
},
|
|
297
320
|
};
|
|
298
321
|
})();
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/* global CM */
|
|
2
|
+
/**
|
|
3
|
+
* CodeEditor — a reusable wrapper around CodeMirror 6.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* CodeEditor.open({
|
|
7
|
+
* content: '# Hello',
|
|
8
|
+
* language: 'markdown',
|
|
9
|
+
* title: 'SKILL.md',
|
|
10
|
+
* readOnly: false,
|
|
11
|
+
* onSave: async (content) => { ... }
|
|
12
|
+
* });
|
|
13
|
+
*/
|
|
14
|
+
;(function(window) {
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
const LANG_MAP = {
|
|
18
|
+
markdown: () => CM.markdown({ base: CM.markdownLanguage }),
|
|
19
|
+
md: () => CM.markdown({ base: CM.markdownLanguage }),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const IMAGE_EXTS = new Set(["png","jpg","jpeg","gif","bmp","webp","svg","ico","tiff","tif","avif"]);
|
|
23
|
+
const BINARY_EXTS = new Set(["zip","gz","7z","tar","dmg","pdf","xls","xlsx","doc","docx","exe","rar","ttf","mov","mp4","mp3","db","db3","sqlite","sqlite3","dat","wasm","bin","so","dylib","dll"]);
|
|
24
|
+
|
|
25
|
+
function _fileKind(filename) {
|
|
26
|
+
if (!filename) return "text";
|
|
27
|
+
const ext = filename.split(".").pop().toLowerCase();
|
|
28
|
+
if (IMAGE_EXTS.has(ext)) return "image";
|
|
29
|
+
if (BINARY_EXTS.has(ext)) return "binary";
|
|
30
|
+
return "text";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function _detectLanguage(filename) {
|
|
34
|
+
if (!filename) return "markdown";
|
|
35
|
+
const ext = filename.split(".").pop().toLowerCase();
|
|
36
|
+
const map = { md: "markdown", markdown: "markdown" };
|
|
37
|
+
return map[ext] || "markdown";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _isDark() {
|
|
41
|
+
return document.documentElement.getAttribute("data-theme") === "dark";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _buildExtensions(opts) {
|
|
45
|
+
const extensions = [
|
|
46
|
+
CM.lineNumbers(),
|
|
47
|
+
CM.highlightActiveLineGutter(),
|
|
48
|
+
CM.highlightSpecialChars(),
|
|
49
|
+
CM.history(),
|
|
50
|
+
CM.drawSelection(),
|
|
51
|
+
CM.dropCursor(),
|
|
52
|
+
CM.indentOnInput(),
|
|
53
|
+
CM.bracketMatching(),
|
|
54
|
+
CM.rectangularSelection(),
|
|
55
|
+
CM.crosshairCursor(),
|
|
56
|
+
CM.highlightActiveLine(),
|
|
57
|
+
CM.highlightSelectionMatches(),
|
|
58
|
+
CM.keymap.of([
|
|
59
|
+
...CM.defaultKeymap,
|
|
60
|
+
...CM.historyKeymap,
|
|
61
|
+
...CM.searchKeymap,
|
|
62
|
+
...CM.foldKeymap,
|
|
63
|
+
CM.indentWithTab,
|
|
64
|
+
]),
|
|
65
|
+
CM.search(),
|
|
66
|
+
CM.foldGutter(),
|
|
67
|
+
CM.syntaxHighlighting(CM.defaultHighlightStyle, { fallback: true }),
|
|
68
|
+
CM.EditorView.lineWrapping,
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
if (_isDark()) {
|
|
72
|
+
extensions.push(CM.oneDark);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const langFn = LANG_MAP[opts.language || "markdown"];
|
|
76
|
+
if (langFn) extensions.push(langFn());
|
|
77
|
+
|
|
78
|
+
if (opts.readOnly) {
|
|
79
|
+
extensions.push(CM.EditorState.readOnly.of(true));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (opts.onSave) {
|
|
83
|
+
extensions.push(CM.keymap.of([{
|
|
84
|
+
key: "Mod-s",
|
|
85
|
+
run: () => { opts.onSave(opts._getContent()); return true; }
|
|
86
|
+
}]));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return extensions;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function open(opts) {
|
|
93
|
+
const {
|
|
94
|
+
content = "",
|
|
95
|
+
title = "Editor",
|
|
96
|
+
readOnly = false,
|
|
97
|
+
onSave = null,
|
|
98
|
+
onClose = null,
|
|
99
|
+
imageUrl = null,
|
|
100
|
+
} = opts;
|
|
101
|
+
|
|
102
|
+
const kind = opts.kind || (opts.filename ? _fileKind(opts.filename) : "text");
|
|
103
|
+
const language = opts.language || _detectLanguage(opts.filename);
|
|
104
|
+
|
|
105
|
+
let overlay = document.getElementById("code-editor-overlay");
|
|
106
|
+
if (overlay) overlay.remove();
|
|
107
|
+
|
|
108
|
+
overlay = document.createElement("div");
|
|
109
|
+
overlay.id = "code-editor-overlay";
|
|
110
|
+
overlay.className = "modal-overlay";
|
|
111
|
+
|
|
112
|
+
const cancelLabel = I18n.t("modal.cancel");
|
|
113
|
+
const closeLabel = I18n.t("modal.close");
|
|
114
|
+
const saveLabel = I18n.t("modal.save");
|
|
115
|
+
|
|
116
|
+
const isReadOnlyOrImage = readOnly || kind === "image";
|
|
117
|
+
const footerActions = isReadOnlyOrImage
|
|
118
|
+
? `<button class="btn btn-secondary code-editor-cancel">${closeLabel}</button>`
|
|
119
|
+
: `<button class="btn btn-secondary code-editor-cancel">${cancelLabel}</button><button class="btn btn-primary code-editor-save">${saveLabel}</button>`;
|
|
120
|
+
|
|
121
|
+
overlay.innerHTML = `
|
|
122
|
+
<div class="code-editor-modal${kind === "image" ? " code-editor-modal--image" : ""}">
|
|
123
|
+
<div class="code-editor-header">
|
|
124
|
+
<h3 class="code-editor-title"></h3>
|
|
125
|
+
<button class="code-editor-close" title="${closeLabel}">×</button>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="code-editor-body"></div>
|
|
128
|
+
<div class="code-editor-footer">
|
|
129
|
+
<span class="code-editor-status"></span>
|
|
130
|
+
<div class="code-editor-actions">${footerActions}</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>`;
|
|
133
|
+
|
|
134
|
+
document.body.appendChild(overlay);
|
|
135
|
+
overlay.querySelector(".code-editor-title").textContent = title;
|
|
136
|
+
|
|
137
|
+
const body = overlay.querySelector(".code-editor-body");
|
|
138
|
+
const status = overlay.querySelector(".code-editor-status");
|
|
139
|
+
const closeBtn = overlay.querySelector(".code-editor-close");
|
|
140
|
+
const cancelBtn = overlay.querySelector(".code-editor-cancel");
|
|
141
|
+
const saveBtn = overlay.querySelector(".code-editor-save");
|
|
142
|
+
|
|
143
|
+
function close() {
|
|
144
|
+
overlay.remove();
|
|
145
|
+
if (onClose) onClose();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
closeBtn.addEventListener("click", close);
|
|
149
|
+
if (cancelBtn) cancelBtn.addEventListener("click", close);
|
|
150
|
+
overlay.addEventListener("click", (e) => { if (e.target === overlay) close(); });
|
|
151
|
+
|
|
152
|
+
if (kind === "image") {
|
|
153
|
+
body.classList.add("code-editor-body--image");
|
|
154
|
+
const img = document.createElement("img");
|
|
155
|
+
img.className = "code-editor-img-preview";
|
|
156
|
+
img.alt = title;
|
|
157
|
+
img.src = imageUrl || "";
|
|
158
|
+
body.appendChild(img);
|
|
159
|
+
return { close };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const editorOpts = { language, readOnly, onSave: null, _getContent: null };
|
|
163
|
+
const getContent = () => view.state.doc.toString();
|
|
164
|
+
editorOpts._getContent = getContent;
|
|
165
|
+
editorOpts.onSave = onSave ? () => doSave() : null;
|
|
166
|
+
|
|
167
|
+
const view = new CM.EditorView({
|
|
168
|
+
state: CM.EditorState.create({
|
|
169
|
+
doc: content,
|
|
170
|
+
extensions: _buildExtensions(editorOpts),
|
|
171
|
+
}),
|
|
172
|
+
parent: body,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
async function doSave() {
|
|
176
|
+
if (!onSave) return;
|
|
177
|
+
if (saveBtn) saveBtn.disabled = true;
|
|
178
|
+
status.textContent = I18n.t("modal.saving");
|
|
179
|
+
status.className = "code-editor-status";
|
|
180
|
+
try {
|
|
181
|
+
await onSave(getContent());
|
|
182
|
+
close();
|
|
183
|
+
} catch (e) {
|
|
184
|
+
status.textContent = e.message || "Save failed";
|
|
185
|
+
status.className = "code-editor-status code-editor-status-error";
|
|
186
|
+
if (saveBtn) saveBtn.disabled = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (saveBtn) saveBtn.addEventListener("click", doSave);
|
|
191
|
+
setTimeout(() => view.focus(), 50);
|
|
192
|
+
|
|
193
|
+
return { view, close, getContent };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
window.CodeEditor = { open, fileKind: _fileKind };
|
|
197
|
+
})(window);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// ── Session aside chrome — resize / collapse / opener ─────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Host-owned controls for the right column (#session-aside). The tab bar and
|
|
4
|
+
// bodies inside are rendered by Clacky.ext; this only drives the surrounding
|
|
5
|
+
// chrome so slot re-renders never disturb width or collapse state.
|
|
6
|
+
//
|
|
7
|
+
// - drag #session-aside-resize to change width (persisted)
|
|
8
|
+
// - #btn-aside-collapse hides the column; #btn-aside-open brings it back
|
|
9
|
+
// - when the slot is empty (no panels for this agent) CSS collapses the
|
|
10
|
+
// column on its own; the opener stays hidden in that case
|
|
11
|
+
//
|
|
12
|
+
// Depends on: nothing (loads right after core/ext.js).
|
|
13
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
14
|
+
"use strict";
|
|
15
|
+
|
|
16
|
+
(() => {
|
|
17
|
+
const WIDTH_KEY = "clacky.aside.width";
|
|
18
|
+
const OPEN_KEY = "clacky.aside.open";
|
|
19
|
+
const MIN_W = 280;
|
|
20
|
+
const MAX_W = 720;
|
|
21
|
+
|
|
22
|
+
const $ = (id) => document.getElementById(id);
|
|
23
|
+
|
|
24
|
+
function slotEmpty() {
|
|
25
|
+
const slot = $("ext-slot-session-aside");
|
|
26
|
+
return !slot || slot.childElementCount === 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function applyOpenState() {
|
|
30
|
+
const aside = $("session-aside");
|
|
31
|
+
const opener = $("btn-aside-open");
|
|
32
|
+
const overlay = $("workspace-overlay");
|
|
33
|
+
if (!aside) return;
|
|
34
|
+
let open = true;
|
|
35
|
+
try { open = localStorage.getItem(OPEN_KEY) !== "0"; } catch (_e) { /* ignore */ }
|
|
36
|
+
const empty = slotEmpty();
|
|
37
|
+
aside.classList.toggle("collapsed", !open);
|
|
38
|
+
if (opener) opener.style.display = (!open && !empty) ? "" : "none";
|
|
39
|
+
if (overlay) overlay.classList.toggle("active", open && !empty);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function setOpen(open) {
|
|
43
|
+
try { localStorage.setItem(OPEN_KEY, open ? "1" : "0"); } catch (_e) { /* ignore */ }
|
|
44
|
+
applyOpenState();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function initResize() {
|
|
48
|
+
const aside = $("session-aside");
|
|
49
|
+
const handle = $("session-aside-resize");
|
|
50
|
+
if (!aside || !handle) return;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const saved = parseFloat(localStorage.getItem(WIDTH_KEY));
|
|
54
|
+
if (saved >= MIN_W && saved <= MAX_W) aside.style.setProperty("--session-aside-width", saved + "px");
|
|
55
|
+
} catch (_e) { /* ignore */ }
|
|
56
|
+
|
|
57
|
+
let dragging = false;
|
|
58
|
+
let startX = 0;
|
|
59
|
+
let startW = 0;
|
|
60
|
+
|
|
61
|
+
handle.addEventListener("mousedown", (e) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
dragging = true;
|
|
64
|
+
startX = e.clientX;
|
|
65
|
+
startW = parseFloat(getComputedStyle(aside).getPropertyValue("--session-aside-width"));
|
|
66
|
+
handle.classList.add("active");
|
|
67
|
+
document.body.style.cursor = "col-resize";
|
|
68
|
+
document.body.style.userSelect = "none";
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
document.addEventListener("mousemove", (e) => {
|
|
72
|
+
if (!dragging) return;
|
|
73
|
+
const dx = startX - e.clientX;
|
|
74
|
+
const w = Math.min(MAX_W, Math.max(MIN_W, startW + dx));
|
|
75
|
+
aside.style.setProperty("--session-aside-width", w + "px");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
document.addEventListener("mouseup", () => {
|
|
79
|
+
if (!dragging) return;
|
|
80
|
+
dragging = false;
|
|
81
|
+
handle.classList.remove("active");
|
|
82
|
+
document.body.style.cursor = "";
|
|
83
|
+
document.body.style.userSelect = "";
|
|
84
|
+
const w = parseFloat(getComputedStyle(aside).getPropertyValue("--session-aside-width"));
|
|
85
|
+
try { localStorage.setItem(WIDTH_KEY, w); } catch (_e) { /* ignore */ }
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function init() {
|
|
90
|
+
const collapse = $("btn-aside-collapse");
|
|
91
|
+
const opener = $("btn-aside-open");
|
|
92
|
+
const overlay = $("workspace-overlay");
|
|
93
|
+
if (collapse) collapse.addEventListener("click", () => setOpen(false));
|
|
94
|
+
if (opener) opener.addEventListener("click", () => setOpen(true));
|
|
95
|
+
if (overlay) overlay.addEventListener("click", () => setOpen(false));
|
|
96
|
+
initResize();
|
|
97
|
+
applyOpenState();
|
|
98
|
+
|
|
99
|
+
// Re-evaluate opener visibility whenever the slot content changes (panels
|
|
100
|
+
// re-render on session / agent switch).
|
|
101
|
+
const slot = $("ext-slot-session-aside");
|
|
102
|
+
if (slot && window.MutationObserver) {
|
|
103
|
+
new MutationObserver(() => applyOpenState()).observe(slot, { childList: true });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (document.readyState === "loading") {
|
|
108
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
109
|
+
} else {
|
|
110
|
+
init();
|
|
111
|
+
}
|
|
112
|
+
})();
|