@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.
Files changed (137) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +412 -0
  3. package/lib/api.d.ts +288 -0
  4. package/lib/api.js +927 -0
  5. package/lib/cell-output-bundle.d.ts +25 -0
  6. package/lib/cell-output-bundle.js +129 -0
  7. package/lib/cell-output-toolbar.d.ts +26 -0
  8. package/lib/cell-output-toolbar.js +188 -0
  9. package/lib/chat-progress-feedback.d.ts +3 -0
  10. package/lib/chat-progress-feedback.js +27 -0
  11. package/lib/chat-sidebar.d.ts +92 -0
  12. package/lib/chat-sidebar.js +3452 -0
  13. package/lib/command-ids.d.ts +39 -0
  14. package/lib/command-ids.js +44 -0
  15. package/lib/components/ask-user-question.d.ts +2 -0
  16. package/lib/components/ask-user-question.js +85 -0
  17. package/lib/components/checkbox.d.ts +2 -0
  18. package/lib/components/checkbox.js +30 -0
  19. package/lib/components/claude-mcp-panel.d.ts +2 -0
  20. package/lib/components/claude-mcp-panel.js +275 -0
  21. package/lib/components/claude-mcp-paste.d.ts +7 -0
  22. package/lib/components/claude-mcp-paste.js +104 -0
  23. package/lib/components/claude-session-picker.d.ts +8 -0
  24. package/lib/components/claude-session-picker.js +127 -0
  25. package/lib/components/form-dialog.d.ts +25 -0
  26. package/lib/components/form-dialog.js +35 -0
  27. package/lib/components/launcher-picker.d.ts +6 -0
  28. package/lib/components/launcher-picker.js +135 -0
  29. package/lib/components/mcp-util.d.ts +2 -0
  30. package/lib/components/mcp-util.js +37 -0
  31. package/lib/components/notebook-generation-popover.d.ts +7 -0
  32. package/lib/components/notebook-generation-popover.js +60 -0
  33. package/lib/components/pill.d.ts +2 -0
  34. package/lib/components/pill.js +5 -0
  35. package/lib/components/plugins-panel.d.ts +3 -0
  36. package/lib/components/plugins-panel.js +466 -0
  37. package/lib/components/settings-panel.d.ts +11 -0
  38. package/lib/components/settings-panel.js +742 -0
  39. package/lib/components/skills-panel.d.ts +2 -0
  40. package/lib/components/skills-panel.js +1264 -0
  41. package/lib/handler.d.ts +8 -0
  42. package/lib/handler.js +36 -0
  43. package/lib/icons.d.ts +45 -0
  44. package/lib/icons.js +54 -0
  45. package/lib/index.d.ts +8 -0
  46. package/lib/index.js +2079 -0
  47. package/lib/markdown-renderer.d.ts +10 -0
  48. package/lib/markdown-renderer.js +64 -0
  49. package/lib/notebook-generation-toolbar.d.ts +16 -0
  50. package/lib/notebook-generation-toolbar.js +197 -0
  51. package/lib/notebook-generation.d.ts +8 -0
  52. package/lib/notebook-generation.js +12 -0
  53. package/lib/open-file-refresh-watcher-env.d.ts +4 -0
  54. package/lib/open-file-refresh-watcher-env.js +33 -0
  55. package/lib/open-file-refresh-watcher.d.ts +97 -0
  56. package/lib/open-file-refresh-watcher.js +190 -0
  57. package/lib/shell-utils.d.ts +6 -0
  58. package/lib/shell-utils.js +9 -0
  59. package/lib/task-target-notebook.d.ts +2 -0
  60. package/lib/task-target-notebook.js +28 -0
  61. package/lib/terminal-drag-format.d.ts +9 -0
  62. package/lib/terminal-drag-format.js +23 -0
  63. package/lib/terminal-drag.d.ts +12 -0
  64. package/lib/terminal-drag.js +268 -0
  65. package/lib/tokens.d.ts +149 -0
  66. package/lib/tokens.js +88 -0
  67. package/lib/tour/tour-anchors.d.ts +18 -0
  68. package/lib/tour/tour-anchors.js +18 -0
  69. package/lib/tour/tour-config.d.ts +66 -0
  70. package/lib/tour/tour-config.js +99 -0
  71. package/lib/tour/tour-defaults.json +58 -0
  72. package/lib/tour/tour-events.d.ts +19 -0
  73. package/lib/tour/tour-events.js +30 -0
  74. package/lib/tour/tour-overlay.d.ts +6 -0
  75. package/lib/tour/tour-overlay.js +350 -0
  76. package/lib/tour/tour-state.d.ts +20 -0
  77. package/lib/tour/tour-state.js +81 -0
  78. package/lib/tour/tour-steps.d.ts +33 -0
  79. package/lib/tour/tour-steps.js +216 -0
  80. package/lib/utils.d.ts +53 -0
  81. package/lib/utils.js +385 -0
  82. package/package.json +258 -0
  83. package/schema/plugin.json +42 -0
  84. package/src/api.ts +1424 -0
  85. package/src/cell-output-bundle.ts +176 -0
  86. package/src/cell-output-toolbar.ts +232 -0
  87. package/src/chat-progress-feedback.ts +35 -0
  88. package/src/chat-sidebar.tsx +5147 -0
  89. package/src/command-ids.ts +67 -0
  90. package/src/components/ask-user-question.tsx +151 -0
  91. package/src/components/checkbox.tsx +62 -0
  92. package/src/components/claude-mcp-panel.tsx +543 -0
  93. package/src/components/claude-mcp-paste.ts +132 -0
  94. package/src/components/claude-session-picker.tsx +214 -0
  95. package/src/components/form-dialog.tsx +75 -0
  96. package/src/components/launcher-picker.tsx +237 -0
  97. package/src/components/mcp-util.ts +53 -0
  98. package/src/components/notebook-generation-popover.tsx +127 -0
  99. package/src/components/pill.tsx +15 -0
  100. package/src/components/plugins-panel.tsx +774 -0
  101. package/src/components/settings-panel.tsx +1631 -0
  102. package/src/components/skills-panel.tsx +2084 -0
  103. package/src/handler.ts +51 -0
  104. package/src/icons.ts +71 -0
  105. package/src/index.ts +2583 -0
  106. package/src/markdown-renderer.tsx +153 -0
  107. package/src/notebook-generation-toolbar.tsx +281 -0
  108. package/src/notebook-generation.ts +23 -0
  109. package/src/open-file-refresh-watcher-env.ts +52 -0
  110. package/src/open-file-refresh-watcher.ts +260 -0
  111. package/src/shell-utils.ts +10 -0
  112. package/src/svg.d.ts +4 -0
  113. package/src/task-target-notebook.ts +37 -0
  114. package/src/terminal-drag-format.ts +29 -0
  115. package/src/terminal-drag.ts +382 -0
  116. package/src/tokens.ts +171 -0
  117. package/src/tour/tour-anchors.ts +21 -0
  118. package/src/tour/tour-config.ts +160 -0
  119. package/src/tour/tour-events.ts +34 -0
  120. package/src/tour/tour-overlay.tsx +474 -0
  121. package/src/tour/tour-state.ts +87 -0
  122. package/src/tour/tour-steps.ts +281 -0
  123. package/src/utils.ts +455 -0
  124. package/style/base.css +3238 -0
  125. package/style/icons/cell-toolbar-bug.svg +5 -0
  126. package/style/icons/cell-toolbar-chat.svg +5 -0
  127. package/style/icons/cell-toolbar-sparkle.svg +5 -0
  128. package/style/icons/claude.svg +1 -0
  129. package/style/icons/copilot-warning.svg +1 -0
  130. package/style/icons/copilot.svg +1 -0
  131. package/style/icons/copy.svg +1 -0
  132. package/style/icons/openai.svg +1 -0
  133. package/style/icons/opencode.svg +1 -0
  134. package/style/icons/sparkles-warning.svg +5 -0
  135. package/style/icons/sparkles.svg +1 -0
  136. package/style/index.css +1 -0
  137. package/style/index.js +1 -0
