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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/lib/clacky/billing/platform_billing.rb +3 -0
  4. data/lib/clacky/default_extensions/git/panels/git/view.js +1911 -398
  5. data/lib/clacky/default_extensions/preview/panels/preview/view.js +199 -34
  6. data/lib/clacky/providers.rb +43 -17
  7. data/lib/clacky/search_config.rb +33 -11
  8. data/lib/clacky/server/git_panel.rb +360 -34
  9. data/lib/clacky/server/http_server.rb +145 -47
  10. data/lib/clacky/server/preview.rb +118 -17
  11. data/lib/clacky/server/scheduler.rb +81 -2
  12. data/lib/clacky/server/web_ui_controller.rb +35 -27
  13. data/lib/clacky/session_manager.rb +52 -5
  14. data/lib/clacky/utils/mac_app_detector.rb +8 -7
  15. data/lib/clacky/utils/model_pricing.rb +60 -2
  16. data/lib/clacky/utils/windows_app_detector.rb +3 -1
  17. data/lib/clacky/version.rb +1 -1
  18. data/lib/clacky/web/app.css +650 -96
  19. data/lib/clacky/web/app.js +59 -6
  20. data/lib/clacky/web/auth.js +1 -1
  21. data/lib/clacky/web/components/composer.js +107 -8
  22. data/lib/clacky/web/components/image-zoom.js +165 -0
  23. data/lib/clacky/web/components/model-picker.js +6 -4
  24. data/lib/clacky/web/components/notify.js +2 -2
  25. data/lib/clacky/web/core/aside.js +60 -3
  26. data/lib/clacky/web/features/billing/view.js +2 -2
  27. data/lib/clacky/web/features/channels/view.js +5 -2
  28. data/lib/clacky/web/features/extensions/view.js +2 -2
  29. data/lib/clacky/web/features/new-session/store.js +79 -2
  30. data/lib/clacky/web/features/new-session/view.js +496 -58
  31. data/lib/clacky/web/features/skills/store.js +1 -1
  32. data/lib/clacky/web/features/tasks/view.js +162 -51
  33. data/lib/clacky/web/features/workspace/store.js +13 -6
  34. data/lib/clacky/web/features/workspace/view.js +153 -163
  35. data/lib/clacky/web/i18n.js +238 -56
  36. data/lib/clacky/web/index.html +173 -157
  37. data/lib/clacky/web/projects.js +37 -4
  38. data/lib/clacky/web/sessions.js +181 -105
  39. data/lib/clacky/web/settings.js +26 -8
  40. data/lib/clacky/web/skills.js +2 -2
  41. data/lib/clacky/web/theme.js +17 -2
  42. data/lib/clacky/web/utils.js +17 -2
  43. data/lib/clacky/web/weixin-qr.html +2 -1
  44. data/lib/clacky/web/ws-dispatcher.js +8 -29
  45. metadata +2 -1
