clay-server 3.8.0-beta.1 → 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/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 +4 -3
- package/lib/public/css/avatar-imprints.css +46 -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 +2 -2
- 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/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 +1 -0
- 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
|
@@ -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
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) {}
|
package/lib/server-dm.js
CHANGED
|
@@ -28,9 +28,9 @@ function attachDm(ctx) {
|
|
|
28
28
|
id: otherUser.id,
|
|
29
29
|
displayName: p.name || otherUser.displayName || otherUser.username,
|
|
30
30
|
username: otherUser.username,
|
|
31
|
-
avatarStyle: p.avatarStyle || "
|
|
31
|
+
avatarStyle: p.avatarStyle || "imprint",
|
|
32
32
|
avatarSeed: p.avatarSeed || otherUser.username,
|
|
33
|
-
avatarColor: p.avatarColor || "#
|
|
33
|
+
avatarColor: p.avatarColor || "#5857fc",
|
|
34
34
|
avatarCustom: p.avatarCustom || "",
|
|
35
35
|
};
|
|
36
36
|
}
|
|
@@ -69,9 +69,9 @@ function attachDm(ctx) {
|
|
|
69
69
|
id: mate.id,
|
|
70
70
|
displayName: mp.displayName || mate.name || "New Mate",
|
|
71
71
|
username: mate.id,
|
|
72
|
-
avatarStyle: mp.avatarStyle || "
|
|
72
|
+
avatarStyle: mp.avatarStyle || "imprint",
|
|
73
73
|
avatarSeed: mp.avatarSeed || mate.id,
|
|
74
|
-
avatarColor: mp.avatarColor || "#
|
|
74
|
+
avatarColor: mp.avatarColor || "#5857fc",
|
|
75
75
|
avatarCustom: mp.avatarCustom || "",
|
|
76
76
|
isMate: true,
|
|
77
77
|
primary: !!mate.primary,
|
|
@@ -94,9 +94,9 @@ function attachDm(ctx) {
|
|
|
94
94
|
id: targetUser.id,
|
|
95
95
|
displayName: tp.name || targetUser.displayName || targetUser.username,
|
|
96
96
|
username: targetUser.username,
|
|
97
|
-
avatarStyle: tp.avatarStyle || "
|
|
97
|
+
avatarStyle: tp.avatarStyle || "imprint",
|
|
98
98
|
avatarSeed: tp.avatarSeed || targetUser.username,
|
|
99
|
-
avatarColor: tp.avatarColor || "#
|
|
99
|
+
avatarColor: tp.avatarColor || "#5857fc",
|
|
100
100
|
avatarCustom: tp.avatarCustom || "",
|
|
101
101
|
} : null,
|
|
102
102
|
}));
|
|
@@ -191,9 +191,9 @@ function attachDm(ctx) {
|
|
|
191
191
|
displayName: p.name || u.displayName || u.username,
|
|
192
192
|
username: u.username,
|
|
193
193
|
role: u.role,
|
|
194
|
-
avatarStyle: p.avatarStyle || "
|
|
194
|
+
avatarStyle: p.avatarStyle || "imprint",
|
|
195
195
|
avatarSeed: p.avatarSeed || u.username,
|
|
196
|
-
avatarColor: p.avatarColor || "#
|
|
196
|
+
avatarColor: p.avatarColor || "#5857fc",
|
|
197
197
|
avatarCustom: p.avatarCustom || "",
|
|
198
198
|
};
|
|
199
199
|
});
|
package/lib/server-mates.js
CHANGED
|
@@ -152,7 +152,7 @@ function attachMates(ctx) {
|
|
|
152
152
|
displayName: bDef2.displayName,
|
|
153
153
|
bio: bDef2.bio,
|
|
154
154
|
avatarCustom: bDef2.avatarCustom || "",
|
|
155
|
-
avatarStyle: bDef2.avatarStyle || "
|
|
155
|
+
avatarStyle: bDef2.avatarStyle || "imprint",
|
|
156
156
|
avatarColor: bDef2.avatarColor || "",
|
|
157
157
|
});
|
|
158
158
|
}
|
|
@@ -186,7 +186,7 @@ function attachMates(ctx) {
|
|
|
186
186
|
displayName: bDef3.displayName,
|
|
187
187
|
bio: bDef3.bio,
|
|
188
188
|
avatarCustom: bDef3.avatarCustom || "",
|
|
189
|
-
avatarStyle: bDef3.avatarStyle || "
|
|
189
|
+
avatarStyle: bDef3.avatarStyle || "imprint",
|
|
190
190
|
avatarColor: bDef3.avatarColor || "",
|
|
191
191
|
});
|
|
192
192
|
}
|
|
@@ -227,7 +227,7 @@ function attachMates(ctx) {
|
|
|
227
227
|
for (var abkR = 0; abkR < missingKeysR.length; abkR++) {
|
|
228
228
|
var bDefR = builtinDefsR.getBuiltinByKey(missingKeysR[abkR]);
|
|
229
229
|
if (bDefR) {
|
|
230
|
-
availableBuiltinsR.push({ key: bDefR.key, displayName: bDefR.displayName, bio: bDefR.bio, avatarCustom: bDefR.avatarCustom || "", avatarStyle: bDefR.avatarStyle || "
|
|
230
|
+
availableBuiltinsR.push({ key: bDefR.key, displayName: bDefR.displayName, bio: bDefR.bio, avatarCustom: bDefR.avatarCustom || "", avatarStyle: bDefR.avatarStyle || "imprint", avatarColor: bDefR.avatarColor || "" });
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
ws.send(JSON.stringify({ type: "mate_created", mate: newMate, projectSlug: readdSlug, availableBuiltins: availableBuiltinsR, dmFavorites: updatedFavsR }));
|
package/lib/server-settings.js
CHANGED
|
@@ -21,7 +21,7 @@ function attachSettings(ctx) {
|
|
|
21
21
|
res.end('{"error":"unauthorized"}');
|
|
22
22
|
return true;
|
|
23
23
|
}
|
|
24
|
-
var profile = mu.profile || { name: "", lang: "en-US", avatarColor: "#
|
|
24
|
+
var profile = mu.profile || { name: "", lang: "en-US", avatarColor: "#5857fc", avatarStyle: "imprint", avatarSeed: "", avatarCustom: "" };
|
|
25
25
|
profile.username = mu.username;
|
|
26
26
|
profile.userId = mu.id;
|
|
27
27
|
profile.role = mu.role;
|
|
@@ -35,7 +35,7 @@ function attachSettings(ctx) {
|
|
|
35
35
|
res.end(JSON.stringify(profile));
|
|
36
36
|
return true;
|
|
37
37
|
}
|
|
38
|
-
var profile = { name: "", lang: "en-US", avatarColor: "#
|
|
38
|
+
var profile = { name: "", lang: "en-US", avatarColor: "#5857fc", avatarStyle: "imprint", avatarSeed: "", avatarCustom: "" };
|
|
39
39
|
try {
|
|
40
40
|
var raw = fs.readFileSync(profilePath, "utf8");
|
|
41
41
|
var saved = JSON.parse(raw);
|