clay-server 2.26.0-beta.1 → 2.26.0-beta.11
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 +5 -9
- package/lib/browser-mcp-server.js +496 -0
- package/lib/daemon.js +1 -1
- package/lib/os-users.js +23 -0
- package/lib/project-debate.js +243 -95
- package/lib/project-mate-interaction.js +766 -0
- package/lib/project-memory.js +677 -0
- package/lib/project.js +546 -1361
- package/lib/public/app.js +817 -175
- package/lib/public/css/debate.css +224 -2
- package/lib/public/css/icon-strip.css +10 -10
- package/lib/public/css/input.css +296 -83
- package/lib/public/css/mates.css +56 -57
- package/lib/public/css/mention.css +7 -4
- package/lib/public/css/menus.css +7 -0
- package/lib/public/css/messages.css +17 -0
- package/lib/public/css/mobile-nav.css +3 -1
- package/lib/public/css/overlays.css +181 -0
- package/lib/public/css/rewind.css +79 -0
- package/lib/public/css/server-settings.css +1 -0
- package/lib/public/css/sidebar.css +10 -0
- package/lib/public/css/title-bar.css +189 -3
- package/lib/public/index.html +53 -16
- package/lib/public/modules/context-sources.js +328 -0
- package/lib/public/modules/debate.js +184 -97
- package/lib/public/modules/input.js +18 -1
- package/lib/public/modules/mate-knowledge.js +11 -11
- package/lib/public/modules/mate-memory.js +5 -5
- package/lib/public/modules/mate-sidebar.js +13 -9
- package/lib/public/modules/mention.js +40 -2
- package/lib/public/modules/notifications.js +109 -1
- package/lib/public/modules/rewind.js +36 -0
- package/lib/public/modules/sidebar.js +107 -28
- package/lib/public/modules/terminal.js +8 -0
- package/lib/public/modules/theme.js +2 -1
- package/lib/public/modules/tools.js +69 -24
- package/lib/sdk-bridge.js +81 -7
- package/lib/sdk-worker.js +13 -1
- package/lib/server.js +42 -0
- package/lib/sessions.js +39 -7
- package/lib/terminal-manager.js +36 -6
- package/package.json +2 -2
package/lib/public/app.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { avatarUrl, userAvatarUrl, mateAvatarUrl } from './modules/avatar.js';
|
|
2
2
|
import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
|
|
3
|
-
import { refreshIcons, iconHtml
|
|
3
|
+
import { refreshIcons, iconHtml } from './modules/icons.js';
|
|
4
4
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
|
|
5
|
-
import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge, updateSessionBadge, updateProjectBadge, closeDmUserPicker, spawnDustParticles, openMobileSheet, setMobileSheetMateData } from './modules/sidebar.js';
|
|
5
|
+
import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge, updateSessionBadge, updateProjectBadge, closeDmUserPicker, spawnDustParticles, openMobileSheet, setMobileSheetMateData, refreshMobileChatSheet } from './modules/sidebar.js';
|
|
6
6
|
import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
|
|
7
7
|
import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
|
|
8
8
|
import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-memory.js';
|
|
9
|
-
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
|
|
9
|
+
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton, onRewindComplete, onRewindError } from './modules/rewind.js';
|
|
10
10
|
import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
|
|
11
11
|
import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage, hasSendableContent, setScheduleBtnDisabled, setScheduleDelayMs, clearScheduleDelay } from './modules/input.js';
|
|
12
12
|
import { initQrCode, triggerShare } from './modules/qrcode.js';
|
|
13
13
|
import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
|
|
14
|
-
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
|
|
14
|
+
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
|
|
15
|
+
import { initContextSources, updateTerminalList, updateBrowserTabList, handleContextSourcesState, getActiveSources, hasActiveSources } from './modules/context-sources.js';
|
|
15
16
|
import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible } from './modules/sticky-notes.js';
|
|
16
|
-
import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
|
|
17
|
-
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';
|
|
17
|
+
import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme, getChatLayout } from './modules/theme.js';
|
|
18
|
+
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, resetTurnMetaCost, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
|
|
18
19
|
import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
|
|
19
20
|
import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './modules/project-settings.js';
|
|
20
21
|
import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
|
|
@@ -31,7 +32,7 @@ import { initMateWizard, openMateWizard, closeMateWizard, handleMateCreated } fr
|
|
|
31
32
|
import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } from './modules/command-palette.js';
|
|
32
33
|
import { initLongPress } from './modules/longpress.js';
|
|
33
34
|
import { initMention, handleMentionStart, handleMentionStream, handleMentionDone, handleMentionError, handleMentionActivity, renderMentionUser, renderMentionResponse } from './modules/mention.js';
|
|
34
|
-
import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState } from './modules/debate.js';
|
|
35
|
+
import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState, exportDebateAsPdf } from './modules/debate.js';
|
|
35
36
|
|
|
36
37
|
// --- Base path for multi-project routing ---
|
|
37
38
|
var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
|
|
@@ -564,6 +565,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
564
565
|
// --- DM Mode Functions ---
|
|
565
566
|
function openDm(targetUserId) {
|
|
566
567
|
if (!ws || ws.readyState !== 1) return;
|
|
568
|
+
// Persist DM state for refresh recovery
|
|
569
|
+
try { localStorage.setItem("clay-active-dm", targetUserId); } catch (e) {}
|
|
567
570
|
// Check mate skill updates before opening mate DM
|
|
568
571
|
if (typeof targetUserId === "string" && targetUserId.indexOf("mate_") === 0) {
|
|
569
572
|
showMateOnboarding(function () {
|
|
@@ -815,6 +818,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
815
818
|
dmMode = false;
|
|
816
819
|
dmKey = null;
|
|
817
820
|
dmTargetUser = null;
|
|
821
|
+
try { localStorage.removeItem("clay-active-dm"); } catch (e) {}
|
|
818
822
|
setCurrentDmUser(null);
|
|
819
823
|
|
|
820
824
|
var mainCol = document.getElementById("main-column");
|
|
@@ -1797,9 +1801,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1797
1801
|
};
|
|
1798
1802
|
initSidebar(sidebarCtx);
|
|
1799
1803
|
initIconStrip(sidebarCtx);
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1804
|
+
var wsGetter = function () { return ws; };
|
|
1805
|
+
initMateSidebar(wsGetter);
|
|
1806
|
+
initMateKnowledge(wsGetter);
|
|
1807
|
+
initMateMemory(wsGetter, { onShow: function () { hideKnowledge(); hideNotes(); } });
|
|
1803
1808
|
initMateWizard(
|
|
1804
1809
|
function (msg) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg)); },
|
|
1805
1810
|
function (mate) { handleMateCreatedInApp(mate); }
|
|
@@ -2082,12 +2087,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2082
2087
|
activityEl = document.createElement("div");
|
|
2083
2088
|
activityEl.className = "activity-inline";
|
|
2084
2089
|
activityEl.innerHTML =
|
|
2085
|
-
'<
|
|
2086
|
-
'<span class="activity-text"></span>';
|
|
2090
|
+
'<div class="mate-thinking-dots"><span></span><span></span><span></span></div>';
|
|
2087
2091
|
addToMessages(activityEl);
|
|
2088
|
-
refreshIcons();
|
|
2089
2092
|
}
|
|
2090
|
-
activityEl.querySelector(".activity-text").textContent = text;
|
|
2091
2093
|
scrollToBottom();
|
|
2092
2094
|
} else {
|
|
2093
2095
|
if (activityEl) {
|
|
@@ -2097,9 +2099,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2097
2099
|
}
|
|
2098
2100
|
}
|
|
2099
2101
|
|
|
2100
|
-
// ---
|
|
2102
|
+
// --- Pre-thinking (instant dots before server responds) ---
|
|
2101
2103
|
var matePreThinkingEl = null;
|
|
2102
2104
|
var matePreThinkingTimer = null;
|
|
2105
|
+
function showClaudePreThinking() {
|
|
2106
|
+
if (getChatLayout() !== "channel") return;
|
|
2107
|
+
var claudeAvatar = CLAUDE_CODE_AVATAR;
|
|
2108
|
+
doShowMatePreThinking("Claude Code", claudeAvatar);
|
|
2109
|
+
}
|
|
2103
2110
|
function showMatePreThinking() {
|
|
2104
2111
|
removeMatePreThinking();
|
|
2105
2112
|
var mateName = dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate";
|
|
@@ -2113,10 +2120,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2113
2120
|
'<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="" style="display:block">' +
|
|
2114
2121
|
'<div class="dm-bubble-content">' +
|
|
2115
2122
|
'<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
|
|
2116
|
-
'<div class="
|
|
2117
|
-
'<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
|
|
2118
|
-
'<span class="activity-text">' + randomThinkingVerb() + '...</span>' +
|
|
2119
|
-
'</div>' +
|
|
2123
|
+
'<div class="mate-thinking-dots"><span></span><span></span><span></span></div>' +
|
|
2120
2124
|
'</div>';
|
|
2121
2125
|
if (activityEl && activityEl.parentNode) {
|
|
2122
2126
|
activityEl.parentNode.insertBefore(matePreThinkingEl, activityEl);
|
|
@@ -2468,7 +2472,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2468
2472
|
}
|
|
2469
2473
|
|
|
2470
2474
|
function accumulateUsage(cost, usage) {
|
|
2471
|
-
|
|
2475
|
+
// cost is the SDK's total_cost_usd — a cumulative running total, not a delta.
|
|
2476
|
+
// Assign directly instead of summing to avoid overcounting.
|
|
2477
|
+
if (cost != null) sessionUsage.cost = cost;
|
|
2472
2478
|
if (usage) {
|
|
2473
2479
|
sessionUsage.input += usage.input_tokens || usage.inputTokens || 0;
|
|
2474
2480
|
sessionUsage.output += usage.output_tokens || usage.outputTokens || 0;
|
|
@@ -2596,6 +2602,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2596
2602
|
var contextMiniLabel = $("context-mini-label");
|
|
2597
2603
|
var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
2598
2604
|
var headerContextEl = null;
|
|
2605
|
+
var richContextUsage = null;
|
|
2606
|
+
var ctxPopoverEl = null;
|
|
2607
|
+
var ctxPopoverVisible = false;
|
|
2599
2608
|
|
|
2600
2609
|
// Known context window sizes per model (fallback when SDK omits feature flag)
|
|
2601
2610
|
var KNOWN_CONTEXT_WINDOWS = {
|
|
@@ -2643,6 +2652,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2643
2652
|
headerContextEl.className = "header-context";
|
|
2644
2653
|
headerContextEl.innerHTML = '<div class="header-context-bar"><div class="header-context-fill"></div></div><span class="header-context-label"></span>';
|
|
2645
2654
|
statusArea.insertBefore(headerContextEl, statusArea.firstChild);
|
|
2655
|
+
headerContextEl.addEventListener("mouseenter", function() {
|
|
2656
|
+
if (richContextUsage) {
|
|
2657
|
+
showCtxPopover();
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
headerContextEl.addEventListener("mouseleave", function() {
|
|
2661
|
+
ctxHoverTimer = setTimeout(hideCtxPopover, 120);
|
|
2662
|
+
});
|
|
2646
2663
|
}
|
|
2647
2664
|
if (headerContextEl) {
|
|
2648
2665
|
var hFill = headerContextEl.querySelector(".header-context-fill");
|
|
@@ -2650,7 +2667,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2650
2667
|
hFill.style.width = pct.toFixed(1) + "%";
|
|
2651
2668
|
hFill.className = "header-context-fill" + cls;
|
|
2652
2669
|
hLabel.textContent = pct.toFixed(0) + "%";
|
|
2653
|
-
|
|
2670
|
+
// Use data-tip as fallback when rich data is not yet loaded
|
|
2671
|
+
if (richContextUsage) {
|
|
2672
|
+
headerContextEl.removeAttribute("data-tip");
|
|
2673
|
+
} else {
|
|
2674
|
+
headerContextEl.dataset.tip = "Context window " + pct.toFixed(0) + "% used (" + formatTokens(used) + " / " + formatTokens(win) + " tokens)";
|
|
2675
|
+
}
|
|
2654
2676
|
}
|
|
2655
2677
|
}
|
|
2656
2678
|
contextUsedEl.textContent = formatTokens(used);
|
|
@@ -2666,7 +2688,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2666
2688
|
}
|
|
2667
2689
|
|
|
2668
2690
|
function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens) {
|
|
2669
|
-
|
|
2691
|
+
// cost is the SDK's total_cost_usd — a cumulative running total, not a delta.
|
|
2692
|
+
if (cost != null) contextData.cost = cost;
|
|
2670
2693
|
// Use latest turn values (not cumulative) since each turn's input_tokens
|
|
2671
2694
|
// already includes the full conversation context up to that point
|
|
2672
2695
|
if (usage) {
|
|
@@ -2714,9 +2737,158 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2714
2737
|
|
|
2715
2738
|
function resetContextData() {
|
|
2716
2739
|
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
2740
|
+
richContextUsage = null;
|
|
2741
|
+
hideCtxPopover();
|
|
2717
2742
|
updateContextPanel();
|
|
2718
2743
|
}
|
|
2719
2744
|
|
|
2745
|
+
// --- Rich context usage popover ---
|
|
2746
|
+
|
|
2747
|
+
var ctxHoverTimer = null;
|
|
2748
|
+
|
|
2749
|
+
function ensureCtxPopover() {
|
|
2750
|
+
if (ctxPopoverEl) return;
|
|
2751
|
+
ctxPopoverEl = document.createElement("div");
|
|
2752
|
+
ctxPopoverEl.className = "context-usage-popover hidden";
|
|
2753
|
+
// Keep popover open when hovering over it
|
|
2754
|
+
ctxPopoverEl.addEventListener("mouseenter", function() {
|
|
2755
|
+
if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
|
|
2756
|
+
});
|
|
2757
|
+
ctxPopoverEl.addEventListener("mouseleave", function() {
|
|
2758
|
+
hideCtxPopover();
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
function showCtxPopover() {
|
|
2763
|
+
if (!headerContextEl || !richContextUsage) return;
|
|
2764
|
+
if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
|
|
2765
|
+
ensureCtxPopover();
|
|
2766
|
+
headerContextEl.appendChild(ctxPopoverEl);
|
|
2767
|
+
renderCtxPopover();
|
|
2768
|
+
ctxPopoverEl.classList.remove("hidden");
|
|
2769
|
+
ctxPopoverVisible = true;
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
function hideCtxPopover() {
|
|
2773
|
+
if (!ctxPopoverEl) return;
|
|
2774
|
+
ctxPopoverEl.classList.add("hidden");
|
|
2775
|
+
ctxPopoverVisible = false;
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// Categories to hide from the legend (noise, not actionable)
|
|
2779
|
+
var CTX_HIDDEN_CATS = { "Free space": 1, "Autocompact buffer": 1 };
|
|
2780
|
+
|
|
2781
|
+
function renderCtxPopover() {
|
|
2782
|
+
if (!ctxPopoverEl || !richContextUsage) return;
|
|
2783
|
+
var d = richContextUsage;
|
|
2784
|
+
var cats = d.categories || [];
|
|
2785
|
+
var total = d.totalTokens || 0;
|
|
2786
|
+
var max = d.maxTokens || 0;
|
|
2787
|
+
var pct = d.percentage != null ? d.percentage : (max > 0 ? (total / max) * 100 : 0);
|
|
2788
|
+
|
|
2789
|
+
var html = "";
|
|
2790
|
+
|
|
2791
|
+
// Header
|
|
2792
|
+
html += '<div class="ctx-pop-header">';
|
|
2793
|
+
html += '<span class="ctx-pop-model">' + escHtml(d.model || contextData.model || "-") + '</span>';
|
|
2794
|
+
html += '<span class="ctx-pop-pct">' + pct.toFixed(0) + '%';
|
|
2795
|
+
html += '<span class="ctx-pop-tokens">' + formatTokens(total) + ' / ' + formatTokens(max) + '</span>';
|
|
2796
|
+
html += '</span>';
|
|
2797
|
+
html += '</div>';
|
|
2798
|
+
|
|
2799
|
+
// Category emoji map
|
|
2800
|
+
var CTX_EMOJI = {
|
|
2801
|
+
"System prompt": "\ud83d\udcdc", "System tools": "\ud83d\udee0\ufe0f",
|
|
2802
|
+
"Memory files": "\ud83d\udcc1", "Skills": "\u26a1", "Messages": "\ud83d\udcac",
|
|
2803
|
+
"MCP tools": "\ud83d\udd0c", "Agents": "\ud83e\udd16", "Deferred tools": "\ud83d\udce6"
|
|
2804
|
+
};
|
|
2805
|
+
|
|
2806
|
+
// Stacked bar
|
|
2807
|
+
if (cats.length > 0 && max > 0) {
|
|
2808
|
+
html += '<div class="ctx-cat-bar">';
|
|
2809
|
+
for (var i = 0; i < cats.length; i++) {
|
|
2810
|
+
var cat = cats[i];
|
|
2811
|
+
if (cat.isDeferred || !cat.tokens || CTX_HIDDEN_CATS[cat.name]) continue;
|
|
2812
|
+
var w = Math.max(0.3, (cat.tokens / max) * 100);
|
|
2813
|
+
html += '<div style="width:' + w.toFixed(2) + '%;background:' + escHtml(cat.color) + '"></div>';
|
|
2814
|
+
}
|
|
2815
|
+
html += '</div>';
|
|
2816
|
+
|
|
2817
|
+
// Legend
|
|
2818
|
+
html += '<div class="ctx-cat-legend">';
|
|
2819
|
+
for (var j = 0; j < cats.length; j++) {
|
|
2820
|
+
var c = cats[j];
|
|
2821
|
+
if (c.isDeferred || !c.tokens || CTX_HIDDEN_CATS[c.name]) continue;
|
|
2822
|
+
var emoji = CTX_EMOJI[c.name] || "\ud83d\udcca";
|
|
2823
|
+
html += '<div class="ctx-cat-item">';
|
|
2824
|
+
html += '<span class="ctx-cat-name">' + em(emoji) + ' ' + escHtml(c.name) + '</span>';
|
|
2825
|
+
html += '<span class="ctx-cat-value">' + formatTokens(c.tokens) + '</span>';
|
|
2826
|
+
html += '</div>';
|
|
2827
|
+
}
|
|
2828
|
+
html += '</div>';
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
// Message breakdown
|
|
2832
|
+
var mb = d.messageBreakdown;
|
|
2833
|
+
if (mb) {
|
|
2834
|
+
html += '<div class="ctx-pop-divider"></div>';
|
|
2835
|
+
html += '<div class="ctx-pop-section-label">' + em("\ud83d\udcac") + ' Messages</div>';
|
|
2836
|
+
if (mb.userMessageTokens) {
|
|
2837
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udc64") + ' User</span><span class="ctx-pop-row-value">' + formatTokens(mb.userMessageTokens) + '</span></div>';
|
|
2838
|
+
}
|
|
2839
|
+
if (mb.assistantMessageTokens) {
|
|
2840
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83e\udd16") + ' Assistant</span><span class="ctx-pop-row-value">' + formatTokens(mb.assistantMessageTokens) + '</span></div>';
|
|
2841
|
+
}
|
|
2842
|
+
if (mb.toolCallTokens) {
|
|
2843
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udee0\ufe0f") + ' Tool calls</span><span class="ctx-pop-row-value">' + formatTokens(mb.toolCallTokens) + '</span></div>';
|
|
2844
|
+
}
|
|
2845
|
+
if (mb.toolResultTokens) {
|
|
2846
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udccb") + ' Tool results</span><span class="ctx-pop-row-value">' + formatTokens(mb.toolResultTokens) + '</span></div>';
|
|
2847
|
+
}
|
|
2848
|
+
if (mb.attachmentTokens) {
|
|
2849
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udcce") + ' Attachments</span><span class="ctx-pop-row-value">' + formatTokens(mb.attachmentTokens) + '</span></div>';
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// Memory files
|
|
2854
|
+
var mf = d.memoryFiles;
|
|
2855
|
+
if (mf && mf.length > 0) {
|
|
2856
|
+
html += '<div class="ctx-pop-divider"></div>';
|
|
2857
|
+
html += '<div class="ctx-pop-section-label">' + em("\ud83d\udcc1") + ' Memory Files</div>';
|
|
2858
|
+
var baseCount = {};
|
|
2859
|
+
for (var mc = 0; mc < mf.length; mc++) {
|
|
2860
|
+
var bn = mf[mc].path.split("/").pop() || mf[mc].path;
|
|
2861
|
+
baseCount[bn] = (baseCount[bn] || 0) + 1;
|
|
2862
|
+
}
|
|
2863
|
+
for (var mi = 0; mi < mf.length; mi++) {
|
|
2864
|
+
var fpath = mf[mi].path;
|
|
2865
|
+
var fname = fpath.split("/").pop() || fpath;
|
|
2866
|
+
if (baseCount[fname] > 1) {
|
|
2867
|
+
var parts = fpath.split("/");
|
|
2868
|
+
fname = parts.length >= 2 ? parts[parts.length - 2] + "/" + fname : fpath;
|
|
2869
|
+
}
|
|
2870
|
+
html += '<div class="ctx-pop-row"><span class="ctx-pop-row-label">' + em("\ud83d\udcc4") + ' ' + escHtml(fname) + '</span><span class="ctx-pop-row-value">' + formatTokens(mf[mi].tokens) + '</span></div>';
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
// Auto-compact note
|
|
2875
|
+
if (d.isAutoCompactEnabled && d.autoCompactThreshold) {
|
|
2876
|
+
html += '<div class="ctx-pop-note">' + em("\u267b\ufe0f") + ' Auto-compact at ' + formatTokens(d.autoCompactThreshold) + '</div>';
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
ctxPopoverEl.innerHTML = html;
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
function escHtml(s) {
|
|
2883
|
+
var div = document.createElement("div");
|
|
2884
|
+
div.textContent = s;
|
|
2885
|
+
return div.innerHTML;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
function em(emoji) {
|
|
2889
|
+
return '<span class="ctx-emoji">' + emoji + '</span>';
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2720
2892
|
function resetContext() {
|
|
2721
2893
|
resetContextData();
|
|
2722
2894
|
// Keep view state, just reset data
|
|
@@ -2851,6 +3023,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2851
3023
|
isMateDm: function () { return dmMode && dmTargetUser && dmTargetUser.isMate; },
|
|
2852
3024
|
getMateName: function () { return dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate"; },
|
|
2853
3025
|
getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
|
|
3026
|
+
getClaudeAvatar: function () { return CLAUDE_CODE_AVATAR; },
|
|
2854
3027
|
getMateById: function (id) {
|
|
2855
3028
|
if (!id || !cachedMatesList) return null;
|
|
2856
3029
|
for (var i = 0; i < cachedMatesList.length; i++) {
|
|
@@ -2871,6 +3044,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2871
3044
|
var div = document.createElement("div");
|
|
2872
3045
|
div.className = "msg-user" + (isOtherUser ? " msg-user-other" : "");
|
|
2873
3046
|
div.dataset.turn = ++turnCounter;
|
|
3047
|
+
if (shouldGroupMessage("msg-user")) div.classList.add("grouped");
|
|
2874
3048
|
var bubble = document.createElement("div");
|
|
2875
3049
|
bubble.className = "bubble";
|
|
2876
3050
|
bubble.dir = "auto";
|
|
@@ -2961,8 +3135,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2961
3135
|
header.appendChild(nameSpan);
|
|
2962
3136
|
var timeSpan = document.createElement("span");
|
|
2963
3137
|
timeSpan.className = "dm-bubble-time";
|
|
2964
|
-
|
|
2965
|
-
timeSpan.textContent = String(nowH.getHours()).padStart(2, "0") + ":" + String(nowH.getMinutes()).padStart(2, "0");
|
|
3138
|
+
timeSpan.textContent = getMsgTime();
|
|
2966
3139
|
header.appendChild(timeSpan);
|
|
2967
3140
|
contentWrap.appendChild(header);
|
|
2968
3141
|
contentWrap.appendChild(bubble);
|
|
@@ -2971,10 +3144,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2971
3144
|
// Action bar below bubble (icons visible on hover)
|
|
2972
3145
|
var actions = document.createElement("div");
|
|
2973
3146
|
actions.className = "msg-actions";
|
|
2974
|
-
var now = new Date();
|
|
2975
|
-
var timeStr = String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0");
|
|
2976
3147
|
actions.innerHTML =
|
|
2977
|
-
'<span class="msg-action-time">' +
|
|
3148
|
+
'<span class="msg-action-time">' + getMsgTime() + '</span>' +
|
|
2978
3149
|
'<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + iconHtml("copy") + '</button>' +
|
|
2979
3150
|
'<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + iconHtml("git-branch") + '</button>' +
|
|
2980
3151
|
'<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + iconHtml("rotate-ccw") + '</button>' +
|
|
@@ -2995,11 +3166,33 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2995
3166
|
forceScrollToBottom();
|
|
2996
3167
|
}
|
|
2997
3168
|
|
|
3169
|
+
// Track the timestamp of the current message being processed (from history _ts or now)
|
|
3170
|
+
var currentMsgTs = null;
|
|
3171
|
+
|
|
3172
|
+
function getMsgTime() {
|
|
3173
|
+
var d = currentMsgTs ? new Date(currentMsgTs) : new Date();
|
|
3174
|
+
return String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
function shouldGroupMessage(senderClass) {
|
|
3178
|
+
// Skip grouping during history replay if no timestamp data
|
|
3179
|
+
if (replayingHistory && !currentMsgTs) return false;
|
|
3180
|
+
var prev = messagesEl.lastElementChild;
|
|
3181
|
+
if (!prev || !prev.classList.contains(senderClass)) return false;
|
|
3182
|
+
var prevTime = prev.querySelector(".dm-bubble-time");
|
|
3183
|
+
if (!prevTime) return false;
|
|
3184
|
+
return prevTime.textContent === getMsgTime();
|
|
3185
|
+
}
|
|
3186
|
+
|
|
2998
3187
|
function ensureAssistantBlock() {
|
|
2999
3188
|
if (!currentMsgEl) {
|
|
3000
3189
|
currentMsgEl = document.createElement("div");
|
|
3001
3190
|
currentMsgEl.className = "msg-assistant";
|
|
3002
3191
|
currentMsgEl.dataset.turn = turnCounter;
|
|
3192
|
+
|
|
3193
|
+
var grouped = shouldGroupMessage("msg-assistant");
|
|
3194
|
+
if (grouped) currentMsgEl.classList.add("grouped");
|
|
3195
|
+
|
|
3003
3196
|
// Always render avatar + header structure (CSS controls visibility)
|
|
3004
3197
|
var _isDm2 = document.body.classList.contains("mate-dm-active") && document.body.dataset.mateAvatarUrl;
|
|
3005
3198
|
var avi = document.createElement("img");
|
|
@@ -3018,8 +3211,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
3018
3211
|
header.appendChild(nameSpan);
|
|
3019
3212
|
var timeSpan = document.createElement("span");
|
|
3020
3213
|
timeSpan.className = "dm-bubble-time";
|
|
3021
|
-
|
|
3022
|
-
timeSpan.textContent = String(nowA.getHours()).padStart(2, "0") + ":" + String(nowA.getMinutes()).padStart(2, "0");
|
|
3214
|
+
timeSpan.textContent = getMsgTime();
|
|
3023
3215
|
header.appendChild(timeSpan);
|
|
3024
3216
|
contentWrap.appendChild(header);
|
|
3025
3217
|
|
|
@@ -3547,42 +3739,134 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
3547
3739
|
|
|
3548
3740
|
function addScheduledMessageBubble(text, resetsAt) {
|
|
3549
3741
|
removeScheduledMessageBubble();
|
|
3742
|
+
var isChannel = document.body.classList.contains("wide-view");
|
|
3550
3743
|
var wrap = document.createElement("div");
|
|
3551
3744
|
wrap.className = "msg-user scheduled-msg-wrap";
|
|
3552
3745
|
wrap.id = "scheduled-msg-bubble";
|
|
3553
3746
|
|
|
3554
|
-
var
|
|
3555
|
-
|
|
3747
|
+
var countdownEl;
|
|
3748
|
+
var cancelBtn;
|
|
3556
3749
|
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3750
|
+
if (isChannel) {
|
|
3751
|
+
// Channel mode: avatar + header with scheduled badge + message
|
|
3752
|
+
var _me = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
3753
|
+
if (!_me) { try { _me = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {} }
|
|
3754
|
+
var _myName = document.body.dataset.myDisplayName || (_me && (_me.displayName || _me.username)) || "Me";
|
|
3560
3755
|
|
|
3561
|
-
|
|
3562
|
-
|
|
3756
|
+
var avi = document.createElement("img");
|
|
3757
|
+
avi.className = "dm-bubble-avatar dm-bubble-avatar-me";
|
|
3758
|
+
avi.src = document.body.dataset.myAvatarUrl || userAvatarUrl(_me || { id: myUserId }, 36);
|
|
3759
|
+
wrap.appendChild(avi);
|
|
3563
3760
|
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
clockIcon.innerHTML = iconHtml("clock");
|
|
3567
|
-
metaEl.appendChild(clockIcon);
|
|
3761
|
+
var content = document.createElement("div");
|
|
3762
|
+
content.className = "dm-bubble-content";
|
|
3568
3763
|
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
metaEl.appendChild(countdownEl);
|
|
3764
|
+
var header = document.createElement("div");
|
|
3765
|
+
header.className = "dm-bubble-header";
|
|
3572
3766
|
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3767
|
+
var nameSpan = document.createElement("span");
|
|
3768
|
+
nameSpan.className = "dm-bubble-name";
|
|
3769
|
+
nameSpan.textContent = _myName;
|
|
3770
|
+
header.appendChild(nameSpan);
|
|
3771
|
+
|
|
3772
|
+
var badge = document.createElement("span");
|
|
3773
|
+
badge.className = "scheduled-msg-badge";
|
|
3774
|
+
badge.innerHTML = iconHtml("clock");
|
|
3775
|
+
countdownEl = document.createElement("span");
|
|
3776
|
+
countdownEl.className = "scheduled-msg-countdown";
|
|
3777
|
+
badge.appendChild(countdownEl);
|
|
3778
|
+
header.appendChild(badge);
|
|
3779
|
+
|
|
3780
|
+
var actions = document.createElement("span");
|
|
3781
|
+
actions.className = "scheduled-msg-actions";
|
|
3782
|
+
|
|
3783
|
+
var sendNowBtn = document.createElement("button");
|
|
3784
|
+
sendNowBtn.className = "scheduled-msg-send-now";
|
|
3785
|
+
sendNowBtn.textContent = "Send now";
|
|
3786
|
+
sendNowBtn.addEventListener("click", function () {
|
|
3787
|
+
if (ws && ws.readyState === 1) {
|
|
3788
|
+
ws.send(JSON.stringify({ type: "send_scheduled_now" }));
|
|
3789
|
+
}
|
|
3790
|
+
});
|
|
3791
|
+
actions.appendChild(sendNowBtn);
|
|
3792
|
+
|
|
3793
|
+
var sep = document.createElement("span");
|
|
3794
|
+
sep.className = "scheduled-msg-sep";
|
|
3795
|
+
sep.textContent = "\u00b7";
|
|
3796
|
+
actions.appendChild(sep);
|
|
3797
|
+
|
|
3798
|
+
cancelBtn = document.createElement("button");
|
|
3799
|
+
cancelBtn.className = "scheduled-msg-cancel";
|
|
3800
|
+
cancelBtn.textContent = "Cancel";
|
|
3801
|
+
cancelBtn.addEventListener("click", function () {
|
|
3802
|
+
if (ws && ws.readyState === 1) {
|
|
3803
|
+
ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
|
|
3804
|
+
}
|
|
3805
|
+
});
|
|
3806
|
+
actions.appendChild(cancelBtn);
|
|
3807
|
+
|
|
3808
|
+
header.appendChild(actions);
|
|
3809
|
+
|
|
3810
|
+
content.appendChild(header);
|
|
3811
|
+
|
|
3812
|
+
var bubble = document.createElement("div");
|
|
3813
|
+
bubble.className = "bubble scheduled-msg-bubble";
|
|
3814
|
+
var textEl = document.createElement("span");
|
|
3815
|
+
textEl.textContent = text;
|
|
3816
|
+
bubble.appendChild(textEl);
|
|
3817
|
+
content.appendChild(bubble);
|
|
3818
|
+
|
|
3819
|
+
wrap.appendChild(content);
|
|
3820
|
+
} else {
|
|
3821
|
+
// Bubble mode: original layout
|
|
3822
|
+
var bubble = document.createElement("div");
|
|
3823
|
+
bubble.className = "bubble scheduled-msg-bubble";
|
|
3824
|
+
|
|
3825
|
+
var textEl = document.createElement("span");
|
|
3826
|
+
textEl.textContent = text;
|
|
3827
|
+
bubble.appendChild(textEl);
|
|
3828
|
+
|
|
3829
|
+
var metaEl = document.createElement("div");
|
|
3830
|
+
metaEl.className = "scheduled-msg-meta";
|
|
3831
|
+
|
|
3832
|
+
var clockIcon = document.createElement("span");
|
|
3833
|
+
clockIcon.className = "scheduled-msg-icon";
|
|
3834
|
+
clockIcon.innerHTML = iconHtml("clock");
|
|
3835
|
+
metaEl.appendChild(clockIcon);
|
|
3836
|
+
|
|
3837
|
+
countdownEl = document.createElement("span");
|
|
3838
|
+
countdownEl.className = "scheduled-msg-countdown";
|
|
3839
|
+
metaEl.appendChild(countdownEl);
|
|
3840
|
+
|
|
3841
|
+
var sendNowBtn2 = document.createElement("button");
|
|
3842
|
+
sendNowBtn2.className = "scheduled-msg-send-now";
|
|
3843
|
+
sendNowBtn2.textContent = "Send now";
|
|
3844
|
+
sendNowBtn2.addEventListener("click", function () {
|
|
3845
|
+
if (ws && ws.readyState === 1) {
|
|
3846
|
+
ws.send(JSON.stringify({ type: "send_scheduled_now" }));
|
|
3847
|
+
}
|
|
3848
|
+
});
|
|
3849
|
+
metaEl.appendChild(sendNowBtn2);
|
|
3850
|
+
|
|
3851
|
+
var sep2 = document.createElement("span");
|
|
3852
|
+
sep2.className = "scheduled-msg-sep";
|
|
3853
|
+
sep2.textContent = "\u00b7";
|
|
3854
|
+
metaEl.appendChild(sep2);
|
|
3855
|
+
|
|
3856
|
+
cancelBtn = document.createElement("button");
|
|
3857
|
+
cancelBtn.className = "scheduled-msg-cancel";
|
|
3858
|
+
cancelBtn.textContent = "Cancel";
|
|
3859
|
+
cancelBtn.addEventListener("click", function () {
|
|
3860
|
+
if (ws && ws.readyState === 1) {
|
|
3861
|
+
ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
|
|
3862
|
+
}
|
|
3863
|
+
});
|
|
3864
|
+
metaEl.appendChild(cancelBtn);
|
|
3865
|
+
|
|
3866
|
+
wrap.appendChild(bubble);
|
|
3867
|
+
wrap.appendChild(metaEl);
|
|
3868
|
+
}
|
|
3583
3869
|
|
|
3584
|
-
wrap.appendChild(bubble);
|
|
3585
|
-
wrap.appendChild(metaEl);
|
|
3586
3870
|
addToMessages(wrap);
|
|
3587
3871
|
scheduledMsgEl = wrap;
|
|
3588
3872
|
scrollToBottom();
|
|
@@ -3660,23 +3944,13 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
3660
3944
|
suggestionChipsEl.innerHTML = "";
|
|
3661
3945
|
var chip = document.createElement("button");
|
|
3662
3946
|
chip.className = "suggestion-chip";
|
|
3663
|
-
chip.innerHTML =
|
|
3664
|
-
'<span class="suggestion-chip-
|
|
3665
|
-
'<span class="suggestion-chip-text">' + escapeHtml(suggestion) + '</span></span>' +
|
|
3666
|
-
'<span class="suggestion-chip-edit">' + iconHtml("pencil") + '</span>';
|
|
3947
|
+
chip.innerHTML = iconHtml("sparkles") +
|
|
3948
|
+
'<span class="suggestion-chip-text">' + escapeHtml(suggestion) + '</span>';
|
|
3667
3949
|
chip.addEventListener("click", function () {
|
|
3668
3950
|
inputEl.value = suggestion;
|
|
3669
3951
|
hideSuggestionChips();
|
|
3670
3952
|
sendMessage();
|
|
3671
3953
|
});
|
|
3672
|
-
chip.querySelector(".suggestion-chip-edit").addEventListener("click", function (e) {
|
|
3673
|
-
e.stopPropagation();
|
|
3674
|
-
inputEl.value = suggestion;
|
|
3675
|
-
inputEl.focus();
|
|
3676
|
-
inputEl.select();
|
|
3677
|
-
autoResize();
|
|
3678
|
-
hideSuggestionChips();
|
|
3679
|
-
});
|
|
3680
3954
|
suggestionChipsEl.appendChild(chip);
|
|
3681
3955
|
suggestionChipsEl.classList.remove("hidden");
|
|
3682
3956
|
refreshIcons();
|
|
@@ -3718,6 +3992,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
3718
3992
|
setStatus("connected");
|
|
3719
3993
|
if (!loopActive) enableMainInput();
|
|
3720
3994
|
resetUsage();
|
|
3995
|
+
resetTurnMetaCost();
|
|
3721
3996
|
resetContext();
|
|
3722
3997
|
// Clear header indicators
|
|
3723
3998
|
clearRateLimitIndicator();
|
|
@@ -3868,6 +4143,31 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
3868
4143
|
|
|
3869
4144
|
// Session restore is now server-driven (user-presence.json).
|
|
3870
4145
|
// Mate DM restore is also server-driven via "restore_mate_dm" message.
|
|
4146
|
+
// Fallback: if server doesn't restore DM within 2s, try localStorage
|
|
4147
|
+
var savedDm = null;
|
|
4148
|
+
try { savedDm = localStorage.getItem("clay-active-dm"); } catch (e) {}
|
|
4149
|
+
if (savedDm && !dmMode && !mateProjectSlug) {
|
|
4150
|
+
var dmFallbackTimer = setTimeout(function () {
|
|
4151
|
+
if (!dmMode && savedDm) {
|
|
4152
|
+
console.log("[dm-restore] Server did not restore DM, using localStorage fallback:", savedDm);
|
|
4153
|
+
openDm(savedDm);
|
|
4154
|
+
}
|
|
4155
|
+
}, 2000);
|
|
4156
|
+
// Cancel fallback if server restores DM first
|
|
4157
|
+
var origHandler = ws.onmessage;
|
|
4158
|
+
var patchedOnce = false;
|
|
4159
|
+
var checkRestore = function (evt) {
|
|
4160
|
+
try {
|
|
4161
|
+
var d = JSON.parse(evt.data);
|
|
4162
|
+
if (d.type === "restore_mate_dm" && !patchedOnce) {
|
|
4163
|
+
patchedOnce = true;
|
|
4164
|
+
clearTimeout(dmFallbackTimer);
|
|
4165
|
+
}
|
|
4166
|
+
} catch (e) {}
|
|
4167
|
+
};
|
|
4168
|
+
ws.addEventListener("message", checkRestore);
|
|
4169
|
+
setTimeout(function () { ws.removeEventListener("message", checkRestore); }, 3000);
|
|
4170
|
+
}
|
|
3871
4171
|
// Safety: clear returningFromMateDm after initial messages settle
|
|
3872
4172
|
// (handles case where we connect to a non-main project that won't send restore_mate_dm)
|
|
3873
4173
|
if (returningFromMateDm) {
|
|
@@ -3966,6 +4266,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
3966
4266
|
}
|
|
3967
4267
|
|
|
3968
4268
|
function processMessage(msg) {
|
|
4269
|
+
// Preserve original timestamp from history replay
|
|
4270
|
+
currentMsgTs = msg._ts || null;
|
|
3969
4271
|
var isMateDm = dmMode && dmTargetUser && dmTargetUser.isMate;
|
|
3970
4272
|
|
|
3971
4273
|
// DEBUG: trace session/history loading
|
|
@@ -3980,6 +4282,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
3980
4282
|
if (isMateDm) {
|
|
3981
4283
|
if (msg.type === "session_list") {
|
|
3982
4284
|
renderMateSessionList(msg.sessions || []);
|
|
4285
|
+
refreshMobileChatSheet();
|
|
3983
4286
|
// Override title bar with mate name and re-apply color
|
|
3984
4287
|
var _mdn = (dmTargetUser.displayName || "New Mate");
|
|
3985
4288
|
if (headerTitleEl) headerTitleEl.textContent = _mdn;
|
|
@@ -4057,6 +4360,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4057
4360
|
|
|
4058
4361
|
case "history_done":
|
|
4059
4362
|
replayingHistory = false;
|
|
4363
|
+
// Restore cached rich context usage BEFORE updateContextPanel runs
|
|
4364
|
+
if (msg.contextUsage) {
|
|
4365
|
+
richContextUsage = msg.contextUsage;
|
|
4366
|
+
}
|
|
4060
4367
|
// Restore accurate context data from the last result in full history
|
|
4061
4368
|
if (msg.lastUsage || msg.lastModelUsage) {
|
|
4062
4369
|
accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
|
|
@@ -4321,9 +4628,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4321
4628
|
break;
|
|
4322
4629
|
|
|
4323
4630
|
case "session_list":
|
|
4324
|
-
|
|
4325
|
-
renderMateSessionList(msg.sessions || []);
|
|
4326
|
-
}
|
|
4631
|
+
renderMateSessionList(msg.sessions || []);
|
|
4327
4632
|
renderSessionList(msg.sessions || []);
|
|
4328
4633
|
handlePaletteSessionSwitch();
|
|
4329
4634
|
break;
|
|
@@ -4414,6 +4719,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4414
4719
|
break;
|
|
4415
4720
|
|
|
4416
4721
|
case "user_message":
|
|
4722
|
+
if (msg._internal) break;
|
|
4417
4723
|
resetThinkingGroup();
|
|
4418
4724
|
if (msg.planContent) {
|
|
4419
4725
|
setPlanContent(msg.planContent);
|
|
@@ -4424,20 +4730,73 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4424
4730
|
}
|
|
4425
4731
|
break;
|
|
4426
4732
|
|
|
4733
|
+
case "context_preview":
|
|
4734
|
+
// Show a Context Card with tab screenshot between user message and assistant response
|
|
4735
|
+
if (msg.tab) {
|
|
4736
|
+
var card = document.createElement("div");
|
|
4737
|
+
card.className = "context-card";
|
|
4738
|
+
|
|
4739
|
+
// Header
|
|
4740
|
+
var header = document.createElement("div");
|
|
4741
|
+
header.className = "context-card-header";
|
|
4742
|
+
var icon = document.createElement("span");
|
|
4743
|
+
icon.className = "context-card-icon";
|
|
4744
|
+
icon.textContent = "\uD83D\uDC41";
|
|
4745
|
+
header.appendChild(icon);
|
|
4746
|
+
var label = document.createElement("span");
|
|
4747
|
+
label.textContent = "Viewing tab";
|
|
4748
|
+
header.appendChild(label);
|
|
4749
|
+
card.appendChild(header);
|
|
4750
|
+
|
|
4751
|
+
// Screenshot
|
|
4752
|
+
if (msg.tab.screenshotUrl) {
|
|
4753
|
+
var img = document.createElement("img");
|
|
4754
|
+
img.className = "context-card-screenshot";
|
|
4755
|
+
img.src = msg.tab.screenshotUrl;
|
|
4756
|
+
img.loading = "lazy";
|
|
4757
|
+
img.addEventListener("click", function () { showImageModal(this.src); });
|
|
4758
|
+
card.appendChild(img);
|
|
4759
|
+
}
|
|
4760
|
+
|
|
4761
|
+
// Meta: title + domain
|
|
4762
|
+
var tabTitle = msg.tab.title || "";
|
|
4763
|
+
var tabDomain = "";
|
|
4764
|
+
try { tabDomain = new URL(msg.tab.url).hostname; } catch (e) {}
|
|
4765
|
+
if (tabTitle || tabDomain) {
|
|
4766
|
+
var meta = document.createElement("div");
|
|
4767
|
+
meta.className = "context-card-meta";
|
|
4768
|
+
var titleEl = document.createElement("span");
|
|
4769
|
+
titleEl.className = "context-card-title";
|
|
4770
|
+
titleEl.textContent = tabTitle;
|
|
4771
|
+
meta.appendChild(titleEl);
|
|
4772
|
+
if (tabDomain) {
|
|
4773
|
+
var domainEl = document.createElement("span");
|
|
4774
|
+
domainEl.className = "context-card-domain";
|
|
4775
|
+
domainEl.textContent = tabDomain;
|
|
4776
|
+
meta.appendChild(domainEl);
|
|
4777
|
+
}
|
|
4778
|
+
card.appendChild(meta);
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4781
|
+
messagesEl.appendChild(card);
|
|
4782
|
+
scrollToBottom();
|
|
4783
|
+
}
|
|
4784
|
+
break;
|
|
4785
|
+
|
|
4427
4786
|
case "status":
|
|
4428
4787
|
if (msg.status === "processing") {
|
|
4429
4788
|
setStatus("processing");
|
|
4430
|
-
if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
|
|
4431
|
-
setActivity(
|
|
4789
|
+
if (!(dmMode && dmTargetUser && dmTargetUser.isMate) && !matePreThinkingEl) {
|
|
4790
|
+
setActivity("thinking");
|
|
4432
4791
|
}
|
|
4433
4792
|
}
|
|
4434
4793
|
break;
|
|
4435
4794
|
|
|
4436
4795
|
case "compacting":
|
|
4437
4796
|
if (msg.active) {
|
|
4438
|
-
setActivity("
|
|
4797
|
+
setActivity("compacting");
|
|
4439
4798
|
} else if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
|
|
4440
|
-
setActivity(
|
|
4799
|
+
setActivity("thinking");
|
|
4441
4800
|
}
|
|
4442
4801
|
break;
|
|
4443
4802
|
|
|
@@ -4453,7 +4812,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4453
4812
|
case "thinking_stop":
|
|
4454
4813
|
stopThinking(msg.duration);
|
|
4455
4814
|
if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
|
|
4456
|
-
setActivity(
|
|
4815
|
+
setActivity("thinking");
|
|
4457
4816
|
}
|
|
4458
4817
|
break;
|
|
4459
4818
|
|
|
@@ -4619,6 +4978,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4619
4978
|
accumulateContext(msg.cost, msg.usage, msg.modelUsage, msg.lastStreamInputTokens);
|
|
4620
4979
|
break;
|
|
4621
4980
|
|
|
4981
|
+
case "context_usage":
|
|
4982
|
+
if (msg.data && !replayingHistory) {
|
|
4983
|
+
richContextUsage = msg.data;
|
|
4984
|
+
if (headerContextEl) headerContextEl.removeAttribute("data-tip");
|
|
4985
|
+
if (ctxPopoverVisible) renderCtxPopover();
|
|
4986
|
+
}
|
|
4987
|
+
break;
|
|
4988
|
+
|
|
4622
4989
|
case "done":
|
|
4623
4990
|
setActivity(null);
|
|
4624
4991
|
stopThinking();
|
|
@@ -4711,6 +5078,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4711
5078
|
break;
|
|
4712
5079
|
|
|
4713
5080
|
case "rewind_complete":
|
|
5081
|
+
onRewindComplete();
|
|
4714
5082
|
setRewindMode(false);
|
|
4715
5083
|
var rewindText = "Rewound to earlier point. Files have been restored.";
|
|
4716
5084
|
if (msg.mode === "chat") rewindText = "Conversation rewound to earlier point.";
|
|
@@ -4719,6 +5087,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4719
5087
|
break;
|
|
4720
5088
|
|
|
4721
5089
|
case "rewind_error":
|
|
5090
|
+
onRewindError();
|
|
4722
5091
|
clearPendingRewindUuid();
|
|
4723
5092
|
addSystemMessage(msg.text || "Rewind failed.", true);
|
|
4724
5093
|
break;
|
|
@@ -4791,6 +5160,15 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4791
5160
|
|
|
4792
5161
|
case "term_list":
|
|
4793
5162
|
handleTermList(msg);
|
|
5163
|
+
updateTerminalList(msg.terminals);
|
|
5164
|
+
break;
|
|
5165
|
+
|
|
5166
|
+
case "context_sources_state":
|
|
5167
|
+
handleContextSourcesState(msg);
|
|
5168
|
+
break;
|
|
5169
|
+
|
|
5170
|
+
case "extension_command":
|
|
5171
|
+
sendExtensionCommand(msg.command, msg.args, msg.requestId);
|
|
4794
5172
|
break;
|
|
4795
5173
|
|
|
4796
5174
|
case "term_created":
|
|
@@ -4809,6 +5187,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4809
5187
|
handleTermOutput(msg);
|
|
4810
5188
|
break;
|
|
4811
5189
|
|
|
5190
|
+
case "term_resized":
|
|
5191
|
+
handleTermResized(msg);
|
|
5192
|
+
break;
|
|
5193
|
+
|
|
4812
5194
|
case "term_exited":
|
|
4813
5195
|
handleTermExited(msg);
|
|
4814
5196
|
break;
|
|
@@ -4976,6 +5358,22 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4976
5358
|
// Update mate sidebar if currently viewing this mate
|
|
4977
5359
|
if (dmMode && dmTargetUser && dmTargetUser.isMate && dmTargetUser.id === msg.mate.id) {
|
|
4978
5360
|
updateMateSidebarProfile(msg.mate);
|
|
5361
|
+
// Sync dmTargetUser so subsequent renders use fresh data
|
|
5362
|
+
var mp2 = msg.mate.profile || {};
|
|
5363
|
+
dmTargetUser.displayName = mp2.displayName || msg.mate.name || dmTargetUser.displayName;
|
|
5364
|
+
dmTargetUser.avatarStyle = mp2.avatarStyle || dmTargetUser.avatarStyle;
|
|
5365
|
+
dmTargetUser.avatarSeed = mp2.avatarSeed || dmTargetUser.avatarSeed;
|
|
5366
|
+
dmTargetUser.avatarColor = mp2.avatarColor || dmTargetUser.avatarColor;
|
|
5367
|
+
dmTargetUser.avatarCustom = mp2.avatarCustom || "";
|
|
5368
|
+
dmTargetUser.profile = mp2;
|
|
5369
|
+
// Refresh body dataset so new chat bubbles use the updated avatar
|
|
5370
|
+
document.body.dataset.mateAvatarUrl = mateAvatarUrl(dmTargetUser, 36);
|
|
5371
|
+
document.body.dataset.mateName = mp2.displayName || msg.mate.name || "";
|
|
5372
|
+
// Update existing chat bubble avatars
|
|
5373
|
+
var mateAvis = document.querySelectorAll(".dm-bubble-avatar-mate");
|
|
5374
|
+
for (var mbi = 0; mbi < mateAvis.length; mbi++) {
|
|
5375
|
+
mateAvis[mbi].src = document.body.dataset.mateAvatarUrl;
|
|
5376
|
+
}
|
|
4979
5377
|
}
|
|
4980
5378
|
// Update DM header if currently chatting with this mate
|
|
4981
5379
|
if (dmMode && dmTargetUser && dmTargetUser.id === msg.mate.id) {
|
|
@@ -5005,6 +5403,23 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
5005
5403
|
break;
|
|
5006
5404
|
|
|
5007
5405
|
// --- @Mention ---
|
|
5406
|
+
case "mention_processing":
|
|
5407
|
+
// Broadcast: show/hide activity dot on mate avatar across all tabs
|
|
5408
|
+
if (msg.mateId) {
|
|
5409
|
+
var mateContainers = document.querySelectorAll('.icon-strip-mate[data-user-id="' + msg.mateId + '"]');
|
|
5410
|
+
for (var mi = 0; mi < mateContainers.length; mi++) {
|
|
5411
|
+
var dot = mateContainers[mi].querySelector(".icon-strip-status");
|
|
5412
|
+
if (msg.active) {
|
|
5413
|
+
if (dot) dot.classList.add("processing");
|
|
5414
|
+
mateContainers[mi].classList.add("mention-active");
|
|
5415
|
+
} else {
|
|
5416
|
+
if (dot) dot.classList.remove("processing");
|
|
5417
|
+
mateContainers[mi].classList.remove("mention-active");
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
break;
|
|
5422
|
+
|
|
5008
5423
|
case "mention_start":
|
|
5009
5424
|
handleMentionStart(msg);
|
|
5010
5425
|
break;
|
|
@@ -5082,6 +5497,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
5082
5497
|
}
|
|
5083
5498
|
break;
|
|
5084
5499
|
|
|
5500
|
+
case "debate_hand_raised":
|
|
5501
|
+
// Visual feedback: hand is raised, waiting for floor
|
|
5502
|
+
break;
|
|
5503
|
+
|
|
5085
5504
|
case "debate_comment_queued":
|
|
5086
5505
|
handleDebateCommentQueued(msg);
|
|
5087
5506
|
break;
|
|
@@ -5098,6 +5517,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
5098
5517
|
showDebateConcludeConfirm(msg);
|
|
5099
5518
|
break;
|
|
5100
5519
|
|
|
5520
|
+
case "debate_user_floor":
|
|
5521
|
+
showDebateUserFloor(msg);
|
|
5522
|
+
break;
|
|
5523
|
+
|
|
5524
|
+
case "debate_user_floor_done":
|
|
5525
|
+
renderDebateUserFloorDone(msg);
|
|
5526
|
+
break;
|
|
5527
|
+
|
|
5101
5528
|
case "debate_user_resume":
|
|
5102
5529
|
renderDebateUserResume(msg);
|
|
5103
5530
|
break;
|
|
@@ -5443,10 +5870,17 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
5443
5870
|
isDmMode: function () { return dmMode && !(dmTargetUser && dmTargetUser.isMate); },
|
|
5444
5871
|
getDmKey: function () { return dmKey; },
|
|
5445
5872
|
handleDmSend: function () { handleDmSend(); },
|
|
5873
|
+
isDebateEndedMode: function () { return debateEndedMode; },
|
|
5874
|
+
handleDebateEndedSend: function () { handleDebateEndedSend(); },
|
|
5875
|
+
isDebateConcludeMode: function () { return debateConcludeMode; },
|
|
5876
|
+
handleDebateConcludeSend: function () { handleDebateConcludeSend(); },
|
|
5877
|
+
isDebateFloorMode: function () { return debateFloorMode; },
|
|
5878
|
+
handleDebateFloorSend: function () { handleDebateFloorSend(); },
|
|
5446
5879
|
isMateDm: function () { return dmMode && dmTargetUser && dmTargetUser.isMate; },
|
|
5447
5880
|
getMateName: function () { return dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate"; },
|
|
5448
5881
|
getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
|
|
5449
5882
|
showMatePreThinking: function () { showMatePreThinking(); },
|
|
5883
|
+
showClaudePreThinking: function () { showClaudePreThinking(); },
|
|
5450
5884
|
});
|
|
5451
5885
|
|
|
5452
5886
|
// --- @Mention module ---
|
|
@@ -5475,6 +5909,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
5475
5909
|
availableBuiltins: function () { return cachedAvailableBuiltins || []; },
|
|
5476
5910
|
currentMateId: function () { return (dmTargetUser && dmTargetUser.isMate) ? dmTargetUser.id : null; },
|
|
5477
5911
|
requireSkills: requireSkills,
|
|
5912
|
+
showDebateEndedMode: function (msg) { showDebateEndedMode(msg); },
|
|
5478
5913
|
});
|
|
5479
5914
|
|
|
5480
5915
|
// --- STT module (voice input via Web Speech API) ---
|
|
@@ -5737,6 +6172,65 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
5737
6172
|
fileViewerEl: $("file-viewer"),
|
|
5738
6173
|
});
|
|
5739
6174
|
|
|
6175
|
+
// --- Context Sources ---
|
|
6176
|
+
initContextSources({
|
|
6177
|
+
get ws() { return ws; },
|
|
6178
|
+
get connected() { return connected; },
|
|
6179
|
+
});
|
|
6180
|
+
|
|
6181
|
+
// --- Chrome Extension Bridge ---
|
|
6182
|
+
var _extRequestCallbacks = {}; // requestId -> callback function
|
|
6183
|
+
|
|
6184
|
+
function sendExtensionCommand(command, args, requestId) {
|
|
6185
|
+
window.postMessage({
|
|
6186
|
+
source: "clay-page",
|
|
6187
|
+
payload: {
|
|
6188
|
+
type: "clay_ext_command",
|
|
6189
|
+
command: command,
|
|
6190
|
+
args: args,
|
|
6191
|
+
requestId: requestId
|
|
6192
|
+
}
|
|
6193
|
+
}, "*");
|
|
6194
|
+
}
|
|
6195
|
+
|
|
6196
|
+
function handleExtensionResult(requestId, result) {
|
|
6197
|
+
// Check local callback first (for server-initiated requests)
|
|
6198
|
+
var cb = _extRequestCallbacks[requestId];
|
|
6199
|
+
if (cb) {
|
|
6200
|
+
delete _extRequestCallbacks[requestId];
|
|
6201
|
+
cb(result);
|
|
6202
|
+
return;
|
|
6203
|
+
}
|
|
6204
|
+
// Forward to server
|
|
6205
|
+
if (ws && ws.readyState === 1) {
|
|
6206
|
+
ws.send(JSON.stringify({
|
|
6207
|
+
type: "extension_result",
|
|
6208
|
+
requestId: requestId,
|
|
6209
|
+
result: result
|
|
6210
|
+
}));
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
|
|
6214
|
+
window.addEventListener("message", function(event) {
|
|
6215
|
+
if (event.source !== window) return;
|
|
6216
|
+
if (!event.data || event.data.source !== "clay-chrome-extension") return;
|
|
6217
|
+
var msg = event.data.payload;
|
|
6218
|
+
|
|
6219
|
+
if (msg.type === "clay_ext_tab_list") {
|
|
6220
|
+
updateBrowserTabList(msg.tabs);
|
|
6221
|
+
// Also inform server about tab list
|
|
6222
|
+
if (ws && ws.readyState === 1) {
|
|
6223
|
+
ws.send(JSON.stringify({
|
|
6224
|
+
type: "browser_tab_list",
|
|
6225
|
+
tabs: msg.tabs
|
|
6226
|
+
}));
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
if (msg.type === "clay_ext_result") {
|
|
6230
|
+
handleExtensionResult(msg.requestId, msg.result);
|
|
6231
|
+
}
|
|
6232
|
+
});
|
|
6233
|
+
|
|
5740
6234
|
// --- Playbook Engine ---
|
|
5741
6235
|
initPlaybook();
|
|
5742
6236
|
|
|
@@ -6706,6 +7200,223 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
6706
7200
|
scrollToBottom();
|
|
6707
7201
|
}
|
|
6708
7202
|
|
|
7203
|
+
var debateFloorMode = false;
|
|
7204
|
+
var debateConcludeMode = false;
|
|
7205
|
+
|
|
7206
|
+
function showDebateConcludeMode() {
|
|
7207
|
+
debateConcludeMode = true;
|
|
7208
|
+
removeDebateBottomBar();
|
|
7209
|
+
var inputArea = document.getElementById("input-area");
|
|
7210
|
+
if (inputArea) {
|
|
7211
|
+
inputArea.classList.add("debate-floor-mode");
|
|
7212
|
+
inputArea.style.display = "";
|
|
7213
|
+
}
|
|
7214
|
+
// Add conclude banner above input
|
|
7215
|
+
var existingBanner = document.getElementById("debate-floor-banner");
|
|
7216
|
+
if (existingBanner) existingBanner.remove();
|
|
7217
|
+
var banner = document.createElement("div");
|
|
7218
|
+
banner.id = "debate-floor-banner";
|
|
7219
|
+
banner.className = "debate-floor-banner";
|
|
7220
|
+
banner.innerHTML = iconHtml("check-circle") + " <span>The moderator is ready to conclude</span>" +
|
|
7221
|
+
'<button class="debate-floor-done-btn debate-floor-end-btn" id="debate-floor-end-btn">End Debate</button>';
|
|
7222
|
+
if (inputArea && inputArea.parentNode) {
|
|
7223
|
+
inputArea.parentNode.insertBefore(banner, inputArea);
|
|
7224
|
+
}
|
|
7225
|
+
refreshIcons();
|
|
7226
|
+
// End Debate button
|
|
7227
|
+
var endBtn = document.getElementById("debate-floor-end-btn");
|
|
7228
|
+
if (endBtn) {
|
|
7229
|
+
endBtn.addEventListener("click", function () {
|
|
7230
|
+
if (ws && ws.readyState === 1) {
|
|
7231
|
+
ws.send(JSON.stringify({ type: "debate_conclude_response", action: "end" }));
|
|
7232
|
+
}
|
|
7233
|
+
exitDebateConcludeMode();
|
|
7234
|
+
});
|
|
7235
|
+
}
|
|
7236
|
+
// Update placeholder
|
|
7237
|
+
var inputEl = document.getElementById("input");
|
|
7238
|
+
if (inputEl) {
|
|
7239
|
+
inputEl._origPlaceholder = inputEl._origPlaceholder || inputEl.placeholder;
|
|
7240
|
+
inputEl.placeholder = "Add a direction to continue the debate...";
|
|
7241
|
+
inputEl.focus();
|
|
7242
|
+
}
|
|
7243
|
+
scrollToBottom();
|
|
7244
|
+
}
|
|
7245
|
+
|
|
7246
|
+
function exitDebateConcludeMode() {
|
|
7247
|
+
debateConcludeMode = false;
|
|
7248
|
+
var inputArea = document.getElementById("input-area");
|
|
7249
|
+
if (inputArea) inputArea.classList.remove("debate-floor-mode");
|
|
7250
|
+
var banner = document.getElementById("debate-floor-banner");
|
|
7251
|
+
if (banner) banner.remove();
|
|
7252
|
+
var inputEl = document.getElementById("input");
|
|
7253
|
+
if (inputEl && inputEl._origPlaceholder) {
|
|
7254
|
+
inputEl.placeholder = inputEl._origPlaceholder;
|
|
7255
|
+
delete inputEl._origPlaceholder;
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
|
|
7259
|
+
function handleDebateConcludeSend() {
|
|
7260
|
+
var text = inputEl.value.trim();
|
|
7261
|
+
if (ws && ws.readyState === 1) {
|
|
7262
|
+
ws.send(JSON.stringify({ type: "debate_conclude_response", action: "continue", text: text }));
|
|
7263
|
+
}
|
|
7264
|
+
inputEl.value = "";
|
|
7265
|
+
exitDebateConcludeMode();
|
|
7266
|
+
showDebateBottomBar("live");
|
|
7267
|
+
}
|
|
7268
|
+
|
|
7269
|
+
var debateEndedMode = false;
|
|
7270
|
+
|
|
7271
|
+
function showDebateEndedMode(msg) {
|
|
7272
|
+
debateEndedMode = true;
|
|
7273
|
+
removeDebateBottomBar();
|
|
7274
|
+
var inputArea = document.getElementById("input-area");
|
|
7275
|
+
if (inputArea) {
|
|
7276
|
+
inputArea.classList.add("debate-floor-mode");
|
|
7277
|
+
inputArea.style.display = "";
|
|
7278
|
+
}
|
|
7279
|
+
var existingBanner = document.getElementById("debate-floor-banner");
|
|
7280
|
+
if (existingBanner) existingBanner.remove();
|
|
7281
|
+
var banner = document.createElement("div");
|
|
7282
|
+
banner.id = "debate-floor-banner";
|
|
7283
|
+
banner.className = "debate-floor-banner";
|
|
7284
|
+
banner.innerHTML = iconHtml("check-circle") + " <span>Debate ended</span>" +
|
|
7285
|
+
'<button class="debate-floor-done-btn" id="debate-ended-resume-btn">Resume</button>' +
|
|
7286
|
+
'<button class="debate-floor-done-btn" id="debate-ended-pdf-btn">' + iconHtml("download") + ' PDF</button>';
|
|
7287
|
+
if (inputArea && inputArea.parentNode) {
|
|
7288
|
+
inputArea.parentNode.insertBefore(banner, inputArea);
|
|
7289
|
+
}
|
|
7290
|
+
refreshIcons();
|
|
7291
|
+
// Resume button
|
|
7292
|
+
var resumeBtn = document.getElementById("debate-ended-resume-btn");
|
|
7293
|
+
if (resumeBtn) {
|
|
7294
|
+
resumeBtn.addEventListener("click", function () {
|
|
7295
|
+
handleDebateEndedSend();
|
|
7296
|
+
});
|
|
7297
|
+
}
|
|
7298
|
+
// PDF button
|
|
7299
|
+
var pdfBtn = document.getElementById("debate-ended-pdf-btn");
|
|
7300
|
+
if (pdfBtn) {
|
|
7301
|
+
pdfBtn.addEventListener("click", function () {
|
|
7302
|
+
pdfBtn.disabled = true;
|
|
7303
|
+
exportDebateAsPdf().then(function () { pdfBtn.disabled = false; }).catch(function () { pdfBtn.disabled = false; });
|
|
7304
|
+
});
|
|
7305
|
+
}
|
|
7306
|
+
var inputEl2 = document.getElementById("input");
|
|
7307
|
+
if (inputEl2) {
|
|
7308
|
+
inputEl2._origPlaceholder = inputEl2._origPlaceholder || inputEl2.placeholder;
|
|
7309
|
+
inputEl2.placeholder = "Continue with a new direction...";
|
|
7310
|
+
}
|
|
7311
|
+
scrollToBottom();
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
function exitDebateEndedMode() {
|
|
7315
|
+
debateEndedMode = false;
|
|
7316
|
+
var inputArea = document.getElementById("input-area");
|
|
7317
|
+
if (inputArea) inputArea.classList.remove("debate-floor-mode");
|
|
7318
|
+
var banner = document.getElementById("debate-floor-banner");
|
|
7319
|
+
if (banner) banner.remove();
|
|
7320
|
+
var inputEl2 = document.getElementById("input");
|
|
7321
|
+
if (inputEl2 && inputEl2._origPlaceholder) {
|
|
7322
|
+
inputEl2.placeholder = inputEl2._origPlaceholder;
|
|
7323
|
+
delete inputEl2._origPlaceholder;
|
|
7324
|
+
}
|
|
7325
|
+
}
|
|
7326
|
+
|
|
7327
|
+
function handleDebateEndedSend() {
|
|
7328
|
+
var text = inputEl.value.trim();
|
|
7329
|
+
if (ws && ws.readyState === 1) {
|
|
7330
|
+
ws.send(JSON.stringify({ type: "debate_conclude_response", action: "continue", text: text }));
|
|
7331
|
+
}
|
|
7332
|
+
inputEl.value = "";
|
|
7333
|
+
exitDebateEndedMode();
|
|
7334
|
+
}
|
|
7335
|
+
|
|
7336
|
+
function showDebateUserFloor(msg) {
|
|
7337
|
+
debateFloorMode = true;
|
|
7338
|
+
// Remove debate bottom bar and show input area in floor mode
|
|
7339
|
+
removeDebateBottomBar();
|
|
7340
|
+
var inputArea = document.getElementById("input-area");
|
|
7341
|
+
if (inputArea) {
|
|
7342
|
+
inputArea.classList.add("debate-floor-mode");
|
|
7343
|
+
inputArea.style.display = "";
|
|
7344
|
+
}
|
|
7345
|
+
// Add floor banner above input
|
|
7346
|
+
var existingBanner = document.getElementById("debate-floor-banner");
|
|
7347
|
+
if (existingBanner) existingBanner.remove();
|
|
7348
|
+
var banner = document.createElement("div");
|
|
7349
|
+
banner.id = "debate-floor-banner";
|
|
7350
|
+
banner.className = "debate-floor-banner";
|
|
7351
|
+
banner.innerHTML = iconHtml("mic") + " <span>You have the floor</span>" +
|
|
7352
|
+
'<button class="debate-floor-done-btn" id="debate-floor-done-btn">Pass</button>';
|
|
7353
|
+
if (inputArea && inputArea.parentNode) {
|
|
7354
|
+
inputArea.parentNode.insertBefore(banner, inputArea);
|
|
7355
|
+
}
|
|
7356
|
+
refreshIcons();
|
|
7357
|
+
// Done button: exit floor mode without sending
|
|
7358
|
+
var doneBtn = document.getElementById("debate-floor-done-btn");
|
|
7359
|
+
if (doneBtn) {
|
|
7360
|
+
doneBtn.addEventListener("click", function () {
|
|
7361
|
+
// Pass without speaking: resume debate
|
|
7362
|
+
if (ws && ws.readyState === 1) {
|
|
7363
|
+
ws.send(JSON.stringify({ type: "debate_user_floor_response", text: "(The user passed without speaking)" }));
|
|
7364
|
+
}
|
|
7365
|
+
exitDebateFloorMode();
|
|
7366
|
+
showDebateBottomBar("live");
|
|
7367
|
+
});
|
|
7368
|
+
}
|
|
7369
|
+
// Update placeholder
|
|
7370
|
+
var inputEl = document.getElementById("input");
|
|
7371
|
+
if (inputEl) {
|
|
7372
|
+
inputEl._origPlaceholder = inputEl.placeholder;
|
|
7373
|
+
inputEl.placeholder = "Share your thoughts with the panel...";
|
|
7374
|
+
inputEl.focus();
|
|
7375
|
+
}
|
|
7376
|
+
scrollToBottom();
|
|
7377
|
+
}
|
|
7378
|
+
|
|
7379
|
+
function exitDebateFloorMode() {
|
|
7380
|
+
debateFloorMode = false;
|
|
7381
|
+
var inputArea = document.getElementById("input-area");
|
|
7382
|
+
if (inputArea) inputArea.classList.remove("debate-floor-mode");
|
|
7383
|
+
var banner = document.getElementById("debate-floor-banner");
|
|
7384
|
+
if (banner) banner.remove();
|
|
7385
|
+
var inputEl = document.getElementById("input");
|
|
7386
|
+
if (inputEl && inputEl._origPlaceholder) {
|
|
7387
|
+
inputEl.placeholder = inputEl._origPlaceholder;
|
|
7388
|
+
delete inputEl._origPlaceholder;
|
|
7389
|
+
}
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
function handleDebateFloorSend() {
|
|
7393
|
+
var text = inputEl.value.trim();
|
|
7394
|
+
if (!text) return;
|
|
7395
|
+
if (ws && ws.readyState === 1) {
|
|
7396
|
+
ws.send(JSON.stringify({ type: "debate_user_floor_response", text: text }));
|
|
7397
|
+
}
|
|
7398
|
+
inputEl.value = "";
|
|
7399
|
+
exitDebateFloorMode();
|
|
7400
|
+
showDebateBottomBar("live");
|
|
7401
|
+
}
|
|
7402
|
+
|
|
7403
|
+
function renderDebateUserFloorDone(msg) {
|
|
7404
|
+
if (!messagesEl) return;
|
|
7405
|
+
var el = document.createElement("div");
|
|
7406
|
+
el.className = "debate-user-comment";
|
|
7407
|
+
var label = document.createElement("span");
|
|
7408
|
+
label.className = "debate-comment-label";
|
|
7409
|
+
label.innerHTML = iconHtml("mic") + " User:";
|
|
7410
|
+
var textEl = document.createElement("div");
|
|
7411
|
+
textEl.className = "debate-comment-text";
|
|
7412
|
+
textEl.textContent = msg.text || "";
|
|
7413
|
+
el.appendChild(label);
|
|
7414
|
+
el.appendChild(textEl);
|
|
7415
|
+
messagesEl.appendChild(el);
|
|
7416
|
+
refreshIcons();
|
|
7417
|
+
scrollToBottom();
|
|
7418
|
+
}
|
|
7419
|
+
|
|
6709
7420
|
// Legacy handler kept for compatibility
|
|
6710
7421
|
function showDebateSticky(phase, msg) {
|
|
6711
7422
|
if (phase === "ended" || phase === "hide") {
|
|
@@ -6769,6 +7480,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
6769
7480
|
bar.innerHTML =
|
|
6770
7481
|
'<div class="debate-bottom-inner">' +
|
|
6771
7482
|
'<button class="debate-bottom-hand" id="debate-bottom-hand">' + iconHtml("hand") + ' Raise hand</button>' +
|
|
7483
|
+
'<span class="debate-bottom-waiting hidden" id="debate-bottom-waiting">' + iconHtml("loader") + ' You will get the floor after the current speaker</span>' +
|
|
6772
7484
|
'<button class="debate-bottom-stop" id="debate-bottom-stop">' + iconHtml("square") + ' Stop</button>' +
|
|
6773
7485
|
'</div>';
|
|
6774
7486
|
|
|
@@ -6776,6 +7488,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
6776
7488
|
inputArea.style.display = "none";
|
|
6777
7489
|
refreshIcons();
|
|
6778
7490
|
|
|
7491
|
+
// Restore raised state if hand was already raised
|
|
7492
|
+
if (debateHandRaiseOpen) {
|
|
7493
|
+
var handBtn = document.getElementById("debate-bottom-hand");
|
|
7494
|
+
var waitingEl = document.getElementById("debate-bottom-waiting");
|
|
7495
|
+
if (handBtn) { handBtn.classList.add("raised"); handBtn.classList.add("hidden"); }
|
|
7496
|
+
if (waitingEl) waitingEl.classList.remove("hidden");
|
|
7497
|
+
}
|
|
7498
|
+
|
|
6779
7499
|
document.getElementById("debate-bottom-hand").addEventListener("click", function () {
|
|
6780
7500
|
toggleDebateHandRaise();
|
|
6781
7501
|
});
|
|
@@ -6785,47 +7505,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
6785
7505
|
}
|
|
6786
7506
|
});
|
|
6787
7507
|
} else if (mode === "conclude") {
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
'<textarea class="debate-bottom-conclude-input" id="debate-bottom-conclude-input" rows="3" placeholder="Or add a direction to continue..."></textarea>' +
|
|
6792
|
-
'<div class="debate-bottom-conclude-actions">' +
|
|
6793
|
-
'<button class="debate-bottom-continue" id="debate-bottom-continue">Continue</button>' +
|
|
6794
|
-
'<button class="debate-bottom-end" id="debate-bottom-end">End Debate</button>' +
|
|
6795
|
-
'</div>' +
|
|
6796
|
-
'</div>';
|
|
6797
|
-
|
|
6798
|
-
inputArea.parentNode.insertBefore(bar, inputArea);
|
|
6799
|
-
inputArea.style.display = "none";
|
|
6800
|
-
refreshIcons();
|
|
6801
|
-
|
|
6802
|
-
var textArea = document.getElementById("debate-bottom-conclude-input");
|
|
6803
|
-
document.getElementById("debate-bottom-end").addEventListener("click", function () {
|
|
6804
|
-
if (ws && ws.readyState === 1) {
|
|
6805
|
-
ws.send(JSON.stringify({ type: "debate_conclude_response", action: "end" }));
|
|
6806
|
-
}
|
|
6807
|
-
removeDebateBottomBar();
|
|
6808
|
-
});
|
|
6809
|
-
document.getElementById("debate-bottom-continue").addEventListener("click", function () {
|
|
6810
|
-
var text = textArea ? textArea.value.trim() : "";
|
|
6811
|
-
if (ws && ws.readyState === 1) {
|
|
6812
|
-
ws.send(JSON.stringify({ type: "debate_conclude_response", action: "continue", text: text }));
|
|
6813
|
-
}
|
|
6814
|
-
removeDebateBottomBar();
|
|
6815
|
-
showDebateBottomBar("live");
|
|
6816
|
-
});
|
|
6817
|
-
if (textArea) {
|
|
6818
|
-
textArea.focus();
|
|
6819
|
-
textArea.addEventListener("keydown", function (e) {
|
|
6820
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
6821
|
-
e.preventDefault();
|
|
6822
|
-
document.getElementById("debate-bottom-continue").click();
|
|
6823
|
-
}
|
|
6824
|
-
});
|
|
6825
|
-
textArea.addEventListener("input", function () {
|
|
6826
|
-
debateAutoResize(textArea, 12);
|
|
6827
|
-
});
|
|
6828
|
-
}
|
|
7508
|
+
// Use native input area with conclude banner
|
|
7509
|
+
showDebateConcludeMode();
|
|
7510
|
+
return; // don't create a separate bar
|
|
6829
7511
|
}
|
|
6830
7512
|
}
|
|
6831
7513
|
|
|
@@ -6845,74 +7527,34 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
6845
7527
|
var handBar = document.getElementById("debate-hand-raise-bar");
|
|
6846
7528
|
if (handBar) handBar.remove();
|
|
6847
7529
|
debateHandRaiseOpen = false;
|
|
7530
|
+
// Clean up floor/conclude modes
|
|
7531
|
+
if (debateFloorMode) exitDebateFloorMode();
|
|
7532
|
+
if (debateConcludeMode) exitDebateConcludeMode();
|
|
7533
|
+
if (debateEndedMode) exitDebateEndedMode();
|
|
6848
7534
|
// Restore input area
|
|
6849
7535
|
var inputArea = document.getElementById("input-area");
|
|
6850
7536
|
if (inputArea) inputArea.style.display = "";
|
|
6851
7537
|
}
|
|
6852
7538
|
|
|
6853
7539
|
function toggleDebateHandRaise(forceState) {
|
|
6854
|
-
var
|
|
6855
|
-
debateHandRaiseOpen =
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
if (
|
|
6865
|
-
|
|
6866
|
-
}
|
|
6867
|
-
|
|
6868
|
-
// Create hand raise bar above input area
|
|
6869
|
-
var bar = document.createElement("div");
|
|
6870
|
-
bar.id = "debate-hand-raise-bar";
|
|
6871
|
-
bar.className = "debate-hand-raise-bar";
|
|
6872
|
-
bar.innerHTML =
|
|
6873
|
-
'<div class="debate-hand-raise-inner">' +
|
|
6874
|
-
'<span class="debate-hand-raise-label">' + iconHtml("hand") + ' Your comment:</span>' +
|
|
6875
|
-
'<textarea class="debate-hand-input" rows="1" placeholder="Type your comment..."></textarea>' +
|
|
6876
|
-
'<button class="debate-hand-send">Send</button>' +
|
|
6877
|
-
'<button class="debate-hand-cancel">Cancel</button>' +
|
|
6878
|
-
'</div>';
|
|
6879
|
-
|
|
6880
|
-
var inputArea = document.getElementById("input-area");
|
|
6881
|
-
if (inputArea && inputArea.parentNode) {
|
|
6882
|
-
inputArea.parentNode.insertBefore(bar, inputArea);
|
|
6883
|
-
}
|
|
6884
|
-
refreshIcons();
|
|
6885
|
-
|
|
6886
|
-
var textarea = bar.querySelector(".debate-hand-input");
|
|
6887
|
-
var sendBtn = bar.querySelector(".debate-hand-send");
|
|
6888
|
-
var cancelBtn = bar.querySelector(".debate-hand-cancel");
|
|
6889
|
-
|
|
6890
|
-
if (textarea) {
|
|
6891
|
-
textarea.focus();
|
|
6892
|
-
textarea.addEventListener("input", function () {
|
|
6893
|
-
debateAutoResize(textarea, 12);
|
|
6894
|
-
});
|
|
7540
|
+
var raise = typeof forceState === "boolean" ? forceState : !debateHandRaiseOpen;
|
|
7541
|
+
debateHandRaiseOpen = raise;
|
|
7542
|
+
|
|
7543
|
+
// Update UI: hide hand button, show waiting message
|
|
7544
|
+
var handBtn = document.getElementById("debate-bottom-hand");
|
|
7545
|
+
var waitingEl = document.getElementById("debate-bottom-waiting");
|
|
7546
|
+
if (raise) {
|
|
7547
|
+
if (handBtn) { handBtn.classList.add("raised"); handBtn.classList.add("hidden"); }
|
|
7548
|
+
if (waitingEl) waitingEl.classList.remove("hidden");
|
|
7549
|
+
} else {
|
|
7550
|
+
if (handBtn) { handBtn.classList.remove("raised"); handBtn.classList.remove("hidden"); }
|
|
7551
|
+
if (waitingEl) waitingEl.classList.add("hidden");
|
|
6895
7552
|
}
|
|
6896
7553
|
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
if (!text) return;
|
|
6900
|
-
if (ws && ws.readyState === 1) {
|
|
6901
|
-
ws.send(JSON.stringify({ type: "debate_comment", text: text }));
|
|
6902
|
-
}
|
|
6903
|
-
toggleDebateHandRaise(false);
|
|
6904
|
-
});
|
|
6905
|
-
|
|
6906
|
-
cancelBtn.addEventListener("click", function () {
|
|
6907
|
-
toggleDebateHandRaise(false);
|
|
6908
|
-
});
|
|
6909
|
-
|
|
6910
|
-
if (textarea) {
|
|
6911
|
-
textarea.addEventListener("keydown", function (e) {
|
|
6912
|
-
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendBtn.click(); }
|
|
6913
|
-
if (e.key === "Escape") { toggleDebateHandRaise(false); }
|
|
6914
|
-
});
|
|
7554
|
+
if (raise && ws && ws.readyState === 1) {
|
|
7555
|
+
ws.send(JSON.stringify({ type: "debate_hand_raise" }));
|
|
6915
7556
|
}
|
|
7557
|
+
// Floor mode will be activated when server sends debate_user_floor
|
|
6916
7558
|
}
|
|
6917
7559
|
|
|
6918
7560
|
function sendDebateStickyComment() {
|