@@ -130,12 +130,14 @@ const ChannelsView = (() => {
130
130
  row.className = "channel-row";
131
131
  row.id = `channel-card-${platform}`;
132
132
 
133
+ // Rows survive a language switch, so the tooltip also carries the lookup
134
+ // key that I18n.applyAll() rewrites in place.
133
135
  const action = hasConfig
134
136
  ? `${_toggleHtml(platform, !!data.enabled)}
135
137
  <button type="button" class="channel-row-btn" data-channel-more="${_esc(platform)}"
136
138
  title="${_esc(I18n.t("channels.btn.more"))}" aria-haspopup="menu" aria-expanded="false">${MORE_SVG}</button>`
137
139
  : `<button type="button" class="channel-row-btn" data-channel-setup="${_esc(platform)}"
138
- title="${_esc(I18n.t("channels.btn.setup"))}">${PLUS_SVG}</button>`;
140
+ data-tooltip="${_esc(I18n.t("channels.btn.setup"))}" data-i18n-tooltip="channels.btn.setup">${PLUS_SVG}</button>`;
139
141
 
140
142
  row.innerHTML = `
141
143
  <span class="channel-logo ${_esc(meta.logoClass)}">${meta.logo}</span>
@@ -177,7 +179,8 @@ const ChannelsView = (() => {
177
179
  <div class="channel-row-desc" title="${_esc(desc)}">${_esc(desc)}</div>
178
180
  </div>
179
181
  <div class="channel-row-action">
180
- <button type="button" class="channel-row-btn" id="btn-custom-dev-guide" title="${_esc(I18n.t("channels.customDev.btn"))}">${PLUS_SVG}</button>
182
+ <button type="button" class="channel-row-btn" id="btn-custom-dev-guide"
183
+ data-tooltip="${_esc(I18n.t("channels.customDev.btn"))}" data-i18n-tooltip="channels.customDev.btn">${PLUS_SVG}</button>
181
184
  </div>
182
185
  `;
183
186
  row.querySelector("#btn-custom-dev-guide").addEventListener("click", () => {
@@ -192,12 +192,12 @@ const ExtensionsView = (() => {
192
192
  // keeps the same "+" (enable) affordance as anything waiting to be
193
193
  // installed rather than the "…" menu.
194
194
  if (ext.layer === "builtin" && ext.disabled === true) {
195
- return `${failedHtml}<button type="button" class="ext-row-add" data-ext-row-enable="${escapeHtml(id)}" title="${escapeHtml(I18n.t("extensions.action.enable"))}">${_plusSvg()}</button>`;
195
+ return `${failedHtml}<button type="button" class="ext-row-add" data-ext-row-enable="${escapeHtml(id)}" data-tooltip="${escapeHtml(I18n.t("extensions.action.enable"))}">${_plusSvg()}</button>`;
196
196
  }
197
197
  return `${failedHtml}${_rowMore(id)}`;
198
198
  }
199
199
 
200
- return `${failedHtml}<button type="button" class="ext-row-add" data-ext-row-install="${escapeHtml(id)}" title="${escapeHtml(I18n.t("extensions.action.install"))}">${_plusSvg()}</button>`;
200
+ return `${failedHtml}<button type="button" class="ext-row-add" data-ext-row-install="${escapeHtml(id)}" data-tooltip="${escapeHtml(I18n.t("extensions.action.install"))}">${_plusSvg()}</button>`;
201
201
  }
202
202
 
203
203
  function _rowMore(id) {
@@ -13,8 +13,8 @@
13
13
  // ───────────────────────────────────────────────────────────────────────────
14
14
 
15
15
  const NewSessionStore = (() => {
16
- const LS_AGENT_KEY = "openclacky.newSession.lastAgent";
17
- const LS_PROJECT_KEY = "openclacky.newSession.lastProject";
16
+ const LS_AGENT_KEY = "clacky-new-session-last-agent";
17
+ const LS_PROJECT_KEY = "clacky-new-session-last-project";
18
18
 
19
19
  const _state = {
20
20
  agents: [],
@@ -30,6 +30,9 @@ const NewSessionStore = (() => {
30
30
  },
31
31
  advancedOpen: false,
32
32
  creating: false,
33
+ // Branch state of the selected project's working dir. `loaded` separates
34
+ // "never fetched" from "fetched, not a repo" so the chip can hide quietly.
35
+ git: { key: null, projectId: null, dir: "", loaded: false, loading: false, repo: false, branch: "", branches: [] },
33
36
  };
34
37
 
35
38
  const _listeners = {};
@@ -138,6 +141,77 @@ const NewSessionStore = (() => {
138
141
  }
139
142
  }
140
143
 
144
+ // ── Git state of the selected project's working dir ──────────────────────
145
+ // Only the branch chip cares, so failures collapse to `repo: false` rather
146
+ // than surfacing an error the user cannot act on. Keyed by project + dir
147
+ // because a session's working dir may point at a different repo entirely.
148
+ function gitKey(projectId, dir) {
149
+ return `${projectId || ""}\u0000${dir || ""}`;
150
+ }
151
+
152
+ async function loadGitInfo(projectId, { dir = "", force = false } = {}) {
153
+ const key = gitKey(projectId, dir);
154
+ if (!projectId) {
155
+ if (_state.git.key || !_state.git.loaded) {
156
+ _state.git = { key: null, projectId: null, dir: "", loaded: true, loading: false, repo: false, branch: "", branches: [] };
157
+ _emit("newSession:git-changed", { git: _state.git });
158
+ }
159
+ return _state.git;
160
+ }
161
+ if (!force && _state.git.loaded && _state.git.key === key) return _state.git;
162
+ if (!force && _state.git.loading && _state.git.key === key) return _state.git;
163
+
164
+ _state.git = {
165
+ key,
166
+ projectId: String(projectId),
167
+ dir: dir || "",
168
+ loaded: false,
169
+ loading: true,
170
+ repo: false,
171
+ branch: "",
172
+ branches: [],
173
+ };
174
+ _emit("newSession:git-changed", { git: _state.git });
175
+
176
+ let data = null;
177
+ try {
178
+ data = await Projects.gitInfo(projectId, dir);
179
+ } catch (e) {
180
+ data = { repo: false };
181
+ }
182
+ // A slow response for a project the user already switched away from must
183
+ // not overwrite the newer one.
184
+ if (_state.git.key !== key) return _state.git;
185
+
186
+ _state.git = {
187
+ key,
188
+ projectId: String(projectId),
189
+ dir: dir || "",
190
+ loaded: true,
191
+ loading: false,
192
+ repo: !!(data && data.repo),
193
+ branch: (data && data.branch) || "",
194
+ branches: (data && data.branches) || [],
195
+ };
196
+ _emit("newSession:git-changed", { git: _state.git });
197
+ return _state.git;
198
+ }
199
+
200
+ /** Merge a checkout response into the cached git state. */
201
+ function applyGitState(data) {
202
+ if (!data) return _state.git;
203
+ _state.git = {
204
+ ..._state.git,
205
+ loaded: true,
206
+ loading: false,
207
+ repo: true,
208
+ branch: data.branch || _state.git.branch,
209
+ branches: data.branches || _state.git.branches,
210
+ };
211
+ _emit("newSession:git-changed", { git: _state.git });
212
+ return _state.git;
213
+ }
214
+
141
215
  function _autoName(existingSessions) {
142
216
  const maxN = (existingSessions || []).reduce((max, s) => {
143
217
  const m = s.name && s.name.match(/^Session (\d+)$/);
@@ -231,6 +305,9 @@ const NewSessionStore = (() => {
231
305
  updateAdvanced,
232
306
  loadModels,
233
307
  loadDefaultDirectory,
308
+ loadGitInfo,
309
+ gitKey,
310
+ applyGitState,
234
311
  createSession,
235
312
  fetchSkillsForAgent,
236
313
  loadAgentName,