clay-server 5.0.2-beta.1 → 5.1.0-beta.2

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 (162) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/lib/daemon-project-access.js +7 -1
  4. package/lib/daemon.js +2 -0
  5. package/lib/dicebear-avatar.js +62 -0
  6. package/lib/knowledge-db-service.js +175 -0
  7. package/lib/knowledge-db-validation.js +0 -0
  8. package/lib/knowledge-document-index.js +49 -0
  9. package/lib/knowledge-draft-store.js +56 -0
  10. package/lib/knowledge-file-store.js +199 -0
  11. package/lib/knowledge-markdown.js +128 -0
  12. package/lib/mate-knowledge-mcp-server.js +29 -6
  13. package/lib/mate-knowledge-service.js +23 -2
  14. package/lib/mate-knowledge-sync.js +36 -21
  15. package/lib/mate-workspace-color.js +4 -0
  16. package/lib/mates.js +18 -0
  17. package/lib/project-connection.js +36 -2
  18. package/lib/project-debate-proposal.js +6 -0
  19. package/lib/project-debate-workspace.js +43 -0
  20. package/lib/project-debate.js +1 -0
  21. package/lib/project-file-path.js +2 -3
  22. package/lib/project-knowledge.js +315 -136
  23. package/lib/project-mate-creation-proposal.js +1 -0
  24. package/lib/project-request-access.js +13 -1
  25. package/lib/project-sessions.js +39 -2
  26. package/lib/project-user-knowledge.js +60 -0
  27. package/lib/project.js +59 -18
  28. package/lib/public/app.js +73 -34
  29. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  30. package/lib/public/css/clay-workspace.css +105 -0
  31. package/lib/public/css/debates-workbench.css +47 -0
  32. package/lib/public/css/filebrowser.css +10 -0
  33. package/lib/public/css/home-debate-live.css +26 -0
  34. package/lib/public/css/home-hub.css +3 -1
  35. package/lib/public/css/home-mate-model-picker.css +15 -131
  36. package/lib/public/css/home-mate-settings.css +168 -16
  37. package/lib/public/css/icon-strip.css +249 -32
  38. package/lib/public/css/mate-conversation.css +176 -0
  39. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  40. package/lib/public/css/mate-workspace-color.css +16 -0
  41. package/lib/public/css/mates.css +28 -28
  42. package/lib/public/css/messages.css +1 -114
  43. package/lib/public/css/mobile-nav.css +16 -16
  44. package/lib/public/css/overlays.css +8 -0
  45. package/lib/public/css/project-logs.css +7 -0
  46. package/lib/public/css/project-mate-creation.css +105 -0
  47. package/lib/public/css/right-workbench.css +3 -3
  48. package/lib/public/css/session-folders.css +80 -21
  49. package/lib/public/css/sidebar.css +183 -107
  50. package/lib/public/css/title-bar.css +151 -0
  51. package/lib/public/css/worker-delegation.css +47 -0
  52. package/lib/public/css/worker-proposal.css +72 -150
  53. package/lib/public/css/you.css +74 -0
  54. package/lib/public/index.html +33 -101
  55. package/lib/public/modules/account-island.js +230 -0
  56. package/lib/public/modules/app-cursors.js +19 -43
  57. package/lib/public/modules/app-debate-ui.js +4 -3
  58. package/lib/public/modules/app-header.js +106 -0
  59. package/lib/public/modules/app-message-router.js +12 -3
  60. package/lib/public/modules/app-messages.js +34 -9
  61. package/lib/public/modules/app-notifications.js +4 -7
  62. package/lib/public/modules/app-projects.js +5 -38
  63. package/lib/public/modules/app-rendering.js +12 -13
  64. package/lib/public/modules/avatar.js +25 -9
  65. package/lib/public/modules/chat-render-runtime.js +16 -4
  66. package/lib/public/modules/command-palette.js +2 -15
  67. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  68. package/lib/public/modules/debate-controls.js +299 -0
  69. package/lib/public/modules/debate.js +2 -0
  70. package/lib/public/modules/debates-workbench.js +339 -0
  71. package/lib/public/modules/default-ai.js +81 -65
  72. package/lib/public/modules/filebrowser.js +28 -8
  73. package/lib/public/modules/home-debate-controls.js +18 -289
  74. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  75. package/lib/public/modules/home-mate-color-picker.js +66 -0
  76. package/lib/public/modules/home-mate-creation.js +1 -1
  77. package/lib/public/modules/home-mate-instructions.js +90 -0
  78. package/lib/public/modules/home-mate-model-picker.js +80 -271
  79. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  80. package/lib/public/modules/home-mate-settings.js +118 -125
  81. package/lib/public/modules/home-shell.js +3 -0
  82. package/lib/public/modules/home-surface-boot.js +1 -9
  83. package/lib/public/modules/html-preview.js +19 -0
  84. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  85. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  86. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  87. package/lib/public/modules/knowledge-history-state.js +55 -0
  88. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  89. package/lib/public/modules/knowledge-preview-links.js +70 -0
  90. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  91. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  92. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  93. package/lib/public/modules/mate-knowledge.js +11 -3
  94. package/lib/public/modules/mate-management.js +54 -10
  95. package/lib/public/modules/mate-sidebar.js +11 -1
  96. package/lib/public/modules/mate-workspace-color.js +33 -0
  97. package/lib/public/modules/mention.js +5 -13
  98. package/lib/public/modules/profile.js +51 -18
  99. package/lib/public/modules/project-chat-identity.js +79 -0
  100. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  101. package/lib/public/modules/project-debate-workspace.js +185 -0
  102. package/lib/public/modules/project-header-identity.js +127 -0
  103. package/lib/public/modules/project-logs-render.js +9 -4
  104. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  105. package/lib/public/modules/project-mate-navigation.js +238 -0
  106. package/lib/public/modules/project-session-navigation.js +29 -0
  107. package/lib/public/modules/project-switcher.js +6 -8
  108. package/lib/public/modules/projects-hub-view.js +5 -6
  109. package/lib/public/modules/projects-hub.js +2 -6
  110. package/lib/public/modules/qrcode.js +27 -8
  111. package/lib/public/modules/search-clay-chat.js +8 -11
  112. package/lib/public/modules/session-activity-tooltip.js +12 -0
  113. package/lib/public/modules/session-create-form.js +86 -14
  114. package/lib/public/modules/session-create.js +50 -7
  115. package/lib/public/modules/session-list-visibility.js +41 -0
  116. package/lib/public/modules/session-presence-ui.js +4 -1
  117. package/lib/public/modules/session-title-marquee.js +51 -0
  118. package/lib/public/modules/sidebar-mates.js +3 -50
  119. package/lib/public/modules/sidebar-mobile.js +63 -60
  120. package/lib/public/modules/sidebar-projects.js +84 -97
  121. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  122. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  123. package/lib/public/modules/sidebar-sessions.js +49 -50
  124. package/lib/public/modules/sidebar.js +11 -13
  125. package/lib/public/modules/thinking-lifecycle.js +7 -6
  126. package/lib/public/modules/tool-palette-order.js +4 -3
  127. package/lib/public/modules/tool-palette.js +13 -3
  128. package/lib/public/modules/tools.js +22 -22
  129. package/lib/public/modules/worker-proposal.js +42 -8
  130. package/lib/public/modules/worker-runtime-picker.js +115 -0
  131. package/lib/public/modules/you-activity.js +121 -0
  132. package/lib/public/modules/you.js +251 -0
  133. package/lib/public/style.css +16 -8
  134. package/lib/public/vendor/codemirror/LICENSE +21 -0
  135. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  136. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  137. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  138. package/lib/public/vendor/codemirror/markdown.js +886 -0
  139. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  140. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  141. package/lib/sdk-bridge.js +15 -3
  142. package/lib/server-admin.js +11 -0
  143. package/lib/server-home-chat-events.js +1 -1
  144. package/lib/server-home-chat.js +25 -3
  145. package/lib/server-home-debate-planning.js +4 -1
  146. package/lib/server-home-debates.js +2 -0
  147. package/lib/server-home-mate-creation.js +4 -1
  148. package/lib/server-mate-instructions.js +44 -0
  149. package/lib/server-mates.js +12 -3
  150. package/lib/server-palette.js +1 -1
  151. package/lib/server-settings.js +65 -1
  152. package/lib/server-user-knowledge.js +119 -0
  153. package/lib/server.js +21 -1
  154. package/lib/session-visibility.js +7 -2
  155. package/lib/sessions.js +32 -6
  156. package/lib/user-knowledge-service.js +167 -0
  157. package/lib/users-permissions.js +7 -0
  158. package/lib/users-project-mate-filter-preferences.js +39 -0
  159. package/lib/users.js +6 -0
  160. package/lib/workspace-query-access.js +4 -1
  161. package/lib/ws-schema.js +50 -0
  162. package/package.json +10 -1
