@notebook-intelligence/notebook-intelligence 3.1.0 → 4.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/lib/tokens.js CHANGED
@@ -18,6 +18,7 @@ export var BackendMessageType;
18
18
  BackendMessageType["RunUICommand"] = "run-ui-command";
19
19
  BackendMessageType["GitHubCopilotLoginStatusChange"] = "github-copilot-login-status-change";
20
20
  BackendMessageType["MCPServerStatusChange"] = "mcp-server-status-change";
21
+ BackendMessageType["ClaudeCodeStatusChange"] = "claude-code-status-change";
21
22
  })(BackendMessageType || (BackendMessageType = {}));
22
23
  export var ResponseStreamDataType;
23
24
  (function (ResponseStreamDataType) {
@@ -30,6 +31,7 @@ export var ResponseStreamDataType;
30
31
  ResponseStreamDataType["Anchor"] = "anchor";
31
32
  ResponseStreamDataType["Progress"] = "progress";
32
33
  ResponseStreamDataType["Confirmation"] = "confirmation";
34
+ ResponseStreamDataType["AskUserQuestion"] = "ask-user-question";
33
35
  })(ResponseStreamDataType || (ResponseStreamDataType = {}));
34
36
  export var ContextType;
35
37
  (function (ContextType) {
@@ -58,6 +60,7 @@ export var BuiltinToolsetType;
58
60
  BuiltinToolsetType["CommandExecute"] = "nbi-command-execute";
59
61
  })(BuiltinToolsetType || (BuiltinToolsetType = {}));
60
62
  export const GITHUB_COPILOT_PROVIDER_ID = 'github-copilot';
63
+ export const CLAUDE_CODE_CHAT_PARTICIPANT_ID = 'claude-code';
61
64
  export var TelemetryEventType;
