clay-server 2.10.0 → 2.11.0-beta.10
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/bin/cli.js +157 -1
- package/lib/daemon.js +341 -2
- package/lib/dm.js +135 -0
- package/lib/os-users.js +301 -0
- package/lib/pages.js +36 -0
- package/lib/project.js +386 -67
- package/lib/public/app.js +675 -17
- package/lib/public/css/admin.css +99 -10
- package/lib/public/css/filebrowser.css +22 -0
- package/lib/public/css/icon-strip.css +162 -1
- package/lib/public/css/menus.css +23 -0
- package/lib/public/css/messages.css +245 -0
- package/lib/public/css/overlays.css +88 -0
- package/lib/public/css/server-settings.css +30 -2
- package/lib/public/css/sidebar.css +4 -0
- package/lib/public/index.html +140 -66
- package/lib/public/modules/admin.js +179 -12
- package/lib/public/modules/input.js +13 -2
- package/lib/public/modules/notifications.js +3 -1
- package/lib/public/modules/project-settings.js +154 -168
- package/lib/public/modules/server-settings.js +78 -189
- package/lib/public/modules/settings-defaults.js +243 -0
- package/lib/public/modules/sidebar.js +112 -6
- package/lib/public/modules/terminal.js +48 -10
- package/lib/public/modules/tools.js +214 -1
- package/lib/sdk-bridge.js +634 -6
- package/lib/sdk-worker.js +446 -0
- package/lib/server.js +335 -3
- package/lib/sessions.js +26 -0
- package/lib/terminal-manager.js +2 -2
- package/lib/terminal.js +20 -4
- package/lib/updater.js +38 -11
- package/lib/users.js +79 -0
- package/package.json +2 -2
package/lib/public/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
|
|
2
2
|
import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
|
|
3
3
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
|
|
4
|
-
import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories } from './modules/sidebar.js';
|
|
4
|
+
import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge } from './modules/sidebar.js';
|
|
5
5
|
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
|
|
6
6
|
import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
|
|
7
7
|
import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage } from './modules/input.js';
|
|
@@ -10,9 +10,9 @@ import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFs
|
|
|
10
10
|
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
|
|
11
11
|
import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen } from './modules/sticky-notes.js';
|
|
12
12
|
import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
|
|
13
|
-
import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
|
|
13
|
+
import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
|
|
14
14
|
import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
|
|
15
|
-
import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved } from './modules/project-settings.js';
|
|
15
|
+
import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './modules/project-settings.js';
|
|
16
16
|
import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
|
|
17
17
|
import { initScheduler, resetScheduler, handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, openSchedulerToTab, isSchedulerOpen, closeScheduler, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles, getUpcomingSchedules } from './modules/scheduler.js';
|
|
18
18
|
import { initAsciiLogo, startLogoAnimation, stopLogoAnimation } from './modules/ascii-logo.js';
|
|
@@ -49,6 +49,13 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
49
49
|
var imagePreviewBar = $("image-preview-bar");
|
|
50
50
|
var connectOverlay = $("connect-overlay");
|
|
51
51
|
|
|
52
|
+
// --- DM Mode ---
|
|
53
|
+
var dmMode = false;
|
|
54
|
+
var dmKey = null;
|
|
55
|
+
var dmTargetUser = null;
|
|
56
|
+
var dmUnread = {}; // { otherUserId: count }
|
|
57
|
+
var cachedAllUsers = [];
|
|
58
|
+
|
|
52
59
|
// --- Home Hub ---
|
|
53
60
|
var homeHub = $("home-hub");
|
|
54
61
|
var homeHubVisible = false;
|
|
@@ -524,9 +531,232 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
524
531
|
}
|
|
525
532
|
}
|
|
526
533
|
|
|
534
|
+
// --- DM Mode Functions ---
|
|
535
|
+
function openDm(targetUserId) {
|
|
536
|
+
if (!ws || ws.readyState !== 1) return;
|
|
537
|
+
ws.send(JSON.stringify({ type: "dm_open", targetUserId: targetUserId }));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function enterDmMode(key, targetUser, messages) {
|
|
541
|
+
dmMode = true;
|
|
542
|
+
dmKey = key;
|
|
543
|
+
dmTargetUser = targetUser;
|
|
544
|
+
|
|
545
|
+
// Clear unread for this user
|
|
546
|
+
if (targetUser) {
|
|
547
|
+
dmUnread[targetUser.id] = 0;
|
|
548
|
+
updateDmBadge(targetUser.id, 0);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Update icon strip active state
|
|
552
|
+
setCurrentDmUser(targetUser ? targetUser.id : null);
|
|
553
|
+
var activeProj = document.querySelector("#icon-strip-projects .icon-strip-item.active");
|
|
554
|
+
if (activeProj) activeProj.classList.remove("active");
|
|
555
|
+
var homeIcon = document.querySelector(".icon-strip-home");
|
|
556
|
+
if (homeIcon) homeIcon.classList.remove("active");
|
|
557
|
+
// Re-render user strip to show active state
|
|
558
|
+
if (cachedProjects && cachedProjects.length > 0) {
|
|
559
|
+
renderProjectList();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Hide home hub if visible
|
|
563
|
+
hideHomeHub();
|
|
564
|
+
|
|
565
|
+
// Hide project UI + sidebar, show DM UI
|
|
566
|
+
var mainCol = document.getElementById("main-column");
|
|
567
|
+
if (mainCol) mainCol.classList.add("dm-mode");
|
|
568
|
+
var sidebarCol = document.getElementById("sidebar-column");
|
|
569
|
+
if (sidebarCol) sidebarCol.classList.add("dm-mode");
|
|
570
|
+
var resizeHandle = document.getElementById("sidebar-resize-handle");
|
|
571
|
+
if (resizeHandle) resizeHandle.classList.add("dm-mode");
|
|
572
|
+
|
|
573
|
+
// Hide user-island (my avatar behind it becomes visible)
|
|
574
|
+
var userIsland = document.getElementById("user-island");
|
|
575
|
+
if (userIsland) userIsland.classList.add("dm-hidden");
|
|
576
|
+
|
|
577
|
+
// Render DM messages
|
|
578
|
+
messagesEl.innerHTML = "";
|
|
579
|
+
if (messages && messages.length > 0) {
|
|
580
|
+
for (var i = 0; i < messages.length; i++) {
|
|
581
|
+
appendDmMessage(messages[i]);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
scrollToBottom();
|
|
585
|
+
|
|
586
|
+
// Focus input
|
|
587
|
+
if (inputEl) {
|
|
588
|
+
inputEl.placeholder = "Message " + (targetUser ? targetUser.displayName : "");
|
|
589
|
+
inputEl.focus();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Populate DM header bar with user avatar, name, and personal color
|
|
593
|
+
if (targetUser) {
|
|
594
|
+
var dmHeaderBar = document.getElementById("dm-header-bar");
|
|
595
|
+
var dmAvatar = document.getElementById("dm-header-avatar");
|
|
596
|
+
var dmName = document.getElementById("dm-header-name");
|
|
597
|
+
if (dmAvatar) {
|
|
598
|
+
dmAvatar.src = "https://api.dicebear.com/9.x/" + (targetUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(targetUser.avatarSeed || targetUser.username) + "&size=28";
|
|
599
|
+
}
|
|
600
|
+
if (dmName) dmName.textContent = targetUser.displayName;
|
|
601
|
+
if (dmHeaderBar && targetUser.avatarColor) {
|
|
602
|
+
dmHeaderBar.style.background = targetUser.avatarColor;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function exitDmMode() {
|
|
608
|
+
if (!dmMode) return;
|
|
609
|
+
dmMode = false;
|
|
610
|
+
dmKey = null;
|
|
611
|
+
dmTargetUser = null;
|
|
612
|
+
setCurrentDmUser(null);
|
|
613
|
+
|
|
614
|
+
var mainCol = document.getElementById("main-column");
|
|
615
|
+
if (mainCol) mainCol.classList.remove("dm-mode");
|
|
616
|
+
var sidebarCol = document.getElementById("sidebar-column");
|
|
617
|
+
if (sidebarCol) sidebarCol.classList.remove("dm-mode");
|
|
618
|
+
var resizeHandle = document.getElementById("sidebar-resize-handle");
|
|
619
|
+
if (resizeHandle) resizeHandle.classList.remove("dm-mode");
|
|
620
|
+
|
|
621
|
+
// Reset DM header
|
|
622
|
+
var dmHeaderBar = document.getElementById("dm-header-bar");
|
|
623
|
+
if (dmHeaderBar) dmHeaderBar.style.background = "";
|
|
624
|
+
|
|
625
|
+
// Restore user-island (covers my avatar again)
|
|
626
|
+
var userIsland = document.getElementById("user-island");
|
|
627
|
+
if (userIsland) userIsland.classList.remove("dm-hidden");
|
|
628
|
+
|
|
629
|
+
// Restore project UI
|
|
630
|
+
if (inputEl) inputEl.placeholder = "";
|
|
631
|
+
renderProjectList();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function appendDmMessage(msg) {
|
|
635
|
+
var isMe = msg.from === myUserId;
|
|
636
|
+
var d = new Date(msg.ts);
|
|
637
|
+
var timeStr = d.getHours().toString().padStart(2, "0") + ":" + d.getMinutes().toString().padStart(2, "0");
|
|
638
|
+
|
|
639
|
+
// Check if we can compact (same sender as previous, within 5 min)
|
|
640
|
+
var prev = messagesEl.lastElementChild;
|
|
641
|
+
var compact = false;
|
|
642
|
+
if (prev && prev.dataset.from === msg.from) {
|
|
643
|
+
var prevTs = parseInt(prev.dataset.ts || "0", 10);
|
|
644
|
+
if (msg.ts - prevTs < 300000) compact = true;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
var div = document.createElement("div");
|
|
648
|
+
div.className = "dm-msg" + (compact ? " dm-msg-compact" : "");
|
|
649
|
+
div.dataset.from = msg.from;
|
|
650
|
+
div.dataset.ts = msg.ts;
|
|
651
|
+
|
|
652
|
+
if (compact) {
|
|
653
|
+
// Compact: just hover-time + text, no avatar/name
|
|
654
|
+
var hoverTime = document.createElement("span");
|
|
655
|
+
hoverTime.className = "dm-msg-hover-time";
|
|
656
|
+
hoverTime.textContent = timeStr;
|
|
657
|
+
div.appendChild(hoverTime);
|
|
658
|
+
|
|
659
|
+
var body = document.createElement("div");
|
|
660
|
+
body.className = "dm-msg-body";
|
|
661
|
+
body.textContent = msg.text;
|
|
662
|
+
div.appendChild(body);
|
|
663
|
+
} else {
|
|
664
|
+
// Full: avatar + header(name, time) + text
|
|
665
|
+
var avatar = document.createElement("img");
|
|
666
|
+
avatar.className = "dm-msg-avatar";
|
|
667
|
+
if (isMe) {
|
|
668
|
+
var myUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
669
|
+
var myStyle = myUser ? myUser.avatarStyle : "thumbs";
|
|
670
|
+
var mySeed = myUser ? (myUser.avatarSeed || myUser.username) : myUserId;
|
|
671
|
+
avatar.src = "https://api.dicebear.com/9.x/" + (myStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(mySeed) + "&size=36";
|
|
672
|
+
} else if (dmTargetUser) {
|
|
673
|
+
avatar.src = "https://api.dicebear.com/9.x/" + (dmTargetUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(dmTargetUser.avatarSeed || dmTargetUser.username) + "&size=36";
|
|
674
|
+
}
|
|
675
|
+
div.appendChild(avatar);
|
|
676
|
+
|
|
677
|
+
var content = document.createElement("div");
|
|
678
|
+
content.className = "dm-msg-content";
|
|
679
|
+
|
|
680
|
+
var header = document.createElement("div");
|
|
681
|
+
header.className = "dm-msg-header";
|
|
682
|
+
|
|
683
|
+
var name = document.createElement("span");
|
|
684
|
+
name.className = "dm-msg-name";
|
|
685
|
+
if (isMe) {
|
|
686
|
+
var mu = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
687
|
+
name.textContent = mu ? mu.displayName : "Me";
|
|
688
|
+
} else {
|
|
689
|
+
name.textContent = dmTargetUser ? dmTargetUser.displayName : "User";
|
|
690
|
+
}
|
|
691
|
+
header.appendChild(name);
|
|
692
|
+
|
|
693
|
+
var time = document.createElement("span");
|
|
694
|
+
time.className = "dm-msg-time";
|
|
695
|
+
time.textContent = timeStr;
|
|
696
|
+
header.appendChild(time);
|
|
697
|
+
|
|
698
|
+
content.appendChild(header);
|
|
699
|
+
|
|
700
|
+
var body = document.createElement("div");
|
|
701
|
+
body.className = "dm-msg-body";
|
|
702
|
+
body.textContent = msg.text;
|
|
703
|
+
content.appendChild(body);
|
|
704
|
+
|
|
705
|
+
div.appendChild(content);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
messagesEl.appendChild(div);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
var dmTypingTimer = null;
|
|
712
|
+
|
|
713
|
+
function showDmTypingIndicator(typing) {
|
|
714
|
+
var existing = document.getElementById("dm-typing-indicator");
|
|
715
|
+
if (!typing) {
|
|
716
|
+
if (existing) existing.remove();
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (existing) return; // already showing
|
|
720
|
+
if (!dmTargetUser) return;
|
|
721
|
+
|
|
722
|
+
var div = document.createElement("div");
|
|
723
|
+
div.id = "dm-typing-indicator";
|
|
724
|
+
div.className = "dm-msg dm-typing-indicator";
|
|
725
|
+
|
|
726
|
+
var avatar = document.createElement("img");
|
|
727
|
+
avatar.className = "dm-msg-avatar";
|
|
728
|
+
avatar.src = "https://api.dicebear.com/9.x/" + (dmTargetUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(dmTargetUser.avatarSeed || dmTargetUser.username) + "&size=36";
|
|
729
|
+
div.appendChild(avatar);
|
|
730
|
+
|
|
731
|
+
var dots = document.createElement("div");
|
|
732
|
+
dots.className = "dm-typing-dots";
|
|
733
|
+
dots.innerHTML = "<span></span><span></span><span></span>";
|
|
734
|
+
div.appendChild(dots);
|
|
735
|
+
|
|
736
|
+
messagesEl.appendChild(div);
|
|
737
|
+
scrollToBottom();
|
|
738
|
+
|
|
739
|
+
// Auto-hide after 5s in case stop signal is missed
|
|
740
|
+
clearTimeout(dmTypingTimer);
|
|
741
|
+
dmTypingTimer = setTimeout(function () {
|
|
742
|
+
showDmTypingIndicator(false);
|
|
743
|
+
}, 5000);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function handleDmSend() {
|
|
747
|
+
if (!dmMode || !dmKey || !inputEl) return false;
|
|
748
|
+
var text = inputEl.value.trim();
|
|
749
|
+
if (!text) return false;
|
|
750
|
+
ws.send(JSON.stringify({ type: "dm_send", dmKey: dmKey, text: text }));
|
|
751
|
+
inputEl.value = "";
|
|
752
|
+
autoResize();
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
|
|
527
756
|
var hubCloseBtn = document.getElementById("home-hub-close");
|
|
528
757
|
|
|
529
758
|
function showHomeHub() {
|
|
759
|
+
if (dmMode) exitDmMode();
|
|
530
760
|
homeHubVisible = true;
|
|
531
761
|
homeHub.classList.remove("hidden");
|
|
532
762
|
// Show close button only if there's a project to return to
|
|
@@ -591,6 +821,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
591
821
|
var projectHintDismiss = $("project-hint-dismiss");
|
|
592
822
|
var cachedProjects = [];
|
|
593
823
|
var cachedProjectCount = 0;
|
|
824
|
+
var currentProjectOwnerId = null;
|
|
594
825
|
var currentSlug = slugMatch ? slugMatch[1] : null;
|
|
595
826
|
|
|
596
827
|
function updateProjectList(msg) {
|
|
@@ -611,6 +842,23 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
611
842
|
if (msg.serverUsers) {
|
|
612
843
|
renderTopbarPresence(msg.serverUsers);
|
|
613
844
|
}
|
|
845
|
+
// Update user strip (DM targets) in icon strip
|
|
846
|
+
if (msg.allUsers) {
|
|
847
|
+
cachedAllUsers = msg.allUsers;
|
|
848
|
+
var onlineIds = (msg.serverUsers || []).map(function (u) { return u.id; });
|
|
849
|
+
renderUserStrip(msg.allUsers, onlineIds, myUserId);
|
|
850
|
+
// Render my avatar (always present, hidden behind user-island)
|
|
851
|
+
var meEl = document.getElementById("icon-strip-me");
|
|
852
|
+
if (meEl && !meEl.hasChildNodes()) {
|
|
853
|
+
var myUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
854
|
+
if (myUser) {
|
|
855
|
+
var meAvatar = document.createElement("img");
|
|
856
|
+
meAvatar.className = "icon-strip-me-avatar";
|
|
857
|
+
meAvatar.src = "https://api.dicebear.com/9.x/" + (myUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(myUser.avatarSeed || myUser.username) + "&size=34";
|
|
858
|
+
meEl.appendChild(meAvatar);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
614
862
|
}
|
|
615
863
|
|
|
616
864
|
function renderTopbarPresence(serverUsers) {
|
|
@@ -915,9 +1163,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
915
1163
|
|
|
916
1164
|
var confirmCallback = null;
|
|
917
1165
|
|
|
918
|
-
function showConfirm(text, onConfirm) {
|
|
1166
|
+
function showConfirm(text, onConfirm, okLabel, destructive) {
|
|
919
1167
|
confirmText.textContent = text;
|
|
920
1168
|
confirmCallback = onConfirm;
|
|
1169
|
+
confirmOk.textContent = okLabel || "Delete";
|
|
1170
|
+
confirmOk.className = "confirm-btn " + (destructive === false ? "confirm-ok" : "confirm-delete");
|
|
921
1171
|
confirmModal.classList.remove("hidden");
|
|
922
1172
|
}
|
|
923
1173
|
|
|
@@ -972,6 +1222,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
972
1222
|
getUpcomingSchedules: getUpcomingSchedules,
|
|
973
1223
|
get multiUser() { return isMultiUserMode; },
|
|
974
1224
|
get myUserId() { return myUserId; },
|
|
1225
|
+
get projectOwnerId() { return currentProjectOwnerId; },
|
|
1226
|
+
openDm: function (userId) { openDm(userId); },
|
|
1227
|
+
openAddProjectModal: function () { openAddProjectModal(); },
|
|
975
1228
|
};
|
|
976
1229
|
initSidebar(sidebarCtx);
|
|
977
1230
|
initIconStrip(sidebarCtx);
|
|
@@ -1215,12 +1468,20 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1215
1468
|
var configBetaSection = $("config-beta-section");
|
|
1216
1469
|
var configBeta1mBtn = $("config-beta-1m");
|
|
1217
1470
|
|
|
1471
|
+
var configThinkingSection = $("config-thinking-section");
|
|
1472
|
+
var configThinkingBar = $("config-thinking-bar");
|
|
1473
|
+
var configThinkingBudgetRow = $("config-thinking-budget-row");
|
|
1474
|
+
var configThinkingBudgetInput = $("config-thinking-budget");
|
|
1475
|
+
|
|
1218
1476
|
var currentModels = [];
|
|
1219
1477
|
var currentModel = "";
|
|
1220
1478
|
var currentMode = "default";
|
|
1221
1479
|
var currentEffort = "medium";
|
|
1222
1480
|
var currentBetas = [];
|
|
1481
|
+
var currentThinking = "adaptive";
|
|
1482
|
+
var currentThinkingBudget = 10000;
|
|
1223
1483
|
var skipPermsEnabled = false;
|
|
1484
|
+
var isOsUsers = false;
|
|
1224
1485
|
|
|
1225
1486
|
var MODE_OPTIONS = [
|
|
1226
1487
|
{ value: "default", label: "Default" },
|
|
@@ -1230,6 +1491,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1230
1491
|
var MODE_FULL_AUTO = { value: "bypassPermissions", label: "Full auto" };
|
|
1231
1492
|
|
|
1232
1493
|
var EFFORT_LEVELS = ["low", "medium", "high", "max"];
|
|
1494
|
+
var THINKING_OPTIONS = ["disabled", "adaptive", "budget"];
|
|
1233
1495
|
|
|
1234
1496
|
function modelDisplayName(value, models) {
|
|
1235
1497
|
if (!value) return "";
|
|
@@ -1255,6 +1517,13 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1255
1517
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
1256
1518
|
}
|
|
1257
1519
|
|
|
1520
|
+
function thinkingDisplayName(value) {
|
|
1521
|
+
if (value === "disabled") return "Off";
|
|
1522
|
+
if (value === "adaptive") return "Adaptive";
|
|
1523
|
+
if (value === "budget") return "Budget";
|
|
1524
|
+
return value || "Adaptive";
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1258
1527
|
function isSonnetModel(model) {
|
|
1259
1528
|
if (!model) return false;
|
|
1260
1529
|
var lower = model.toLowerCase();
|
|
@@ -1278,6 +1547,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1278
1547
|
if (modelSupportsEffort) {
|
|
1279
1548
|
parts.push(effortDisplayName(currentEffort));
|
|
1280
1549
|
}
|
|
1550
|
+
if (currentThinking && currentThinking !== "adaptive") {
|
|
1551
|
+
parts.push(thinkingDisplayName(currentThinking));
|
|
1552
|
+
}
|
|
1281
1553
|
if (hasBeta("context-1m")) {
|
|
1282
1554
|
parts.push("1M");
|
|
1283
1555
|
}
|
|
@@ -1285,6 +1557,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1285
1557
|
rebuildModelList();
|
|
1286
1558
|
rebuildModeList();
|
|
1287
1559
|
rebuildEffortBar();
|
|
1560
|
+
rebuildThinkingSection();
|
|
1288
1561
|
rebuildBetaSection();
|
|
1289
1562
|
}
|
|
1290
1563
|
|
|
@@ -1405,6 +1678,51 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1405
1678
|
configBeta1mBtn.setAttribute("aria-checked", active ? "true" : "false");
|
|
1406
1679
|
}
|
|
1407
1680
|
|
|
1681
|
+
function rebuildThinkingSection() {
|
|
1682
|
+
if (!configThinkingBar || !configThinkingSection) return;
|
|
1683
|
+
configThinkingSection.style.display = "";
|
|
1684
|
+
configThinkingBar.innerHTML = "";
|
|
1685
|
+
for (var i = 0; i < THINKING_OPTIONS.length; i++) {
|
|
1686
|
+
var opt = THINKING_OPTIONS[i];
|
|
1687
|
+
var btn = document.createElement("button");
|
|
1688
|
+
btn.className = "config-segment-btn";
|
|
1689
|
+
if (opt === currentThinking) btn.classList.add("active");
|
|
1690
|
+
btn.dataset.thinking = opt;
|
|
1691
|
+
btn.textContent = thinkingDisplayName(opt);
|
|
1692
|
+
btn.addEventListener("click", function () {
|
|
1693
|
+
var thinking = this.dataset.thinking;
|
|
1694
|
+
var msg = { type: "set_thinking", thinking: thinking };
|
|
1695
|
+
if (thinking === "budget") {
|
|
1696
|
+
msg.budgetTokens = currentThinkingBudget;
|
|
1697
|
+
}
|
|
1698
|
+
if (ws && ws.readyState === 1) {
|
|
1699
|
+
ws.send(JSON.stringify(msg));
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
configThinkingBar.appendChild(btn);
|
|
1703
|
+
}
|
|
1704
|
+
// Show/hide budget input
|
|
1705
|
+
if (configThinkingBudgetRow) {
|
|
1706
|
+
configThinkingBudgetRow.style.display = currentThinking === "budget" ? "" : "none";
|
|
1707
|
+
}
|
|
1708
|
+
if (configThinkingBudgetInput) {
|
|
1709
|
+
configThinkingBudgetInput.value = currentThinkingBudget;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
if (configThinkingBudgetInput) {
|
|
1714
|
+
configThinkingBudgetInput.addEventListener("change", function () {
|
|
1715
|
+
var val = parseInt(this.value, 10);
|
|
1716
|
+
if (isNaN(val) || val < 1024) val = 1024;
|
|
1717
|
+
if (val > 128000) val = 128000;
|
|
1718
|
+
currentThinkingBudget = val;
|
|
1719
|
+
this.value = val;
|
|
1720
|
+
if (ws && ws.readyState === 1) {
|
|
1721
|
+
ws.send(JSON.stringify({ type: "set_thinking", thinking: "budget", budgetTokens: val }));
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1408
1726
|
configBeta1mBtn.addEventListener("click", function (e) {
|
|
1409
1727
|
e.stopPropagation();
|
|
1410
1728
|
var active = hasBeta("context-1m");
|
|
@@ -1790,6 +2108,20 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1790
2108
|
forceScrollToBottom();
|
|
1791
2109
|
});
|
|
1792
2110
|
|
|
2111
|
+
// Fork session from a user message
|
|
2112
|
+
messagesEl.addEventListener("click", function(e) {
|
|
2113
|
+
var btn = e.target.closest(".msg-action-fork");
|
|
2114
|
+
if (!btn) return;
|
|
2115
|
+
var msgEl = btn.closest("[data-uuid]");
|
|
2116
|
+
if (!msgEl || !msgEl.dataset.uuid) return;
|
|
2117
|
+
var forkUuid = msgEl.dataset.uuid;
|
|
2118
|
+
showConfirm("Fork session from this message?", function() {
|
|
2119
|
+
if (ws && ws.readyState === 1) {
|
|
2120
|
+
ws.send(JSON.stringify({ type: "fork_session", uuid: forkUuid }));
|
|
2121
|
+
}
|
|
2122
|
+
}, "Fork", false);
|
|
2123
|
+
});
|
|
2124
|
+
|
|
1793
2125
|
function scrollToBottom() {
|
|
1794
2126
|
if (prependAnchor) return;
|
|
1795
2127
|
if (isUserScrolledUp) {
|
|
@@ -1897,7 +2229,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
1897
2229
|
actions.innerHTML =
|
|
1898
2230
|
'<span class="msg-action-time">' + timeStr + '</span>' +
|
|
1899
2231
|
'<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + iconHtml("copy") + '</button>' +
|
|
1900
|
-
'<button class="msg-action-btn msg-action-
|
|
2232
|
+
'<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + iconHtml("git-branch") + '</button>' +
|
|
1901
2233
|
'<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + iconHtml("rotate-ccw") + '</button>' +
|
|
1902
2234
|
'<button class="msg-action-btn msg-action-hidden msg-action-edit" type="button" title="Edit">' + iconHtml("pencil") + '</button>';
|
|
1903
2235
|
div.appendChild(actions);
|
|
@@ -2137,6 +2469,50 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2137
2469
|
scrollToBottom();
|
|
2138
2470
|
}
|
|
2139
2471
|
|
|
2472
|
+
// Pending command to run in the next created terminal
|
|
2473
|
+
var pendingTermCommand = null;
|
|
2474
|
+
|
|
2475
|
+
function addAuthRequiredMessage(msg) {
|
|
2476
|
+
var div = document.createElement("div");
|
|
2477
|
+
div.className = "auth-required-msg";
|
|
2478
|
+
|
|
2479
|
+
var header = document.createElement("div");
|
|
2480
|
+
header.className = "auth-required-header";
|
|
2481
|
+
header.textContent = msg.text || "Claude Code is not logged in.";
|
|
2482
|
+
div.appendChild(header);
|
|
2483
|
+
|
|
2484
|
+
var hint = document.createElement("div");
|
|
2485
|
+
hint.className = "auth-required-hint";
|
|
2486
|
+
|
|
2487
|
+
if (msg.canAutoLogin) {
|
|
2488
|
+
// Auto-open terminal and run claude
|
|
2489
|
+
if (msg.linuxUser) {
|
|
2490
|
+
hint.textContent = "Opening a terminal as " + msg.linuxUser + " to log in...";
|
|
2491
|
+
} else {
|
|
2492
|
+
hint.textContent = "Opening a terminal to log in...";
|
|
2493
|
+
}
|
|
2494
|
+
div.appendChild(hint);
|
|
2495
|
+
|
|
2496
|
+
var guide = document.createElement("div");
|
|
2497
|
+
guide.className = "auth-required-guide";
|
|
2498
|
+
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.";
|
|
2499
|
+
div.appendChild(guide);
|
|
2500
|
+
|
|
2501
|
+
addToMessages(div);
|
|
2502
|
+
scrollToBottom();
|
|
2503
|
+
|
|
2504
|
+
pendingTermCommand = "claude\n";
|
|
2505
|
+
ws.send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
|
|
2506
|
+
openTerminal();
|
|
2507
|
+
} else {
|
|
2508
|
+
// Multi-user regular user: show message only, no auto-login
|
|
2509
|
+
hint.textContent = "Please ask an administrator to log in to Claude Code.";
|
|
2510
|
+
div.appendChild(hint);
|
|
2511
|
+
addToMessages(div);
|
|
2512
|
+
scrollToBottom();
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2140
2516
|
// --- Rate Limit ---
|
|
2141
2517
|
|
|
2142
2518
|
var rateLimitCountdownTimer = null;
|
|
@@ -2358,6 +2734,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2358
2734
|
// --- Project switching (no full reload) ---
|
|
2359
2735
|
function switchProject(slug) {
|
|
2360
2736
|
if (!slug) return;
|
|
2737
|
+
if (dmMode) exitDmMode();
|
|
2361
2738
|
if (homeHubVisible) {
|
|
2362
2739
|
hideHomeHub();
|
|
2363
2740
|
if (slug === currentSlug) return;
|
|
@@ -2573,6 +2950,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2573
2950
|
var vEl = $("footer-version");
|
|
2574
2951
|
if (vEl) vEl.textContent = "v" + msg.version;
|
|
2575
2952
|
}
|
|
2953
|
+
if (msg.projectOwnerId !== undefined) currentProjectOwnerId = msg.projectOwnerId;
|
|
2954
|
+
if (msg.osUsers !== undefined) isOsUsers = !!msg.osUsers;
|
|
2576
2955
|
if (msg.lanHost) window.__lanHost = msg.lanHost;
|
|
2577
2956
|
if (msg.dangerouslySkipPermissions) {
|
|
2578
2957
|
skipPermsEnabled = true;
|
|
@@ -2594,6 +2973,12 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2594
2973
|
updResetBtn.innerHTML = '<i data-lucide="download"></i> Update now';
|
|
2595
2974
|
updResetBtn.disabled = false;
|
|
2596
2975
|
}
|
|
2976
|
+
// Update manual command based on version (beta vs stable)
|
|
2977
|
+
var updManualCmd = $("update-manual-cmd");
|
|
2978
|
+
if (updManualCmd) {
|
|
2979
|
+
var updTag = msg.version.indexOf("-beta") !== -1 ? "beta" : "latest";
|
|
2980
|
+
updManualCmd.textContent = "npx clay-server@" + updTag;
|
|
2981
|
+
}
|
|
2597
2982
|
refreshIcons();
|
|
2598
2983
|
}
|
|
2599
2984
|
// Update the settings check-for-updates button
|
|
@@ -2644,6 +3029,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2644
3029
|
if (msg.mode) currentMode = msg.mode;
|
|
2645
3030
|
if (msg.effort) currentEffort = msg.effort;
|
|
2646
3031
|
if (msg.betas) currentBetas = msg.betas;
|
|
3032
|
+
if (msg.thinking) currentThinking = msg.thinking;
|
|
3033
|
+
if (msg.thinkingBudget) currentThinkingBudget = msg.thinkingBudget;
|
|
2647
3034
|
// Validate effort against current model's supported levels
|
|
2648
3035
|
if (currentModels.length > 0) {
|
|
2649
3036
|
var levels = getModelEffortLevels();
|
|
@@ -2724,7 +3111,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2724
3111
|
break;
|
|
2725
3112
|
|
|
2726
3113
|
case "input_sync":
|
|
2727
|
-
handleInputSync(msg.text);
|
|
3114
|
+
if (!dmMode) handleInputSync(msg.text);
|
|
2728
3115
|
break;
|
|
2729
3116
|
|
|
2730
3117
|
case "session_list":
|
|
@@ -2932,6 +3319,16 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2932
3319
|
startUrgentBlink();
|
|
2933
3320
|
break;
|
|
2934
3321
|
|
|
3322
|
+
case "elicitation_request":
|
|
3323
|
+
renderElicitationRequest(msg);
|
|
3324
|
+
startUrgentBlink();
|
|
3325
|
+
break;
|
|
3326
|
+
|
|
3327
|
+
case "elicitation_resolved":
|
|
3328
|
+
markElicitationResolved(msg.requestId, msg.action);
|
|
3329
|
+
stopUrgentBlink();
|
|
3330
|
+
break;
|
|
3331
|
+
|
|
2935
3332
|
case "slash_command_result":
|
|
2936
3333
|
finalizeAssistantBlock();
|
|
2937
3334
|
var cmdBlock = document.createElement("div");
|
|
@@ -2964,7 +3361,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
2964
3361
|
break;
|
|
2965
3362
|
|
|
2966
3363
|
case "task_progress":
|
|
2967
|
-
updateSubagentProgress(msg.parentToolId, msg.usage, msg.lastToolName);
|
|
3364
|
+
updateSubagentProgress(msg.parentToolId, msg.usage, msg.lastToolName, msg.summary);
|
|
2968
3365
|
break;
|
|
2969
3366
|
|
|
2970
3367
|
case "result":
|
|
@@ -3014,6 +3411,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3014
3411
|
addContextOverflowMessage(msg);
|
|
3015
3412
|
break;
|
|
3016
3413
|
|
|
3414
|
+
case "auth_required":
|
|
3415
|
+
setActivity(null);
|
|
3416
|
+
addAuthRequiredMessage(msg);
|
|
3417
|
+
break;
|
|
3418
|
+
|
|
3017
3419
|
case "rate_limit":
|
|
3018
3420
|
handleRateLimitEvent(msg);
|
|
3019
3421
|
break;
|
|
@@ -3047,6 +3449,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3047
3449
|
addSystemMessage(msg.text || "Rewind failed.", true);
|
|
3048
3450
|
break;
|
|
3049
3451
|
|
|
3452
|
+
case "fork_complete":
|
|
3453
|
+
addSystemMessage("Session forked successfully.");
|
|
3454
|
+
break;
|
|
3455
|
+
|
|
3050
3456
|
case "fs_list_result":
|
|
3051
3457
|
handleFsList(msg);
|
|
3052
3458
|
break;
|
|
@@ -3115,6 +3521,14 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3115
3521
|
|
|
3116
3522
|
case "term_created":
|
|
3117
3523
|
handleTermCreated(msg);
|
|
3524
|
+
if (pendingTermCommand) {
|
|
3525
|
+
var cmd = pendingTermCommand;
|
|
3526
|
+
pendingTermCommand = null;
|
|
3527
|
+
// Small delay to let terminal initialize
|
|
3528
|
+
setTimeout(function() {
|
|
3529
|
+
sendTerminalCommand(cmd);
|
|
3530
|
+
}, 300);
|
|
3531
|
+
}
|
|
3118
3532
|
break;
|
|
3119
3533
|
|
|
3120
3534
|
case "term_output":
|
|
@@ -3158,6 +3572,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3158
3572
|
handleAddProjectResult(msg);
|
|
3159
3573
|
break;
|
|
3160
3574
|
|
|
3575
|
+
case "clone_project_progress":
|
|
3576
|
+
handleCloneProgress(msg);
|
|
3577
|
+
break;
|
|
3578
|
+
|
|
3161
3579
|
case "remove_project_result":
|
|
3162
3580
|
handleRemoveProjectResult(msg);
|
|
3163
3581
|
break;
|
|
@@ -3184,6 +3602,41 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3184
3602
|
updateProjectList(msg);
|
|
3185
3603
|
break;
|
|
3186
3604
|
|
|
3605
|
+
case "project_owner_changed":
|
|
3606
|
+
currentProjectOwnerId = msg.ownerId;
|
|
3607
|
+
handleProjectOwnerChanged(msg);
|
|
3608
|
+
break;
|
|
3609
|
+
|
|
3610
|
+
// --- DM ---
|
|
3611
|
+
case "dm_history":
|
|
3612
|
+
enterDmMode(msg.dmKey, msg.targetUser, msg.messages);
|
|
3613
|
+
break;
|
|
3614
|
+
|
|
3615
|
+
case "dm_message":
|
|
3616
|
+
if (dmMode && msg.dmKey === dmKey) {
|
|
3617
|
+
showDmTypingIndicator(false); // hide typing when message arrives
|
|
3618
|
+
appendDmMessage(msg.message);
|
|
3619
|
+
scrollToBottom();
|
|
3620
|
+
} else if (msg.message) {
|
|
3621
|
+
// DM notification when not in that DM
|
|
3622
|
+
var fromId = msg.message.from;
|
|
3623
|
+
if (fromId && fromId !== myUserId) {
|
|
3624
|
+
dmUnread[fromId] = (dmUnread[fromId] || 0) + 1;
|
|
3625
|
+
updateDmBadge(fromId, dmUnread[fromId]);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
break;
|
|
3629
|
+
|
|
3630
|
+
case "dm_typing":
|
|
3631
|
+
if (dmMode && msg.dmKey === dmKey) {
|
|
3632
|
+
showDmTypingIndicator(msg.typing);
|
|
3633
|
+
}
|
|
3634
|
+
break;
|
|
3635
|
+
|
|
3636
|
+
case "dm_list":
|
|
3637
|
+
// Could be used for DM list view later
|
|
3638
|
+
break;
|
|
3639
|
+
|
|
3187
3640
|
case "daemon_config":
|
|
3188
3641
|
updateDaemonConfig(msg.config);
|
|
3189
3642
|
break;
|
|
@@ -3488,6 +3941,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3488
3941
|
showImageModal: showImageModal,
|
|
3489
3942
|
hideSuggestionChips: hideSuggestionChips,
|
|
3490
3943
|
setSendBtnMode: setSendBtnMode,
|
|
3944
|
+
isDmMode: function () { return dmMode; },
|
|
3945
|
+
getDmKey: function () { return dmKey; },
|
|
3946
|
+
handleDmSend: function () { handleDmSend(); },
|
|
3491
3947
|
});
|
|
3492
3948
|
|
|
3493
3949
|
// --- STT module (voice input via Web Speech API) ---
|
|
@@ -3501,6 +3957,105 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3501
3957
|
basePath: basePath,
|
|
3502
3958
|
});
|
|
3503
3959
|
|
|
3960
|
+
// --- Force PIN change overlay (for admin-created accounts with temp PIN) ---
|
|
3961
|
+
function showForceChangePinOverlay() {
|
|
3962
|
+
var ov = document.createElement("div");
|
|
3963
|
+
ov.id = "force-change-pin-overlay";
|
|
3964
|
+
ov.style.cssText = "position:fixed;inset:0;background:var(--bg,#0e0e10);z-index:99999;display:flex;align-items:center;justify-content:center;flex-direction:column";
|
|
3965
|
+
ov.innerHTML = '<div style="width:100%;max-width:380px;padding:24px;text-align:center">' +
|
|
3966
|
+
'<h2 style="margin:0 0 8px;color:var(--text,#fff);font-size:22px">Set your new PIN</h2>' +
|
|
3967
|
+
'<p style="margin:0 0 24px;color:var(--text-secondary,#aaa);font-size:14px">Your temporary PIN has expired. Please set a new 6-digit PIN to continue.</p>' +
|
|
3968
|
+
'<div style="display:flex;gap:8px;justify-content:center;margin-bottom:16px" id="fcp-boxes">' +
|
|
3969
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
|
|
3970
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
|
|
3971
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
|
|
3972
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
|
|
3973
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
|
|
3974
|
+
'<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
|
|
3975
|
+
'</div>' +
|
|
3976
|
+
'<button id="fcp-save" disabled style="width:100%;padding:12px;border:none;border-radius:10px;background:var(--accent,#7c3aed);color:#fff;font-size:15px;font-weight:600;cursor:pointer;opacity:0.5">Save PIN</button>' +
|
|
3977
|
+
'<div id="fcp-err" style="margin-top:12px;color:#ef4444;font-size:13px"></div>' +
|
|
3978
|
+
'</div>';
|
|
3979
|
+
document.body.appendChild(ov);
|
|
3980
|
+
|
|
3981
|
+
var digits = ov.querySelectorAll(".fcp-digit");
|
|
3982
|
+
var saveBtn = ov.querySelector("#fcp-save");
|
|
3983
|
+
var errEl = ov.querySelector("#fcp-err");
|
|
3984
|
+
|
|
3985
|
+
function getPin() {
|
|
3986
|
+
var pin = "";
|
|
3987
|
+
for (var i = 0; i < digits.length; i++) pin += digits[i].value;
|
|
3988
|
+
return pin;
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
function updateBtn() {
|
|
3992
|
+
var ready = getPin().length === 6;
|
|
3993
|
+
saveBtn.disabled = !ready;
|
|
3994
|
+
saveBtn.style.opacity = ready ? "1" : "0.5";
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
for (var i = 0; i < digits.length; i++) {
|
|
3998
|
+
(function (idx) {
|
|
3999
|
+
digits[idx].addEventListener("input", function () {
|
|
4000
|
+
var val = this.value.replace(/\D/g, "");
|
|
4001
|
+
this.value = val.substring(0, 1);
|
|
4002
|
+
if (val && idx < digits.length - 1) digits[idx + 1].focus();
|
|
4003
|
+
updateBtn();
|
|
4004
|
+
});
|
|
4005
|
+
digits[idx].addEventListener("keydown", function (e) {
|
|
4006
|
+
if (e.key === "Backspace" && !this.value && idx > 0) {
|
|
4007
|
+
digits[idx - 1].focus();
|
|
4008
|
+
digits[idx - 1].value = "";
|
|
4009
|
+
updateBtn();
|
|
4010
|
+
}
|
|
4011
|
+
if (e.key === "Enter" && !saveBtn.disabled) doSave();
|
|
4012
|
+
e.stopPropagation();
|
|
4013
|
+
});
|
|
4014
|
+
digits[idx].addEventListener("keyup", function (e) { e.stopPropagation(); });
|
|
4015
|
+
digits[idx].addEventListener("keypress", function (e) { e.stopPropagation(); });
|
|
4016
|
+
digits[idx].addEventListener("paste", function (e) {
|
|
4017
|
+
e.preventDefault();
|
|
4018
|
+
var text = (e.clipboardData || window.clipboardData).getData("text").replace(/\D/g, "").substring(0, 6);
|
|
4019
|
+
for (var j = 0; j < text.length && (idx + j) < digits.length; j++) {
|
|
4020
|
+
digits[idx + j].value = text[j];
|
|
4021
|
+
}
|
|
4022
|
+
if (text.length > 0) {
|
|
4023
|
+
var focusIdx = Math.min(idx + text.length, digits.length - 1);
|
|
4024
|
+
digits[focusIdx].focus();
|
|
4025
|
+
}
|
|
4026
|
+
updateBtn();
|
|
4027
|
+
});
|
|
4028
|
+
})(i);
|
|
4029
|
+
}
|
|
4030
|
+
digits[0].focus();
|
|
4031
|
+
|
|
4032
|
+
function doSave() {
|
|
4033
|
+
var pin = getPin();
|
|
4034
|
+
if (pin.length !== 6) return;
|
|
4035
|
+
saveBtn.disabled = true;
|
|
4036
|
+
saveBtn.style.opacity = "0.5";
|
|
4037
|
+
errEl.textContent = "";
|
|
4038
|
+
fetch("/api/user/pin", {
|
|
4039
|
+
method: "PUT",
|
|
4040
|
+
headers: { "Content-Type": "application/json" },
|
|
4041
|
+
body: JSON.stringify({ newPin: pin }),
|
|
4042
|
+
}).then(function (r) { return r.json(); }).then(function (d) {
|
|
4043
|
+
if (d.ok) {
|
|
4044
|
+
ov.remove();
|
|
4045
|
+
return;
|
|
4046
|
+
}
|
|
4047
|
+
errEl.textContent = d.error || "Failed to save PIN";
|
|
4048
|
+
saveBtn.disabled = false;
|
|
4049
|
+
saveBtn.style.opacity = "1";
|
|
4050
|
+
}).catch(function () {
|
|
4051
|
+
errEl.textContent = "Connection error";
|
|
4052
|
+
saveBtn.disabled = false;
|
|
4053
|
+
saveBtn.style.opacity = "1";
|
|
4054
|
+
});
|
|
4055
|
+
}
|
|
4056
|
+
saveBtn.addEventListener("click", doSave);
|
|
4057
|
+
}
|
|
4058
|
+
|
|
3504
4059
|
// --- Admin (multi-user mode) ---
|
|
3505
4060
|
var isMultiUserMode = false;
|
|
3506
4061
|
var myUserId = null;
|
|
@@ -3510,6 +4065,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3510
4065
|
fetch("/api/me").then(function (r) { return r.json(); }).then(function (d) {
|
|
3511
4066
|
if (d.multiUser) isMultiUserMode = true;
|
|
3512
4067
|
if (d.user && d.user.id) myUserId = d.user.id;
|
|
4068
|
+
if (d.mustChangePin) showForceChangePinOverlay();
|
|
3513
4069
|
}).catch(function () {});
|
|
3514
4070
|
// Hide server settings and update controls for non-admin users in multi-user mode
|
|
3515
4071
|
checkAdminAccess().then(function (isAdmin) {
|
|
@@ -3546,6 +4102,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3546
4102
|
get currentMode() { return currentMode; },
|
|
3547
4103
|
get currentEffort() { return currentEffort; },
|
|
3548
4104
|
get currentBetas() { return currentBetas; },
|
|
4105
|
+
get currentThinking() { return currentThinking; },
|
|
4106
|
+
get currentThinkingBudget() { return currentThinkingBudget; },
|
|
3549
4107
|
setContextView: setContextView,
|
|
3550
4108
|
applyContextView: applyContextView,
|
|
3551
4109
|
});
|
|
@@ -3559,6 +4117,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
3559
4117
|
get currentMode() { return currentMode; },
|
|
3560
4118
|
get currentEffort() { return currentEffort; },
|
|
3561
4119
|
get currentBetas() { return currentBetas; },
|
|
4120
|
+
get currentThinking() { return currentThinking; },
|
|
4121
|
+
get currentThinkingBudget() { return currentThinkingBudget; },
|
|
3562
4122
|
}, getEmojiCategories());
|
|
3563
4123
|
|
|
3564
4124
|
// --- QR code ---
|
|
@@ -4544,34 +5104,98 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
4544
5104
|
// --- Add project modal ---
|
|
4545
5105
|
var addProjectModal = document.getElementById("add-project-modal");
|
|
4546
5106
|
var addProjectInput = document.getElementById("add-project-input");
|
|
5107
|
+
var addProjectCreateInput = document.getElementById("add-project-create-input");
|
|
5108
|
+
var addProjectCloneInput = document.getElementById("add-project-clone-input");
|
|
5109
|
+
var addProjectCloneProgress = document.getElementById("add-project-clone-progress");
|
|
4547
5110
|
var addProjectSuggestions = document.getElementById("add-project-suggestions");
|
|
4548
5111
|
var addProjectError = document.getElementById("add-project-error");
|
|
4549
5112
|
var addProjectOk = document.getElementById("add-project-ok");
|
|
4550
5113
|
var addProjectCancel = document.getElementById("add-project-cancel");
|
|
5114
|
+
var addProjectModeBtns = addProjectModal.querySelectorAll(".add-project-mode-btn");
|
|
5115
|
+
var addProjectPanels = addProjectModal.querySelectorAll(".add-project-panel");
|
|
4551
5116
|
var addProjectDebounce = null;
|
|
4552
5117
|
var addProjectActiveIdx = -1;
|
|
5118
|
+
var addProjectMode = "existing";
|
|
5119
|
+
|
|
5120
|
+
function switchAddProjectMode(mode) {
|
|
5121
|
+
addProjectMode = mode;
|
|
5122
|
+
for (var mi = 0; mi < addProjectModeBtns.length; mi++) {
|
|
5123
|
+
var btn = addProjectModeBtns[mi];
|
|
5124
|
+
if (btn.dataset.mode === mode) {
|
|
5125
|
+
btn.classList.add("active");
|
|
5126
|
+
} else {
|
|
5127
|
+
btn.classList.remove("active");
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
for (var pi = 0; pi < addProjectPanels.length; pi++) {
|
|
5131
|
+
var panel = addProjectPanels[pi];
|
|
5132
|
+
if (panel.dataset.panel === mode) {
|
|
5133
|
+
panel.classList.add("active");
|
|
5134
|
+
} else {
|
|
5135
|
+
panel.classList.remove("active");
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
addProjectError.classList.add("hidden");
|
|
5139
|
+
addProjectCloneProgress.classList.add("hidden");
|
|
5140
|
+
// Update OK button text
|
|
5141
|
+
if (mode === "existing") {
|
|
5142
|
+
addProjectOk.textContent = "Add";
|
|
5143
|
+
} else if (mode === "create") {
|
|
5144
|
+
addProjectOk.textContent = "Create";
|
|
5145
|
+
} else if (mode === "clone") {
|
|
5146
|
+
addProjectOk.textContent = "Clone";
|
|
5147
|
+
}
|
|
5148
|
+
// Focus the right input
|
|
5149
|
+
setTimeout(function () {
|
|
5150
|
+
if (mode === "existing") {
|
|
5151
|
+
addProjectInput.focus();
|
|
5152
|
+
} else if (mode === "create") {
|
|
5153
|
+
addProjectCreateInput.focus();
|
|
5154
|
+
} else if (mode === "clone") {
|
|
5155
|
+
addProjectCloneInput.focus();
|
|
5156
|
+
}
|
|
5157
|
+
}, 50);
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
for (var mbi = 0; mbi < addProjectModeBtns.length; mbi++) {
|
|
5161
|
+
addProjectModeBtns[mbi].addEventListener("click", function () {
|
|
5162
|
+
if (this.disabled) return;
|
|
5163
|
+
switchAddProjectMode(this.dataset.mode);
|
|
5164
|
+
});
|
|
5165
|
+
}
|
|
4553
5166
|
|
|
4554
5167
|
function openAddProjectModal() {
|
|
4555
5168
|
addProjectModal.classList.remove("hidden");
|
|
4556
5169
|
addProjectInput.value = "/";
|
|
5170
|
+
addProjectCreateInput.value = "";
|
|
5171
|
+
addProjectCloneInput.value = "";
|
|
4557
5172
|
addProjectError.classList.add("hidden");
|
|
4558
5173
|
addProjectError.textContent = "";
|
|
5174
|
+
addProjectCloneProgress.classList.add("hidden");
|
|
4559
5175
|
addProjectSuggestions.classList.add("hidden");
|
|
4560
5176
|
addProjectSuggestions.innerHTML = "";
|
|
4561
5177
|
addProjectActiveIdx = -1;
|
|
4562
5178
|
addProjectOk.disabled = false;
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
5179
|
+
// In osUsers mode, disable "existing" and default to "create"
|
|
5180
|
+
var existingBtn = addProjectModal.querySelector('.add-project-mode-btn[data-mode="existing"]');
|
|
5181
|
+
if (isOsUsers) {
|
|
5182
|
+
existingBtn.disabled = true;
|
|
5183
|
+
switchAddProjectMode("create");
|
|
5184
|
+
} else {
|
|
5185
|
+
existingBtn.disabled = false;
|
|
5186
|
+
switchAddProjectMode("existing");
|
|
5187
|
+
}
|
|
4567
5188
|
}
|
|
4568
5189
|
|
|
4569
5190
|
function closeAddProjectModal() {
|
|
4570
5191
|
addProjectModal.classList.add("hidden");
|
|
4571
5192
|
addProjectInput.value = "";
|
|
5193
|
+
addProjectCreateInput.value = "";
|
|
5194
|
+
addProjectCloneInput.value = "";
|
|
4572
5195
|
addProjectSuggestions.classList.add("hidden");
|
|
4573
5196
|
addProjectSuggestions.innerHTML = "";
|
|
4574
5197
|
addProjectError.classList.add("hidden");
|
|
5198
|
+
addProjectCloneProgress.classList.add("hidden");
|
|
4575
5199
|
addProjectActiveIdx = -1;
|
|
4576
5200
|
if (addProjectDebounce) { clearTimeout(addProjectDebounce); addProjectDebounce = null; }
|
|
4577
5201
|
}
|
|
@@ -4614,12 +5238,14 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
4614
5238
|
}
|
|
4615
5239
|
|
|
4616
5240
|
function handleAddProjectResult(msg) {
|
|
5241
|
+
addProjectCloneProgress.classList.add("hidden");
|
|
4617
5242
|
if (msg.ok) {
|
|
4618
5243
|
closeAddProjectModal();
|
|
4619
5244
|
if (msg.existing) {
|
|
4620
5245
|
showToast("Project already registered", "info");
|
|
4621
5246
|
} else {
|
|
4622
|
-
|
|
5247
|
+
var toastMsg = addProjectMode === "create" ? "Project created" : addProjectMode === "clone" ? "Project cloned" : "Project added";
|
|
5248
|
+
showToast(toastMsg, "success");
|
|
4623
5249
|
// Navigate to the new project
|
|
4624
5250
|
if (msg.slug) {
|
|
4625
5251
|
switchProject(msg.slug);
|
|
@@ -4632,6 +5258,12 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
4632
5258
|
}
|
|
4633
5259
|
}
|
|
4634
5260
|
|
|
5261
|
+
function handleCloneProgress(msg) {
|
|
5262
|
+
if (msg.status === "cloning") {
|
|
5263
|
+
addProjectCloneProgress.classList.remove("hidden");
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
|
|
4635
5267
|
function setActiveIdx(idx) {
|
|
4636
5268
|
var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
|
|
4637
5269
|
addProjectActiveIdx = idx;
|
|
@@ -4727,13 +5359,39 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
|
4727
5359
|
}
|
|
4728
5360
|
});
|
|
4729
5361
|
|
|
5362
|
+
// Enter key on create/clone inputs
|
|
5363
|
+
addProjectCreateInput.addEventListener("keydown", function (e) {
|
|
5364
|
+
if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
|
|
5365
|
+
if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
|
|
5366
|
+
});
|
|
5367
|
+
|
|
5368
|
+
addProjectCloneInput.addEventListener("keydown", function (e) {
|
|
5369
|
+
if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
|
|
5370
|
+
if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
|
|
5371
|
+
});
|
|
5372
|
+
|
|
4730
5373
|
function submitAddProject() {
|
|
4731
|
-
var val = addProjectInput.value.replace(/\/+$/, "");
|
|
4732
|
-
if (!val) return;
|
|
4733
|
-
addProjectOk.disabled = true;
|
|
4734
5374
|
addProjectError.classList.add("hidden");
|
|
4735
|
-
|
|
4736
|
-
|
|
5375
|
+
addProjectOk.disabled = true;
|
|
5376
|
+
|
|
5377
|
+
if (addProjectMode === "existing") {
|
|
5378
|
+
var val = addProjectInput.value.replace(/\/+$/, "");
|
|
5379
|
+
if (!val) { addProjectOk.disabled = false; return; }
|
|
5380
|
+
if (ws && ws.readyState === 1) {
|
|
5381
|
+
ws.send(JSON.stringify({ type: "add_project", path: val }));
|
|
5382
|
+
}
|
|
5383
|
+
} else if (addProjectMode === "create") {
|
|
5384
|
+
var name = addProjectCreateInput.value.trim();
|
|
5385
|
+
if (!name) { addProjectOk.disabled = false; return; }
|
|
5386
|
+
if (ws && ws.readyState === 1) {
|
|
5387
|
+
ws.send(JSON.stringify({ type: "create_project", name: name }));
|
|
5388
|
+
}
|
|
5389
|
+
} else if (addProjectMode === "clone") {
|
|
5390
|
+
var url = addProjectCloneInput.value.trim();
|
|
5391
|
+
if (!url) { addProjectOk.disabled = false; return; }
|
|
5392
|
+
if (ws && ws.readyState === 1) {
|
|
5393
|
+
ws.send(JSON.stringify({ type: "clone_project", url: url }));
|
|
5394
|
+
}
|
|
4737
5395
|
}
|
|
4738
5396
|
}
|
|
4739
5397
|
|