@notebook-intelligence/notebook-intelligence 2.4.2 → 2.6.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.
@@ -20,7 +20,6 @@ import {
20
20
  BackendMessageType,
21
21
  BuiltinToolsetType,
22
22
  ContextType,
23
- GITHUB_COPILOT_PROVIDER_ID,
24
23
  IActiveDocumentInfo,
25
24
  ICellContents,
26
25
  IChatCompletionResponseEmitter,
@@ -46,24 +45,15 @@ import {
46
45
  VscEyeClosed,
47
46
  VscTriangleRight,
48
47
  VscTriangleDown,
49
- VscWarning,
50
48
  VscSettingsGear,
51
49
  VscPassFilled,
52
50
  VscTools,
53
51
  VscTrash
54
52
  } from 'react-icons/vsc';
55
53
 
56
- import { MdOutlineCheckBoxOutlineBlank, MdCheckBox } from 'react-icons/md';
57
-
58
54
  import { extractLLMGeneratedCode, isDarkTheme } from './utils';
59
- import * as path from 'path';
60
-
61
- const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
62
- const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
63
- const OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID =
64
- 'openai-compatible-inline-completion-model';
65
- const LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID =
66
- 'litellm-compatible-inline-completion-model';
55
+ import { CheckBoxItem } from './components/checkbox';
56
+ import { mcpServerSettingsToEnabledState } from './components/mcp-util';
67
57
 
68
58
  export enum RunChatCompletionType {
69
59
  Chat,
@@ -127,6 +117,8 @@ export interface IInlinePromptWidgetOptions {
127
117
  existingCode: string;
128
118
  prefix: string;
129
119
  suffix: string;
120
+ language?: string;
121
+ filename?: string;
130
122
  onRequestSubmitted: (prompt: string) => void;
131
123
  onRequestCancelled: () => void;
132
124
  onContentStream: (content: string) => void;
@@ -264,30 +256,6 @@ export class GitHubCopilotLoginDialogBody extends ReactWidget {
264
256
  private _onLoggedIn: () => void;
265
257
  }
266
258
 
267
- export class ConfigurationDialogBody extends ReactWidget {
268
- constructor(options: {
269
- onSave: () => void;
270
- onEditMCPConfigClicked: () => void;
271
- }) {
272
- super();
273
-
274
- this._onEditMCPConfigClicked = options.onEditMCPConfigClicked;
275
- this._onSave = options.onSave;
276
- }
277
-
278
- render(): JSX.Element {
279
- return (
280
- <ConfigurationDialogBodyComponent
281
- onEditMCPConfigClicked={this._onEditMCPConfigClicked}
282
- onSave={this._onSave}
283
- />
284
- );
285
- }
286
-
287
- private _onEditMCPConfigClicked: () => void;
288
- private _onSave: () => void;
289
- }
290
-
291
259
  interface IChatMessageContent {
292
260
  id: string;
293
261
  type: ResponseStreamDataType;
@@ -684,30 +652,6 @@ async function submitCompletionRequest(
684
652
  }
685
653
  }
686
654
 
687
- function CheckBoxItem(props: any) {
688
- const indent = props.indent || 0;
689
-
690
- return (
691
- <div
692
- className={`checkbox-item checkbox-item-indent-${indent} ${props.header ? 'checkbox-item-header' : ''}`}
693
- title={props.title}
694
- onClick={event => props.onClick(event)}
695
- >
696
- <div className="checkbox-item-toggle">
697
- {props.checked ? (
698
- <MdCheckBox className="checkbox-icon" />
699
- ) : (
700
- <MdOutlineCheckBoxOutlineBlank className="checkbox-icon" />
701
- )}
702
- {props.label}
703
- </div>
704
- {props.title && (
705
- <div className="checkbox-item-description">{props.title}</div>
706
- )}
707
- </div>
708
- );
709
- }
710
-
711
655
  function SidebarComponent(props: any) {
712
656
  const [chatMessages, setChatMessages] = useState<IChatMessage[]>([]);
713
657
  const [prompt, setPrompt] = useState<string>('');
@@ -743,7 +687,9 @@ function SidebarComponent(props: any) {
743
687
  const [selectedToolCount, setSelectedToolCount] = useState(0);
744
688
  const [notebookExecuteToolSelected, setNotebookExecuteToolSelected] =
745
689
  useState(false);
746
- const [toolConfig, setToolConfig] = useState({
690
+
691
+ const [renderCount, setRenderCount] = useState(1);
692
+ const toolConfigRef = useRef({
747
693
  builtinToolsets: [
748
694
  { id: BuiltinToolsetType.NotebookEdit, name: 'Notebook edit' },
749
695
  { id: BuiltinToolsetType.NotebookExecute, name: 'Notebook execute' }
@@ -751,6 +697,16 @@ function SidebarComponent(props: any) {
751
697
  mcpServers: [],
752
698
  extensions: []
753
699
  });
700
+ const mcpServerSettingsRef = useRef(NBIAPI.config.mcpServerSettings);
701
+ const [mcpServerEnabledState, setMCPServerEnabledState] = useState(
702
+ new Map<string, Set<string>>(
703
+ mcpServerSettingsToEnabledState(
704
+ toolConfigRef.current.mcpServers,
705
+ mcpServerSettingsRef.current
706
+ )
707
+ )
708
+ );
709
+
754
710
  const [showModeTools, setShowModeTools] = useState(false);
755
711
  const toolSelectionsInitial: any = {
756
712
  builtinToolsets: [BuiltinToolsetType.NotebookEdit],
@@ -762,25 +718,37 @@ function SidebarComponent(props: any) {
762
718
  mcpServers: {},
763
719
  extensions: {}
764
720
  };
765
- const [toolSelections, setToolSelections] = useState(toolSelectionsInitial);
721
+ const [toolSelections, setToolSelections] = useState(
722
+ structuredClone(toolSelectionsInitial)
723
+ );
766
724
  const [hasExtensionTools, setHasExtensionTools] = useState(false);
767
725
  const [lastScrollTime, setLastScrollTime] = useState(0);
768
726
  const [scrollPending, setScrollPending] = useState(false);
769
727
 
770
- NBIAPI.configChanged.connect(() => {
771
- setToolConfig(NBIAPI.config.toolConfig);
772
- });
728
+ useEffect(() => {
729
+ NBIAPI.configChanged.connect(() => {
730
+ toolConfigRef.current = NBIAPI.config.toolConfig;
731
+ mcpServerSettingsRef.current = NBIAPI.config.mcpServerSettings;
732
+ const newMcpServerEnabledState = mcpServerSettingsToEnabledState(
733
+ toolConfigRef.current.mcpServers,
734
+ mcpServerSettingsRef.current
735
+ );
736
+ setMCPServerEnabledState(newMcpServerEnabledState);
737
+ setToolSelections(structuredClone(toolSelectionsInitial));
738
+ setRenderCount(renderCount => renderCount + 1);
739
+ });
740
+ }, []);
773
741
 
774
742
  useEffect(() => {
775
743
  let hasTools = false;
776
- for (const extension of toolConfig.extensions) {
744
+ for (const extension of toolConfigRef.current.extensions) {
777
745
  if (extension.toolsets.length > 0) {
778
746
  hasTools = true;
779
747
  break;
780
748
  }
781
749
  }
782
750
  setHasExtensionTools(hasTools);
783
- }, [toolConfig]);
751
+ }, [toolConfigRef.current]);
784
752
 
785
753
  useEffect(() => {
786
754
  const builtinToolSelCount = toolSelections.builtinToolsets.length;
@@ -862,7 +830,9 @@ function SidebarComponent(props: any) {
862
830
  return false;
863
831
  }
864
832
 
865
- const mcpServer = toolConfig.mcpServers.find(server => server.id === id);
833
+ const mcpServer = toolConfigRef.current.mcpServers.find(
834
+ server => server.id === id
835
+ );
866
836
 
867
837
  const selectedServerTools: string[] = toolSelections.mcpServers[id];
868
838
 
@@ -881,10 +851,16 @@ function SidebarComponent(props: any) {
881
851
  delete newConfig.mcpServers[id];
882
852
  setToolSelections(newConfig);
883
853
  } else {
884
- const mcpServer = toolConfig.mcpServers.find(server => server.id === id);
854
+ const mcpServer = toolConfigRef.current.mcpServers.find(
855
+ server => server.id === id
856
+ );
885
857
  const newConfig = { ...toolSelections };
886
858
  newConfig.mcpServers[id] = structuredClone(
887
- mcpServer.tools.map((tool: any) => tool.name)
859
+ mcpServer.tools
860
+ .filter((tool: any) =>
861
+ mcpServerEnabledState.get(mcpServer.id).has(tool.name)
862
+ )
863
+ .map((tool: any) => tool.name)
888
864
  );
889
865
  setToolSelections(newConfig);
890
866
  }
@@ -931,7 +907,7 @@ function SidebarComponent(props: any) {
931
907
  return false;
932
908
  }
933
909
 
934
- const extension = toolConfig.extensions.find(
910
+ const extension = toolConfigRef.current.extensions.find(
935
911
  extension => extension.id === extensionId
936
912
  );
937
913
 
@@ -956,7 +932,9 @@ function SidebarComponent(props: any) {
956
932
  return false;
957
933
  }
958
934
 
959
- const extension = toolConfig.extensions.find(ext => ext.id === extensionId);
935
+ const extension = toolConfigRef.current.extensions.find(
936
+ ext => ext.id === extensionId
937
+ );
960
938
  const extensionToolset = extension.toolsets.find(
961
939
  (toolset: any) => toolset.id === toolsetId
962
940
  );
@@ -988,7 +966,7 @@ function SidebarComponent(props: any) {
988
966
  setToolSelections(newConfig);
989
967
  } else {
990
968
  const newConfig = { ...toolSelections };
991
- const extension = toolConfig.extensions.find(
969
+ const extension = toolConfigRef.current.extensions.find(
992
970
  ext => ext.id === extensionId
993
971
  );
994
972
  if (extensionId in newConfig.extensions) {
@@ -1030,7 +1008,7 @@ function SidebarComponent(props: any) {
1030
1008
  }
1031
1009
  setToolSelections(newConfig);
1032
1010
  } else {
1033
- const extension = toolConfig.extensions.find(
1011
+ const extension = toolConfigRef.current.extensions.find(
1034
1012
  ext => ext.id === extensionId
1035
1013
  );
1036
1014
  const extensionToolset = extension.toolsets.find(
@@ -1101,30 +1079,38 @@ function SidebarComponent(props: any) {
1101
1079
 
1102
1080
  useEffect(() => {
1103
1081
  const prefixes: string[] = [];
1104
- if (chatMode !== 'ask') {
1105
- prefixes.push('/clear');
1106
- setOriginalPrefixes(prefixes);
1107
- setPrefixSuggestions(prefixes);
1108
- return;
1082
+ prefixes.push('/clear');
1083
+
1084
+ if (chatMode === 'ask') {
1085
+ const chatParticipants = NBIAPI.config.chatParticipants;
1086
+ for (const participant of chatParticipants) {
1087
+ const id = participant.id;
1088
+ const commands = participant.commands;
1089
+ const participantPrefix = id === 'default' ? '' : `@${id}`;
1090
+ if (participantPrefix !== '') {
1091
+ prefixes.push(participantPrefix);
1092
+ }
1093
+ const commandPrefix =
1094
+ participantPrefix === '' ? '' : `${participantPrefix} `;
1095
+ for (const command of commands) {
1096
+ prefixes.push(`${commandPrefix}/${command}`);
1097
+ }
1098
+ }
1109
1099
  }
1110
1100
 
1111
- const chatParticipants = NBIAPI.config.chatParticipants;
1112
- for (const participant of chatParticipants) {
1113
- const id = participant.id;
1114
- const commands = participant.commands;
1115
- const participantPrefix = id === 'default' ? '' : `@${id}`;
1116
- if (participantPrefix !== '') {
1117
- prefixes.push(participantPrefix);
1118
- }
1119
- const commandPrefix =
1120
- participantPrefix === '' ? '' : `${participantPrefix} `;
1121
- for (const command of commands) {
1122
- prefixes.push(`${commandPrefix}/${command}`);
1101
+ const mcpServers = NBIAPI.config.toolConfig.mcpServers;
1102
+ const mcpServerSettings = NBIAPI.config.mcpServerSettings;
1103
+ for (const mcpServer of mcpServers) {
1104
+ if (mcpServerSettings[mcpServer.id]?.disabled !== true) {
1105
+ for (const prompt of mcpServer.prompts) {
1106
+ prefixes.push(`/mcp:${mcpServer.id}:${prompt.name}`);
1107
+ }
1123
1108
  }
1124
1109
  }
1110
+
1125
1111
  setOriginalPrefixes(prefixes);
1126
1112
  setPrefixSuggestions(prefixes);
1127
- }, [chatMode]);
1113
+ }, [chatMode, renderCount]);
1128
1114
 
1129
1115
  useEffect(() => {
1130
1116
  const fetchData = () => {
@@ -1160,11 +1146,41 @@ function SidebarComponent(props: any) {
1160
1146
  }
1161
1147
  };
1162
1148
 
1163
- const applyPrefixSuggestion = (prefix: string) => {
1149
+ const applyPrefixSuggestion = async (prefix: string) => {
1150
+ let mcpArguments = '';
1151
+ if (prefix.startsWith('/mcp:')) {
1152
+ mcpArguments = ':';
1153
+ const serverId = prefix.split(':')[1];
1154
+ const promptName = prefix.split(':')[2];
1155
+ const promptConfig = NBIAPI.config.getMCPServerPrompt(
1156
+ serverId,
1157
+ promptName
1158
+ );
1159
+ if (
1160
+ promptConfig &&
1161
+ promptConfig.arguments &&
1162
+ promptConfig.arguments.length > 0
1163
+ ) {
1164
+ const result = await props
1165
+ .getApp()
1166
+ .commands.execute('notebook-intelligence:show-form-input-dialog', {
1167
+ title: 'Input Parameters',
1168
+ fields: promptConfig.arguments
1169
+ });
1170
+ const argumentValues: string[] = [];
1171
+ for (const argument of promptConfig.arguments) {
1172
+ if (result[argument.name] !== undefined) {
1173
+ argumentValues.push(`${argument.name}=${result[argument.name]}`);
1174
+ }
1175
+ }
1176
+ mcpArguments = `(${argumentValues.join(', ')}):`;
1177
+ }
1178
+ }
1179
+
1164
1180
  if (prefix.includes(prompt)) {
1165
- setPrompt(`${prefix} `);
1181
+ setPrompt(`${prefix}${mcpArguments} `);
1166
1182
  } else {
1167
- setPrompt(`${prefix} ${prompt} `);
1183
+ setPrompt(`${prefix} ${prompt}${mcpArguments} `);
1168
1184
  }
1169
1185
  setShowPopover(false);
1170
1186
  promptInputRef.current?.focus();
@@ -1194,7 +1210,16 @@ function SidebarComponent(props: any) {
1194
1210
 
1195
1211
  const handleChatToolsButtonClick = async () => {
1196
1212
  if (!showModeTools) {
1197
- NBIAPI.fetchCapabilities();
1213
+ NBIAPI.fetchCapabilities().then(() => {
1214
+ toolConfigRef.current = NBIAPI.config.toolConfig;
1215
+ mcpServerSettingsRef.current = NBIAPI.config.mcpServerSettings;
1216
+ const newMcpServerEnabledState = mcpServerSettingsToEnabledState(
1217
+ toolConfigRef.current.mcpServers,
1218
+ mcpServerSettingsRef.current
1219
+ );
1220
+ setMCPServerEnabledState(newMcpServerEnabledState);
1221
+ setRenderCount(renderCount => renderCount + 1);
1222
+ });
1198
1223
  }
1199
1224
  setShowModeTools(!showModeTools);
1200
1225
  };
@@ -1215,9 +1240,6 @@ function SidebarComponent(props: any) {
1215
1240
  }
1216
1241
  }
1217
1242
 
1218
- const promptPrefix =
1219
- promptPrefixParts.length > 0 ? promptPrefixParts.join(' ') + ' ' : '';
1220
-
1221
1243
  lastMessageId.current = UUID.uuid4();
1222
1244
  lastRequestTime.current = new Date();
1223
1245
 
@@ -1289,7 +1311,7 @@ function SidebarComponent(props: any) {
1289
1311
  type: RunChatCompletionType.Chat,
1290
1312
  content: extractedPrompt,
1291
1313
  language: activeDocInfo.language,
1292
- filename: activeDocInfo.filename,
1314
+ filename: activeDocInfo.filePath,
1293
1315
  additionalContext,
1294
1316
  chatMode,
1295
1317
  toolSelections: toolSelections
@@ -1382,7 +1404,7 @@ function SidebarComponent(props: any) {
1382
1404
  }
1383
1405
  );
1384
1406
 
1385
- const newPrompt = prompt.startsWith('/settings') ? '' : promptPrefix;
1407
+ const newPrompt = '';
1386
1408
 
1387
1409
  setPrompt(newPrompt);
1388
1410
  filterPrefixSuggestions(newPrompt);
@@ -1711,32 +1733,21 @@ function SidebarComponent(props: any) {
1711
1733
  return `${activeDocumentInfo.filename}${cellAndLineIndicator}`;
1712
1734
  };
1713
1735
 
1714
- const nbiConfig = NBIAPI.config;
1715
- const getGHLoginRequired = () => {
1716
- return (
1717
- nbiConfig.usingGitHubCopilotModel &&
1718
- NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn
1719
- );
1720
- };
1721
- const getChatEnabled = () => {
1722
- return nbiConfig.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
1723
- ? !getGHLoginRequired()
1724
- : nbiConfig.llmProviders.find(
1725
- provider => provider.id === nbiConfig.chatModel.provider
1726
- );
1727
- };
1728
-
1729
- const [ghLoginRequired, setGHLoginRequired] = useState(getGHLoginRequired());
1730
- const [chatEnabled, setChatEnabled] = useState(getChatEnabled());
1736
+ const [ghLoginRequired, setGHLoginRequired] = useState(
1737
+ NBIAPI.getGHLoginRequired()
1738
+ );
1739
+ const [chatEnabled, setChatEnabled] = useState(NBIAPI.getChatEnabled());
1731
1740
 
1732
- NBIAPI.configChanged.connect(() => {
1733
- setGHLoginRequired(getGHLoginRequired());
1734
- setChatEnabled(getChatEnabled());
1735
- });
1741
+ useEffect(() => {
1742
+ NBIAPI.configChanged.connect(() => {
1743
+ setGHLoginRequired(NBIAPI.getGHLoginRequired());
1744
+ setChatEnabled(NBIAPI.getChatEnabled());
1745
+ });
1746
+ }, []);
1736
1747
 
1737
1748
  useEffect(() => {
1738
- setGHLoginRequired(getGHLoginRequired());
1739
- setChatEnabled(getChatEnabled());
1749
+ setGHLoginRequired(NBIAPI.getGHLoginRequired());
1750
+ setChatEnabled(NBIAPI.getChatEnabled());
1740
1751
  }, [ghLoginStatus]);
1741
1752
 
1742
1753
  return (
@@ -1876,7 +1887,7 @@ function SidebarComponent(props: any) {
1876
1887
  if (event.target.value === 'ask') {
1877
1888
  setToolSelections(toolSelectionsEmpty);
1878
1889
  } else if (event.target.value === 'agent') {
1879
- setToolSelections(toolSelectionsInitial);
1890
+ setToolSelections(structuredClone(toolSelectionsInitial));
1880
1891
  }
1881
1892
  setShowModeTools(false);
1882
1893
  setChatMode(event.target.value);
@@ -1980,7 +1991,7 @@ function SidebarComponent(props: any) {
1980
1991
  <div className="mode-tools-popover-tool-list">
1981
1992
  <div className="mode-tools-group-header">Built-in</div>
1982
1993
  <div className="mode-tools-group mode-tools-group-built-in">
1983
- {toolConfig.builtinToolsets.map((toolset: any) => (
1994
+ {toolConfigRef.current.builtinToolsets.map((toolset: any) => (
1984
1995
  <CheckBoxItem
1985
1996
  key={toolset.id}
1986
1997
  label={toolset.name}
@@ -1995,87 +2006,113 @@ function SidebarComponent(props: any) {
1995
2006
  />
1996
2007
  ))}
1997
2008
  </div>
1998
- {toolConfig.mcpServers.length > 0 && (
1999
- <div className="mode-tools-group-header">MCP Servers</div>
2000
- )}
2001
- {toolConfig.mcpServers.map((mcpServer, index: number) => (
2002
- <div className="mode-tools-group">
2003
- <CheckBoxItem
2004
- label={mcpServer.id}
2005
- header={true}
2006
- checked={getMCPServerState(mcpServer.id)}
2007
- onClick={() => onMCPServerClicked(mcpServer.id)}
2008
- />
2009
- {mcpServer.tools.map((tool: any, index: number) => (
2010
- <CheckBoxItem
2011
- label={tool.name}
2012
- title={tool.description}
2013
- indent={1}
2014
- checked={getMCPServerToolState(mcpServer.id, tool.name)}
2015
- onClick={() =>
2016
- setMCPServerToolState(
2017
- mcpServer.id,
2018
- tool.name,
2019
- !getMCPServerToolState(mcpServer.id, tool.name)
2009
+ {renderCount > 0 &&
2010
+ mcpServerEnabledState.size > 0 &&
2011
+ toolConfigRef.current.mcpServers.length > 0 && (
2012
+ <div className="mode-tools-group-header">
2013
+ MCP Server Tools
2014
+ </div>
2015
+ )}
2016
+ {renderCount > 0 &&
2017
+ toolConfigRef.current.mcpServers
2018
+ .filter(mcpServer =>
2019
+ mcpServerEnabledState.has(mcpServer.id)
2020
+ )
2021
+ .map((mcpServer, index: number) => (
2022
+ <div className="mode-tools-group">
2023
+ <CheckBoxItem
2024
+ label={mcpServer.id}
2025
+ header={true}
2026
+ checked={getMCPServerState(mcpServer.id)}
2027
+ onClick={() => onMCPServerClicked(mcpServer.id)}
2028
+ />
2029
+ {mcpServer.tools
2030
+ .filter((tool: any) =>
2031
+ mcpServerEnabledState
2032
+ .get(mcpServer.id)
2033
+ .has(tool.name)
2020
2034
  )
2021
- }
2022
- />
2035
+ .map((tool: any, index: number) => (
2036
+ <CheckBoxItem
2037
+ label={tool.name}
2038
+ title={tool.description}
2039
+ indent={1}
2040
+ checked={getMCPServerToolState(
2041
+ mcpServer.id,
2042
+ tool.name
2043
+ )}
2044
+ onClick={() =>
2045
+ setMCPServerToolState(
2046
+ mcpServer.id,
2047
+ tool.name,
2048
+ !getMCPServerToolState(
2049
+ mcpServer.id,
2050
+ tool.name
2051
+ )
2052
+ )
2053
+ }
2054
+ />
2055
+ ))}
2056
+ </div>
2023
2057
  ))}
2024
- </div>
2025
- ))}
2026
2058
  {hasExtensionTools && (
2027
2059
  <div className="mode-tools-group-header">Extension tools</div>
2028
2060
  )}
2029
- {toolConfig.extensions.map((extension, index: number) => (
2030
- <div className="mode-tools-group">
2031
- <CheckBoxItem
2032
- label={`${extension.name} (${extension.id})`}
2033
- header={true}
2034
- checked={getExtensionState(extension.id)}
2035
- onClick={() => onExtensionClicked(extension.id)}
2036
- />
2037
- {extension.toolsets.map((toolset: any, index: number) => (
2038
- <>
2039
- <CheckBoxItem
2040
- label={`${toolset.name} (${toolset.id})`}
2041
- title={toolset.description}
2042
- indent={1}
2043
- checked={getExtensionToolsetState(
2044
- extension.id,
2045
- toolset.id
2046
- )}
2047
- onClick={() =>
2048
- onExtensionToolsetClicked(extension.id, toolset.id)
2049
- }
2050
- />
2051
- {toolset.tools.map((tool: any, index: number) => (
2061
+ {toolConfigRef.current.extensions.map(
2062
+ (extension, index: number) => (
2063
+ <div className="mode-tools-group">
2064
+ <CheckBoxItem
2065
+ label={`${extension.name} (${extension.id})`}
2066
+ header={true}
2067
+ checked={getExtensionState(extension.id)}
2068
+ onClick={() => onExtensionClicked(extension.id)}
2069
+ />
2070
+ {extension.toolsets.map((toolset: any, index: number) => (
2071
+ <>
2052
2072
  <CheckBoxItem
2053
- label={tool.name}
2054
- title={tool.description}
2055
- indent={2}
2056
- checked={getExtensionToolsetToolState(
2073
+ label={`${toolset.name} (${toolset.id})`}
2074
+ title={toolset.description}
2075
+ indent={1}
2076
+ checked={getExtensionToolsetState(
2057
2077
  extension.id,
2058
- toolset.id,
2059
- tool.name
2078
+ toolset.id
2060
2079
  )}
2061
2080
  onClick={() =>
2062
- setExtensionToolsetToolState(
2081
+ onExtensionToolsetClicked(
2082
+ extension.id,
2083
+ toolset.id
2084
+ )
2085
+ }
2086
+ />
2087
+ {toolset.tools.map((tool: any, index: number) => (
2088
+ <CheckBoxItem
2089
+ label={tool.name}
2090
+ title={tool.description}
2091
+ indent={2}
2092
+ checked={getExtensionToolsetToolState(
2063
2093
  extension.id,
2064
2094
  toolset.id,
2065
- tool.name,
2066
- !getExtensionToolsetToolState(
2095
+ tool.name
2096
+ )}
2097
+ onClick={() =>
2098
+ setExtensionToolsetToolState(
2067
2099
  extension.id,
2068
2100
  toolset.id,
2069
- tool.name
2101
+ tool.name,
2102
+ !getExtensionToolsetToolState(
2103
+ extension.id,
2104
+ toolset.id,
2105
+ tool.name
2106
+ )
2070
2107
  )
2071
- )
2072
- }
2073
- />
2074
- ))}
2075
- </>
2076
- ))}
2077
- </div>
2078
- ))}
2108
+ }
2109
+ />
2110
+ ))}
2111
+ </>
2112
+ ))}
2113
+ </div>
2114
+ )
2115
+ )}
2079
2116
  </div>
2080
2117
  </div>
2081
2118
  )}
@@ -2224,8 +2261,8 @@ function InlinePromptComponent(props: any) {
2224
2261
  chatId: UUID.uuid4(),
2225
2262
  type: RunChatCompletionType.GenerateCode,
2226
2263
  content: prompt,
2227
- language: undefined,
2228
- filename: undefined,
2264
+ language: props.language || 'python',
2265
+ filename: props.filename || 'Untitled.ipynb',
2229
2266
  prefix: props.prefix,
2230
2267
  suffix: props.suffix,
2231
2268
  existingCode: props.existingCode,
@@ -2498,526 +2535,75 @@ function GitHubCopilotLoginDialogBodyComponent(props: any) {
2498
2535
  );
2499
2536
  }
2500
2537
 
2501
- function ConfigurationDialogBodyComponent(props: any) {
2502
- const nbiConfig = NBIAPI.config;
2503
- const llmProviders = nbiConfig.llmProviders;
2504
- const [chatModels, setChatModels] = useState([]);
2505
- const [inlineCompletionModels, setInlineCompletionModels] = useState([]);
2506
- const [mcpServerNames, setMcpServerNames] = useState(
2507
- nbiConfig.toolConfig.mcpServers?.map((server: any) => server.id) || []
2508
- );
2509
-
2510
- const handleSaveClick = async () => {
2511
- const config: any = {
2512
- default_chat_mode: defaultChatMode,
2513
- chat_model: {
2514
- provider: chatModelProvider,
2515
- model: chatModel,
2516
- properties: chatModelProperties
2517
- },
2518
- inline_completion_model: {
2519
- provider: inlineCompletionModelProvider,
2520
- model: inlineCompletionModel,
2521
- properties: inlineCompletionModelProperties
2522
- }
2523
- };
2524
-
2525
- if (
2526
- chatModelProvider === 'github-copilot' ||
2527
- inlineCompletionModelProvider === 'github-copilot'
2528
- ) {
2529
- config.store_github_access_token = storeGitHubAccessToken;
2530
- }
2531
-
2532
- await NBIAPI.setConfig(config);
2533
-
2534
- props.onSave();
2535
- };
2536
-
2537
- const handleRefreshOllamaModelListClick = async () => {
2538
- await NBIAPI.updateOllamaModelList();
2539
- updateModelOptionsForProvider(chatModelProvider, 'chat');
2540
- };
2538
+ export class FormInputDialogBody extends ReactWidget {
2539
+ constructor(options: { fields: any; onDone: (formData: any) => void }) {
2540
+ super();
2541
2541
 
2542
- const [chatModelProvider, setChatModelProvider] = useState(
2543
- nbiConfig.chatModel.provider || 'none'
2544
- );
2545
- const [inlineCompletionModelProvider, setInlineCompletionModelProvider] =
2546
- useState(nbiConfig.inlineCompletionModel.provider || 'none');
2547
- const [defaultChatMode, setDefaultChatMode] = useState<string>(
2548
- nbiConfig.defaultChatMode
2549
- );
2550
- const [chatModel, setChatModel] = useState<string>(nbiConfig.chatModel.model);
2551
- const [chatModelProperties, setChatModelProperties] = useState<any[]>([]);
2552
- const [inlineCompletionModelProperties, setInlineCompletionModelProperties] =
2553
- useState<any[]>([]);
2554
- const [inlineCompletionModel, setInlineCompletionModel] = useState(
2555
- nbiConfig.inlineCompletionModel.model
2556
- );
2557
- const [storeGitHubAccessToken, setStoreGitHubAccessToken] = useState(
2558
- nbiConfig.storeGitHubAccessToken
2559
- );
2542
+ this._fields = options.fields || [];
2543
+ this._onDone = options.onDone || (() => {});
2544
+ }
2560
2545
 
2561
- const updateModelOptionsForProvider = (
2562
- providerId: string,
2563
- modelType: 'chat' | 'inline-completion'
2564
- ) => {
2565
- if (modelType === 'chat') {
2566
- setChatModelProvider(providerId);
2567
- } else {
2568
- setInlineCompletionModelProvider(providerId);
2569
- }
2570
- const models =
2571
- modelType === 'chat'
2572
- ? nbiConfig.chatModels
2573
- : nbiConfig.inlineCompletionModels;
2574
- const selectedModelId =
2575
- modelType === 'chat'
2576
- ? nbiConfig.chatModel.model
2577
- : nbiConfig.inlineCompletionModel.model;
2578
-
2579
- const providerModels = models.filter(
2580
- (model: any) => model.provider === providerId
2581
- );
2582
- if (modelType === 'chat') {
2583
- setChatModels(providerModels);
2584
- } else {
2585
- setInlineCompletionModels(providerModels);
2586
- }
2587
- let selectedModel = providerModels.find(
2588
- (model: any) => model.id === selectedModelId
2546
+ render(): JSX.Element {
2547
+ return (
2548
+ <FormInputDialogBodyComponent
2549
+ fields={this._fields}
2550
+ onDone={this._onDone}
2551
+ />
2589
2552
  );
2590
- if (!selectedModel) {
2591
- selectedModel = providerModels?.[0];
2592
- }
2593
- if (selectedModel) {
2594
- if (modelType === 'chat') {
2595
- setChatModel(selectedModel.id);
2596
- setChatModelProperties(selectedModel.properties);
2597
- } else {
2598
- setInlineCompletionModel(selectedModel.id);
2599
- setInlineCompletionModelProperties(selectedModel.properties);
2600
- }
2601
- } else {
2602
- if (modelType === 'chat') {
2603
- setChatModelProperties([]);
2604
- } else {
2605
- setInlineCompletionModelProperties([]);
2606
- }
2607
- }
2608
- };
2553
+ }
2609
2554
 
2610
- const onModelPropertyChange = (
2611
- modelType: 'chat' | 'inline-completion',
2612
- propertyId: string,
2613
- value: string
2614
- ) => {
2615
- const modelProperties =
2616
- modelType === 'chat'
2617
- ? chatModelProperties
2618
- : inlineCompletionModelProperties;
2619
- const updatedProperties = modelProperties.map((property: any) => {
2620
- if (property.id === propertyId) {
2621
- return { ...property, value };
2622
- }
2623
- return property;
2624
- });
2625
- if (modelType === 'chat') {
2626
- setChatModelProperties(updatedProperties);
2627
- } else {
2628
- setInlineCompletionModelProperties(updatedProperties);
2629
- }
2630
- };
2555
+ private _fields: any;
2556
+ private _onDone: (formData: any) => void;
2557
+ }
2631
2558
 
2632
- const handleReloadMCPServersClick = async () => {
2633
- const data = await NBIAPI.reloadMCPServerList();
2634
- setMcpServerNames(data.mcpServers?.map((server: any) => server.id) || []);
2635
- };
2559
+ function FormInputDialogBodyComponent(props: any) {
2560
+ const [formData, setFormData] = useState<any>({});
2636
2561
 
2637
- useEffect(() => {
2638
- updateModelOptionsForProvider(chatModelProvider, 'chat');
2639
- updateModelOptionsForProvider(
2640
- inlineCompletionModelProvider,
2641
- 'inline-completion'
2642
- );
2643
- }, []);
2562
+ const handleInputChange = (event: any) => {
2563
+ setFormData({ ...formData, [event.target.name]: event.target.value });
2564
+ };
2644
2565
 
2645
2566
  return (
2646
- <div className="config-dialog">
2647
- <div className="config-dialog-body">
2648
- <div className="model-config-section">
2649
- <div className="model-config-section-header">Default chat mode</div>
2650
- <div className="model-config-section-body">
2651
- <div className="model-config-section-row">
2652
- <div className="model-config-section-column">
2653
- <div>
2654
- <select
2655
- className="jp-mod-styled"
2656
- value={defaultChatMode}
2657
- onChange={event => setDefaultChatMode(event.target.value)}
2658
- >
2659
- <option value="ask">Ask</option>
2660
- <option value="agent">Agent</option>
2661
- </select>
2662
- </div>
2663
- </div>
2664
- <div className="model-config-section-column"> </div>
2665
- </div>
2666
- </div>
2667
- </div>
2668
-
2669
- <div className="model-config-section">
2670
- <div className="model-config-section-header">Chat model</div>
2671
- <div className="model-config-section-body">
2672
- <div className="model-config-section-row">
2673
- <div className="model-config-section-column">
2674
- <div>Provider</div>
2675
- <div>
2676
- <select
2677
- className="jp-mod-styled"
2678
- onChange={event =>
2679
- updateModelOptionsForProvider(event.target.value, 'chat')
2680
- }
2681
- >
2682
- {llmProviders.map((provider: any, index: number) => (
2683
- <option
2684
- key={index}
2685
- value={provider.id}
2686
- selected={provider.id === chatModelProvider}
2687
- >
2688
- {provider.name}
2689
- </option>
2690
- ))}
2691
- <option
2692
- key={-1}
2693
- value="none"
2694
- selected={
2695
- chatModelProvider === 'none' ||
2696
- !llmProviders.find(
2697
- provider => provider.id === chatModelProvider
2698
- )
2699
- }
2700
- >
2701
- None
2702
- </option>
2703
- </select>
2704
- </div>
2705
- </div>
2706
- {!['openai-compatible', 'litellm-compatible', 'none'].includes(
2707
- chatModelProvider
2708
- ) &&
2709
- chatModels.length > 0 && (
2710
- <div className="model-config-section-column">
2711
- <div>Model</div>
2712
- {![
2713
- OPENAI_COMPATIBLE_CHAT_MODEL_ID,
2714
- LITELLM_COMPATIBLE_CHAT_MODEL_ID
2715
- ].includes(chatModel) &&
2716
- chatModels.length > 0 && (
2717
- <div>
2718
- <select
2719
- className="jp-mod-styled"
2720
- onChange={event => setChatModel(event.target.value)}
2721
- >
2722
- {chatModels.map((model: any, index: number) => (
2723
- <option
2724
- key={index}
2725
- value={model.id}
2726
- selected={model.id === chatModel}
2727
- >
2728
- {model.name}
2729
- </option>
2730
- ))}
2731
- </select>
2732
- </div>
2733
- )}
2734
- </div>
2735
- )}
2736
- </div>
2737
-
2738
- <div className="model-config-section-row">
2739
- <div className="model-config-section-column">
2740
- {chatModelProvider === 'ollama' && chatModels.length === 0 && (
2741
- <div className="ollama-warning-message">
2742
- No Ollama models found! Make sure{' '}
2743
- <a href="https://ollama.com/" target="_blank">
2744
- Ollama
2745
- </a>{' '}
2746
- is running and models are downloaded to your computer.{' '}
2747
- <a
2748
- href="javascript:void(0)"
2749
- onClick={handleRefreshOllamaModelListClick}
2750
- >
2751
- Try again
2752
- </a>{' '}
2753
- once ready.
2754
- </div>
2755
- )}
2756
- </div>
2757
- </div>
2758
-
2759
- <div className="model-config-section-row">
2760
- <div className="model-config-section-column">
2761
- {chatModelProperties.map((property: any, index: number) => (
2762
- <div className="form-field-row" key={index}>
2763
- <div className="form-field-description">
2764
- {property.name} {property.optional ? '(optional)' : ''}
2765
- </div>
2766
- <input
2767
- name="chat-model-id-input"
2768
- placeholder={property.description}
2769
- className="jp-mod-styled"
2770
- spellCheck={false}
2771
- value={property.value}
2772
- onChange={event =>
2773
- onModelPropertyChange(
2774
- 'chat',
2775
- property.id,
2776
- event.target.value
2777
- )
2778
- }
2779
- />
2780
- </div>
2781
- ))}
2782
- </div>
2783
- </div>
2784
- </div>
2785
- </div>
2786
-
2787
- <div className="model-config-section">
2788
- <div className="model-config-section-header">Auto-complete model</div>
2789
- <div className="model-config-section-body">
2790
- <div className="model-config-section-row">
2791
- <div className="model-config-section-column">
2792
- <div>Provider</div>
2793
- <div>
2794
- <select
2795
- className="jp-mod-styled"
2796
- onChange={event =>
2797
- updateModelOptionsForProvider(
2798
- event.target.value,
2799
- 'inline-completion'
2800
- )
2801
- }
2802
- >
2803
- {llmProviders.map((provider: any, index: number) => (
2804
- <option
2805
- key={index}
2806
- value={provider.id}
2807
- selected={provider.id === inlineCompletionModelProvider}
2808
- >
2809
- {provider.name}
2810
- </option>
2811
- ))}
2812
- <option
2813
- key={-1}
2814
- value="none"
2815
- selected={
2816
- inlineCompletionModelProvider === 'none' ||
2817
- !llmProviders.find(
2818
- provider =>
2819
- provider.id === inlineCompletionModelProvider
2820
- )
2821
- }
2822
- >
2823
- None
2824
- </option>
2825
- </select>
2826
- </div>
2827
- </div>
2828
- {!['openai-compatible', 'litellm-compatible', 'none'].includes(
2829
- inlineCompletionModelProvider
2830
- ) && (
2831
- <div className="model-config-section-column">
2832
- <div>Model</div>
2833
- {![
2834
- OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID,
2835
- LITELLM_COMPATIBLE_INLINE_COMPLETION_MODEL_ID
2836
- ].includes(inlineCompletionModel) && (
2837
- <div>
2838
- <select
2839
- className="jp-mod-styled"
2840
- onChange={event =>
2841
- setInlineCompletionModel(event.target.value)
2842
- }
2843
- >
2844
- {inlineCompletionModels.map(
2845
- (model: any, index: number) => (
2846
- <option
2847
- key={index}
2848
- value={model.id}
2849
- selected={model.id === inlineCompletionModel}
2850
- >
2851
- {model.name}
2852
- </option>
2853
- )
2854
- )}
2855
- </select>
2856
- </div>
2857
- )}
2858
- </div>
2859
- )}
2860
- </div>
2861
-
2862
- <div className="model-config-section-row">
2863
- <div className="model-config-section-column">
2864
- {inlineCompletionModelProperties.map(
2865
- (property: any, index: number) => (
2866
- <div className="form-field-row" key={index}>
2867
- <div className="form-field-description">
2868
- {property.name} {property.optional ? '(optional)' : ''}
2869
- </div>
2870
- <input
2871
- name="inline-completion-model-id-input"
2872
- placeholder={property.description}
2873
- className="jp-mod-styled"
2874
- spellCheck={false}
2875
- value={property.value}
2876
- onChange={event =>
2877
- onModelPropertyChange(
2878
- 'inline-completion',
2879
- property.id,
2880
- event.target.value
2881
- )
2882
- }
2883
- />
2884
- </div>
2885
- )
2886
- )}
2887
- </div>
2888
- </div>
2889
- </div>
2567
+ <div className="form-input-dialog-body">
2568
+ <div className="form-input-dialog-body-content">
2569
+ <div className="form-input-dialog-body-content-title">
2570
+ {props.title}
2890
2571
  </div>
2891
-
2892
- {(chatModelProvider === 'github-copilot' ||
2893
- inlineCompletionModelProvider === 'github-copilot') && (
2894
- <div className="model-config-section">
2895
- <div className="model-config-section-header access-token-config-header">
2896
- GitHub Copilot login{' '}
2897
- <a
2898
- href="https://github.com/notebook-intelligence/notebook-intelligence/blob/main/README.md#remembering-github-copilot-login"
2899
- target="_blank"
2900
- >
2901
- {' '}
2902
- <VscWarning
2903
- className="access-token-warning"
2904
- title="Click to learn more about security implications"
2905
- />
2906
- </a>
2907
- </div>
2908
- <div className="model-config-section-body">
2909
- <div className="model-config-section-row">
2910
- <div className="model-config-section-column">
2911
- <label>
2912
- <input
2913
- type="checkbox"
2914
- checked={storeGitHubAccessToken}
2915
- onChange={event => {
2916
- setStoreGitHubAccessToken(event.target.checked);
2917
- }}
2918
- />
2919
- Remember my GitHub Copilot access token
2920
- </label>
2921
- </div>
2922
- </div>
2923
- </div>
2924
- </div>
2925
- )}
2926
-
2927
- <div className="model-config-section">
2928
- <div className="model-config-section-header">
2929
- MCP Servers ({mcpServerNames.length}) [
2930
- <a href="javascript:void(0)" onClick={props.onEditMCPConfigClicked}>
2931
- edit
2932
- </a>
2933
- ]
2934
- </div>
2935
- <div className="model-config-section-body">
2936
- <div className="model-config-section-row">
2937
- <div className="model-config-section-column">
2938
- {mcpServerNames.length === 0 && (
2939
- <div>
2940
- No MCP servers found. Add MCP servers in the configuration
2941
- file.
2942
- </div>
2943
- )}
2944
- {mcpServerNames.length > 0 && (
2945
- <div>{mcpServerNames.sort().join(', ')}</div>
2946
- )}
2947
- </div>
2948
- <div
2949
- className="model-config-section-column"
2950
- style={{ flexGrow: 'initial' }}
2572
+ <div className="form-input-dialog-body-content-fields">
2573
+ {props.fields.map((field: any) => (
2574
+ <div
2575
+ className="form-input-dialog-body-content-field"
2576
+ key={field.name}
2577
+ >
2578
+ <label
2579
+ className="form-input-dialog-body-content-field-label jp-mod-styled"
2580
+ htmlFor={field.name}
2951
2581
  >
2952
- <button
2953
- className="jp-Dialog-button jp-mod-reject jp-mod-styled"
2954
- onClick={handleReloadMCPServersClick}
2955
- >
2956
- <div className="jp-Dialog-buttonLabel">Reload</div>
2957
- </button>
2958
- </div>
2582
+ {field.name}
2583
+ {field.required ? ' (required)' : ''}
2584
+ </label>
2585
+ <input
2586
+ className="form-input-dialog-body-content-field-input jp-mod-styled"
2587
+ type={field.type}
2588
+ id={field.name}
2589
+ name={field.name}
2590
+ onChange={handleInputChange}
2591
+ value={formData[field.name] || ''}
2592
+ />
2959
2593
  </div>
2960
- </div>
2594
+ ))}
2961
2595
  </div>
2962
-
2963
- <div className="model-config-section">
2964
- <div className="model-config-section-header">Config file path</div>
2965
- <div className="model-config-section-body">
2966
- <div className="model-config-section-row">
2967
- <div className="model-config-section-column">
2968
- <span
2969
- className="user-code-span"
2970
- onClick={() => {
2971
- navigator.clipboard.writeText(
2972
- path.join(NBIAPI.config.userConfigDir, 'config.json')
2973
- );
2974
- return true;
2975
- }}
2976
- >
2977
- {path.join(NBIAPI.config.userConfigDir, 'config.json')}{' '}
2978
- <span
2979
- className="copy-icon"
2980
- dangerouslySetInnerHTML={{ __html: copySvgstr }}
2981
- ></span>
2982
- </span>
2983
- </div>
2984
- </div>
2985
- </div>
2986
- <div className="model-config-section-header">
2987
- MCP config file path
2988
- </div>
2989
- <div className="model-config-section-body">
2990
- <div className="model-config-section-row">
2991
- <div className="model-config-section-column">
2992
- <span
2993
- className="user-code-span"
2994
- onClick={() => {
2995
- navigator.clipboard.writeText(
2996
- path.join(NBIAPI.config.userConfigDir, 'mcp.json')
2997
- );
2998
- return true;
2999
- }}
3000
- >
3001
- {path.join(NBIAPI.config.userConfigDir, 'mcp.json')}{' '}
3002
- <span
3003
- className="copy-icon"
3004
- dangerouslySetInnerHTML={{ __html: copySvgstr }}
3005
- ></span>
3006
- </span>
3007
- </div>
3008
- </div>
2596
+ <div>
2597
+ <div style={{ marginTop: '10px' }}>
2598
+ <button
2599
+ className="jp-Dialog-button jp-mod-accept jp-mod-styled"
2600
+ onClick={() => props.onDone(formData)}
2601
+ >
2602
+ <div className="jp-Dialog-buttonLabel">Done</div>
2603
+ </button>
3009
2604
  </div>
3010
2605
  </div>
3011
2606
  </div>
3012
-
3013
- <div className="config-dialog-footer">
3014
- <button
3015
- className="jp-Dialog-button jp-mod-accept jp-mod-styled"
3016
- onClick={handleSaveClick}
3017
- >
3018
- <div className="jp-Dialog-buttonLabel">Save</div>
3019
- </button>
3020
- </div>
3021
2607
  </div>
3022
2608
  );
3023
2609
  }