62
65
  (function (TelemetryEventType) {
63
66
  TelemetryEventType["InlineCompletionRequest"] = "inline-completion-request";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notebook-intelligence/notebook-intelligence",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "description": "AI coding assistant for JupyterLab",
5
5
  "keywords": [
6
6
  "AI",
package/src/api.ts CHANGED
@@ -28,6 +28,17 @@ export interface IDeviceVerificationInfo {
28
28
  userCode: string;
29
29
  }
30
30
 
31
+ export enum ClaudeModelType {
32
+ Default = '',
33
+ ClaudeOpus45 = 'claude-opus-4-5',
34
+ ClaudeHaiku45 = 'claude-haiku-4-5'
35
+ }
36
+
37
+ export enum ClaudeToolType {
38
+ ClaudeCodeTools = 'claude-code:built-in-tools',
39
+ JupyterUITools = 'nbi:built-in-jupyter-ui-tools'
40
+ }
41
+
31
42
  export class NBIConfig {
32
43
  get userHomeDir(): string {
33
44
  return this.capabilities.user_home_dir;
@@ -98,6 +109,14 @@ export class NBIConfig {
98
109
  return this.capabilities.mcp_server_settings;
99
110
  }
100
111
 
112
+ get claudeSettings(): any {
113
+ return this.capabilities.claude_settings;
114
+ }
115
+
116
+ get isInClaudeCodeMode(): boolean {
117
+ return this.claudeSettings.enabled === true;
118
+ }
119
+
101
120
  capabilities: any = {};
102
121
  chatParticipants: IChatParticipant[] = [];
103
122
 
@@ -124,7 +143,10 @@ export class NBIAPI {
124
143
 
125
144
  this._messageReceived.connect((_, msg) => {
126
145
  msg = JSON.parse(msg);
127
- if (msg.type === BackendMessageType.MCPServerStatusChange) {
146
+ if (
147
+ msg.type === BackendMessageType.MCPServerStatusChange ||
148
+ msg.type === BackendMessageType.ClaudeCodeStatusChange
149
+ ) {
128
150
  this.fetchCapabilities();
129
151
  } else if (
130
152
  msg.type === BackendMessageType.GitHubCopilotLoginStatusChange
@@ -178,20 +200,26 @@ export class NBIAPI {
178
200
  }
179
201
 
180
202
  static getChatEnabled() {
181
- return this.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
182
- ? !this.getGHLoginRequired()
183
- : this.config.llmProviders.find(
184
- provider => provider.id === this.config.chatModel.provider
185
- );
203
+ return (
204
+ this.config.isInClaudeCodeMode ||
205
+ (this.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
206
+ ? !this.getGHLoginRequired()
207
+ : this.config.llmProviders.find(
208
+ provider => provider.id === this.config.chatModel.provider
209
+ ))
210
+ );
186
211
  }
187
212
 
188
213
  static getInlineCompletionEnabled() {
189
- return this.config.inlineCompletionModel.provider ===
190
- GITHUB_COPILOT_PROVIDER_ID
191
- ? !this.getGHLoginRequired()
192
- : this.config.llmProviders.find(
193
- provider => provider.id === this.config.inlineCompletionModel.provider
194
- );
214
+ return (
215
+ this.config.isInClaudeCodeMode ||
216
+ (this.config.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
217
+ ? !this.getGHLoginRequired()
218
+ : this.config.llmProviders.find(
219
+ provider =>
220
+ provider.id === this.config.inlineCompletionModel.provider
221
+ ))
222
+ );
195
223
  }
196
224
 
197
225
  static async loginToGitHub() {
@@ -19,6 +19,7 @@ import { NBIAPI, GitHubCopilotLoginStatus } from './api';
19
19
  import {
20
20
  BackendMessageType,
21
21
  BuiltinToolsetType,
22
+ CLAUDE_CODE_CHAT_PARTICIPANT_ID,
22
23
  ContextType,
23
24
  IActiveDocumentInfo,
24
25
  ICellContents,
@@ -54,6 +55,8 @@ import {
54
55
  import { extractLLMGeneratedCode, isDarkTheme } from './utils';
55
56
  import { CheckBoxItem } from './components/checkbox';
56
57
  import { mcpServerSettingsToEnabledState } from './components/mcp-util';
58
+ import claudeSvg from '../style/claude.svg';
59
+ import { AskUserQuestion } from './components/ask-user-question';
57
60
 
58
61
  export enum RunChatCompletionType {
59
62
  Chat,
@@ -449,6 +452,8 @@ function ChatResponse(props: any) {
449
452
  : `Output (${Math.floor(item.reasoningTime)} s)`;
450
453
  };
451
454
 
455
+ const chatParticipantId = msg.participant?.id || 'default';
456
+
452
457
  return (
453
458
  <div
454
459
  className={`chat-message chat-message-${msg.from}`}
@@ -458,7 +463,7 @@ function ChatResponse(props: any) {
458
463
  <div className="chat-message-from">
459
464
  {msg.participant?.iconPath && (
460
465
  <div
461
- className={`chat-message-from-icon ${msg.participant?.id === 'default' ? 'chat-message-from-icon-default' : ''} ${isDarkTheme() ? 'dark' : ''}`}
466
+ className={`chat-message-from-icon chat-message-from-icon-${chatParticipantId} ${isDarkTheme() ? 'dark' : ''}`}
462
467
  >
463
468
  <img src={msg.participant.iconPath} />
464
469
  </div>
@@ -624,6 +629,44 @@ function ChatResponse(props: any) {
624
629
  </button>
625
630
  </div>
626
631
  );
632
+ case ResponseStreamDataType.AskUserQuestion:
633
+ return answeredForms.get(item.id) ===
634
+ 'confirmed' ? null : answeredForms.get(item.id) ===
635
+ 'canceled' ? (
636
+ <div>&#10006; Canceled</div>
637
+ ) : (
638
+ <div
639
+ className="chat-confirmation-form ask-user-question"
640
+ key={`key-${index}`}
641
+ >
642
+ <AskUserQuestion
643
+ userQuestions={item}
644
+ onSubmit={(selectedAnswers: any) => {
645
+ markFormConfirmed(item.id);
646
+ runCommand('notebook-intelligence:chat-user-input', {
647
+ id: item.content.identifier.id,
648
+ data: {
649
+ callback_id: item.content.identifier.callback_id,
650
+ data: {
651
+ confirmed: true,
652
+ selectedAnswers
653
+ }
654
+ }
655
+ });
656
+ }}
657
+ onCancel={() => {
658
+ markFormCanceled(item.id);
659
+ runCommand('notebook-intelligence:chat-user-input', {
660
+ id: item.content.identifier.id,
661
+ data: {
662
+ callback_id: item.content.identifier.callback_id,
663
+ data: { confirmed: false }
664
+ }
665
+ });
666
+ }}
667
+ />
668
+ </div>
669
+ );
627
670
  }
628
671
  return null;
629
672
  })}
@@ -655,7 +698,7 @@ async function submitCompletionRequest(
655
698
  request.content,
656
699
  request.language || 'python',
657
700
  request.currentDirectory || '',
658
- request.filename || 'Untitled.ipynb',
701
+ request.filename || '',
659
702
  request.additionalContext || [],
660
703
  request.chatMode,
661
704
  request.toolSelections || {},
@@ -671,7 +714,7 @@ async function submitCompletionRequest(
671
714
  request.content,
672
715
  request.language || 'python',
673
716
  request.currentDirectory || '',
674
- request.filename || 'Untitled.ipynb',
717
+ request.filename || '',
675
718
  [],
676
719
  'ask',
677
720
  {},
@@ -686,7 +729,7 @@ async function submitCompletionRequest(
686
729
  request.suffix || '',
687
730
  request.existingCode || '',
688
731
  request.language || 'python',
689
- request.filename || 'Untitled.ipynb',
732
+ request.filename || '',
690
733
  responseEmitter
691
734
  );
692
735
  }
@@ -1169,23 +1212,35 @@ function SidebarComponent(props: any) {
1169
1212
  useEffect(() => {
1170
1213
  const prefixes: string[] = [];
1171
1214
 
1172
- if (chatMode === 'ask') {
1173
- const chatParticipants = NBIAPI.config.chatParticipants;
1174
- for (const participant of chatParticipants) {
1175
- const id = participant.id;
1176
- const commands = participant.commands;
1177
- const participantPrefix = id === 'default' ? '' : `@${id}`;
1178
- if (participantPrefix !== '') {
1179
- prefixes.push(participantPrefix);
1180
- }
1181
- const commandPrefix =
1182
- participantPrefix === '' ? '' : `${participantPrefix} `;
1215
+ if (NBIAPI.config.isInClaudeCodeMode) {
1216
+ const claudeChatParticipant = NBIAPI.config.chatParticipants.find(
1217
+ participant => participant.id === CLAUDE_CODE_CHAT_PARTICIPANT_ID
1218
+ );
1219
+ if (claudeChatParticipant) {
1220
+ const commands = claudeChatParticipant.commands;
1183
1221
  for (const command of commands) {
1184
- prefixes.push(`${commandPrefix}/${command}`);
1222
+ prefixes.push(`/${command}`);
1185
1223
  }
1186
1224
  }
1187
1225
  } else {
1188
- prefixes.push('/clear');
1226
+ if (chatMode === 'ask') {
1227
+ const chatParticipants = NBIAPI.config.chatParticipants;
1228
+ for (const participant of chatParticipants) {
1229
+ const id = participant.id;
1230
+ const commands = participant.commands;
1231
+ const participantPrefix = id === 'default' ? '' : `@${id}`;
1232
+ if (participantPrefix !== '') {
1233
+ prefixes.push(participantPrefix);
1234
+ }
1235
+ const commandPrefix =
1236
+ participantPrefix === '' ? '' : `${participantPrefix} `;
1237
+ for (const command of commands) {
1238
+ prefixes.push(`${commandPrefix}/${command}`);
1239
+ }
1240
+ }
1241
+ } else {
1242
+ prefixes.push('/clear');
1243
+ }
1189
1244
  }
1190
1245
 
1191
1246
  const mcpServers = NBIAPI.config.toolConfig.mcpServers;
@@ -1969,24 +2024,26 @@ function SidebarComponent(props: any) {
1969
2024
  )}
1970
2025
  <div style={{ flexGrow: 1 }}></div>
1971
2026
  <div className="chat-mode-widgets-container">
1972
- <div>
1973
- <select
1974
- className="chat-mode-select"
1975
- title="Chat mode"
1976
- value={chatMode}
1977
- onChange={event => {
1978
- if (event.target.value === 'ask') {
1979
- setToolSelections(toolSelectionsEmpty);
1980
- }
1981
- setShowModeTools(false);
1982
- setChatMode(event.target.value);
1983
- }}
1984
- >
1985
- <option value="ask">Ask</option>
1986
- <option value="agent">Agent</option>
1987
- </select>
1988
- </div>
1989
- {chatMode !== 'ask' && (
2027
+ {!NBIAPI.config.isInClaudeCodeMode && (
2028
+ <div>
2029
+ <select
2030
+ className="chat-mode-select"
2031
+ title="Chat mode"
2032
+ value={chatMode}
2033
+ onChange={event => {
2034
+ if (event.target.value === 'ask') {
2035
+ setToolSelections(toolSelectionsEmpty);
2036
+ }
2037
+ setShowModeTools(false);
2038
+ setChatMode(event.target.value);
2039
+ }}
2040
+ >
2041
+ <option value="ask">Ask</option>
2042
+ <option value="agent">Agent</option>
2043
+ </select>
2044
+ </div>
2045
+ )}
2046
+ {chatMode !== 'ask' && !NBIAPI.config.isInClaudeCodeMode && (
1990
2047
  <div
1991
2048
  className={`user-input-footer-button tools-button ${unsafeToolSelected ? 'tools-button-warning' : selectedToolCount > 0 ? 'tools-button-active' : ''}`}
1992
2049
  onClick={() => handleChatToolsButtonClick()}
@@ -2000,6 +2057,13 @@ function SidebarComponent(props: any) {
2000
2057
  {selectedToolCount > 0 && <>{selectedToolCount}</>}
2001
2058
  </div>
2002
2059
  )}
2060
+ {NBIAPI.config.isInClaudeCodeMode && (
2061
+ <span
2062
+ title="Claude mode"
2063
+ className="claude-icon"
2064
+ dangerouslySetInnerHTML={{ __html: claudeSvg }}
2065
+ ></span>
2066
+ )}
2003
2067
  </div>
2004
2068
  <div>
2005
2069
  <button
@@ -2352,7 +2416,7 @@ function InlinePromptComponent(props: any) {
2352
2416
  type: RunChatCompletionType.GenerateCode,
2353
2417
  content: prompt,
2354
2418
  language: props.language || 'python',
2355
- filename: props.filename || 'Untitled.ipynb',
2419
+ filename: props.filename || '',
2356
2420
  prefix: props.prefix,
2357
2421
  suffix: props.suffix,
2358
2422
  existingCode: props.existingCode,
@@ -0,0 +1,101 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+
3
+ import React, { useState } from 'react';
4
+
5
+ export function AskUserQuestion(props: any) {
6
+ const userQuestions = props.userQuestions.content;
7
+ const [selectedAnswers, setSelectedAnswers] = useState<{
8
+ [key: string]: string[];
9
+ }>({});
10
+
11
+ const onOptionSelected = (question: any, option: any) => {
12
+ if (question.multiSelect) {
13
+ if (selectedAnswers[question.question]?.includes(option.label)) {
14
+ setSelectedAnswers({
15
+ ...selectedAnswers,
16
+ [question.question]: (
17
+ selectedAnswers[question.question] ?? []
18
+ ).filter((o: any) => o !== option.label)
19
+ });
20
+ } else {
21
+ setSelectedAnswers({
22
+ ...selectedAnswers,
23
+ [question.question]: [
24
+ ...(selectedAnswers[question.question] ?? []),
25
+ option.label
26
+ ]
27
+ });
28
+ }
29
+ } else {
30
+ setSelectedAnswers({
31
+ ...selectedAnswers,
32
+ [question.question]: [option.label]
33
+ });
34
+ }
35
+ };
36
+
37
+ return (
38
+ <>
39
+ {userQuestions.title ? (
40
+ <div>
41
+ <b>{userQuestions.title}</b>
42
+ </div>
43
+ ) : null}
44
+ {userQuestions.message ? <div>{userQuestions.message}</div> : null}
45
+ {userQuestions.questions.map((question: any) => (
46
+ <div className="ask-user-question-container" key={question.question}>
47
+ <form className="ask-user-question-form">
48
+ <div className="ask-user-question-question">
49
+ {question.question}
50
+ </div>
51
+ <div className="ask-user-question-header">{question.header}</div>
52
+ <div className="ask-user-question-options">
53
+ {question.options.map((option: any) => (
54
+ <div className="ask-user-question-option" key={option.label}>
55
+ <div>
56
+ <input
57
+ id={option.label}
58
+ type="checkbox"
59
+ checked={
60
+ selectedAnswers[question.question]?.includes(
61
+ option.label
62
+ ) ?? false
63
+ }
64
+ onChange={() => onOptionSelected(question, option)}
65
+ />
66
+ <label htmlFor={option.label}>{option.label}</label>
67
+ </div>
68
+ <div className="ask-user-question-option-description">
69
+ {option.description}
70
+ </div>
71
+ </div>
72
+ ))}
73
+ </div>
74
+ </form>
75
+ </div>
76
+ ))}
77
+ <div className="ask-user-question-footer">
78
+ <button
79
+ className="jp-Dialog-button jp-mod-accept jp-mod-styled"
80
+ onClick={() => {
81
+ props.onSubmit(selectedAnswers);
82
+ }}
83
+ >
84
+ <div className="jp-Dialog-buttonLabel">
85
+ {userQuestions.submitLabel}
86
+ </div>
87
+ </button>
88
+ <button
89
+ className="jp-Dialog-button jp-mod-reject jp-mod-styled"
90
+ onClick={() => {
91
+ props.onCancel();
92
+ }}
93
+ >
94
+ <div className="jp-Dialog-buttonLabel">
95
+ {userQuestions.cancelLabel}
96
+ </div>
97
+ </button>
98
+ </div>
99
+ </>
100
+ );
101
+ }
@@ -6,20 +6,31 @@ import { MdOutlineCheckBoxOutlineBlank, MdCheckBox } from 'react-icons/md';
6
6
 
7
7
  export function CheckBoxItem(props: any) {
8
8
  const indent = props.indent || 0;
9
+ const disabled = props.disabled || false;
9
10
 
10
11
  return (
11
12
  <div
12
13
  className={`checkbox-item checkbox-item-indent-${indent} ${props.header ? 'checkbox-item-header' : ''}`}
13
14
  title={props.tooltip || props.title || ''}
14
- onClick={event => props.onClick(event)}
15
+ onClick={event => {
16
+ if (!disabled) {
17
+ props.onClick(event);
18
+ }
19
+ }}
15
20
  >
16
21
  <div className="checkbox-item-toggle">
17
22
  {props.checked ? (
18
- <MdCheckBox className="checkbox-icon" />
23
+ <MdCheckBox
24
+ className="checkbox-icon"
25
+ style={{ opacity: disabled ? 0.5 : 1 }}
26
+ />
19
27
  ) : (
20
- <MdOutlineCheckBoxOutlineBlank className="checkbox-icon" />
28
+ <MdOutlineCheckBoxOutlineBlank
29
+ className="checkbox-icon"
30
+ style={{ opacity: disabled ? 0.5 : 1 }}
31
+ />
21
32
  )}
22
- {props.label}
33
+ <span style={{ opacity: disabled ? 0.5 : 1 }}>{props.label}</span>
23
34
  </div>
24
35
  {props.title && (
25
36
  <div className="checkbox-item-description">{props.title}</div>