clay-server 2.33.0 → 2.33.1
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-notifications.js +17 -0
- package/lib/project-sessions.js +27 -0
- package/lib/public/app.js +1 -3
- package/lib/public/css/messages.css +0 -63
- package/lib/public/css/notifications-center.css +87 -27
- package/lib/public/css/sidebar.css +24 -0
- package/lib/public/css/user-settings.css +537 -147
- package/lib/public/index.html +52 -11
- package/lib/public/modules/app-connection.js +0 -39
- package/lib/public/modules/app-messages.js +18 -4
- package/lib/public/modules/app-notifications.js +133 -20
- package/lib/public/modules/app-panels.js +1 -1
- package/lib/public/modules/app-rendering.js +0 -75
- package/lib/public/modules/input.js +1 -0
- package/lib/public/modules/profile.js +1 -31
- package/lib/public/modules/stt.js +0 -14
- package/lib/public/modules/theme.js +19 -0
- package/lib/public/modules/user-settings.js +266 -21
- package/lib/sdk-bridge.js +118 -12
- package/lib/sdk-message-processor.js +25 -4
- package/lib/server-settings.js +13 -0
- package/lib/yoke/codex-app-server.js +3 -3
- package/lib/yoke/index.js +81 -11
- package/package.json +1 -1
package/lib/public/index.html
CHANGED
|
@@ -858,6 +858,7 @@
|
|
|
858
858
|
</div>
|
|
859
859
|
</div>
|
|
860
860
|
<div class="user-island-actions">
|
|
861
|
+
<button id="user-theme-toggle-btn" title="Switch theme" aria-label="Switch theme"><i data-lucide="moon"></i></button>
|
|
861
862
|
<button id="user-settings-btn" title="User settings"><i data-lucide="settings"></i></button>
|
|
862
863
|
</div>
|
|
863
864
|
</div>
|
|
@@ -928,23 +929,48 @@
|
|
|
928
929
|
<div class="us-section" data-section="us-account">
|
|
929
930
|
<h2>Account</h2>
|
|
930
931
|
<div class="settings-card">
|
|
931
|
-
<div class="
|
|
932
|
-
<div>
|
|
933
|
-
<
|
|
934
|
-
<
|
|
932
|
+
<div class="us-account-hero">
|
|
933
|
+
<div class="us-account-avatar" aria-hidden="true">
|
|
934
|
+
<img id="us-account-avatar-img" class="us-account-avatar-img hidden" alt="">
|
|
935
|
+
<span id="us-account-avatar-fallback" class="us-account-avatar-fallback">?</span>
|
|
936
|
+
</div>
|
|
937
|
+
<div class="us-account-hero-copy">
|
|
938
|
+
<div class="us-account-name" id="us-account-name">-</div>
|
|
939
|
+
<div class="us-account-subline" id="us-account-subline">-</div>
|
|
940
|
+
<div class="settings-hint">Your profile is stored on this server and used across sessions.</div>
|
|
935
941
|
</div>
|
|
936
942
|
</div>
|
|
937
943
|
</div>
|
|
938
944
|
<h3>Security</h3>
|
|
939
945
|
<div class="settings-card">
|
|
940
|
-
<div class="settings-field">
|
|
941
|
-
<
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
<input type="password" class="us-text-input us-pin-input" id="us-pin-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="Enter new PIN" autocomplete="off">
|
|
945
|
-
<button class="us-btn" id="us-pin-save" disabled>Change PIN</button>
|
|
946
|
+
<div class="settings-field-row">
|
|
947
|
+
<div>
|
|
948
|
+
<label class="settings-label">PIN</label>
|
|
949
|
+
<div class="settings-hint">Protect the web UI with a 6-digit PIN.</div>
|
|
946
950
|
</div>
|
|
947
|
-
<
|
|
951
|
+
<button class="settings-btn-sm" id="us-pin-set-btn">Set PIN</button>
|
|
952
|
+
</div>
|
|
953
|
+
<div class="settings-field hidden" id="us-pin-form">
|
|
954
|
+
<div class="settings-pin-form-title" id="us-pin-form-title">Change PIN</div>
|
|
955
|
+
<div class="settings-pin-current-row" id="us-pin-current-row">
|
|
956
|
+
<label class="settings-label" for="us-pin-current-input">Current PIN</label>
|
|
957
|
+
<input type="password" class="us-text-input us-pin-input" id="us-pin-current-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="000000" autocomplete="off">
|
|
958
|
+
</div>
|
|
959
|
+
<div class="settings-pin-row">
|
|
960
|
+
<div class="settings-pin-field">
|
|
961
|
+
<label class="settings-label" for="us-pin-input">New PIN</label>
|
|
962
|
+
<input type="password" class="us-text-input us-pin-input" id="us-pin-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="000000" autocomplete="off">
|
|
963
|
+
</div>
|
|
964
|
+
<div class="settings-pin-field">
|
|
965
|
+
<label class="settings-label" for="us-pin-confirm-input">Confirm PIN</label>
|
|
966
|
+
<input type="password" class="us-text-input us-pin-input" id="us-pin-confirm-input" maxlength="6" inputmode="numeric" pattern="[0-9]*" placeholder="000000" autocomplete="off">
|
|
967
|
+
</div>
|
|
968
|
+
</div>
|
|
969
|
+
<div class="settings-pin-actions-row">
|
|
970
|
+
<button class="us-btn" id="us-pin-save" disabled>Save</button>
|
|
971
|
+
<button class="settings-btn-sm settings-btn-ghost" id="us-pin-cancel">Cancel</button>
|
|
972
|
+
</div>
|
|
973
|
+
<div class="settings-hint settings-pin-error hidden" id="us-pin-msg"></div>
|
|
948
974
|
</div>
|
|
949
975
|
</div>
|
|
950
976
|
</div>
|
|
@@ -987,6 +1013,21 @@
|
|
|
987
1013
|
<!-- Chat section -->
|
|
988
1014
|
<div class="us-section" data-section="us-chat">
|
|
989
1015
|
<h2>Chat</h2>
|
|
1016
|
+
<div class="settings-card">
|
|
1017
|
+
<div class="settings-field">
|
|
1018
|
+
<label class="settings-label" for="us-lang-select">Voice Input Language</label>
|
|
1019
|
+
<div class="settings-hint">Used by speech-to-text when you dictate messages.</div>
|
|
1020
|
+
<select id="us-lang-select" class="settings-select us-lang-select">
|
|
1021
|
+
<option value="en-US">English</option>
|
|
1022
|
+
<option value="ko-KR">Korean</option>
|
|
1023
|
+
<option value="ja-JP">Japanese</option>
|
|
1024
|
+
<option value="zh-CN">Chinese</option>
|
|
1025
|
+
<option value="es-ES">Spanish</option>
|
|
1026
|
+
<option value="fr-FR">French</option>
|
|
1027
|
+
<option value="de-DE">German</option>
|
|
1028
|
+
</select>
|
|
1029
|
+
</div>
|
|
1030
|
+
</div>
|
|
990
1031
|
<div class="settings-card">
|
|
991
1032
|
<label class="settings-toggle-row">
|
|
992
1033
|
<div>
|
|
@@ -7,19 +7,15 @@ import { getStatusDot, getSendBtn } from './dom-refs.js';
|
|
|
7
7
|
import { setSendBtnMode, blinkIO, setActivity } from './app-favicon.js';
|
|
8
8
|
import { startLogoAnimation, stopLogoAnimation } from './ascii-logo.js';
|
|
9
9
|
import { hasSendableContent } from './input.js';
|
|
10
|
-
import { isNotifAlertEnabled } from './notifications.js';
|
|
11
10
|
import { processMessage } from './app-messages.js';
|
|
12
11
|
import { flushPendingExtMessages } from './app-misc.js';
|
|
13
12
|
import { resetTerminals } from './terminal.js';
|
|
14
13
|
import { closeDmUserPicker } from './sidebar-mates.js';
|
|
15
14
|
import { openDm } from './app-dm.js';
|
|
16
15
|
|
|
17
|
-
var wasConnected = false;
|
|
18
16
|
var reconnectTimer = null;
|
|
19
17
|
var reconnectDelay = 1000;
|
|
20
18
|
var connectTimeoutId = null;
|
|
21
|
-
var disconnectNotifTimer = null;
|
|
22
|
-
var disconnectNotifShown = false;
|
|
23
19
|
var connectOverlay = null;
|
|
24
20
|
|
|
25
21
|
export function initConnection() {
|
|
@@ -162,24 +158,6 @@ export function connect() {
|
|
|
162
158
|
|
|
163
159
|
newWs.onopen = function () {
|
|
164
160
|
if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
|
|
165
|
-
// Cancel pending "connection lost" notification if reconnected quickly
|
|
166
|
-
if (disconnectNotifTimer) {
|
|
167
|
-
clearTimeout(disconnectNotifTimer);
|
|
168
|
-
disconnectNotifTimer = null;
|
|
169
|
-
}
|
|
170
|
-
// Only show "restored" notification if "lost" was actually shown
|
|
171
|
-
var isMobileDevice = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
172
|
-
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
173
|
-
if (wasConnected && disconnectNotifShown && !isMobileDevice && isNotifAlertEnabled() && !document.hasFocus() && "serviceWorker" in navigator && Notification.permission === "granted") {
|
|
174
|
-
navigator.serviceWorker.ready.then(function (reg) {
|
|
175
|
-
return reg.showNotification("Clay", {
|
|
176
|
-
body: "Server connection restored",
|
|
177
|
-
tag: "claude-disconnect",
|
|
178
|
-
});
|
|
179
|
-
}).catch(function () {});
|
|
180
|
-
}
|
|
181
|
-
disconnectNotifShown = false;
|
|
182
|
-
wasConnected = true;
|
|
183
161
|
setStatus("connected");
|
|
184
162
|
reconnectDelay = 1000;
|
|
185
163
|
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
@@ -200,23 +178,6 @@ export function connect() {
|
|
|
200
178
|
closeDmUserPicker();
|
|
201
179
|
setStatus("disconnected");
|
|
202
180
|
setActivity(null);
|
|
203
|
-
// Delay "connection lost" notification by 5s to suppress brief disconnects
|
|
204
|
-
if (!disconnectNotifTimer) {
|
|
205
|
-
disconnectNotifTimer = setTimeout(function () {
|
|
206
|
-
disconnectNotifTimer = null;
|
|
207
|
-
disconnectNotifShown = true;
|
|
208
|
-
var isMobileDevice = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
209
|
-
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
210
|
-
if (!isMobileDevice && isNotifAlertEnabled() && !document.hasFocus() && "serviceWorker" in navigator && Notification.permission === "granted") {
|
|
211
|
-
navigator.serviceWorker.ready.then(function (reg) {
|
|
212
|
-
return reg.showNotification("Clay", {
|
|
213
|
-
body: "Server connection lost",
|
|
214
|
-
tag: "claude-disconnect",
|
|
215
|
-
});
|
|
216
|
-
}).catch(function () {});
|
|
217
|
-
}
|
|
218
|
-
}, 5000);
|
|
219
|
-
}
|
|
220
181
|
scheduleReconnect();
|
|
221
182
|
};
|
|
222
183
|
|
|
@@ -37,7 +37,7 @@ import { handleMcpServersState } from './mcp-ui.js';
|
|
|
37
37
|
import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, isSchedulerOpen, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './scheduler.js';
|
|
38
38
|
|
|
39
39
|
// --- App module imports ---
|
|
40
|
-
import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage,
|
|
40
|
+
import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, showSuggestionChips } from './app-rendering.js';
|
|
41
41
|
import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot, updateCrossProjectBlink } from './app-favicon.js';
|
|
42
42
|
import { setStatus } from './app-connection.js';
|
|
43
43
|
import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
|
|
@@ -343,7 +343,7 @@ export function processMessage(msg) {
|
|
|
343
343
|
} else {
|
|
344
344
|
_miUpdate.currentModel = store.get('currentModel');
|
|
345
345
|
}
|
|
346
|
-
if (msg.vendor) _miUpdate.currentVendor = msg.vendor;
|
|
346
|
+
if (msg.vendor && !store.get('vendorSelectionLocked')) _miUpdate.currentVendor = msg.vendor;
|
|
347
347
|
if (msg.availableVendors) _miUpdate.availableVendors = msg.availableVendors;
|
|
348
348
|
if (msg.installedVendors) _miUpdate.installedVendors = msg.installedVendors;
|
|
349
349
|
store.set(_miUpdate);
|
|
@@ -506,10 +506,16 @@ export function processMessage(msg) {
|
|
|
506
506
|
}
|
|
507
507
|
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, vendorCapabilities: msg.capabilities || {} });
|
|
508
508
|
if (msg.vendor) {
|
|
509
|
-
store.
|
|
509
|
+
if (!store.get('vendorSelectionLocked') || msg.hasHistory) {
|
|
510
|
+
store.set({ currentVendor: msg.vendor });
|
|
511
|
+
}
|
|
512
|
+
if (msg.hasHistory) {
|
|
513
|
+
store.set({ vendorSelectionLocked: false });
|
|
514
|
+
}
|
|
510
515
|
} else if (msg.hasHistory) {
|
|
511
516
|
// Existing session without explicit vendor: reset to claude
|
|
512
517
|
store.set({ currentVendor: "claude" });
|
|
518
|
+
store.set({ vendorSelectionLocked: false });
|
|
513
519
|
} else if (!msg.hasHistory) {
|
|
514
520
|
// New session without vendor: use mate's default vendor if in DM mode
|
|
515
521
|
var _dmTarget = store.get('dmTargetUser');
|
|
@@ -523,6 +529,9 @@ export function processMessage(msg) {
|
|
|
523
529
|
}
|
|
524
530
|
}
|
|
525
531
|
}
|
|
532
|
+
if (!msg.hasHistory && !msg.vendor) {
|
|
533
|
+
// Preserve explicit pre-message vendor choice on brand-new sessions.
|
|
534
|
+
}
|
|
526
535
|
// Show vendor toggle only for new sessions (no history)
|
|
527
536
|
var _vtw = document.getElementById("vendor-toggle-wrap");
|
|
528
537
|
if (_vtw) {
|
|
@@ -914,7 +923,12 @@ export function processMessage(msg) {
|
|
|
914
923
|
case "auth_required":
|
|
915
924
|
removeMatePreThinking();
|
|
916
925
|
setActivity(null);
|
|
917
|
-
|
|
926
|
+
stopThinking();
|
|
927
|
+
markAllToolsDone();
|
|
928
|
+
closeToolGroup();
|
|
929
|
+
appendDelta((msg.text || "Authentication required.") + "\n");
|
|
930
|
+
setStatus("connected");
|
|
931
|
+
if (!store.get('loopActive')) enableMainInput();
|
|
918
932
|
break;
|
|
919
933
|
|
|
920
934
|
case "rate_limit":
|
|
@@ -10,6 +10,7 @@ import { openDm } from './app-dm.js';
|
|
|
10
10
|
import { getCachedProjects } from './app-projects.js';
|
|
11
11
|
import { switchProject } from './app-projects.js';
|
|
12
12
|
import { mateAvatarUrl } from './avatar.js';
|
|
13
|
+
import { openTerminal } from './terminal.js';
|
|
13
14
|
var notifications = [];
|
|
14
15
|
var unreadCount = 0;
|
|
15
16
|
var bannerContainer = null;
|
|
@@ -21,6 +22,8 @@ var badgeEl = null;
|
|
|
21
22
|
// per-banner-instance and doesn't need to persist. The next server push
|
|
22
23
|
// (next hour) acts as a fresh ping.
|
|
23
24
|
var pendingUpdateMsg = null;
|
|
25
|
+
var activeAuthRequiredMsg = null;
|
|
26
|
+
var authReminderVisible = false;
|
|
24
27
|
|
|
25
28
|
// ========================================================
|
|
26
29
|
// Init
|
|
@@ -54,7 +57,14 @@ function showAllBanners() {
|
|
|
54
57
|
// Re-add update banner if present (may be suppressed by recent dismiss)
|
|
55
58
|
if (pendingUpdateMsg) showUpdateBanner(pendingUpdateMsg);
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
for (var i = 0; i < notifications.length; i++) {
|
|
61
|
+
showBanner(notifications[i], false);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (activeAuthRequiredMsg) showAuthRequiredBanner(activeAuthRequiredMsg);
|
|
65
|
+
if (authReminderVisible) showLoginReminderBanner();
|
|
66
|
+
|
|
67
|
+
// Check if any banner actually got rendered (update/auth banners can be suppressed)
|
|
58
68
|
var hasVisibleBanner = bannerContainer.children.length > 0;
|
|
59
69
|
if (notifications.length === 0 && !hasVisibleBanner) {
|
|
60
70
|
showBanner({
|
|
@@ -64,11 +74,6 @@ function showAllBanners() {
|
|
|
64
74
|
body: "",
|
|
65
75
|
slug: "",
|
|
66
76
|
}, 3000);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
for (var i = 0; i < notifications.length; i++) {
|
|
71
|
-
showBanner(notifications[i], false);
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -80,20 +85,26 @@ function showBanner(notif, autoDismissMs) {
|
|
|
80
85
|
if (!bannerContainer) return;
|
|
81
86
|
|
|
82
87
|
var isEmpty = notif.id === "_empty";
|
|
83
|
-
var projectIcon = isEmpty ? null : getProjectIcon(notif.slug);
|
|
84
|
-
var projectName = isEmpty ? "" : getProjectName(notif.slug);
|
|
85
88
|
var isPermission = notif.type === "permission_request" && notif.meta && notif.meta.requestId;
|
|
89
|
+
var isAuthRequired = notif.type === "auth_required";
|
|
90
|
+
var projectIcon = isEmpty ? null : getProjectIcon(notif.slug);
|
|
91
|
+
var projectName = isEmpty ? "" : (isAuthRequired ? "CLAY" : getProjectName(notif.slug));
|
|
86
92
|
var mate = isEmpty ? null : getMateForNotification(notif);
|
|
87
93
|
|
|
88
94
|
var banner = document.createElement("div");
|
|
89
|
-
banner.className = "notif-banner" + (isPermission ? " notif-banner-permission" : "");
|
|
95
|
+
banner.className = "notif-banner" + (isAuthRequired ? " notif-banner-update notif-banner-auth" : isPermission ? " notif-banner-permission" : "");
|
|
90
96
|
if (!isEmpty) banner.setAttribute("data-notif-id", notif.id);
|
|
97
|
+
if (isAuthRequired) banner.setAttribute("data-auth-banner", "true");
|
|
91
98
|
|
|
92
99
|
var iconHtmlStr = mate
|
|
93
100
|
? '<img class="notif-banner-avatar" src="' + escapeHtml(mateAvatarUrl(mate, 32)) + '" alt="' + escapeHtml(mate.displayName || mate.name || "Mate") + '">'
|
|
94
|
-
:
|
|
101
|
+
: isAuthRequired
|
|
102
|
+
? '<img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px">'
|
|
103
|
+
: projectIcon
|
|
95
104
|
? '<span class="notif-banner-emoji">' + projectIcon + '</span>'
|
|
96
|
-
:
|
|
105
|
+
: isEmpty
|
|
106
|
+
? '<img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px">'
|
|
107
|
+
: iconHtml("folder");
|
|
97
108
|
|
|
98
109
|
// Format permission title as "Can I ..." style
|
|
99
110
|
if (isPermission && notif.meta) {
|
|
@@ -112,6 +123,11 @@ function showBanner(notif, autoDismissMs) {
|
|
|
112
123
|
'<button class="notif-banner-deny">No</button>' +
|
|
113
124
|
'<button class="notif-banner-goto" title="Go to session">' + iconHtml("external-link") + '</button>' +
|
|
114
125
|
'</div>';
|
|
126
|
+
} else if (isAuthRequired) {
|
|
127
|
+
actionsHtml =
|
|
128
|
+
'<div class="notif-banner-actions">' +
|
|
129
|
+
'<button class="notif-banner-auth-login notif-banner-update-now">Open terminal & log in</button>' +
|
|
130
|
+
'</div>';
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
banner.innerHTML =
|
|
@@ -133,19 +149,22 @@ function showBanner(notif, autoDismissMs) {
|
|
|
133
149
|
});
|
|
134
150
|
|
|
135
151
|
if (!isEmpty) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
if (!isAuthRequired) {
|
|
153
|
+
// Click banner body -> navigate + dismiss
|
|
154
|
+
banner.addEventListener("click", function (e) {
|
|
155
|
+
if (e.target.closest(".notif-banner-close")) return;
|
|
156
|
+
removeBanner(banner);
|
|
157
|
+
dismissNotif(notif.id);
|
|
158
|
+
navigateToNotification(notif);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
143
161
|
|
|
144
162
|
// Close button -> dismiss
|
|
145
163
|
var closeBtn = banner.querySelector(".notif-banner-close");
|
|
146
164
|
if (closeBtn) {
|
|
147
165
|
closeBtn.addEventListener("click", function (e) {
|
|
148
166
|
e.stopPropagation();
|
|
167
|
+
if (isAuthRequired) activeAuthRequiredMsg = null;
|
|
149
168
|
removeBanner(banner);
|
|
150
169
|
dismissNotif(notif.id);
|
|
151
170
|
});
|
|
@@ -191,6 +210,21 @@ function showBanner(notif, autoDismissMs) {
|
|
|
191
210
|
});
|
|
192
211
|
}
|
|
193
212
|
}
|
|
213
|
+
|
|
214
|
+
if (isAuthRequired) {
|
|
215
|
+
var loginBtn = banner.querySelector(".notif-banner-auth-login");
|
|
216
|
+
if (loginBtn) {
|
|
217
|
+
loginBtn.addEventListener("click", function (e) {
|
|
218
|
+
e.stopPropagation();
|
|
219
|
+
activeAuthRequiredMsg = null;
|
|
220
|
+
removeBanner(banner);
|
|
221
|
+
dismissNotif(notif.id);
|
|
222
|
+
var authMeta = notif.meta || {};
|
|
223
|
+
startLoginCommand(authMeta.loginCommand || ((authMeta.vendor || "claude") === "codex" ? "codex login --device-auth" : "claude login"));
|
|
224
|
+
showLoginReminderBanner();
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
194
228
|
}
|
|
195
229
|
|
|
196
230
|
// Auto-dismiss (number = ms, false = stay)
|
|
@@ -201,6 +235,85 @@ function showBanner(notif, autoDismissMs) {
|
|
|
201
235
|
}
|
|
202
236
|
}
|
|
203
237
|
|
|
238
|
+
function startLoginCommand(loginCommand) {
|
|
239
|
+
var ws = getWs();
|
|
240
|
+
if (!ws || ws.readyState !== 1) return;
|
|
241
|
+
var termCommand = (loginCommand || "claude login") + "\n";
|
|
242
|
+
store.set({ pendingTermCommand: termCommand });
|
|
243
|
+
ws.send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
|
|
244
|
+
openTerminal();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function showLoginReminderBanner() {
|
|
248
|
+
if (!bannerContainer) return;
|
|
249
|
+
authReminderVisible = true;
|
|
250
|
+
var existing = bannerContainer.querySelector('[data-auth-reminder="true"]');
|
|
251
|
+
if (existing) removeBanner(existing);
|
|
252
|
+
var banner = document.createElement("div");
|
|
253
|
+
banner.className = "notif-banner notif-banner-update";
|
|
254
|
+
banner.setAttribute("data-notif-id", "_auth_reminder");
|
|
255
|
+
banner.setAttribute("data-auth-reminder", "true");
|
|
256
|
+
banner.innerHTML =
|
|
257
|
+
'<div class="notif-banner-icon">' + iconHtml("check-circle") + '</div>' +
|
|
258
|
+
'<div class="notif-banner-body">' +
|
|
259
|
+
'<div class="notif-banner-project">CLAY</div>' +
|
|
260
|
+
'<div class="notif-banner-title">Login started</div>' +
|
|
261
|
+
'<div class="notif-banner-text">After the login completes, open a new session so Clay picks up the fresh auth state.</div>' +
|
|
262
|
+
'<div class="notif-banner-actions">' +
|
|
263
|
+
'<button class="notif-banner-new-session notif-banner-update-now">Open new session</button>' +
|
|
264
|
+
'</div>' +
|
|
265
|
+
'</div>' +
|
|
266
|
+
'<button class="notif-banner-close">' + iconHtml("x") + '</button>';
|
|
267
|
+
|
|
268
|
+
bannerContainer.appendChild(banner);
|
|
269
|
+
refreshIcons();
|
|
270
|
+
|
|
271
|
+
requestAnimationFrame(function () {
|
|
272
|
+
banner.classList.add("show");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
var newSessionBtn = banner.querySelector(".notif-banner-new-session");
|
|
276
|
+
if (newSessionBtn) {
|
|
277
|
+
newSessionBtn.addEventListener("click", function (e) {
|
|
278
|
+
e.stopPropagation();
|
|
279
|
+
authReminderVisible = false;
|
|
280
|
+
var ws = getWs();
|
|
281
|
+
if (ws && ws.readyState === 1) {
|
|
282
|
+
ws.send(JSON.stringify({ type: "new_session" }));
|
|
283
|
+
}
|
|
284
|
+
removeBanner(banner);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
var closeBtn = banner.querySelector(".notif-banner-close");
|
|
289
|
+
if (closeBtn) {
|
|
290
|
+
closeBtn.addEventListener("click", function (e) {
|
|
291
|
+
e.stopPropagation();
|
|
292
|
+
authReminderVisible = false;
|
|
293
|
+
removeBanner(banner);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function showAuthRequiredBanner(msg) {
|
|
299
|
+
if (!bannerContainer) return;
|
|
300
|
+
var vendor = (msg && (msg.vendor || (msg.meta && msg.meta.vendor))) || "claude";
|
|
301
|
+
var loginCommand = (msg && (msg.loginCommand || (msg.meta && msg.meta.loginCommand))) || (vendor === "codex" ? "codex login --device-auth" : "claude login");
|
|
302
|
+
activeAuthRequiredMsg = Object.assign({}, msg || {}, {
|
|
303
|
+
id: (msg && msg.id) || ("_auth_" + Date.now()),
|
|
304
|
+
type: "auth_required",
|
|
305
|
+
vendor: vendor,
|
|
306
|
+
loginCommand: loginCommand,
|
|
307
|
+
meta: Object.assign({}, msg && msg.meta ? msg.meta : {}, {
|
|
308
|
+
vendor: vendor,
|
|
309
|
+
loginCommand: loginCommand,
|
|
310
|
+
}),
|
|
311
|
+
});
|
|
312
|
+
var existing = bannerContainer.querySelector('[data-auth-banner="true"]');
|
|
313
|
+
if (existing) removeBanner(existing);
|
|
314
|
+
showBanner(activeAuthRequiredMsg, false);
|
|
315
|
+
}
|
|
316
|
+
|
|
204
317
|
function removeBanner(banner) {
|
|
205
318
|
if (!banner || !banner.parentNode) return;
|
|
206
319
|
banner.classList.remove("show");
|
|
@@ -306,7 +419,7 @@ export function handleNotificationCreated(msg) {
|
|
|
306
419
|
// Auto-dismiss if it's for the session the user is currently viewing
|
|
307
420
|
var activeSession = store.get('activeSessionId') || null;
|
|
308
421
|
console.log("[notif] created:", notif.type, "sessionId=" + notif.sessionId + "(" + typeof notif.sessionId + ")", "active=" + activeSession + "(" + typeof activeSession + ")", "match=" + (notif.sessionId == activeSession));
|
|
309
|
-
if (notif.sessionId && String(notif.sessionId) === String(activeSession)) {
|
|
422
|
+
if (notif.type !== "auth_required" && notif.sessionId && String(notif.sessionId) === String(activeSession)) {
|
|
310
423
|
dismissNotif(notif.id);
|
|
311
424
|
return;
|
|
312
425
|
}
|
|
@@ -315,7 +428,7 @@ export function handleNotificationCreated(msg) {
|
|
|
315
428
|
unreadCount = msg.unreadCount;
|
|
316
429
|
updateBadge();
|
|
317
430
|
|
|
318
|
-
var _autoDismiss = notif.type === "permission_request" ? false : true;
|
|
431
|
+
var _autoDismiss = notif.type === "permission_request" || notif.type === "auth_required" ? false : true;
|
|
319
432
|
showBanner(notif, _autoDismiss);
|
|
320
433
|
}
|
|
321
434
|
|
|
@@ -410,7 +410,7 @@ export function initPanels() {
|
|
|
410
410
|
if (vendor === (store.get('currentVendor') || "claude")) return;
|
|
411
411
|
var installed = store.get('installedVendors') || [];
|
|
412
412
|
if (installed.indexOf(vendor) === -1) return;
|
|
413
|
-
store.set({ currentVendor: vendor, currentModel: "", currentModels: [] });
|
|
413
|
+
store.set({ currentVendor: vendor, currentModel: "", currentModels: [], vendorSelectionLocked: true });
|
|
414
414
|
var ws = getWs();
|
|
415
415
|
if (ws) ws.send(JSON.stringify({ type: "set_vendor", vendor: vendor }));
|
|
416
416
|
}
|
|
@@ -7,7 +7,6 @@ import { getMessagesEl, getInputEl, getSendBtn } from './dom-refs.js';
|
|
|
7
7
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
8
8
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
9
9
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
10
|
-
import { openTerminal } from './terminal.js';
|
|
11
10
|
import { userAvatarUrl } from './avatar.js';
|
|
12
11
|
import { closeToolGroup } from './tools.js';
|
|
13
12
|
import { showImageModal, showPasteModal } from './app-misc.js';
|
|
@@ -491,80 +490,6 @@ export function addContextOverflowMessage(msg) {
|
|
|
491
490
|
scrollToBottom();
|
|
492
491
|
}
|
|
493
492
|
|
|
494
|
-
export function addAuthRequiredMessage(msg) {
|
|
495
|
-
var div = document.createElement("div");
|
|
496
|
-
div.className = "auth-required-msg";
|
|
497
|
-
|
|
498
|
-
var vendor = msg.vendor || "claude";
|
|
499
|
-
var loginCmd = msg.loginCommand || (vendor === "codex" ? "codex login --device-auth" : "claude login");
|
|
500
|
-
var vendorName = msg.text || "Claude Code is not logged in.";
|
|
501
|
-
|
|
502
|
-
var header = document.createElement("div");
|
|
503
|
-
header.className = "auth-required-header";
|
|
504
|
-
header.textContent = vendorName;
|
|
505
|
-
div.appendChild(header);
|
|
506
|
-
|
|
507
|
-
var hint = document.createElement("div");
|
|
508
|
-
hint.className = "auth-required-hint";
|
|
509
|
-
|
|
510
|
-
if (msg.canAutoLogin) {
|
|
511
|
-
if (msg.linuxUser) {
|
|
512
|
-
hint.textContent = "Opening a terminal as " + msg.linuxUser + " to log in...";
|
|
513
|
-
} else {
|
|
514
|
-
hint.textContent = "Opening a terminal to log in...";
|
|
515
|
-
}
|
|
516
|
-
div.appendChild(hint);
|
|
517
|
-
|
|
518
|
-
var guide = document.createElement("div");
|
|
519
|
-
guide.className = "auth-required-guide";
|
|
520
|
-
if (vendor === "codex") {
|
|
521
|
-
guide.textContent = "Run the login command in the terminal and follow the instructions to authenticate.";
|
|
522
|
-
} else {
|
|
523
|
-
guide.textContent = "When a login URL appears in the terminal, click it to open in your browser. Do not press 'c' as it will try to open the browser on the server.";
|
|
524
|
-
}
|
|
525
|
-
div.appendChild(guide);
|
|
526
|
-
|
|
527
|
-
var sessionHint = document.createElement("div");
|
|
528
|
-
sessionHint.className = "auth-required-guide";
|
|
529
|
-
sessionHint.textContent = "After logging in, start a new session to continue.";
|
|
530
|
-
div.appendChild(sessionHint);
|
|
531
|
-
|
|
532
|
-
var termCommand = loginCmd + "\n";
|
|
533
|
-
var loginBtn = document.createElement("button");
|
|
534
|
-
loginBtn.className = "auth-required-btn";
|
|
535
|
-
loginBtn.textContent = "Open terminal & log in";
|
|
536
|
-
loginBtn.addEventListener("click", function () {
|
|
537
|
-
store.set({ pendingTermCommand: termCommand });
|
|
538
|
-
getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
|
|
539
|
-
openTerminal();
|
|
540
|
-
});
|
|
541
|
-
div.appendChild(loginBtn);
|
|
542
|
-
|
|
543
|
-
addToMessages(div);
|
|
544
|
-
scrollToBottom();
|
|
545
|
-
|
|
546
|
-
var inputArea = document.getElementById("input-area");
|
|
547
|
-
if (inputArea) inputArea.classList.add("hidden");
|
|
548
|
-
|
|
549
|
-
if (!store.get('replayingHistory')) {
|
|
550
|
-
store.set({ pendingTermCommand: termCommand });
|
|
551
|
-
getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
|
|
552
|
-
openTerminal();
|
|
553
|
-
}
|
|
554
|
-
} else {
|
|
555
|
-
var adminVendorName = vendor.charAt(0).toUpperCase() + vendor.slice(1);
|
|
556
|
-
hint.textContent = "Please ask an administrator to log in to " + adminVendorName + ".";
|
|
557
|
-
div.appendChild(hint);
|
|
558
|
-
addToMessages(div);
|
|
559
|
-
scrollToBottom();
|
|
560
|
-
|
|
561
|
-
var inputEl = getInputEl();
|
|
562
|
-
inputEl.disabled = true;
|
|
563
|
-
inputEl.placeholder = "Login required. Start a new session after logging in.";
|
|
564
|
-
getSendBtn().disabled = true;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
493
|
// --- Pre-thinking (instant dots before server responds) ---
|
|
569
494
|
|
|
570
495
|
export function showClaudePreThinking() {
|
|
@@ -228,6 +228,7 @@ export function sendMessage() {
|
|
|
228
228
|
// Hide vendor toggle after first message (vendor is locked to this session)
|
|
229
229
|
var _vtw2 = document.getElementById("vendor-toggle-wrap");
|
|
230
230
|
if (_vtw2) { _vtw2.classList.remove("hidden"); _vtw2.classList.add("locked"); }
|
|
231
|
+
store.set({ vendorSelectionLocked: false });
|
|
231
232
|
|
|
232
233
|
// Show pre-thinking dots before server responds
|
|
233
234
|
if (ctx.isMateDm && ctx.isMateDm()) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Avatar generated via DiceBear API (deterministic SVG from seed)
|
|
4
4
|
|
|
5
5
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
|
-
import { setSTTLang
|
|
6
|
+
import { setSTTLang } from './stt.js';
|
|
7
7
|
import { avatarUrl, mateAvatarUrl, AVATAR_STYLES } from './avatar.js';
|
|
8
8
|
|
|
9
9
|
var ctx;
|
|
@@ -13,16 +13,6 @@ var popoverEl = null;
|
|
|
13
13
|
var saveTimer = null;
|
|
14
14
|
var previewSeed = '';
|
|
15
15
|
|
|
16
|
-
var LANGUAGES = [
|
|
17
|
-
{ code: 'en-US', name: 'English' },
|
|
18
|
-
{ code: 'ko-KR', name: 'Korean' },
|
|
19
|
-
{ code: 'ja-JP', name: 'Japanese' },
|
|
20
|
-
{ code: 'zh-CN', name: 'Chinese' },
|
|
21
|
-
{ code: 'es-ES', name: 'Spanish' },
|
|
22
|
-
{ code: 'fr-FR', name: 'French' },
|
|
23
|
-
{ code: 'de-DE', name: 'German' },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
16
|
// AVATAR_STYLES imported from avatar.js
|
|
27
17
|
|
|
28
18
|
var COLORS = [
|
|
@@ -295,7 +285,6 @@ function showPopover() {
|
|
|
295
285
|
popoverEl.className = 'profile-popover';
|
|
296
286
|
|
|
297
287
|
var displayName = profile.name || '';
|
|
298
|
-
var currentLang = profile.lang || 'en-US';
|
|
299
288
|
var currentColor = profile.avatarColor || '#7c3aed';
|
|
300
289
|
var currentStyle = profile.avatarStyle || 'thumbs';
|
|
301
290
|
var seed = getAvatarSeed();
|
|
@@ -325,18 +314,6 @@ function showPopover() {
|
|
|
325
314
|
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">';
|
|
326
315
|
html += '</div>';
|
|
327
316
|
|
|
328
|
-
// Language dropdown
|
|
329
|
-
html += '<div class="profile-field">';
|
|
330
|
-
html += '<label class="profile-field-label">Language <span class="profile-field-hint">for voice input</span></label>';
|
|
331
|
-
html += '<select class="profile-field-select" id="profile-lang-select">';
|
|
332
|
-
for (var i = 0; i < LANGUAGES.length; i++) {
|
|
333
|
-
var l = LANGUAGES[i];
|
|
334
|
-
var sel = (currentLang === l.code) ? ' selected' : '';
|
|
335
|
-
html += '<option value="' + l.code + '"' + sel + '>' + l.name + '</option>';
|
|
336
|
-
}
|
|
337
|
-
html += '</select>';
|
|
338
|
-
html += '</div>';
|
|
339
|
-
|
|
340
317
|
// Avatar picker
|
|
341
318
|
html += '<div class="profile-field">';
|
|
342
319
|
html += '<label class="profile-field-label">Avatar <button class="profile-shuffle-btn" title="Shuffle">' + iconHtml('shuffle') + '</button></label>';
|
|
@@ -401,13 +378,6 @@ function showPopover() {
|
|
|
401
378
|
nameInput.addEventListener('keyup', function(e) { e.stopPropagation(); });
|
|
402
379
|
nameInput.addEventListener('keypress', function(e) { e.stopPropagation(); });
|
|
403
380
|
|
|
404
|
-
// Language dropdown
|
|
405
|
-
popoverEl.querySelector('#profile-lang-select').addEventListener('change', function(e) {
|
|
406
|
-
profile.lang = e.target.value;
|
|
407
|
-
setSTTLang(profile.lang);
|
|
408
|
-
debouncedSave();
|
|
409
|
-
});
|
|
410
|
-
|
|
411
381
|
// Avatar upload button
|
|
412
382
|
var uploadBtn = popoverEl.querySelector('.profile-avatar-upload');
|
|
413
383
|
var fileInput = popoverEl.querySelector('#profile-avatar-file');
|