clay-server 2.7.2 → 2.8.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/bin/cli.js +31 -17
- package/lib/config.js +7 -4
- package/lib/project.js +343 -15
- package/lib/public/app.js +1039 -134
- package/lib/public/apple-touch-icon-dark.png +0 -0
- package/lib/public/apple-touch-icon.png +0 -0
- package/lib/public/clay-logo.png +0 -0
- package/lib/public/css/base.css +18 -1
- package/lib/public/css/filebrowser.css +1 -0
- package/lib/public/css/home-hub.css +455 -0
- package/lib/public/css/icon-strip.css +6 -5
- package/lib/public/css/loop.css +141 -23
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/mobile-nav.css +38 -12
- package/lib/public/css/overlays.css +205 -169
- package/lib/public/css/playbook.css +264 -0
- package/lib/public/css/profile.css +268 -0
- package/lib/public/css/scheduler-modal.css +1429 -0
- package/lib/public/css/scheduler.css +1305 -0
- package/lib/public/css/sidebar.css +305 -11
- package/lib/public/css/sticky-notes.css +23 -19
- package/lib/public/css/stt.css +155 -0
- package/lib/public/css/title-bar.css +14 -6
- package/lib/public/favicon-banded-32.png +0 -0
- package/lib/public/favicon-banded.png +0 -0
- package/lib/public/icon-192-dark.png +0 -0
- package/lib/public/icon-192.png +0 -0
- package/lib/public/icon-512-dark.png +0 -0
- package/lib/public/icon-512.png +0 -0
- package/lib/public/icon-banded-76.png +0 -0
- package/lib/public/icon-banded-96.png +0 -0
- package/lib/public/index.html +336 -44
- package/lib/public/modules/ascii-logo.js +442 -0
- package/lib/public/modules/markdown.js +18 -0
- package/lib/public/modules/notifications.js +50 -63
- package/lib/public/modules/playbook.js +578 -0
- package/lib/public/modules/profile.js +357 -0
- package/lib/public/modules/project-settings.js +1 -9
- package/lib/public/modules/scheduler.js +2826 -0
- package/lib/public/modules/server-settings.js +1 -1
- package/lib/public/modules/sidebar.js +376 -32
- package/lib/public/modules/stt.js +272 -0
- package/lib/public/modules/terminal.js +32 -0
- package/lib/public/modules/theme.js +3 -10
- package/lib/public/style.css +6 -0
- package/lib/public/sw.js +82 -3
- package/lib/public/wordmark-banded-20.png +0 -0
- package/lib/public/wordmark-banded-32.png +0 -0
- package/lib/public/wordmark-banded-64.png +0 -0
- package/lib/public/wordmark-banded-80.png +0 -0
- package/lib/scheduler.js +402 -0
- package/lib/sdk-bridge.js +3 -2
- package/lib/server.js +124 -3
- package/lib/sessions.js +35 -2
- package/package.json +1 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// User profile module — Discord-style popover for name, language, avatar
|
|
2
|
+
// Stores profile server-side in ~/.clay/profile.json
|
|
3
|
+
// Avatar generated via DiceBear API (deterministic SVG from seed)
|
|
4
|
+
|
|
5
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
|
+
import { setSTTLang, getSTTLang } from './stt.js';
|
|
7
|
+
|
|
8
|
+
var ctx;
|
|
9
|
+
var profile = { name: '', lang: 'en-US', avatarStyle: 'thumbs', avatarSeed: '', avatarColor: '#7c3aed' };
|
|
10
|
+
var popoverEl = null;
|
|
11
|
+
var saveTimer = null;
|
|
12
|
+
var previewSeed = '';
|
|
13
|
+
|
|
14
|
+
var LANGUAGES = [
|
|
15
|
+
{ code: 'en-US', name: 'English' },
|
|
16
|
+
{ code: 'ko-KR', name: 'Korean' },
|
|
17
|
+
{ code: 'ja-JP', name: 'Japanese' },
|
|
18
|
+
{ code: 'zh-CN', name: 'Chinese' },
|
|
19
|
+
{ code: 'es-ES', name: 'Spanish' },
|
|
20
|
+
{ code: 'fr-FR', name: 'French' },
|
|
21
|
+
{ code: 'de-DE', name: 'German' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
var AVATAR_STYLES = [
|
|
25
|
+
{ id: 'thumbs', name: 'Thumbs' },
|
|
26
|
+
{ id: 'bottts', name: 'Bots' },
|
|
27
|
+
{ id: 'pixel-art', name: 'Pixel' },
|
|
28
|
+
{ id: 'adventurer', name: 'Adventurer' },
|
|
29
|
+
{ id: 'micah', name: 'Micah' },
|
|
30
|
+
{ id: 'lorelei', name: 'Lorelei' },
|
|
31
|
+
{ id: 'fun-emoji', name: 'Emoji' },
|
|
32
|
+
{ id: 'icons', name: 'Icons' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
var COLORS = [
|
|
36
|
+
'#7c3aed', '#4f46e5', '#2563eb', '#0891b2',
|
|
37
|
+
'#059669', '#65a30d', '#d97706', '#dc2626',
|
|
38
|
+
'#db2777', '#6366f1', '#0d9488', '#ea580c',
|
|
39
|
+
'#475569', '#1e293b', '#be123c', '#a21caf',
|
|
40
|
+
'#0369a1', '#15803d',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// --- DiceBear URL builder ---
|
|
44
|
+
function avatarUrl(style, seed, size) {
|
|
45
|
+
var s = encodeURIComponent(seed || 'anonymous');
|
|
46
|
+
return 'https://api.dicebear.com/9.x/' + style + '/svg?seed=' + s + '&size=' + (size || 64);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getAvatarSeed() {
|
|
50
|
+
return profile.avatarSeed || 'anonymous';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- API ---
|
|
54
|
+
function fetchProfile() {
|
|
55
|
+
return fetch('/api/profile').then(function(r) { return r.json(); });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function saveProfile() {
|
|
59
|
+
return fetch('/api/profile', {
|
|
60
|
+
method: 'PUT',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify(profile),
|
|
63
|
+
}).then(function(r) { return r.json(); });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function debouncedSave() {
|
|
67
|
+
if (saveTimer) clearTimeout(saveTimer);
|
|
68
|
+
saveTimer = setTimeout(function() {
|
|
69
|
+
saveProfile();
|
|
70
|
+
saveTimer = null;
|
|
71
|
+
}, 400);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- DOM updates ---
|
|
75
|
+
function applyToIsland() {
|
|
76
|
+
var avatarWrap = document.querySelector('.user-island-avatar');
|
|
77
|
+
var nameEl = document.querySelector('.user-island-name');
|
|
78
|
+
if (!avatarWrap || !nameEl) return;
|
|
79
|
+
|
|
80
|
+
var displayName = profile.name || 'Awesome Clay User';
|
|
81
|
+
|
|
82
|
+
// Replace letter fallback with DiceBear img
|
|
83
|
+
var existingImg = avatarWrap.querySelector('img');
|
|
84
|
+
var existingLetter = avatarWrap.querySelector('.user-island-avatar-letter');
|
|
85
|
+
var url = avatarUrl(profile.avatarStyle || 'thumbs', getAvatarSeed(), 32);
|
|
86
|
+
|
|
87
|
+
if (existingImg) {
|
|
88
|
+
existingImg.src = url;
|
|
89
|
+
} else {
|
|
90
|
+
if (existingLetter) existingLetter.style.display = 'none';
|
|
91
|
+
var img = document.createElement('img');
|
|
92
|
+
img.src = url;
|
|
93
|
+
img.alt = displayName;
|
|
94
|
+
avatarWrap.appendChild(img);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
nameEl.textContent = displayName;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- Popover ---
|
|
101
|
+
function showPopover() {
|
|
102
|
+
if (popoverEl) {
|
|
103
|
+
hidePopover();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
popoverEl = document.createElement('div');
|
|
108
|
+
popoverEl.className = 'profile-popover';
|
|
109
|
+
|
|
110
|
+
var displayName = profile.name || '';
|
|
111
|
+
var currentLang = profile.lang || 'en-US';
|
|
112
|
+
var currentColor = profile.avatarColor || '#7c3aed';
|
|
113
|
+
var currentStyle = profile.avatarStyle || 'thumbs';
|
|
114
|
+
var seed = getAvatarSeed();
|
|
115
|
+
previewSeed = seed;
|
|
116
|
+
|
|
117
|
+
var html = '';
|
|
118
|
+
|
|
119
|
+
// Banner + close
|
|
120
|
+
html += '<div class="profile-banner" style="background:' + currentColor + '">';
|
|
121
|
+
html += '<button class="profile-close-btn">×</button>';
|
|
122
|
+
html += '</div>';
|
|
123
|
+
|
|
124
|
+
// Avatar row (overlapping banner)
|
|
125
|
+
html += '<div class="profile-avatar-row">';
|
|
126
|
+
html += '<div class="profile-popover-avatar">';
|
|
127
|
+
html += '<img class="profile-popover-avatar-img" src="' + avatarUrl(currentStyle, seed, 80) + '" alt="avatar">';
|
|
128
|
+
html += '</div>';
|
|
129
|
+
html += '<div class="profile-name-display">' + escapeAttr(displayName || 'Awesome Clay User') + '</div>';
|
|
130
|
+
html += '</div>';
|
|
131
|
+
|
|
132
|
+
// Body
|
|
133
|
+
html += '<div class="profile-popover-body">';
|
|
134
|
+
|
|
135
|
+
// Name
|
|
136
|
+
html += '<div class="profile-field">';
|
|
137
|
+
html += '<label class="profile-field-label">Display Name</label>';
|
|
138
|
+
html += '<input type="text" class="profile-field-input" id="profile-name-input" value="' + escapeAttr(displayName) + '" placeholder="Enter your name..." maxlength="50" spellcheck="false" autocomplete="off">';
|
|
139
|
+
html += '</div>';
|
|
140
|
+
|
|
141
|
+
// Language dropdown
|
|
142
|
+
html += '<div class="profile-field">';
|
|
143
|
+
html += '<label class="profile-field-label">Language <span class="profile-field-hint">for voice input</span></label>';
|
|
144
|
+
html += '<select class="profile-field-select" id="profile-lang-select">';
|
|
145
|
+
for (var i = 0; i < LANGUAGES.length; i++) {
|
|
146
|
+
var l = LANGUAGES[i];
|
|
147
|
+
var sel = (currentLang === l.code) ? ' selected' : '';
|
|
148
|
+
html += '<option value="' + l.code + '"' + sel + '>' + l.name + '</option>';
|
|
149
|
+
}
|
|
150
|
+
html += '</select>';
|
|
151
|
+
html += '</div>';
|
|
152
|
+
|
|
153
|
+
// Avatar picker
|
|
154
|
+
html += '<div class="profile-field">';
|
|
155
|
+
html += '<label class="profile-field-label">Avatar <button class="profile-shuffle-btn" title="Shuffle">' + iconHtml('shuffle') + '</button></label>';
|
|
156
|
+
html += '<div class="profile-avatar-grid">';
|
|
157
|
+
for (var j = 0; j < AVATAR_STYLES.length; j++) {
|
|
158
|
+
var st = AVATAR_STYLES[j];
|
|
159
|
+
var activeS = (currentStyle === st.id) ? ' profile-avatar-option-active' : '';
|
|
160
|
+
html += '<button class="profile-avatar-option' + activeS + '" data-style="' + st.id + '" title="' + st.name + '">';
|
|
161
|
+
html += '<img src="' + avatarUrl(st.id, seed, 40) + '" alt="' + st.name + '">';
|
|
162
|
+
html += '</button>';
|
|
163
|
+
}
|
|
164
|
+
html += '</div>';
|
|
165
|
+
html += '</div>';
|
|
166
|
+
|
|
167
|
+
// Color
|
|
168
|
+
html += '<div class="profile-field">';
|
|
169
|
+
html += '<label class="profile-field-label">Color</label>';
|
|
170
|
+
html += '<div class="profile-color-grid">';
|
|
171
|
+
for (var k = 0; k < COLORS.length; k++) {
|
|
172
|
+
var c = COLORS[k];
|
|
173
|
+
var activeC = (currentColor === c) ? ' profile-color-active' : '';
|
|
174
|
+
html += '<button class="profile-color-swatch' + activeC + '" data-color="' + c + '" style="background:' + c + '"></button>';
|
|
175
|
+
}
|
|
176
|
+
html += '</div>';
|
|
177
|
+
html += '</div>';
|
|
178
|
+
|
|
179
|
+
html += '</div>'; // close body
|
|
180
|
+
|
|
181
|
+
popoverEl.innerHTML = html;
|
|
182
|
+
|
|
183
|
+
// --- Events ---
|
|
184
|
+
popoverEl.querySelector('.profile-close-btn').addEventListener('click', function(e) {
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
hidePopover();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
var nameInput = popoverEl.querySelector('#profile-name-input');
|
|
190
|
+
nameInput.addEventListener('input', function() {
|
|
191
|
+
profile.name = nameInput.value.trim();
|
|
192
|
+
applyToIsland();
|
|
193
|
+
updatePopoverHeader();
|
|
194
|
+
debouncedSave();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
nameInput.addEventListener('keydown', function(e) {
|
|
198
|
+
if (e.key === 'Enter') {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
hidePopover();
|
|
201
|
+
}
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
});
|
|
204
|
+
nameInput.addEventListener('keyup', function(e) { e.stopPropagation(); });
|
|
205
|
+
nameInput.addEventListener('keypress', function(e) { e.stopPropagation(); });
|
|
206
|
+
|
|
207
|
+
// Language dropdown
|
|
208
|
+
popoverEl.querySelector('#profile-lang-select').addEventListener('change', function(e) {
|
|
209
|
+
profile.lang = e.target.value;
|
|
210
|
+
setSTTLang(profile.lang);
|
|
211
|
+
debouncedSave();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Avatar style — clicking confirms both the style and the current previewSeed
|
|
215
|
+
popoverEl.querySelectorAll('.profile-avatar-option[data-style]').forEach(function(btn) {
|
|
216
|
+
btn.addEventListener('click', function() {
|
|
217
|
+
profile.avatarStyle = btn.dataset.style;
|
|
218
|
+
profile.avatarSeed = previewSeed;
|
|
219
|
+
applyToIsland();
|
|
220
|
+
updatePopoverHeader();
|
|
221
|
+
popoverEl.querySelectorAll('.profile-avatar-option').forEach(function(b) {
|
|
222
|
+
b.classList.remove('profile-avatar-option-active');
|
|
223
|
+
});
|
|
224
|
+
btn.classList.add('profile-avatar-option-active');
|
|
225
|
+
debouncedSave();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Shuffle button — only changes preview candidates, not the actual profile
|
|
230
|
+
popoverEl.querySelector('.profile-shuffle-btn').addEventListener('click', function(e) {
|
|
231
|
+
e.stopPropagation();
|
|
232
|
+
previewSeed = Math.random().toString(36).substring(2, 10);
|
|
233
|
+
refreshAvatarPreviews();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Color swatches
|
|
237
|
+
popoverEl.querySelectorAll('.profile-color-swatch').forEach(function(btn) {
|
|
238
|
+
btn.addEventListener('click', function() {
|
|
239
|
+
profile.avatarColor = btn.dataset.color;
|
|
240
|
+
applyToIsland();
|
|
241
|
+
var bannerEl = popoverEl.querySelector('.profile-banner');
|
|
242
|
+
if (bannerEl) bannerEl.style.background = profile.avatarColor;
|
|
243
|
+
popoverEl.querySelectorAll('.profile-color-swatch').forEach(function(b) {
|
|
244
|
+
b.classList.remove('profile-color-active');
|
|
245
|
+
});
|
|
246
|
+
btn.classList.add('profile-color-active');
|
|
247
|
+
debouncedSave();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Prevent clicks inside popover from closing it
|
|
252
|
+
popoverEl.addEventListener('click', function(e) {
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
var island = document.getElementById('user-island');
|
|
257
|
+
island.appendChild(popoverEl);
|
|
258
|
+
refreshIcons();
|
|
259
|
+
|
|
260
|
+
if (!profile.name) {
|
|
261
|
+
nameInput.focus();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
setTimeout(function() {
|
|
265
|
+
document.addEventListener('click', closeOnOutside);
|
|
266
|
+
document.addEventListener('keydown', closeOnEscape);
|
|
267
|
+
}, 0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function updatePopoverHeader() {
|
|
271
|
+
if (!popoverEl) return;
|
|
272
|
+
var img = popoverEl.querySelector('.profile-popover-avatar-img');
|
|
273
|
+
var nd = popoverEl.querySelector('.profile-name-display');
|
|
274
|
+
if (img) img.src = avatarUrl(profile.avatarStyle || 'thumbs', getAvatarSeed(), 80);
|
|
275
|
+
if (nd) nd.textContent = profile.name || 'Awesome Clay User';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
function refreshAvatarPreviews() {
|
|
280
|
+
if (!popoverEl) return;
|
|
281
|
+
popoverEl.querySelectorAll('.profile-avatar-option[data-style] img').forEach(function(img) {
|
|
282
|
+
var style = img.closest('.profile-avatar-option').dataset.style;
|
|
283
|
+
img.src = avatarUrl(style, previewSeed, 40);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function closeOnOutside(e) {
|
|
288
|
+
var island = document.getElementById('user-island');
|
|
289
|
+
if (popoverEl && !popoverEl.contains(e.target) && !island.contains(e.target)) {
|
|
290
|
+
hidePopover();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function closeOnEscape(e) {
|
|
295
|
+
if (e.key === 'Escape' && popoverEl) {
|
|
296
|
+
hidePopover();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function hidePopover() {
|
|
301
|
+
if (popoverEl) {
|
|
302
|
+
popoverEl.remove();
|
|
303
|
+
popoverEl = null;
|
|
304
|
+
}
|
|
305
|
+
document.removeEventListener('click', closeOnOutside);
|
|
306
|
+
document.removeEventListener('keydown', closeOnEscape);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function escapeAttr(str) {
|
|
310
|
+
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// --- Init ---
|
|
314
|
+
export function initProfile(_ctx) {
|
|
315
|
+
ctx = _ctx;
|
|
316
|
+
|
|
317
|
+
var island = document.getElementById('user-island');
|
|
318
|
+
if (!island) return;
|
|
319
|
+
|
|
320
|
+
var profileArea = island.querySelector('.user-island-profile');
|
|
321
|
+
if (profileArea) {
|
|
322
|
+
profileArea.addEventListener('click', function(e) {
|
|
323
|
+
e.stopPropagation();
|
|
324
|
+
showPopover();
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
fetchProfile().then(function(data) {
|
|
329
|
+
if (data.name !== undefined) profile.name = data.name;
|
|
330
|
+
if (data.lang) profile.lang = data.lang;
|
|
331
|
+
if (data.avatarColor) profile.avatarColor = data.avatarColor;
|
|
332
|
+
if (data.avatarStyle) profile.avatarStyle = data.avatarStyle;
|
|
333
|
+
if (data.avatarSeed) profile.avatarSeed = data.avatarSeed;
|
|
334
|
+
|
|
335
|
+
// Auto-generate seed if none exists
|
|
336
|
+
if (!profile.avatarSeed) {
|
|
337
|
+
profile.avatarSeed = Math.random().toString(36).substring(2, 10);
|
|
338
|
+
saveProfile();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
applyToIsland();
|
|
342
|
+
|
|
343
|
+
if (profile.lang) {
|
|
344
|
+
setSTTLang(profile.lang);
|
|
345
|
+
}
|
|
346
|
+
}).catch(function(err) {
|
|
347
|
+
console.warn('[Profile] Failed to load:', err);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function getProfile() {
|
|
352
|
+
return profile;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function getProfileLang() {
|
|
356
|
+
return profile.lang;
|
|
357
|
+
}
|
|
@@ -252,9 +252,6 @@ function updateIconPreview(icon) {
|
|
|
252
252
|
var removeBtn = document.getElementById("ps-icon-remove-btn");
|
|
253
253
|
if (preview) {
|
|
254
254
|
preview.textContent = icon || "";
|
|
255
|
-
if (typeof twemoji !== "undefined" && icon) {
|
|
256
|
-
twemoji.parse(preview, { folder: "svg", ext: ".svg" });
|
|
257
|
-
}
|
|
258
255
|
}
|
|
259
256
|
if (removeBtn) {
|
|
260
257
|
removeBtn.classList.toggle("hidden", !icon);
|
|
@@ -339,9 +336,7 @@ function showPsEmojiPicker() {
|
|
|
339
336
|
grid.appendChild(btn);
|
|
340
337
|
})(emojis[i]);
|
|
341
338
|
}
|
|
342
|
-
|
|
343
|
-
twemoji.parse(grid, { folder: "svg", ext: ".svg" });
|
|
344
|
-
}
|
|
339
|
+
|
|
345
340
|
scrollArea.scrollTop = 0;
|
|
346
341
|
}
|
|
347
342
|
|
|
@@ -353,9 +348,6 @@ function showPsEmojiPicker() {
|
|
|
353
348
|
}
|
|
354
349
|
|
|
355
350
|
buildGrid(EMOJI_CATEGORIES[0].emojis);
|
|
356
|
-
if (typeof twemoji !== "undefined") {
|
|
357
|
-
twemoji.parse(tabBar, { folder: "svg", ext: ".svg" });
|
|
358
|
-
}
|
|
359
351
|
|
|
360
352
|
anchor.innerHTML = "";
|
|
361
353
|
anchor.appendChild(picker);
|