clay-server 2.19.0 → 2.20.0-beta.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/README.md +51 -91
- package/bin/cli.js +49 -14
- package/lib/cli-sessions.js +3 -3
- package/lib/config.js +30 -4
- package/lib/daemon.js +28 -5
- package/lib/mates.js +61 -7
- package/lib/notes.js +20 -0
- package/lib/os-users.js +71 -2
- package/lib/project.js +850 -184
- package/lib/public/app.js +160 -16
- package/lib/public/css/mates.css +316 -2
- package/lib/public/css/mention.css +23 -0
- package/lib/public/css/mobile-nav.css +198 -0
- package/lib/public/css/overlays.css +23 -0
- package/lib/public/css/rewind.css +32 -0
- package/lib/public/css/title-bar.css +3 -0
- package/lib/public/css/user-settings.css +2 -2
- package/lib/public/index.html +64 -14
- package/lib/public/modules/command-palette.js +44 -4
- package/lib/public/modules/filebrowser.js +2 -0
- package/lib/public/modules/input.js +11 -3
- package/lib/public/modules/mate-knowledge.js +2 -0
- package/lib/public/modules/mate-memory.js +353 -0
- package/lib/public/modules/mention.js +77 -2
- package/lib/public/modules/notifications.js +0 -8
- package/lib/public/modules/server-settings.js +11 -4
- package/lib/public/modules/sidebar.js +284 -6
- package/lib/public/modules/theme.js +10 -12
- package/lib/public/modules/tools.js +84 -12
- package/lib/public/modules/user-settings.js +45 -12
- package/lib/sdk-bridge.js +114 -35
- package/lib/server.js +84 -6
- package/lib/sessions.js +4 -4
- package/lib/users.js +26 -0
- package/package.json +1 -1
|
@@ -20,6 +20,8 @@ var searchDebounce = null;
|
|
|
20
20
|
var cachedSessions = [];
|
|
21
21
|
var expandedLoopGroups = new Set();
|
|
22
22
|
var expandedLoopRuns = new Set();
|
|
23
|
+
var expandedMobileLoopGroups = new Set();
|
|
24
|
+
var expandedMobileLoopRuns = new Set();
|
|
23
25
|
|
|
24
26
|
// --- Cached project data for mobile sheet ---
|
|
25
27
|
var cachedProjectList = [];
|
|
@@ -1104,7 +1106,242 @@ function createMobileSessionItem(s) {
|
|
|
1104
1106
|
return el;
|
|
1105
1107
|
}
|
|
1106
1108
|
|
|
1107
|
-
// Helper:
|
|
1109
|
+
// Helper: create a mobile loop child element (individual session inside a group)
|
|
1110
|
+
function createMobileLoopChild(s) {
|
|
1111
|
+
var el = document.createElement("button");
|
|
1112
|
+
el.className = "mobile-loop-child" + (s.active ? " active" : "");
|
|
1113
|
+
|
|
1114
|
+
if (s.isProcessing) {
|
|
1115
|
+
var dot = document.createElement("span");
|
|
1116
|
+
dot.className = "mobile-session-processing";
|
|
1117
|
+
el.appendChild(dot);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
var textSpan = document.createElement("span");
|
|
1121
|
+
textSpan.className = "mobile-session-title";
|
|
1122
|
+
if (s.loop) {
|
|
1123
|
+
var isRalphChild = s.loop.source === "ralph";
|
|
1124
|
+
var roleName = s.loop.role === "crafting" ? "Crafting" : s.loop.role === "judge" ? "Judge" : (isRalphChild ? "Coder" : "Run");
|
|
1125
|
+
var iterSuffix = s.loop.role === "crafting" ? "" : " #" + s.loop.iteration;
|
|
1126
|
+
var roleCls = s.loop.role === "crafting" ? " crafting" : (!isRalphChild ? " scheduled" : "");
|
|
1127
|
+
var badge = document.createElement("span");
|
|
1128
|
+
badge.className = "mobile-loop-role-badge" + roleCls;
|
|
1129
|
+
badge.textContent = roleName + iterSuffix;
|
|
1130
|
+
textSpan.appendChild(badge);
|
|
1131
|
+
}
|
|
1132
|
+
el.appendChild(textSpan);
|
|
1133
|
+
|
|
1134
|
+
(function (id) {
|
|
1135
|
+
el.addEventListener("click", function () {
|
|
1136
|
+
if (ctx.ws && ctx.connected) {
|
|
1137
|
+
ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
|
|
1138
|
+
}
|
|
1139
|
+
dismissOverlayPanels();
|
|
1140
|
+
closeMobileSheet();
|
|
1141
|
+
});
|
|
1142
|
+
})(s.id);
|
|
1143
|
+
|
|
1144
|
+
return el;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// Helper: create a mobile loop run sub-group (collapsible time group)
|
|
1148
|
+
function createMobileLoopRun(parentGk, startedAtKey, sessions, isRalph) {
|
|
1149
|
+
var runGk = parentGk + ":" + startedAtKey;
|
|
1150
|
+
var expanded = expandedMobileLoopRuns.has(runGk);
|
|
1151
|
+
var startedAt = Number(startedAtKey);
|
|
1152
|
+
var timeLabel = startedAt ? new Date(startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "Unknown";
|
|
1153
|
+
|
|
1154
|
+
var hasActive = false;
|
|
1155
|
+
var anyProcessing = false;
|
|
1156
|
+
var latestSession = sessions[0];
|
|
1157
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
1158
|
+
if (sessions[i].active) hasActive = true;
|
|
1159
|
+
if (sessions[i].isProcessing) anyProcessing = true;
|
|
1160
|
+
if ((sessions[i].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
1161
|
+
latestSession = sessions[i];
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
var wrapper = document.createElement("div");
|
|
1166
|
+
wrapper.className = "mobile-loop-run-wrapper";
|
|
1167
|
+
|
|
1168
|
+
var header = document.createElement("button");
|
|
1169
|
+
header.className = "mobile-loop-run" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
|
|
1170
|
+
|
|
1171
|
+
var chevron = document.createElement("span");
|
|
1172
|
+
chevron.className = "mobile-loop-chevron";
|
|
1173
|
+
chevron.innerHTML = iconHtml("chevron-right");
|
|
1174
|
+
header.appendChild(chevron);
|
|
1175
|
+
|
|
1176
|
+
var label = document.createElement("span");
|
|
1177
|
+
label.className = "mobile-loop-run-time";
|
|
1178
|
+
var labelHtml = "";
|
|
1179
|
+
if (anyProcessing) {
|
|
1180
|
+
labelHtml += '<span class="mobile-session-processing"></span> ';
|
|
1181
|
+
}
|
|
1182
|
+
labelHtml += escapeHtml(timeLabel);
|
|
1183
|
+
label.innerHTML = labelHtml;
|
|
1184
|
+
header.appendChild(label);
|
|
1185
|
+
|
|
1186
|
+
var countBadge = document.createElement("span");
|
|
1187
|
+
countBadge.className = "mobile-loop-count" + (isRalph ? "" : " scheduled");
|
|
1188
|
+
countBadge.textContent = String(sessions.length);
|
|
1189
|
+
header.appendChild(countBadge);
|
|
1190
|
+
|
|
1191
|
+
header.addEventListener("click", (function (rk) {
|
|
1192
|
+
return function (e) {
|
|
1193
|
+
e.stopPropagation();
|
|
1194
|
+
if (expandedMobileLoopRuns.has(rk)) {
|
|
1195
|
+
expandedMobileLoopRuns.delete(rk);
|
|
1196
|
+
} else {
|
|
1197
|
+
expandedMobileLoopRuns.add(rk);
|
|
1198
|
+
}
|
|
1199
|
+
refreshMobileChatSheet();
|
|
1200
|
+
};
|
|
1201
|
+
})(runGk));
|
|
1202
|
+
|
|
1203
|
+
wrapper.appendChild(header);
|
|
1204
|
+
|
|
1205
|
+
if (expanded) {
|
|
1206
|
+
var childContainer = document.createElement("div");
|
|
1207
|
+
childContainer.className = "mobile-loop-children";
|
|
1208
|
+
for (var k = 0; k < sessions.length; k++) {
|
|
1209
|
+
childContainer.appendChild(createMobileLoopChild(sessions[k]));
|
|
1210
|
+
}
|
|
1211
|
+
wrapper.appendChild(childContainer);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
return wrapper;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// Helper: create a mobile loop group element (collapsible group header)
|
|
1218
|
+
function createMobileLoopGroup(loopId, children, groupKey) {
|
|
1219
|
+
var gk = groupKey || loopId;
|
|
1220
|
+
|
|
1221
|
+
// Sub-group children by startedAt (each run)
|
|
1222
|
+
var runMap = {};
|
|
1223
|
+
for (var i = 0; i < children.length; i++) {
|
|
1224
|
+
var runKey = String(children[i].loop && children[i].loop.startedAt || 0);
|
|
1225
|
+
if (!runMap[runKey]) runMap[runKey] = [];
|
|
1226
|
+
runMap[runKey].push(children[i]);
|
|
1227
|
+
}
|
|
1228
|
+
var runKeys = Object.keys(runMap);
|
|
1229
|
+
|
|
1230
|
+
// Sort each run's children by iteration then role
|
|
1231
|
+
for (var ri = 0; ri < runKeys.length; ri++) {
|
|
1232
|
+
runMap[runKeys[ri]].sort(function (a, b) {
|
|
1233
|
+
var ai = (a.loop && a.loop.iteration) || 0;
|
|
1234
|
+
var bi = (b.loop && b.loop.iteration) || 0;
|
|
1235
|
+
if (ai !== bi) return ai - bi;
|
|
1236
|
+
var ar = (a.loop && a.loop.role === "judge") ? 1 : 0;
|
|
1237
|
+
var br = (b.loop && b.loop.role === "judge") ? 1 : 0;
|
|
1238
|
+
return ar - br;
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Sort runs by startedAt descending (newest first)
|
|
1243
|
+
runKeys.sort(function (a, b) { return Number(b) - Number(a); });
|
|
1244
|
+
|
|
1245
|
+
var expanded = expandedMobileLoopGroups.has(gk);
|
|
1246
|
+
var hasActive = false;
|
|
1247
|
+
var anyProcessing = false;
|
|
1248
|
+
var latestSession = children[0];
|
|
1249
|
+
for (var ci = 0; ci < children.length; ci++) {
|
|
1250
|
+
if (children[ci].active) hasActive = true;
|
|
1251
|
+
if (children[ci].isProcessing) anyProcessing = true;
|
|
1252
|
+
if ((children[ci].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
1253
|
+
latestSession = children[ci];
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
var loopName = (children[0].loop && children[0].loop.name) || "Ralph Loop";
|
|
1258
|
+
var isRalph = children[0].loop && children[0].loop.source === "ralph";
|
|
1259
|
+
var isCrafting = false;
|
|
1260
|
+
for (var j = 0; j < children.length; j++) {
|
|
1261
|
+
if (children[j].loop && children[j].loop.role === "crafting") isCrafting = true;
|
|
1262
|
+
}
|
|
1263
|
+
var runCount = runKeys.length;
|
|
1264
|
+
|
|
1265
|
+
var wrapper = document.createElement("div");
|
|
1266
|
+
wrapper.className = "mobile-loop-wrapper";
|
|
1267
|
+
|
|
1268
|
+
// Group header row
|
|
1269
|
+
var header = document.createElement("button");
|
|
1270
|
+
header.className = "mobile-loop-group" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
|
|
1271
|
+
|
|
1272
|
+
var chevron = document.createElement("span");
|
|
1273
|
+
chevron.className = "mobile-loop-chevron";
|
|
1274
|
+
chevron.innerHTML = iconHtml("chevron-right");
|
|
1275
|
+
header.appendChild(chevron);
|
|
1276
|
+
|
|
1277
|
+
var iconSpan = document.createElement("span");
|
|
1278
|
+
var groupIcon = isRalph ? "repeat" : "calendar-clock";
|
|
1279
|
+
iconSpan.className = "mobile-loop-icon" + (isRalph ? "" : " scheduled");
|
|
1280
|
+
iconSpan.innerHTML = iconHtml(groupIcon);
|
|
1281
|
+
header.appendChild(iconSpan);
|
|
1282
|
+
|
|
1283
|
+
if (anyProcessing) {
|
|
1284
|
+
var dot = document.createElement("span");
|
|
1285
|
+
dot.className = "mobile-session-processing";
|
|
1286
|
+
header.appendChild(dot);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
var nameSpan = document.createElement("span");
|
|
1290
|
+
nameSpan.className = "mobile-loop-name";
|
|
1291
|
+
nameSpan.textContent = loopName;
|
|
1292
|
+
header.appendChild(nameSpan);
|
|
1293
|
+
|
|
1294
|
+
if (isCrafting && children.length === 1) {
|
|
1295
|
+
var craftBadge = document.createElement("span");
|
|
1296
|
+
craftBadge.className = "mobile-loop-badge crafting";
|
|
1297
|
+
craftBadge.textContent = "Crafting";
|
|
1298
|
+
header.appendChild(craftBadge);
|
|
1299
|
+
} else {
|
|
1300
|
+
var countBadge = document.createElement("span");
|
|
1301
|
+
countBadge.className = "mobile-loop-count" + (isRalph ? "" : " scheduled");
|
|
1302
|
+
var countLabel = runCount === 1 ? String(children.length) : runCount + (runCount === 1 ? " run" : " runs");
|
|
1303
|
+
countBadge.textContent = countLabel;
|
|
1304
|
+
header.appendChild(countBadge);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// Chevron toggles expansion
|
|
1308
|
+
header.addEventListener("click", (function (lid) {
|
|
1309
|
+
return function (e) {
|
|
1310
|
+
e.stopPropagation();
|
|
1311
|
+
if (expandedMobileLoopGroups.has(lid)) {
|
|
1312
|
+
expandedMobileLoopGroups.delete(lid);
|
|
1313
|
+
} else {
|
|
1314
|
+
expandedMobileLoopGroups.add(lid);
|
|
1315
|
+
}
|
|
1316
|
+
refreshMobileChatSheet();
|
|
1317
|
+
};
|
|
1318
|
+
})(gk));
|
|
1319
|
+
|
|
1320
|
+
wrapper.appendChild(header);
|
|
1321
|
+
|
|
1322
|
+
// Expanded: show runs
|
|
1323
|
+
if (expanded) {
|
|
1324
|
+
var childContainer = document.createElement("div");
|
|
1325
|
+
childContainer.className = "mobile-loop-children";
|
|
1326
|
+
|
|
1327
|
+
if (runCount === 1) {
|
|
1328
|
+
var singleRun = runMap[runKeys[0]];
|
|
1329
|
+
for (var sk = 0; sk < singleRun.length; sk++) {
|
|
1330
|
+
childContainer.appendChild(createMobileLoopChild(singleRun[sk]));
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
for (var rk = 0; rk < runKeys.length; rk++) {
|
|
1334
|
+
childContainer.appendChild(createMobileLoopRun(gk, runKeys[rk], runMap[runKeys[rk]], isRalph));
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
wrapper.appendChild(childContainer);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
return wrapper;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Helper: render sorted sessions into a container with date groups (with loop session grouping)
|
|
1108
1345
|
function renderMobileSessionsInto(container) {
|
|
1109
1346
|
var newBtn = document.createElement("button");
|
|
1110
1347
|
newBtn.className = "mobile-session-new";
|
|
@@ -1117,14 +1354,51 @@ function renderMobileSessionsInto(container) {
|
|
|
1117
1354
|
});
|
|
1118
1355
|
container.appendChild(newBtn);
|
|
1119
1356
|
|
|
1120
|
-
|
|
1357
|
+
// Partition: loop sessions vs normal sessions (same logic as desktop renderSessionList)
|
|
1358
|
+
var loopGroups = {};
|
|
1359
|
+
var normalSessions = [];
|
|
1360
|
+
for (var i = 0; i < cachedSessions.length; i++) {
|
|
1361
|
+
var s = cachedSessions[i];
|
|
1362
|
+
if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph") {
|
|
1363
|
+
continue;
|
|
1364
|
+
} else if (s.loop && s.loop.loopId) {
|
|
1365
|
+
var startedAt = s.loop.startedAt || 0;
|
|
1366
|
+
var dateStr = startedAt ? new Date(startedAt).toISOString().slice(0, 10) : "unknown";
|
|
1367
|
+
var groupKey = s.loop.loopId + ":" + dateStr;
|
|
1368
|
+
if (!loopGroups[groupKey]) loopGroups[groupKey] = [];
|
|
1369
|
+
loopGroups[groupKey].push(s);
|
|
1370
|
+
} else {
|
|
1371
|
+
normalSessions.push(s);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// Build virtual items
|
|
1376
|
+
var items = [];
|
|
1377
|
+
for (var j = 0; j < normalSessions.length; j++) {
|
|
1378
|
+
items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
|
|
1379
|
+
}
|
|
1380
|
+
var groupKeys = Object.keys(loopGroups);
|
|
1381
|
+
for (var k = 0; k < groupKeys.length; k++) {
|
|
1382
|
+
var gk = groupKeys[k];
|
|
1383
|
+
var children = loopGroups[gk];
|
|
1384
|
+
var realLoopId = children[0].loop.loopId;
|
|
1385
|
+
var maxActivity = 0;
|
|
1386
|
+
for (var m = 0; m < children.length; m++) {
|
|
1387
|
+
var act = children[m].lastActivity || 0;
|
|
1388
|
+
if (act > maxActivity) maxActivity = act;
|
|
1389
|
+
}
|
|
1390
|
+
items.push({ type: "loop", loopId: realLoopId, groupKey: gk, children: children, lastActivity: maxActivity });
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Sort by lastActivity descending
|
|
1394
|
+
items.sort(function (a, b) {
|
|
1121
1395
|
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
1122
1396
|
});
|
|
1123
1397
|
|
|
1124
1398
|
var currentGroup = "";
|
|
1125
|
-
for (var
|
|
1126
|
-
var
|
|
1127
|
-
var group = getDateGroup(
|
|
1399
|
+
for (var n = 0; n < items.length; n++) {
|
|
1400
|
+
var item = items[n];
|
|
1401
|
+
var group = getDateGroup(item.lastActivity || 0);
|
|
1128
1402
|
if (group !== currentGroup) {
|
|
1129
1403
|
currentGroup = group;
|
|
1130
1404
|
var header = document.createElement("div");
|
|
@@ -1132,7 +1406,11 @@ function renderMobileSessionsInto(container) {
|
|
|
1132
1406
|
header.textContent = group;
|
|
1133
1407
|
container.appendChild(header);
|
|
1134
1408
|
}
|
|
1135
|
-
|
|
1409
|
+
if (item.type === "loop") {
|
|
1410
|
+
container.appendChild(createMobileLoopGroup(item.loopId, item.children, item.groupKey));
|
|
1411
|
+
} else {
|
|
1412
|
+
container.appendChild(createMobileSessionItem(item.data));
|
|
1413
|
+
}
|
|
1136
1414
|
}
|
|
1137
1415
|
}
|
|
1138
1416
|
|
|
@@ -495,13 +495,16 @@ export function toggleDarkMode() {
|
|
|
495
495
|
updateToggleIcon();
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
// Update
|
|
498
|
+
// Update all theme toggle checkboxes
|
|
499
499
|
function updateToggleIcon() {
|
|
500
|
-
var checkbox = document.getElementById("theme-toggle-check");
|
|
501
|
-
if (!checkbox) return;
|
|
502
500
|
var mode = getEffectiveMode();
|
|
503
|
-
|
|
504
|
-
|
|
501
|
+
var isLight = mode === "light";
|
|
502
|
+
// Legacy top-bar toggle (may not exist)
|
|
503
|
+
var checkbox = document.getElementById("theme-toggle-check");
|
|
504
|
+
if (checkbox) checkbox.checked = isLight;
|
|
505
|
+
// User settings toggle
|
|
506
|
+
var usToggle = document.getElementById("us-theme-toggle");
|
|
507
|
+
if (usToggle) usToggle.checked = isLight;
|
|
505
508
|
}
|
|
506
509
|
|
|
507
510
|
// --- Theme picker UI ---
|
|
@@ -671,13 +674,8 @@ export function initTheme() {
|
|
|
671
674
|
// Load all themes from server, then apply properly
|
|
672
675
|
loadThemes();
|
|
673
676
|
|
|
674
|
-
//
|
|
675
|
-
|
|
676
|
-
if (toggleCheck) {
|
|
677
|
-
toggleCheck.addEventListener("change", function () {
|
|
678
|
-
toggleDarkMode();
|
|
679
|
-
});
|
|
680
|
-
}
|
|
677
|
+
// Theme toggle is now in User Settings (us-theme-toggle)
|
|
678
|
+
// Wired up in user-settings.js initUserSettings()
|
|
681
679
|
|
|
682
680
|
// Listen for system preference changes (only applies if user has no manual override)
|
|
683
681
|
if (window.matchMedia) {
|
|
@@ -389,6 +389,9 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
|
|
|
389
389
|
enableMainInput();
|
|
390
390
|
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
391
391
|
|
|
392
|
+
// Show user's answers inline
|
|
393
|
+
showAnswerSummary(container, questions, result);
|
|
394
|
+
|
|
392
395
|
if (ctx.ws && ctx.connected) {
|
|
393
396
|
ctx.ws.send(JSON.stringify({
|
|
394
397
|
type: "ask_user_response",
|
|
@@ -398,11 +401,72 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
|
|
|
398
401
|
}
|
|
399
402
|
}
|
|
400
403
|
|
|
401
|
-
|
|
404
|
+
function showAnswerSummary(container, questions, answers) {
|
|
405
|
+
if (!answers || Object.keys(answers).length === 0) return;
|
|
406
|
+
var existing = container.querySelector(".ask-user-answer-summary");
|
|
407
|
+
if (existing) return;
|
|
408
|
+
var summary = document.createElement("div");
|
|
409
|
+
summary.className = "ask-user-answer-summary";
|
|
410
|
+
for (var key in answers) {
|
|
411
|
+
if (!answers.hasOwnProperty(key)) continue;
|
|
412
|
+
var row = document.createElement("div");
|
|
413
|
+
row.className = "ask-user-answer-row";
|
|
414
|
+
var qi = parseInt(key, 10);
|
|
415
|
+
var label = (questions && questions[qi] && questions[qi].question) ? questions[qi].question : "Answer";
|
|
416
|
+
row.innerHTML = '<span class="ask-user-answer-label">' + escapeHtml(label) + '</span>' +
|
|
417
|
+
'<span class="ask-user-answer-value">' + escapeHtml(String(answers[key])) + '</span>';
|
|
418
|
+
summary.appendChild(row);
|
|
419
|
+
}
|
|
420
|
+
// Insert before submit button
|
|
421
|
+
var submitBtn = container.querySelector(".ask-user-submit");
|
|
422
|
+
if (submitBtn) {
|
|
423
|
+
submitBtn.parentNode.insertBefore(summary, submitBtn);
|
|
424
|
+
} else {
|
|
425
|
+
container.appendChild(summary);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function markAskUserAnswered(toolId, answers) {
|
|
402
430
|
var container = document.querySelector('.ask-user-container[data-tool-id="' + toolId + '"]');
|
|
403
431
|
if (container && !container.classList.contains("answered")) {
|
|
404
432
|
container.classList.add("answered");
|
|
405
433
|
enableMainInput();
|
|
434
|
+
// Restore answers from history replay
|
|
435
|
+
if (answers && Object.keys(answers).length > 0) {
|
|
436
|
+
// Find matching questions from the tool_executing input
|
|
437
|
+
var questions = null;
|
|
438
|
+
var askQuestions = container.querySelectorAll(".ask-user-question");
|
|
439
|
+
if (askQuestions.length > 0) {
|
|
440
|
+
questions = [];
|
|
441
|
+
for (var qi = 0; qi < askQuestions.length; qi++) {
|
|
442
|
+
var qTextEl = askQuestions[qi].querySelector(".ask-user-question-text");
|
|
443
|
+
questions.push({ question: qTextEl ? qTextEl.textContent : "" });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
showAnswerSummary(container, questions, answers);
|
|
447
|
+
// Also mark selected options to match the answers
|
|
448
|
+
for (var key in answers) {
|
|
449
|
+
if (!answers.hasOwnProperty(key)) continue;
|
|
450
|
+
var idx = parseInt(key, 10);
|
|
451
|
+
if (askQuestions[idx]) {
|
|
452
|
+
var answerVal = String(answers[key]);
|
|
453
|
+
var options = askQuestions[idx].querySelectorAll(".ask-user-option");
|
|
454
|
+
var matched = false;
|
|
455
|
+
for (var oi = 0; oi < options.length; oi++) {
|
|
456
|
+
var labelEl = options[oi].querySelector(".option-label");
|
|
457
|
+
if (labelEl && labelEl.textContent === answerVal) {
|
|
458
|
+
options[oi].classList.add("selected");
|
|
459
|
+
matched = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// If not matched to an option, fill the "Other" input
|
|
463
|
+
if (!matched) {
|
|
464
|
+
var otherInput = askQuestions[idx].querySelector(".ask-user-other input");
|
|
465
|
+
if (otherInput) otherInput.value = answerVal;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
406
470
|
}
|
|
407
471
|
}
|
|
408
472
|
|
|
@@ -1345,17 +1409,22 @@ export function startThinking() {
|
|
|
1345
1409
|
var el = thinkingGroup.el;
|
|
1346
1410
|
el.classList.remove("done");
|
|
1347
1411
|
el.querySelector(".thinking-content").textContent = "";
|
|
1348
|
-
// Mate mode: restore
|
|
1412
|
+
// Mate mode: restore sparkle activity row, hide thinking header
|
|
1349
1413
|
if (el.classList.contains("mate-thinking")) {
|
|
1350
|
-
var
|
|
1351
|
-
if (
|
|
1414
|
+
var actRow = el.querySelector(".mate-thinking-activity");
|
|
1415
|
+
if (actRow) {
|
|
1416
|
+
actRow.style.display = "";
|
|
1417
|
+
actRow.querySelector(".activity-text").textContent = randomThinkingVerb() + "...";
|
|
1418
|
+
}
|
|
1352
1419
|
var header = el.querySelector(".thinking-header");
|
|
1353
1420
|
if (header) header.style.display = "none";
|
|
1354
1421
|
}
|
|
1355
1422
|
currentThinking = { el: el, fullText: "", startTime: Date.now() };
|
|
1356
1423
|
refreshIcons();
|
|
1357
1424
|
ctx.scrollToBottom();
|
|
1358
|
-
|
|
1425
|
+
if (!el.classList.contains("mate-thinking")) {
|
|
1426
|
+
ctx.setActivity(randomThinkingVerb() + "...");
|
|
1427
|
+
}
|
|
1359
1428
|
return;
|
|
1360
1429
|
}
|
|
1361
1430
|
|
|
@@ -1369,9 +1438,10 @@ export function startThinking() {
|
|
|
1369
1438
|
el.innerHTML =
|
|
1370
1439
|
'<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
|
|
1371
1440
|
'<div class="dm-bubble-content">' +
|
|
1372
|
-
'<div class="
|
|
1373
|
-
'<
|
|
1374
|
-
'<span class="
|
|
1441
|
+
'<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
|
|
1442
|
+
'<div class="activity-inline mate-thinking-activity">' +
|
|
1443
|
+
'<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
|
|
1444
|
+
'<span class="activity-text">' + randomThinkingVerb() + '...</span>' +
|
|
1375
1445
|
'</div>' +
|
|
1376
1446
|
'<div class="thinking-header" style="display:none">' +
|
|
1377
1447
|
'<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
@@ -1401,7 +1471,9 @@ export function startThinking() {
|
|
|
1401
1471
|
ctx.scrollToBottom();
|
|
1402
1472
|
thinkingGroup = { el: el, count: 0, totalDuration: 0 };
|
|
1403
1473
|
currentThinking = { el: el, fullText: "", startTime: Date.now() };
|
|
1404
|
-
ctx.
|
|
1474
|
+
if (!ctx.isMateDm()) {
|
|
1475
|
+
ctx.setActivity(randomThinkingVerb() + "...");
|
|
1476
|
+
}
|
|
1405
1477
|
}
|
|
1406
1478
|
|
|
1407
1479
|
export function appendThinking(text) {
|
|
@@ -1422,10 +1494,10 @@ export function stopThinking(duration) {
|
|
|
1422
1494
|
} else {
|
|
1423
1495
|
currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs.toFixed(1) + "s";
|
|
1424
1496
|
}
|
|
1425
|
-
// In mate mode: hide
|
|
1497
|
+
// In mate mode: hide sparkle activity, show compact expandable thinking header
|
|
1426
1498
|
if (currentThinking.el.classList.contains("mate-thinking")) {
|
|
1427
|
-
var
|
|
1428
|
-
if (
|
|
1499
|
+
var actRow = currentThinking.el.querySelector(".mate-thinking-activity");
|
|
1500
|
+
if (actRow) actRow.style.display = "none";
|
|
1429
1501
|
var header = currentThinking.el.querySelector(".thinking-header");
|
|
1430
1502
|
if (header) {
|
|
1431
1503
|
header.style.display = "";
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { refreshIcons } from './icons.js';
|
|
5
5
|
import { showToast } from './utils.js';
|
|
6
|
+
import { toggleDarkMode, getCurrentTheme } from './theme.js';
|
|
6
7
|
|
|
7
8
|
var ctx = null;
|
|
8
9
|
var settingsEl = null;
|
|
@@ -66,17 +67,40 @@ export function initUserSettings(appCtx) {
|
|
|
66
67
|
var pinInput = document.getElementById('us-pin-input');
|
|
67
68
|
var pinSave = document.getElementById('us-pin-save');
|
|
68
69
|
if (pinInput && pinSave) {
|
|
69
|
-
|
|
70
|
+
function validatePin() {
|
|
70
71
|
pinSave.disabled = !/^\d{6}$/.test(pinInput.value);
|
|
71
|
-
}
|
|
72
|
+
}
|
|
73
|
+
pinInput.addEventListener('input', validatePin);
|
|
74
|
+
pinInput.addEventListener('keyup', function (e) { e.stopPropagation(); validatePin(); });
|
|
72
75
|
pinInput.addEventListener('keydown', stopProp);
|
|
73
|
-
pinInput.addEventListener('keyup', stopProp);
|
|
74
76
|
pinInput.addEventListener('keypress', stopProp);
|
|
75
77
|
pinSave.addEventListener('click', function () {
|
|
76
78
|
savePin(pinInput.value);
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
// Auto-continue toggle
|
|
83
|
+
var autoContinueToggle = document.getElementById('us-auto-continue');
|
|
84
|
+
if (autoContinueToggle) {
|
|
85
|
+
autoContinueToggle.addEventListener('change', function () {
|
|
86
|
+
fetch('/api/user/auto-continue', {
|
|
87
|
+
method: 'PUT',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({ enabled: this.checked }),
|
|
90
|
+
}).then(function (r) { return r.json(); }).then(function (data) {
|
|
91
|
+
if (data.ok) showToast(data.autoContinueOnRateLimit ? 'Auto-continue on' : 'Auto-continue off');
|
|
92
|
+
}).catch(function () {});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Theme toggle (light/dark)
|
|
97
|
+
var themeToggle = document.getElementById('us-theme-toggle');
|
|
98
|
+
if (themeToggle) {
|
|
99
|
+
themeToggle.addEventListener('change', function () {
|
|
100
|
+
toggleDarkMode();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
80
104
|
// Logout button
|
|
81
105
|
var logoutBtn = document.getElementById('us-logout-btn');
|
|
82
106
|
if (logoutBtn) {
|
|
@@ -139,6 +163,15 @@ function populateAccount() {
|
|
|
139
163
|
if (accountNav && !data.username) {
|
|
140
164
|
accountNav.style.display = 'none';
|
|
141
165
|
}
|
|
166
|
+
// Auto-continue toggle
|
|
167
|
+
var acToggle = document.getElementById('us-auto-continue');
|
|
168
|
+
if (acToggle) acToggle.checked = !!data.autoContinueOnRateLimit;
|
|
169
|
+
// Theme toggle
|
|
170
|
+
var thToggle = document.getElementById('us-theme-toggle');
|
|
171
|
+
if (thToggle) {
|
|
172
|
+
var theme = getCurrentTheme();
|
|
173
|
+
thToggle.checked = theme && theme.variant === 'light';
|
|
174
|
+
}
|
|
142
175
|
}).catch(function () {});
|
|
143
176
|
}
|
|
144
177
|
|
|
@@ -148,36 +181,36 @@ function savePin(pin) {
|
|
|
148
181
|
var pinMsg = document.getElementById('us-pin-msg');
|
|
149
182
|
|
|
150
183
|
pinSave.disabled = true;
|
|
151
|
-
pinSave.textContent = 'Saving
|
|
184
|
+
pinSave.textContent = 'Saving\u2026';
|
|
152
185
|
|
|
153
186
|
fetch('/api/user/pin', {
|
|
154
187
|
method: 'PUT',
|
|
155
188
|
headers: { 'Content-Type': 'application/json' },
|
|
156
|
-
body: JSON.stringify({
|
|
189
|
+
body: JSON.stringify({ newPin: pin }),
|
|
157
190
|
}).then(function (r) { return r.json(); }).then(function (data) {
|
|
158
191
|
if (data.ok) {
|
|
159
192
|
pinInput.value = '';
|
|
160
|
-
pinSave.textContent = '
|
|
193
|
+
pinSave.textContent = 'Change PIN';
|
|
161
194
|
if (pinMsg) {
|
|
162
|
-
pinMsg.textContent = 'PIN
|
|
195
|
+
pinMsg.textContent = 'Your PIN has been changed.';
|
|
163
196
|
pinMsg.className = 'us-pin-msg us-pin-msg-ok';
|
|
164
197
|
pinMsg.classList.remove('hidden');
|
|
165
198
|
}
|
|
166
|
-
showToast('PIN
|
|
199
|
+
showToast('PIN changed');
|
|
167
200
|
} else {
|
|
168
201
|
pinSave.disabled = false;
|
|
169
|
-
pinSave.textContent = '
|
|
202
|
+
pinSave.textContent = 'Change PIN';
|
|
170
203
|
if (pinMsg) {
|
|
171
|
-
pinMsg.textContent = data.error || '
|
|
204
|
+
pinMsg.textContent = data.error || 'Could not change your PIN. Please try again.';
|
|
172
205
|
pinMsg.className = 'us-pin-msg us-pin-msg-err';
|
|
173
206
|
pinMsg.classList.remove('hidden');
|
|
174
207
|
}
|
|
175
208
|
}
|
|
176
209
|
}).catch(function () {
|
|
177
210
|
pinSave.disabled = false;
|
|
178
|
-
pinSave.textContent = '
|
|
211
|
+
pinSave.textContent = 'Change PIN';
|
|
179
212
|
if (pinMsg) {
|
|
180
|
-
pinMsg.textContent = '
|
|
213
|
+
pinMsg.textContent = 'Connection lost. Check your network and try again.';
|
|
181
214
|
pinMsg.className = 'us-pin-msg us-pin-msg-err';
|
|
182
215
|
pinMsg.classList.remove('hidden');
|
|
183
216
|
}
|