clay-server 2.17.0-beta.7 → 2.17.0-beta.9
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/project.js +4 -0
- package/lib/public/app.js +23 -24
- package/lib/public/css/mates.css +13 -14
- package/lib/public/css/menus.css +19 -0
- package/lib/public/css/overlays.css +25 -0
- package/lib/public/css/profile.css +128 -0
- package/lib/public/index.html +3 -1
- package/lib/public/modules/avatar.js +36 -0
- package/lib/public/modules/command-palette.js +4 -7
- package/lib/public/modules/mate-sidebar.js +5 -8
- package/lib/public/modules/profile.js +350 -23
- package/lib/public/modules/qrcode.js +23 -3
- package/lib/public/modules/sidebar.js +10 -9
- package/lib/server.js +211 -3
- package/package.json +1 -1
package/lib/project.js
CHANGED
|
@@ -246,6 +246,7 @@ function createProjectContext(opts) {
|
|
|
246
246
|
username: u.username,
|
|
247
247
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
248
248
|
avatarSeed: p.avatarSeed || u.username,
|
|
249
|
+
avatarCustom: p.avatarCustom || "",
|
|
249
250
|
});
|
|
250
251
|
}
|
|
251
252
|
msg.users = userList;
|
|
@@ -2076,6 +2077,7 @@ function createProjectContext(opts) {
|
|
|
2076
2077
|
displayName: p.name || u.displayName || u.username,
|
|
2077
2078
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
2078
2079
|
avatarSeed: p.avatarSeed || u.username,
|
|
2080
|
+
avatarCustom: p.avatarCustom || "",
|
|
2079
2081
|
};
|
|
2080
2082
|
if (msg.type === "cursor_move") {
|
|
2081
2083
|
cursorMsg.turn = msg.turn;
|
|
@@ -3504,6 +3506,7 @@ function createProjectContext(opts) {
|
|
|
3504
3506
|
username: u.username,
|
|
3505
3507
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
3506
3508
|
avatarSeed: p.avatarSeed || u.username,
|
|
3509
|
+
avatarCustom: p.avatarCustom || "",
|
|
3507
3510
|
});
|
|
3508
3511
|
}
|
|
3509
3512
|
send({ type: "session_presence", presence: presence });
|
|
@@ -4124,6 +4127,7 @@ function createProjectContext(opts) {
|
|
|
4124
4127
|
username: u.username,
|
|
4125
4128
|
avatarStyle: p.avatarStyle || "thumbs",
|
|
4126
4129
|
avatarSeed: p.avatarSeed || u.username,
|
|
4130
|
+
avatarCustom: p.avatarCustom || "",
|
|
4127
4131
|
});
|
|
4128
4132
|
}
|
|
4129
4133
|
status.onlineUsers = onlineUsers;
|
package/lib/public/app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { avatarUrl, userAvatarUrl, mateAvatarUrl } from './modules/avatar.js';
|
|
1
2
|
import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
|
|
2
3
|
import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
|
|
3
4
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
|
|
@@ -7,7 +8,7 @@ import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKno
|
|
|
7
8
|
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
|
|
8
9
|
import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
|
|
9
10
|
import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage } from './modules/input.js';
|
|
10
|
-
import { initQrCode } from './modules/qrcode.js';
|
|
11
|
+
import { initQrCode, triggerShare } from './modules/qrcode.js';
|
|
11
12
|
import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
|
|
12
13
|
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
|
|
13
14
|
import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible } from './modules/sticky-notes.js';
|
|
@@ -651,20 +652,20 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
651
652
|
document.body.classList.add("mate-dm-active");
|
|
652
653
|
// Build mate avatar URL for DM bubble injection
|
|
653
654
|
var mp = targetUser.profile || {};
|
|
654
|
-
var
|
|
655
|
+
var mateAvUrlDm = mateAvatarUrl(targetUser, 36);
|
|
655
656
|
var myUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
656
657
|
if (!myUser) {
|
|
657
658
|
try { var cached = JSON.parse(localStorage.getItem("clay_my_user") || "null"); if (cached) myUser = cached; } catch(e) {}
|
|
658
659
|
}
|
|
659
|
-
var myAvatarUrl =
|
|
660
|
+
var myAvatarUrl = userAvatarUrl(myUser || { id: myUserId }, 36);
|
|
660
661
|
var myDisplayName = (myUser && myUser.displayName) || "";
|
|
661
|
-
document.body.dataset.mateAvatarUrl =
|
|
662
|
+
document.body.dataset.mateAvatarUrl = mateAvUrlDm;
|
|
662
663
|
document.body.dataset.mateName = mp.displayName || targetUser.displayName || targetUser.name || "";
|
|
663
664
|
document.body.dataset.myAvatarUrl = myAvatarUrl;
|
|
664
665
|
document.body.dataset.myDisplayName = myDisplayName;
|
|
665
666
|
// Cache my info for restore after hard refresh
|
|
666
667
|
if (myUser) {
|
|
667
|
-
try { localStorage.setItem("clay_my_user", JSON.stringify({ displayName: myUser.displayName, avatarStyle: myUser.avatarStyle, avatarSeed: myUser.avatarSeed, username: myUser.username })); } catch(e) {}
|
|
668
|
+
try { localStorage.setItem("clay_my_user", JSON.stringify({ displayName: myUser.displayName, avatarStyle: myUser.avatarStyle, avatarSeed: myUser.avatarSeed, avatarCustom: myUser.avatarCustom, username: myUser.username })); } catch(e) {}
|
|
668
669
|
}
|
|
669
670
|
var titleBarContent = document.querySelector(".title-bar-content");
|
|
670
671
|
if (titleBarContent) {
|
|
@@ -677,7 +678,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
677
678
|
var mateMobileAvatar = document.getElementById("mate-mobile-avatar");
|
|
678
679
|
var mateMobileName = document.getElementById("mate-mobile-name");
|
|
679
680
|
var mateMobileStatus = document.getElementById("mate-mobile-status");
|
|
680
|
-
if (mateMobileAvatar) mateMobileAvatar.src =
|
|
681
|
+
if (mateMobileAvatar) mateMobileAvatar.src = mateAvUrlDm;
|
|
681
682
|
if (mateMobileName) mateMobileName.textContent = (mp.displayName || targetUser.displayName || targetUser.name || "");
|
|
682
683
|
if (mateMobileStatus) mateMobileStatus.textContent = "online";
|
|
683
684
|
mateMobileTitle.classList.remove("hidden");
|
|
@@ -686,7 +687,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
686
687
|
id: targetUser.id,
|
|
687
688
|
displayName: mp.displayName || targetUser.displayName || targetUser.name || "",
|
|
688
689
|
description: mp.description || targetUser.description || "",
|
|
689
|
-
avatarUrl:
|
|
690
|
+
avatarUrl: mateAvUrlDm,
|
|
690
691
|
color: mateColor
|
|
691
692
|
});
|
|
692
693
|
}
|
|
@@ -723,7 +724,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
723
724
|
} else {
|
|
724
725
|
if (dmHeaderBar) dmHeaderBar.style.display = "";
|
|
725
726
|
if (dmAvatar) {
|
|
726
|
-
dmAvatar.src =
|
|
727
|
+
dmAvatar.src = userAvatarUrl(targetUser, 28);
|
|
727
728
|
}
|
|
728
729
|
if (dmName) dmName.textContent = targetUser.displayName;
|
|
729
730
|
if (dmHeaderBar && targetUser.avatarColor) {
|
|
@@ -1165,11 +1166,9 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
1165
1166
|
avatar.className = "dm-msg-avatar";
|
|
1166
1167
|
if (isMe) {
|
|
1167
1168
|
var myUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
1168
|
-
|
|
1169
|
-
var mySeed = myUser ? (myUser.avatarSeed || myUser.username) : myUserId;
|
|
1170
|
-
avatar.src = "https://api.dicebear.com/9.x/" + (myStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(mySeed) + "&size=36";
|
|
1169
|
+
avatar.src = userAvatarUrl(myUser || { id: myUserId }, 36);
|
|
1171
1170
|
} else if (dmTargetUser) {
|
|
1172
|
-
avatar.src =
|
|
1171
|
+
avatar.src = userAvatarUrl(dmTargetUser, 36);
|
|
1173
1172
|
}
|
|
1174
1173
|
div.appendChild(avatar);
|
|
1175
1174
|
|
|
@@ -1224,7 +1223,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
1224
1223
|
|
|
1225
1224
|
var avatar = document.createElement("img");
|
|
1226
1225
|
avatar.className = "dm-msg-avatar";
|
|
1227
|
-
avatar.src =
|
|
1226
|
+
avatar.src = userAvatarUrl(dmTargetUser, 36);
|
|
1228
1227
|
div.appendChild(avatar);
|
|
1229
1228
|
|
|
1230
1229
|
var dots = document.createElement("div");
|
|
@@ -1272,10 +1271,10 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
1272
1271
|
avatarWrap.className = "home-hub-mate-avatar-wrap";
|
|
1273
1272
|
|
|
1274
1273
|
var mp = mate.profile || {};
|
|
1275
|
-
var
|
|
1274
|
+
var mateAvUrl = mateAvatarUrl(mate, 48);
|
|
1276
1275
|
var avatar = document.createElement("img");
|
|
1277
1276
|
avatar.className = "home-hub-mate-avatar";
|
|
1278
|
-
avatar.src =
|
|
1277
|
+
avatar.src = mateAvUrl;
|
|
1279
1278
|
avatar.alt = mp.displayName || mate.displayName || mate.name || "";
|
|
1280
1279
|
avatarWrap.appendChild(avatar);
|
|
1281
1280
|
|
|
@@ -1407,7 +1406,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
1407
1406
|
var refreshedMyUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
1408
1407
|
if (refreshedMyUser) {
|
|
1409
1408
|
document.body.dataset.myDisplayName = refreshedMyUser.displayName || "";
|
|
1410
|
-
document.body.dataset.myAvatarUrl =
|
|
1409
|
+
document.body.dataset.myAvatarUrl = userAvatarUrl(refreshedMyUser, 36);
|
|
1411
1410
|
}
|
|
1412
1411
|
}
|
|
1413
1412
|
// Render my avatar (always present, hidden behind user-island)
|
|
@@ -1417,7 +1416,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
1417
1416
|
if (myUser) {
|
|
1418
1417
|
var meAvatar = document.createElement("img");
|
|
1419
1418
|
meAvatar.className = "icon-strip-me-avatar";
|
|
1420
|
-
meAvatar.src =
|
|
1419
|
+
meAvatar.src = userAvatarUrl(myUser, 34);
|
|
1421
1420
|
meEl.appendChild(meAvatar);
|
|
1422
1421
|
}
|
|
1423
1422
|
}
|
|
@@ -1433,7 +1432,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
1433
1432
|
var cu = serverUsers[cui];
|
|
1434
1433
|
var cuImg = document.createElement("img");
|
|
1435
1434
|
cuImg.className = "client-avatar";
|
|
1436
|
-
cuImg.src =
|
|
1435
|
+
cuImg.src = userAvatarUrl(cu, 24);
|
|
1437
1436
|
cuImg.alt = cu.displayName;
|
|
1438
1437
|
cuImg.dataset.tip = cu.displayName + " (@" + cu.username + ")";
|
|
1439
1438
|
if (cui > 0) cuImg.style.marginLeft = "-6px";
|
|
@@ -4618,7 +4617,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
4618
4617
|
updateMateSidebarProfile(msg.mate);
|
|
4619
4618
|
}
|
|
4620
4619
|
// Update DM header if currently chatting with this mate
|
|
4621
|
-
if (dmMode &&
|
|
4620
|
+
if (dmMode && dmTargetUser && dmTargetUser.id === msg.mate.id) {
|
|
4622
4621
|
var updatedName = (msg.mate.profile && msg.mate.profile.displayName) || msg.mate.name;
|
|
4623
4622
|
if (updatedName) {
|
|
4624
4623
|
var dmHeaderName = document.getElementById("dm-header-name");
|
|
@@ -5169,6 +5168,8 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
5169
5168
|
|
|
5170
5169
|
// --- QR code ---
|
|
5171
5170
|
initQrCode();
|
|
5171
|
+
var sharePill = document.getElementById("share-pill");
|
|
5172
|
+
if (sharePill) sharePill.addEventListener("click", triggerShare);
|
|
5172
5173
|
|
|
5173
5174
|
// --- File browser ---
|
|
5174
5175
|
initFileBrowser({
|
|
@@ -6855,7 +6856,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
6855
6856
|
return userColorMap[userId];
|
|
6856
6857
|
}
|
|
6857
6858
|
|
|
6858
|
-
function createCursorElement(userId, displayName, color, avatarStyle, avatarSeed) {
|
|
6859
|
+
function createCursorElement(userId, displayName, color, avatarStyle, avatarSeed, avatarCustom) {
|
|
6859
6860
|
var wrapper = document.createElement("div");
|
|
6860
6861
|
wrapper.className = "remote-cursor";
|
|
6861
6862
|
wrapper.dataset.userId = userId;
|
|
@@ -6891,9 +6892,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
6891
6892
|
// Avatar
|
|
6892
6893
|
var avatarImg = document.createElement("img");
|
|
6893
6894
|
avatarImg.className = "remote-cursor-avatar";
|
|
6894
|
-
|
|
6895
|
-
var seed = avatarSeed || userId;
|
|
6896
|
-
avatarImg.src = "https://api.dicebear.com/9.x/" + style + "/svg?seed=" + encodeURIComponent(seed) + "&size=16";
|
|
6895
|
+
avatarImg.src = avatarCustom ? avatarCustom : avatarUrl(avatarStyle || "thumbs", avatarSeed || userId, 16);
|
|
6897
6896
|
avatarImg.style.cssText = "width:14px;height:14px;border-radius:50%;background:#fff;flex-shrink:0;";
|
|
6898
6897
|
tag.appendChild(avatarImg);
|
|
6899
6898
|
|
|
@@ -7065,7 +7064,7 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
7065
7064
|
var entry = remoteCursors[userId];
|
|
7066
7065
|
if (!entry) {
|
|
7067
7066
|
var color = getCursorColor(userId);
|
|
7068
|
-
var el = createCursorElement(userId, msg.displayName, color, msg.avatarStyle, msg.avatarSeed);
|
|
7067
|
+
var el = createCursorElement(userId, msg.displayName, color, msg.avatarStyle, msg.avatarSeed, msg.avatarCustom);
|
|
7069
7068
|
messagesEl.appendChild(el);
|
|
7070
7069
|
var indicator = createOffscreenIndicator(userId, msg.displayName, color);
|
|
7071
7070
|
messagesEl.appendChild(indicator);
|
package/lib/public/css/mates.css
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/* === Avatar anti-aliasing === */
|
|
2
|
+
.mate-sidebar-avatar,
|
|
3
|
+
.mate-collapsed-avatar,
|
|
4
|
+
.dm-bubble-avatar,
|
|
5
|
+
.dm-bubble-avatar-me,
|
|
6
|
+
.home-hub-mate-avatar {
|
|
7
|
+
image-rendering: -webkit-optimize-contrast;
|
|
8
|
+
image-rendering: smooth;
|
|
9
|
+
-webkit-backface-visibility: hidden;
|
|
10
|
+
backface-visibility: hidden;
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
/* === Mate Chat Title Bar === */
|
|
2
14
|
|
|
3
15
|
.title-bar-content.mate-dm-active {
|
|
@@ -151,20 +163,7 @@
|
|
|
151
163
|
font-size: 48px;
|
|
152
164
|
line-height: 1;
|
|
153
165
|
}
|
|
154
|
-
|
|
155
|
-
display: inline-block;
|
|
156
|
-
font-size: 10px;
|
|
157
|
-
font-weight: 700;
|
|
158
|
-
letter-spacing: 0.05em;
|
|
159
|
-
text-transform: uppercase;
|
|
160
|
-
color: #f59e0b;
|
|
161
|
-
background: rgba(245, 158, 11, 0.12);
|
|
162
|
-
border: 1px solid rgba(245, 158, 11, 0.25);
|
|
163
|
-
border-radius: 10px;
|
|
164
|
-
padding: 2px 7px;
|
|
165
|
-
vertical-align: middle;
|
|
166
|
-
margin-right: 4px;
|
|
167
|
-
}
|
|
166
|
+
|
|
168
167
|
.mate-intro-title {
|
|
169
168
|
font-size: 22px;
|
|
170
169
|
font-weight: 700;
|
package/lib/public/css/menus.css
CHANGED
|
@@ -244,6 +244,25 @@
|
|
|
244
244
|
margin-top: 2px;
|
|
245
245
|
font-weight: 400;
|
|
246
246
|
}
|
|
247
|
+
.qr-share-btn {
|
|
248
|
+
display: inline-flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 6px;
|
|
251
|
+
margin-top: 12px;
|
|
252
|
+
padding: 8px 16px;
|
|
253
|
+
border: 1px solid #ddd;
|
|
254
|
+
border-radius: 8px;
|
|
255
|
+
background: #fff;
|
|
256
|
+
color: #333;
|
|
257
|
+
font-family: inherit;
|
|
258
|
+
font-size: 13px;
|
|
259
|
+
font-weight: 500;
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
transition: background 0.15s;
|
|
262
|
+
}
|
|
263
|
+
.qr-share-btn:hover { background: #f5f5f5; }
|
|
264
|
+
.qr-share-btn .lucide { width: 14px; height: 14px; }
|
|
265
|
+
.qr-share-btn.hidden { display: none; }
|
|
247
266
|
|
|
248
267
|
/* --- Notification menu --- */
|
|
249
268
|
#notif-menu-wrap {
|
|
@@ -75,6 +75,31 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
|
|
|
75
75
|
.top-bar-install-btn.hidden { display: none; }
|
|
76
76
|
.pwa-standalone .top-bar-install-btn { display: none !important; }
|
|
77
77
|
|
|
78
|
+
/* Share button — desktop only, same style as install pill */
|
|
79
|
+
.top-bar-share-btn {
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 4px;
|
|
83
|
+
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
84
|
+
color: var(--accent);
|
|
85
|
+
border: none;
|
|
86
|
+
border-radius: 10px;
|
|
87
|
+
padding: 2px 10px;
|
|
88
|
+
font-family: inherit;
|
|
89
|
+
font-size: 11px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
line-height: 1;
|
|
94
|
+
transition: background 0.15s;
|
|
95
|
+
}
|
|
96
|
+
.top-bar-share-btn .lucide { width: 12px; height: 12px; }
|
|
97
|
+
.top-bar-share-btn:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
|
|
98
|
+
@media (max-width: 768px) {
|
|
99
|
+
.top-bar-share-btn { display: none; }
|
|
100
|
+
}
|
|
101
|
+
.pwa-standalone .top-bar-share-btn { display: none !important; }
|
|
102
|
+
|
|
78
103
|
/* PWA install modal */
|
|
79
104
|
.pwa-modal {
|
|
80
105
|
position: fixed;
|
|
@@ -241,6 +241,134 @@
|
|
|
241
241
|
box-shadow: 0 0 0 1px var(--accent);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
/* Avatar upload button */
|
|
245
|
+
.profile-avatar-upload .profile-avatar-upload-icon {
|
|
246
|
+
display: flex;
|
|
247
|
+
align-items: center;
|
|
248
|
+
justify-content: center;
|
|
249
|
+
width: 100%;
|
|
250
|
+
height: 100%;
|
|
251
|
+
color: var(--text-muted);
|
|
252
|
+
}
|
|
253
|
+
.profile-avatar-upload .profile-avatar-upload-icon svg {
|
|
254
|
+
width: 24px;
|
|
255
|
+
height: 24px;
|
|
256
|
+
}
|
|
257
|
+
.profile-avatar-upload:hover .profile-avatar-upload-icon {
|
|
258
|
+
color: var(--text);
|
|
259
|
+
}
|
|
260
|
+
.profile-avatar-custom-preview {
|
|
261
|
+
width: 100%;
|
|
262
|
+
height: 100%;
|
|
263
|
+
object-fit: cover;
|
|
264
|
+
border-radius: 6px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Avatar positioner overlay */
|
|
268
|
+
.avatar-positioner-overlay {
|
|
269
|
+
position: fixed;
|
|
270
|
+
inset: 0;
|
|
271
|
+
z-index: 10000;
|
|
272
|
+
background: rgba(0,0,0,0.6);
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
justify-content: center;
|
|
276
|
+
}
|
|
277
|
+
.avatar-positioner-container {
|
|
278
|
+
background: var(--bg-secondary, #1e1e2e);
|
|
279
|
+
border-radius: 16px;
|
|
280
|
+
padding: 20px;
|
|
281
|
+
display: flex;
|
|
282
|
+
flex-direction: column;
|
|
283
|
+
align-items: center;
|
|
284
|
+
gap: 16px;
|
|
285
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
|
286
|
+
min-width: 260px;
|
|
287
|
+
position: relative;
|
|
288
|
+
}
|
|
289
|
+
.avatar-positioner-close {
|
|
290
|
+
position: absolute;
|
|
291
|
+
top: 8px;
|
|
292
|
+
right: 8px;
|
|
293
|
+
width: 28px;
|
|
294
|
+
height: 28px;
|
|
295
|
+
border: none;
|
|
296
|
+
background: transparent;
|
|
297
|
+
color: var(--text-muted);
|
|
298
|
+
font-size: 20px;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
border-radius: 50%;
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
justify-content: center;
|
|
304
|
+
line-height: 1;
|
|
305
|
+
}
|
|
306
|
+
.avatar-positioner-close:hover {
|
|
307
|
+
background: var(--bg-hover, rgba(255,255,255,0.1));
|
|
308
|
+
color: var(--text);
|
|
309
|
+
}
|
|
310
|
+
.avatar-positioner-title {
|
|
311
|
+
font-size: 14px;
|
|
312
|
+
font-weight: 600;
|
|
313
|
+
color: var(--text);
|
|
314
|
+
}
|
|
315
|
+
.avatar-positioner-viewport {
|
|
316
|
+
border-radius: 50%;
|
|
317
|
+
overflow: hidden;
|
|
318
|
+
position: relative;
|
|
319
|
+
cursor: grab;
|
|
320
|
+
border: 2px solid var(--border);
|
|
321
|
+
background: var(--bg);
|
|
322
|
+
touch-action: none;
|
|
323
|
+
}
|
|
324
|
+
.avatar-positioner-viewport:active {
|
|
325
|
+
cursor: grabbing;
|
|
326
|
+
}
|
|
327
|
+
.avatar-positioner-img {
|
|
328
|
+
position: absolute;
|
|
329
|
+
top: 0;
|
|
330
|
+
left: 0;
|
|
331
|
+
pointer-events: none;
|
|
332
|
+
will-change: transform;
|
|
333
|
+
}
|
|
334
|
+
.avatar-positioner-slider-wrap {
|
|
335
|
+
width: 100%;
|
|
336
|
+
padding: 0 8px;
|
|
337
|
+
}
|
|
338
|
+
.avatar-positioner-slider {
|
|
339
|
+
width: 100%;
|
|
340
|
+
accent-color: var(--accent);
|
|
341
|
+
}
|
|
342
|
+
.avatar-positioner-buttons {
|
|
343
|
+
display: flex;
|
|
344
|
+
gap: 8px;
|
|
345
|
+
width: 100%;
|
|
346
|
+
}
|
|
347
|
+
.avatar-positioner-btn {
|
|
348
|
+
flex: 1;
|
|
349
|
+
padding: 8px 0;
|
|
350
|
+
border-radius: 8px;
|
|
351
|
+
border: none;
|
|
352
|
+
cursor: pointer;
|
|
353
|
+
font-size: 13px;
|
|
354
|
+
font-weight: 600;
|
|
355
|
+
}
|
|
356
|
+
.avatar-positioner-btn-cancel {
|
|
357
|
+
background: var(--bg);
|
|
358
|
+
color: var(--text-muted);
|
|
359
|
+
border: 1px solid var(--border);
|
|
360
|
+
}
|
|
361
|
+
.avatar-positioner-btn-cancel:hover {
|
|
362
|
+
background: var(--bg-hover, var(--bg));
|
|
363
|
+
}
|
|
364
|
+
.avatar-positioner-btn-done {
|
|
365
|
+
background: var(--accent);
|
|
366
|
+
color: #fff;
|
|
367
|
+
}
|
|
368
|
+
.avatar-positioner-btn-done:hover {
|
|
369
|
+
filter: brightness(1.1);
|
|
370
|
+
}
|
|
371
|
+
|
|
244
372
|
/* Color swatch grid */
|
|
245
373
|
.profile-color-grid {
|
|
246
374
|
display: flex;
|
package/lib/public/index.html
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
<div id="top-bar">
|
|
32
32
|
<div class="top-bar-left-pills">
|
|
33
33
|
<button id="pwa-install-pill" class="top-bar-install-btn hidden" title="Open as app"><i data-lucide="download"></i> Open as app</button>
|
|
34
|
+
<button id="share-pill" class="top-bar-share-btn" title="Share"><i data-lucide="qr-code"></i> Share</button>
|
|
34
35
|
<div id="update-pill-wrap" class="top-bar-update hidden">
|
|
35
36
|
<button id="update-pill" class="top-bar-update-btn"><i data-lucide="arrow-up-circle"></i> <span id="update-version"></span> is available. Update now</button>
|
|
36
37
|
<div id="update-popover" class="top-bar-popover">
|
|
@@ -723,6 +724,7 @@
|
|
|
723
724
|
<div id="qr-overlay-inner">
|
|
724
725
|
<div id="qr-canvas"></div>
|
|
725
726
|
<div id="qr-url"></div>
|
|
727
|
+
<button id="qr-share-btn" class="qr-share-btn hidden"><i data-lucide="share-2"></i> Share</button>
|
|
726
728
|
</div>
|
|
727
729
|
</div>
|
|
728
730
|
|
|
@@ -1416,7 +1418,7 @@
|
|
|
1416
1418
|
<div class="mate-intro-step"><span class="mate-intro-step-num">2</span> Have a short interview where your Mate gets to know you</div>
|
|
1417
1419
|
<div class="mate-intro-step"><span class="mate-intro-step-num">3</span> Start talking</div>
|
|
1418
1420
|
</div>
|
|
1419
|
-
<p class="mate-intro-privacy">Mates run on Claude Code. No separate API keys or external services needed. Your conversations stay on your Clay server and are never stored elsewhere
|
|
1421
|
+
<p class="mate-intro-privacy">Mates run on Claude Code. No separate API keys or external services needed. Your conversations stay on your Clay server and are never stored elsewhere.</p>
|
|
1420
1422
|
</div>
|
|
1421
1423
|
</div>
|
|
1422
1424
|
<!-- Step 1: Relationship -->
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Centralized avatar URL builder and style definitions
|
|
2
|
+
// All DiceBear avatar URLs should be constructed through this module.
|
|
3
|
+
|
|
4
|
+
export var AVATAR_STYLES = [
|
|
5
|
+
{ id: 'thumbs', name: 'Thumbs' },
|
|
6
|
+
{ id: 'bottts', name: 'Bots' },
|
|
7
|
+
{ id: 'pixel-art', name: 'Pixel' },
|
|
8
|
+
{ id: 'adventurer', name: 'Adventurer' },
|
|
9
|
+
{ id: 'micah', name: 'Micah' },
|
|
10
|
+
{ id: 'fun-emoji', name: 'Emoji' },
|
|
11
|
+
{ id: 'icons', name: 'Icons' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// Build a DiceBear avatar URL from style, seed, and optional size.
|
|
15
|
+
export function avatarUrl(style, seed, size) {
|
|
16
|
+
var s = encodeURIComponent(seed || 'anonymous');
|
|
17
|
+
return 'https://api.dicebear.com/9.x/' + (style || 'thumbs') + '/svg?seed=' + s + '&size=' + (size || 64);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Build avatar URL for a user object, preferring custom avatar if set.
|
|
21
|
+
export function userAvatarUrl(user, size) {
|
|
22
|
+
if (user && user.avatarCustom) return user.avatarCustom;
|
|
23
|
+
var style = (user && user.avatarStyle) || 'thumbs';
|
|
24
|
+
var seed = (user && (user.avatarSeed || user.username || user.id)) || 'anonymous';
|
|
25
|
+
return avatarUrl(style, seed, size);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Build avatar URL for a mate object, preferring custom avatar if set.
|
|
29
|
+
export function mateAvatarUrl(mate, size) {
|
|
30
|
+
if (!mate) return avatarUrl('bottts', 'mate', size);
|
|
31
|
+
var p = mate.profile || mate;
|
|
32
|
+
if (p.avatarCustom || mate.avatarCustom) return p.avatarCustom || mate.avatarCustom;
|
|
33
|
+
var style = p.avatarStyle || mate.avatarStyle || 'bottts';
|
|
34
|
+
var seed = p.avatarSeed || mate.avatarSeed || mate.id || 'mate';
|
|
35
|
+
return avatarUrl(style, seed, size);
|
|
36
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { avatarUrl, userAvatarUrl, mateAvatarUrl } from './avatar.js';
|
|
1
2
|
import { escapeHtml } from './utils.js';
|
|
2
3
|
import { refreshIcons } from './icons.js';
|
|
3
4
|
import { openSearch as openSessionSearch } from './session-search.js';
|
|
@@ -274,9 +275,7 @@ function renderHome(filter) {
|
|
|
274
275
|
items.push({ type: "user", data: user });
|
|
275
276
|
var userName = escapeHtml(user.displayName || user.username);
|
|
276
277
|
var userSub = user.username ? "@" + escapeHtml(user.username) : "";
|
|
277
|
-
var
|
|
278
|
-
var uAvatarSeed = user.avatarSeed || user.username || user.id;
|
|
279
|
-
var uAvatarUrl = "https://api.dicebear.com/9.x/" + uAvatarStyle + "/svg?seed=" + encodeURIComponent(uAvatarSeed) + "&size=28";
|
|
278
|
+
var uAvatarUrl = userAvatarUrl(user, 28);
|
|
280
279
|
var uAvatarHtml = '<img src="' + uAvatarUrl + '" width="28" height="28" style="border-radius:50%;" alt="">';
|
|
281
280
|
html += renderItem(flatIndex, uAvatarHtml, userName, userSub, null);
|
|
282
281
|
flatIndex++;
|
|
@@ -298,10 +297,8 @@ function renderHome(filter) {
|
|
|
298
297
|
var mp = mate.profile || {};
|
|
299
298
|
items.push({ type: "mate", data: mate });
|
|
300
299
|
var mateName = escapeHtml(mp.displayName || mate.name || "Mate");
|
|
301
|
-
var
|
|
302
|
-
var
|
|
303
|
-
var avatarUrl = "https://api.dicebear.com/9.x/" + avatarStyle + "/svg?seed=" + encodeURIComponent(avatarSeed) + "&size=28";
|
|
304
|
-
var avatarHtml = '<img src="' + avatarUrl + '" width="28" height="28" style="border-radius:50%;" alt="">';
|
|
300
|
+
var mateAvUrl = mateAvatarUrl(mate, 28);
|
|
301
|
+
var avatarHtml = '<img src="' + mateAvUrl + '" width="28" height="28" style="border-radius:50%;" alt="">';
|
|
305
302
|
html += renderItem(flatIndex, avatarHtml, mateName, null, null);
|
|
306
303
|
flatIndex++;
|
|
307
304
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { avatarUrl, mateAvatarUrl } from './avatar.js';
|
|
1
2
|
import { escapeHtml } from './utils.js';
|
|
2
3
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
4
|
import { hideKnowledge } from './mate-knowledge.js';
|
|
@@ -134,19 +135,17 @@ export function showMateSidebar(mateId, mateData) {
|
|
|
134
135
|
// Populate header
|
|
135
136
|
var profile = mateData.profile || mateData || {};
|
|
136
137
|
var displayName = profile.displayName || mateData.displayName || mateData.name || "Mate";
|
|
137
|
-
var avatarStyle = profile.avatarStyle || "bottts";
|
|
138
|
-
var avatarSeed = profile.avatarSeed || mateId;
|
|
139
138
|
|
|
140
139
|
var mateColor = profile.avatarColor || mateData.avatarColor || "#7c3aed";
|
|
141
140
|
|
|
142
|
-
var
|
|
143
|
-
if (avatarEl) avatarEl.src =
|
|
141
|
+
var mateAvUrl = mateAvatarUrl(mateData, 32);
|
|
142
|
+
if (avatarEl) avatarEl.src = mateAvUrl;
|
|
144
143
|
if (nameEl) nameEl.textContent = displayName;
|
|
145
144
|
|
|
146
145
|
// Also populate collapsed header info
|
|
147
146
|
var collapsedAvatar = document.getElementById("mate-collapsed-avatar");
|
|
148
147
|
var collapsedName = document.getElementById("mate-collapsed-name");
|
|
149
|
-
if (collapsedAvatar) collapsedAvatar.src =
|
|
148
|
+
if (collapsedAvatar) collapsedAvatar.src = mateAvUrl;
|
|
150
149
|
if (collapsedName) collapsedName.textContent = displayName;
|
|
151
150
|
|
|
152
151
|
// Apply mate color to sidebar
|
|
@@ -215,12 +214,10 @@ export function updateMateSidebarProfile(mateData) {
|
|
|
215
214
|
if (!columnEl || !mateData) return;
|
|
216
215
|
var profile = mateData.profile || mateData || {};
|
|
217
216
|
var displayName = profile.displayName || mateData.displayName || mateData.name || "Mate";
|
|
218
|
-
var avatarStyle = profile.avatarStyle || "bottts";
|
|
219
|
-
var avatarSeed = profile.avatarSeed || (mateData.id || "mate");
|
|
220
217
|
var mateColor = profile.avatarColor || mateData.avatarColor || "#7c3aed";
|
|
221
218
|
|
|
222
219
|
if (avatarEl) {
|
|
223
|
-
avatarEl.src =
|
|
220
|
+
avatarEl.src = mateAvatarUrl(mateData, 32);
|
|
224
221
|
}
|
|
225
222
|
// Check if name changed for engrave effect
|
|
226
223
|
var oldName = nameEl ? nameEl.textContent : "";
|