@@ -0,0 +1,25 @@
1
+ import { CodeCell } from '@jupyterlab/cells';
2
+ import { IOutputContextItem } from './tokens';
3
+ export declare const MAX_TOKENS_PER_OUTPUT = 4000;
4
+ export declare const MAX_TOKENS_PER_TURN = 8000;
5
+ export type IMimeBundle = IOutputContextItem['mimeBundles'][number];
6
+ export type IOutputContextBundle = IOutputContextItem;
7
+ export interface IBundleOptions {
8
+ maxTokensPerOutput?: number;
9
+ maxTokensPerTurn?: number;
10
+ supportsVision?: boolean;
11
+ }
12
+ interface ICellLike {
13
+ outputArea: {
14
+ model: {
15
+ toJSON: () => any[];
16
+ };
17
+ };
18
+ model?: {
19
+ sharedModel?: {
20
+ getSource: () => string;
21
+ };
22
+ };
23
+ }
24
+ export declare function cellOutputAsContextBundle(cell: CodeCell | ICellLike, opts?: IBundleOptions): IOutputContextBundle;
25
+ export {};
@@ -0,0 +1,129 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+ import { formatJupyterError, joinMultilineString, truncateToTokenCount } from './utils';
3
+ export const MAX_TOKENS_PER_OUTPUT = 4000;
4
+ export const MAX_TOKENS_PER_TURN = 8000;
5
+ // Above this raw base64 length (~150 KiB decoded), we drop the image and
6
+ // emit a placeholder rather than blow up the context window.
7
+ const MAX_IMAGE_BASE64_BYTES = 200000;
8
+ export function cellOutputAsContextBundle(cell, opts = {}) {
9
+ var _a, _b, _c, _d, _e, _f, _g;
10
+ const maxOut = (_a = opts.maxTokensPerOutput) !== null && _a !== void 0 ? _a : MAX_TOKENS_PER_OUTPUT;
11
+ const maxTurn = (_b = opts.maxTokensPerTurn) !== null && _b !== void 0 ? _b : MAX_TOKENS_PER_TURN;
12
+ const supportsVision = (_c = opts.supportsVision) !== null && _c !== void 0 ? _c : false;
13
+ const cellLike = cell;
14
+ const cellSource = (_f = (_e = (_d = cellLike.model) === null || _d === void 0 ? void 0 : _d.sharedModel) === null || _e === void 0 ? void 0 : _e.getSource()) !== null && _f !== void 0 ? _f : '';
15
+ const outputs = cellLike.outputArea.model.toJSON();
16
+ const mimeBundles = [];
17
+ let truncated = false;
18
+ let isError = false;
19
+ let totalTokens = 0;
20
+ const remaining = () => Math.max(0, maxTurn - totalTokens);
21
+ const pushBundle = (mimeType, data) => {
22
+ const perOutCap = Math.min(maxOut, remaining());
23
+ const { text, size, truncated: didTruncate } = truncateToTokenCount(data, perOutCap);
24
+ if (didTruncate) {
25
+ truncated = true;
26
+ }
27
+ if (size === 0 && text.length === 0) {
28
+ return;
29
+ }
30
+ mimeBundles.push({ mimeType, data: text, sizeTokens: size });
31
+ totalTokens += size;
32
+ };
33
+ for (const output of outputs) {
34
+ if (remaining() === 0) {
35
+ truncated = true;
36
+ break;
37
+ }
38
+ if (output.output_type === 'error') {
39
+ isError = true;
40
+ pushBundle('application/vnd.jupyter.error', formatJupyterError(output));
41
+ continue;
42
+ }
43
+ if (output.output_type === 'stream') {
44
+ pushBundle('text/plain', joinMultilineString(output.text));
45
+ continue;
46
+ }
47
+ // execute_result and display_data
48
+ const data = ((_g = output.data) !== null && _g !== void 0 ? _g : {});
49
+ // DataFrames render as both text/html and text/plain; prefer the plain
50
+ // form — it's the formatted ASCII table, cheaper, and an LLM reads it
51
+ // just fine. text/html alone is stripped to plain text below.
52
+ // Use ``in`` rather than truthy check: an empty array ``[]`` is legal
53
+ // (if rare) nbformat for ``text/plain`` but falsy in JS, which would
54
+ // otherwise let the html branch fire over an explicit-but-empty plain
55
+ // entry.
56
+ if ('text/plain' in data) {
57
+ pushBundle('text/plain', joinMultilineString(data['text/plain']));
58
+ }
59
+ else if ('text/html' in data) {
60
+ pushBundle('text/html', stripHtml(joinMultilineString(data['text/html'])));
61
+ }
62
+ if (data['application/vnd.plotly.v1+json']) {
63
+ pushBundle('application/vnd.plotly.v1+json', summarizePlotly(data['application/vnd.plotly.v1+json']));
64
+ }
65
+ else if (data['application/json']) {
66
+ pushBundle('application/json', JSON.stringify(data['application/json'], null, 2));
67
+ }
68
+ for (const imgMime of ['image/png', 'image/jpeg']) {
69
+ if (!data[imgMime]) {
70
+ continue;
71
+ }
72
+ const raw = joinMultilineString(data[imgMime]);
73
+ if (!supportsVision) {
74
+ pushBundle(imgMime, '<image omitted: model lacks vision support>');
75
+ }
76
+ else if (raw.length > MAX_IMAGE_BASE64_BYTES) {
77
+ pushBundle(imgMime, '<image omitted: too large for inline attachment>');
78
+ truncated = true;
79
+ }
80
+ else if (!isValidBase64(raw)) {
81
+ pushBundle(imgMime, '<image omitted: invalid base64 payload>');
82
+ }
83
+ else {
84
+ // Send raw base64; the server constructs the data URL after
85
+ // re-validating, so a forged POST can't inject markdown.
86
+ pushBundle(imgMime, raw);
87
+ }
88
+ }
89
+ }
90
+ return { cellSource, mimeBundles, isError, truncated };
91
+ }
92
+ function isValidBase64(s) {
93
+ // Standard base64 alphabet only. No URL-safe chars; no characters that
94
+ // could break out of a markdown image URL on the server side.
95
+ const compact = s.replace(/\s+/g, '');
96
+ return /^[A-Za-z0-9+/]+=*$/.test(compact);
97
+ }
98
+ function stripHtml(html) {
99
+ // NOTE: not a sanitizer. Output is intended only for LLM context; never
100
+ // route this back into the DOM without a real sanitizer.
101
+ // Replace block-level closers with a newline so structure survives, then
102
+ // strip the remaining tags. Cheap and good enough for LLM consumption.
103
+ return html
104
+ .replace(/<\/(p|div|tr|li|h[1-6]|br)\s*\/?>/gi, '\n')
105
+ .replace(/<br\s*\/?>/gi, '\n')
106
+ .replace(/<[^>]+>/g, '')
107
+ .replace(/\n{3,}/g, '\n\n')
108
+ .trim();
109
+ }
110
+ function summarizePlotly(payload) {
111
+ var _a, _b, _c, _d, _e, _f;
112
+ const lines = [];
113
+ const traces = Array.isArray(payload === null || payload === void 0 ? void 0 : payload.data) ? payload.data : [];
114
+ const types = traces.map((t) => { var _a; return (_a = t === null || t === void 0 ? void 0 : t.type) !== null && _a !== void 0 ? _a : 'trace'; }).join(', ');
115
+ if (types) {
116
+ lines.push(`Plotly figure traces: ${types}`);
117
+ }
118
+ const layout = (_a = payload === null || payload === void 0 ? void 0 : payload.layout) !== null && _a !== void 0 ? _a : {};
119
+ if ((_b = layout.title) === null || _b === void 0 ? void 0 : _b.text) {
120
+ lines.push(`Title: ${layout.title.text}`);
121
+ }
122
+ if ((_d = (_c = layout.xaxis) === null || _c === void 0 ? void 0 : _c.title) === null || _d === void 0 ? void 0 : _d.text) {
123
+ lines.push(`X axis: ${layout.xaxis.title.text}`);
124
+ }
125
+ if ((_f = (_e = layout.yaxis) === null || _e === void 0 ? void 0 : _e.title) === null || _f === void 0 ? void 0 : _f.text) {
126
+ lines.push(`Y axis: ${layout.yaxis.title.text}`);
127
+ }
128
+ return lines.join('\n');
129
+ }
@@ -0,0 +1,26 @@
1
+ import { JupyterFrontEnd } from '@jupyterlab/application';
2
+ import { CommandRegistry } from '@lumino/commands';
3
+ import { IDisposable } from '@lumino/disposable';
4
+ /**
5
+ * Show a hover toolbar over Jupyter cell outputs that surfaces the existing
6
+ * Explain / Ask / Troubleshoot commands as one-click buttons.
7
+ *
8
+ * The toolbar respects the `output_toolbar` feature flag (whole-toolbar
9
+ * gate) and the per-action `explain_error` / `output_followup` flags so a
10
+ * locked-off feature stays locked off here too.
11
+ */
12
+ export declare class CellOutputHoverToolbar implements IDisposable {
13
+ private _app;
14
+ private _commands;
15
+ private _disposed;
16
+ private _activeArea;
17
+ private _onMouseOver;
18
+ private _onMouseLeave;
19
+ constructor(app: JupyterFrontEnd, commands: CommandRegistry);
20
+ get isDisposed(): boolean;
21
+ dispose(): void;
22
+ private _handleMouseOver;
23
+ private _removeActiveToolbar;
24
+ private _locateCell;
25
+ private _buildToolbar;
26
+ }
@@ -0,0 +1,188 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+ import { CodeCell } from '@jupyterlab/cells';
3
+ import { NotebookPanel } from '@jupyterlab/notebook';
4
+ import { LabIcon } from '@jupyterlab/ui-components';
5
+ import { NBIAPI } from './api';
6
+ import { cellOutputHasError } from './utils';
7
+ import sparkleSvgstr from '../style/icons/cell-toolbar-sparkle.svg';
8
+ import chatSvgstr from '../style/icons/cell-toolbar-chat.svg';
9
+ import bugSvgstr from '../style/icons/cell-toolbar-bug.svg';
10
+ const TOOLBAR_CLASS = 'nbi-cell-output-toolbar';
11
+ const BUTTON_CLASS = 'nbi-cell-output-toolbar-button';
12
+ // SVGs live under style/icons/ and are loaded via LabIcon to match the rest
13
+ // of the project's icon plumbing. The source assets are Microsoft's
14
+ // vscode-codicons (sparkle, comment-discussion, bug), CC BY 4.0 — the
15
+ // license header lives inside each .svg file.
16
+ const sparkleIcon = new LabIcon({
17
+ name: 'notebook-intelligence:cell-toolbar-sparkle',
18
+ svgstr: sparkleSvgstr
19
+ });
20
+ const chatIcon = new LabIcon({
21
+ name: 'notebook-intelligence:cell-toolbar-chat',
22
+ svgstr: chatSvgstr
23
+ });
24
+ const bugIcon = new LabIcon({
25
+ name: 'notebook-intelligence:cell-toolbar-bug',
26
+ svgstr: bugSvgstr
27
+ });
28
+ const ACTIONS = [
29
+ {
30
+ id: 'explain',
31
+ label: 'Explain',
32
+ title: "Explain this cell's output",
33
+ icon: sparkleIcon,
34
+ command: 'notebook-intelligence:editor-explain-this-output',
35
+ featureFlag: 'output_followup'
36
+ },
37
+ {
38
+ id: 'ask',
39
+ label: 'Ask',
40
+ title: 'Ask about this output',
41
+ icon: chatIcon,
42
+ command: 'notebook-intelligence:editor-ask-about-this-output',
43
+ featureFlag: 'output_followup'
44
+ },
45
+ {
46
+ id: 'troubleshoot',
47
+ label: 'Troubleshoot',
48
+ title: 'Troubleshoot the error in this cell',
49
+ icon: bugIcon,
50
+ command: 'notebook-intelligence:editor-troubleshoot-this-output',
51
+ featureFlag: 'explain_error',
52
+ requireError: true
53
+ }
54
+ ];
55
+ /**
56
+ * Show a hover toolbar over Jupyter cell outputs that surfaces the existing
57
+ * Explain / Ask / Troubleshoot commands as one-click buttons.
58
+ *
59
+ * The toolbar respects the `output_toolbar` feature flag (whole-toolbar
60
+ * gate) and the per-action `explain_error` / `output_followup` flags so a
61
+ * locked-off feature stays locked off here too.
62
+ */
63
+ export class CellOutputHoverToolbar {
64
+ constructor(app, commands) {
65
+ this._disposed = false;
66
+ this._activeArea = null;
67
+ this._app = app;
68
+ // The Explain / Ask / Troubleshoot commands live on the context-menu's
69
+ // private CommandRegistry, not on `app.commands`, so callers must pass
70
+ // the same registry the menu uses.
71
+ this._commands = commands;
72
+ this._onMouseOver = this._handleMouseOver.bind(this);
73
+ // mouseleave only fires when the cursor exits the area entirely —
74
+ // descendants (including the toolbar itself) don't trigger it.
75
+ this._onMouseLeave = this._removeActiveToolbar.bind(this);
76
+ document.body.addEventListener('mouseover', this._onMouseOver);
77
+ }
78
+ get isDisposed() {
79
+ return this._disposed;
80
+ }
81
+ dispose() {
82
+ if (this._disposed) {
83
+ return;
84
+ }
85
+ this._disposed = true;
86
+ document.body.removeEventListener('mouseover', this._onMouseOver);
87
+ this._removeActiveToolbar();
88
+ }
89
+ _handleMouseOver(event) {
90
+ if (!NBIAPI.config.cellOutputFeatures.output_toolbar.enabled) {
91
+ this._removeActiveToolbar();
92
+ return;
93
+ }
94
+ const target = event.target;
95
+ if (!target) {
96
+ return;
97
+ }
98
+ const area = target.closest('.jp-Cell-outputArea');
99
+ if (!area) {
100
+ return;
101
+ }
102
+ if (area === this._activeArea) {
103
+ return;
104
+ }
105
+ this._removeActiveToolbar();
106
+ const cellEl = area.closest('.jp-Cell');
107
+ if (!cellEl) {
108
+ return;
109
+ }
110
+ const located = this._locateCell(cellEl);
111
+ if (!located) {
112
+ return;
113
+ }
114
+ const toolbar = this._buildToolbar(located.panel, located.cellIndex, located.cell);
115
+ if (!toolbar) {
116
+ return;
117
+ }
118
+ area.appendChild(toolbar);
119
+ this._activeArea = area;
120
+ area.addEventListener('mouseleave', this._onMouseLeave);
121
+ }
122
+ _removeActiveToolbar() {
123
+ if (!this._activeArea) {
124
+ return;
125
+ }
126
+ this._activeArea.removeEventListener('mouseleave', this._onMouseLeave);
127
+ const existing = this._activeArea.querySelector(`.${TOOLBAR_CLASS}`);
128
+ if (existing) {
129
+ existing.remove();
130
+ }
131
+ this._activeArea = null;
132
+ }
133
+ _locateCell(cellEl) {
134
+ const widget = this._app.shell.currentWidget;
135
+ if (!(widget instanceof NotebookPanel)) {
136
+ return null;
137
+ }
138
+ const widgets = widget.content.widgets;
139
+ for (let i = 0; i < widgets.length; i++) {
140
+ const cell = widgets[i];
141
+ if (cell.node === cellEl && cell instanceof CodeCell) {
142
+ return { panel: widget, cell, cellIndex: i };
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+ _buildToolbar(panel, cellIndex, cell) {
148
+ const features = NBIAPI.config.cellOutputFeatures;
149
+ const hasError = cellOutputHasError(cell);
150
+ const visible = ACTIONS.filter(a => {
151
+ if (!features[a.featureFlag].enabled) {
152
+ return false;
153
+ }
154
+ if (a.requireError && !hasError) {
155
+ return false;
156
+ }
157
+ return true;
158
+ });
159
+ if (visible.length === 0) {
160
+ return null;
161
+ }
162
+ const toolbar = document.createElement('div');
163
+ toolbar.className = TOOLBAR_CLASS;
164
+ toolbar.setAttribute('role', 'toolbar');
165
+ toolbar.setAttribute('aria-label', 'Notebook Intelligence cell actions');
166
+ for (const action of visible) {
167
+ const button = document.createElement('button');
168
+ button.type = 'button';
169
+ button.className = BUTTON_CLASS;
170
+ button.title = action.title;
171
+ button.setAttribute('aria-label', action.title);
172
+ action.icon.element({ container: button, tag: 'span' });
173
+ const label = document.createElement('span');
174
+ label.className = `${BUTTON_CLASS}-label`;
175
+ label.textContent = action.label;
176
+ button.appendChild(label);
177
+ button.addEventListener('click', event => {
178
+ event.stopPropagation();
179
+ // The editor commands act on the active cell, so activate the
180
+ // hovered one first.
181
+ panel.content.activeCellIndex = cellIndex;
182
+ void this._commands.execute(action.command);
183
+ });
184
+ toolbar.appendChild(button);
185
+ }
186
+ return toolbar;
187
+ }
188
+ }
@@ -0,0 +1,3 @@
1
+ export declare const HEARTBEAT_STALE_MS = 30000;
2
+ export declare function formatElapsedSeconds(totalSeconds: number): string;
3
+ export declare function isHeartbeatStale(lastHeartbeatAt: number | null, now: number, staleMs?: number): boolean;
@@ -0,0 +1,27 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+ // Small helpers for the chat sidebar's "Generating" progress indicator.
3
+ // Pulled out so each piece (elapsed-time formatting, heartbeat staleness)
4
+ // can be unit-tested without dragging JupyterLab into Jest's import graph.
5
+ // 30 seconds without a heartbeat counts as "stalled" — long enough that a
6
+ // healthy round-trip + retransmission delay can't account for it, short
7
+ // enough that a real hang is signaled while there's still time to act.
8
+ // The Claude heartbeat fires every 20s server-side, so 30s gives roughly
9
+ // 1.5 expected intervals of slack before we change copy.
10
+ export const HEARTBEAT_STALE_MS = 30000;
11
+ export function formatElapsedSeconds(totalSeconds) {
12
+ const safe = Math.max(0, Math.floor(totalSeconds));
13
+ const hours = Math.floor(safe / 3600);
14
+ const minutes = Math.floor((safe % 3600) / 60);
15
+ const seconds = safe % 60;
16
+ const ss = String(seconds).padStart(2, '0');
17
+ if (hours > 0) {
18
+ return `${hours}:${String(minutes).padStart(2, '0')}:${ss}`;
19
+ }
20
+ return `${minutes}:${ss}`;
21
+ }
22
+ export function isHeartbeatStale(lastHeartbeatAt, now, staleMs = HEARTBEAT_STALE_MS) {
23
+ if (lastHeartbeatAt === null) {
24
+ return false;
25
+ }
26
+ return now - lastHeartbeatAt > staleMs;
27
+ }
@@ -0,0 +1,92 @@
1
+ import React from 'react';
2
+ import { ReactWidget } from '@jupyterlab/apputils';
3
+ import { IActiveDocumentInfo, ICellContents, IContextItem, ITelemetryEmitter, IToolSelections } from './tokens';
4
+ import { JupyterFrontEnd } from '@jupyterlab/application';
5
+ export declare enum RunChatCompletionType {
6
+ Chat = 0,
7
+ ExplainThis = 1,
8
+ FixThis = 2,
9
+ GenerateCode = 3,
10
+ NotebookGeneration = 4
11
+ }
12
+ export interface IRunChatCompletionRequest {
13
+ messageId: string;
14
+ chatId: string;
15
+ type: RunChatCompletionType;
16
+ content: string;
17
+ language?: string;
18
+ currentDirectory?: string;
19
+ filename?: string;
20
+ prefix?: string;
21
+ suffix?: string;
22
+ existingCode?: string;
23
+ additionalContext?: IContextItem[];
24
+ chatMode: string;
25
+ toolSelections?: IToolSelections;
26
+ externalRequestId?: string;
27
+ hideInChat?: boolean;
28
+ }
29
+ export interface IChatSidebarOptions {
30
+ getCurrentDirectory: () => string;
31
+ getActiveDocumentInfo: () => IActiveDocumentInfo;
32
+ getActiveSelectionContent: () => string;
33
+ getCurrentCellContents: () => ICellContents;
34
+ openFile: (path: string) => void;
35
+ getApp: () => JupyterFrontEnd;
36
+ getTelemetryEmitter: () => ITelemetryEmitter;
37
+ }
38
+ export declare class ChatSidebar extends ReactWidget {
39
+ constructor(options: IChatSidebarOptions);
40
+ render(): JSX.Element;
41
+ private _options;
42
+ }
43
+ export interface IInlinePromptWidgetOptions {
44
+ prompt: string;
45
+ existingCode: string;
46
+ prefix: string;
47
+ suffix: string;
48
+ language?: string;
49
+ filename?: string;
50
+ onRequestSubmitted: (prompt: string) => void;
51
+ onRequestCancelled: () => void;
52
+ onContentStream: (content: string) => void;
53
+ onContentStreamEnd: (streamError?: string | null) => void;
54
+ onUpdatedCodeChange: (content: string) => void;
55
+ onUpdatedCodeAccepted: () => void;
56
+ telemetryEmitter: ITelemetryEmitter;
57
+ }
58
+ export declare class InlinePromptWidget extends ReactWidget {
59
+ constructor(rect: DOMRect | null, options: IInlinePromptWidgetOptions);
60
+ updatePosition(rect: DOMRect): void;
61
+ _onResponse(response: any): void;
62
+ _onRequestSubmitted(prompt: string): void;
63
+ render(): JSX.Element;
64
+ private _options;
65
+ private _requestTime;
66
+ private _streamError;
67
+ private _floating;
68
+ }
69
+ export declare class GitHubCopilotStatusBarItem extends ReactWidget {
70
+ constructor(options: {
71
+ getApp: () => JupyterFrontEnd;
72
+ });
73
+ render(): JSX.Element;
74
+ private _getApp;
75
+ }
76
+ export declare class GitHubCopilotLoginDialogBody extends ReactWidget {
77
+ constructor(options: {
78
+ onLoggedIn: () => void;
79
+ });
80
+ render(): JSX.Element;
81
+ private _onLoggedIn;
82
+ }
83
+ export declare function InlinePopoverComponent(props: any): React.JSX.Element;
84
+ export declare class FormInputDialogBody extends ReactWidget {
85
+ constructor(options: {
86
+ fields: any;
87
+ onDone: (formData: any) => void;
88
+ });
89
+ render(): JSX.Element;
90
+ private _fields;
91
+ private _onDone;
92
+ }