@plmbr/notebook-intelligence 5.0.0
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/LICENSE +674 -0
- package/README.md +412 -0
- package/lib/api.d.ts +288 -0
- package/lib/api.js +927 -0
- package/lib/cell-output-bundle.d.ts +25 -0
- package/lib/cell-output-bundle.js +129 -0
- package/lib/cell-output-toolbar.d.ts +26 -0
- package/lib/cell-output-toolbar.js +188 -0
- package/lib/chat-progress-feedback.d.ts +3 -0
- package/lib/chat-progress-feedback.js +27 -0
- package/lib/chat-sidebar.d.ts +92 -0
- package/lib/chat-sidebar.js +3452 -0
- package/lib/command-ids.d.ts +39 -0
- package/lib/command-ids.js +44 -0
- package/lib/components/ask-user-question.d.ts +2 -0
- package/lib/components/ask-user-question.js +85 -0
- package/lib/components/checkbox.d.ts +2 -0
- package/lib/components/checkbox.js +30 -0
- package/lib/components/claude-mcp-panel.d.ts +2 -0
- package/lib/components/claude-mcp-panel.js +275 -0
- package/lib/components/claude-mcp-paste.d.ts +7 -0
- package/lib/components/claude-mcp-paste.js +104 -0
- package/lib/components/claude-session-picker.d.ts +8 -0
- package/lib/components/claude-session-picker.js +127 -0
- package/lib/components/form-dialog.d.ts +25 -0
- package/lib/components/form-dialog.js +35 -0
- package/lib/components/launcher-picker.d.ts +6 -0
- package/lib/components/launcher-picker.js +135 -0
- package/lib/components/mcp-util.d.ts +2 -0
- package/lib/components/mcp-util.js +37 -0
- package/lib/components/notebook-generation-popover.d.ts +7 -0
- package/lib/components/notebook-generation-popover.js +60 -0
- package/lib/components/pill.d.ts +2 -0
- package/lib/components/pill.js +5 -0
- package/lib/components/plugins-panel.d.ts +3 -0
- package/lib/components/plugins-panel.js +466 -0
- package/lib/components/settings-panel.d.ts +11 -0
- package/lib/components/settings-panel.js +742 -0
- package/lib/components/skills-panel.d.ts +2 -0
- package/lib/components/skills-panel.js +1264 -0
- package/lib/handler.d.ts +8 -0
- package/lib/handler.js +36 -0
- package/lib/icons.d.ts +45 -0
- package/lib/icons.js +54 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +2079 -0
- package/lib/markdown-renderer.d.ts +10 -0
- package/lib/markdown-renderer.js +64 -0
- package/lib/notebook-generation-toolbar.d.ts +16 -0
- package/lib/notebook-generation-toolbar.js +197 -0
- package/lib/notebook-generation.d.ts +8 -0
- package/lib/notebook-generation.js +12 -0
- package/lib/open-file-refresh-watcher-env.d.ts +4 -0
- package/lib/open-file-refresh-watcher-env.js +33 -0
- package/lib/open-file-refresh-watcher.d.ts +97 -0
- package/lib/open-file-refresh-watcher.js +190 -0
- package/lib/shell-utils.d.ts +6 -0
- package/lib/shell-utils.js +9 -0
- package/lib/task-target-notebook.d.ts +2 -0
- package/lib/task-target-notebook.js +28 -0
- package/lib/terminal-drag-format.d.ts +9 -0
- package/lib/terminal-drag-format.js +23 -0
- package/lib/terminal-drag.d.ts +12 -0
- package/lib/terminal-drag.js +268 -0
- package/lib/tokens.d.ts +149 -0
- package/lib/tokens.js +88 -0
- package/lib/tour/tour-anchors.d.ts +18 -0
- package/lib/tour/tour-anchors.js +18 -0
- package/lib/tour/tour-config.d.ts +66 -0
- package/lib/tour/tour-config.js +99 -0
- package/lib/tour/tour-defaults.json +58 -0
- package/lib/tour/tour-events.d.ts +19 -0
- package/lib/tour/tour-events.js +30 -0
- package/lib/tour/tour-overlay.d.ts +6 -0
- package/lib/tour/tour-overlay.js +350 -0
- package/lib/tour/tour-state.d.ts +20 -0
- package/lib/tour/tour-state.js +81 -0
- package/lib/tour/tour-steps.d.ts +33 -0
- package/lib/tour/tour-steps.js +216 -0
- package/lib/utils.d.ts +53 -0
- package/lib/utils.js +385 -0
- package/package.json +258 -0
- package/schema/plugin.json +42 -0
- package/src/api.ts +1424 -0
- package/src/cell-output-bundle.ts +176 -0
- package/src/cell-output-toolbar.ts +232 -0
- package/src/chat-progress-feedback.ts +35 -0
- package/src/chat-sidebar.tsx +5147 -0
- package/src/command-ids.ts +67 -0
- package/src/components/ask-user-question.tsx +151 -0
- package/src/components/checkbox.tsx +62 -0
- package/src/components/claude-mcp-panel.tsx +543 -0
- package/src/components/claude-mcp-paste.ts +132 -0
- package/src/components/claude-session-picker.tsx +214 -0
- package/src/components/form-dialog.tsx +75 -0
- package/src/components/launcher-picker.tsx +237 -0
- package/src/components/mcp-util.ts +53 -0
- package/src/components/notebook-generation-popover.tsx +127 -0
- package/src/components/pill.tsx +15 -0
- package/src/components/plugins-panel.tsx +774 -0
- package/src/components/settings-panel.tsx +1631 -0
- package/src/components/skills-panel.tsx +2084 -0
- package/src/handler.ts +51 -0
- package/src/icons.ts +71 -0
- package/src/index.ts +2583 -0
- package/src/markdown-renderer.tsx +153 -0
- package/src/notebook-generation-toolbar.tsx +281 -0
- package/src/notebook-generation.ts +23 -0
- package/src/open-file-refresh-watcher-env.ts +52 -0
- package/src/open-file-refresh-watcher.ts +260 -0
- package/src/shell-utils.ts +10 -0
- package/src/svg.d.ts +4 -0
- package/src/task-target-notebook.ts +37 -0
- package/src/terminal-drag-format.ts +29 -0
- package/src/terminal-drag.ts +382 -0
- package/src/tokens.ts +171 -0
- package/src/tour/tour-anchors.ts +21 -0
- package/src/tour/tour-config.ts +160 -0
- package/src/tour/tour-events.ts +34 -0
- package/src/tour/tour-overlay.tsx +474 -0
- package/src/tour/tour-state.ts +87 -0
- package/src/tour/tour-steps.ts +281 -0
- package/src/utils.ts +455 -0
- package/style/base.css +3238 -0
- package/style/icons/cell-toolbar-bug.svg +5 -0
- package/style/icons/cell-toolbar-chat.svg +5 -0
- package/style/icons/cell-toolbar-sparkle.svg +5 -0
- package/style/icons/claude.svg +1 -0
- package/style/icons/copilot-warning.svg +1 -0
- package/style/icons/copilot.svg +1 -0
- package/style/icons/copy.svg +1 -0
- package/style/icons/openai.svg +1 -0
- package/style/icons/opencode.svg +1 -0
- package/style/icons/sparkles-warning.svg +5 -0
- package/style/icons/sparkles.svg +1 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { VscCheck, VscClose, VscCopy, VscHistory } from '../icons';
|
|
4
|
+
import { NBIAPI } from '../api';
|
|
5
|
+
import { buildResumeCommand, writeTextToClipboard } from '../utils';
|
|
6
|
+
function formatTimestamp(epochSeconds) {
|
|
7
|
+
if (!epochSeconds) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
const date = new Date(epochSeconds * 1000);
|
|
11
|
+
if (Number.isNaN(date.getTime())) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
return date.toLocaleString();
|
|
15
|
+
}
|
|
16
|
+
const COPY_LABELS = {
|
|
17
|
+
idle: 'Copy resume command',
|
|
18
|
+
copied: 'Resume command copied',
|
|
19
|
+
failed: 'Failed to copy resume command'
|
|
20
|
+
};
|
|
21
|
+
export function ClaudeSessionPicker(props) {
|
|
22
|
+
const [sessions, setSessions] = useState([]);
|
|
23
|
+
const [currentCwd, setCurrentCwd] = useState('');
|
|
24
|
+
const [loading, setLoading] = useState(true);
|
|
25
|
+
const [resuming, setResuming] = useState(false);
|
|
26
|
+
const [error, setError] = useState('');
|
|
27
|
+
const [copyFeedback, setCopyFeedback] = useState(null);
|
|
28
|
+
const copyTimerRef = useRef(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
return () => {
|
|
31
|
+
if (copyTimerRef.current !== null) {
|
|
32
|
+
clearTimeout(copyTimerRef.current);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
var _a;
|
|
38
|
+
let cancelled = false;
|
|
39
|
+
const fetch = (_a = props.fetchSessions) !== null && _a !== void 0 ? _a : (() => NBIAPI.listClaudeSessions('cwd'));
|
|
40
|
+
fetch()
|
|
41
|
+
.then(result => {
|
|
42
|
+
if (cancelled) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setSessions(result.sessions);
|
|
46
|
+
setCurrentCwd(result.currentCwd);
|
|
47
|
+
setLoading(false);
|
|
48
|
+
})
|
|
49
|
+
.catch(reason => {
|
|
50
|
+
var _a, _b;
|
|
51
|
+
if (cancelled) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
setError(String((_b = (_a = reason === null || reason === void 0 ? void 0 : reason.message) !== null && _a !== void 0 ? _a : reason) !== null && _b !== void 0 ? _b : 'Unknown error'));
|
|
55
|
+
setLoading(false);
|
|
56
|
+
});
|
|
57
|
+
return () => {
|
|
58
|
+
cancelled = true;
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
const handleCopyResumeCommand = async (event, session) => {
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
const ok = await writeTextToClipboard(buildResumeCommand(currentCwd, session.session_id));
|
|
65
|
+
setCopyFeedback({
|
|
66
|
+
sessionId: session.session_id,
|
|
67
|
+
status: ok ? 'copied' : 'failed'
|
|
68
|
+
});
|
|
69
|
+
if (copyTimerRef.current !== null) {
|
|
70
|
+
clearTimeout(copyTimerRef.current);
|
|
71
|
+
}
|
|
72
|
+
copyTimerRef.current = setTimeout(() => {
|
|
73
|
+
setCopyFeedback(null);
|
|
74
|
+
copyTimerRef.current = null;
|
|
75
|
+
}, 1500);
|
|
76
|
+
};
|
|
77
|
+
const handleResume = async (session) => {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
if (resuming) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
setResuming(true);
|
|
83
|
+
// When a custom fetchSessions is provided the caller owns the resume
|
|
84
|
+
// lifecycle (e.g. the launcher tile opens a terminal directly), so skip
|
|
85
|
+
// the NBI sidebar API call which requires Claude Code mode to be active.
|
|
86
|
+
if (props.fetchSessions) {
|
|
87
|
+
props.onResume(session);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await NBIAPI.resumeClaudeSession(session.session_id);
|
|
92
|
+
props.onResume(session);
|
|
93
|
+
}
|
|
94
|
+
catch (reason) {
|
|
95
|
+
setError(String((_b = (_a = reason === null || reason === void 0 ? void 0 : reason.message) !== null && _a !== void 0 ? _a : reason) !== null && _b !== void 0 ? _b : 'Unknown error'));
|
|
96
|
+
setResuming(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
return (React.createElement("div", { className: "workspace-file-popover claude-session-picker", tabIndex: 1, autoFocus: true, onKeyDown: (event) => {
|
|
100
|
+
if (event.key === 'Escape') {
|
|
101
|
+
event.stopPropagation();
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
props.onClose();
|
|
104
|
+
}
|
|
105
|
+
} },
|
|
106
|
+
React.createElement("div", { className: "mode-tools-popover-header" },
|
|
107
|
+
React.createElement("div", { className: "mode-tools-popover-header-icon" },
|
|
108
|
+
React.createElement(VscHistory, null)),
|
|
109
|
+
React.createElement("div", { className: "mode-tools-popover-title" }, "Resume Claude session"),
|
|
110
|
+
React.createElement("div", { style: { flexGrow: 1 } }),
|
|
111
|
+
React.createElement("div", { className: "mode-tools-popover-button mode-tools-popover-close-button", title: "Close", onClick: props.onClose },
|
|
112
|
+
React.createElement(VscClose, null))),
|
|
113
|
+
React.createElement("div", { className: "workspace-file-popover-body" },
|
|
114
|
+
error && (React.createElement("div", { className: "workspace-file-popover-status error" }, error)),
|
|
115
|
+
loading ? (React.createElement("div", { className: "workspace-file-popover-status" }, "Loading sessions\u2026")) : sessions.length === 0 ? (React.createElement("div", { className: "workspace-file-popover-status" }, "No previous Claude sessions found for this working directory.")) : (React.createElement("ul", { className: "claude-session-picker-list" }, sessions.map(session => {
|
|
116
|
+
const feedback = copyFeedback && copyFeedback.sessionId === session.session_id
|
|
117
|
+
? copyFeedback.status
|
|
118
|
+
: null;
|
|
119
|
+
const buttonLabel = COPY_LABELS[feedback !== null && feedback !== void 0 ? feedback : 'idle'];
|
|
120
|
+
return (React.createElement("li", { key: session.session_id, className: `claude-session-picker-item${resuming ? ' busy' : ''}`, onClick: () => handleResume(session) },
|
|
121
|
+
session.preview && (React.createElement("div", { className: "claude-session-picker-item-preview" }, session.preview)),
|
|
122
|
+
React.createElement("div", { className: "claude-session-picker-item-meta" },
|
|
123
|
+
React.createElement("span", null, formatTimestamp(session.modified_at)),
|
|
124
|
+
React.createElement("span", { className: "claude-session-picker-item-id", title: session.session_id }, session.session_id.slice(0, 8)),
|
|
125
|
+
React.createElement("button", { type: "button", className: `claude-session-picker-item-copy${feedback ? ` ${feedback}` : ''}`, title: buttonLabel, "aria-label": buttonLabel, onClick: event => handleCopyResumeCommand(event, session) }, feedback === 'copied' ? React.createElement(VscCheck, null) : React.createElement(VscCopy, null)))));
|
|
126
|
+
}))))));
|
|
127
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Modal-form shell shared by the simple add/install dialogs across the
|
|
4
|
+
* Claude-MCP and Plugins panels. Owns:
|
|
5
|
+
* * backdrop + card layout (cancel-on-backdrop-click)
|
|
6
|
+
* * title / body / actions slots
|
|
7
|
+
* * Escape-to-cancel keyboard handler
|
|
8
|
+
* * inline error rendering when ``error`` is non-null
|
|
9
|
+
* * primary button label transitions for the submitting state
|
|
10
|
+
*
|
|
11
|
+
* Multi-step flows (e.g. the GitHub-import preview-then-install dialog in
|
|
12
|
+
* ``skills-panel.tsx``) keep their own shell since fitting them here
|
|
13
|
+
* would bloat the abstraction.
|
|
14
|
+
*/
|
|
15
|
+
export declare function FormDialog(props: {
|
|
16
|
+
title: string;
|
|
17
|
+
submitLabel: string;
|
|
18
|
+
submitInProgressLabel?: string;
|
|
19
|
+
canSubmit: boolean;
|
|
20
|
+
submitting: boolean;
|
|
21
|
+
error?: string | null;
|
|
22
|
+
onCancel: () => void;
|
|
23
|
+
onSubmit: () => void;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
import React from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Modal-form shell shared by the simple add/install dialogs across the
|
|
5
|
+
* Claude-MCP and Plugins panels. Owns:
|
|
6
|
+
* * backdrop + card layout (cancel-on-backdrop-click)
|
|
7
|
+
* * title / body / actions slots
|
|
8
|
+
* * Escape-to-cancel keyboard handler
|
|
9
|
+
* * inline error rendering when ``error`` is non-null
|
|
10
|
+
* * primary button label transitions for the submitting state
|
|
11
|
+
*
|
|
12
|
+
* Multi-step flows (e.g. the GitHub-import preview-then-install dialog in
|
|
13
|
+
* ``skills-panel.tsx``) keep their own shell since fitting them here
|
|
14
|
+
* would bloat the abstraction.
|
|
15
|
+
*/
|
|
16
|
+
export function FormDialog(props) {
|
|
17
|
+
var _a;
|
|
18
|
+
return (React.createElement("div", { className: "nbi-modal-backdrop", onClick: props.onCancel },
|
|
19
|
+
React.createElement("div", { className: "nbi-modal-card", role: "dialog", "aria-modal": "true", onClick: e => e.stopPropagation(), onKeyDown: e => {
|
|
20
|
+
if (e.key === 'Escape' && !props.submitting) {
|
|
21
|
+
props.onCancel();
|
|
22
|
+
}
|
|
23
|
+
}, tabIndex: -1 },
|
|
24
|
+
React.createElement("div", { className: "nbi-modal-title" }, props.title),
|
|
25
|
+
React.createElement("div", { className: "nbi-modal-body" },
|
|
26
|
+
props.children,
|
|
27
|
+
props.error && (React.createElement("div", { className: "nbi-skills-error", role: "alert" }, props.error))),
|
|
28
|
+
React.createElement("div", { className: "nbi-modal-actions" },
|
|
29
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: props.onCancel, disabled: props.submitting },
|
|
30
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Cancel")),
|
|
31
|
+
React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: props.onSubmit, disabled: !props.canSubmit },
|
|
32
|
+
React.createElement("div", { className: "jp-Dialog-buttonLabel" }, props.submitting
|
|
33
|
+
? ((_a = props.submitInProgressLabel) !== null && _a !== void 0 ? _a : `${props.submitLabel}…`)
|
|
34
|
+
: props.submitLabel))))));
|
|
35
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { IClaudeSessionInfo } from '../api';
|
|
3
|
+
export interface ILauncherPickerProps {
|
|
4
|
+
onSessionSelected: (session: IClaudeSessionInfo) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function LauncherPicker({ onSessionSelected }: ILauncherPickerProps): JSX.Element;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { NBIAPI } from '../api';
|
|
4
|
+
// Pick a short, glanceable label for a session's project. The basename
|
|
5
|
+
// of the cwd is usually the project's actual name; the full path stays
|
|
6
|
+
// available via the row's `title` attribute on hover.
|
|
7
|
+
function projectLabel(cwd) {
|
|
8
|
+
if (!cwd) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
const trimmed = cwd.replace(/\/+$/, '');
|
|
12
|
+
const idx = trimmed.lastIndexOf('/');
|
|
13
|
+
return idx >= 0 ? trimmed.slice(idx + 1) : trimmed;
|
|
14
|
+
}
|
|
15
|
+
export function LauncherPicker({ onSessionSelected }) {
|
|
16
|
+
const [sessions, setSessions] = useState([]);
|
|
17
|
+
const [loading, setLoading] = useState(true);
|
|
18
|
+
const [error, setError] = useState('');
|
|
19
|
+
const [filter, setFilter] = useState('');
|
|
20
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
21
|
+
const containerRef = useRef(null);
|
|
22
|
+
const listRef = useRef(null);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
NBIAPI.listClaudeSessions('all')
|
|
25
|
+
.then(result => {
|
|
26
|
+
setSessions(result.sessions);
|
|
27
|
+
setLoading(false);
|
|
28
|
+
})
|
|
29
|
+
.catch((reason) => {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
setError(String((_b = (_a = reason === null || reason === void 0 ? void 0 : reason.message) !== null && _a !== void 0 ? _a : reason) !== null && _b !== void 0 ? _b : 'Unknown error'));
|
|
32
|
+
setLoading(false);
|
|
33
|
+
});
|
|
34
|
+
}, []);
|
|
35
|
+
const needle = filter.toLowerCase();
|
|
36
|
+
const filtered = filter
|
|
37
|
+
? sessions.filter(s => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
return ((_a = s.preview) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(needle)) ||
|
|
40
|
+
((_b = s.cwd) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes(needle));
|
|
41
|
+
})
|
|
42
|
+
: sessions;
|
|
43
|
+
// A held-over index against a refetched session set could silently
|
|
44
|
+
// point at a different session, so reset on any sessions change —
|
|
45
|
+
// not just length, which would miss equal-length-but-different sets.
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setHighlightedIndex(-1);
|
|
48
|
+
}, [filter, sessions]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (highlightedIndex < 0 || !listRef.current) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const row = listRef.current.children[highlightedIndex];
|
|
54
|
+
row === null || row === void 0 ? void 0 : row.scrollIntoView({ block: 'nearest' });
|
|
55
|
+
}, [highlightedIndex]);
|
|
56
|
+
// Refs so the document-level keydown listener installed below can read
|
|
57
|
+
// the latest values without re-attaching on every render.
|
|
58
|
+
const filteredRef = useRef(filtered);
|
|
59
|
+
const highlightedIndexRef = useRef(highlightedIndex);
|
|
60
|
+
const onSessionSelectedRef = useRef(onSessionSelected);
|
|
61
|
+
filteredRef.current = filtered;
|
|
62
|
+
highlightedIndexRef.current = highlightedIndex;
|
|
63
|
+
onSessionSelectedRef.current = onSessionSelected;
|
|
64
|
+
// Lumino's Dialog catches Enter at capture phase on the dialog node and
|
|
65
|
+
// triggers its default OK button (the "New Session" button), so a React
|
|
66
|
+
// bubble-phase handler never sees Enter inside the picker. Attach a
|
|
67
|
+
// document-capture listener here so we beat the dialog and activate the
|
|
68
|
+
// highlighted row instead — falling through to the dialog's New Session
|
|
69
|
+
// path only when there's nothing selectable to land on.
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const handler = (e) => {
|
|
72
|
+
if (e.key !== 'Enter') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const node = containerRef.current;
|
|
76
|
+
if (!node || !node.contains(e.target)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const list = filteredRef.current;
|
|
80
|
+
if (list.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
e.stopImmediatePropagation();
|
|
85
|
+
const idx = highlightedIndexRef.current;
|
|
86
|
+
onSessionSelectedRef.current(list[idx >= 0 ? idx : 0]);
|
|
87
|
+
};
|
|
88
|
+
document.addEventListener('keydown', handler, { capture: true });
|
|
89
|
+
return () => document.removeEventListener('keydown', handler, { capture: true });
|
|
90
|
+
}, []);
|
|
91
|
+
const handleKeyDown = (e) => {
|
|
92
|
+
if (filtered.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// From "no row highlighted" (-1), ArrowDown jumps to the first row
|
|
96
|
+
// and ArrowUp jumps to the last — each direction lands at its
|
|
97
|
+
// nearest end so the user always reaches a valid row in one press.
|
|
98
|
+
if (e.key === 'ArrowDown') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
setHighlightedIndex(i => (i < 0 || i >= filtered.length - 1 ? 0 : i + 1));
|
|
101
|
+
}
|
|
102
|
+
else if (e.key === 'ArrowUp') {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
setHighlightedIndex(i => (i <= 0 ? filtered.length - 1 : i - 1));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
if (loading) {
|
|
108
|
+
return (React.createElement("div", { className: "nbi-claude-code-picker-status" }, "Loading sessions\u2026"));
|
|
109
|
+
}
|
|
110
|
+
if (error) {
|
|
111
|
+
return (React.createElement("div", { className: "nbi-claude-code-picker-status nbi-claude-code-picker-error" }, error));
|
|
112
|
+
}
|
|
113
|
+
const activeRowId = highlightedIndex >= 0
|
|
114
|
+
? `nbi-claude-session-row-${filtered[highlightedIndex].session_id}`
|
|
115
|
+
: undefined;
|
|
116
|
+
return (React.createElement("div", { className: "nbi-claude-code-picker-body", ref: containerRef, onKeyDown: handleKeyDown },
|
|
117
|
+
React.createElement("input", { className: "nbi-claude-code-picker-search", type: "text", placeholder: "Filter sessions...", value: filter, onChange: (e) => setFilter(e.target.value), autoFocus: true, role: "combobox", "aria-expanded": filtered.length > 0, "aria-controls": "nbi-claude-session-listbox", "aria-activedescendant": activeRowId }),
|
|
118
|
+
React.createElement("div", { className: "nbi-claude-code-picker-list", id: "nbi-claude-session-listbox", role: "listbox", ref: listRef }, filtered.length === 0 ? (React.createElement("div", { className: "nbi-claude-code-picker-empty" }, filter
|
|
119
|
+
? 'No sessions match your filter.'
|
|
120
|
+
: 'No previous sessions found.')) : (filtered.map((session, index) => {
|
|
121
|
+
const isHighlighted = index === highlightedIndex;
|
|
122
|
+
return (React.createElement("div", { key: session.session_id, id: `nbi-claude-session-row-${session.session_id}`, role: "option", className: 'nbi-claude-code-picker-session' +
|
|
123
|
+
(isHighlighted ? ' highlighted' : ''), tabIndex: 0, "aria-selected": isHighlighted, onClick: () => onSessionSelected(session), onFocus: () => setHighlightedIndex(index) },
|
|
124
|
+
React.createElement("div", { className: "nbi-claude-code-picker-session-top" },
|
|
125
|
+
React.createElement("span", { className: "nbi-claude-code-picker-session-id" }, session.session_id.slice(0, 8)),
|
|
126
|
+
(() => {
|
|
127
|
+
var _a;
|
|
128
|
+
const full = (_a = session.cwd) !== null && _a !== void 0 ? _a : '';
|
|
129
|
+
const label = projectLabel(full);
|
|
130
|
+
const fullDiffersFromLabel = full && full !== label;
|
|
131
|
+
return (React.createElement("span", { className: "nbi-claude-code-picker-session-project", title: full, "aria-label": fullDiffersFromLabel ? full : undefined }, label));
|
|
132
|
+
})()),
|
|
133
|
+
session.preview && (React.createElement("div", { className: "nbi-claude-code-picker-msg" }, session.preview))));
|
|
134
|
+
})))));
|
|
135
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
export function mcpServerSettingsToEnabledState(mcpServers, mcpServerSettings) {
|
|
3
|
+
const mcpServerEnabledState = new Map();
|
|
4
|
+
for (const server of mcpServers) {
|
|
5
|
+
const mcpServerToolEnabledState = mcpServerSettingsToServerToolEnabledState(mcpServers, mcpServerSettings, server.id);
|
|
6
|
+
if (mcpServerToolEnabledState) {
|
|
7
|
+
mcpServerEnabledState.set(server.id, mcpServerToolEnabledState);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return mcpServerEnabledState;
|
|
11
|
+
}
|
|
12
|
+
export function mcpServerSettingsToServerToolEnabledState(mcpServers, mcpServerSettings, serverId) {
|
|
13
|
+
var _a;
|
|
14
|
+
const server = mcpServers.find((server) => server.id === serverId);
|
|
15
|
+
let mcpServerToolEnabledState = null;
|
|
16
|
+
if (!server) {
|
|
17
|
+
return mcpServerToolEnabledState;
|
|
18
|
+
}
|
|
19
|
+
if (mcpServerSettings[server.id]) {
|
|
20
|
+
const serverSettings = mcpServerSettings[server.id];
|
|
21
|
+
if (!serverSettings.disabled) {
|
|
22
|
+
mcpServerToolEnabledState = new Set();
|
|
23
|
+
for (const tool of server.tools) {
|
|
24
|
+
if (!((_a = serverSettings.disabled_tools) === null || _a === void 0 ? void 0 : _a.includes(tool.name))) {
|
|
25
|
+
mcpServerToolEnabledState.add(tool.name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
mcpServerToolEnabledState = new Set();
|
|
32
|
+
for (const tool of server.tools) {
|
|
33
|
+
mcpServerToolEnabledState.add(tool.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return mcpServerToolEnabledState;
|
|
37
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export interface INotebookGenerationPopoverProps {
|
|
3
|
+
initialShowInChat?: boolean;
|
|
4
|
+
onSubmit: (prompt: string, showInChat: boolean) => void;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function NotebookGenerationPopover(props: INotebookGenerationPopoverProps): JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { VscClose, VscSend, VscSparkle } from '../icons';
|
|
4
|
+
import { CheckBoxItem } from './checkbox';
|
|
5
|
+
export function NotebookGenerationPopover(props) {
|
|
6
|
+
var _a;
|
|
7
|
+
const [prompt, setPrompt] = useState('');
|
|
8
|
+
const [showInChat, setShowInChat] = useState((_a = props.initialShowInChat) !== null && _a !== void 0 ? _a : true);
|
|
9
|
+
const textareaRef = useRef(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// Defer to the next frame so the focus call lands after Lumino's
|
|
12
|
+
// attach lifecycle and JupyterLab's focus tracker have settled —
|
|
13
|
+
// otherwise the active notebook can win the focus race and the
|
|
14
|
+
// textarea stays unfocused (issue #231).
|
|
15
|
+
const handle = window.requestAnimationFrame(() => {
|
|
16
|
+
var _a;
|
|
17
|
+
(_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
18
|
+
});
|
|
19
|
+
return () => window.cancelAnimationFrame(handle);
|
|
20
|
+
}, []);
|
|
21
|
+
const trimmed = prompt.trim();
|
|
22
|
+
const canSubmit = trimmed.length > 0;
|
|
23
|
+
const handleSubmit = () => {
|
|
24
|
+
if (!canSubmit) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
props.onSubmit(trimmed, showInChat);
|
|
28
|
+
};
|
|
29
|
+
const handleKeyDown = (event) => {
|
|
30
|
+
if (event.key === 'Escape') {
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
props.onClose();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const handleTextareaKeyDown = (event) => {
|
|
38
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
handleSubmit();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return (React.createElement("div", { className: "notebook-generation-popover", tabIndex: -1, onKeyDown: handleKeyDown },
|
|
44
|
+
React.createElement("div", { className: "notebook-generation-popover-header" },
|
|
45
|
+
React.createElement("div", { className: "notebook-generation-popover-header-icon" },
|
|
46
|
+
React.createElement(VscSparkle, null)),
|
|
47
|
+
React.createElement("div", { className: "notebook-generation-popover-title" }, "Update active notebook"),
|
|
48
|
+
React.createElement("div", { style: { flexGrow: 1 } }),
|
|
49
|
+
React.createElement("button", { type: "button", className: "notebook-generation-popover-close-button", "aria-label": "Close notebook generation popover", title: "Close", onClick: props.onClose },
|
|
50
|
+
React.createElement(VscClose, { "aria-hidden": "true" }))),
|
|
51
|
+
React.createElement("div", { className: "notebook-generation-popover-body" },
|
|
52
|
+
React.createElement("textarea", { ref: textareaRef, className: "notebook-generation-popover-input", rows: 4, placeholder: "Describe how to update the active notebook...", value: prompt, onChange: (event) => setPrompt(event.target.value), onKeyDown: handleTextareaKeyDown }),
|
|
53
|
+
React.createElement(CheckBoxItem, { checked: showInChat, label: "Show in chat", tooltip: 'When enabled, the prompt opens the Notebook Intelligence ' +
|
|
54
|
+
'chat sidebar. Disable to keep the chat hidden and only ' +
|
|
55
|
+
'show progress on the notebook toolbar.', onClick: () => setShowInChat(value => !value) }),
|
|
56
|
+
React.createElement("div", { className: "notebook-generation-popover-actions" },
|
|
57
|
+
React.createElement("button", { type: "button", className: "notebook-generation-popover-submit", disabled: !canSubmit, onClick: handleSubmit, title: "Generate (Enter)" },
|
|
58
|
+
React.createElement(VscSend, null),
|
|
59
|
+
React.createElement("span", null, "Generate"))))));
|
|
60
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export function PillItem(props) {
|
|
4
|
+
return (React.createElement("div", { className: `pill-item ${props.checked ? 'checked' : ''}`, title: props.title, onClick: event => props.onClick(event) }, props.label));
|
|
5
|
+
}
|