jupyterlab-codex-sidebar 0.1.5 → 0.1.6
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/.jupyterlab-playwright.log +0 -0
- package/README.md +19 -3
- package/jupyterlab_codex/labextension/package.json +2 -2
- package/jupyterlab_codex/labextension/static/737.e7de3ad9dd6ded798340.js +1 -0
- package/jupyterlab_codex/labextension/static/{remoteEntry.c1e865f207776f7f24ff.js → remoteEntry.6ef5e7167763a316c000.js} +1 -1
- package/jupyterlab_codex/sessions.py +1 -1
- package/lib/codexChat.js +148 -52
- package/lib/codexChat.js.map +1 -1
- package/lib/codexChatAttachmentLimit.d.ts +12 -2
- package/lib/codexChatAttachmentLimit.js +17 -28
- package/lib/codexChatAttachmentLimit.js.map +1 -1
- package/lib/codexChatAttachmentState.d.ts +15 -0
- package/lib/codexChatAttachmentState.js +16 -0
- package/lib/codexChatAttachmentState.js.map +1 -0
- package/lib/codexChatDocumentUtils.d.ts +3 -1
- package/lib/codexChatDocumentUtils.js +44 -18
- package/lib/codexChatDocumentUtils.js.map +1 -1
- package/lib/codexChatPrimitives.d.ts +4 -1
- package/lib/codexChatPrimitives.js +4 -0
- package/lib/codexChatPrimitives.js.map +1 -1
- package/package.json +1 -1
- package/playwright.config.cjs +4 -1
- package/pyproject.toml +1 -1
- package/src/codexChat.tsx +234 -75
- package/src/codexChatAttachmentLimit.ts +30 -31
- package/src/codexChatAttachmentState.ts +37 -0
- package/src/codexChatDocumentUtils.ts +52 -20
- package/src/codexChatPrimitives.tsx +25 -1
- package/style/index.css +96 -40
- package/test-results/.last-run.json +4 -0
- package/test.py +0 -0
- package/tests/e2e/cell-output-error-tail.spec.js +156 -0
- package/tests/e2e/codex-ui-test-helpers.js +138 -0
- package/tests/e2e/fixtures/notebooks/error-output-tail.ipynb +58 -0
- package/tests/e2e/fixtures/notebooks/error-output-tail.py +19 -0
- package/tests/e2e/mock-codex-cli-prompt-echo.py +88 -0
- package/tests/unit/codexChatAttachmentLimit.spec.ts +23 -8
- package/tests/unit/codexChatAttachmentState.spec.ts +71 -0
- package/tests/unit/codexChatDocumentUtils.spec.ts +63 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/jupyterlab_codex/labextension/static/855.d20f6158cd81bb4c9056.js +0 -1
package/lib/codexChat.js
CHANGED
|
@@ -12,10 +12,11 @@ import { STORAGE_KEY_SESSION_THREADS, STORAGE_KEY_SESSION_THREADS_EVENT, buildSe
|
|
|
12
12
|
import { createSession as createBaseSession, createThreadResetSession as createBaseThreadResetSession } from './codexChatSessionFactory';
|
|
13
13
|
import { resolveCurrentSessionKey, resolveSessionKey } from './codexChatSessionKey';
|
|
14
14
|
import { resolveMessageSessionKey as resolveMessageSessionKeyForMessage } from './codexSessionResolver';
|
|
15
|
-
import { MESSAGE_SELECTION_PREVIEW_STORED_MAX_CHARS, captureDocumentViewState, findDocumentWidgetByPath, getActiveCellOutput, getActiveCellText, getActiveDocumentWidget, getDocumentContext, getSelectedContext, getSelectedTextFromActiveCell, getSelectedTextFromFileEditor, getSupportedDocumentPath, normalizeSelectionPreviewText, restoreDocumentViewState, toCellOutputPreview,
|
|
15
|
+
import { MESSAGE_SELECTION_PREVIEW_STORED_MAX_CHARS, captureDocumentViewState, findDocumentWidgetByPath, getActiveCellOutput, getActiveCellText, getActiveDocumentWidget, getDocumentContext, getSelectedContext, getSelectedTextFromActiveCell, getSelectedTextFromFileEditor, getSupportedDocumentPath, isNotebookWidget, normalizeSelectionPreviewText, restoreDocumentViewState, toCellOutputPreview, toMessageSelectionPreview, } from './codexChatDocumentUtils';
|
|
16
|
+
import { resolveCellAttachmentState } from './codexChatAttachmentState';
|
|
16
17
|
import { buildActiveCellOutputSignature, buildActiveCellSelectionSignature, isDuplicateActiveCellAttachmentSignature, makeActiveCellAttachmentDedupKey } from './codexChatAttachmentDedup';
|
|
17
|
-
import { buildAttachmentTruncationNotice, limitActiveCellAttachmentPayload } from './codexChatAttachmentLimit';
|
|
18
|
-
import { ArrowDownIcon, ArrowUpIcon, CheckIcon, ContextWindowIcon, FileIcon, GearIcon, ImageIcon, PlusIcon, PortalMenu, ReasoningEffortIcon, ShieldIcon, StopIcon, XIcon
|
|
18
|
+
import { buildAttachmentTruncationNotice, limitActiveCellAttachmentPayload, resolveSentAttachmentTruncation } from './codexChatAttachmentLimit';
|
|
19
|
+
import { ArrowDownIcon, ArrowUpIcon, BatteryIcon, CellAttachmentIcon, CheckIcon, ContextWindowIcon, FileIcon, GearIcon, ImageIcon, PlusIcon, PortalMenu, ReasoningEffortIcon, ShieldIcon, StopIcon, XIcon } from './codexChatPrimitives';
|
|
19
20
|
import { hasStoredValue, safeLocalStorageGet, safeLocalStorageRemove, safeLocalStorageSet } from './codexChatStorage';
|
|
20
21
|
const truncateEnd = truncateEndShared;
|
|
21
22
|
export class CodexPanel extends ReactWidget {
|
|
@@ -93,7 +94,8 @@ const SELECTION_PREVIEWS_STORAGE_KEY = 'jupyterlab-codex:selection-previews';
|
|
|
93
94
|
const MAX_IMAGE_ATTACHMENTS = 4;
|
|
94
95
|
const MAX_IMAGE_ATTACHMENT_BYTES = 4 * 1024 * 1024; // Avoid huge WebSocket payloads.
|
|
95
96
|
const MAX_IMAGE_ATTACHMENT_TOTAL_BYTES = 6 * 1024 * 1024;
|
|
96
|
-
const
|
|
97
|
+
const MAX_ACTIVE_CELL_SELECTION_CHARS = 4000;
|
|
98
|
+
const MAX_ACTIVE_CELL_OUTPUT_CHARS = 20000;
|
|
97
99
|
const MAX_STORED_SELECTION_PREVIEW_THREADS = 80;
|
|
98
100
|
const MAX_STORED_SELECTION_PREVIEW_MESSAGES_PER_THREAD = 10;
|
|
99
101
|
const MAX_SESSION_MESSAGES = 100;
|
|
@@ -444,7 +446,7 @@ function CodexChat(props) {
|
|
|
444
446
|
const [autoSaveBeforeSend, setAutoSaveBeforeSend] = useState(() => readStoredAutoSave());
|
|
445
447
|
const [includeActiveCell, setIncludeActiveCell] = useState(() => readStoredIncludeActiveCell());
|
|
446
448
|
const [includeActiveCellOutput, setIncludeActiveCellOutput] = useState(() => readStoredIncludeActiveCellOutput());
|
|
447
|
-
const [
|
|
449
|
+
const [currentDocumentIsNotebookEditor, setCurrentDocumentIsNotebookEditor] = useState(false);
|
|
448
450
|
const [notifyOnDone, setNotifyOnDone] = useState(() => readStoredNotifyOnDone());
|
|
449
451
|
const [notifyOnDoneMinSeconds, setNotifyOnDoneMinSeconds] = useState(() => readStoredNotifyOnDoneMinSeconds());
|
|
450
452
|
const [settingsOpen, setSettingsOpen] = useState(() => readStoredSettingsOpen());
|
|
@@ -463,7 +465,9 @@ function CodexChat(props) {
|
|
|
463
465
|
const [reasoningMenuOpen, setReasoningMenuOpen] = useState(false);
|
|
464
466
|
const [usagePopoverOpen, setUsagePopoverOpen] = useState(false);
|
|
465
467
|
const [permissionMenuOpen, setPermissionMenuOpen] = useState(false);
|
|
468
|
+
const [contextPopoverOpen, setContextPopoverOpen] = useState(false);
|
|
466
469
|
const [isPlainPyRunInProgress, setIsPlainPyRunInProgress] = useState(false);
|
|
470
|
+
const [cellAttachmentPopoverOpen, setCellAttachmentPopoverOpen] = useState(false);
|
|
467
471
|
const [selectionPopover, setSelectionPopover] = useState(null);
|
|
468
472
|
const storedSelectionPreviewsRef = useRef(readStoredSelectionPreviewsByThread());
|
|
469
473
|
const previousSessionThreadIdsRef = useRef(new Map());
|
|
@@ -486,6 +490,7 @@ function CodexChat(props) {
|
|
|
486
490
|
const reasoningMenuWrapRef = useRef(null);
|
|
487
491
|
const usageMenuWrapRef = useRef(null);
|
|
488
492
|
const permissionMenuWrapRef = useRef(null);
|
|
493
|
+
const contextMenuWrapRef = useRef(null);
|
|
489
494
|
const modelBtnRef = useRef(null);
|
|
490
495
|
const modelPopoverRef = useRef(null);
|
|
491
496
|
const reasoningBtnRef = useRef(null);
|
|
@@ -494,7 +499,13 @@ function CodexChat(props) {
|
|
|
494
499
|
const usagePopoverRef = useRef(null);
|
|
495
500
|
const permissionBtnRef = useRef(null);
|
|
496
501
|
const permissionPopoverRef = useRef(null);
|
|
502
|
+
const contextBtnRef = useRef(null);
|
|
503
|
+
const contextPopoverRef = useRef(null);
|
|
497
504
|
const plainPyRunSessionKeyRef = useRef('');
|
|
505
|
+
const cellAttachmentAnchorRef = useRef(null);
|
|
506
|
+
const cellAttachmentPopoverRef = useRef(null);
|
|
507
|
+
const cellAttachmentPopoverCloseTimerRef = useRef(null);
|
|
508
|
+
const contextPopoverCloseTimerRef = useRef(null);
|
|
498
509
|
const selectionPopoverAnchorRef = useRef(null);
|
|
499
510
|
const selectionPopoverRef = useRef(null);
|
|
500
511
|
const notebookLabelRef = useRef(null);
|
|
@@ -643,12 +654,6 @@ function CodexChat(props) {
|
|
|
643
654
|
useEffect(() => {
|
|
644
655
|
persistIncludeActiveCellOutput(includeActiveCellOutput);
|
|
645
656
|
}, [includeActiveCellOutput]);
|
|
646
|
-
useEffect(() => {
|
|
647
|
-
// Reset one-time exclusion when the base setting is turned off.
|
|
648
|
-
if (!includeActiveCell && excludeCellAttachmentForNextSend) {
|
|
649
|
-
setExcludeCellAttachmentForNextSend(false);
|
|
650
|
-
}
|
|
651
|
-
}, [includeActiveCell, excludeCellAttachmentForNextSend]);
|
|
652
657
|
useEffect(() => {
|
|
653
658
|
persistCommandPath(commandPath);
|
|
654
659
|
}, [commandPath]);
|
|
@@ -671,7 +676,7 @@ function CodexChat(props) {
|
|
|
671
676
|
persistNotifyOnDoneMinSeconds(normalized);
|
|
672
677
|
}, [notifyOnDoneMinSeconds]);
|
|
673
678
|
useEffect(() => {
|
|
674
|
-
if (!modelMenuOpen && !reasoningMenuOpen && !usagePopoverOpen && !permissionMenuOpen) {
|
|
679
|
+
if (!modelMenuOpen && !reasoningMenuOpen && !usagePopoverOpen && !permissionMenuOpen && !contextPopoverOpen) {
|
|
675
680
|
return;
|
|
676
681
|
}
|
|
677
682
|
const onPointerDown = (event) => {
|
|
@@ -683,24 +688,29 @@ function CodexChat(props) {
|
|
|
683
688
|
const inReasoning = reasoningMenuWrapRef.current?.contains(target) ?? false;
|
|
684
689
|
const inUsage = usageMenuWrapRef.current?.contains(target) ?? false;
|
|
685
690
|
const inPermission = permissionMenuWrapRef.current?.contains(target) ?? false;
|
|
691
|
+
const inContext = contextMenuWrapRef.current?.contains(target) ?? false;
|
|
686
692
|
const inModelPopover = modelPopoverRef.current?.contains(target) ?? false;
|
|
687
693
|
const inReasoningPopover = reasoningPopoverRef.current?.contains(target) ?? false;
|
|
688
694
|
const inUsagePopover = usagePopoverRef.current?.contains(target) ?? false;
|
|
689
695
|
const inPermissionPopover = permissionPopoverRef.current?.contains(target) ?? false;
|
|
696
|
+
const inContextPopover = contextPopoverRef.current?.contains(target) ?? false;
|
|
690
697
|
if (inModel ||
|
|
691
698
|
inReasoning ||
|
|
692
699
|
inUsage ||
|
|
693
700
|
inPermission ||
|
|
701
|
+
inContext ||
|
|
694
702
|
inModelPopover ||
|
|
695
703
|
inReasoningPopover ||
|
|
696
704
|
inUsagePopover ||
|
|
697
|
-
inPermissionPopover
|
|
705
|
+
inPermissionPopover ||
|
|
706
|
+
inContextPopover) {
|
|
698
707
|
return;
|
|
699
708
|
}
|
|
700
709
|
setModelMenuOpen(false);
|
|
701
710
|
setReasoningMenuOpen(false);
|
|
702
711
|
setUsagePopoverOpen(false);
|
|
703
712
|
setPermissionMenuOpen(false);
|
|
713
|
+
setContextPopoverOpen(false);
|
|
704
714
|
};
|
|
705
715
|
const onKeyDown = (event) => {
|
|
706
716
|
if (event.key !== 'Escape') {
|
|
@@ -711,6 +721,7 @@ function CodexChat(props) {
|
|
|
711
721
|
setReasoningMenuOpen(false);
|
|
712
722
|
setUsagePopoverOpen(false);
|
|
713
723
|
setPermissionMenuOpen(false);
|
|
724
|
+
setContextPopoverOpen(false);
|
|
714
725
|
};
|
|
715
726
|
window.addEventListener('pointerdown', onPointerDown, true);
|
|
716
727
|
window.addEventListener('keydown', onKeyDown);
|
|
@@ -718,7 +729,7 @@ function CodexChat(props) {
|
|
|
718
729
|
window.removeEventListener('pointerdown', onPointerDown, true);
|
|
719
730
|
window.removeEventListener('keydown', onKeyDown);
|
|
720
731
|
};
|
|
721
|
-
}, [modelMenuOpen, reasoningMenuOpen, usagePopoverOpen, permissionMenuOpen]);
|
|
732
|
+
}, [modelMenuOpen, reasoningMenuOpen, usagePopoverOpen, permissionMenuOpen, contextPopoverOpen]);
|
|
722
733
|
useEffect(() => {
|
|
723
734
|
if (!selectionPopover) {
|
|
724
735
|
return;
|
|
@@ -755,6 +766,66 @@ function CodexChat(props) {
|
|
|
755
766
|
setSelectionPopover(null);
|
|
756
767
|
selectionPopoverAnchorRef.current = null;
|
|
757
768
|
}
|
|
769
|
+
function clearCellAttachmentPopoverCloseTimer() {
|
|
770
|
+
if (cellAttachmentPopoverCloseTimerRef.current !== null) {
|
|
771
|
+
window.clearTimeout(cellAttachmentPopoverCloseTimerRef.current);
|
|
772
|
+
cellAttachmentPopoverCloseTimerRef.current = null;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
function clearContextPopoverCloseTimer() {
|
|
776
|
+
if (contextPopoverCloseTimerRef.current !== null) {
|
|
777
|
+
window.clearTimeout(contextPopoverCloseTimerRef.current);
|
|
778
|
+
contextPopoverCloseTimerRef.current = null;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function openCellAttachmentPopover() {
|
|
782
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
783
|
+
if (!showCellAttachmentBadge) {
|
|
784
|
+
setCellAttachmentPopoverOpen(false);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
setCellAttachmentPopoverOpen(true);
|
|
788
|
+
}
|
|
789
|
+
function scheduleCloseCellAttachmentPopover() {
|
|
790
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
791
|
+
cellAttachmentPopoverCloseTimerRef.current = window.setTimeout(() => {
|
|
792
|
+
setCellAttachmentPopoverOpen(false);
|
|
793
|
+
cellAttachmentPopoverCloseTimerRef.current = null;
|
|
794
|
+
}, 90);
|
|
795
|
+
}
|
|
796
|
+
function openContextPopover() {
|
|
797
|
+
clearContextPopoverCloseTimer();
|
|
798
|
+
if (!hasContextUsageSnapshot) {
|
|
799
|
+
setContextPopoverOpen(false);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
setContextPopoverOpen(true);
|
|
803
|
+
}
|
|
804
|
+
function scheduleCloseContextPopover() {
|
|
805
|
+
clearContextPopoverCloseTimer();
|
|
806
|
+
contextPopoverCloseTimerRef.current = window.setTimeout(() => {
|
|
807
|
+
setContextPopoverOpen(false);
|
|
808
|
+
contextPopoverCloseTimerRef.current = null;
|
|
809
|
+
}, 90);
|
|
810
|
+
}
|
|
811
|
+
function handleContextPopoverBlur(event) {
|
|
812
|
+
const nextFocused = event.relatedTarget;
|
|
813
|
+
const inAnchor = nextFocused ? contextMenuWrapRef.current?.contains(nextFocused) ?? false : false;
|
|
814
|
+
const inPopover = nextFocused ? contextPopoverRef.current?.contains(nextFocused) ?? false : false;
|
|
815
|
+
if (inAnchor || inPopover) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
scheduleCloseContextPopover();
|
|
819
|
+
}
|
|
820
|
+
function handleCellAttachmentBlur(event) {
|
|
821
|
+
const nextFocused = event.relatedTarget;
|
|
822
|
+
const inAnchor = nextFocused ? cellAttachmentAnchorRef.current?.contains(nextFocused) ?? false : false;
|
|
823
|
+
const inPopover = nextFocused ? cellAttachmentPopoverRef.current?.contains(nextFocused) ?? false : false;
|
|
824
|
+
if (inAnchor || inPopover) {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
scheduleCloseCellAttachmentPopover();
|
|
828
|
+
}
|
|
758
829
|
function toggleSelectionPopover(messageId, preview, event) {
|
|
759
830
|
if (!messageId) {
|
|
760
831
|
return;
|
|
@@ -932,6 +1003,7 @@ function CodexChat(props) {
|
|
|
932
1003
|
setModelMenuOpen(false);
|
|
933
1004
|
setReasoningMenuOpen(false);
|
|
934
1005
|
setPermissionMenuOpen(false);
|
|
1006
|
+
setContextPopoverOpen(false);
|
|
935
1007
|
}
|
|
936
1008
|
async function updateNotifyOnDone(enabled) {
|
|
937
1009
|
if (!enabled) {
|
|
@@ -1428,6 +1500,8 @@ function CodexChat(props) {
|
|
|
1428
1500
|
if (activeWidget) {
|
|
1429
1501
|
activeDocumentWidgetRef.current = activeWidget;
|
|
1430
1502
|
}
|
|
1503
|
+
const nextIsNotebookEditor = isNotebookWidget(activeWidget);
|
|
1504
|
+
setCurrentDocumentIsNotebookEditor(prev => (prev === nextIsNotebookEditor ? prev : nextIsNotebookEditor));
|
|
1431
1505
|
const path = getSupportedDocumentPath(activeWidget);
|
|
1432
1506
|
const sessionKey = resolveSessionKey(path);
|
|
1433
1507
|
const previousKey = currentNotebookSessionKeyRef.current;
|
|
@@ -1472,23 +1546,6 @@ function CodexChat(props) {
|
|
|
1472
1546
|
props.notebooks.currentChanged.disconnect(updateNotebook);
|
|
1473
1547
|
};
|
|
1474
1548
|
}, [props.app, props.notebooks]);
|
|
1475
|
-
useEffect(() => {
|
|
1476
|
-
const onActiveCellChanged = (_tracker, _cell) => {
|
|
1477
|
-
if (!includeActiveCell || !excludeCellAttachmentForNextSend) {
|
|
1478
|
-
return;
|
|
1479
|
-
}
|
|
1480
|
-
const currentNotebookWidget = props.notebooks.currentWidget;
|
|
1481
|
-
const currentNotebookWidgetPath = getSupportedDocumentPath(currentNotebookWidget);
|
|
1482
|
-
if (!currentNotebookWidgetPath || currentNotebookWidgetPath !== currentNotebookPathRef.current) {
|
|
1483
|
-
return;
|
|
1484
|
-
}
|
|
1485
|
-
setExcludeCellAttachmentForNextSend(false);
|
|
1486
|
-
};
|
|
1487
|
-
props.notebooks.activeCellChanged.connect(onActiveCellChanged);
|
|
1488
|
-
return () => {
|
|
1489
|
-
props.notebooks.activeCellChanged.disconnect(onActiveCellChanged);
|
|
1490
|
-
};
|
|
1491
|
-
}, [props.notebooks, includeActiveCell, excludeCellAttachmentForNextSend]);
|
|
1492
1549
|
useEffect(() => {
|
|
1493
1550
|
const onStorage = (event) => {
|
|
1494
1551
|
if (event.key !== STORAGE_KEY_SESSION_THREADS_EVENT || !event.newValue) {
|
|
@@ -1935,8 +1992,7 @@ function CodexChat(props) {
|
|
|
1935
1992
|
const selectedTextForContext = selectedContext?.text || '';
|
|
1936
1993
|
let includeSelectionKey = false;
|
|
1937
1994
|
let selection = '';
|
|
1938
|
-
|
|
1939
|
-
if (includeActiveCellForNextSend) {
|
|
1995
|
+
if (includeActiveCell) {
|
|
1940
1996
|
if (notebookMode === 'plain_py') {
|
|
1941
1997
|
const selectedText = selectedTextForContext || getSelectedTextFromActiveCell(activeWidget) || getSelectedTextFromFileEditor(activeWidget);
|
|
1942
1998
|
if (selectedText) {
|
|
@@ -1950,21 +2006,23 @@ function CodexChat(props) {
|
|
|
1950
2006
|
selectedTextForContext || getActiveCellText(activeWidget) || getSelectedTextFromFileEditor(activeWidget);
|
|
1951
2007
|
}
|
|
1952
2008
|
}
|
|
1953
|
-
const includeCellOutputKey =
|
|
2009
|
+
const includeCellOutputKey = includeActiveCell &&
|
|
2010
|
+
includeActiveCellOutput &&
|
|
2011
|
+
(notebookMode === 'ipynb' || notebookMode === 'jupytext_py');
|
|
1954
2012
|
const cellOutputRaw = includeCellOutputKey ? getActiveCellOutput(activeWidget) : '';
|
|
1955
|
-
const attachmentLimit = limitActiveCellAttachmentPayload(includeSelectionKey ? selection : '', includeCellOutputKey ? cellOutputRaw : '',
|
|
2013
|
+
const attachmentLimit = limitActiveCellAttachmentPayload(includeSelectionKey ? selection : '', includeCellOutputKey ? cellOutputRaw : '', MAX_ACTIVE_CELL_SELECTION_CHARS, MAX_ACTIVE_CELL_OUTPUT_CHARS);
|
|
1956
2014
|
const selectionForAttachment = includeSelectionKey ? attachmentLimit.selection : '';
|
|
1957
2015
|
const cellOutputForAttachment = includeCellOutputKey ? attachmentLimit.cellOutput : '';
|
|
1958
2016
|
const includeSelectionKeyAfterLimit = Boolean(selectionForAttachment);
|
|
1959
2017
|
const includeCellOutputKeyAfterLimit = Boolean(cellOutputForAttachment);
|
|
1960
2018
|
const messageSelectionPreview = includeSelectionKeyAfterLimit
|
|
1961
|
-
?
|
|
2019
|
+
? toMessageSelectionPreview(selectedContext, activeWidget, notebookMode, selectionForAttachment)
|
|
1962
2020
|
: undefined;
|
|
1963
2021
|
const messageCellOutputPreview = includeCellOutputKeyAfterLimit
|
|
1964
2022
|
? toCellOutputPreview(selectedContext, activeWidget, notebookMode, cellOutputForAttachment)
|
|
1965
2023
|
: undefined;
|
|
1966
|
-
const shouldDeduplicateSelection =
|
|
1967
|
-
const shouldDeduplicateCellOutput =
|
|
2024
|
+
const shouldDeduplicateSelection = includeActiveCell && includeSelectionKeyAfterLimit;
|
|
2025
|
+
const shouldDeduplicateCellOutput = includeActiveCell && includeCellOutputKeyAfterLimit;
|
|
1968
2026
|
const activeCellAttachmentDedupKey = makeActiveCellAttachmentDedupKey(sessionKey, session.threadId);
|
|
1969
2027
|
const previousActiveCellSignatures = lastActiveCellAttachmentSignatureRef.current.get(activeCellAttachmentDedupKey);
|
|
1970
2028
|
const activeCellSelectionSignature = shouldDeduplicateSelection
|
|
@@ -1987,6 +2045,12 @@ function CodexChat(props) {
|
|
|
1987
2045
|
isDuplicateActiveCellAttachmentSignature(previousActiveCellSignatures?.cellOutputSignature, activeCellOutputSignature);
|
|
1988
2046
|
const includeSelectionKeyForSend = includeSelectionKeyAfterLimit && !hasDuplicateSelectionAttachment;
|
|
1989
2047
|
const includeCellOutputKeyForSend = includeCellOutputKeyAfterLimit && !hasDuplicateCellOutputAttachment;
|
|
2048
|
+
const sentAttachmentTruncation = resolveSentAttachmentTruncation({
|
|
2049
|
+
includeSelection: includeSelectionKeyForSend,
|
|
2050
|
+
includeCellOutput: includeCellOutputKeyForSend,
|
|
2051
|
+
selectionTruncated: attachmentLimit.selectionTruncated,
|
|
2052
|
+
cellOutputTruncated: attachmentLimit.cellOutputTruncated
|
|
2053
|
+
});
|
|
1990
2054
|
const messageSelectionPreviewForSend = hasDuplicateSelectionAttachment ? undefined : messageSelectionPreview;
|
|
1991
2055
|
const messageCellOutputPreviewForSend = hasDuplicateCellOutputAttachment ? undefined : messageCellOutputPreview;
|
|
1992
2056
|
const messageContextPreview = messageSelectionPreviewForSend || messageCellOutputPreviewForSend
|
|
@@ -2030,8 +2094,8 @@ function CodexChat(props) {
|
|
|
2030
2094
|
sandbox: sandboxForSend,
|
|
2031
2095
|
...(includeSelectionKeyForSend ? { selection: selectionForAttachment } : {}),
|
|
2032
2096
|
...(includeCellOutputKeyForSend ? { cellOutput: cellOutputForAttachment } : {}),
|
|
2033
|
-
...(
|
|
2034
|
-
...(
|
|
2097
|
+
...(sentAttachmentTruncation.selectionTruncated ? { selectionTruncated: true } : {}),
|
|
2098
|
+
...(sentAttachmentTruncation.cellOutputTruncated ? { cellOutputTruncated: true } : {}),
|
|
2035
2099
|
...(images ? { images } : {}),
|
|
2036
2100
|
...(messageSelectionPreviewForSend ? { uiSelectionPreview: messageSelectionPreviewForSend } : {}),
|
|
2037
2101
|
...(messageCellOutputPreviewForSend ? { uiCellOutputPreview: messageCellOutputPreviewForSend } : {})
|
|
@@ -2057,7 +2121,7 @@ function CodexChat(props) {
|
|
|
2057
2121
|
appendStoredSelectionPreviewEntry(session.threadId, content, messageContextPreview);
|
|
2058
2122
|
const imageCount = images ? images.length : 0;
|
|
2059
2123
|
const showReadOnlyWarning = sandboxForSend === 'read-only';
|
|
2060
|
-
const attachmentTruncationNotice = buildAttachmentTruncationNotice(
|
|
2124
|
+
const attachmentTruncationNotice = buildAttachmentTruncationNotice(sentAttachmentTruncation.selectionTruncated, sentAttachmentTruncation.cellOutputTruncated, MAX_ACTIVE_CELL_SELECTION_CHARS, MAX_ACTIVE_CELL_OUTPUT_CHARS);
|
|
2061
2125
|
if (notebookMode === 'plain_py' || notebookMode === 'jupytext_py') {
|
|
2062
2126
|
plainPyRunSessionKeyRef.current = sessionKey;
|
|
2063
2127
|
setIsPlainPyRunInProgress(true);
|
|
@@ -2156,13 +2220,36 @@ function CodexChat(props) {
|
|
|
2156
2220
|
const displayPath = currentNotebookPath
|
|
2157
2221
|
? currentNotebookPath.split('/').pop() || 'Untitled'
|
|
2158
2222
|
: 'No notebook';
|
|
2159
|
-
const includeActiveCellForNextSend = includeActiveCell && !excludeCellAttachmentForNextSend;
|
|
2160
2223
|
const composerNotebookMode = currentSession?.notebookMode ?? inferNotebookModeFromPath(currentNotebookPath);
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2224
|
+
const cellAttachmentState = resolveCellAttachmentState({
|
|
2225
|
+
includeActiveCell,
|
|
2226
|
+
includeActiveCellOutput,
|
|
2227
|
+
notebookMode: composerNotebookMode,
|
|
2228
|
+
isNotebookEditor: currentDocumentIsNotebookEditor,
|
|
2229
|
+
currentNotebookPath,
|
|
2230
|
+
pairedOk: currentSession?.pairedOk
|
|
2231
|
+
});
|
|
2232
|
+
const includeCellOutputForNextSend = cellAttachmentState.outputEnabled;
|
|
2233
|
+
const showCellAttachmentBadge = cellAttachmentState.showBadge;
|
|
2234
|
+
const cellAttachmentContentEnabled = cellAttachmentState.contentEnabled;
|
|
2235
|
+
const cellAttachmentOutputEnabled = cellAttachmentState.outputEnabled;
|
|
2236
|
+
useEffect(() => {
|
|
2237
|
+
if (showCellAttachmentBadge) {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
setCellAttachmentPopoverOpen(false);
|
|
2241
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
2242
|
+
}, [showCellAttachmentBadge]);
|
|
2243
|
+
useEffect(() => {
|
|
2244
|
+
return () => {
|
|
2245
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
2246
|
+
};
|
|
2247
|
+
}, []);
|
|
2248
|
+
useEffect(() => {
|
|
2249
|
+
return () => {
|
|
2250
|
+
clearContextPopoverCloseTimer();
|
|
2251
|
+
};
|
|
2252
|
+
}, []);
|
|
2166
2253
|
const trimmedInput = input.trim();
|
|
2167
2254
|
const canSend = status !== 'disconnected' &&
|
|
2168
2255
|
currentNotebookPath.length > 0 &&
|
|
@@ -2244,6 +2331,13 @@ function CodexChat(props) {
|
|
|
2244
2331
|
? `${Math.round(clampNumber(contextUsedPercent, 0, 100))}%`
|
|
2245
2332
|
: 'Unknown';
|
|
2246
2333
|
const hasContextUsageSnapshot = rateLimits?.contextWindow != null;
|
|
2334
|
+
useEffect(() => {
|
|
2335
|
+
if (hasContextUsageSnapshot) {
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
setContextPopoverOpen(false);
|
|
2339
|
+
clearContextPopoverCloseTimer();
|
|
2340
|
+
}, [hasContextUsageSnapshot]);
|
|
2247
2341
|
useLayoutEffect(() => {
|
|
2248
2342
|
const target = notebookLabelRef.current;
|
|
2249
2343
|
if (!target) {
|
|
@@ -2286,6 +2380,7 @@ function CodexChat(props) {
|
|
|
2286
2380
|
setReasoningMenuOpen(false);
|
|
2287
2381
|
setUsagePopoverOpen(false);
|
|
2288
2382
|
setPermissionMenuOpen(false);
|
|
2383
|
+
setContextPopoverOpen(false);
|
|
2289
2384
|
}, className: `jp-CodexHeaderBtn jp-CodexHeaderBtn-icon${settingsOpen ? ' is-active' : ''}`, "aria-label": "Settings", "aria-expanded": settingsOpen, title: "Settings", children: _jsx(GearIcon, { width: 16, height: 16 }) })] })] }), currentSession?.pairedOk === false && (_jsxs("div", { className: "jp-CodexPairingNotice", role: "status", "aria-live": "polite", children: [_jsx("div", { className: "jp-CodexPairingNotice-title", children: "Jupytext pairing required" }), _jsx("div", { className: "jp-CodexPairingNotice-body", children: currentSession.pairedMessage ||
|
|
2290
2385
|
'This notebook must be paired (.ipynb ↔ .py) via Jupytext to enable running.' })] }))] }), _jsx("div", { className: "jp-CodexChat-body", children: _jsxs("div", { className: "jp-CodexChat-messages", ref: scrollRef, onScroll: onScrollMessages, children: [status === 'disconnected' && !isReconnecting && (_jsxs("div", { className: "jp-CodexChat-message jp-CodexChat-system jp-CodexChat-reconnectNotice", children: [_jsx("div", { className: "jp-CodexChat-role", children: "system" }), _jsx("div", { className: "jp-CodexChat-text", children: "Codex connection was lost. Reconnect to continue." }), _jsx("button", { type: "button", className: "jp-CodexReconnectBtn", onClick: () => reconnectSocket(), disabled: isReconnecting, "aria-label": isReconnecting ? 'Codex reconnecting' : 'Reconnect to Codex', title: isReconnecting ? 'Attempting to reconnect...' : 'Reconnect to Codex', children: isReconnecting ? 'Connecting...' : 'Reconnect' })] })), messages.length === 0 && (_jsxs("div", { className: "jp-CodexChat-message jp-CodexChat-system", children: [_jsx("div", { className: "jp-CodexChat-role", children: "system" }), _jsx("div", { className: "jp-CodexChat-text", children: "Select a notebook, then start a conversation." })] })), messages.map(entry => {
|
|
2291
2386
|
if (entry.kind === 'text') {
|
|
@@ -2322,9 +2417,7 @@ function CodexChat(props) {
|
|
|
2322
2417
|
return (_jsxs("details", { className: activityClassName, role: "status", "aria-live": "polite", children: [_jsx("summary", { className: "jp-CodexActivitySummary", children: summaryContent }), _jsx("div", { className: "jp-CodexActivityBody", children: _jsx("pre", { className: "jp-CodexActivityCode", children: _jsx("code", { children: trimmedDetail }) }) })] }, entry.id));
|
|
2323
2418
|
}
|
|
2324
2419
|
return (_jsx("div", { className: activityClassName, role: "status", "aria-live": "polite", children: _jsx("div", { className: "jp-CodexActivitySummary jp-CodexActivitySummaryStatic", children: summaryContent }) }, entry.id));
|
|
2325
|
-
}), status === 'running' && (_jsx("div", { className: `jp-CodexChat-loading${progressKind === 'reasoning' ? ' is-reasoning' : ''}`, "aria-label": progressKind === 'reasoning' ? 'Reasoning' : 'Running', children: _jsxs("div", { className: "jp-CodexChat-loading-dots", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }) })), _jsx("div", { ref: endRef })] }) }), _jsx(PortalMenu, { open: Boolean(selectionPopover), anchorRef: selectionPopoverAnchorRef, popoverRef: selectionPopoverRef, className: "jp-CodexChat-selectionPopover", ariaLabel: "Message context", constrainHeightToViewport: true, viewportMargin: 20, role: "dialog", align: "right", children: selectionPopover && (_jsxs("div", { className: "jp-CodexChat-selectionCard", role: "note", "aria-label": "Message context", children: [selectionPopover.preview.selectionPreview && (_jsxs("div", { className: "jp-CodexChat-contextSection", children: [_jsx("div", { className: "jp-CodexChat-selectionMeta", children: selectionPopover.preview.selectionPreview.locationLabel }), _jsx(SelectionPreviewCode, { code: selectionPopover.preview.selectionPreview.previewText })] })), selectionPopover.preview.cellOutputPreview && (_jsxs("div", { className: "jp-CodexChat-contextSection", children: [_jsx("div", { className: "jp-CodexChat-selectionMeta", children: selectionPopover.preview.cellOutputPreview.locationLabel }), _jsx(SelectionPreviewCode, { code: selectionPopover.preview.cellOutputPreview.previewText })] }))] })) }), _jsxs("div", { className: "jp-CodexChat-input", children: [_jsx("div", { className: `jp-CodexJumpBar${isAtBottom ? '' : ' is-visible'}`, children: _jsx("button", { type: "button", className: "jp-CodexJumpToLatestBtn", onClick: scrollToBottom, "aria-label": "Jump to latest", "aria-hidden": isAtBottom, tabIndex: isAtBottom ? -1 : 0, title: "Jump to latest", children: _jsx(ArrowDownIcon, { width: 20, height: 20 }) }) }), _jsxs("div", { className: "jp-CodexComposer", children: [_jsx("div", { className: `jp-CodexComposer-cellAttachmentWrap${showCellAttachmentBadge ? ' is-visible' : ''}`, "aria-hidden": !showCellAttachmentBadge, children:
|
|
2326
|
-
? 'Active cell and output will be attached on next send.'
|
|
2327
|
-
: 'Active cell will be attached on next send.', children: [_jsx("span", { className: "jp-CodexComposer-cellAttachmentLabel", children: "Cell Attached" }), _jsx("button", { type: "button", className: "jp-CodexComposer-cellAttachmentRemove", onClick: () => setExcludeCellAttachmentForNextSend(true), "aria-label": "Do not attach cell on next send", title: "Do not attach cell on next send", disabled: !showCellAttachmentBadge, tabIndex: showCellAttachmentBadge ? 0 : -1, children: _jsx(XIcon, { width: 10, height: 10 }) })] }) }), _jsx("textarea", { ref: composerTextareaRef, value: input, onChange: e => {
|
|
2420
|
+
}), status === 'running' && (_jsx("div", { className: `jp-CodexChat-loading${progressKind === 'reasoning' ? ' is-reasoning' : ''}`, "aria-label": progressKind === 'reasoning' ? 'Reasoning' : 'Running', children: _jsxs("div", { className: "jp-CodexChat-loading-dots", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }) })), _jsx("div", { ref: endRef })] }) }), _jsx(PortalMenu, { open: Boolean(selectionPopover), anchorRef: selectionPopoverAnchorRef, popoverRef: selectionPopoverRef, className: "jp-CodexChat-selectionPopover", ariaLabel: "Message context", constrainHeightToViewport: true, viewportMargin: 20, role: "dialog", align: "right", children: selectionPopover && (_jsxs("div", { className: "jp-CodexChat-selectionCard", role: "note", "aria-label": "Message context", children: [selectionPopover.preview.selectionPreview && (_jsxs("div", { className: "jp-CodexChat-contextSection", children: [_jsx("div", { className: "jp-CodexChat-selectionMeta", children: selectionPopover.preview.selectionPreview.locationLabel }), _jsx(SelectionPreviewCode, { code: selectionPopover.preview.selectionPreview.previewText })] })), selectionPopover.preview.cellOutputPreview && (_jsxs("div", { className: "jp-CodexChat-contextSection", children: [_jsx("div", { className: "jp-CodexChat-selectionMeta", children: selectionPopover.preview.cellOutputPreview.locationLabel }), _jsx(SelectionPreviewCode, { code: selectionPopover.preview.cellOutputPreview.previewText })] }))] })) }), _jsx(PortalMenu, { open: cellAttachmentPopoverOpen && showCellAttachmentBadge, anchorRef: cellAttachmentAnchorRef, popoverRef: cellAttachmentPopoverRef, className: "jp-CodexCellAttachmentPopoverMenu", ariaLabel: "Cell attachment details", role: "dialog", align: "left", onMouseEnter: openCellAttachmentPopover, onMouseLeave: scheduleCloseCellAttachmentPopover, children: _jsxs("div", { className: "jp-CodexCellAttachmentPopoverCard", role: "note", "aria-label": "Cell attachment details", children: [_jsx("div", { className: "jp-CodexCellAttachmentPopoverTitle", children: "Attach On Next Send" }), _jsxs("div", { className: "jp-CodexCellAttachmentPopoverRow", children: [_jsx("span", { children: "Current cell content" }), _jsx("span", { className: `jp-CodexCellAttachmentDot ${cellAttachmentContentEnabled ? 'is-on' : 'is-off'}`, "aria-label": cellAttachmentContentEnabled ? 'Attached' : 'Not attached', title: cellAttachmentContentEnabled ? 'Attached' : 'Not attached' })] }), _jsxs("div", { className: "jp-CodexCellAttachmentPopoverRow", children: [_jsx("span", { children: "Current cell output" }), _jsx("span", { className: `jp-CodexCellAttachmentDot ${cellAttachmentOutputEnabled ? 'is-on' : 'is-off'}`, "aria-label": cellAttachmentOutputEnabled ? 'Attached' : 'Not attached', title: cellAttachmentOutputEnabled ? 'Attached' : 'Not attached' })] })] }) }), _jsxs("div", { className: "jp-CodexChat-input", children: [_jsx("div", { className: `jp-CodexJumpBar${isAtBottom ? '' : ' is-visible'}`, children: _jsx("button", { type: "button", className: "jp-CodexJumpToLatestBtn", onClick: scrollToBottom, "aria-label": "Jump to latest", "aria-hidden": isAtBottom, tabIndex: isAtBottom ? -1 : 0, title: "Jump to latest", children: _jsx(ArrowDownIcon, { width: 20, height: 20 }) }) }), _jsxs("div", { className: "jp-CodexComposer", children: [_jsx("div", { className: `jp-CodexCellAttachmentWrap jp-CodexComposer-cellAttachmentWrap${showCellAttachmentBadge ? ' is-visible' : ''}`, ref: cellAttachmentAnchorRef, "aria-hidden": !showCellAttachmentBadge, onMouseEnter: openCellAttachmentPopover, onMouseLeave: scheduleCloseCellAttachmentPopover, onFocusCapture: openCellAttachmentPopover, onBlurCapture: handleCellAttachmentBlur, children: _jsx("div", { role: "group", "aria-label": "Active-cell attachment", children: _jsxs("button", { type: "button", className: `jp-CodexComposer-cellAttachment${cellAttachmentContentEnabled ? '' : ' is-off'}`, onClick: () => setIncludeActiveCell(value => !value), "aria-pressed": cellAttachmentContentEnabled, "aria-label": cellAttachmentContentEnabled ? 'Disable active-cell attachment' : 'Enable active-cell attachment', title: cellAttachmentContentEnabled ? 'Disable active-cell attachment' : 'Enable active-cell attachment', disabled: !showCellAttachmentBadge, tabIndex: showCellAttachmentBadge ? 0 : -1, children: [_jsx(CellAttachmentIcon, { active: cellAttachmentContentEnabled, width: 15, height: 15 }), _jsx("span", { className: "jp-CodexComposer-cellAttachmentLabel", children: "Cell Attatch" })] }) }) }), _jsx("textarea", { ref: composerTextareaRef, value: input, onChange: e => {
|
|
2328
2421
|
updateInput(e.currentTarget.value);
|
|
2329
2422
|
// Resize using the current target so typing feels immediate.
|
|
2330
2423
|
window.requestAnimationFrame(() => autosizeComposerTextarea(e.currentTarget));
|
|
@@ -2354,6 +2447,7 @@ function CodexChat(props) {
|
|
|
2354
2447
|
setReasoningMenuOpen(false);
|
|
2355
2448
|
setUsagePopoverOpen(false);
|
|
2356
2449
|
setPermissionMenuOpen(false);
|
|
2450
|
+
setContextPopoverOpen(false);
|
|
2357
2451
|
}, disabled: status === 'running', "aria-label": `Model: ${selectedModelLabel}`, "aria-haspopup": "menu", "aria-expanded": modelMenuOpen, title: `Model: ${selectedModelLabel}`, children: _jsx("span", { className: "jp-CodexModelBtn-label", children: selectedModelLabel }) }) }), _jsxs(PortalMenu, { open: modelMenuOpen, anchorRef: modelBtnRef, popoverRef: modelPopoverRef, role: "menu", ariaLabel: "Model", align: "left", children: [modelOptions.length === 0 && (_jsx("div", { className: "jp-CodexMenuItem", children: "No models available" })), modelOptions.map(option => {
|
|
2358
2452
|
const inferred = activeModelOption === '__config__' && autoModel && modelOptions.some(option => option.value === autoModel)
|
|
2359
2453
|
? autoModel
|
|
@@ -2368,6 +2462,7 @@ function CodexChat(props) {
|
|
|
2368
2462
|
setModelMenuOpen(false);
|
|
2369
2463
|
setUsagePopoverOpen(false);
|
|
2370
2464
|
setPermissionMenuOpen(false);
|
|
2465
|
+
setContextPopoverOpen(false);
|
|
2371
2466
|
}, disabled: status === 'running', "aria-label": `Reasoning: ${selectedReasoningLabel}`, "aria-haspopup": "menu", "aria-expanded": reasoningMenuOpen, title: `Reasoning: ${selectedReasoningLabel}`, children: _jsx(ReasoningEffortIcon, { isConfig: activeReasoningEffort === '__config__' && !autoReasoningEffort, activeBars: getReasoningEffortBars((activeReasoningEffort === '__config__' && autoReasoningEffort
|
|
2372
2467
|
? autoReasoningEffort
|
|
2373
2468
|
: activeReasoningEffort), reasoningOptions), width: 17, height: 17 }) }) }), _jsxs(PortalMenu, { open: reasoningMenuOpen, anchorRef: reasoningBtnRef, popoverRef: reasoningPopoverRef, role: "menu", ariaLabel: "Reasoning", align: "left", children: [reasoningOptions.length === 0 && (_jsx("div", { className: "jp-CodexMenuItem", children: "No reasoning options" })), reasoningOptions.map(option => {
|
|
@@ -2382,14 +2477,15 @@ function CodexChat(props) {
|
|
|
2382
2477
|
setModelMenuOpen(false);
|
|
2383
2478
|
setReasoningMenuOpen(false);
|
|
2384
2479
|
setUsagePopoverOpen(false);
|
|
2480
|
+
setContextPopoverOpen(false);
|
|
2385
2481
|
}, disabled: status === 'running', "aria-label": `Permission: ${selectedSandboxLabel}`, "aria-haspopup": "menu", "aria-expanded": permissionMenuOpen, title: `Permission: ${selectedSandboxLabel}`, children: _jsx(ShieldIcon, { width: 17, height: 17 }) }) }), _jsx(PortalMenu, { open: permissionMenuOpen, anchorRef: permissionBtnRef, popoverRef: permissionPopoverRef, role: "menu", ariaLabel: "Permissions", align: "right", children: SANDBOX_OPTIONS.map(option => (_jsxs("button", { type: "button", className: `jp-CodexMenuItem ${activeSandboxMode === option.value ? 'is-active' : ''}`, onClick: () => {
|
|
2386
2482
|
setCurrentSessionSandboxMode(option.value);
|
|
2387
2483
|
setPermissionMenuOpen(false);
|
|
2388
|
-
}, children: [_jsx("span", { className: "jp-CodexMenuItemLabel", children: option.label }), activeSandboxMode === option.value && _jsx(CheckIcon, { className: "jp-CodexMenuCheck", width: 16, height: 16 })] }, option.value))) }), hasContextUsageSnapshot && (_jsxs("div", { className: "jp-CodexContextWrap", children: [_jsx("button", { type: "button", className: `jp-CodexIconBtn jp-CodexContextBtn${usageIsStale ? ' is-stale' : ''}`, "aria-label": contextUsedTokens == null || contextLeftTokens == null
|
|
2484
|
+
}, children: [_jsx("span", { className: "jp-CodexMenuItemLabel", children: option.label }), activeSandboxMode === option.value && _jsx(CheckIcon, { className: "jp-CodexMenuCheck", width: 16, height: 16 })] }, option.value))) }), hasContextUsageSnapshot && (_jsxs("div", { className: "jp-CodexContextWrap", ref: contextMenuWrapRef, onMouseEnter: openContextPopover, onMouseLeave: scheduleCloseContextPopover, onFocusCapture: openContextPopover, onBlurCapture: handleContextPopoverBlur, children: [_jsx("button", { type: "button", className: `jp-CodexIconBtn jp-CodexContextBtn${usageIsStale ? ' is-stale' : ''}`, ref: contextBtnRef, "aria-label": contextUsedTokens == null || contextLeftTokens == null
|
|
2389
2485
|
? 'Context window usage unavailable'
|
|
2390
2486
|
: `Context window: used ${contextUsedLabel} tokens, left ${contextLeftLabel} tokens`, title: contextUsedTokens == null || contextLeftTokens == null
|
|
2391
2487
|
? 'Context window usage unavailable'
|
|
2392
|
-
: `Used ${contextUsedLabel} / left ${contextLeftLabel}`, children: _jsx(ContextWindowIcon, { level: contextLevel, width: 20, height: 20 }) }), _jsxs(
|
|
2488
|
+
: `Used ${contextUsedLabel} / left ${contextLeftLabel}`, children: _jsx(ContextWindowIcon, { level: contextLevel, width: 20, height: 20 }) }), _jsxs(PortalMenu, { open: contextPopoverOpen, anchorRef: contextBtnRef, popoverRef: contextPopoverRef, className: "jp-CodexContextPopover", role: "tooltip", ariaLabel: "Context window", align: "right", onMouseEnter: openContextPopover, onMouseLeave: scheduleCloseContextPopover, children: [_jsx("div", { className: "jp-CodexContextPopoverTitle", children: "Context window" }), _jsxs("div", { className: "jp-CodexContextPopoverRow", children: [_jsx("span", { children: "Used" }), _jsx("strong", { children: contextUsedLabel })] }), _jsxs("div", { className: "jp-CodexContextPopoverRow", children: [_jsx("span", { children: "Left" }), _jsx("strong", { children: contextLeftLabel })] }), _jsx("div", { className: "jp-CodexContextPopoverMeta", children: contextWindowTokens == null
|
|
2393
2489
|
? 'Window size unavailable'
|
|
2394
2490
|
: `Window: ${contextWindowLabel} tokens (${contextUsedPercentLabel} used)` })] })] }))] }), _jsx("div", { className: "jp-CodexComposer-toolbarRight", children: _jsx("button", { type: "button", className: `jp-CodexSendBtn${sendButtonMode === 'stop' ? ' is-stop' : ''}`, onClick: () => {
|
|
2395
2491
|
if (sendButtonMode === 'stop') {
|