clay-server 3.7.0 → 3.8.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/lib/builtin-mates.js +14 -14
- package/lib/mates.js +2 -6
- package/lib/os-users.js +17 -1
- package/lib/project-http.js +34 -1
- package/lib/project-image.js +87 -1
- package/lib/project-mate-interaction.js +7 -7
- package/lib/project-sessions.js +1 -1
- package/lib/project-user-mention.js +4 -4
- package/lib/project.js +6 -4
- package/lib/public/css/avatar-imprints.css +46 -0
- package/lib/public/css/generated-images.css +242 -0
- package/lib/public/css/icon-strip.css +3 -3
- package/lib/public/css/profile.css +123 -95
- package/lib/public/css/session-actions.css +40 -9
- package/lib/public/css/user-settings.css +1 -1
- package/lib/public/index.html +3 -3
- package/lib/public/modules/app-cursors.js +2 -2
- package/lib/public/modules/app-dm.js +1 -8
- package/lib/public/modules/app-messages.js +2 -2
- package/lib/public/modules/app-projects.js +7 -6
- package/lib/public/modules/app-rate-limit.js +0 -1
- package/lib/public/modules/app-rendering.js +0 -3
- package/lib/public/modules/avatar-imprint.js +134 -0
- package/lib/public/modules/avatar.js +25 -19
- package/lib/public/modules/debate.js +10 -5
- package/lib/public/modules/generated-images.js +211 -2
- package/lib/public/modules/mate-sidebar.js +2 -2
- package/lib/public/modules/mention.js +26 -16
- package/lib/public/modules/profile.js +73 -190
- package/lib/public/modules/session-actions.js +5 -3
- package/lib/public/modules/sidebar-mates.js +5 -4
- package/lib/public/modules/sidebar-sessions.js +1 -1
- package/lib/public/style.css +2 -1
- package/lib/server-auth.js +2 -2
- package/lib/server-dm.js +8 -8
- package/lib/server-mates.js +3 -3
- package/lib/server-settings.js +2 -2
- package/lib/server.js +5 -4
- package/lib/users.js +4 -4
- package/package.json +1 -1
|
@@ -112,7 +112,7 @@ function buildMentionCandidates() {
|
|
|
112
112
|
kind: "mate",
|
|
113
113
|
id: m.id,
|
|
114
114
|
name: (m.profile && m.profile.displayName) || m.name || "Mate",
|
|
115
|
-
color: (m.profile && m.profile.avatarColor) || "#
|
|
115
|
+
color: (m.profile && m.profile.avatarColor) || "#5857fc",
|
|
116
116
|
avatarSrc: mateAvatarUrl(m, 24),
|
|
117
117
|
vendor: m.vendor || "claude",
|
|
118
118
|
bio: m.bio || (m.profile && m.profile.bio) || "",
|
|
@@ -134,9 +134,8 @@ function buildMentionCandidates() {
|
|
|
134
134
|
kind: "user",
|
|
135
135
|
id: u.id,
|
|
136
136
|
name: u.displayName || u.username || "User",
|
|
137
|
-
color: u.avatarColor || "#
|
|
138
|
-
|
|
139
|
-
avatarSrc: "https://api.dicebear.com/7.x/" + (u.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(u.avatarSeed || u.username || u.id),
|
|
137
|
+
color: u.avatarColor || "#5857fc",
|
|
138
|
+
avatarSrc: userAvatarUrl(u, 24),
|
|
140
139
|
vendor: null,
|
|
141
140
|
bio: u.username ? "@" + u.username : "",
|
|
142
141
|
primary: false,
|
|
@@ -424,7 +423,7 @@ export function stickyReapplyMention() {
|
|
|
424
423
|
var kind = selectedMateKind;
|
|
425
424
|
var id = selectedMateId;
|
|
426
425
|
var name = selectedMateName;
|
|
427
|
-
var color = selectedMateColor || "#
|
|
426
|
+
var color = selectedMateColor || "#5857fc";
|
|
428
427
|
var avatarSrc = selectedMateAvatar || "";
|
|
429
428
|
// Reset index but keep selection
|
|
430
429
|
mentionAtIdx = -1;
|
|
@@ -516,7 +515,7 @@ export function handleMentionStart(msg) {
|
|
|
516
515
|
header.className = "dm-bubble-header";
|
|
517
516
|
var nameSpan = document.createElement("span");
|
|
518
517
|
nameSpan.className = "dm-bubble-name";
|
|
519
|
-
nameSpan.style.color = msg.avatarColor || "#
|
|
518
|
+
nameSpan.style.color = msg.avatarColor || "#5857fc";
|
|
520
519
|
nameSpan.textContent = msg.mateName || "Mate";
|
|
521
520
|
header.appendChild(nameSpan);
|
|
522
521
|
|
|
@@ -556,7 +555,7 @@ export function handleMentionStart(msg) {
|
|
|
556
555
|
// Project chat: mention block style
|
|
557
556
|
currentMentionEl = document.createElement("div");
|
|
558
557
|
currentMentionEl.className = "msg-mention";
|
|
559
|
-
currentMentionEl.style.setProperty("--mention-color", msg.avatarColor || "#
|
|
558
|
+
currentMentionEl.style.setProperty("--mention-color", msg.avatarColor || "#5857fc");
|
|
560
559
|
|
|
561
560
|
var header = document.createElement("div");
|
|
562
561
|
header.className = "mention-header";
|
|
@@ -754,15 +753,13 @@ function isDmLayout() {
|
|
|
754
753
|
|
|
755
754
|
function getMyAvatarSrc() {
|
|
756
755
|
if (document.body.dataset.myAvatarUrl) return document.body.dataset.myAvatarUrl;
|
|
757
|
-
var myUser =
|
|
758
|
-
try { myUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch (e) {}
|
|
756
|
+
var myUser = (store.get('cachedAllUsers') || []).find(function (u) { return u.id === store.get('myUserId'); });
|
|
759
757
|
return userAvatarUrl(myUser || {}, 36);
|
|
760
758
|
}
|
|
761
759
|
|
|
762
760
|
function getMyDisplayName() {
|
|
763
761
|
if (document.body.dataset.myDisplayName) return document.body.dataset.myDisplayName;
|
|
764
|
-
var myUser =
|
|
765
|
-
try { myUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch (e) {}
|
|
762
|
+
var myUser = (store.get('cachedAllUsers') || []).find(function (u) { return u.id === store.get('myUserId'); });
|
|
766
763
|
return (myUser && (myUser.displayName || myUser.username)) || "Me";
|
|
767
764
|
}
|
|
768
765
|
|
|
@@ -772,14 +769,27 @@ function timeStr() {
|
|
|
772
769
|
}
|
|
773
770
|
|
|
774
771
|
function buildMentionAvatarUrl(meta) {
|
|
775
|
-
return
|
|
772
|
+
return mateAvatarUrl({
|
|
773
|
+
id: meta.mateId,
|
|
774
|
+
profile: {
|
|
775
|
+
avatarStyle: meta.avatarStyle || "imprint",
|
|
776
|
+
avatarSeed: meta.avatarSeed || meta.mateId,
|
|
777
|
+
avatarColor: meta.avatarColor || "#5857fc",
|
|
778
|
+
avatarCustom: meta.avatarCustom || "",
|
|
779
|
+
},
|
|
780
|
+
}, 36);
|
|
776
781
|
}
|
|
777
782
|
|
|
778
783
|
function buildUserAvatarUrlFromEntry(entry, who) {
|
|
779
784
|
// who = "from" | "target"
|
|
780
|
-
var style = entry[who + "AvatarStyle"] || "thumbs";
|
|
781
785
|
var seed = entry[who + "AvatarSeed"] || entry[who === "from" ? "fromName" : "targetName"] || entry[who === "from" ? "from" : "targetUserId"];
|
|
782
|
-
return
|
|
786
|
+
return userAvatarUrl({
|
|
787
|
+
id: entry[who === "from" ? "from" : "targetUserId"],
|
|
788
|
+
avatarStyle: entry[who + "AvatarStyle"] || "imprint",
|
|
789
|
+
avatarSeed: seed || "user",
|
|
790
|
+
avatarColor: entry[who + "AvatarColor"] || "#5857fc",
|
|
791
|
+
avatarCustom: entry[who + "AvatarCustom"] || "",
|
|
792
|
+
}, 36);
|
|
783
793
|
}
|
|
784
794
|
|
|
785
795
|
// Render a user-to-user @mention bubble (live receive AND history replay).
|
|
@@ -1021,7 +1031,7 @@ export function renderMentionResponse(entry) {
|
|
|
1021
1031
|
header.className = "dm-bubble-header";
|
|
1022
1032
|
var nameSpan = document.createElement("span");
|
|
1023
1033
|
nameSpan.className = "dm-bubble-name";
|
|
1024
|
-
nameSpan.style.color = entry.avatarColor || "#
|
|
1034
|
+
nameSpan.style.color = entry.avatarColor || "#5857fc";
|
|
1025
1035
|
nameSpan.textContent = entry.mateName || "Mate";
|
|
1026
1036
|
header.appendChild(nameSpan);
|
|
1027
1037
|
|
|
@@ -1050,7 +1060,7 @@ export function renderMentionResponse(entry) {
|
|
|
1050
1060
|
// Project chat: use mention block style
|
|
1051
1061
|
var el = document.createElement("div");
|
|
1052
1062
|
el.className = "msg-mention";
|
|
1053
|
-
el.style.setProperty("--mention-color", entry.avatarColor || "#
|
|
1063
|
+
el.style.setProperty("--mention-color", entry.avatarColor || "#5857fc");
|
|
1054
1064
|
|
|
1055
1065
|
var mheader = document.createElement("div");
|
|
1056
1066
|
mheader.className = "mention-header";
|
|
@@ -1,31 +1,18 @@
|
|
|
1
|
-
// User profile module —
|
|
1
|
+
// User profile module — profile popovers for people and Mates
|
|
2
2
|
// Stores profile server-side in ~/.clay/profile.json
|
|
3
|
-
//
|
|
3
|
+
// Generated identities use deterministic Clay Imprints.
|
|
4
4
|
|
|
5
5
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
6
|
import { setSTTLang } from './stt.js';
|
|
7
|
-
import { avatarUrl, mateAvatarUrl,
|
|
7
|
+
import { avatarUrl, mateAvatarUrl, mateMarkUrl } from './avatar.js';
|
|
8
8
|
import { store } from './store.js';
|
|
9
9
|
import { applyTerminalFont } from './terminal-prefs.js';
|
|
10
10
|
|
|
11
11
|
var ctx;
|
|
12
|
-
var profile = { name: '', lang: 'en-US', avatarStyle: '
|
|
12
|
+
var profile = { name: '', lang: 'en-US', avatarStyle: 'imprint', avatarSeed: '', avatarColor: '#5857fc', avatarCustom: '' };
|
|
13
13
|
var profileUsername = '';
|
|
14
14
|
var popoverEl = null;
|
|
15
15
|
var saveTimer = null;
|
|
16
|
-
var previewSeed = '';
|
|
17
|
-
|
|
18
|
-
// AVATAR_STYLES imported from avatar.js
|
|
19
|
-
|
|
20
|
-
var COLORS = [
|
|
21
|
-
'#7c3aed', '#4f46e5', '#2563eb', '#0891b2',
|
|
22
|
-
'#059669', '#65a30d', '#d97706', '#dc2626',
|
|
23
|
-
'#db2777', '#6366f1', '#0d9488', '#ea580c',
|
|
24
|
-
'#475569', '#1e293b', '#be123c', '#a21caf',
|
|
25
|
-
'#0369a1', '#15803d',
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
// avatarUrl imported from avatar.js
|
|
29
16
|
|
|
30
17
|
function getAvatarSeed() {
|
|
31
18
|
return profile.avatarSeed || 'anonymous';
|
|
@@ -68,7 +55,7 @@ export function applyToIsland() {
|
|
|
68
55
|
var existingLetter = avatarWrap.querySelector('.user-island-avatar-letter');
|
|
69
56
|
var url = profile.avatarCustom
|
|
70
57
|
? profile.avatarCustom
|
|
71
|
-
: avatarUrl(profile.avatarStyle || '
|
|
58
|
+
: avatarUrl(profile.avatarStyle || 'imprint', getAvatarSeed(), 32);
|
|
72
59
|
|
|
73
60
|
if (existingImg) {
|
|
74
61
|
existingImg.src = url;
|
|
@@ -81,6 +68,12 @@ export function applyToIsland() {
|
|
|
81
68
|
}
|
|
82
69
|
|
|
83
70
|
nameEl.textContent = displayName;
|
|
71
|
+
document.body.dataset.myAvatarUrl = url;
|
|
72
|
+
document.body.dataset.myDisplayName = displayName;
|
|
73
|
+
var messageAvatars = document.querySelectorAll('.dm-bubble-avatar-me, .icon-strip-me-avatar');
|
|
74
|
+
for (var avatarIndex = 0; avatarIndex < messageAvatars.length; avatarIndex++) {
|
|
75
|
+
messageAvatars[avatarIndex].src = url;
|
|
76
|
+
}
|
|
84
77
|
|
|
85
78
|
// Show CTA if user hasn't personalized their name
|
|
86
79
|
var ctaEl = document.querySelector('.user-island-cta');
|
|
@@ -104,7 +97,7 @@ export function showAvatarPositioner(img, objectUrl, onDone) {
|
|
|
104
97
|
var offsetX = 0;
|
|
105
98
|
var offsetY = 0;
|
|
106
99
|
|
|
107
|
-
// Fit image so shorter side fills the
|
|
100
|
+
// Fit image so the shorter side fills the imprint frame.
|
|
108
101
|
var baseScale = Math.max(viewSize / img.width, viewSize / img.height);
|
|
109
102
|
|
|
110
103
|
function clampOffsets() {
|
|
@@ -287,22 +280,20 @@ function showPopover() {
|
|
|
287
280
|
popoverEl.className = 'profile-popover';
|
|
288
281
|
|
|
289
282
|
var displayName = profile.name || '';
|
|
290
|
-
var
|
|
291
|
-
var currentStyle = profile.avatarStyle || 'thumbs';
|
|
283
|
+
var currentStyle = profile.avatarStyle || 'imprint';
|
|
292
284
|
var seed = getAvatarSeed();
|
|
293
|
-
previewSeed = seed;
|
|
294
285
|
|
|
295
286
|
var html = '';
|
|
296
287
|
|
|
297
288
|
// Banner + close
|
|
298
|
-
html += '<div class="profile-banner"
|
|
289
|
+
html += '<div class="profile-banner">';
|
|
299
290
|
html += '<button class="profile-close-btn">×</button>';
|
|
300
291
|
html += '</div>';
|
|
301
292
|
|
|
302
293
|
// Avatar row (overlapping banner)
|
|
303
294
|
html += '<div class="profile-avatar-row">';
|
|
304
295
|
html += '<div class="profile-popover-avatar">';
|
|
305
|
-
html += '<img class="profile-popover-avatar-img" src="' + (profile.avatarCustom || avatarUrl(currentStyle, seed, 80)) + '" alt="
|
|
296
|
+
html += '<img class="profile-popover-avatar-img" src="' + (profile.avatarCustom || avatarUrl(currentStyle, seed, 80)) + '" alt="' + escapeAttr(displayName || 'Clay user') + '">';
|
|
306
297
|
html += '</div>';
|
|
307
298
|
html += '<div class="profile-name-display">' + escapeAttr(displayName || 'Awesome Clay User') + '</div>';
|
|
308
299
|
html += '</div>';
|
|
@@ -316,40 +307,18 @@ function showPopover() {
|
|
|
316
307
|
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">';
|
|
317
308
|
html += '</div>';
|
|
318
309
|
|
|
319
|
-
//
|
|
310
|
+
// Clay Imprint or custom photo
|
|
320
311
|
html += '<div class="profile-field">';
|
|
321
|
-
html += '<label class="profile-field-label">
|
|
322
|
-
html += '<div class="profile-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
html += '<button class="profile-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
} else {
|
|
329
|
-
html += '<span class="profile-avatar-upload-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></span>';
|
|
330
|
-
}
|
|
331
|
-
html += '</button>';
|
|
332
|
-
html += '<input type="file" id="profile-avatar-file" accept="image/*" style="display:none">';
|
|
333
|
-
for (var j = 0; j < AVATAR_STYLES.length; j++) {
|
|
334
|
-
var st = AVATAR_STYLES[j];
|
|
335
|
-
var activeS = (!profile.avatarCustom && currentStyle === st.id) ? ' profile-avatar-option-active' : '';
|
|
336
|
-
html += '<button class="profile-avatar-option' + activeS + '" data-style="' + st.id + '" title="' + st.name + '">';
|
|
337
|
-
html += '<img src="' + avatarUrl(st.id, seed, 40) + '" alt="' + st.name + '">';
|
|
338
|
-
html += '</button>';
|
|
339
|
-
}
|
|
312
|
+
html += '<label class="profile-field-label">Identity</label>';
|
|
313
|
+
html += '<div class="profile-imprint-panel">';
|
|
314
|
+
html += '<div class="profile-imprint-preview"><img src="' + avatarUrl(currentStyle, seed, 72) + '" alt="Generated Clay Imprint"><span><strong>Clay Imprint</strong><small>Unique to this identity</small></span></div>';
|
|
315
|
+
html += '<div class="profile-imprint-actions">';
|
|
316
|
+
html += '<button class="profile-use-imprint' + (!profile.avatarCustom ? ' is-active' : '') + '" type="button">Use imprint</button>';
|
|
317
|
+
html += '<button class="profile-regenerate-imprint" type="button" title="Generate another imprint">' + iconHtml('shuffle') + ' New imprint</button>';
|
|
318
|
+
html += '<button class="profile-avatar-upload' + (profile.avatarCustom ? ' is-active' : '') + '" type="button">Upload photo</button>';
|
|
340
319
|
html += '</div>';
|
|
341
320
|
html += '</div>';
|
|
342
|
-
|
|
343
|
-
// Color
|
|
344
|
-
html += '<div class="profile-field">';
|
|
345
|
-
html += '<label class="profile-field-label">Color</label>';
|
|
346
|
-
html += '<div class="profile-color-grid">';
|
|
347
|
-
for (var k = 0; k < COLORS.length; k++) {
|
|
348
|
-
var c = COLORS[k];
|
|
349
|
-
var activeC = (currentColor === c) ? ' profile-color-active' : '';
|
|
350
|
-
html += '<button class="profile-color-swatch' + activeC + '" data-color="' + c + '" style="background:' + c + '"></button>';
|
|
351
|
-
}
|
|
352
|
-
html += '</div>';
|
|
321
|
+
html += '<input type="file" id="profile-avatar-file" accept="image/*" style="display:none">';
|
|
353
322
|
html += '</div>';
|
|
354
323
|
|
|
355
324
|
html += '</div>'; // close body
|
|
@@ -403,15 +372,11 @@ function showPopover() {
|
|
|
403
372
|
profile.avatarCustom = data.avatar;
|
|
404
373
|
applyToIsland();
|
|
405
374
|
updatePopoverHeader();
|
|
406
|
-
if (popoverEl) {
|
|
407
|
-
popoverEl.querySelectorAll('.profile-avatar-option').forEach(function(b) {
|
|
408
|
-
b.classList.remove('profile-avatar-option-active');
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
375
|
if (uploadBtn) {
|
|
412
|
-
uploadBtn.classList.add('
|
|
413
|
-
uploadBtn.innerHTML = '<img src="' + data.avatar + '" alt="Custom" class="profile-avatar-custom-preview">';
|
|
376
|
+
uploadBtn.classList.add('is-active');
|
|
414
377
|
}
|
|
378
|
+
var imprintButton = popoverEl && popoverEl.querySelector('.profile-use-imprint');
|
|
379
|
+
if (imprintButton) imprintButton.classList.remove('is-active');
|
|
415
380
|
debouncedSave();
|
|
416
381
|
}
|
|
417
382
|
});
|
|
@@ -431,45 +396,25 @@ function showPopover() {
|
|
|
431
396
|
});
|
|
432
397
|
}
|
|
433
398
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
updatePopoverHeader();
|
|
442
|
-
popoverEl.querySelectorAll('.profile-avatar-option').forEach(function(b) {
|
|
443
|
-
b.classList.remove('profile-avatar-option-active');
|
|
444
|
-
});
|
|
445
|
-
btn.classList.add('profile-avatar-option-active');
|
|
446
|
-
// Reset upload button to + icon
|
|
447
|
-
var upBtn = popoverEl.querySelector('.profile-avatar-upload');
|
|
448
|
-
if (upBtn) upBtn.innerHTML = '<span class="profile-avatar-upload-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></span>';
|
|
449
|
-
debouncedSave();
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// Shuffle button — only changes preview candidates, not the actual profile
|
|
454
|
-
popoverEl.querySelector('.profile-shuffle-btn').addEventListener('click', function(e) {
|
|
455
|
-
e.stopPropagation();
|
|
456
|
-
previewSeed = Math.random().toString(36).substring(2, 10);
|
|
457
|
-
refreshAvatarPreviews();
|
|
399
|
+
popoverEl.querySelector('.profile-use-imprint').addEventListener('click', function() {
|
|
400
|
+
profile.avatarCustom = '';
|
|
401
|
+
applyToIsland();
|
|
402
|
+
updatePopoverHeader();
|
|
403
|
+
popoverEl.querySelector('.profile-use-imprint').classList.add('is-active');
|
|
404
|
+
popoverEl.querySelector('.profile-avatar-upload').classList.remove('is-active');
|
|
405
|
+
debouncedSave();
|
|
458
406
|
});
|
|
459
407
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
btn.classList.add('profile-color-active');
|
|
471
|
-
debouncedSave();
|
|
472
|
-
});
|
|
408
|
+
popoverEl.querySelector('.profile-regenerate-imprint').addEventListener('click', function() {
|
|
409
|
+
profile.avatarCustom = '';
|
|
410
|
+
profile.avatarStyle = 'imprint';
|
|
411
|
+
profile.avatarSeed = Math.random().toString(36).substring(2, 10);
|
|
412
|
+
applyToIsland();
|
|
413
|
+
updatePopoverHeader();
|
|
414
|
+
refreshImprintPreview(popoverEl, profile.avatarStyle, profile.avatarSeed);
|
|
415
|
+
popoverEl.querySelector('.profile-use-imprint').classList.add('is-active');
|
|
416
|
+
popoverEl.querySelector('.profile-avatar-upload').classList.remove('is-active');
|
|
417
|
+
debouncedSave();
|
|
473
418
|
});
|
|
474
419
|
|
|
475
420
|
// Prevent clicks inside popover from closing it
|
|
@@ -497,17 +442,15 @@ function updatePopoverHeader() {
|
|
|
497
442
|
var nd = popoverEl.querySelector('.profile-name-display');
|
|
498
443
|
if (img) img.src = profile.avatarCustom
|
|
499
444
|
? profile.avatarCustom
|
|
500
|
-
: avatarUrl(profile.avatarStyle || '
|
|
445
|
+
: avatarUrl(profile.avatarStyle || 'imprint', getAvatarSeed(), 80);
|
|
501
446
|
if (nd) nd.textContent = profile.name || 'Awesome Clay User';
|
|
502
447
|
}
|
|
503
448
|
|
|
504
449
|
|
|
505
|
-
function
|
|
506
|
-
if (!
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
img.src = avatarUrl(style, previewSeed, 40);
|
|
510
|
-
});
|
|
450
|
+
function refreshImprintPreview(root, style, seed) {
|
|
451
|
+
if (!root) return;
|
|
452
|
+
var img = root.querySelector('.profile-imprint-preview img');
|
|
453
|
+
if (img) img.src = avatarUrl(style || 'imprint', seed || 'anonymous', 72);
|
|
511
454
|
}
|
|
512
455
|
|
|
513
456
|
function closeOnOutside(e) {
|
|
@@ -610,7 +553,6 @@ export function getProfileLang() {
|
|
|
610
553
|
// --- Mate profile popover (reuses same UI minus language) ---
|
|
611
554
|
var matePopoverEl = null;
|
|
612
555
|
var mateSaveTimer = null;
|
|
613
|
-
var matePreviewSeed = '';
|
|
614
556
|
|
|
615
557
|
export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
616
558
|
if (matePopoverEl) {
|
|
@@ -620,11 +562,10 @@ export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
|
620
562
|
|
|
621
563
|
var mp = mateData.profile || {};
|
|
622
564
|
var mateName = mp.displayName || mateData.name || '';
|
|
623
|
-
var mateColor = mp.avatarColor || '#
|
|
624
|
-
var mateStyle = mp.avatarStyle || '
|
|
565
|
+
var mateColor = mp.avatarColor || '#5857fc';
|
|
566
|
+
var mateStyle = mp.avatarStyle || 'imprint';
|
|
625
567
|
var mateSeed = mp.avatarSeed || mateData.id || 'mate';
|
|
626
568
|
var mateCustom = mp.avatarCustom || '';
|
|
627
|
-
matePreviewSeed = mateSeed;
|
|
628
569
|
|
|
629
570
|
matePopoverEl = document.createElement('div');
|
|
630
571
|
matePopoverEl.className = 'profile-popover mate-profile-popover';
|
|
@@ -632,15 +573,15 @@ export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
|
632
573
|
var html = '';
|
|
633
574
|
|
|
634
575
|
// Banner + close
|
|
635
|
-
html += '<div class="profile-banner"
|
|
576
|
+
html += '<div class="profile-banner">';
|
|
636
577
|
html += '<button class="profile-close-btn">×</button>';
|
|
637
578
|
html += '</div>';
|
|
638
579
|
|
|
639
580
|
// Avatar row
|
|
640
|
-
var mateAvatarSrc = mateCustom ? mateCustom :
|
|
581
|
+
var mateAvatarSrc = mateCustom ? mateCustom : mateMarkUrl(mateName || 'Mate', 80);
|
|
641
582
|
html += '<div class="profile-avatar-row">';
|
|
642
583
|
html += '<div class="profile-popover-avatar">';
|
|
643
|
-
html += '<img class="profile-popover-avatar-img" src="' + mateAvatarSrc + '" alt="
|
|
584
|
+
html += '<img class="profile-popover-avatar-img" src="' + mateAvatarSrc + '" alt="' + escapeAttr(mateName || 'Clay Mate') + '">';
|
|
644
585
|
html += '</div>';
|
|
645
586
|
html += '<div class="profile-name-display">' + escapeAttr(mateName || 'New Mate') + '</div>';
|
|
646
587
|
html += '</div>';
|
|
@@ -658,40 +599,17 @@ export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
|
658
599
|
var mateAvatarLocked = mp.avatarLocked || false;
|
|
659
600
|
if (!mateAvatarLocked) {
|
|
660
601
|
html += '<div class="profile-field">';
|
|
661
|
-
html += '<label class="profile-field-label">
|
|
662
|
-
html += '<div class="profile-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
html += '<button class="profile-
|
|
666
|
-
|
|
667
|
-
html += '<img src="' + mateCustom + '" alt="Custom" class="profile-avatar-custom-preview">';
|
|
668
|
-
} else {
|
|
669
|
-
html += '<span class="profile-avatar-upload-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></span>';
|
|
670
|
-
}
|
|
671
|
-
html += '</button>';
|
|
672
|
-
html += '<input type="file" id="mate-profile-avatar-file" accept="image/*" style="display:none">';
|
|
673
|
-
for (var j = 0; j < AVATAR_STYLES.length; j++) {
|
|
674
|
-
var st = AVATAR_STYLES[j];
|
|
675
|
-
var activeS = (!mateCustom && mateStyle === st.id) ? ' profile-avatar-option-active' : '';
|
|
676
|
-
html += '<button class="profile-avatar-option' + activeS + '" data-style="' + st.id + '" title="' + st.name + '">';
|
|
677
|
-
html += '<img src="' + avatarUrl(st.id, mateSeed, 40) + '" alt="' + st.name + '">';
|
|
678
|
-
html += '</button>';
|
|
679
|
-
}
|
|
680
|
-
html += '</div>';
|
|
602
|
+
html += '<label class="profile-field-label">Identity</label>';
|
|
603
|
+
html += '<div class="profile-imprint-panel">';
|
|
604
|
+
html += '<div class="profile-imprint-preview"><img src="' + mateMarkUrl(mateName || 'Mate', 72) + '" alt="Clay Mate initial"><span><strong>Mate initial</strong><small>Based on display name</small></span></div>';
|
|
605
|
+
html += '<div class="profile-imprint-actions profile-mate-mark-actions">';
|
|
606
|
+
html += '<button class="profile-use-imprint' + (!mateCustom ? ' is-active' : '') + '" type="button">Use initial</button>';
|
|
607
|
+
html += '<button class="profile-avatar-upload' + (mateCustom ? ' is-active' : '') + '" type="button">Upload image</button>';
|
|
681
608
|
html += '</div>';
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Color
|
|
685
|
-
html += '<div class="profile-field">';
|
|
686
|
-
html += '<label class="profile-field-label">Color</label>';
|
|
687
|
-
html += '<div class="profile-color-grid">';
|
|
688
|
-
for (var k = 0; k < COLORS.length; k++) {
|
|
689
|
-
var c = COLORS[k];
|
|
690
|
-
var activeC = (mateColor === c) ? ' profile-color-active' : '';
|
|
691
|
-
html += '<button class="profile-color-swatch' + activeC + '" data-color="' + c + '" style="background:' + c + '"></button>';
|
|
692
|
-
}
|
|
693
609
|
html += '</div>';
|
|
610
|
+
html += '<input type="file" id="mate-profile-avatar-file" accept="image/*" style="display:none">';
|
|
694
611
|
html += '</div>';
|
|
612
|
+
}
|
|
695
613
|
|
|
696
614
|
html += '</div>'; // close body
|
|
697
615
|
|
|
@@ -746,7 +664,9 @@ export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
|
746
664
|
var nd = matePopoverEl.querySelector('.profile-name-display');
|
|
747
665
|
if (img) img.src = mateProfile.avatarCustom
|
|
748
666
|
? mateProfile.avatarCustom
|
|
749
|
-
:
|
|
667
|
+
: mateMarkUrl(mateProfile.displayName || 'Mate', 80);
|
|
668
|
+
var preview = matePopoverEl.querySelector('.profile-imprint-preview img');
|
|
669
|
+
if (preview) preview.src = mateMarkUrl(mateProfile.displayName || 'Mate', 72);
|
|
750
670
|
if (nd) nd.textContent = mateProfile.displayName || 'New Mate';
|
|
751
671
|
}
|
|
752
672
|
|
|
@@ -791,15 +711,11 @@ export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
|
791
711
|
if (data.ok) {
|
|
792
712
|
mateProfile.avatarCustom = data.avatar;
|
|
793
713
|
updateMatePopoverHeader();
|
|
794
|
-
if (matePopoverEl) {
|
|
795
|
-
matePopoverEl.querySelectorAll('.profile-avatar-option').forEach(function(b) {
|
|
796
|
-
b.classList.remove('profile-avatar-option-active');
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
714
|
if (mateUploadBtn) {
|
|
800
|
-
mateUploadBtn.classList.add('
|
|
801
|
-
mateUploadBtn.innerHTML = '<img src="' + data.avatar + '" alt="Custom" class="profile-avatar-custom-preview">';
|
|
715
|
+
mateUploadBtn.classList.add('is-active');
|
|
802
716
|
}
|
|
717
|
+
var imprintButton = matePopoverEl && matePopoverEl.querySelector('.profile-use-imprint');
|
|
718
|
+
if (imprintButton) imprintButton.classList.remove('is-active');
|
|
803
719
|
debouncedMateUpdate();
|
|
804
720
|
}
|
|
805
721
|
});
|
|
@@ -819,48 +735,15 @@ export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
|
819
735
|
});
|
|
820
736
|
}
|
|
821
737
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
btn.addEventListener('click', function() {
|
|
738
|
+
if (!mateAvatarLocked) {
|
|
739
|
+
matePopoverEl.querySelector('.profile-use-imprint').addEventListener('click', function() {
|
|
825
740
|
mateProfile.avatarCustom = '';
|
|
826
|
-
mateProfile.avatarStyle = btn.dataset.style;
|
|
827
|
-
mateProfile.avatarSeed = matePreviewSeed;
|
|
828
741
|
updateMatePopoverHeader();
|
|
829
|
-
matePopoverEl.
|
|
830
|
-
|
|
831
|
-
});
|
|
832
|
-
btn.classList.add('profile-avatar-option-active');
|
|
833
|
-
// Reset upload button to + icon
|
|
834
|
-
var upBtn = matePopoverEl.querySelector('.profile-avatar-upload');
|
|
835
|
-
if (upBtn) upBtn.innerHTML = '<span class="profile-avatar-upload-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></span>';
|
|
742
|
+
matePopoverEl.querySelector('.profile-use-imprint').classList.add('is-active');
|
|
743
|
+
matePopoverEl.querySelector('.profile-avatar-upload').classList.remove('is-active');
|
|
836
744
|
debouncedMateUpdate();
|
|
837
745
|
});
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// Shuffle
|
|
841
|
-
matePopoverEl.querySelector('.profile-shuffle-btn').addEventListener('click', function(e) {
|
|
842
|
-
e.stopPropagation();
|
|
843
|
-
matePreviewSeed = Math.random().toString(36).substring(2, 10);
|
|
844
|
-
if (!matePopoverEl) return;
|
|
845
|
-
matePopoverEl.querySelectorAll('.profile-avatar-option[data-style] img').forEach(function(img) {
|
|
846
|
-
var style = img.closest('.profile-avatar-option').dataset.style;
|
|
847
|
-
img.src = avatarUrl(style, matePreviewSeed, 40);
|
|
848
|
-
});
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
// Color swatches
|
|
852
|
-
matePopoverEl.querySelectorAll('.profile-color-swatch').forEach(function(btn) {
|
|
853
|
-
btn.addEventListener('click', function() {
|
|
854
|
-
mateProfile.avatarColor = btn.dataset.color;
|
|
855
|
-
var bannerEl = matePopoverEl.querySelector('.profile-banner');
|
|
856
|
-
if (bannerEl) bannerEl.style.background = mateProfile.avatarColor;
|
|
857
|
-
matePopoverEl.querySelectorAll('.profile-color-swatch').forEach(function(b) {
|
|
858
|
-
b.classList.remove('profile-color-active');
|
|
859
|
-
});
|
|
860
|
-
btn.classList.add('profile-color-active');
|
|
861
|
-
debouncedMateUpdate();
|
|
862
|
-
});
|
|
863
|
-
});
|
|
746
|
+
}
|
|
864
747
|
|
|
865
748
|
matePopoverEl.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
866
749
|
|
|
@@ -29,13 +29,14 @@ function positionMenu() {
|
|
|
29
29
|
var rect = button.getBoundingClientRect();
|
|
30
30
|
var width = menu.offsetWidth;
|
|
31
31
|
menu.style.top = Math.round(rect.bottom + 6) + "px";
|
|
32
|
-
menu.style.left = Math.max(8, Math.round(rect.right - width)) + "px";
|
|
32
|
+
menu.style.left = Math.max(8, Math.min(window.innerWidth - width - 8, Math.round(rect.right - width))) + "px";
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function actionRow(icon, label, description) {
|
|
36
36
|
var row = document.createElement("button");
|
|
37
37
|
row.type = "button";
|
|
38
38
|
row.className = "session-actions-row";
|
|
39
|
+
row.setAttribute("role", "menuitem");
|
|
39
40
|
row.innerHTML = iconHtml(icon, "session-actions-row-icon") +
|
|
40
41
|
'<span class="session-actions-row-copy"><strong></strong><span></span></span>' +
|
|
41
42
|
iconHtml("chevron-right", "session-actions-row-chevron");
|
|
@@ -217,7 +218,8 @@ function showHandoffDialog(options) {
|
|
|
217
218
|
function showMainMenu() {
|
|
218
219
|
closeMenu();
|
|
219
220
|
var el = menuShell("Session actions");
|
|
220
|
-
var addWorker = actionRow("bot", "Add AI
|
|
221
|
+
var addWorker = actionRow("bot", "Add AI worker", "Open a second agent beside this session.");
|
|
222
|
+
addWorker.classList.add("is-primary");
|
|
221
223
|
addWorker.addEventListener("click", function () {
|
|
222
224
|
var sessionId = store.get('activeSessionId');
|
|
223
225
|
if (!sessionId) return;
|
|
@@ -228,7 +230,7 @@ function showMainMenu() {
|
|
|
228
230
|
vendor: store.get('currentVendor') || "claude",
|
|
229
231
|
});
|
|
230
232
|
});
|
|
231
|
-
var handoff = actionRow("
|
|
233
|
+
var handoff = actionRow("send", "Send context to another agent", "Start a new session with this conversation's context.");
|
|
232
234
|
var unavailable = !store.get('sessionHasHistory') || store.get('sessionIsProcessing');
|
|
233
235
|
handoff.disabled = unavailable;
|
|
234
236
|
handoff.title = unavailable ? "Available after the current turn is complete" : "";
|
|
@@ -262,7 +262,7 @@ export function renderSidebarPresence(onlineUsers) {
|
|
|
262
262
|
// Presence avatar URL helper
|
|
263
263
|
function presenceAvatarUrl(userOrStyle) {
|
|
264
264
|
if (userOrStyle && typeof userOrStyle === "object") return userAvatarUrl(userOrStyle, 24);
|
|
265
|
-
return userAvatarUrl({ avatarStyle: userOrStyle || "
|
|
265
|
+
return userAvatarUrl({ avatarStyle: userOrStyle || "imprint" }, 24);
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
// renderUserStrip: call with no args to read from store (subscriber pattern),
|
|
@@ -424,7 +424,7 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
|
|
|
424
424
|
avatar.className = "icon-strip-user-avatar" + (mate.primary ? " icon-strip-primary-mate" : "");
|
|
425
425
|
avatar.src = mateAvatarUrl(mate, 34);
|
|
426
426
|
avatar.alt = mp.displayName || mate.name || "Mate";
|
|
427
|
-
var mateColor = (mp.avatarColor) || mate.avatarColor || "#
|
|
427
|
+
var mateColor = (mp.avatarColor) || mate.avatarColor || "#5857fc";
|
|
428
428
|
avatar.style.background = mateColor + "30";
|
|
429
429
|
el.appendChild(avatar);
|
|
430
430
|
|
|
@@ -679,7 +679,7 @@ function toggleDmUserPicker(anchorEl) {
|
|
|
679
679
|
bItem.style.opacity = "0.7";
|
|
680
680
|
var bAv = document.createElement("img");
|
|
681
681
|
bAv.className = "dm-user-picker-avatar";
|
|
682
|
-
bAv.src = mateAvatarUrl({ avatarCustom: b.avatarCustom, avatarStyle: b.avatarStyle || "
|
|
682
|
+
bAv.src = mateAvatarUrl({ avatarCustom: b.avatarCustom, avatarStyle: b.avatarStyle || "imprint", avatarSeed: b.displayName, id: b.key, avatarColor: b.avatarColor }, 28);
|
|
683
683
|
bAv.alt = b.displayName;
|
|
684
684
|
bItem.appendChild(bAv);
|
|
685
685
|
var bNameWrap = document.createElement("div");
|
|
@@ -866,7 +866,8 @@ function toggleDmUserPicker(anchorEl) {
|
|
|
866
866
|
} else {
|
|
867
867
|
avEl.src = mateAvatarUrl({
|
|
868
868
|
avatarCustom: src.avatarCustom,
|
|
869
|
-
avatarStyle: src.avatarStyle || "
|
|
869
|
+
avatarStyle: src.avatarStyle || "imprint",
|
|
870
|
+
avatarColor: src.avatarColor,
|
|
870
871
|
avatarSeed: src.displayName,
|
|
871
872
|
id: src.key,
|
|
872
873
|
}, 32);
|
|
@@ -1543,7 +1543,7 @@ export function updateSessionPresence(presence) {
|
|
|
1543
1543
|
|
|
1544
1544
|
function presenceAvatarUrl(userOrStyle, seed) {
|
|
1545
1545
|
if (userOrStyle && typeof userOrStyle === "object") return userAvatarUrl(userOrStyle, 24);
|
|
1546
|
-
return avatarUrl(userOrStyle || "
|
|
1546
|
+
return avatarUrl(userOrStyle || "imprint", seed, 24);
|
|
1547
1547
|
}
|
|
1548
1548
|
|
|
1549
1549
|
function renderPresenceAvatars(el, sessionId) {
|
package/lib/public/style.css
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
@import url("css/overlays.css?v=20260827-brand3");
|
|
9
9
|
@import url("css/menus.css");
|
|
10
10
|
@import url("css/messages.css?v=20260828-delegated-muted1");
|
|
11
|
-
@import url("css/generated-images.css?v=
|
|
11
|
+
@import url("css/generated-images.css?v=20260829-save-details1");
|
|
12
12
|
@import url("css/rewind.css");
|
|
13
13
|
@import url("css/input.css?v=20260828-composer-depth1");
|
|
14
14
|
@import url("css/filebrowser.css?v=20260829-terminal-canvas1");
|
|
@@ -38,3 +38,4 @@
|
|
|
38
38
|
@import url("css/notifications-center.css");
|
|
39
39
|
@import url("css/tui-attention.css?v=20260829-terminal-canvas1");
|
|
40
40
|
@import url("css/pwa-mobile.css?v=20260824f");
|
|
41
|
+
@import url("css/avatar-imprints.css?v=20260829-imprints1");
|
package/lib/server-auth.js
CHANGED
|
@@ -414,8 +414,8 @@ function attachAuth(ctx) {
|
|
|
414
414
|
adminProfile = {
|
|
415
415
|
name: data.displayName || data.username,
|
|
416
416
|
lang: existingProfile.lang || "en-US",
|
|
417
|
-
avatarColor: existingProfile.avatarColor || "#
|
|
418
|
-
avatarStyle: existingProfile.avatarStyle || "
|
|
417
|
+
avatarColor: existingProfile.avatarColor || "#5857fc",
|
|
418
|
+
avatarStyle: existingProfile.avatarStyle || "imprint",
|
|
419
419
|
avatarSeed: existingProfile.avatarSeed || crypto.randomBytes(4).toString("hex"),
|
|
420
420
|
};
|
|
421
421
|
} catch (e) {}
|