@notebook-intelligence/notebook-intelligence 3.0.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.0.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
  }
@@ -1168,23 +1211,36 @@ function SidebarComponent(props: any) {
1168
1211
 
1169
1212
  useEffect(() => {
1170
1213
  const prefixes: string[] = [];
1171
- prefixes.push('/clear');
1172
-
1173
- if (chatMode === 'ask') {
1174
- const chatParticipants = NBIAPI.config.chatParticipants;
1175
- for (const participant of chatParticipants) {
1176
- const id = participant.id;
1177
- const commands = participant.commands;
1178
- const participantPrefix = id === 'default' ? '' : `@${id}`;
1179
- if (participantPrefix !== '') {
1180
- prefixes.push(participantPrefix);
1181
- }
1182
- const commandPrefix =
1183
- participantPrefix === '' ? '' : `${participantPrefix} `;
1214
+
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;
1184
1221
  for (const command of commands) {
1185
- prefixes.push(`${commandPrefix}/${command}`);
1222
+ prefixes.push(`/${command}`);
1186
1223
  }
1187
1224
  }
1225
+ } else {
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
+ }
1188
1244
  }
1189
1245
 
1190
1246
  const mcpServers = NBIAPI.config.toolConfig.mcpServers;
@@ -1968,24 +2024,26 @@ function SidebarComponent(props: any) {
1968
2024
  )}
1969
2025
  <div style={{ flexGrow: 1 }}></div>
1970
2026
  <div className="chat-mode-widgets-container">
1971
- <div>
1972
- <select
1973
- className="chat-mode-select"
1974
- title="Chat mode"
1975
- value={chatMode}
1976
- onChange={event => {
1977
- if (event.target.value === 'ask') {
1978
- setToolSelections(toolSelectionsEmpty);
1979
- }
1980
- setShowModeTools(false);
1981
- setChatMode(event.target.value);
1982
- }}
1983
- >
1984
- <option value="ask">Ask</option>
1985
- <option value="agent">Agent</option>
1986
- </select>
1987
- </div>
1988
- {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 && (
1989
2047
  <div
1990
2048
  className={`user-input-footer-button tools-button ${unsafeToolSelected ? 'tools-button-warning' : selectedToolCount > 0 ? 'tools-button-active' : ''}`}
1991
2049
  onClick={() => handleChatToolsButtonClick()}
@@ -1999,6 +2057,13 @@ function SidebarComponent(props: any) {
1999
2057
  {selectedToolCount > 0 && <>{selectedToolCount}</>}
2000
2058
  </div>
2001
2059
  )}
2060
+ {NBIAPI.config.isInClaudeCodeMode && (
2061
+ <span
2062
+ title="Claude mode"
2063
+ className="claude-icon"
2064
+ dangerouslySetInnerHTML={{ __html: claudeSvg }}
2065
+ ></span>
2066
+ )}
2002
2067
  </div>
2003
2068
  <div>
2004
2069
  <button
@@ -2351,7 +2416,7 @@ function InlinePromptComponent(props: any) {
2351
2416
  type: RunChatCompletionType.GenerateCode,
2352
2417
  content: prompt,
2353
2418
  language: props.language || 'python',
2354
- filename: props.filename || 'Untitled.ipynb',
2419
+ filename: props.filename || '',
2355
2420
  prefix: props.prefix,
2356
2421
  suffix: props.suffix,
2357
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>