jupyterlab-codex-sidebar 0.1.5 → 0.1.7
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 +43 -0
- package/README.md +19 -3
- package/jupyterlab_codex/labextension/package.json +2 -2
- package/jupyterlab_codex/labextension/static/737.58bcf09100c9bc7bd90d.js +1 -0
- package/jupyterlab_codex/labextension/static/{remoteEntry.c1e865f207776f7f24ff.js → remoteEntry.34af8a0df422b4d029c3.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 +43 -30
- 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 +4 -1
- package/lib/codexChatDocumentUtils.js +71 -19
- 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 +59 -33
- package/src/codexChatAttachmentState.ts +37 -0
- package/src/codexChatDocumentUtils.ts +89 -21
- 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 +165 -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 +92 -0
- package/tests/unit/codexChatAttachmentLimit.spec.ts +33 -8
- package/tests/unit/codexChatAttachmentState.spec.ts +71 -0
- package/tests/unit/codexChatDocumentUtils.spec.ts +78 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/jupyterlab_codex/labextension/static/855.d20f6158cd81bb4c9056.js +0 -1
package/src/codexChat.tsx
CHANGED
|
@@ -73,11 +73,13 @@ import {
|
|
|
73
73
|
getSelectedTextFromActiveCell,
|
|
74
74
|
getSelectedTextFromFileEditor,
|
|
75
75
|
getSupportedDocumentPath,
|
|
76
|
+
isNotebookWidget,
|
|
76
77
|
normalizeSelectionPreviewText,
|
|
77
78
|
restoreDocumentViewState,
|
|
78
79
|
toCellOutputPreview,
|
|
79
|
-
|
|
80
|
+
toMessageSelectionPreview,
|
|
80
81
|
} from './codexChatDocumentUtils';
|
|
82
|
+
import { resolveCellAttachmentState } from './codexChatAttachmentState';
|
|
81
83
|
import {
|
|
82
84
|
buildActiveCellOutputSignature,
|
|
83
85
|
buildActiveCellSelectionSignature,
|
|
@@ -86,13 +88,15 @@ import {
|
|
|
86
88
|
} from './codexChatAttachmentDedup';
|
|
87
89
|
import {
|
|
88
90
|
buildAttachmentTruncationNotice,
|
|
89
|
-
limitActiveCellAttachmentPayload
|
|
91
|
+
limitActiveCellAttachmentPayload,
|
|
92
|
+
resolveSentAttachmentTruncation
|
|
90
93
|
} from './codexChatAttachmentLimit';
|
|
91
94
|
import {
|
|
92
95
|
ArrowDownIcon,
|
|
93
96
|
ArrowUpIcon,
|
|
97
|
+
BatteryIcon,
|
|
98
|
+
CellAttachmentIcon,
|
|
94
99
|
CheckIcon,
|
|
95
|
-
ChipIcon,
|
|
96
100
|
ContextWindowIcon,
|
|
97
101
|
FileIcon,
|
|
98
102
|
GaugeIcon,
|
|
@@ -103,8 +107,7 @@ import {
|
|
|
103
107
|
ReasoningEffortIcon,
|
|
104
108
|
ShieldIcon,
|
|
105
109
|
StopIcon,
|
|
106
|
-
XIcon
|
|
107
|
-
BatteryIcon
|
|
110
|
+
XIcon
|
|
108
111
|
} from './codexChatPrimitives';
|
|
109
112
|
import {
|
|
110
113
|
hasStoredValue,
|
|
@@ -306,7 +309,8 @@ const SELECTION_PREVIEWS_STORAGE_KEY = 'jupyterlab-codex:selection-previews';
|
|
|
306
309
|
const MAX_IMAGE_ATTACHMENTS = 4;
|
|
307
310
|
const MAX_IMAGE_ATTACHMENT_BYTES = 4 * 1024 * 1024; // Avoid huge WebSocket payloads.
|
|
308
311
|
const MAX_IMAGE_ATTACHMENT_TOTAL_BYTES = 6 * 1024 * 1024;
|
|
309
|
-
const
|
|
312
|
+
const MAX_ACTIVE_CELL_SELECTION_CHARS = 4000;
|
|
313
|
+
const MAX_ACTIVE_CELL_OUTPUT_CHARS = 20000;
|
|
310
314
|
const MAX_STORED_SELECTION_PREVIEW_THREADS = 80;
|
|
311
315
|
const MAX_STORED_SELECTION_PREVIEW_MESSAGES_PER_THREAD = 10;
|
|
312
316
|
const MAX_SESSION_MESSAGES = 100;
|
|
@@ -755,7 +759,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
755
759
|
const [includeActiveCellOutput, setIncludeActiveCellOutput] = useState<boolean>(() =>
|
|
756
760
|
readStoredIncludeActiveCellOutput()
|
|
757
761
|
);
|
|
758
|
-
const [
|
|
762
|
+
const [currentDocumentIsNotebookEditor, setCurrentDocumentIsNotebookEditor] = useState(false);
|
|
759
763
|
const [notifyOnDone, setNotifyOnDone] = useState<boolean>(() => readStoredNotifyOnDone());
|
|
760
764
|
const [notifyOnDoneMinSeconds, setNotifyOnDoneMinSeconds] = useState<number>(() => readStoredNotifyOnDoneMinSeconds());
|
|
761
765
|
const [settingsOpen, setSettingsOpen] = useState<boolean>(() => readStoredSettingsOpen());
|
|
@@ -779,7 +783,9 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
779
783
|
const [reasoningMenuOpen, setReasoningMenuOpen] = useState(false);
|
|
780
784
|
const [usagePopoverOpen, setUsagePopoverOpen] = useState(false);
|
|
781
785
|
const [permissionMenuOpen, setPermissionMenuOpen] = useState(false);
|
|
786
|
+
const [contextPopoverOpen, setContextPopoverOpen] = useState(false);
|
|
782
787
|
const [isPlainPyRunInProgress, setIsPlainPyRunInProgress] = useState<boolean>(false);
|
|
788
|
+
const [cellAttachmentPopoverOpen, setCellAttachmentPopoverOpen] = useState(false);
|
|
783
789
|
const [selectionPopover, setSelectionPopover] = useState<{
|
|
784
790
|
messageId: string;
|
|
785
791
|
preview: MessageContextPreview;
|
|
@@ -807,6 +813,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
807
813
|
const reasoningMenuWrapRef = useRef<HTMLDivElement | null>(null);
|
|
808
814
|
const usageMenuWrapRef = useRef<HTMLDivElement | null>(null);
|
|
809
815
|
const permissionMenuWrapRef = useRef<HTMLDivElement | null>(null);
|
|
816
|
+
const contextMenuWrapRef = useRef<HTMLDivElement | null>(null);
|
|
810
817
|
const modelBtnRef = useRef<HTMLButtonElement>(null);
|
|
811
818
|
const modelPopoverRef = useRef<HTMLDivElement>(null);
|
|
812
819
|
const reasoningBtnRef = useRef<HTMLButtonElement>(null);
|
|
@@ -815,7 +822,13 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
815
822
|
const usagePopoverRef = useRef<HTMLDivElement>(null);
|
|
816
823
|
const permissionBtnRef = useRef<HTMLButtonElement>(null);
|
|
817
824
|
const permissionPopoverRef = useRef<HTMLDivElement>(null);
|
|
825
|
+
const contextBtnRef = useRef<HTMLButtonElement>(null);
|
|
826
|
+
const contextPopoverRef = useRef<HTMLDivElement>(null);
|
|
818
827
|
const plainPyRunSessionKeyRef = useRef<string>('');
|
|
828
|
+
const cellAttachmentAnchorRef = useRef<HTMLDivElement | null>(null);
|
|
829
|
+
const cellAttachmentPopoverRef = useRef<HTMLDivElement>(null);
|
|
830
|
+
const cellAttachmentPopoverCloseTimerRef = useRef<number | null>(null);
|
|
831
|
+
const contextPopoverCloseTimerRef = useRef<number | null>(null);
|
|
819
832
|
const selectionPopoverAnchorRef = useRef<HTMLElement | null>(null);
|
|
820
833
|
const selectionPopoverRef = useRef<HTMLDivElement>(null);
|
|
821
834
|
const notebookLabelRef = useRef<HTMLSpanElement | null>(null);
|
|
@@ -991,13 +1004,6 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
991
1004
|
persistIncludeActiveCellOutput(includeActiveCellOutput);
|
|
992
1005
|
}, [includeActiveCellOutput]);
|
|
993
1006
|
|
|
994
|
-
useEffect(() => {
|
|
995
|
-
// Reset one-time exclusion when the base setting is turned off.
|
|
996
|
-
if (!includeActiveCell && excludeCellAttachmentForNextSend) {
|
|
997
|
-
setExcludeCellAttachmentForNextSend(false);
|
|
998
|
-
}
|
|
999
|
-
}, [includeActiveCell, excludeCellAttachmentForNextSend]);
|
|
1000
|
-
|
|
1001
1007
|
useEffect(() => {
|
|
1002
1008
|
persistCommandPath(commandPath);
|
|
1003
1009
|
}, [commandPath]);
|
|
@@ -1026,7 +1032,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1026
1032
|
}, [notifyOnDoneMinSeconds]);
|
|
1027
1033
|
|
|
1028
1034
|
useEffect(() => {
|
|
1029
|
-
if (!modelMenuOpen && !reasoningMenuOpen && !usagePopoverOpen && !permissionMenuOpen) {
|
|
1035
|
+
if (!modelMenuOpen && !reasoningMenuOpen && !usagePopoverOpen && !permissionMenuOpen && !contextPopoverOpen) {
|
|
1030
1036
|
return;
|
|
1031
1037
|
}
|
|
1032
1038
|
|
|
@@ -1040,19 +1046,23 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1040
1046
|
const inReasoning = reasoningMenuWrapRef.current?.contains(target) ?? false;
|
|
1041
1047
|
const inUsage = usageMenuWrapRef.current?.contains(target) ?? false;
|
|
1042
1048
|
const inPermission = permissionMenuWrapRef.current?.contains(target) ?? false;
|
|
1049
|
+
const inContext = contextMenuWrapRef.current?.contains(target) ?? false;
|
|
1043
1050
|
const inModelPopover = modelPopoverRef.current?.contains(target) ?? false;
|
|
1044
1051
|
const inReasoningPopover = reasoningPopoverRef.current?.contains(target) ?? false;
|
|
1045
1052
|
const inUsagePopover = usagePopoverRef.current?.contains(target) ?? false;
|
|
1046
1053
|
const inPermissionPopover = permissionPopoverRef.current?.contains(target) ?? false;
|
|
1054
|
+
const inContextPopover = contextPopoverRef.current?.contains(target) ?? false;
|
|
1047
1055
|
if (
|
|
1048
1056
|
inModel ||
|
|
1049
1057
|
inReasoning ||
|
|
1050
1058
|
inUsage ||
|
|
1051
1059
|
inPermission ||
|
|
1060
|
+
inContext ||
|
|
1052
1061
|
inModelPopover ||
|
|
1053
1062
|
inReasoningPopover ||
|
|
1054
1063
|
inUsagePopover ||
|
|
1055
|
-
inPermissionPopover
|
|
1064
|
+
inPermissionPopover ||
|
|
1065
|
+
inContextPopover
|
|
1056
1066
|
) {
|
|
1057
1067
|
return;
|
|
1058
1068
|
}
|
|
@@ -1061,6 +1071,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1061
1071
|
setReasoningMenuOpen(false);
|
|
1062
1072
|
setUsagePopoverOpen(false);
|
|
1063
1073
|
setPermissionMenuOpen(false);
|
|
1074
|
+
setContextPopoverOpen(false);
|
|
1064
1075
|
};
|
|
1065
1076
|
|
|
1066
1077
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
@@ -1072,6 +1083,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1072
1083
|
setReasoningMenuOpen(false);
|
|
1073
1084
|
setUsagePopoverOpen(false);
|
|
1074
1085
|
setPermissionMenuOpen(false);
|
|
1086
|
+
setContextPopoverOpen(false);
|
|
1075
1087
|
};
|
|
1076
1088
|
|
|
1077
1089
|
window.addEventListener('pointerdown', onPointerDown, true);
|
|
@@ -1080,7 +1092,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1080
1092
|
window.removeEventListener('pointerdown', onPointerDown, true);
|
|
1081
1093
|
window.removeEventListener('keydown', onKeyDown);
|
|
1082
1094
|
};
|
|
1083
|
-
}, [modelMenuOpen, reasoningMenuOpen, usagePopoverOpen, permissionMenuOpen]);
|
|
1095
|
+
}, [modelMenuOpen, reasoningMenuOpen, usagePopoverOpen, permissionMenuOpen, contextPopoverOpen]);
|
|
1084
1096
|
|
|
1085
1097
|
useEffect(() => {
|
|
1086
1098
|
if (!selectionPopover) {
|
|
@@ -1125,6 +1137,74 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1125
1137
|
selectionPopoverAnchorRef.current = null;
|
|
1126
1138
|
}
|
|
1127
1139
|
|
|
1140
|
+
function clearCellAttachmentPopoverCloseTimer(): void {
|
|
1141
|
+
if (cellAttachmentPopoverCloseTimerRef.current !== null) {
|
|
1142
|
+
window.clearTimeout(cellAttachmentPopoverCloseTimerRef.current);
|
|
1143
|
+
cellAttachmentPopoverCloseTimerRef.current = null;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function clearContextPopoverCloseTimer(): void {
|
|
1148
|
+
if (contextPopoverCloseTimerRef.current !== null) {
|
|
1149
|
+
window.clearTimeout(contextPopoverCloseTimerRef.current);
|
|
1150
|
+
contextPopoverCloseTimerRef.current = null;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function openCellAttachmentPopover(): void {
|
|
1155
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
1156
|
+
if (!showCellAttachmentBadge) {
|
|
1157
|
+
setCellAttachmentPopoverOpen(false);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
setCellAttachmentPopoverOpen(true);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function scheduleCloseCellAttachmentPopover(): void {
|
|
1164
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
1165
|
+
cellAttachmentPopoverCloseTimerRef.current = window.setTimeout(() => {
|
|
1166
|
+
setCellAttachmentPopoverOpen(false);
|
|
1167
|
+
cellAttachmentPopoverCloseTimerRef.current = null;
|
|
1168
|
+
}, 90);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function openContextPopover(): void {
|
|
1172
|
+
clearContextPopoverCloseTimer();
|
|
1173
|
+
if (!hasContextUsageSnapshot) {
|
|
1174
|
+
setContextPopoverOpen(false);
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
setContextPopoverOpen(true);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function scheduleCloseContextPopover(): void {
|
|
1181
|
+
clearContextPopoverCloseTimer();
|
|
1182
|
+
contextPopoverCloseTimerRef.current = window.setTimeout(() => {
|
|
1183
|
+
setContextPopoverOpen(false);
|
|
1184
|
+
contextPopoverCloseTimerRef.current = null;
|
|
1185
|
+
}, 90);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function handleContextPopoverBlur(event: React.FocusEvent<HTMLDivElement>): void {
|
|
1189
|
+
const nextFocused = event.relatedTarget as Node | null;
|
|
1190
|
+
const inAnchor = nextFocused ? contextMenuWrapRef.current?.contains(nextFocused) ?? false : false;
|
|
1191
|
+
const inPopover = nextFocused ? contextPopoverRef.current?.contains(nextFocused) ?? false : false;
|
|
1192
|
+
if (inAnchor || inPopover) {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
scheduleCloseContextPopover();
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function handleCellAttachmentBlur(event: React.FocusEvent<HTMLDivElement>): void {
|
|
1199
|
+
const nextFocused = event.relatedTarget as Node | null;
|
|
1200
|
+
const inAnchor = nextFocused ? cellAttachmentAnchorRef.current?.contains(nextFocused) ?? false : false;
|
|
1201
|
+
const inPopover = nextFocused ? cellAttachmentPopoverRef.current?.contains(nextFocused) ?? false : false;
|
|
1202
|
+
if (inAnchor || inPopover) {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
scheduleCloseCellAttachmentPopover();
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1128
1208
|
function toggleSelectionPopover(
|
|
1129
1209
|
messageId: string,
|
|
1130
1210
|
preview: MessageContextPreview,
|
|
@@ -1352,6 +1432,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1352
1432
|
setModelMenuOpen(false);
|
|
1353
1433
|
setReasoningMenuOpen(false);
|
|
1354
1434
|
setPermissionMenuOpen(false);
|
|
1435
|
+
setContextPopoverOpen(false);
|
|
1355
1436
|
}
|
|
1356
1437
|
|
|
1357
1438
|
async function updateNotifyOnDone(enabled: boolean): Promise<void> {
|
|
@@ -1929,6 +2010,8 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1929
2010
|
if (activeWidget) {
|
|
1930
2011
|
activeDocumentWidgetRef.current = activeWidget;
|
|
1931
2012
|
}
|
|
2013
|
+
const nextIsNotebookEditor = isNotebookWidget(activeWidget);
|
|
2014
|
+
setCurrentDocumentIsNotebookEditor(prev => (prev === nextIsNotebookEditor ? prev : nextIsNotebookEditor));
|
|
1932
2015
|
const path = getSupportedDocumentPath(activeWidget);
|
|
1933
2016
|
const sessionKey = resolveSessionKey(path);
|
|
1934
2017
|
const previousKey = currentNotebookSessionKeyRef.current;
|
|
@@ -1980,26 +2063,6 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
1980
2063
|
};
|
|
1981
2064
|
}, [props.app, props.notebooks]);
|
|
1982
2065
|
|
|
1983
|
-
useEffect(() => {
|
|
1984
|
-
const onActiveCellChanged = (_tracker: INotebookTracker, _cell: unknown) => {
|
|
1985
|
-
if (!includeActiveCell || !excludeCellAttachmentForNextSend) {
|
|
1986
|
-
return;
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
const currentNotebookWidget = props.notebooks.currentWidget as DocumentWidgetLike | null;
|
|
1990
|
-
const currentNotebookWidgetPath = getSupportedDocumentPath(currentNotebookWidget);
|
|
1991
|
-
if (!currentNotebookWidgetPath || currentNotebookWidgetPath !== currentNotebookPathRef.current) {
|
|
1992
|
-
return;
|
|
1993
|
-
}
|
|
1994
|
-
setExcludeCellAttachmentForNextSend(false);
|
|
1995
|
-
};
|
|
1996
|
-
|
|
1997
|
-
props.notebooks.activeCellChanged.connect(onActiveCellChanged);
|
|
1998
|
-
return () => {
|
|
1999
|
-
props.notebooks.activeCellChanged.disconnect(onActiveCellChanged);
|
|
2000
|
-
};
|
|
2001
|
-
}, [props.notebooks, includeActiveCell, excludeCellAttachmentForNextSend]);
|
|
2002
|
-
|
|
2003
2066
|
useEffect(() => {
|
|
2004
2067
|
const onStorage = (event: StorageEvent) => {
|
|
2005
2068
|
if (event.key !== STORAGE_KEY_SESSION_THREADS_EVENT || !event.newValue) {
|
|
@@ -2533,8 +2596,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2533
2596
|
const selectedTextForContext = selectedContext?.text || '';
|
|
2534
2597
|
let includeSelectionKey = false;
|
|
2535
2598
|
let selection = '';
|
|
2536
|
-
|
|
2537
|
-
if (includeActiveCellForNextSend) {
|
|
2599
|
+
if (includeActiveCell) {
|
|
2538
2600
|
if (notebookMode === 'plain_py') {
|
|
2539
2601
|
const selectedText =
|
|
2540
2602
|
selectedTextForContext || getSelectedTextFromActiveCell(activeWidget) || getSelectedTextFromFileEditor(activeWidget);
|
|
@@ -2549,12 +2611,15 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2549
2611
|
}
|
|
2550
2612
|
}
|
|
2551
2613
|
const includeCellOutputKey =
|
|
2552
|
-
|
|
2614
|
+
includeActiveCell &&
|
|
2615
|
+
includeActiveCellOutput &&
|
|
2616
|
+
(notebookMode === 'ipynb' || notebookMode === 'jupytext_py');
|
|
2553
2617
|
const cellOutputRaw = includeCellOutputKey ? getActiveCellOutput(activeWidget) : '';
|
|
2554
2618
|
const attachmentLimit = limitActiveCellAttachmentPayload(
|
|
2555
2619
|
includeSelectionKey ? selection : '',
|
|
2556
2620
|
includeCellOutputKey ? cellOutputRaw : '',
|
|
2557
|
-
|
|
2621
|
+
MAX_ACTIVE_CELL_SELECTION_CHARS,
|
|
2622
|
+
MAX_ACTIVE_CELL_OUTPUT_CHARS
|
|
2558
2623
|
);
|
|
2559
2624
|
const selectionForAttachment = includeSelectionKey ? attachmentLimit.selection : '';
|
|
2560
2625
|
const cellOutputForAttachment = includeCellOutputKey ? attachmentLimit.cellOutput : '';
|
|
@@ -2562,15 +2627,15 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2562
2627
|
const includeCellOutputKeyAfterLimit = Boolean(cellOutputForAttachment);
|
|
2563
2628
|
const messageSelectionPreview =
|
|
2564
2629
|
includeSelectionKeyAfterLimit
|
|
2565
|
-
?
|
|
2630
|
+
? toMessageSelectionPreview(selectedContext, activeWidget, notebookMode, selectionForAttachment)
|
|
2566
2631
|
: undefined;
|
|
2567
2632
|
const messageCellOutputPreview =
|
|
2568
2633
|
includeCellOutputKeyAfterLimit
|
|
2569
2634
|
? toCellOutputPreview(selectedContext, activeWidget, notebookMode, cellOutputForAttachment)
|
|
2570
2635
|
: undefined;
|
|
2571
|
-
const shouldDeduplicateSelection =
|
|
2636
|
+
const shouldDeduplicateSelection = includeActiveCell && includeSelectionKeyAfterLimit;
|
|
2572
2637
|
const shouldDeduplicateCellOutput =
|
|
2573
|
-
|
|
2638
|
+
includeActiveCell && includeCellOutputKeyAfterLimit;
|
|
2574
2639
|
const activeCellAttachmentDedupKey = makeActiveCellAttachmentDedupKey(sessionKey, session.threadId);
|
|
2575
2640
|
const previousActiveCellSignatures =
|
|
2576
2641
|
lastActiveCellAttachmentSignatureRef.current.get(activeCellAttachmentDedupKey);
|
|
@@ -2602,6 +2667,12 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2602
2667
|
);
|
|
2603
2668
|
const includeSelectionKeyForSend = includeSelectionKeyAfterLimit && !hasDuplicateSelectionAttachment;
|
|
2604
2669
|
const includeCellOutputKeyForSend = includeCellOutputKeyAfterLimit && !hasDuplicateCellOutputAttachment;
|
|
2670
|
+
const sentAttachmentTruncation = resolveSentAttachmentTruncation({
|
|
2671
|
+
includeSelection: includeSelectionKeyForSend,
|
|
2672
|
+
includeCellOutput: includeCellOutputKeyForSend,
|
|
2673
|
+
selectionTruncated: attachmentLimit.selectionTruncated,
|
|
2674
|
+
cellOutputTruncated: attachmentLimit.cellOutputTruncated
|
|
2675
|
+
});
|
|
2605
2676
|
const messageSelectionPreviewForSend = hasDuplicateSelectionAttachment ? undefined : messageSelectionPreview;
|
|
2606
2677
|
const messageCellOutputPreviewForSend = hasDuplicateCellOutputAttachment ? undefined : messageCellOutputPreview;
|
|
2607
2678
|
const messageContextPreview: MessageContextPreview | undefined =
|
|
@@ -2661,8 +2732,8 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2661
2732
|
sandbox: sandboxForSend,
|
|
2662
2733
|
...(includeSelectionKeyForSend ? { selection: selectionForAttachment } : {}),
|
|
2663
2734
|
...(includeCellOutputKeyForSend ? { cellOutput: cellOutputForAttachment } : {}),
|
|
2664
|
-
...(
|
|
2665
|
-
...(
|
|
2735
|
+
...(sentAttachmentTruncation.selectionTruncated ? { selectionTruncated: true } : {}),
|
|
2736
|
+
...(sentAttachmentTruncation.cellOutputTruncated ? { cellOutputTruncated: true } : {}),
|
|
2666
2737
|
...(images ? { images } : {}),
|
|
2667
2738
|
...(messageSelectionPreviewForSend ? { uiSelectionPreview: messageSelectionPreviewForSend } : {}),
|
|
2668
2739
|
...(messageCellOutputPreviewForSend ? { uiCellOutputPreview: messageCellOutputPreviewForSend } : {})
|
|
@@ -2693,9 +2764,10 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2693
2764
|
const imageCount = images ? images.length : 0;
|
|
2694
2765
|
const showReadOnlyWarning = sandboxForSend === 'read-only';
|
|
2695
2766
|
const attachmentTruncationNotice = buildAttachmentTruncationNotice(
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2767
|
+
sentAttachmentTruncation.selectionTruncated,
|
|
2768
|
+
sentAttachmentTruncation.cellOutputTruncated,
|
|
2769
|
+
MAX_ACTIVE_CELL_SELECTION_CHARS,
|
|
2770
|
+
MAX_ACTIVE_CELL_OUTPUT_CHARS
|
|
2699
2771
|
);
|
|
2700
2772
|
if (notebookMode === 'plain_py' || notebookMode === 'jupytext_py') {
|
|
2701
2773
|
plainPyRunSessionKeyRef.current = sessionKey;
|
|
@@ -2801,15 +2873,40 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2801
2873
|
const displayPath = currentNotebookPath
|
|
2802
2874
|
? currentNotebookPath.split('/').pop() || 'Untitled'
|
|
2803
2875
|
: 'No notebook';
|
|
2804
|
-
const includeActiveCellForNextSend = includeActiveCell && !excludeCellAttachmentForNextSend;
|
|
2805
2876
|
const composerNotebookMode = currentSession?.notebookMode ?? inferNotebookModeFromPath(currentNotebookPath);
|
|
2806
|
-
const
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
currentNotebookPath
|
|
2812
|
-
currentSession?.pairedOk
|
|
2877
|
+
const cellAttachmentState = resolveCellAttachmentState({
|
|
2878
|
+
includeActiveCell,
|
|
2879
|
+
includeActiveCellOutput,
|
|
2880
|
+
notebookMode: composerNotebookMode,
|
|
2881
|
+
isNotebookEditor: currentDocumentIsNotebookEditor,
|
|
2882
|
+
currentNotebookPath,
|
|
2883
|
+
pairedOk: currentSession?.pairedOk
|
|
2884
|
+
});
|
|
2885
|
+
const includeCellOutputForNextSend = cellAttachmentState.outputEnabled;
|
|
2886
|
+
const showCellAttachmentBadge = cellAttachmentState.showBadge;
|
|
2887
|
+
const cellAttachmentContentEnabled = cellAttachmentState.contentEnabled;
|
|
2888
|
+
const cellAttachmentOutputEnabled = cellAttachmentState.outputEnabled;
|
|
2889
|
+
|
|
2890
|
+
useEffect(() => {
|
|
2891
|
+
if (showCellAttachmentBadge) {
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
setCellAttachmentPopoverOpen(false);
|
|
2895
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
2896
|
+
}, [showCellAttachmentBadge]);
|
|
2897
|
+
|
|
2898
|
+
useEffect(() => {
|
|
2899
|
+
return () => {
|
|
2900
|
+
clearCellAttachmentPopoverCloseTimer();
|
|
2901
|
+
};
|
|
2902
|
+
}, []);
|
|
2903
|
+
|
|
2904
|
+
useEffect(() => {
|
|
2905
|
+
return () => {
|
|
2906
|
+
clearContextPopoverCloseTimer();
|
|
2907
|
+
};
|
|
2908
|
+
}, []);
|
|
2909
|
+
|
|
2813
2910
|
const trimmedInput = input.trim();
|
|
2814
2911
|
const canSend =
|
|
2815
2912
|
status !== 'disconnected' &&
|
|
@@ -2903,6 +3000,14 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
2903
3000
|
: 'Unknown';
|
|
2904
3001
|
const hasContextUsageSnapshot = rateLimits?.contextWindow != null;
|
|
2905
3002
|
|
|
3003
|
+
useEffect(() => {
|
|
3004
|
+
if (hasContextUsageSnapshot) {
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
setContextPopoverOpen(false);
|
|
3008
|
+
clearContextPopoverCloseTimer();
|
|
3009
|
+
}, [hasContextUsageSnapshot]);
|
|
3010
|
+
|
|
2906
3011
|
useLayoutEffect(() => {
|
|
2907
3012
|
const target = notebookLabelRef.current;
|
|
2908
3013
|
if (!target) {
|
|
@@ -3059,6 +3164,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3059
3164
|
setReasoningMenuOpen(false);
|
|
3060
3165
|
setUsagePopoverOpen(false);
|
|
3061
3166
|
setPermissionMenuOpen(false);
|
|
3167
|
+
setContextPopoverOpen(false);
|
|
3062
3168
|
}}
|
|
3063
3169
|
className={`jp-CodexHeaderBtn jp-CodexHeaderBtn-icon${settingsOpen ? ' is-active' : ''}`}
|
|
3064
3170
|
aria-label="Settings"
|
|
@@ -3068,7 +3174,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3068
3174
|
<GearIcon width={16} height={16} />
|
|
3069
3175
|
</button>
|
|
3070
3176
|
</div>
|
|
3071
|
-
|
|
3177
|
+
</div>
|
|
3072
3178
|
|
|
3073
3179
|
{currentSession?.pairedOk === false && (
|
|
3074
3180
|
<div className="jp-CodexPairingNotice" role="status" aria-live="polite">
|
|
@@ -3281,6 +3387,38 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3281
3387
|
)}
|
|
3282
3388
|
</PortalMenu>
|
|
3283
3389
|
|
|
3390
|
+
<PortalMenu
|
|
3391
|
+
open={cellAttachmentPopoverOpen && showCellAttachmentBadge}
|
|
3392
|
+
anchorRef={cellAttachmentAnchorRef as React.RefObject<HTMLElement>}
|
|
3393
|
+
popoverRef={cellAttachmentPopoverRef}
|
|
3394
|
+
className="jp-CodexCellAttachmentPopoverMenu"
|
|
3395
|
+
ariaLabel="Cell attachment details"
|
|
3396
|
+
role="dialog"
|
|
3397
|
+
align="left"
|
|
3398
|
+
onMouseEnter={openCellAttachmentPopover}
|
|
3399
|
+
onMouseLeave={scheduleCloseCellAttachmentPopover}
|
|
3400
|
+
>
|
|
3401
|
+
<div className="jp-CodexCellAttachmentPopoverCard" role="note" aria-label="Cell attachment details">
|
|
3402
|
+
<div className="jp-CodexCellAttachmentPopoverTitle">Attach On Next Send</div>
|
|
3403
|
+
<div className="jp-CodexCellAttachmentPopoverRow">
|
|
3404
|
+
<span>Current cell content</span>
|
|
3405
|
+
<span
|
|
3406
|
+
className={`jp-CodexCellAttachmentDot ${cellAttachmentContentEnabled ? 'is-on' : 'is-off'}`}
|
|
3407
|
+
aria-label={cellAttachmentContentEnabled ? 'Attached' : 'Not attached'}
|
|
3408
|
+
title={cellAttachmentContentEnabled ? 'Attached' : 'Not attached'}
|
|
3409
|
+
/>
|
|
3410
|
+
</div>
|
|
3411
|
+
<div className="jp-CodexCellAttachmentPopoverRow">
|
|
3412
|
+
<span>Current cell output</span>
|
|
3413
|
+
<span
|
|
3414
|
+
className={`jp-CodexCellAttachmentDot ${cellAttachmentOutputEnabled ? 'is-on' : 'is-off'}`}
|
|
3415
|
+
aria-label={cellAttachmentOutputEnabled ? 'Attached' : 'Not attached'}
|
|
3416
|
+
title={cellAttachmentOutputEnabled ? 'Attached' : 'Not attached'}
|
|
3417
|
+
/>
|
|
3418
|
+
</div>
|
|
3419
|
+
</div>
|
|
3420
|
+
</PortalMenu>
|
|
3421
|
+
|
|
3284
3422
|
<div className="jp-CodexChat-input">
|
|
3285
3423
|
<div className={`jp-CodexJumpBar${isAtBottom ? '' : ' is-visible'}`}>
|
|
3286
3424
|
<button
|
|
@@ -3297,30 +3435,30 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3297
3435
|
</div>
|
|
3298
3436
|
<div className="jp-CodexComposer">
|
|
3299
3437
|
<div
|
|
3300
|
-
className={`jp-CodexComposer-cellAttachmentWrap${showCellAttachmentBadge ? ' is-visible' : ''}`}
|
|
3438
|
+
className={`jp-CodexCellAttachmentWrap jp-CodexComposer-cellAttachmentWrap${showCellAttachmentBadge ? ' is-visible' : ''}`}
|
|
3439
|
+
ref={cellAttachmentAnchorRef}
|
|
3301
3440
|
aria-hidden={!showCellAttachmentBadge}
|
|
3441
|
+
onMouseEnter={openCellAttachmentPopover}
|
|
3442
|
+
onMouseLeave={scheduleCloseCellAttachmentPopover}
|
|
3443
|
+
onFocusCapture={openCellAttachmentPopover}
|
|
3444
|
+
onBlurCapture={handleCellAttachmentBlur}
|
|
3302
3445
|
>
|
|
3303
3446
|
<div
|
|
3304
|
-
className="jp-CodexComposer-cellAttachment"
|
|
3305
3447
|
role="group"
|
|
3306
|
-
aria-label="
|
|
3307
|
-
title={
|
|
3308
|
-
includeCellOutputForNextSend
|
|
3309
|
-
? 'Active cell and output will be attached on next send.'
|
|
3310
|
-
: 'Active cell will be attached on next send.'
|
|
3311
|
-
}
|
|
3448
|
+
aria-label="Active-cell attachment"
|
|
3312
3449
|
>
|
|
3313
|
-
<span className="jp-CodexComposer-cellAttachmentLabel">Cell Attached</span>
|
|
3314
3450
|
<button
|
|
3315
3451
|
type="button"
|
|
3316
|
-
className=
|
|
3317
|
-
onClick={() =>
|
|
3318
|
-
aria-
|
|
3319
|
-
|
|
3452
|
+
className={`jp-CodexComposer-cellAttachment${cellAttachmentContentEnabled ? '' : ' is-off'}`}
|
|
3453
|
+
onClick={() => setIncludeActiveCell(value => !value)}
|
|
3454
|
+
aria-pressed={cellAttachmentContentEnabled}
|
|
3455
|
+
aria-label={cellAttachmentContentEnabled ? 'Disable active-cell attachment' : 'Enable active-cell attachment'}
|
|
3456
|
+
title={cellAttachmentContentEnabled ? 'Disable active-cell attachment' : 'Enable active-cell attachment'}
|
|
3320
3457
|
disabled={!showCellAttachmentBadge}
|
|
3321
3458
|
tabIndex={showCellAttachmentBadge ? 0 : -1}
|
|
3322
3459
|
>
|
|
3323
|
-
<
|
|
3460
|
+
<CellAttachmentIcon active={cellAttachmentContentEnabled} width={15} height={15} />
|
|
3461
|
+
<span className="jp-CodexComposer-cellAttachmentLabel">Cell Attatch</span>
|
|
3324
3462
|
</button>
|
|
3325
3463
|
</div>
|
|
3326
3464
|
</div>
|
|
@@ -3390,6 +3528,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3390
3528
|
setReasoningMenuOpen(false);
|
|
3391
3529
|
setUsagePopoverOpen(false);
|
|
3392
3530
|
setPermissionMenuOpen(false);
|
|
3531
|
+
setContextPopoverOpen(false);
|
|
3393
3532
|
}}
|
|
3394
3533
|
disabled={status === 'running'}
|
|
3395
3534
|
aria-label={`Model: ${selectedModelLabel}`}
|
|
@@ -3446,6 +3585,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3446
3585
|
setModelMenuOpen(false);
|
|
3447
3586
|
setUsagePopoverOpen(false);
|
|
3448
3587
|
setPermissionMenuOpen(false);
|
|
3588
|
+
setContextPopoverOpen(false);
|
|
3449
3589
|
}}
|
|
3450
3590
|
disabled={status === 'running'}
|
|
3451
3591
|
aria-label={`Reasoning: ${selectedReasoningLabel}`}
|
|
@@ -3510,6 +3650,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3510
3650
|
setModelMenuOpen(false);
|
|
3511
3651
|
setReasoningMenuOpen(false);
|
|
3512
3652
|
setUsagePopoverOpen(false);
|
|
3653
|
+
setContextPopoverOpen(false);
|
|
3513
3654
|
}}
|
|
3514
3655
|
disabled={status === 'running'}
|
|
3515
3656
|
aria-label={`Permission: ${selectedSandboxLabel}`}
|
|
@@ -3544,10 +3685,18 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3544
3685
|
))}
|
|
3545
3686
|
</PortalMenu>
|
|
3546
3687
|
{hasContextUsageSnapshot && (
|
|
3547
|
-
<div
|
|
3688
|
+
<div
|
|
3689
|
+
className="jp-CodexContextWrap"
|
|
3690
|
+
ref={contextMenuWrapRef}
|
|
3691
|
+
onMouseEnter={openContextPopover}
|
|
3692
|
+
onMouseLeave={scheduleCloseContextPopover}
|
|
3693
|
+
onFocusCapture={openContextPopover}
|
|
3694
|
+
onBlurCapture={handleContextPopoverBlur}
|
|
3695
|
+
>
|
|
3548
3696
|
<button
|
|
3549
3697
|
type="button"
|
|
3550
3698
|
className={`jp-CodexIconBtn jp-CodexContextBtn${usageIsStale ? ' is-stale' : ''}`}
|
|
3699
|
+
ref={contextBtnRef}
|
|
3551
3700
|
aria-label={
|
|
3552
3701
|
contextUsedTokens == null || contextLeftTokens == null
|
|
3553
3702
|
? 'Context window usage unavailable'
|
|
@@ -3561,7 +3710,17 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3561
3710
|
>
|
|
3562
3711
|
<ContextWindowIcon level={contextLevel} width={20} height={20} />
|
|
3563
3712
|
</button>
|
|
3564
|
-
<
|
|
3713
|
+
<PortalMenu
|
|
3714
|
+
open={contextPopoverOpen}
|
|
3715
|
+
anchorRef={contextBtnRef}
|
|
3716
|
+
popoverRef={contextPopoverRef}
|
|
3717
|
+
className="jp-CodexContextPopover"
|
|
3718
|
+
role="tooltip"
|
|
3719
|
+
ariaLabel="Context window"
|
|
3720
|
+
align="right"
|
|
3721
|
+
onMouseEnter={openContextPopover}
|
|
3722
|
+
onMouseLeave={scheduleCloseContextPopover}
|
|
3723
|
+
>
|
|
3565
3724
|
<div className="jp-CodexContextPopoverTitle">Context window</div>
|
|
3566
3725
|
<div className="jp-CodexContextPopoverRow">
|
|
3567
3726
|
<span>Used</span>
|
|
@@ -3576,7 +3735,7 @@ function CodexChat(props: CodexChatProps): JSX.Element {
|
|
|
3576
3735
|
? 'Window size unavailable'
|
|
3577
3736
|
: `Window: ${contextWindowLabel} tokens (${contextUsedPercentLabel} used)`}
|
|
3578
3737
|
</div>
|
|
3579
|
-
</
|
|
3738
|
+
</PortalMenu>
|
|
3580
3739
|
</div>
|
|
3581
3740
|
)}
|
|
3582
3741
|
</div>
|