@@ -7,7 +7,9 @@ import { refreshIcons } from './icons.js';
7
7
 
8
8
  var sequence = 0;
9
9
  var opener = null;
10
+ var preferredOpener = null;
10
11
  var popover = null;
12
+ var inlineTarget = null;
11
13
 
12
14
  function nextRequestId() {
13
15
  sequence += 1;
@@ -49,6 +51,7 @@ function close() {
49
51
 
50
52
  function position() {
51
53
  if (!popover || !opener) return;
54
+ if (inlineTarget) return;
52
55
  var rect = opener.getBoundingClientRect();
53
56
  var width = Math.min(390, Math.max(0, window.innerWidth - 24));
54
57
  popover.style.width = width + "px";
@@ -101,6 +104,40 @@ function addFieldLabel(text, control) {
101
104
  popover.appendChild(label);
102
105
  }
103
106
 
107
+ function inlineCurrentSelection(state, draft) {
108
+ return state.preference || state.selection || draft || {};
109
+ }
110
+
111
+ function renderInlineSummary() {
112
+ var state = store.get("defaultAiState") || {};
113
+ var draft = store.get("defaultAiDraft") || {};
114
+ var current = inlineCurrentSelection(state, draft);
115
+ var vendor = current.vendor ? (VENDOR_NAMES[current.vendor] || current.vendor) : "Loading default…";
116
+ var model = current.model || "Model unavailable";
117
+ var effort = current.effort || "Provider default";
118
+ popover.innerHTML = '';
119
+ var summary = document.createElement('button');
120
+ summary.type = 'button';
121
+ summary.className = 'default-ai-summary';
122
+ summary.id = 'account-ai-summary';
123
+ summary.setAttribute('aria-label', 'Default AI: ' + vendor + ', ' + model + ', ' + effort);
124
+ var label = document.createElement('span');
125
+ label.className = 'default-ai-summary-label';
126
+ label.textContent = 'Default AI';
127
+ var value = document.createElement('span');
128
+ value.className = 'default-ai-summary-value';
129
+ value.textContent = vendor + ' · ' + model + ' · ' + effort;
130
+ var icon = document.createElement('i');
131
+ icon.setAttribute('data-lucide', 'chevron-right');
132
+ icon.setAttribute('aria-hidden', 'true');
133
+ summary.appendChild(label);
134
+ summary.appendChild(value);
135
+ summary.appendChild(icon);
136
+ popover.appendChild(summary);
137
+ summary.addEventListener('click', function () { store.set({ defaultAiInlineEditing: true }); });
138
+ refreshIcons();
139
+ }
140
+
104
141
  function render() {
105
142
  if (!popover) return;
106
143
  var state = store.get("defaultAiState") || {};
@@ -109,7 +146,17 @@ function render() {
109
146
  var catalog = (state.catalogs || {})[draft.vendor] || null;
110
147
  var models = catalog && catalog.models || [];
111
148
 
112
- popover.innerHTML = '<div class="default-ai-title">Default AI</div><div class="default-ai-copy">Used for new Clay interviews and built-in AI assistance.</div>';
149
+ if (inlineTarget && store.get("defaultAiInlineEditing") !== true) {
150
+ renderInlineSummary();
151
+ position();
152
+ return;
153
+ }
154
+
155
+ if (inlineTarget) {
156
+ popover.innerHTML = '<div class="default-ai-edit-heading"><strong>Default AI</strong><button type="button" class="default-ai-cancel" aria-label="Cancel Default AI editing">Cancel</button></div>';
157
+ } else {
158
+ popover.innerHTML = '<div class="default-ai-title">Default AI</div><div class="default-ai-copy">Used for new Clay interviews and built-in AI assistance.</div>';
159
+ }
113
160
  if (state.loading && !vendors.length) {
114
161
  addStatus("Loading available AI providers…", false);
115
162
  position();
@@ -170,8 +217,8 @@ function render() {
170
217
  var save = document.createElement("button");
171
218
  save.type = "button";
172
219
  save.className = "default-ai-save";
173
- save.textContent = state.saving ? "Saving…" : "Save default";
174
- save.disabled = state.saving === true || vendors.indexOf(vendor.value) === -1 || !selectedModel || !selectedEffort;
220
+ save.textContent = state.saving ? "Applying…" : "Apply";
221
+ save.disabled = state.saving === true || store.get("defaultAiDraftDirty") !== true || vendors.indexOf(vendor.value) === -1 || !selectedModel || !selectedEffort;
175
222
  save.addEventListener("click", function () {
176
223
  var id = nextRequestId();
177
224
  var latest = store.get("defaultAiState") || {};
@@ -181,6 +228,13 @@ function render() {
181
228
  }
182
229
  render();
183
230
  });
231
+ if (inlineTarget) {
232
+ var cancel = popover.querySelector(".default-ai-cancel");
233
+ cancel.addEventListener("click", function () {
234
+ var canonical = inlineCurrentSelection(store.get("defaultAiState") || {}, store.get("defaultAiDraft") || {});
235
+ store.set({ defaultAiDraft: Object.assign({}, canonical), defaultAiDraftDirty: false, defaultAiInlineEditing: false });
236
+ });
237
+ }
184
238
  popover.appendChild(save);
185
239
  if (state.error) addStatus(state.error, true);
186
240
  position();
@@ -293,71 +347,25 @@ export function handleDefaultAiMessage(msg) {
293
347
  }
294
348
 
295
349
  export function initDefaultAi() {
296
- var actions = document.querySelector(".user-island-actions");
297
- if (!actions || document.getElementById("default-ai-btn")) return;
298
- var button = document.createElement("button");
299
- button.id = "default-ai-btn";
300
- button.className = "default-ai-button";
301
- button.title = "Default AI";
302
- button.setAttribute("aria-label", "Default AI");
303
- button.setAttribute("aria-haspopup", "dialog");
304
- button.setAttribute("aria-expanded", "false");
305
- button.innerHTML = '<span class="default-ai-avatar"></span>';
306
- var settings = document.getElementById("user-settings-btn");
307
- if (settings) actions.insertBefore(button, settings);
308
- else actions.appendChild(button);
309
- popover = document.createElement("section");
310
- popover.id = "default-ai-popover";
311
- popover.className = "default-ai-popover hidden";
312
- popover.setAttribute("role", "dialog");
313
- popover.setAttribute("aria-label", "Default AI settings");
314
- document.body.appendChild(popover);
315
- button.addEventListener("click", function () {
316
- if (!popover.classList.contains("hidden")) { close(); return; }
317
- opener = button;
318
- button.setAttribute("aria-expanded", "true");
319
- popover.classList.remove("hidden");
320
- requestDefaultAi();
321
- render();
322
- });
323
- document.addEventListener("click", function (event) {
324
- var path = typeof event.composedPath === "function" ? event.composedPath() : [];
325
- var clickedButton = path.indexOf(button) !== -1 || button.contains(event.target);
326
- var clickedPopover = path.indexOf(popover) !== -1 || popover.contains(event.target);
327
- if (!popover || popover.classList.contains("hidden") || clickedPopover || clickedButton) return;
328
- close();
329
- });
330
- document.addEventListener("keydown", function (event) {
331
- if (event.key !== "Escape" || !popover || popover.classList.contains("hidden")) return;
332
- event.preventDefault();
333
- close();
334
- });
335
- window.addEventListener("resize", position);
336
- window.addEventListener("scroll", position, true);
337
- updateBadge();
350
+ return;
338
351
  }
339
352
 
340
- export function openDefaultAi() {
341
- var button = document.getElementById("default-ai-btn");
342
- if (button && popover && popover.classList.contains("hidden")) button.click();
353
+ export function openDefaultAi(openerElement) {
354
+ document.dispatchEvent(new CustomEvent('clay:open-account-default-ai', { detail: { opener: openerElement || null } }));
343
355
  }
344
356
 
345
- function updateBadge() {
346
- var avatar = document.querySelector("#default-ai-btn .default-ai-avatar");
347
- var selection = (store.get("defaultAiState") || {}).selection;
348
- if (!avatar) return;
349
- var source = selection && VENDOR_AVATARS[selection.vendor];
350
- var image = avatar.querySelector("img");
351
- if (!source) {
352
- if (image) image.remove();
353
- return;
354
- }
355
- if (!image) {
356
- image = document.createElement("img");
357
- image.alt = "";
358
- avatar.appendChild(image);
359
- }
360
- if (image.getAttribute("src") !== source) image.setAttribute("src", source);
357
+ export function mountDefaultAi(target) {
358
+ inlineTarget = target;
359
+ popover = target;
360
+ opener = target;
361
+ render();
362
+ }
363
+
364
+ export function unmountDefaultAi(target) {
365
+ if (inlineTarget !== target) return;
366
+ inlineTarget = null;
367
+ opener = null;
368
+ popover = null;
361
369
  }
362
370
 
363
371
  store.subscribe(function (state, previous) {
@@ -371,7 +379,15 @@ store.subscribe(function (state, previous) {
371
379
  if (accountId && state.connected) requestDefaultAi();
372
380
  return;
373
381
  }
382
+ if (state.defaultAiInlineEditing !== previous.defaultAiInlineEditing) {
383
+ if (popover) render();
384
+ return;
385
+ }
374
386
  if (state.defaultAiState === previous.defaultAiState) return;
375
- updateBadge();
387
+ if (inlineTarget && previous.defaultAiState && previous.defaultAiState.saving === true && state.defaultAiState && state.defaultAiState.saving === false && state.defaultAiDraftDirty !== true && state.defaultAiInlineEditing === true) {
388
+ store.set({ defaultAiInlineEditing: false });
389
+ render();
390
+ return;
391
+ }
376
392
  if (popover && !popover.classList.contains("hidden")) render();
377
393
  });
@@ -13,6 +13,7 @@ import { initFileViewerTabs, openFileViewerTab, previewFileViewerTab, updateFile
13
13
  import { initFileBrowserContextMenu, downloadProjectFile } from './filebrowser-context-menu.js';
14
14
  import { registerClayFileLinkOpener } from './clay-file-links.js';
15
15
  import { isPlantUmlFile, renderPlantUmlPreview, cancelPlantUmlPreview } from './plantuml-preview.js';
16
+ import { isHtmlFile, renderHtmlPreview } from './html-preview.js';
16
17
 
17
18
  var ctx;
18
19
  var showDropHint = function () {};
@@ -43,7 +44,7 @@ export function initFileBrowser(_ctx) {
43
44
  onFocus: function(path) {
44
45
  claimRightWorkbench("file-viewer");
45
46
  ctx.fileViewerEl.classList.remove("hidden");
46
- if (path !== currentFilePath) requestFileContent(path);
47
+ if (path !== currentFilePath || (store.get("fileViewerIsHtml") && isRendered && !document.querySelector(".file-viewer-html-preview"))) requestFileContent(path);
47
48
  },
48
49
  onEmpty: function() { teardownFileViewer(); },
49
50
  });
@@ -194,10 +195,14 @@ export function initFileBrowser(_ctx) {
194
195
  downloadProjectFile(currentFilePath);
195
196
  });
196
197
 
197
- // Markdown render toggle
198
+ // File preview/source toggle
198
199
  document.getElementById("file-viewer-render").addEventListener("click", function () {
199
- if (currentContent == null || (!currentIsMarkdown && !currentIsSvg && !store.get("fileViewerIsPlantUml"))) return;
200
+ if (currentContent == null || (!currentIsMarkdown && !currentIsSvg && !store.get("fileViewerIsPlantUml") && !store.get("fileViewerIsHtml"))) return;
200
201
  isRendered = !isRendered;
202
+ if (store.get("fileViewerIsHtml")) {
203
+ renderHtmlBody();
204
+ return;
205
+ }
201
206
  if (store.get("fileViewerIsPlantUml")) {
202
207
  renderPlantUmlBody();
203
208
  return;
@@ -491,6 +496,8 @@ export function closeFileViewer() {
491
496
 
492
497
  function teardownFileViewer() {
493
498
  cancelPlantUmlPreview();
499
+ var htmlFrame = document.querySelector(".file-viewer-html-preview");
500
+ if (htmlFrame) htmlFrame.remove();
494
501
  if (currentFilePath && isFollowingMarkdown(currentFilePath)) cancelMarkdownFollow();
495
502
  sendUnwatch();
496
503
  inlineDiffActive = false;
@@ -528,7 +535,7 @@ export function resetFileBrowser() {
528
535
  isRendered = false;
529
536
  currentIsMarkdown = false;
530
537
  currentIsSvg = false;
531
- store.set({ fileViewerIsPlantUml: false });
538
+ store.set({ fileViewerIsPlantUml: false, fileViewerIsHtml: false });
532
539
  cancelPlantUmlPreview();
533
540
  historyVisible = false;
534
541
  currentHistoryEntries = [];
@@ -1165,7 +1172,7 @@ function showFileContent(msg, expectedRevision) {
1165
1172
  currentFilePath = msg.path;
1166
1173
  currentIsMarkdown = false;
1167
1174
  currentIsSvg = false;
1168
- store.set({ fileViewerIsPlantUml: false });
1175
+ store.set({ fileViewerIsPlantUml: false, fileViewerIsHtml: false });
1169
1176
  cancelPlantUmlPreview();
1170
1177
  if (!keepRenderState) isRendered = false;
1171
1178
  var requestedExt = msg.path.split(".").pop().toLowerCase();
@@ -1193,19 +1200,20 @@ function showFileContent(msg, expectedRevision) {
1193
1200
  lightweightPreview = (msg.size || 0) > FILE_RICH_PREVIEW_MAX_BYTES;
1194
1201
  currentIsMarkdown = !lightweightPreview && (ext === "md" || ext === "mdx");
1195
1202
  currentIsSvg = !lightweightPreview && ext === "svg";
1203
+ store.set({ fileViewerIsHtml: !lightweightPreview && isHtmlFile(msg.path) });
1196
1204
  store.set({ fileViewerIsPlantUml: !lightweightPreview && isPlantUmlFile(msg.path) });
1197
1205
  if (lightweightPreview) isRendered = false;
1198
1206
  if (pendingRenderedOpen && currentIsMarkdown) isRendered = true;
1199
1207
 
1200
- if (currentIsMarkdown || currentIsSvg || store.get("fileViewerIsPlantUml")) {
1208
+ if (currentIsMarkdown || currentIsSvg || store.get("fileViewerIsPlantUml") || store.get("fileViewerIsHtml")) {
1201
1209
  renderBtn.classList.remove("hidden");
1202
1210
  renderBtn.title = currentIsSvg ? "Show SVG source" : "Render markdown";
1203
- copyBtn.title = store.get("fileViewerIsPlantUml") ? "Copy PlantUML source" : currentIsSvg ? "Copy SVG source" : "Copy Markdown source";
1211
+ copyBtn.title = store.get("fileViewerIsHtml") ? "Copy HTML source" : store.get("fileViewerIsPlantUml") ? "Copy PlantUML source" : currentIsSvg ? "Copy SVG source" : "Copy Markdown source";
1204
1212
  } else {
1205
1213
  copyBtn.title = "Copy contents";
1206
1214
  }
1207
1215
 
1208
- // Markdown starts as source; SVG starts as a safe image preview.
1216
+ // Markdown starts as source; HTML, SVG and diagrams start as previews.
1209
1217
  if (lightweightPreview) {
1210
1218
  renderLargeTextFile(bodyEl, msg.content, msg.size);
1211
1219
  } else if (currentIsMarkdown) {
@@ -1214,6 +1222,9 @@ function showFileContent(msg, expectedRevision) {
1214
1222
  ? (previousContent == null ? "" : previousContent)
1215
1223
  : null;
1216
1224
  renderBody(transitionFrom, refreshSlideIndex);
1225
+ } else if (store.get("fileViewerIsHtml")) {
1226
+ if (!keepRenderState) isRendered = !store.get("pendingFileNavigation");
1227
+ renderHtmlBody();
1217
1228
  } else if (store.get("fileViewerIsPlantUml")) {
1218
1229
  if (!keepRenderState) isRendered = !store.get("pendingFileNavigation");
1219
1230
  renderPlantUmlBody();
@@ -1283,6 +1294,15 @@ function showFileContent(msg, expectedRevision) {
1283
1294
  }
1284
1295
  }
1285
1296
 
1297
+ function renderHtmlBody() {
1298
+ var bodyEl = document.getElementById("file-viewer-body");
1299
+ var renderBtn = document.getElementById("file-viewer-render");
1300
+ renderBtn.classList.toggle("active", isRendered);
1301
+ renderBtn.title = isRendered ? "Show HTML source" : "Show HTML preview";
1302
+ if (isRendered) renderHtmlPreview(bodyEl, currentContent, currentFilePath);
1303
+ else renderCodeWithLineNumbers(bodyEl, currentContent, "html");
1304
+ }
1305
+
1286
1306
  function renderPlantUmlBody() {
1287
1307
  var bodyEl = document.getElementById("file-viewer-body");
1288
1308
  var renderBtn = document.getElementById("file-viewer-render");
@@ -1,295 +1,24 @@
1
1
  import { store } from './store.js';
2
2
  import { getWs } from './ws-ref.js';
3
-
4
- var slotEl = null;
5
- var normalComposerEl = null;
6
- var modelEl = null;
7
- var controlEl = null;
8
- var controlMode = null;
9
- var controlStateKey = null;
10
- var currentState = null;
11
- var currentRequestId = null;
12
- var startNewDebate = null;
13
- var inputEl = null;
14
- var primaryEl = null;
15
- var secondaryEl = null;
16
- var activityEl = null;
17
- var handEl = null;
18
- var stopEl = null;
19
- var composing = false;
20
-
21
- function textElement(tag, className, text) {
22
- var element = document.createElement(tag);
23
- element.className = className;
24
- element.textContent = text || "";
25
- return element;
26
- }
27
-
28
- function findHeader(messages) {
29
- for (var i = (messages || []).length - 1; i >= 0; i--) if (messages[i] && messages[i].role === "debate_header") return messages[i];
30
- return null;
31
- }
32
-
33
- function findActiveTurn(messages) {
34
- for (var i = (messages || []).length - 1; i >= 0; i--) {
35
- if (messages[i] && messages[i].role === "debate_turn" && messages[i].status === "active") return messages[i];
36
- }
37
- return null;
38
- }
39
-
40
- export function homeDebateControlState(messages) {
41
- var header = findHeader(messages);
42
- if (!header || ["live", "ended", "interrupted"].indexOf(header.phase) === -1) return null;
43
- var mode = header.phase === "live" ? (header.interaction || "default") : "terminal";
44
- return { mode: mode, header: header, turn: findActiveTurn(messages), phase: header.phase };
45
- }
46
-
47
- function ensureElements() {
48
- var nextSlot = document.getElementById("home-debate-controls-slot");
49
- if (slotEl === nextSlot) return !!slotEl;
50
- slotEl = nextSlot;
51
- normalComposerEl = document.getElementById("home-mate-chat-composer");
52
- modelEl = document.getElementById("home-mate-chat-session-model");
53
- controlEl = null;
54
- controlMode = null;
55
- controlStateKey = null;
56
- return !!slotEl;
57
- }
58
-
59
- function clearControls() {
60
- if (slotEl) slotEl.innerHTML = "";
61
- controlEl = null;
62
- controlMode = null;
63
- controlStateKey = null;
64
- currentState = null;
65
- inputEl = null;
66
- primaryEl = null;
67
- secondaryEl = null;
68
- activityEl = null;
69
- handEl = null;
70
- stopEl = null;
71
- composing = false;
72
- }
73
-
74
- function setSpecialMode(active) {
75
- if (normalComposerEl) normalComposerEl.hidden = active;
76
- if (modelEl) modelEl.hidden = active;
77
- if (slotEl) slotEl.hidden = !active;
78
- }
79
-
80
- function sendControl(action, data) {
81
- var ws = getWs();
82
- if (!ws || ws.readyState !== 1 || !currentState) return false;
83
- ws.send(JSON.stringify({
84
- type: "home_debate_control",
85
- action: action,
86
- mateId: store.get('homeChatMateId'),
87
- sessionId: store.get('homeChatSessionId'),
88
- requestId: currentRequestId,
89
- text: data && data.text ? data.text : "",
90
- response: data && data.response ? data.response : null,
91
- }));
92
- return true;
93
- }
94
-
95
- function button(label, className) {
96
- var element = textElement("button", className, label);
97
- element.type = "button";
98
- return element;
99
- }
100
-
101
- function dots() {
102
- var group = document.createElement("span");
103
- group.className = "home-debate-control-dots";
104
- group.setAttribute("aria-hidden", "true");
105
- for (var i = 0; i < 3; i++) group.appendChild(document.createElement("i"));
106
- return group;
107
- }
108
-
109
- function activityLabel(state) {
110
- if (state.header.stopping) return state.turn ? "Stopping after " + (state.turn.mateName || "the current speaker") + " finishes" : "Stopping debate";
111
- if (!state.turn) return state.header.handRaised ? "Your hand is raised" : "Debate is live";
112
- var name = state.turn.mateName || "Mate";
113
- var activity = state.turn.activity || (state.turn.text ? "speaking" : "preparing");
114
- return name + " is " + activity.toLowerCase();
115
- }
116
-
117
- function lockSubmission(label) {
118
- if (inputEl) inputEl.disabled = true;
119
- if (primaryEl) { primaryEl.disabled = true; primaryEl.textContent = label; }
120
- if (secondaryEl) secondaryEl.disabled = true;
121
- }
122
-
123
- function submitInput(action, response) {
124
- var text = inputEl ? inputEl.value.trim() : "";
125
- if (action === "user_floor" && !text) return false;
126
- if (!sendControl(action, { text: text, response: response })) return false;
127
- lockSubmission(action === "user_floor" ? "Sending…" : "Continuing…");
128
- return true;
129
- }
130
-
131
- function bindInput(submit) {
132
- inputEl.addEventListener("compositionstart", function () { composing = true; });
133
- inputEl.addEventListener("compositionend", function () { composing = false; });
134
- inputEl.addEventListener("input", function () {
135
- if (controlMode === "user_floor" && primaryEl) primaryEl.disabled = !inputEl.value.trim();
136
- });
137
- inputEl.addEventListener("keydown", function (event) {
138
- if (event.key !== "Enter" || event.shiftKey || event.isComposing || composing || event.keyCode === 229) return;
139
- event.preventDefault();
140
- submit();
141
- });
142
- }
143
-
144
- function focusInputOnce() {
145
- var target = inputEl;
146
- if (!target) return;
147
- var focus = function () {
148
- if (target === inputEl && !target.disabled && target.isConnected !== false) target.focus({ preventScroll: true });
149
- };
150
- if (typeof requestAnimationFrame === "function") requestAnimationFrame(focus);
151
- else focus();
152
- }
153
-
154
- function buildDefault(state) {
155
- var context = document.createElement("div");
156
- context.className = "home-debate-control-context";
157
- context.appendChild(dots());
158
- activityEl = textElement("span", "home-debate-control-activity", activityLabel(state));
159
- activityEl.setAttribute("role", "status");
160
- activityEl.setAttribute("aria-live", "polite");
161
- context.appendChild(activityEl);
162
- controlEl.appendChild(context);
163
- var actions = document.createElement("div");
164
- actions.className = "home-debate-control-actions";
165
- handEl = button(state.header.handRaised ? "Hand raised" : "Raise hand", "home-debate-control-secondary");
166
- handEl.setAttribute("aria-pressed", state.header.handRaised ? "true" : "false");
167
- handEl.disabled = state.header.handRaised === true;
168
- handEl.addEventListener("click", function () {
169
- if (!sendControl("hand_raise")) return;
170
- handEl.disabled = true;
171
- handEl.textContent = "Raising hand…";
172
- });
173
- stopEl = button(state.header.stopping ? "Cancel stop" : "Stop debate", state.header.stopping ? "home-debate-control-secondary" : "home-debate-control-stop");
174
- stopEl.addEventListener("click", function () {
175
- var cancelling = currentState && currentState.header && currentState.header.stopping === true;
176
- if (!sendControl(cancelling ? "cancel_stop" : "stop")) return;
177
- stopEl.disabled = true;
178
- stopEl.textContent = cancelling ? "Cancelling…" : "Stopping…";
179
- });
180
- actions.appendChild(handEl);
181
- actions.appendChild(stopEl);
182
- controlEl.appendChild(actions);
183
- }
184
-
185
- function buildInputMode(state) {
186
- var isFloor = state.mode === "user_floor";
187
- var heading = textElement("div", "home-debate-control-heading", isFloor ? "You have the floor" : "The moderator is ready to conclude");
188
- heading.id = "home-debate-control-heading";
189
- controlEl.appendChild(heading);
190
- inputEl = document.createElement("textarea");
191
- inputEl.rows = 2;
192
- inputEl.placeholder = isFloor ? "Share your thoughts with the panel" : "Add an optional direction to continue";
193
- inputEl.setAttribute("aria-labelledby", heading.id);
194
- controlEl.appendChild(inputEl);
195
- var actions = document.createElement("div");
196
- actions.className = "home-debate-control-actions";
197
- if (isFloor) {
198
- primaryEl = button("Send", "home-debate-control-primary");
199
- primaryEl.disabled = true;
200
- primaryEl.addEventListener("click", function () { submitInput("user_floor"); });
201
- secondaryEl = button("Pass", "home-debate-control-secondary");
202
- secondaryEl.addEventListener("click", function () {
203
- if (!sendControl("user_floor", { text: "(The user passed without speaking)" })) return;
204
- lockSubmission("Sending…");
205
- });
206
- } else {
207
- primaryEl = button("Continue", "home-debate-control-primary");
208
- primaryEl.addEventListener("click", function () { submitInput("conclude", "continue"); });
209
- secondaryEl = button("End debate", "home-debate-control-stop");
210
- secondaryEl.addEventListener("click", function () {
211
- if (!sendControl("conclude", { response: "end" })) return;
212
- lockSubmission("Ending…");
213
- });
214
- }
215
- actions.appendChild(primaryEl);
216
- actions.appendChild(secondaryEl);
217
- controlEl.appendChild(actions);
218
- bindInput(function () { submitInput(isFloor ? "user_floor" : "conclude", isFloor ? null : "continue"); });
219
- focusInputOnce();
220
- }
221
-
222
- function terminalLabel(state) {
223
- if (state.phase === "interrupted") return "Debate interrupted when Clay restarted";
224
- if (state.header.reason === "error") return "The debate stopped after an error";
225
- if (state.header.reason === "stopped" || state.header.reason === "user_stopped") return "Debate stopped";
226
- return "Debate ended";
227
- }
228
-
229
- function buildTerminal(state) {
230
- var status = textElement("div", "home-debate-control-terminal-status", terminalLabel(state));
231
- status.setAttribute("role", "status");
232
- controlEl.appendChild(status);
233
- var actions = document.createElement("div");
234
- actions.className = "home-debate-control-actions";
235
- primaryEl = button("Resume debate", "home-debate-control-primary");
236
- primaryEl.addEventListener("click", function () {
237
- if (!sendControl("resume")) return;
238
- lockSubmission("Resuming…");
239
- });
240
- actions.appendChild(primaryEl);
241
- if (typeof startNewDebate === "function") {
242
- secondaryEl = button("New debate", "home-debate-control-secondary");
243
- secondaryEl.addEventListener("click", function () { startNewDebate(); });
244
- actions.appendChild(secondaryEl);
245
- }
246
- controlEl.appendChild(actions);
247
- }
248
-
249
- function buildControls(state) {
250
- slotEl.innerHTML = "";
251
- controlEl = document.createElement("section");
252
- controlEl.className = "home-debate-control-surface home-debate-control-" + state.mode;
253
- controlEl.setAttribute("role", state.mode === "default" ? "toolbar" : "region");
254
- controlEl.setAttribute("aria-label", state.mode === "terminal" ? "Debate status" : "Live debate controls");
255
- slotEl.appendChild(controlEl);
256
- if (state.mode === "default") buildDefault(state);
257
- else if (state.mode === "user_floor" || state.mode === "conclude") buildInputMode(state);
258
- else buildTerminal(state);
259
- controlMode = state.mode;
260
- controlStateKey = state.mode + ":" + state.phase + ":" + (state.header.reason || "");
261
- }
262
-
263
- function updateControls(state) {
264
- if (state.mode !== "default") return;
265
- var label = activityLabel(state);
266
- if (activityEl && activityEl.textContent !== label) activityEl.textContent = label;
267
- if (state.header.handRaised && handEl) {
268
- handEl.textContent = "Hand raised";
269
- handEl.disabled = true;
270
- handEl.setAttribute("aria-pressed", "true");
3
+ import { createDebateControls } from './debate-controls.js';
4
+ export { homeDebateControlState } from './debate-controls.js';
5
+
6
+ var controls = createDebateControls({
7
+ slotId: "home-debate-controls-slot",
8
+ composerId: "home-mate-chat-composer",
9
+ modelId: "home-mate-chat-session-model",
10
+ send: function (action, data, requestId) {
11
+ var ws = getWs();
12
+ if (!ws || ws.readyState !== 1) return false;
13
+ ws.send(JSON.stringify({
14
+ type: "home_debate_control", action: action,
15
+ mateId: store.get('homeChatMateId'), sessionId: store.get('homeChatSessionId'),
16
+ requestId: requestId, text: data.text || "", response: data.response || null
17
+ }));
18
+ return true;
271
19
  }
272
- if (stopEl) {
273
- stopEl.disabled = false;
274
- stopEl.textContent = state.header.stopping ? "Cancel stop" : "Stop debate";
275
- stopEl.className = state.header.stopping ? "home-debate-control-secondary" : "home-debate-control-stop";
276
- }
277
- }
20
+ });
278
21
 
279
22
  export function renderHomeDebateControls(messages, requestId, onStartNewDebate) {
280
- if (!ensureElements()) return false;
281
- var state = homeDebateControlState(messages);
282
- currentRequestId = requestId || null;
283
- startNewDebate = onStartNewDebate || null;
284
- if (!state) {
285
- setSpecialMode(false);
286
- clearControls();
287
- return false;
288
- }
289
- currentState = state;
290
- setSpecialMode(true);
291
- var nextStateKey = state.mode + ":" + state.phase + ":" + (state.header.reason || "");
292
- if (!controlEl || controlMode !== state.mode || controlStateKey !== nextStateKey) buildControls(state);
293
- else updateControls(state);
294
- return true;
23
+ return controls.render(messages, requestId, onStartNewDebate);
295
24
  }