clay-server 4.1.0-beta.11 → 4.1.0-beta.13
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.
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Local pixel identities: deterministic SVG avatars derived from a stable seed.
|
|
2
2
|
|
|
3
3
|
var PAPER = "#f1efe8";
|
|
4
4
|
var INK = "#1b1b1a";
|
|
5
|
-
var LIGHT_BASE = "#e7e5de";
|
|
6
|
-
var LIGHT_FIELD = "#333330";
|
|
7
5
|
var MUTED_INDIGO = "#72778f";
|
|
6
|
+
var IDENTICON_PALETTE = ["#333330", MUTED_INDIGO, "#6f8382", "#887b91", "#8a806a"];
|
|
8
7
|
var MATE_MARK_PALETTE = [
|
|
9
8
|
{ background: "#6f7489", foreground: PAPER },
|
|
10
9
|
{ background: "#78816c", foreground: PAPER },
|
|
@@ -24,25 +23,6 @@ function hashString(value) {
|
|
|
24
23
|
return hash >>> 0;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
function makeRandom(seed) {
|
|
28
|
-
var state = seed >>> 0;
|
|
29
|
-
return function nextRandom() {
|
|
30
|
-
state += 0x6d2b79f5;
|
|
31
|
-
var value = state;
|
|
32
|
-
value = Math.imul(value ^ (value >>> 15), value | 1);
|
|
33
|
-
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
|
34
|
-
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function between(random, min, max) {
|
|
39
|
-
return min + random() * (max - min);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function number(value) {
|
|
43
|
-
return Math.round(value * 100) / 100;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
26
|
function xmlText(value) {
|
|
47
27
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
48
28
|
}
|
|
@@ -54,62 +34,37 @@ function legacyIdentity(style, seed) {
|
|
|
54
34
|
return "legacy:" + sourceStyle + ":" + sourceSeed;
|
|
55
35
|
}
|
|
56
36
|
|
|
57
|
-
function curvePath(y, bow, endOffset) {
|
|
58
|
-
return "M -28 " + number(y) +
|
|
59
|
-
" C 17 " + number(y + bow) +
|
|
60
|
-
", 71 " + number(y - bow) +
|
|
61
|
-
", 124 " + number(y + endOffset);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function cutPath(random) {
|
|
65
|
-
var start = between(random, 37, 52);
|
|
66
|
-
var first = between(random, 14, 84);
|
|
67
|
-
var second = between(random, 12, 86);
|
|
68
|
-
var end = between(random, 40, 64);
|
|
69
|
-
return "M -22 " + number(start) + " C 21 " + number(first) + ", 73 " + number(second) + ", 118 " + number(end);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
37
|
export function imprintSvg(options) {
|
|
73
38
|
var settings = options || {};
|
|
74
39
|
var size = Math.max(12, Math.min(256, Number(settings.size) || 64));
|
|
75
40
|
var identity = legacyIdentity(settings.style, settings.seed);
|
|
76
41
|
var hash = hashString(identity);
|
|
77
|
-
var
|
|
78
|
-
var
|
|
79
|
-
var
|
|
80
|
-
var
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
var firstBow = between(random, -34, 34);
|
|
89
|
-
var secondBow = between(random, -32, 32);
|
|
90
|
-
var firstPath = curvePath(firstY, firstBow, 6);
|
|
91
|
-
var secondPath = curvePath(secondY, -secondBow, -5);
|
|
92
|
-
var contours = "";
|
|
42
|
+
var cells = identiconCells(hash);
|
|
43
|
+
var foreground = IDENTICON_PALETTE[hash % IDENTICON_PALETTE.length];
|
|
44
|
+
var pixels = "";
|
|
45
|
+
for (var i = 0; i < cells.length; i++) {
|
|
46
|
+
pixels += '<rect x="' + (cells[i][0] + 1) + '" y="' + (cells[i][1] + 1) + '" width="1" height="1"/>';
|
|
47
|
+
}
|
|
48
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size +
|
|
49
|
+
'" viewBox="0 0 7 7" shape-rendering="crispEdges"><rect width="7" height="7" fill="' + PAPER +
|
|
50
|
+
'"/><g fill="' + foreground + '">' + pixels + '</g><rect x="0.5" y="0.5" width="6" height="6" fill="none" stroke="' +
|
|
51
|
+
INK + '" stroke-opacity="0.14" stroke-width="0.08"/></svg>';
|
|
52
|
+
}
|
|
93
53
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
54
|
+
// Generate the left three columns and mirror them into a compact 5x5 grid.
|
|
55
|
+
export function identiconCells(hash) {
|
|
56
|
+
var value = hash >>> 0;
|
|
57
|
+
var cells = [];
|
|
58
|
+
for (var row = 0; row < 5; row++) {
|
|
59
|
+
for (var column = 0; column < 3; column++) {
|
|
60
|
+
value = Math.imul(value ^ (value >>> 13), 16777619) >>> 0;
|
|
61
|
+
if ((value & 1) || (row === 2 && column === 1)) {
|
|
62
|
+
cells.push([column, row]);
|
|
63
|
+
if (column < 2) cells.push([4 - column, row]);
|
|
64
|
+
}
|
|
99
65
|
}
|
|
100
66
|
}
|
|
101
|
-
|
|
102
|
-
var clipId = "imprint-" + hash.toString(16);
|
|
103
|
-
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size +
|
|
104
|
-
'" viewBox="0 0 96 96"><defs><clipPath id="' + clipId +
|
|
105
|
-
'"><rect width="96" height="96" rx="24"/></clipPath></defs><g clip-path="url(#' + clipId +
|
|
106
|
-
')"><rect width="96" height="96" fill="' + base + '"/><g transform="rotate(' + rotation +
|
|
107
|
-
' 48 48)"><path d="' + firstPath + '" fill="none" stroke="' + field + '" stroke-width="' +
|
|
108
|
-
number(bandWidth) + '" stroke-linecap="square"/><path d="' + secondPath + '" fill="none" stroke="' +
|
|
109
|
-
accent + '" stroke-width="' + number(secondaryWidth) + '" stroke-linecap="square"/><path d="' +
|
|
110
|
-
cutPath(random) + '" fill="none" stroke="' + base + '" stroke-width="' + number(cutWidth) +
|
|
111
|
-
'" stroke-linecap="square"/>' + contours + '</g></g><rect x="0.5" y="0.5" width="95" height="95" rx="23.5" fill="none" stroke="' +
|
|
112
|
-
(darkBase ? "#000000" : INK) + '" stroke-opacity="0.16"/></svg>';
|
|
67
|
+
return cells;
|
|
113
68
|
}
|
|
114
69
|
|
|
115
70
|
export function imprintDataUrl(options) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// Centralized
|
|
1
|
+
// Centralized local avatar URL builder for people and Mates.
|
|
2
2
|
import { imprintDataUrl, mateMarkDataUrl } from './avatar-imprint.js';
|
|
3
3
|
|
|
4
4
|
var imprintCache = new Map();
|
|
5
5
|
var mateMarkCache = new Map();
|
|
6
6
|
|
|
7
7
|
// Legacy style remains part of the identity hash so existing selections map
|
|
8
|
-
// deterministically into the
|
|
8
|
+
// deterministically into the local pixel identity system.
|
|
9
9
|
export function avatarUrl(style, seed, size) {
|
|
10
10
|
var cacheKey = [style || 'imprint', seed || 'anonymous', size || 64].join('|');
|
|
11
11
|
if (imprintCache.has(cacheKey)) return imprintCache.get(cacheKey);
|
|
@@ -15,12 +15,20 @@ export function avatarUrl(style, seed, size) {
|
|
|
15
15
|
return url;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Use the same bounded account identity for fresh fallbacks across every surface.
|
|
19
|
+
export function userAvatarSeed(user) {
|
|
20
|
+
var profile = user && user.profile ? user.profile : user;
|
|
21
|
+
var savedSeed = profile && profile.avatarSeed || user && user.avatarSeed;
|
|
22
|
+
if (savedSeed) return String(savedSeed);
|
|
23
|
+
var identity = profile && (profile.username || profile.id || profile.userId) || user && (user.username || user.id || user.userId) || 'anonymous';
|
|
24
|
+
return String(identity).substring(0, 30);
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
// Build avatar URL for a user object, preferring custom avatar if set.
|
|
19
28
|
export function userAvatarUrl(user, size) {
|
|
20
29
|
if (user && user.avatarCustom) return user.avatarCustom;
|
|
21
30
|
var style = (user && user.avatarStyle) || 'imprint';
|
|
22
|
-
|
|
23
|
-
return avatarUrl(style, seed, size);
|
|
31
|
+
return avatarUrl(style, userAvatarSeed(user), size);
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
export function mateMarkUrl(seed, size) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// User profile module — profile popovers for people and Mates
|
|
2
2
|
// Stores profile server-side in ~/.clay/profile.json
|
|
3
|
-
// Generated identities use deterministic
|
|
3
|
+
// Generated identities use deterministic local pixel identicons.
|
|
4
4
|
|
|
5
5
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
6
|
import { setSTTLang } from './stt.js';
|
|
7
|
-
import { avatarUrl, mateAvatarUrl, mateMarkUrl } from './avatar.js';
|
|
7
|
+
import { avatarUrl, mateAvatarUrl, mateMarkUrl, userAvatarSeed } from './avatar.js';
|
|
8
8
|
import { store } from './store.js';
|
|
9
9
|
import { applyTerminalFont } from './terminal-prefs.js';
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ var popoverEl = null;
|
|
|
15
15
|
var saveTimer = null;
|
|
16
16
|
|
|
17
17
|
function getAvatarSeed() {
|
|
18
|
-
return profile.avatarSeed || 'anonymous';
|
|
18
|
+
return profile.avatarSeed || String(profileUsername || 'anonymous').substring(0, 30);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// --- API ---
|
|
@@ -307,14 +307,14 @@ function showPopover() {
|
|
|
307
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">';
|
|
308
308
|
html += '</div>';
|
|
309
309
|
|
|
310
|
-
//
|
|
310
|
+
// Pixel identicon or custom photo
|
|
311
311
|
html += '<div class="profile-field">';
|
|
312
312
|
html += '<label class="profile-field-label">Identity</label>';
|
|
313
313
|
html += '<div class="profile-imprint-panel">';
|
|
314
|
-
html += '<div class="profile-imprint-preview"><img src="' + avatarUrl(currentStyle, seed, 72) + '" alt="Generated
|
|
314
|
+
html += '<div class="profile-imprint-preview"><img src="' + avatarUrl(currentStyle, seed, 72) + '" alt="Generated pixel avatar"><span><strong>Pixel avatar</strong><small>Unique to this identity</small></span></div>';
|
|
315
315
|
html += '<div class="profile-imprint-actions">';
|
|
316
|
-
html += '<button class="profile-use-imprint' + (!profile.avatarCustom ? ' is-active' : '') + '" type="button">Use
|
|
317
|
-
html += '<button class="profile-regenerate-imprint" type="button" title="Generate another
|
|
316
|
+
html += '<button class="profile-use-imprint' + (!profile.avatarCustom ? ' is-active' : '') + '" type="button">Use pixel avatar</button>';
|
|
317
|
+
html += '<button class="profile-regenerate-imprint" type="button" title="Generate another pixel avatar">' + iconHtml('shuffle') + ' New avatar</button>';
|
|
318
318
|
html += '<button class="profile-avatar-upload' + (profile.avatarCustom ? ' is-active' : '') + '" type="button">Upload photo</button>';
|
|
319
319
|
html += '</div>';
|
|
320
320
|
html += '</div>';
|
|
@@ -511,9 +511,9 @@ export function initProfile(_ctx) {
|
|
|
511
511
|
if (data.avatarCustom) profile.avatarCustom = data.avatarCustom;
|
|
512
512
|
if (data.username) profileUsername = data.username;
|
|
513
513
|
|
|
514
|
-
//
|
|
514
|
+
// Persist a stable account identity only when the server has not saved a seed.
|
|
515
515
|
if (!profile.avatarSeed) {
|
|
516
|
-
profile.avatarSeed =
|
|
516
|
+
profile.avatarSeed = userAvatarSeed(data);
|
|
517
517
|
saveProfile();
|
|
518
518
|
}
|
|
519
519
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "4.1.0-beta.
|
|
3
|
+
"version": "4.1.0-beta.13",
|
|
4
4
|
"description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clay-server": "./bin/cli.js"
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"dev": "node bin/cli.js --dev",
|
|
10
10
|
"test": "node --test --test-force-exit test/*.test.js",
|
|
11
|
-
"prepack": "npm pkg delete bin.clay-dev",
|
|
12
|
-
"postpack": "npm pkg set bin.clay-dev=./bin/cli.js",
|
|
13
11
|
"semantic-release": "semantic-release"
|
|
14
12
|
},
|
|
15
13
|
"files": [
|