clay-server 5.1.0-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.
- package/LICENSES/README.md +4 -0
- package/LICENSES/dicebear-avatars.md +16 -0
- package/lib/daemon-project-access.js +7 -1
- package/lib/daemon.js +2 -0
- package/lib/dicebear-avatar.js +62 -0
- package/lib/knowledge-db-service.js +175 -0
- package/lib/knowledge-db-validation.js +0 -0
- package/lib/knowledge-document-index.js +49 -0
- package/lib/knowledge-draft-store.js +56 -0
- package/lib/knowledge-file-store.js +199 -0
- package/lib/knowledge-markdown.js +128 -0
- package/lib/mate-knowledge-mcp-server.js +29 -6
- package/lib/mate-knowledge-service.js +23 -2
- package/lib/mate-knowledge-sync.js +36 -21
- package/lib/mate-workspace-color.js +4 -0
- package/lib/mates.js +18 -0
- package/lib/project-connection.js +36 -2
- package/lib/project-debate-proposal.js +6 -0
- package/lib/project-debate-workspace.js +43 -0
- package/lib/project-debate.js +1 -0
- package/lib/project-knowledge.js +315 -136
- package/lib/project-mate-creation-proposal.js +1 -0
- package/lib/project-sessions.js +39 -2
- package/lib/project-user-knowledge.js +60 -0
- package/lib/project.js +59 -18
- package/lib/public/app.js +73 -34
- package/lib/public/clay-studio-wordmark-bold.svg +20 -0
- package/lib/public/css/clay-workspace.css +105 -0
- package/lib/public/css/debates-workbench.css +47 -0
- package/lib/public/css/home-debate-live.css +26 -0
- package/lib/public/css/home-hub.css +3 -1
- package/lib/public/css/home-mate-model-picker.css +15 -131
- package/lib/public/css/home-mate-settings.css +168 -16
- package/lib/public/css/icon-strip.css +249 -32
- package/lib/public/css/mate-conversation.css +176 -0
- package/lib/public/css/mate-knowledge-workbench.css +118 -0
- package/lib/public/css/mate-workspace-color.css +16 -0
- package/lib/public/css/mates.css +28 -28
- package/lib/public/css/messages.css +1 -114
- package/lib/public/css/mobile-nav.css +16 -16
- package/lib/public/css/overlays.css +8 -0
- package/lib/public/css/project-logs.css +7 -0
- package/lib/public/css/project-mate-creation.css +105 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/session-folders.css +80 -21
- package/lib/public/css/sidebar.css +183 -107
- package/lib/public/css/title-bar.css +151 -0
- package/lib/public/css/worker-delegation.css +47 -0
- package/lib/public/css/worker-proposal.css +72 -150
- package/lib/public/css/you.css +74 -0
- package/lib/public/index.html +33 -101
- package/lib/public/modules/account-island.js +230 -0
- package/lib/public/modules/app-cursors.js +19 -43
- package/lib/public/modules/app-debate-ui.js +4 -3
- package/lib/public/modules/app-header.js +106 -0
- package/lib/public/modules/app-message-router.js +12 -3
- package/lib/public/modules/app-messages.js +34 -9
- package/lib/public/modules/app-notifications.js +4 -7
- package/lib/public/modules/app-projects.js +5 -38
- package/lib/public/modules/app-rendering.js +12 -13
- package/lib/public/modules/avatar.js +25 -9
- package/lib/public/modules/chat-render-runtime.js +16 -4
- package/lib/public/modules/command-palette.js +2 -15
- package/lib/public/modules/cursor-sharing-preference.js +3 -3
- package/lib/public/modules/debate-controls.js +299 -0
- package/lib/public/modules/debate.js +2 -0
- package/lib/public/modules/debates-workbench.js +339 -0
- package/lib/public/modules/default-ai.js +81 -65
- package/lib/public/modules/home-debate-controls.js +18 -289
- package/lib/public/modules/home-mate-avatar-editor.js +228 -0
- package/lib/public/modules/home-mate-color-picker.js +66 -0
- package/lib/public/modules/home-mate-creation.js +1 -1
- package/lib/public/modules/home-mate-instructions.js +90 -0
- package/lib/public/modules/home-mate-model-picker.js +80 -271
- package/lib/public/modules/home-mate-profile-editor.js +72 -0
- package/lib/public/modules/home-mate-settings.js +118 -125
- package/lib/public/modules/home-shell.js +3 -0
- package/lib/public/modules/home-surface-boot.js +1 -9
- package/lib/public/modules/icon-strip-tooltip.js +95 -0
- package/lib/public/modules/knowledge-db-edit-state.js +35 -0
- package/lib/public/modules/knowledge-db-workbench.js +234 -0
- package/lib/public/modules/knowledge-history-state.js +55 -0
- package/lib/public/modules/knowledge-markdown-editor.js +57 -0
- package/lib/public/modules/knowledge-preview-links.js +70 -0
- package/lib/public/modules/knowledge-workbench-shell.js +21 -0
- package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
- package/lib/public/modules/mate-knowledge-workbench.js +500 -0
- package/lib/public/modules/mate-knowledge.js +11 -3
- package/lib/public/modules/mate-management.js +54 -10
- package/lib/public/modules/mate-sidebar.js +11 -1
- package/lib/public/modules/mate-workspace-color.js +33 -0
- package/lib/public/modules/mention.js +5 -13
- package/lib/public/modules/profile.js +51 -18
- package/lib/public/modules/project-chat-identity.js +79 -0
- package/lib/public/modules/project-debate-planning-workspace.js +250 -0
- package/lib/public/modules/project-debate-workspace.js +185 -0
- package/lib/public/modules/project-header-identity.js +127 -0
- package/lib/public/modules/project-logs-render.js +9 -4
- package/lib/public/modules/project-mate-creation-workspace.js +414 -0
- package/lib/public/modules/project-mate-navigation.js +238 -0
- package/lib/public/modules/project-session-navigation.js +29 -0
- package/lib/public/modules/project-switcher.js +6 -8
- package/lib/public/modules/projects-hub-view.js +5 -6
- package/lib/public/modules/projects-hub.js +2 -6
- package/lib/public/modules/qrcode.js +27 -8
- package/lib/public/modules/search-clay-chat.js +8 -11
- package/lib/public/modules/session-activity-tooltip.js +12 -0
- package/lib/public/modules/session-create-form.js +86 -14
- package/lib/public/modules/session-create.js +50 -7
- package/lib/public/modules/session-list-visibility.js +41 -0
- package/lib/public/modules/session-presence-ui.js +4 -1
- package/lib/public/modules/session-title-marquee.js +51 -0
- package/lib/public/modules/sidebar-mates.js +3 -50
- package/lib/public/modules/sidebar-mobile.js +63 -60
- package/lib/public/modules/sidebar-projects.js +84 -97
- package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
- package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
- package/lib/public/modules/sidebar-sessions.js +49 -50
- package/lib/public/modules/sidebar.js +11 -13
- package/lib/public/modules/thinking-lifecycle.js +7 -6
- package/lib/public/modules/tool-palette-order.js +4 -3
- package/lib/public/modules/tool-palette.js +13 -3
- package/lib/public/modules/tools.js +22 -22
- package/lib/public/modules/worker-proposal.js +42 -8
- package/lib/public/modules/worker-runtime-picker.js +115 -0
- package/lib/public/modules/you-activity.js +121 -0
- package/lib/public/modules/you.js +251 -0
- package/lib/public/style.css +16 -8
- package/lib/public/vendor/codemirror/LICENSE +21 -0
- package/lib/public/vendor/codemirror/codemirror.css +344 -0
- package/lib/public/vendor/codemirror/codemirror.js +9884 -0
- package/lib/public/vendor/codemirror/continuelist.js +101 -0
- package/lib/public/vendor/codemirror/markdown.js +886 -0
- package/lib/public/vendor/codemirror/show-hint.css +37 -0
- package/lib/public/vendor/codemirror/show-hint.js +523 -0
- package/lib/sdk-bridge.js +15 -3
- package/lib/server-admin.js +11 -0
- package/lib/server-home-chat-events.js +1 -1
- package/lib/server-home-chat.js +25 -3
- package/lib/server-home-debate-planning.js +4 -1
- package/lib/server-home-debates.js +2 -0
- package/lib/server-home-mate-creation.js +4 -1
- package/lib/server-mate-instructions.js +44 -0
- package/lib/server-mates.js +12 -3
- package/lib/server-palette.js +1 -1
- package/lib/server-settings.js +65 -1
- package/lib/server-user-knowledge.js +119 -0
- package/lib/server.js +21 -1
- package/lib/session-visibility.js +7 -2
- package/lib/sessions.js +32 -6
- package/lib/user-knowledge-service.js +167 -0
- package/lib/users-permissions.js +7 -0
- package/lib/users-project-mate-filter-preferences.js +39 -0
- package/lib/users.js +6 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +50 -0
- package/package.json +10 -1
|
@@ -1,295 +1,24 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Mate avatar choices hosted inside the current settings dialog.
|
|
2
|
+
|
|
3
|
+
import { avatarUrl, mateAvatarUrl, MATE_AVATAR_STYLES } from './avatar.js';
|
|
4
|
+
import { showAvatarPositioner } from './profile.js';
|
|
5
|
+
import { getWs } from './ws-ref.js';
|
|
6
|
+
import { store } from './store.js';
|
|
7
|
+
|
|
8
|
+
var activeToken = 0;
|
|
9
|
+
var activeMateId = null;
|
|
10
|
+
var pending = null;
|
|
11
|
+
var statusByMate = Object.create(null);
|
|
12
|
+
var draftByMate = Object.create(null);
|
|
13
|
+
|
|
14
|
+
function mateName(mate) {
|
|
15
|
+
var profile = mate && mate.profile ? mate.profile : {};
|
|
16
|
+
return profile.displayName || (mate && (mate.displayName || mate.name)) || 'Mate';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function mateProfile(mate) {
|
|
20
|
+
return Object.assign({}, mate && mate.profile ? mate.profile : {});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sendProfile(mate, profile, rerender, message) {
|
|
24
|
+
draftByMate[mate.id] = profile;
|
|
25
|
+
var ws = getWs();
|
|
26
|
+
if (!ws || ws.readyState !== 1) {
|
|
27
|
+
statusByMate[mate.id] = { kind: 'error', text: 'Clay is offline. Your choice was not saved.' };
|
|
28
|
+
rerender();
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
pending = { mateId: mate.id, style: profile.avatarStyle || 'imprint', seed: profile.avatarSeed || '', custom: profile.avatarCustom || '' };
|
|
32
|
+
statusByMate[mate.id] = { kind: 'pending', text: message || 'Saving avatar…' };
|
|
33
|
+
ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: profile } }));
|
|
34
|
+
rerender();
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function appendStatus(body, mate) {
|
|
39
|
+
var state = statusByMate[mate.id];
|
|
40
|
+
var status = document.createElement('div');
|
|
41
|
+
status.className = 'home-mate-avatar-status' + (state ? ' is-' + state.kind : '');
|
|
42
|
+
status.setAttribute('aria-live', 'polite');
|
|
43
|
+
status.textContent = state ? state.text : '';
|
|
44
|
+
body.appendChild(status);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createChoice(label, src, selected) {
|
|
48
|
+
var button = document.createElement('button');
|
|
49
|
+
button.type = 'button';
|
|
50
|
+
button.className = 'home-mate-avatar-choice';
|
|
51
|
+
button.setAttribute('aria-label', label + ' avatar');
|
|
52
|
+
button.setAttribute('aria-pressed', selected ? 'true' : 'false');
|
|
53
|
+
button.title = label;
|
|
54
|
+
var image = document.createElement('img');
|
|
55
|
+
image.src = src;
|
|
56
|
+
image.alt = '';
|
|
57
|
+
image.draggable = false;
|
|
58
|
+
var text = document.createElement('span');
|
|
59
|
+
text.textContent = label;
|
|
60
|
+
button.appendChild(image);
|
|
61
|
+
button.appendChild(text);
|
|
62
|
+
return button;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function selectStyle(mate, style, seed, rerender) {
|
|
66
|
+
var profile = mateProfile(mate);
|
|
67
|
+
profile.avatarStyle = style;
|
|
68
|
+
profile.avatarSeed = seed;
|
|
69
|
+
profile.avatarCustom = '';
|
|
70
|
+
sendProfile(mate, profile, rerender);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function uploadBlob(mate, blob, token, rerender) {
|
|
74
|
+
pending = null;
|
|
75
|
+
statusByMate[mate.id] = { kind: 'pending', text: 'Uploading image…' };
|
|
76
|
+
rerender();
|
|
77
|
+
blob.arrayBuffer().then(function (buffer) {
|
|
78
|
+
return fetch('/api/mate-avatar/' + encodeURIComponent(mate.id), {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
81
|
+
body: new Uint8Array(buffer),
|
|
82
|
+
});
|
|
83
|
+
}).then(function (response) {
|
|
84
|
+
return response.json().then(function (data) {
|
|
85
|
+
if (!response.ok || !data.ok || !data.avatar) throw new Error(data.error || 'Upload failed');
|
|
86
|
+
return data.avatar;
|
|
87
|
+
});
|
|
88
|
+
}).then(function (avatar) {
|
|
89
|
+
if (token !== activeToken) return;
|
|
90
|
+
var profile = mateProfile(mate);
|
|
91
|
+
profile.avatarCustom = avatar;
|
|
92
|
+
draftByMate[mate.id] = profile;
|
|
93
|
+
statusByMate[mate.id] = { kind: 'saved', text: 'Image saved.' };
|
|
94
|
+
var mates = store.get('cachedMatesList') || [];
|
|
95
|
+
store.set({ cachedMatesList: mates.map(function (item) {
|
|
96
|
+
return item && item.id === mate.id ? Object.assign({}, item, { profile: profile }) : item;
|
|
97
|
+
}) });
|
|
98
|
+
var ws = getWs();
|
|
99
|
+
if (ws && ws.readyState === 1) {
|
|
100
|
+
ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: profile } }));
|
|
101
|
+
}
|
|
102
|
+
rerender();
|
|
103
|
+
}).catch(function (error) {
|
|
104
|
+
if (token !== activeToken) return;
|
|
105
|
+
statusByMate[mate.id] = { kind: 'error', text: error && error.message ? error.message : 'Could not upload this image.' };
|
|
106
|
+
rerender();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function openUpload(mate, input, rerender) {
|
|
111
|
+
var file = input.files && input.files[0];
|
|
112
|
+
if (!file) return;
|
|
113
|
+
var token = ++activeToken;
|
|
114
|
+
var image = new Image();
|
|
115
|
+
var objectUrl = URL.createObjectURL(file);
|
|
116
|
+
image.onload = function () {
|
|
117
|
+
if (token !== activeToken) {
|
|
118
|
+
URL.revokeObjectURL(objectUrl);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
showAvatarPositioner(image, objectUrl, function (blob) {
|
|
122
|
+
URL.revokeObjectURL(objectUrl);
|
|
123
|
+
if (token === activeToken) uploadBlob(mate, blob, token, rerender);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
image.onerror = function () {
|
|
127
|
+
URL.revokeObjectURL(objectUrl);
|
|
128
|
+
if (token !== activeToken) return;
|
|
129
|
+
statusByMate[mate.id] = { kind: 'error', text: 'Could not read this image.' };
|
|
130
|
+
rerender();
|
|
131
|
+
};
|
|
132
|
+
image.src = objectUrl;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function renderHomeMateAvatarEditor(body, mate, rerender) {
|
|
136
|
+
if (!mate) return;
|
|
137
|
+
if (activeMateId !== mate.id) {
|
|
138
|
+
activeMateId = mate.id;
|
|
139
|
+
activeToken++;
|
|
140
|
+
}
|
|
141
|
+
var profile = draftByMate[mate.id] || mateProfile(mate);
|
|
142
|
+
var name = mateName(mate);
|
|
143
|
+
var style = profile.avatarStyle || 'imprint';
|
|
144
|
+
var seed = profile.avatarSeed || mate.id || name;
|
|
145
|
+
var locked = profile.avatarLocked === true;
|
|
146
|
+
var section = document.createElement('section');
|
|
147
|
+
section.className = 'home-mate-avatar-editor';
|
|
148
|
+
var heading = document.createElement('div');
|
|
149
|
+
heading.className = 'home-mate-avatar-heading';
|
|
150
|
+
var preview = document.createElement('img');
|
|
151
|
+
preview.className = 'home-mate-avatar-preview';
|
|
152
|
+
preview.src = mateAvatarUrl(Object.assign({}, mate, { profile: profile }), 72);
|
|
153
|
+
preview.alt = name + ' avatar';
|
|
154
|
+
var copy = document.createElement('div');
|
|
155
|
+
var title = document.createElement('h3');
|
|
156
|
+
title.textContent = 'Avatar';
|
|
157
|
+
var note = document.createElement('p');
|
|
158
|
+
note.textContent = locked ? 'This built-in avatar is managed by Clay.' : 'Choose a locally generated style or upload your own image.';
|
|
159
|
+
copy.appendChild(title);
|
|
160
|
+
copy.appendChild(note);
|
|
161
|
+
heading.appendChild(preview);
|
|
162
|
+
heading.appendChild(copy);
|
|
163
|
+
section.appendChild(heading);
|
|
164
|
+
if (!locked) {
|
|
165
|
+
var choices = document.createElement('div');
|
|
166
|
+
choices.className = 'home-mate-avatar-choices';
|
|
167
|
+
choices.setAttribute('role', 'group');
|
|
168
|
+
choices.setAttribute('aria-label', 'Avatar for ' + name);
|
|
169
|
+
for (var i = 0; i < MATE_AVATAR_STYLES.length; i++) {
|
|
170
|
+
(function (entry) {
|
|
171
|
+
var choice = createChoice(entry.name, avatarUrl(entry.id, seed, 48), !profile.avatarCustom && style === entry.id);
|
|
172
|
+
choice.addEventListener('click', function () { selectStyle(mate, entry.id, seed, rerender); });
|
|
173
|
+
choices.appendChild(choice);
|
|
174
|
+
})(MATE_AVATAR_STYLES[i]);
|
|
175
|
+
}
|
|
176
|
+
section.appendChild(choices);
|
|
177
|
+
var actions = document.createElement('div');
|
|
178
|
+
actions.className = 'home-mate-avatar-actions';
|
|
179
|
+
var shuffle = document.createElement('button');
|
|
180
|
+
shuffle.type = 'button';
|
|
181
|
+
shuffle.textContent = 'Shuffle selected style';
|
|
182
|
+
shuffle.disabled = style === 'imprint' || style === 'initial' || !!profile.avatarCustom;
|
|
183
|
+
shuffle.addEventListener('click', function () {
|
|
184
|
+
selectStyle(mate, style, Math.random().toString(36).substring(2, 10), rerender);
|
|
185
|
+
});
|
|
186
|
+
var upload = document.createElement('button');
|
|
187
|
+
upload.type = 'button';
|
|
188
|
+
upload.textContent = profile.avatarCustom ? 'Replace image' : 'Upload image';
|
|
189
|
+
var input = document.createElement('input');
|
|
190
|
+
input.type = 'file';
|
|
191
|
+
input.accept = 'image/png,image/jpeg,image/gif,image/webp';
|
|
192
|
+
input.className = 'home-mate-avatar-file-input';
|
|
193
|
+
input.hidden = true;
|
|
194
|
+
input.tabIndex = -1;
|
|
195
|
+
input.setAttribute('aria-hidden', 'true');
|
|
196
|
+
input.addEventListener('change', function () { openUpload(mate, input, rerender); });
|
|
197
|
+
upload.addEventListener('click', function () { input.click(); });
|
|
198
|
+
actions.appendChild(shuffle);
|
|
199
|
+
actions.appendChild(upload);
|
|
200
|
+
actions.appendChild(input);
|
|
201
|
+
section.appendChild(actions);
|
|
202
|
+
}
|
|
203
|
+
appendStatus(section, mate);
|
|
204
|
+
body.appendChild(section);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function confirmHomeMateAvatar(mate) {
|
|
208
|
+
if (!mate || !pending || pending.mateId !== mate.id) return false;
|
|
209
|
+
var profile = mateProfile(mate);
|
|
210
|
+
if ((profile.avatarStyle || 'imprint') !== pending.style || (profile.avatarSeed || '') !== pending.seed || (profile.avatarCustom || '') !== pending.custom) return false;
|
|
211
|
+
pending = null;
|
|
212
|
+
delete draftByMate[mate.id];
|
|
213
|
+
statusByMate[mate.id] = { kind: 'saved', text: 'Avatar saved.' };
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function failHomeMateAvatar(mateId, message) {
|
|
218
|
+
if (!pending || !mateId || pending.mateId !== mateId) return false;
|
|
219
|
+
statusByMate[pending.mateId] = { kind: 'error', text: message || 'Could not save this avatar.' };
|
|
220
|
+
pending = null;
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function clearHomeMateAvatarEditor() {
|
|
225
|
+
activeToken++;
|
|
226
|
+
activeMateId = null;
|
|
227
|
+
pending = null;
|
|
228
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { MATE_WORKSPACE_COLORS, workspaceColor } from './mate-workspace-color.js';
|
|
4
|
+
|
|
5
|
+
var timer = null;
|
|
6
|
+
var sequence = 0;
|
|
7
|
+
function state() { return store.get('mateColorSave'); }
|
|
8
|
+
function put(value) { store.set({ mateColorSave: value }); }
|
|
9
|
+
export function resetMateColor() { clearTimeout(timer); put(null); }
|
|
10
|
+
export function mateColorSaving() { return !!(state() && state().requestId); }
|
|
11
|
+
export function applyMateColorResult(msg) {
|
|
12
|
+
var s = state();
|
|
13
|
+
if (!s || !s.requestId || msg.requestId !== s.requestId || (msg.mate ? msg.mate.id : msg.mateId) !== s.mateId) return false;
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
put(Object.assign({}, s, { requestId: null, error: msg.mate ? '' : (msg.error || 'Could not save the color. Try again.'), saved: !!msg.mate }));
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
export function renderMateColorPicker(body, mate, render) {
|
|
19
|
+
if (!mate || mate.builtinKey === 'clay' || mate.primary) return;
|
|
20
|
+
var color = workspaceColor(mate);
|
|
21
|
+
var s = state();
|
|
22
|
+
if (s && s.mateId !== mate.id) s = null;
|
|
23
|
+
var section = document.createElement('section'); section.className = 'mate-color-picker';
|
|
24
|
+
var title = document.createElement('h4'); title.textContent = 'Workspace color'; section.appendChild(title);
|
|
25
|
+
var help = document.createElement('p'); help.className = 'mate-settings-help';
|
|
26
|
+
help.textContent = 'Give this Mate its own color. Soft in daylight, muted after dark.';
|
|
27
|
+
section.appendChild(help);
|
|
28
|
+
var preview = document.createElement('div'); preview.className = 'mate-color-previews';
|
|
29
|
+
['light', 'dark'].forEach(function (theme) {
|
|
30
|
+
var sample = document.createElement('div'); sample.className = 'mate-color-preview ' + theme;
|
|
31
|
+
sample.style.backgroundColor = color[theme];
|
|
32
|
+
sample.innerHTML = '<span class="mate-color-preview-label">' + (theme === 'light' ? 'Light' : 'Dark') + '</span><div class="mate-color-preview-canvas"><strong>Room to think</strong><span>A little color. A clear mind.</span></div>';
|
|
33
|
+
preview.appendChild(sample);
|
|
34
|
+
});
|
|
35
|
+
section.appendChild(preview);
|
|
36
|
+
var swatches = document.createElement('div'); swatches.className = 'mate-color-swatches';
|
|
37
|
+
swatches.setAttribute('role', 'group'); swatches.setAttribute('aria-label', 'Workspace color');
|
|
38
|
+
MATE_WORKSPACE_COLORS.forEach(function (choice) {
|
|
39
|
+
var button = document.createElement('button'); button.type = 'button'; button.id = 'mate-color-' + choice.id;
|
|
40
|
+
button.className = 'mate-color-swatch'; button.style.setProperty('--swatch', choice.pigment);
|
|
41
|
+
button.title = choice.label; button.setAttribute('aria-label', choice.label);
|
|
42
|
+
button.setAttribute('aria-pressed', String(color.id === choice.id));
|
|
43
|
+
button.setAttribute('aria-disabled', String(!!(s && s.requestId)));
|
|
44
|
+
if (color.id === choice.id) button.textContent = '✓';
|
|
45
|
+
button.addEventListener('click', function () {
|
|
46
|
+
if (mateColorSaving()) return;
|
|
47
|
+
var ws = getWs();
|
|
48
|
+
if (!ws || ws.readyState !== 1) { put({mateId:mate.id,error:'Clay is offline. Reconnect and try again.'}); render(); return; }
|
|
49
|
+
var requestId = 'mate-color-' + Date.now() + '-' + (++sequence);
|
|
50
|
+
put({mateId:mate.id,requestId:requestId,color:choice.id,error:''}); render();
|
|
51
|
+
try { ws.send(JSON.stringify({type:'mate_update',mateId:mate.id,requestId:requestId,updates:{workspaceColor:choice.id}})); }
|
|
52
|
+
catch (error) { put({mateId:mate.id,error:'Could not send the color. Try again.'}); render(); return; }
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
timer = setTimeout(function () {
|
|
55
|
+
if (state() && state().requestId === requestId) {
|
|
56
|
+
put({mateId:mate.id,error:'No response received. Reconnect to check the saved color before retrying.'}); render();
|
|
57
|
+
}
|
|
58
|
+
},15000);
|
|
59
|
+
});
|
|
60
|
+
swatches.appendChild(button);
|
|
61
|
+
});
|
|
62
|
+
section.appendChild(swatches);
|
|
63
|
+
var status = document.createElement('p'); status.className = 'mate-settings-status'; status.setAttribute('role', s && s.error ? 'alert' : 'status');
|
|
64
|
+
status.textContent = (s && s.error) || (s && s.requestId ? 'Saving color…' : color.label + (s && s.saved ? ' · Saved' : ''));
|
|
65
|
+
section.appendChild(status); body.appendChild(section);
|
|
66
|
+
}
|
|
@@ -81,7 +81,7 @@ export function applyHomeMateProposal(messages, msg) {
|
|
|
81
81
|
export function resolveHomeMateProposal(messages, msg) {
|
|
82
82
|
for (var i = 0; i < messages.length; i++) {
|
|
83
83
|
if (messages[i].role !== "mate_proposal" || !messages[i].proposal || messages[i].proposal.proposalId !== msg.proposalId) continue;
|
|
84
|
-
messages[i] = Object.assign({}, messages[i], { status: msg.action === "create" ? "created" : (msg.action === "cancel" ? "cancelled" : "pending"), mateId: msg.
|
|
84
|
+
messages[i] = Object.assign({}, messages[i], { status: msg.action === "create" ? "created" : (msg.action === "cancel" ? "cancelled" : "pending"), mateId: msg.createdMateId || null, mateName: msg.mateName || "", error: msg.error || "" });
|
|
85
85
|
}
|
|
86
86
|
return messages;
|
|
87
87
|
}
|