@mooncompany/uplink-chat 0.5.0
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.
Potentially problematic release.
This version of @mooncompany/uplink-chat might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/bin/uplink.js +279 -0
- package/middleware/error-handler.js +69 -0
- package/package.json +93 -0
- package/public/css/agents.36b98c0f.css +1469 -0
- package/public/css/agents.css +1469 -0
- package/public/css/app.a6a7f8f5.css +2731 -0
- package/public/css/app.css +2731 -0
- package/public/css/artifacts.css +444 -0
- package/public/css/commands.css +55 -0
- package/public/css/connection.css +131 -0
- package/public/css/dashboard.css +233 -0
- package/public/css/developer.css +328 -0
- package/public/css/files.css +123 -0
- package/public/css/markdown.css +156 -0
- package/public/css/message-actions.css +278 -0
- package/public/css/mobile.css +614 -0
- package/public/css/panels-unified.css +483 -0
- package/public/css/premium.css +415 -0
- package/public/css/realtime.css +189 -0
- package/public/css/satellites.css +401 -0
- package/public/css/shortcuts.css +185 -0
- package/public/css/split-view.4def0262.css +673 -0
- package/public/css/split-view.css +673 -0
- package/public/css/theme-generator.css +391 -0
- package/public/css/themes.css +387 -0
- package/public/css/timestamps.css +54 -0
- package/public/css/variables.css +78 -0
- package/public/dist/bundle.b55050c4.js +15757 -0
- package/public/favicon.svg +24 -0
- package/public/img/agents/ada.png +0 -0
- package/public/img/agents/clarice.png +0 -0
- package/public/img/agents/dennis-nedry.png +0 -0
- package/public/img/agents/elliot-alderson.png +0 -0
- package/public/img/agents/main.png +0 -0
- package/public/img/agents/scotty.png +0 -0
- package/public/img/agents/top-flight-security.png +0 -0
- package/public/index.html +1083 -0
- package/public/js/agents-data.js +234 -0
- package/public/js/agents-ui.js +72 -0
- package/public/js/agents.js +1525 -0
- package/public/js/app.js +79 -0
- package/public/js/appearance-settings.js +111 -0
- package/public/js/artifacts.js +432 -0
- package/public/js/audio-queue.js +168 -0
- package/public/js/bootstrap.js +54 -0
- package/public/js/chat.js +1211 -0
- package/public/js/commands.js +581 -0
- package/public/js/connection-api.js +121 -0
- package/public/js/connection.js +1231 -0
- package/public/js/context-tracker.js +271 -0
- package/public/js/core.js +172 -0
- package/public/js/dashboard.js +452 -0
- package/public/js/developer.js +432 -0
- package/public/js/encryption.js +124 -0
- package/public/js/errors.js +122 -0
- package/public/js/event-bus.js +77 -0
- package/public/js/fetch-utils.js +171 -0
- package/public/js/file-handler.js +229 -0
- package/public/js/files.js +352 -0
- package/public/js/gateway-chat.js +538 -0
- package/public/js/logger.js +112 -0
- package/public/js/markdown.js +190 -0
- package/public/js/message-actions.js +431 -0
- package/public/js/message-renderer.js +288 -0
- package/public/js/missed-messages.js +235 -0
- package/public/js/mobile-debug.js +95 -0
- package/public/js/notifications.js +367 -0
- package/public/js/offline-queue.js +178 -0
- package/public/js/onboarding.js +543 -0
- package/public/js/panels.js +156 -0
- package/public/js/premium.js +412 -0
- package/public/js/realtime-voice.js +844 -0
- package/public/js/satellite-sync.js +256 -0
- package/public/js/satellite-ui.js +175 -0
- package/public/js/satellites.js +1516 -0
- package/public/js/settings.js +1087 -0
- package/public/js/shortcuts.js +381 -0
- package/public/js/split-chat.js +1234 -0
- package/public/js/split-resize.js +211 -0
- package/public/js/splitview.js +340 -0
- package/public/js/storage.js +408 -0
- package/public/js/streaming-handler.js +324 -0
- package/public/js/stt-settings.js +316 -0
- package/public/js/theme-generator.js +661 -0
- package/public/js/themes.js +164 -0
- package/public/js/timestamps.js +198 -0
- package/public/js/tts-settings.js +575 -0
- package/public/js/ui.js +267 -0
- package/public/js/update-notifier.js +143 -0
- package/public/js/utils/constants.js +165 -0
- package/public/js/utils/sanitize.js +93 -0
- package/public/js/utils/sse-parser.js +195 -0
- package/public/js/voice.js +883 -0
- package/public/manifest.json +58 -0
- package/public/moon_texture.jpg +0 -0
- package/public/sw.js +221 -0
- package/public/three.min.js +6 -0
- package/server/channel.js +529 -0
- package/server/chat.js +270 -0
- package/server/config-store.js +362 -0
- package/server/config.js +159 -0
- package/server/context.js +131 -0
- package/server/gateway-commands.js +211 -0
- package/server/gateway-proxy.js +318 -0
- package/server/index.js +22 -0
- package/server/logger.js +89 -0
- package/server/middleware/auth.js +188 -0
- package/server/middleware.js +218 -0
- package/server/openclaw-discover.js +308 -0
- package/server/premium/index.js +156 -0
- package/server/premium/license.js +140 -0
- package/server/realtime/bridge.js +837 -0
- package/server/realtime/index.js +349 -0
- package/server/realtime/tts-stream.js +446 -0
- package/server/routes/agents.js +564 -0
- package/server/routes/artifacts.js +174 -0
- package/server/routes/chat.js +311 -0
- package/server/routes/config-settings.js +345 -0
- package/server/routes/config.js +603 -0
- package/server/routes/files.js +307 -0
- package/server/routes/index.js +18 -0
- package/server/routes/media.js +451 -0
- package/server/routes/missed-messages.js +107 -0
- package/server/routes/premium.js +75 -0
- package/server/routes/push.js +156 -0
- package/server/routes/satellite.js +406 -0
- package/server/routes/status.js +251 -0
- package/server/routes/stt.js +35 -0
- package/server/routes/voice.js +260 -0
- package/server/routes/webhooks.js +203 -0
- package/server/routes.js +206 -0
- package/server/runtime-config.js +336 -0
- package/server/share.js +305 -0
- package/server/stt/faster-whisper.js +72 -0
- package/server/stt/groq.js +51 -0
- package/server/stt/index.js +196 -0
- package/server/stt/openai.js +49 -0
- package/server/sync.js +244 -0
- package/server/tailscale-https.js +175 -0
- package/server/tts.js +646 -0
- package/server/update-checker.js +172 -0
- package/server/utils/filename.js +129 -0
- package/server/utils.js +147 -0
- package/server/watchdog.js +318 -0
- package/server/websocket/broadcast.js +359 -0
- package/server/websocket/connections.js +339 -0
- package/server/websocket/index.js +215 -0
- package/server/websocket/routing.js +277 -0
- package/server/websocket/sync.js +102 -0
- package/server.js +404 -0
- package/utils/detect-tool-usage.js +93 -0
- package/utils/errors.js +158 -0
- package/utils/html-escape.js +84 -0
- package/utils/id-sanitize.js +94 -0
- package/utils/response.js +130 -0
- package/utils/with-retry.js +105 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// THEME GENERATOR MODULE
|
|
3
|
+
// Custom theme creator — Premium feature
|
|
4
|
+
// Multi-channel color control + named themes
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
// ── Color utilities ──
|
|
8
|
+
|
|
9
|
+
function hexToHSL(hex) {
|
|
10
|
+
hex = hex.replace('#', '');
|
|
11
|
+
var r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
12
|
+
var g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
13
|
+
var b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
14
|
+
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
15
|
+
var h, s, l = (max + min) / 2;
|
|
16
|
+
if (max === min) { h = s = 0; }
|
|
17
|
+
else {
|
|
18
|
+
var d = max - min;
|
|
19
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
20
|
+
switch (max) {
|
|
21
|
+
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
|
|
22
|
+
case g: h = ((b - r) / d + 2) / 6; break;
|
|
23
|
+
case b: h = ((r - g) / d + 4) / 6; break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function hslToHex(h, s, l) {
|
|
30
|
+
s /= 100; l /= 100;
|
|
31
|
+
var a = s * Math.min(l, 1 - l);
|
|
32
|
+
var f = function(n) {
|
|
33
|
+
var k = (n + h / 30) % 12;
|
|
34
|
+
var color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
35
|
+
return Math.round(255 * color).toString(16).padStart(2, '0');
|
|
36
|
+
};
|
|
37
|
+
return '#' + f(0) + f(8) + f(4);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hexToRGB(hex) {
|
|
41
|
+
hex = hex.replace('#', '');
|
|
42
|
+
return {
|
|
43
|
+
r: parseInt(hex.substring(0, 2), 16),
|
|
44
|
+
g: parseInt(hex.substring(2, 4), 16),
|
|
45
|
+
b: parseInt(hex.substring(4, 6), 16)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function luminance(hex) {
|
|
50
|
+
var rgb = hexToRGB(hex);
|
|
51
|
+
var vals = [rgb.r, rgb.g, rgb.b].map(function(v) {
|
|
52
|
+
v /= 255;
|
|
53
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
54
|
+
});
|
|
55
|
+
return 0.2126 * vals[0] + 0.7152 * vals[1] + 0.0722 * vals[2];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function bgIsDark(hex) { return luminance(hex) < 0.2; }
|
|
59
|
+
|
|
60
|
+
function escapeHtml(str) {
|
|
61
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
62
|
+
}
|
|
63
|
+
function escapeAttr(str) { return escapeHtml(str); }
|
|
64
|
+
|
|
65
|
+
// ── Theme generation from 6 color channels ──
|
|
66
|
+
|
|
67
|
+
function generateTheme(colors) {
|
|
68
|
+
var accent = colors.accent;
|
|
69
|
+
var bg = colors.background;
|
|
70
|
+
var userColor = colors.userBubble;
|
|
71
|
+
var aiColor = colors.aiBubble;
|
|
72
|
+
var textColor = colors.text;
|
|
73
|
+
var starColor = colors.stars;
|
|
74
|
+
|
|
75
|
+
var aHSL = hexToHSL(accent);
|
|
76
|
+
var aRGB = hexToRGB(accent);
|
|
77
|
+
var bHSL = hexToHSL(bg);
|
|
78
|
+
var bRGB = hexToRGB(bg);
|
|
79
|
+
var uRGB = hexToRGB(userColor);
|
|
80
|
+
var aiRGB = hexToRGB(aiColor);
|
|
81
|
+
var tHSL = hexToHSL(textColor);
|
|
82
|
+
var dark = bgIsDark(bg);
|
|
83
|
+
|
|
84
|
+
var accentHover = hslToHex(aHSL.h, Math.min(aHSL.s, 80), Math.min(aHSL.l + 10, 80));
|
|
85
|
+
var accentSecondary = hslToHex((aHSL.h + 40) % 360, Math.min(aHSL.s, 70), 50);
|
|
86
|
+
|
|
87
|
+
var bgSecL = dark ? Math.min(bHSL.l + 8, 25) : Math.max(bHSL.l - 3, 90);
|
|
88
|
+
var bgSecHex = hslToHex(bHSL.h, Math.min(bHSL.s, 20), bgSecL);
|
|
89
|
+
var bgSecRGB = hexToRGB(bgSecHex);
|
|
90
|
+
|
|
91
|
+
var bgInputL = dark ? Math.min(bHSL.l + 5, 20) : Math.max(bHSL.l - 2, 94);
|
|
92
|
+
var bgInputHex = hslToHex(bHSL.h, Math.min(bHSL.s, 15), bgInputL);
|
|
93
|
+
var bgInputRGB = hexToRGB(bgInputHex);
|
|
94
|
+
|
|
95
|
+
var textMuted = hslToHex(tHSL.h, Math.min(tHSL.s, 30), dark ? 55 : 50);
|
|
96
|
+
var textDim = hslToHex(tHSL.h, Math.min(tHSL.s, 25), dark ? 42 : 60);
|
|
97
|
+
var textInverse = (luminance(accent) < 0.3) ? '#ffffff' : '#000000';
|
|
98
|
+
|
|
99
|
+
var uHSL = hexToHSL(userColor);
|
|
100
|
+
var userAccent = hslToHex(uHSL.h, Math.min(uHSL.s, 60), dark ? 25 : 70);
|
|
101
|
+
var userAccentHover = hslToHex(uHSL.h, Math.min(uHSL.s, 55), dark ? 20 : 65);
|
|
102
|
+
|
|
103
|
+
var h = aHSL.h;
|
|
104
|
+
var vars = {
|
|
105
|
+
'--bg': bg,
|
|
106
|
+
'--bg-secondary': 'rgba(' + bgSecRGB.r + ',' + bgSecRGB.g + ',' + bgSecRGB.b + ',0.95)',
|
|
107
|
+
'--bg-input': 'rgba(' + bgInputRGB.r + ',' + bgInputRGB.g + ',' + bgInputRGB.b + ',0.8)',
|
|
108
|
+
'--accent': accent,
|
|
109
|
+
'--accent-hover': accentHover,
|
|
110
|
+
'--accent-secondary': accentSecondary,
|
|
111
|
+
'--text': textColor,
|
|
112
|
+
'--text-muted': textMuted,
|
|
113
|
+
'--text-dim': textDim,
|
|
114
|
+
'--text-inverse': textInverse,
|
|
115
|
+
'--border': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',' + (dark ? '0.15' : '0.1') + ')',
|
|
116
|
+
'--accent-05': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',0.05)',
|
|
117
|
+
'--accent-10': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',0.1)',
|
|
118
|
+
'--accent-15': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',0.15)',
|
|
119
|
+
'--accent-20': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',0.2)',
|
|
120
|
+
'--accent-30': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',0.3)',
|
|
121
|
+
'--accent-40': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',0.4)',
|
|
122
|
+
'--accent-50': 'rgba(' + aRGB.r + ',' + aRGB.g + ',' + aRGB.b + ',0.5)',
|
|
123
|
+
'--user-bubble': 'rgba(' + uRGB.r + ',' + uRGB.g + ',' + uRGB.b + ',' + (dark ? '0.35' : '0.15') + ')',
|
|
124
|
+
'--user-accent': userAccent,
|
|
125
|
+
'--user-accent-hover': userAccentHover,
|
|
126
|
+
'--assistant-bubble': 'rgba(' + aiRGB.r + ',' + aiRGB.g + ',' + aiRGB.b + ',' + (dark ? '0.9' : '0.12') + ')',
|
|
127
|
+
'--star-color': starColor,
|
|
128
|
+
'--success': dark ? '#4ade80' : '#16a34a',
|
|
129
|
+
'--warning': dark ? '#fbbf24' : '#d97706',
|
|
130
|
+
'--info': '#60a5fa',
|
|
131
|
+
'--recording': dark ? '#ff3366' : '#dc2626',
|
|
132
|
+
'--error': dark ? '#ef4444' : '#dc2626',
|
|
133
|
+
'--neutral': '#6b7280',
|
|
134
|
+
'--syntax-keyword': dark ? hslToHex((h+280)%360,60,75) : hslToHex((h+280)%360,55,40),
|
|
135
|
+
'--syntax-string': dark ? hslToHex((h+120)%360,50,72) : hslToHex((h+120)%360,50,35),
|
|
136
|
+
'--syntax-comment': dark ? hslToHex(h,20,38) : hslToHex(h,8,64),
|
|
137
|
+
'--syntax-number': dark ? hslToHex((h+30)%360,60,65) : hslToHex(h,60,40),
|
|
138
|
+
};
|
|
139
|
+
if (!dark) {
|
|
140
|
+
vars['--error-10'] = 'rgba(220,38,38,0.08)';
|
|
141
|
+
vars['--error-20'] = 'rgba(220,38,38,0.12)';
|
|
142
|
+
vars['--error-30'] = 'rgba(220,38,38,0.2)';
|
|
143
|
+
vars['--success-20'] = 'rgba(22,163,74,0.1)';
|
|
144
|
+
vars['--black-20'] = 'rgba(0,0,0,0.06)';
|
|
145
|
+
vars['--black-40'] = 'rgba(0,0,0,0.1)';
|
|
146
|
+
} else {
|
|
147
|
+
vars['--error-10'] = 'rgba(255,68,102,0.1)';
|
|
148
|
+
vars['--error-20'] = 'rgba(255,68,102,0.2)';
|
|
149
|
+
vars['--error-30'] = 'rgba(255,68,102,0.3)';
|
|
150
|
+
vars['--success-20'] = 'rgba(0,255,136,0.2)';
|
|
151
|
+
}
|
|
152
|
+
return vars;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Apply / Clear ──
|
|
156
|
+
|
|
157
|
+
function applyCustomVars(vars) {
|
|
158
|
+
var root = document.documentElement;
|
|
159
|
+
for (var key in vars) root.style.setProperty(key, vars[key]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
var ALL_VARS = [
|
|
163
|
+
'--bg','--bg-secondary','--bg-input',
|
|
164
|
+
'--accent','--accent-hover','--accent-secondary',
|
|
165
|
+
'--text','--text-muted','--text-dim','--text-inverse','--border',
|
|
166
|
+
'--accent-05','--accent-10','--accent-15','--accent-20',
|
|
167
|
+
'--accent-30','--accent-40','--accent-50',
|
|
168
|
+
'--error-10','--error-20','--error-30','--success-20',
|
|
169
|
+
'--user-bubble','--user-accent','--user-accent-hover',
|
|
170
|
+
'--assistant-bubble','--success','--warning','--info',
|
|
171
|
+
'--recording','--error','--neutral','--star-color',
|
|
172
|
+
'--syntax-keyword','--syntax-string','--syntax-comment','--syntax-number',
|
|
173
|
+
'--black-20','--black-40',
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
function clearCustomVars() {
|
|
177
|
+
var root = document.documentElement;
|
|
178
|
+
ALL_VARS.forEach(function(v) { root.style.removeProperty(v); });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Persistence ──
|
|
182
|
+
|
|
183
|
+
var STORAGE_KEY = 'uplink-custom-themes';
|
|
184
|
+
var ACTIVE_KEY = 'uplink-custom-active';
|
|
185
|
+
|
|
186
|
+
function loadAllThemes() {
|
|
187
|
+
try { var r = localStorage.getItem(STORAGE_KEY); return r ? JSON.parse(r) : []; }
|
|
188
|
+
catch(e) { return []; }
|
|
189
|
+
}
|
|
190
|
+
function saveAllThemes(themes) {
|
|
191
|
+
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(themes)); }
|
|
192
|
+
catch(e) { console.warn('ThemeGen: save failed', e); }
|
|
193
|
+
}
|
|
194
|
+
function loadActiveId() { return localStorage.getItem(ACTIVE_KEY) || null; }
|
|
195
|
+
function saveActiveId(id) {
|
|
196
|
+
if (id) localStorage.setItem(ACTIVE_KEY, id);
|
|
197
|
+
else localStorage.removeItem(ACTIVE_KEY);
|
|
198
|
+
}
|
|
199
|
+
function generateId() {
|
|
200
|
+
return 'ct-' + Date.now().toString(36) + Math.random().toString(36).slice(2,6);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Migrate v1 format
|
|
204
|
+
function migrateOldFormat() {
|
|
205
|
+
try {
|
|
206
|
+
var old = localStorage.getItem('uplink-custom-theme');
|
|
207
|
+
if (!old) return;
|
|
208
|
+
var data = JSON.parse(old);
|
|
209
|
+
if (data && data.accent) {
|
|
210
|
+
var m = {
|
|
211
|
+
id: generateId(), name: 'My Theme',
|
|
212
|
+
colors: {
|
|
213
|
+
accent: data.accent, background: '#000000',
|
|
214
|
+
userBubble: data.accent, aiBubble: '#002840',
|
|
215
|
+
text: '#f1f5f9', stars: data.accent,
|
|
216
|
+
},
|
|
217
|
+
createdAt: Date.now(),
|
|
218
|
+
};
|
|
219
|
+
var themes = loadAllThemes();
|
|
220
|
+
themes.push(m);
|
|
221
|
+
saveAllThemes(themes);
|
|
222
|
+
if (data.active) saveActiveId(m.id);
|
|
223
|
+
localStorage.removeItem('uplink-custom-theme');
|
|
224
|
+
}
|
|
225
|
+
} catch(e) {}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Defaults & presets ──
|
|
229
|
+
|
|
230
|
+
var defaultColors = {
|
|
231
|
+
accent: '#8b5cf6', background: '#000000', userBubble: '#4c1d95',
|
|
232
|
+
aiBubble: '#002840', text: '#f1f5f9', stars: '#ffffff',
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
var presets = [
|
|
236
|
+
{ name: 'Violet', accent:'#8b5cf6', bg:'#000000', user:'#4c1d95', ai:'#1a1030', text:'#f1f5f9', stars:'#c4b5fd' },
|
|
237
|
+
{ name: 'Cyan', accent:'#00f0ff', bg:'#000000', user:'#0e4d65', ai:'#002840', text:'#f1f5f9', stars:'#00f0ff' },
|
|
238
|
+
{ name: 'Rose', accent:'#f43f5e', bg:'#0a0000', user:'#6b1020', ai:'#2a0a10', text:'#fce4ec', stars:'#ff6b8a' },
|
|
239
|
+
{ name: 'Amber', accent:'#f59e0b', bg:'#050200', user:'#6b3a00', ai:'#1a1000', text:'#fef3c7', stars:'#fbbf24' },
|
|
240
|
+
{ name: 'Emerald', accent:'#10b981', bg:'#000a05', user:'#064e3b', ai:'#0a1f18', text:'#d1fae5', stars:'#6ee7b7' },
|
|
241
|
+
{ name: 'Ocean', accent:'#3b82f6', bg:'#000008', user:'#1e3a6e', ai:'#0a1628', text:'#dbeafe', stars:'#60a5fa' },
|
|
242
|
+
{ name: 'Sakura', accent:'#ec4899', bg:'#080004', user:'#6b1048', ai:'#1f0a18', text:'#fce7f3', stars:'#f9a8d4' },
|
|
243
|
+
{ name: 'Snow', accent:'#6366f1', bg:'#f8f9fa', user:'#c7d2fe', ai:'#e9ecef', text:'#1a1a2e', stars:'#ffffff' },
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
var channels = [
|
|
247
|
+
{ key:'accent', label:'Accent', desc:'Buttons, links, highlights' },
|
|
248
|
+
{ key:'background', label:'Background', desc:'Main background' },
|
|
249
|
+
{ key:'userBubble', label:'Your Bubbles', desc:'Your message color' },
|
|
250
|
+
{ key:'aiBubble', label:'AI Bubbles', desc:'Assistant message color' },
|
|
251
|
+
{ key:'text', label:'Text', desc:'Primary text color' },
|
|
252
|
+
{ key:'stars', label:'Stars', desc:'Background particles' },
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
// ── State ──
|
|
256
|
+
|
|
257
|
+
var generatorEl = null;
|
|
258
|
+
var currentColors = assign({}, defaultColors);
|
|
259
|
+
var currentName = '';
|
|
260
|
+
var editingId = null;
|
|
261
|
+
var isActive = false;
|
|
262
|
+
var activeThemeId = null;
|
|
263
|
+
|
|
264
|
+
function assign(target, src) {
|
|
265
|
+
for (var k in src) target[k] = src[k];
|
|
266
|
+
return target;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Apply a saved theme by ID ──
|
|
270
|
+
|
|
271
|
+
function applyThemeById(id) {
|
|
272
|
+
var themes = loadAllThemes();
|
|
273
|
+
var theme = themes.find(function(t) { return t.id === id; });
|
|
274
|
+
if (!theme) return;
|
|
275
|
+
|
|
276
|
+
var vars = generateTheme(theme.colors);
|
|
277
|
+
applyCustomVars(vars);
|
|
278
|
+
document.documentElement.setAttribute('data-theme', 'custom');
|
|
279
|
+
isActive = true;
|
|
280
|
+
activeThemeId = id;
|
|
281
|
+
saveActiveId(id);
|
|
282
|
+
ensureCustomOption(theme.name);
|
|
283
|
+
|
|
284
|
+
var themeSelect = document.getElementById('themeSelect');
|
|
285
|
+
if (themeSelect) themeSelect.value = 'custom';
|
|
286
|
+
var storage = window.UplinkStorage;
|
|
287
|
+
if (storage) storage.saveSettings({ theme: 'custom' });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ── Theme select integration ──
|
|
291
|
+
|
|
292
|
+
function ensureCustomOption(name) {
|
|
293
|
+
var select = document.getElementById('themeSelect');
|
|
294
|
+
if (!select) return;
|
|
295
|
+
var opt = select.querySelector('option[value="custom"]');
|
|
296
|
+
if (!opt) {
|
|
297
|
+
opt = document.createElement('option');
|
|
298
|
+
opt.value = 'custom';
|
|
299
|
+
select.appendChild(opt);
|
|
300
|
+
}
|
|
301
|
+
opt.textContent = '✦ ' + (name || 'Custom');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function removeCustomOption() {
|
|
305
|
+
var select = document.getElementById('themeSelect');
|
|
306
|
+
if (!select) return;
|
|
307
|
+
var opt = select.querySelector('option[value="custom"]');
|
|
308
|
+
if (opt) opt.remove();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── Sync all pickers to currentColors ──
|
|
312
|
+
|
|
313
|
+
function syncPickersToColors() {
|
|
314
|
+
if (!generatorEl) return;
|
|
315
|
+
channels.forEach(function(ch) {
|
|
316
|
+
var val = currentColors[ch.key];
|
|
317
|
+
var picker = generatorEl.querySelector('.tg-color-picker[data-channel="' + ch.key + '"]');
|
|
318
|
+
var hex = generatorEl.querySelector('.tg-hex-input[data-channel="' + ch.key + '"]');
|
|
319
|
+
var display = picker ? picker.parentElement.querySelector('.tg-picker-display') : null;
|
|
320
|
+
if (picker) picker.value = val;
|
|
321
|
+
if (hex) hex.value = val;
|
|
322
|
+
if (display) display.style.background = val;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ── Live update (when theme is already applied) ──
|
|
327
|
+
|
|
328
|
+
function liveUpdate() {
|
|
329
|
+
if (isActive && activeThemeId === editingId) {
|
|
330
|
+
var vars = generateTheme(currentColors);
|
|
331
|
+
applyCustomVars(vars);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ── Preview ──
|
|
336
|
+
|
|
337
|
+
function updatePreview() {
|
|
338
|
+
var vars = generateTheme(currentColors);
|
|
339
|
+
var bar = document.getElementById('tgPreviewBar');
|
|
340
|
+
var dot = document.getElementById('tgPreviewAccent');
|
|
341
|
+
var label = document.getElementById('tgPreviewText');
|
|
342
|
+
var chat = document.getElementById('tgPreviewChat');
|
|
343
|
+
var userMsg = document.getElementById('tgPreviewUser');
|
|
344
|
+
var aiMsg = document.getElementById('tgPreviewAssistant');
|
|
345
|
+
|
|
346
|
+
if (bar) bar.style.background = vars['--bg-secondary'];
|
|
347
|
+
if (dot) dot.style.background = vars['--accent'];
|
|
348
|
+
if (label) label.style.color = vars['--text-muted'];
|
|
349
|
+
if (chat) chat.style.background = vars['--bg'];
|
|
350
|
+
if (userMsg) { userMsg.style.background = vars['--user-bubble']; userMsg.style.color = vars['--text']; }
|
|
351
|
+
if (aiMsg) { aiMsg.style.background = vars['--assistant-bubble']; aiMsg.style.color = vars['--text']; }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ── Build UI ──
|
|
355
|
+
|
|
356
|
+
function buildUI() {
|
|
357
|
+
var container = document.getElementById('themeGeneratorContainer');
|
|
358
|
+
if (!container) return;
|
|
359
|
+
|
|
360
|
+
if (window.UplinkPremium && !window.UplinkPremium.isActive()) {
|
|
361
|
+
container.innerHTML = '';
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
migrateOldFormat();
|
|
366
|
+
var savedThemes = loadAllThemes();
|
|
367
|
+
activeThemeId = loadActiveId();
|
|
368
|
+
isActive = !!activeThemeId;
|
|
369
|
+
|
|
370
|
+
if (editingId) {
|
|
371
|
+
var ed = savedThemes.find(function(t) { return t.id === editingId; });
|
|
372
|
+
if (ed) { currentColors = assign({}, ed.colors); currentName = ed.name; }
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Saved themes list
|
|
376
|
+
var savedHTML = '';
|
|
377
|
+
if (savedThemes.length > 0) {
|
|
378
|
+
var items = savedThemes.map(function(t) {
|
|
379
|
+
var act = activeThemeId === t.id;
|
|
380
|
+
var edt = editingId === t.id;
|
|
381
|
+
return '<div class="tg-saved-item' + (act ? ' active' : '') + (edt ? ' editing' : '') + '" data-theme-id="' + t.id + '">' +
|
|
382
|
+
'<div class="tg-saved-swatch" style="background:' + t.colors.accent + '"></div>' +
|
|
383
|
+
'<span class="tg-saved-name">' + escapeHtml(t.name || 'Untitled') + '</span>' +
|
|
384
|
+
(act ? '<span class="tg-saved-badge">Active</span>' : '') +
|
|
385
|
+
'<div class="tg-saved-actions">' +
|
|
386
|
+
'<button class="tg-saved-btn" title="Apply" data-action="apply">✓</button>' +
|
|
387
|
+
'<button class="tg-saved-btn" title="Edit" data-action="edit">✎</button>' +
|
|
388
|
+
'<button class="tg-saved-btn" title="Delete" data-action="delete">✕</button>' +
|
|
389
|
+
'</div>' +
|
|
390
|
+
'</div>';
|
|
391
|
+
}).join('');
|
|
392
|
+
savedHTML = '<div class="tg-saved"><div class="tg-label">Saved Themes</div><div class="tg-saved-list">' + items + '</div></div>';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Channel pickers
|
|
396
|
+
var channelHTML = channels.map(function(ch) {
|
|
397
|
+
var val = currentColors[ch.key] || defaultColors[ch.key];
|
|
398
|
+
return '<div class="tg-channel">' +
|
|
399
|
+
'<div class="tg-channel-info">' +
|
|
400
|
+
'<span class="tg-channel-label">' + ch.label + '</span>' +
|
|
401
|
+
'<span class="tg-channel-desc">' + ch.desc + '</span>' +
|
|
402
|
+
'</div>' +
|
|
403
|
+
'<div class="tg-channel-controls">' +
|
|
404
|
+
'<div class="tg-picker-wrap">' +
|
|
405
|
+
'<input type="color" class="tg-color-picker" data-channel="' + ch.key + '" value="' + val + '">' +
|
|
406
|
+
'<div class="tg-picker-display" style="background:' + val + '"></div>' +
|
|
407
|
+
'</div>' +
|
|
408
|
+
'<input type="text" class="tg-hex-input" data-channel="' + ch.key + '" value="' + val + '" maxlength="7" spellcheck="false" autocomplete="off">' +
|
|
409
|
+
'</div>' +
|
|
410
|
+
'</div>';
|
|
411
|
+
}).join('');
|
|
412
|
+
|
|
413
|
+
// Presets
|
|
414
|
+
var presetsHTML = presets.map(function(p) {
|
|
415
|
+
return '<button class="tg-preset" data-preset="' + p.name + '" title="' + p.name + '">' +
|
|
416
|
+
'<span class="tg-preset-swatch" style="background:linear-gradient(135deg,' + p.bg + ' 40%,' + p.accent + ' 100%)"></span>' +
|
|
417
|
+
'<span class="tg-preset-name">' + p.name + '</span>' +
|
|
418
|
+
'</button>';
|
|
419
|
+
}).join('');
|
|
420
|
+
|
|
421
|
+
container.innerHTML =
|
|
422
|
+
'<div class="tg-section">' +
|
|
423
|
+
'<div class="tg-header">' +
|
|
424
|
+
'<span class="tg-title">Theme Studio</span>' +
|
|
425
|
+
'<span class="tg-badge">Premium</span>' +
|
|
426
|
+
'</div>' +
|
|
427
|
+
savedHTML +
|
|
428
|
+
'<div class="tg-editor">' +
|
|
429
|
+
'<div class="tg-name-row">' +
|
|
430
|
+
'<input type="text" class="tg-name-input" id="tgNameInput" placeholder="Theme name..." value="' + escapeAttr(currentName) + '" maxlength="24" spellcheck="false">' +
|
|
431
|
+
'</div>' +
|
|
432
|
+
'<div class="tg-presets-row">' +
|
|
433
|
+
'<div class="tg-label">Presets</div>' +
|
|
434
|
+
'<div class="tg-presets">' + presetsHTML + '</div>' +
|
|
435
|
+
'</div>' +
|
|
436
|
+
'<div class="tg-channels">' + channelHTML + '</div>' +
|
|
437
|
+
'<div class="tg-preview">' +
|
|
438
|
+
'<div class="tg-preview-bar" id="tgPreviewBar">' +
|
|
439
|
+
'<div class="tg-preview-dot" id="tgPreviewAccent"></div>' +
|
|
440
|
+
'<span class="tg-preview-label" id="tgPreviewText">Preview</span>' +
|
|
441
|
+
'</div>' +
|
|
442
|
+
'<div class="tg-preview-chat" id="tgPreviewChat">' +
|
|
443
|
+
'<div class="tg-preview-msg tg-preview-assistant" id="tgPreviewAssistant">Hey, how can I help?</div>' +
|
|
444
|
+
'<div class="tg-preview-msg tg-preview-user" id="tgPreviewUser">Looks great!</div>' +
|
|
445
|
+
'</div>' +
|
|
446
|
+
'</div>' +
|
|
447
|
+
'<div class="tg-actions">' +
|
|
448
|
+
'<button class="tg-btn tg-btn-primary" id="tgSaveBtn">' + (editingId ? 'Save Changes' : 'Save & Apply') + '</button>' +
|
|
449
|
+
'<button class="tg-btn tg-btn-secondary" id="tgCancelBtn">' + (editingId ? 'Cancel' : 'Reset') + '</button>' +
|
|
450
|
+
'</div>' +
|
|
451
|
+
'</div>' +
|
|
452
|
+
'</div>';
|
|
453
|
+
|
|
454
|
+
generatorEl = container;
|
|
455
|
+
setupEvents();
|
|
456
|
+
updatePreview();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ── Events ──
|
|
460
|
+
|
|
461
|
+
function setupEvents() {
|
|
462
|
+
if (!generatorEl) return;
|
|
463
|
+
|
|
464
|
+
// Color pickers
|
|
465
|
+
generatorEl.querySelectorAll('.tg-color-picker').forEach(function(picker) {
|
|
466
|
+
picker.addEventListener('input', function() {
|
|
467
|
+
var ch = picker.dataset.channel;
|
|
468
|
+
currentColors[ch] = picker.value;
|
|
469
|
+
var hex = generatorEl.querySelector('.tg-hex-input[data-channel="' + ch + '"]');
|
|
470
|
+
if (hex) hex.value = picker.value;
|
|
471
|
+
var disp = picker.parentElement.querySelector('.tg-picker-display');
|
|
472
|
+
if (disp) disp.style.background = picker.value;
|
|
473
|
+
updatePreview();
|
|
474
|
+
liveUpdate();
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Hex inputs
|
|
479
|
+
generatorEl.querySelectorAll('.tg-hex-input').forEach(function(input) {
|
|
480
|
+
input.addEventListener('input', function() {
|
|
481
|
+
var val = input.value.trim();
|
|
482
|
+
if (/^#[0-9a-fA-F]{6}$/.test(val)) {
|
|
483
|
+
var ch = input.dataset.channel;
|
|
484
|
+
currentColors[ch] = val;
|
|
485
|
+
var picker = generatorEl.querySelector('.tg-color-picker[data-channel="' + ch + '"]');
|
|
486
|
+
if (picker) picker.value = val;
|
|
487
|
+
var disp = input.closest('.tg-channel-controls').querySelector('.tg-picker-display');
|
|
488
|
+
if (disp) disp.style.background = val;
|
|
489
|
+
updatePreview();
|
|
490
|
+
liveUpdate();
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Presets
|
|
496
|
+
generatorEl.querySelectorAll('.tg-preset').forEach(function(btn) {
|
|
497
|
+
btn.addEventListener('click', function() {
|
|
498
|
+
var p = presets.find(function(x) { return x.name === btn.dataset.preset; });
|
|
499
|
+
if (!p) return;
|
|
500
|
+
currentColors = { accent:p.accent, background:p.bg, userBubble:p.user, aiBubble:p.ai, text:p.text, stars:p.stars };
|
|
501
|
+
var nameInput = document.getElementById('tgNameInput');
|
|
502
|
+
if (nameInput && !nameInput.value.trim()) nameInput.value = p.name;
|
|
503
|
+
syncPickersToColors();
|
|
504
|
+
updatePreview();
|
|
505
|
+
liveUpdate();
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Name
|
|
510
|
+
var nameInput = document.getElementById('tgNameInput');
|
|
511
|
+
if (nameInput) nameInput.addEventListener('input', function() { currentName = nameInput.value; });
|
|
512
|
+
|
|
513
|
+
// Save & Apply
|
|
514
|
+
var saveBtn = document.getElementById('tgSaveBtn');
|
|
515
|
+
if (saveBtn) {
|
|
516
|
+
saveBtn.addEventListener('click', function() {
|
|
517
|
+
var themes = loadAllThemes();
|
|
518
|
+
var name = (currentName || '').trim() || 'Custom ' + (themes.length + 1);
|
|
519
|
+
|
|
520
|
+
if (editingId) {
|
|
521
|
+
var idx = -1;
|
|
522
|
+
for (var i = 0; i < themes.length; i++) { if (themes[i].id === editingId) { idx = i; break; } }
|
|
523
|
+
if (idx >= 0) { themes[idx].name = name; themes[idx].colors = assign({}, currentColors); }
|
|
524
|
+
saveAllThemes(themes);
|
|
525
|
+
applyThemeById(editingId);
|
|
526
|
+
} else {
|
|
527
|
+
var newT = { id: generateId(), name: name, colors: assign({}, currentColors), createdAt: Date.now() };
|
|
528
|
+
themes.push(newT);
|
|
529
|
+
saveAllThemes(themes);
|
|
530
|
+
applyThemeById(newT.id);
|
|
531
|
+
}
|
|
532
|
+
editingId = null;
|
|
533
|
+
currentName = '';
|
|
534
|
+
currentColors = assign({}, defaultColors);
|
|
535
|
+
buildUI();
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Cancel / Reset
|
|
540
|
+
var cancelBtn = document.getElementById('tgCancelBtn');
|
|
541
|
+
if (cancelBtn) {
|
|
542
|
+
cancelBtn.addEventListener('click', function() {
|
|
543
|
+
if (editingId) {
|
|
544
|
+
editingId = null;
|
|
545
|
+
currentName = '';
|
|
546
|
+
currentColors = assign({}, defaultColors);
|
|
547
|
+
buildUI();
|
|
548
|
+
} else {
|
|
549
|
+
clearCustomVars();
|
|
550
|
+
isActive = false;
|
|
551
|
+
activeThemeId = null;
|
|
552
|
+
saveActiveId(null);
|
|
553
|
+
document.documentElement.setAttribute('data-theme', 'midnight');
|
|
554
|
+
var sel = document.getElementById('themeSelect');
|
|
555
|
+
if (sel) { removeCustomOption(); sel.value = 'midnight'; }
|
|
556
|
+
var storage = window.UplinkStorage;
|
|
557
|
+
if (storage) storage.saveSettings({ theme: 'midnight' });
|
|
558
|
+
currentColors = assign({}, defaultColors);
|
|
559
|
+
currentName = '';
|
|
560
|
+
editingId = null;
|
|
561
|
+
buildUI();
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Saved theme actions (delegation)
|
|
567
|
+
var savedList = generatorEl.querySelector('.tg-saved-list');
|
|
568
|
+
if (savedList) {
|
|
569
|
+
savedList.addEventListener('click', function(e) {
|
|
570
|
+
var btn = e.target.closest('[data-action]');
|
|
571
|
+
if (!btn) return;
|
|
572
|
+
var item = btn.closest('.tg-saved-item');
|
|
573
|
+
if (!item) return;
|
|
574
|
+
var id = item.dataset.themeId;
|
|
575
|
+
var action = btn.dataset.action;
|
|
576
|
+
|
|
577
|
+
if (action === 'apply') {
|
|
578
|
+
applyThemeById(id);
|
|
579
|
+
buildUI();
|
|
580
|
+
} else if (action === 'edit') {
|
|
581
|
+
editingId = id;
|
|
582
|
+
buildUI();
|
|
583
|
+
} else if (action === 'delete') {
|
|
584
|
+
var themes = loadAllThemes();
|
|
585
|
+
themes = themes.filter(function(t) { return t.id !== id; });
|
|
586
|
+
saveAllThemes(themes);
|
|
587
|
+
if (activeThemeId === id) {
|
|
588
|
+
clearCustomVars();
|
|
589
|
+
isActive = false;
|
|
590
|
+
activeThemeId = null;
|
|
591
|
+
saveActiveId(null);
|
|
592
|
+
document.documentElement.setAttribute('data-theme', 'midnight');
|
|
593
|
+
var sel = document.getElementById('themeSelect');
|
|
594
|
+
if (sel) { removeCustomOption(); sel.value = 'midnight'; }
|
|
595
|
+
var storage = window.UplinkStorage;
|
|
596
|
+
if (storage) storage.saveSettings({ theme: 'midnight' });
|
|
597
|
+
}
|
|
598
|
+
if (editingId === id) {
|
|
599
|
+
editingId = null;
|
|
600
|
+
currentColors = assign({}, defaultColors);
|
|
601
|
+
currentName = '';
|
|
602
|
+
}
|
|
603
|
+
buildUI();
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ── Init ──
|
|
610
|
+
|
|
611
|
+
function init() {
|
|
612
|
+
if (window.UplinkPremium && !window.UplinkPremium.isActive()) {
|
|
613
|
+
window.addEventListener('uplink:premiumChange', function(e) {
|
|
614
|
+
if (e.detail && e.detail.active) buildUI();
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
buildUI();
|
|
619
|
+
|
|
620
|
+
// Restore active custom theme on load
|
|
621
|
+
var id = loadActiveId();
|
|
622
|
+
if (id) {
|
|
623
|
+
var themes = loadAllThemes();
|
|
624
|
+
var theme = themes.find(function(t) { return t.id === id; });
|
|
625
|
+
if (theme) {
|
|
626
|
+
var vars = generateTheme(theme.colors);
|
|
627
|
+
applyCustomVars(vars);
|
|
628
|
+
document.documentElement.setAttribute('data-theme', 'custom');
|
|
629
|
+
isActive = true;
|
|
630
|
+
activeThemeId = id;
|
|
631
|
+
ensureCustomOption(theme.name);
|
|
632
|
+
var sel = document.getElementById('themeSelect');
|
|
633
|
+
if (sel) sel.value = 'custom';
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Listen for built-in theme changes — deactivate custom
|
|
639
|
+
window.addEventListener('uplink:themeChange', function(e) {
|
|
640
|
+
if (e.detail && e.detail.theme !== 'custom' && isActive) {
|
|
641
|
+
clearCustomVars();
|
|
642
|
+
isActive = false;
|
|
643
|
+
activeThemeId = null;
|
|
644
|
+
saveActiveId(null);
|
|
645
|
+
var applyBtn = document.getElementById('tgSaveBtn');
|
|
646
|
+
if (applyBtn) { applyBtn.textContent = 'Save & Apply'; }
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Expose API
|
|
651
|
+
export const UplinkThemeGenerator = {
|
|
652
|
+
build: buildUI,
|
|
653
|
+
isActive: function() { return isActive; },
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
import { UplinkCore } from './core.js';
|
|
657
|
+
|
|
658
|
+
// Backward compat: assign to window
|
|
659
|
+
window.UplinkThemeGenerator = UplinkThemeGenerator;
|
|
660
|
+
|
|
661
|
+
UplinkCore.registerModule('themeGenerator', init);
|