hdsp-jupyter-extension 2.0.22__py3-none-any.whl → 2.0.25__py3-none-any.whl

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 (55) hide show
  1. agent_server/context_providers/__init__.py +22 -0
  2. agent_server/context_providers/actions.py +45 -0
  3. agent_server/context_providers/base.py +231 -0
  4. agent_server/context_providers/file.py +316 -0
  5. agent_server/context_providers/processor.py +150 -0
  6. agent_server/langchain/models/gpt_oss_chat.py +51 -32
  7. agent_server/main.py +2 -1
  8. agent_server/routers/chat.py +61 -10
  9. agent_server/routers/context.py +168 -0
  10. agent_server/routers/langchain_agent.py +609 -182
  11. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  12. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  13. hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.96745acc14125453fba8.js → hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js +245 -121
  14. hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js.map +1 -0
  15. jupyter_ext/labextension/static/lib_index_js.90f80cb80187de8c5ae5.js → hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js +589 -45
  16. hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js.map +1 -0
  17. hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.8496e8475f1bd164669b.js → hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.ffc2b4bc8e6cb300e1e1.js +3 -3
  18. jupyter_ext/labextension/static/remoteEntry.8496e8475f1bd164669b.js.map → hdsp_jupyter_extension-2.0.25.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.ffc2b4bc8e6cb300e1e1.js.map +1 -1
  19. {hdsp_jupyter_extension-2.0.22.dist-info → hdsp_jupyter_extension-2.0.25.dist-info}/METADATA +1 -1
  20. {hdsp_jupyter_extension-2.0.22.dist-info → hdsp_jupyter_extension-2.0.25.dist-info}/RECORD +51 -45
  21. jupyter_ext/_version.py +1 -1
  22. jupyter_ext/handlers.py +29 -0
  23. jupyter_ext/labextension/build_log.json +1 -1
  24. jupyter_ext/labextension/package.json +2 -2
  25. jupyter_ext/labextension/static/{frontend_styles_index_js.96745acc14125453fba8.js → frontend_styles_index_js.b5e4416b4e07ec087aad.js} +245 -121
  26. jupyter_ext/labextension/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js.map +1 -0
  27. hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.90f80cb80187de8c5ae5.js → jupyter_ext/labextension/static/lib_index_js.67505497667f9c0a763d.js +589 -45
  28. jupyter_ext/labextension/static/lib_index_js.67505497667f9c0a763d.js.map +1 -0
  29. jupyter_ext/labextension/static/{remoteEntry.8496e8475f1bd164669b.js → remoteEntry.ffc2b4bc8e6cb300e1e1.js} +3 -3
  30. hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.8496e8475f1bd164669b.js.map → jupyter_ext/labextension/static/remoteEntry.ffc2b4bc8e6cb300e1e1.js.map +1 -1
  31. hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.96745acc14125453fba8.js.map +0 -1
  32. hdsp_jupyter_extension-2.0.22.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.90f80cb80187de8c5ae5.js.map +0 -1
  33. jupyter_ext/labextension/static/frontend_styles_index_js.96745acc14125453fba8.js.map +0 -1
  34. jupyter_ext/labextension/static/lib_index_js.90f80cb80187de8c5ae5.js.map +0 -1
  35. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  36. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  37. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +0 -0
  38. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +0 -0
  39. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +0 -0
  40. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +0 -0
  41. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  42. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +0 -0
  43. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +0 -0
  44. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
  45. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
  46. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
  47. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
  48. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  49. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +0 -0
  50. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  51. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  52. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
  53. {hdsp_jupyter_extension-2.0.22.data → hdsp_jupyter_extension-2.0.25.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
  54. {hdsp_jupyter_extension-2.0.22.dist-info → hdsp_jupyter_extension-2.0.25.dist-info}/WHEEL +0 -0
  55. {hdsp_jupyter_extension-2.0.22.dist-info → hdsp_jupyter_extension-2.0.25.dist-info}/licenses/LICENSE +0 -0
@@ -22,10 +22,11 @@ __webpack_require__.r(__webpack_exports__);
22
22
  /* harmony import */ var _utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/markdownRenderer */ "./lib/utils/markdownRenderer.js");
23
23
  /* harmony import */ var _FileSelectionDialog__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./FileSelectionDialog */ "./lib/components/FileSelectionDialog.js");
24
24
  /* harmony import */ var _StreamingMessage__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./StreamingMessage */ "./lib/components/StreamingMessage.js");
25
- /* harmony import */ var _utils_icons__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../utils/icons */ "./lib/utils/icons.js");
26
- /* harmony import */ var _mui_icons_material_ExpandLess__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @mui/icons-material/ExpandLess */ "./node_modules/@mui/icons-material/esm/ExpandLess.js");
27
- /* harmony import */ var _mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @mui/icons-material/ExpandMore */ "./node_modules/@mui/icons-material/esm/ExpandMore.js");
28
- /* harmony import */ var _logoSvg__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../logoSvg */ "./lib/logoSvg.js");
25
+ /* harmony import */ var _ContextAutocomplete__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./ContextAutocomplete */ "./lib/components/ContextAutocomplete.js");
26
+ /* harmony import */ var _utils_icons__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../utils/icons */ "./lib/utils/icons.js");
27
+ /* harmony import */ var _mui_icons_material_ExpandLess__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @mui/icons-material/ExpandLess */ "./node_modules/@mui/icons-material/esm/ExpandLess.js");
28
+ /* harmony import */ var _mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @mui/icons-material/ExpandMore */ "./node_modules/@mui/icons-material/esm/ExpandMore.js");
29
+ /* harmony import */ var _logoSvg__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ../logoSvg */ "./lib/logoSvg.js");
29
30
  /**
30
31
  * Agent Panel - Main sidebar panel for Jupyter Agent
31
32
  * Cursor AI Style: Unified Chat + Agent Interface
@@ -41,12 +42,13 @@ __webpack_require__.r(__webpack_exports__);
41
42
 
42
43
 
43
44
 
45
+
44
46
  // 로고 이미지 (SVG) - TypeScript 모듈에서 인라인 문자열로 import
45
47
 
46
48
  // 탭바 아이콘 생성
47
49
  const hdspTabIcon = new _jupyterlab_ui_components__WEBPACK_IMPORTED_MODULE_1__.LabIcon({
48
50
  name: 'hdsp-agent:tab-icon',
49
- svgstr: _logoSvg__WEBPACK_IMPORTED_MODULE_11__.tabbarLogoSvg
51
+ svgstr: _logoSvg__WEBPACK_IMPORTED_MODULE_12__.tabbarLogoSvg
50
52
  });
51
53
  // ═══════════════════════════════════════════════════════════════════════════
52
54
  // Python 파일 에러 감지 및 처리 유틸리티
@@ -179,6 +181,11 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
179
181
  // File selection state
180
182
  const [fileSelectionMetadata, setFileSelectionMetadata] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
181
183
  const [pendingAgentRequest, setPendingAgentRequest] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
184
+ // Context autocomplete state (@file)
185
+ const [showAutocomplete, setShowAutocomplete] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
186
+ const [hasAutocompleteOptions, setHasAutocompleteOptions] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
187
+ const [cursorPosition, setCursorPosition] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(0);
188
+ const textareaRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
182
189
  // Human-in-the-Loop state
183
190
  const [debugStatus, setDebugStatusRaw] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
184
191
  const [isDebugExpanded, setIsDebugExpanded] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
@@ -892,7 +899,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
892
899
  const successMessage = {
893
900
  id: makeMessageId('apply-success'),
894
901
  role: 'assistant',
895
- content: `${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.success} **${fix.path}** 파일이 수정되었습니다.\n\n파일 에디터에서 변경사항을 확인하세요.`,
902
+ content: `✅ **${fix.path}** 파일이 수정되었습니다.\n\n파일 에디터에서 변경사항을 확인하세요.`,
896
903
  timestamp: Date.now(),
897
904
  };
898
905
  setMessages(prev => [...prev, successMessage]);
@@ -908,7 +915,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
908
915
  const errorMessage = {
909
916
  id: makeMessageId('error'),
910
917
  role: 'assistant',
911
- content: `${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.warning} ${message}`,
918
+ content: `⚠️ ${message}`,
912
919
  timestamp: Date.now(),
913
920
  };
914
921
  setMessages(prev => [...prev, errorMessage]);
@@ -1013,26 +1020,66 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1013
1020
  showNotification('작업이 중단되었습니다.', 'info');
1014
1021
  console.log('[AgentPanel] Task stopped successfully');
1015
1022
  };
1023
+ // Auto-scroll state refs for smooth scrolling
1024
+ const scrollRafRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
1025
+ const lastScrollTimeRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(0);
1026
+ const userScrolledUpRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);
1027
+ // Track user scroll to detect if they scrolled up
1028
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
1029
+ const container = messagesContainerRef.current;
1030
+ if (!container)
1031
+ return;
1032
+ const handleScroll = () => {
1033
+ const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
1034
+ userScrolledUpRef.current = !isNearBottom;
1035
+ };
1036
+ container.addEventListener('scroll', handleScroll, { passive: true });
1037
+ return () => container.removeEventListener('scroll', handleScroll);
1038
+ }, []);
1016
1039
  // Auto-scroll to bottom when messages change or streaming (only if near bottom)
1017
1040
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
1018
1041
  const container = messagesContainerRef.current;
1019
1042
  if (!container)
1020
1043
  return;
1044
+ // If user scrolled up, don't auto-scroll
1045
+ if (userScrolledUpRef.current && isStreaming) {
1046
+ return;
1047
+ }
1021
1048
  // Check if user is near bottom (within 100px threshold)
1022
1049
  const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
1023
- if (isNearBottom) {
1050
+ if (isNearBottom || !isStreaming) {
1024
1051
  if (isStreaming) {
1025
- // 스트리밍 중에는 즉시 스크롤 (scrollTop 사용으로 jitter 방지)
1026
- container.scrollTop = container.scrollHeight;
1052
+ // 스트리밍 중: requestAnimationFrame으로 부드럽게 스크롤
1053
+ // 이전 RAF가 있으면 취소하고 새로 스케줄링
1054
+ if (scrollRafRef.current) {
1055
+ cancelAnimationFrame(scrollRafRef.current);
1056
+ }
1057
+ scrollRafRef.current = requestAnimationFrame(() => {
1058
+ const now = Date.now();
1059
+ // 최소 16ms 간격 유지 (60fps)
1060
+ if (now - lastScrollTimeRef.current >= 16) {
1061
+ container.scrollTop = container.scrollHeight;
1062
+ lastScrollTimeRef.current = now;
1063
+ }
1064
+ scrollRafRef.current = null;
1065
+ });
1027
1066
  }
1028
- else {
1067
+ else if (isNearBottom) {
1029
1068
  // 일반 메시지 추가 시 부드러운 스크롤
1030
1069
  container.scrollTo({
1031
1070
  top: container.scrollHeight,
1032
1071
  behavior: 'smooth'
1033
1072
  });
1073
+ // 새 메시지 추가 시 userScrolledUp 리셋
1074
+ userScrolledUpRef.current = false;
1034
1075
  }
1035
1076
  }
1077
+ // Cleanup RAF on unmount
1078
+ return () => {
1079
+ if (scrollRafRef.current) {
1080
+ cancelAnimationFrame(scrollRafRef.current);
1081
+ }
1082
+ };
1036
1083
  }, [messages, isStreaming]);
1037
1084
  const handleNextItemSelection = async (nextText) => {
1038
1085
  const trimmed = nextText.trim();
@@ -1124,6 +1171,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1124
1171
  // Agent V2 모드 또는 그 외: LangChain Deep Agent (sendAgentV2Stream)
1125
1172
  if (inputMode === 'chat') {
1126
1173
  // 단순 Chat 모드 - /chat/stream 사용
1174
+ // Create AbortController for chat streaming
1175
+ abortControllerRef.current = new AbortController();
1127
1176
  await apiService.sendChatStream({
1128
1177
  message: messageToSend,
1129
1178
  conversationId: conversationId || undefined,
@@ -1141,7 +1190,9 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1141
1190
  if (metadata.conversationId && !conversationId) {
1142
1191
  setConversationId(metadata.conversationId);
1143
1192
  }
1144
- });
1193
+ },
1194
+ // AbortSignal for stopping the stream
1195
+ abortControllerRef.current.signal);
1145
1196
  }
1146
1197
  else {
1147
1198
  // Agent V2 모드 - /agent/langchain/stream 사용 (HITL, Todo, 도구 실행)
@@ -1207,7 +1258,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1207
1258
  const question = interrupt.args?.question || '응답을 입력하세요...';
1208
1259
  const options = interrupt.args?.options;
1209
1260
  const inputType = interrupt.args?.input_type || 'text';
1210
- let questionContent = `${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.question} **${question}**`;
1261
+ let questionContent = `❓ **${question}**`;
1211
1262
  if (options && options.length > 0) {
1212
1263
  questionContent += '\n\n선택지:\n' + options.map((opt, i) => `${i + 1}. ${opt}`).join('\n');
1213
1264
  }
@@ -1284,20 +1335,34 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1284
1335
  }
1285
1336
  }
1286
1337
  catch (error) {
1287
- const message = error instanceof Error ? error.message : 'Failed to send message';
1288
- setDebugStatus(`오류: ${message}`);
1289
- // Update the assistant message with error
1290
- setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1291
- ? {
1292
- ...msg,
1293
- content: streamedContent + `\n\nError: ${message}`
1338
+ // Handle abort error gracefully (user clicked stop)
1339
+ if (error instanceof Error && error.name === 'AbortError') {
1340
+ console.log('[AgentPanel] Chat stream stopped by user');
1341
+ setDebugStatus(null);
1342
+ // Keep the streamed content as-is, just add a stopped indicator
1343
+ if (streamedContent) {
1344
+ setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1345
+ ? { ...msg, content: streamedContent + '\n\n*(응답 중단됨)*' }
1346
+ : msg));
1294
1347
  }
1295
- : msg));
1348
+ }
1349
+ else {
1350
+ const message = error instanceof Error ? error.message : 'Failed to send message';
1351
+ setDebugStatus(`오류: ${message}`);
1352
+ // Update the assistant message with error
1353
+ setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)
1354
+ ? {
1355
+ ...msg,
1356
+ content: streamedContent + `\n\nError: ${message}`
1357
+ }
1358
+ : msg));
1359
+ }
1296
1360
  }
1297
1361
  finally {
1298
1362
  setIsLoading(false);
1299
1363
  setIsStreaming(false);
1300
1364
  setStreamingMessageId(null);
1365
+ abortControllerRef.current = null;
1301
1366
  // Keep completed todos visible after the run
1302
1367
  }
1303
1368
  };
@@ -2025,7 +2090,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2025
2090
  const outputMessage = {
2026
2091
  id: messageId,
2027
2092
  role: 'system',
2028
- content: `${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.terminal} ${command}\n`,
2093
+ content: `${command}\n`,
2029
2094
  timestamp: Date.now(),
2030
2095
  metadata: {
2031
2096
  kind: 'shell-output',
@@ -2488,7 +2553,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2488
2553
  const question = nextInterrupt.args?.question || '응답을 입력하세요...';
2489
2554
  const options = nextInterrupt.args?.options;
2490
2555
  const inputType = nextInterrupt.args?.input_type || 'text';
2491
- let questionContent = `${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.question} **${question}**`;
2556
+ let questionContent = `❓ **${question}**`;
2492
2557
  if (options && options.length > 0) {
2493
2558
  questionContent += '\n\n선택지:\n' + options.map((opt, i) => `${i + 1}. ${opt}`).join('\n');
2494
2559
  }
@@ -3037,7 +3102,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
3037
3102
  const question = nextInterrupt.args?.question || '응답을 입력하세요...';
3038
3103
  const options = nextInterrupt.args?.options;
3039
3104
  const inputType = nextInterrupt.args?.input_type || 'text';
3040
- let questionContent = `${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.question} **${question}**`;
3105
+ let questionContent = `❓ **${question}**`;
3041
3106
  if (options && options.length > 0) {
3042
3107
  questionContent += '\n\n선택지:\n' + options.map((opt, i) => `${i + 1}. ${opt}`).join('\n');
3043
3108
  }
@@ -3320,7 +3385,137 @@ SyntaxError: '(' was never closed
3320
3385
  }
3321
3386
  await resumeFromInterrupt(interruptData, decision);
3322
3387
  };
3388
+ // Handle input change and track cursor position for autocomplete
3389
+ const handleInputChange = (e) => {
3390
+ const newValue = e.target.value;
3391
+ const newCursorPosition = e.target.selectionStart || 0;
3392
+ setInput(newValue);
3393
+ setCursorPosition(newCursorPosition);
3394
+ // Check if we should show autocomplete (when typing @ or @file:)
3395
+ const textBeforeCursor = newValue.slice(0, newCursorPosition);
3396
+ const lastAtIndex = textBeforeCursor.lastIndexOf('@');
3397
+ if (lastAtIndex >= 0) {
3398
+ // Check if there's a space between @ and cursor (means @ is complete)
3399
+ const textAfterAt = textBeforeCursor.slice(lastAtIndex);
3400
+ if (!/\s/.test(textAfterAt) || textAfterAt.includes(':')) {
3401
+ // Show autocomplete if typing a context command
3402
+ setShowAutocomplete(true);
3403
+ }
3404
+ else {
3405
+ setShowAutocomplete(false);
3406
+ }
3407
+ }
3408
+ else {
3409
+ setShowAutocomplete(false);
3410
+ }
3411
+ };
3412
+ // Handle autocomplete selection
3413
+ // viaEnter: true = Enter key or click (final selection), false = Tab key (navigation)
3414
+ const handleAutocompleteSelect = async (option, viaEnter) => {
3415
+ // Check if this is an action command (like @reset)
3416
+ if (option.id === '@reset' && option.is_complete) {
3417
+ // Execute reset action
3418
+ await executeResetAction();
3419
+ setShowAutocomplete(false);
3420
+ setHasAutocompleteOptions(false);
3421
+ setInput('');
3422
+ return;
3423
+ }
3424
+ if (!textareaRef.current)
3425
+ return;
3426
+ const text = input;
3427
+ // Find the start of the current context command
3428
+ let start = cursorPosition;
3429
+ while (start > 0 && !/\s/.test(text[start - 1])) {
3430
+ start--;
3431
+ }
3432
+ // Build new text with the selected option
3433
+ const beforeCommand = text.slice(0, start);
3434
+ const afterCursor = text.slice(cursorPosition);
3435
+ // For Enter (final selection): add space after complete options
3436
+ // For Tab (navigation): don't add space, allow further navigation
3437
+ const addSpace = viaEnter && option.is_complete;
3438
+ const newText = beforeCommand + option.label + (addSpace ? ' ' : '') + afterCursor;
3439
+ const newCursorPosition = beforeCommand.length + option.label.length + (addSpace ? 1 : 0);
3440
+ setInput(newText);
3441
+ setCursorPosition(newCursorPosition);
3442
+ // Close autocomplete only on Enter (final selection)
3443
+ if (viaEnter) {
3444
+ setShowAutocomplete(false);
3445
+ setHasAutocompleteOptions(false);
3446
+ }
3447
+ // For Tab on directory, keep autocomplete open - it will refetch options
3448
+ // Focus and set cursor position
3449
+ setTimeout(() => {
3450
+ if (textareaRef.current) {
3451
+ textareaRef.current.focus();
3452
+ textareaRef.current.setSelectionRange(newCursorPosition, newCursorPosition);
3453
+ }
3454
+ }, 0);
3455
+ };
3456
+ // Execute @reset action
3457
+ const executeResetAction = async () => {
3458
+ try {
3459
+ // Reset both agent thread and conversation
3460
+ const threadId = agentThreadId || conversationId;
3461
+ if (threadId) {
3462
+ const result = await apiService.resetAgent(threadId);
3463
+ console.log('[AgentPanel] Reset result:', result);
3464
+ }
3465
+ // Clear local state
3466
+ setMessages([]);
3467
+ setConversationId('');
3468
+ setAgentThreadId(null);
3469
+ setIsAgentRunning(false);
3470
+ setIsLoading(false);
3471
+ setIsStreaming(false);
3472
+ setTodos([]);
3473
+ setInterruptData(null);
3474
+ setIsRejectionMode(false);
3475
+ setIsAskUserMode(false);
3476
+ // Add system message about reset
3477
+ const resetMessage = {
3478
+ id: `reset-${Date.now()}`,
3479
+ role: 'assistant',
3480
+ content: 'Agent가 reset되었습니다. 새로운 대화를 시작하세요.',
3481
+ timestamp: Date.now(),
3482
+ };
3483
+ setMessages([resetMessage]);
3484
+ console.log('[AgentPanel] Session reset complete');
3485
+ }
3486
+ catch (error) {
3487
+ console.error('[AgentPanel] Failed to reset session:', error);
3488
+ const errorMessage = {
3489
+ id: `error-${Date.now()}`,
3490
+ role: 'assistant',
3491
+ content: `Failed to reset session: ${error}`,
3492
+ timestamp: Date.now(),
3493
+ };
3494
+ setMessages(prev => [...prev, errorMessage]);
3495
+ }
3496
+ };
3323
3497
  const handleKeyDown = (e) => {
3498
+ // If autocomplete is visible AND has options, let it handle Tab/Enter
3499
+ if (showAutocomplete && hasAutocompleteOptions) {
3500
+ // These keys are handled by ContextAutocomplete
3501
+ if (e.key === 'Tab' || e.key === 'ArrowDown' || e.key === 'ArrowUp') {
3502
+ return; // Let autocomplete handle it
3503
+ }
3504
+ if (e.key === 'Enter' && !e.shiftKey) {
3505
+ return; // Let autocomplete handle it
3506
+ }
3507
+ if (e.key === 'Escape') {
3508
+ setShowAutocomplete(false);
3509
+ setHasAutocompleteOptions(false);
3510
+ return;
3511
+ }
3512
+ }
3513
+ // Escape: Stop chat streaming (only in chat mode when streaming)
3514
+ if (e.key === 'Escape' && inputMode === 'chat' && isStreaming) {
3515
+ e.preventDefault();
3516
+ stopCurrentTask();
3517
+ return;
3518
+ }
3324
3519
  // Enter: 전송
3325
3520
  if (e.key === 'Enter' && !e.shiftKey) {
3326
3521
  e.preventDefault();
@@ -3387,7 +3582,7 @@ SyntaxError: '(' was never closed
3387
3582
  if (!debugStatus || typeof debugStatus === 'string')
3388
3583
  return '';
3389
3584
  if (debugStatus.icon) {
3390
- return (0,_utils_icons__WEBPACK_IMPORTED_MODULE_8__.getIconByName)(debugStatus.icon);
3585
+ return (0,_utils_icons__WEBPACK_IMPORTED_MODULE_9__.getIconByName)(debugStatus.icon);
3391
3586
  }
3392
3587
  return '';
3393
3588
  };
@@ -3415,7 +3610,7 @@ SyntaxError: '(' was never closed
3415
3610
  return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-panel" },
3416
3611
  showSettings && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_SettingsPanel__WEBPACK_IMPORTED_MODULE_3__.SettingsPanel, { onClose: () => setShowSettings(false), onSave: handleSaveConfig, currentConfig: llmConfig || undefined })),
3417
3612
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header" },
3418
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-logo", dangerouslySetInnerHTML: { __html: _logoSvg__WEBPACK_IMPORTED_MODULE_11__.headerLogoSvg } }),
3613
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-logo", dangerouslySetInnerHTML: { __html: _logoSvg__WEBPACK_IMPORTED_MODULE_12__.headerLogoSvg } }),
3419
3614
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-buttons" },
3420
3615
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-clear-button", onClick: clearChat, title: "\uB300\uD654 \uCD08\uAE30\uD654" },
3421
3616
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
@@ -3522,7 +3717,7 @@ SyntaxError: '(' was never closed
3522
3717
  const decision = msg.metadata?.interrupt?.decision;
3523
3718
  const autoApproved = msg.metadata?.interrupt?.autoApproved;
3524
3719
  // 자동 승인일 때는 코드블럭 헤더에 배지가 표시되므로 actionHtml에는 표시하지 않음
3525
- const resolvedIcon = autoApproved ? '' : (decision === 'reject' ? _utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.rejectCircle : _utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.doubleCheck);
3720
+ const resolvedIcon = autoApproved ? '' : (decision === 'reject' ? _utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.rejectCircle : _utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.doubleCheck);
3526
3721
  const resolvedClass = autoApproved ? '' : (decision === 'reject' ? 'jp-agent-interrupt-actions--rejected' : 'jp-agent-interrupt-actions--resolved');
3527
3722
  const actionHtml = resolved && !autoApproved
3528
3723
  ? `<div class="jp-agent-interrupt-actions ${resolvedClass}">${resolvedIcon}</div>`
@@ -3530,8 +3725,8 @@ SyntaxError: '(' was never closed
3530
3725
  ? '' // 자동 승인일 때는 actionHtml 비움 (코드블럭 헤더에 배지 표시)
3531
3726
  : `
3532
3727
  <div class="code-block-actions jp-agent-interrupt-actions">
3533
- <button class="jp-agent-interrupt-approve-btn" data-action="approve" title="승인">${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.approveCircle}</button>
3534
- <button class="jp-agent-interrupt-reject-btn" data-action="reject" title="거부">${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.rejectCircle}</button>
3728
+ <button class="jp-agent-interrupt-approve-btn" data-action="approve" title="승인">${_utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.approveCircle}</button>
3729
+ <button class="jp-agent-interrupt-reject-btn" data-action="reject" title="거부">${_utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.rejectCircle}</button>
3535
3730
  </div>
3536
3731
  `;
3537
3732
  // Get tool description from args (for jupyter_cell_tool)
@@ -3547,7 +3742,7 @@ SyntaxError: '(' was never closed
3547
3742
  }
3548
3743
  if ((isEditFile || isMultiEditFile) && editPath) {
3549
3744
  const safePath = escapeHtml(editPath);
3550
- const editIcon = _utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.edit;
3745
+ const editIcon = _utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.edit;
3551
3746
  html = html.replace(/<span class="code-block-language">[^<]*<\/span>/, `<span class="code-block-language jp-agent-interrupt-path">${editIcon} ${safePath}</span>`);
3552
3747
  }
3553
3748
  // actionHtml이 비어있지 않을 때만 추가
@@ -3574,12 +3769,12 @@ SyntaxError: '(' was never closed
3574
3769
  .replace(/\n/g, ' ') // Remove actual newlines
3575
3770
  .replace(/\s+/g, ' ') // Collapse multiple spaces
3576
3771
  .trim();
3577
- result += `<div class="jp-agent-code-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.description} ${safeDescription}</div>`;
3772
+ result += `<div class="jp-agent-code-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.description} ${safeDescription}</div>`;
3578
3773
  }
3579
3774
  // 2. 승인 메시지 (자동 승인이 아닐 때만) - 코드 블록 바로 위
3580
3775
  if (!msg.metadata?.interrupt?.autoApproved && msg.content) {
3581
3776
  const safeContent = escapeHtml(msg.content);
3582
- result += `<div class="jp-agent-interrupt-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.approvalWarning} ${safeContent}</div>`;
3777
+ result += `<div class="jp-agent-interrupt-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.approvalWarning} ${safeContent}</div>`;
3583
3778
  }
3584
3779
  // 3. 코드 블록
3585
3780
  result += codeHtml;
@@ -3664,7 +3859,7 @@ SyntaxError: '(' was never closed
3664
3859
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current" }, "\uC791\uC5C5 \uB9C8\uBB34\uB9AC \uC911...")));
3665
3860
  }
3666
3861
  else {
3667
- return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current", dangerouslySetInnerHTML: { __html: `${_utils_icons__WEBPACK_IMPORTED_MODULE_8__.Icons.check} 모든 작업 완료` } }));
3862
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current", dangerouslySetInnerHTML: { __html: `${_utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.check} 모든 작업 완료` } }));
3668
3863
  }
3669
3864
  })()),
3670
3865
  (isStreaming || isLoading || isAgentRunning || todos.some(t => t.status === 'in_progress')) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-todo-stop-btn", onClick: (e) => {
@@ -3684,7 +3879,7 @@ SyntaxError: '(' was never closed
3684
3879
  getDebugStatusIcon() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-icon", dangerouslySetInnerHTML: { __html: getDebugStatusIcon() } })),
3685
3880
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: `jp-agent-debug-text${isDebugStatusExpandable() ? ' jp-agent-debug-text--expandable' : ''}`, onClick: isDebugStatusExpandable() ? () => setIsDebugExpanded(!isDebugExpanded) : undefined, title: isDebugStatusExpandable() ? (isDebugExpanded ? '접기' : '펼치기') : undefined },
3686
3881
  statusText,
3687
- isDebugStatusExpandable() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-expand-icon" }, isDebugExpanded ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandLess__WEBPACK_IMPORTED_MODULE_9__["default"], { sx: { fontSize: 14 } }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_10__["default"], { sx: { fontSize: 14 } }))))))),
3882
+ isDebugStatusExpandable() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-expand-icon" }, isDebugExpanded ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandLess__WEBPACK_IMPORTED_MODULE_10__["default"], { sx: { fontSize: 14 } }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_11__["default"], { sx: { fontSize: 14 } }))))))),
3688
3883
  isTodoExpanded && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-expanded" }, todos.map((todo, index) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: index, className: `jp-agent-todo-item jp-agent-todo-item--${todo.status}` },
3689
3884
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-item-indicator" },
3690
3885
  todo.status === 'completed' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "12", height: "12" },
@@ -3697,15 +3892,22 @@ SyntaxError: '(' was never closed
3697
3892
  getDebugStatusIcon() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-icon", dangerouslySetInnerHTML: { __html: getDebugStatusIcon() } })),
3698
3893
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: `jp-agent-debug-text${isDebugStatusExpandable() ? ' jp-agent-debug-text--expandable' : ''}`, onClick: isDebugStatusExpandable() ? () => setIsDebugExpanded(!isDebugExpanded) : undefined, title: isDebugStatusExpandable() ? (isDebugExpanded ? '접기' : '펼치기') : undefined },
3699
3894
  statusText,
3700
- isDebugStatusExpandable() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-expand-icon" }, isDebugExpanded ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandLess__WEBPACK_IMPORTED_MODULE_9__["default"], { sx: { fontSize: 14 } }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_10__["default"], { sx: { fontSize: 14 } }))))))),
3895
+ isDebugStatusExpandable() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-expand-icon" }, isDebugExpanded ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandLess__WEBPACK_IMPORTED_MODULE_10__["default"], { sx: { fontSize: 14 } }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_11__["default"], { sx: { fontSize: 14 } }))))))),
3701
3896
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-container" },
3702
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-wrapper" },
3703
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: `jp-agent-input ${inputMode !== 'chat' ? 'jp-agent-input--agent-mode' : ''} ${isRejectionMode ? 'jp-agent-input--rejection-mode' : ''}`, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, placeholder: isRejectionMode
3897
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-wrapper", style: { position: 'relative' } },
3898
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_ContextAutocomplete__WEBPACK_IMPORTED_MODULE_8__.ContextAutocomplete, { apiService: apiService, inputValue: input, cursorPosition: cursorPosition, baseDir: notebookTracker?.currentWidget?.context?.path ?
3899
+ notebookTracker.currentWidget.context.path.replace(/[^/]*$/, '') : undefined, onSelect: handleAutocompleteSelect, onClose: () => { setShowAutocomplete(false); setHasAutocompleteOptions(false); }, onOptionsChange: setHasAutocompleteOptions, visible: showAutocomplete, anchorEl: textareaRef.current }),
3900
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { ref: textareaRef, className: `jp-agent-input ${inputMode !== 'chat' ? 'jp-agent-input--agent-mode' : ''} ${isRejectionMode ? 'jp-agent-input--rejection-mode' : ''}`, value: input, onChange: handleInputChange, onKeyDown: handleKeyDown, onSelect: (e) => setCursorPosition(e.target.selectionStart || 0), placeholder: isRejectionMode
3704
3901
  ? '다른 방향 제시'
3705
3902
  : (inputMode === 'agent'
3706
- ? '노트북 작업을 입력하세요... (예: 데이터 시각화 해줘)'
3707
- : '메시지를 입력하세요...'), rows: 3, disabled: isLoading || isAgentRunning }),
3708
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-send-button", onClick: handleSendMessage, disabled: (!input.trim() && !isRejectionMode) || isLoading || isStreaming || isAgentRunning, title: isRejectionMode ? "거부 전송 (Enter)" : "전송 (Enter)" }, isAgentRunning ? '실행 중...' : (isRejectionMode ? '거부' : '전송'))),
3903
+ ? '노트북 작업을 입력하세요... (예: @file:path로 파일 참조)'
3904
+ : '메시지를 입력하세요... (@file:path로 파일 참조)'), rows: 3, disabled: isLoading || isAgentRunning }),
3905
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-button-container" },
3906
+ inputMode === 'chat' && isStreaming && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-stop-button", onClick: stopCurrentTask, title: "\uC751\uB2F5 \uC911\uB2E8 (Esc)" },
3907
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "14", height: "14" },
3908
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1" })),
3909
+ "\uC911\uB2E8")),
3910
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-send-button", onClick: handleSendMessage, disabled: (!input.trim() && !isRejectionMode) || isLoading || isStreaming || isAgentRunning, title: isRejectionMode ? "거부 전송 (Enter)" : "전송 (Enter)" }, isAgentRunning ? '실행 중...' : (isRejectionMode ? '거부' : '전송')))),
3709
3911
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-bar" },
3710
3912
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-toggle-container" },
3711
3913
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: `jp-agent-mode-toggle ${inputMode !== 'chat' ? 'jp-agent-mode-toggle--active' : ''}`, onClick: toggleMode, title: `${inputMode === 'chat' ? 'Chat' : 'Agent'} 모드 (⇧Tab)` },
@@ -3909,6 +4111,216 @@ ${cell.output}
3909
4111
  }
3910
4112
 
3911
4113
 
4114
+ /***/ },
4115
+
4116
+ /***/ "./lib/components/ContextAutocomplete.js"
4117
+ /*!***********************************************!*\
4118
+ !*** ./lib/components/ContextAutocomplete.js ***!
4119
+ \***********************************************/
4120
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
4121
+
4122
+ __webpack_require__.r(__webpack_exports__);
4123
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
4124
+ /* harmony export */ ContextAutocomplete: () => (/* binding */ ContextAutocomplete),
4125
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
4126
+ /* harmony export */ });
4127
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "webpack/sharing/consume/default/react");
4128
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
4129
+ /* harmony import */ var _mui_icons_material_InsertDriveFile__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @mui/icons-material/InsertDriveFile */ "./node_modules/@mui/icons-material/esm/InsertDriveFile.js");
4130
+ /* harmony import */ var _mui_icons_material_Folder__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @mui/icons-material/Folder */ "./node_modules/@mui/icons-material/esm/Folder.js");
4131
+ /* harmony import */ var _mui_icons_material_Description__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @mui/icons-material/Description */ "./node_modules/@mui/icons-material/esm/Description.js");
4132
+ /**
4133
+ * Context Autocomplete Component
4134
+ *
4135
+ * Provides autocomplete functionality for @file and other context commands.
4136
+ * Inspired by jupyter-ai's autocomplete implementation.
4137
+ */
4138
+
4139
+
4140
+
4141
+
4142
+ /**
4143
+ * Extract the word at cursor position that might be a context command.
4144
+ * Returns the partial command (e.g., "@file:" or "@file:src/")
4145
+ */
4146
+ function getPartialCommand(text, cursorPosition) {
4147
+ // Find the start of the current word
4148
+ let start = cursorPosition;
4149
+ while (start > 0 && !/\s/.test(text[start - 1])) {
4150
+ start--;
4151
+ }
4152
+ const word = text.slice(start, cursorPosition);
4153
+ // Check if it looks like a context command
4154
+ if (word.startsWith('@')) {
4155
+ return word;
4156
+ }
4157
+ return null;
4158
+ }
4159
+ /**
4160
+ * Get icon for autocomplete option
4161
+ */
4162
+ function getOptionIcon(option) {
4163
+ const description = option.description.toLowerCase();
4164
+ if (description.includes('directory') || description.includes('folder')) {
4165
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_Folder__WEBPACK_IMPORTED_MODULE_2__["default"], { sx: { fontSize: 16, color: 'var(--jp-ui-font-color2)' } });
4166
+ }
4167
+ if (description.includes('python') || description.includes('.py')) {
4168
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_InsertDriveFile__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { fontSize: 16, color: '#3572A5' } });
4169
+ }
4170
+ if (description.includes('notebook') || description.includes('.ipynb')) {
4171
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_InsertDriveFile__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { fontSize: 16, color: '#F37626' } });
4172
+ }
4173
+ if (description.includes('javascript') || description.includes('typescript')) {
4174
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_InsertDriveFile__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { fontSize: 16, color: '#f1e05a' } });
4175
+ }
4176
+ if (description.includes('markdown')) {
4177
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_Description__WEBPACK_IMPORTED_MODULE_3__["default"], { sx: { fontSize: 16, color: 'var(--jp-ui-font-color2)' } });
4178
+ }
4179
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_InsertDriveFile__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { fontSize: 16, color: 'var(--jp-ui-font-color2)' } });
4180
+ }
4181
+ const ContextAutocomplete = ({ apiService, inputValue, cursorPosition, baseDir, onSelect, onClose, onOptionsChange, visible, anchorEl, }) => {
4182
+ const [options, setOptions] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
4183
+ const [selectedIndex, setSelectedIndex] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(0);
4184
+ const [loading, setLoading] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
4185
+ const [partialCommand, setPartialCommand] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
4186
+ const containerRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
4187
+ // Fetch autocomplete options
4188
+ const fetchOptions = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(async (partial) => {
4189
+ setLoading(true);
4190
+ try {
4191
+ const response = await apiService.getContextAutocomplete({
4192
+ partialCommand: partial,
4193
+ baseDir,
4194
+ });
4195
+ setOptions(response.options);
4196
+ setSelectedIndex(0);
4197
+ }
4198
+ catch (error) {
4199
+ console.error('[ContextAutocomplete] Failed to fetch options:', error);
4200
+ setOptions([]);
4201
+ }
4202
+ finally {
4203
+ setLoading(false);
4204
+ }
4205
+ }, [apiService, baseDir]);
4206
+ // Update options when input changes
4207
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
4208
+ const partial = getPartialCommand(inputValue, cursorPosition);
4209
+ setPartialCommand(partial);
4210
+ if (partial) {
4211
+ // Debounce the fetch
4212
+ const timer = setTimeout(() => {
4213
+ fetchOptions(partial);
4214
+ }, 150);
4215
+ return () => clearTimeout(timer);
4216
+ }
4217
+ else {
4218
+ setOptions([]);
4219
+ }
4220
+ }, [inputValue, cursorPosition, fetchOptions]);
4221
+ // Notify parent when options change
4222
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
4223
+ onOptionsChange?.(options.length > 0);
4224
+ }, [options, onOptionsChange]);
4225
+ // Handle keyboard navigation
4226
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
4227
+ if (!visible || options.length === 0)
4228
+ return;
4229
+ const handleKeyDown = (e) => {
4230
+ const selectedOption = options[selectedIndex];
4231
+ switch (e.key) {
4232
+ case 'ArrowDown':
4233
+ e.preventDefault();
4234
+ setSelectedIndex(prev => (prev + 1) % options.length);
4235
+ break;
4236
+ case 'ArrowUp':
4237
+ e.preventDefault();
4238
+ setSelectedIndex(prev => (prev - 1 + options.length) % options.length);
4239
+ break;
4240
+ case 'Tab':
4241
+ // Tab: Only navigate into directories (is_complete=false)
4242
+ // On files (is_complete=true), do nothing
4243
+ if (selectedOption && !selectedOption.is_complete) {
4244
+ e.preventDefault();
4245
+ onSelect(selectedOption, false); // viaEnter=false (Tab navigation)
4246
+ }
4247
+ else {
4248
+ // On complete file, just prevent default but don't select
4249
+ e.preventDefault();
4250
+ }
4251
+ break;
4252
+ case 'Enter':
4253
+ // Enter: Select any option (directory or file) and close autocomplete
4254
+ if (selectedOption) {
4255
+ e.preventDefault();
4256
+ e.stopPropagation(); // Prevent AgentPanel from sending message
4257
+ onSelect(selectedOption, true); // viaEnter=true (final selection)
4258
+ }
4259
+ break;
4260
+ case 'Escape':
4261
+ e.preventDefault();
4262
+ onClose();
4263
+ break;
4264
+ }
4265
+ };
4266
+ document.addEventListener('keydown', handleKeyDown, true);
4267
+ return () => document.removeEventListener('keydown', handleKeyDown, true);
4268
+ }, [visible, options, selectedIndex, onSelect, onClose]);
4269
+ // Scroll selected item into view
4270
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
4271
+ if (containerRef.current && options.length > 0) {
4272
+ const selectedItem = containerRef.current.querySelector('.jp-context-autocomplete-item--selected');
4273
+ if (selectedItem) {
4274
+ selectedItem.scrollIntoView({ block: 'nearest' });
4275
+ }
4276
+ }
4277
+ }, [selectedIndex, options]);
4278
+ // Close on click outside
4279
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
4280
+ if (!visible || options.length === 0)
4281
+ return;
4282
+ const handleClickOutside = (e) => {
4283
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
4284
+ // Also check if click is on the anchor element (textarea)
4285
+ if (anchorEl && !anchorEl.contains(e.target)) {
4286
+ onClose();
4287
+ }
4288
+ }
4289
+ };
4290
+ document.addEventListener('mousedown', handleClickOutside);
4291
+ return () => document.removeEventListener('mousedown', handleClickOutside);
4292
+ }, [visible, options, anchorEl, onClose]);
4293
+ // Don't render if not visible or no partial command
4294
+ if (!visible || !partialCommand || options.length === 0) {
4295
+ return null;
4296
+ }
4297
+ // Calculate position based on anchor element
4298
+ const style = {
4299
+ position: 'absolute',
4300
+ bottom: anchorEl ? anchorEl.offsetHeight + 4 : 'auto',
4301
+ left: 0,
4302
+ right: 0,
4303
+ zIndex: 1000,
4304
+ };
4305
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { ref: containerRef, className: "jp-context-autocomplete", style: style },
4306
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-context-autocomplete-header" },
4307
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-context-autocomplete-hint" }, partialCommand.includes(':') ? 'Select a file' : 'Context commands'),
4308
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-context-autocomplete-loading" }, loading && 'Loading...')),
4309
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-context-autocomplete-list" }, options.map((option, index) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: `${option.id}-${option.label}-${index}`, className: `jp-context-autocomplete-item ${index === selectedIndex ? 'jp-context-autocomplete-item--selected' : ''}`, onClick: () => onSelect(option, true), onMouseEnter: () => setSelectedIndex(index) },
4310
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-context-autocomplete-item-icon" }, getOptionIcon(option)),
4311
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-context-autocomplete-item-label" }, option.label),
4312
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-context-autocomplete-item-description" }, option.description))))),
4313
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-context-autocomplete-footer" },
4314
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("kbd", null, "Tab"),
4315
+ " navigate, ",
4316
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("kbd", null, "Enter"),
4317
+ " select, ",
4318
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("kbd", null, "Esc"),
4319
+ " close")));
4320
+ };
4321
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ContextAutocomplete);
4322
+
4323
+
3912
4324
  /***/ },
3913
4325
 
3914
4326
  /***/ "./lib/components/FileSelectionDialog.js"
@@ -9062,7 +9474,7 @@ class ApiService {
9062
9474
  * 단순 Chat용 스트리밍 - /chat/stream 사용
9063
9475
  * 원래 main 브랜치의 구현 복원 - LangChain 에이전트 없이 단순 Q&A
9064
9476
  */
9065
- async sendChatStream(request, onChunk, onMetadata) {
9477
+ async sendChatStream(request, onChunk, onMetadata, abortSignal) {
9066
9478
  const MAX_RETRIES = 10;
9067
9479
  let currentConfig = request.llmConfig;
9068
9480
  let lastError = null;
@@ -9071,11 +9483,16 @@ class ApiService {
9071
9483
  ? { ...request, llmConfig: (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.buildSingleKeyConfig)(currentConfig) }
9072
9484
  : request;
9073
9485
  try {
9074
- await this.sendChatStreamInternal(requestToSend, onChunk, onMetadata);
9486
+ await this.sendChatStreamInternal(requestToSend, onChunk, onMetadata, abortSignal);
9075
9487
  (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.resetKeyRotation)();
9076
9488
  return;
9077
9489
  }
9078
9490
  catch (error) {
9491
+ // Check if it's an abort error
9492
+ if (error instanceof Error && error.name === 'AbortError') {
9493
+ console.log('[ApiService] Chat stream aborted by user');
9494
+ throw error;
9495
+ }
9079
9496
  const errorMsg = error instanceof Error ? error.message : String(error);
9080
9497
  lastError = error instanceof Error ? error : new Error(errorMsg);
9081
9498
  if ((0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.isRateLimitError)(errorMsg) && request.llmConfig) {
@@ -9097,12 +9514,13 @@ class ApiService {
9097
9514
  /**
9098
9515
  * 단순 Chat 스트리밍 내부 구현 - /chat/stream 엔드포인트 사용
9099
9516
  */
9100
- async sendChatStreamInternal(request, onChunk, onMetadata) {
9517
+ async sendChatStreamInternal(request, onChunk, onMetadata, abortSignal) {
9101
9518
  const response = await fetch(`${this.baseUrl}/chat/stream`, {
9102
9519
  method: 'POST',
9103
9520
  headers: this.getHeaders(),
9104
9521
  credentials: 'include',
9105
- body: JSON.stringify(request)
9522
+ body: JSON.stringify(request),
9523
+ signal: abortSignal
9106
9524
  });
9107
9525
  if (!response.ok) {
9108
9526
  const error = await response.text();
@@ -9624,6 +10042,23 @@ class ApiService {
9624
10042
  }
9625
10043
  return response.json();
9626
10044
  }
10045
+ /**
10046
+ * Reset an agent thread (clear session and recreate agent)
10047
+ */
10048
+ async resetAgent(threadId) {
10049
+ const response = await fetch(`${this.baseUrl}/agent/langchain/reset`, {
10050
+ method: 'POST',
10051
+ headers: this.getHeaders(),
10052
+ credentials: 'include',
10053
+ body: JSON.stringify({ thread_id: threadId })
10054
+ });
10055
+ if (!response.ok) {
10056
+ const error = await response.text();
10057
+ console.warn('[ApiService] Failed to reset agent:', error);
10058
+ return { status: 'error', message: error };
10059
+ }
10060
+ return response.json();
10061
+ }
9627
10062
  async executeCommand(command, timeout, cwd, stdin, workspaceRoot) {
9628
10063
  const response = await fetch(`${this.baseUrl}/execute-command`, {
9629
10064
  method: 'POST',
@@ -10045,6 +10480,49 @@ class ApiService {
10045
10480
  }
10046
10481
  return response.json();
10047
10482
  }
10483
+ // ═══════════════════════════════════════════════════════════════════════════
10484
+ // Context Provider APIs (@file autocomplete)
10485
+ // ═══════════════════════════════════════════════════════════════════════════
10486
+ /**
10487
+ * Get autocomplete options for context commands (@file, etc.)
10488
+ */
10489
+ async getContextAutocomplete(options) {
10490
+ const params = new URLSearchParams();
10491
+ if (options?.partialCommand) {
10492
+ params.append('partialCommand', options.partialCommand);
10493
+ }
10494
+ if (options?.baseDir) {
10495
+ params.append('baseDir', options.baseDir);
10496
+ }
10497
+ const url = `${this.baseUrl}/context/autocomplete${params.toString() ? '?' + params.toString() : ''}`;
10498
+ console.log('[ApiService] getContextAutocomplete:', url);
10499
+ const response = await fetch(url, {
10500
+ method: 'GET',
10501
+ headers: this.getHeaders(),
10502
+ credentials: 'include'
10503
+ });
10504
+ if (!response.ok) {
10505
+ const error = await response.text();
10506
+ throw new Error(`Failed to get autocomplete options: ${error}`);
10507
+ }
10508
+ return response.json();
10509
+ }
10510
+ /**
10511
+ * List available context providers
10512
+ */
10513
+ async listContextProviders() {
10514
+ const response = await fetch(`${this.baseUrl}/context/providers`, {
10515
+ method: 'GET',
10516
+ headers: this.getHeaders(),
10517
+ credentials: 'include'
10518
+ });
10519
+ if (!response.ok) {
10520
+ const error = await response.text();
10521
+ throw new Error(`Failed to list context providers: ${error}`);
10522
+ }
10523
+ const data = await response.json();
10524
+ return data.providers;
10525
+ }
10048
10526
  }
10049
10527
 
10050
10528
 
@@ -11657,6 +12135,28 @@ __webpack_require__.r(__webpack_exports__);
11657
12135
 
11658
12136
  /***/ },
11659
12137
 
12138
+ /***/ "./node_modules/@mui/icons-material/esm/Description.js"
12139
+ /*!*************************************************************!*\
12140
+ !*** ./node_modules/@mui/icons-material/esm/Description.js ***!
12141
+ \*************************************************************/
12142
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
12143
+
12144
+ __webpack_require__.r(__webpack_exports__);
12145
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
12146
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
12147
+ /* harmony export */ });
12148
+ /* harmony import */ var _utils_createSvgIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/createSvgIcon.js */ "./node_modules/@mui/material/utils/createSvgIcon.js");
12149
+ /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react/jsx-runtime */ "./node_modules/react/jsx-runtime.js");
12150
+ "use client";
12151
+
12152
+
12153
+
12154
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((0,_utils_createSvgIcon_js__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__.jsx)("path", {
12155
+ d: "M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8zm2 16H8v-2h8zm0-4H8v-2h8zm-3-5V3.5L18.5 9z"
12156
+ }), 'Description'));
12157
+
12158
+ /***/ },
12159
+
11660
12160
  /***/ "./node_modules/@mui/icons-material/esm/Error.js"
11661
12161
  /*!*******************************************************!*\
11662
12162
  !*** ./node_modules/@mui/icons-material/esm/Error.js ***!
@@ -11723,6 +12223,28 @@ __webpack_require__.r(__webpack_exports__);
11723
12223
 
11724
12224
  /***/ },
11725
12225
 
12226
+ /***/ "./node_modules/@mui/icons-material/esm/Folder.js"
12227
+ /*!********************************************************!*\
12228
+ !*** ./node_modules/@mui/icons-material/esm/Folder.js ***!
12229
+ \********************************************************/
12230
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
12231
+
12232
+ __webpack_require__.r(__webpack_exports__);
12233
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
12234
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
12235
+ /* harmony export */ });
12236
+ /* harmony import */ var _utils_createSvgIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/createSvgIcon.js */ "./node_modules/@mui/material/utils/createSvgIcon.js");
12237
+ /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react/jsx-runtime */ "./node_modules/react/jsx-runtime.js");
12238
+ "use client";
12239
+
12240
+
12241
+
12242
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((0,_utils_createSvgIcon_js__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__.jsx)("path", {
12243
+ d: "M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8z"
12244
+ }), 'Folder'));
12245
+
12246
+ /***/ },
12247
+
11726
12248
  /***/ "./node_modules/@mui/icons-material/esm/GpsFixed.js"
11727
12249
  /*!**********************************************************!*\
11728
12250
  !*** ./node_modules/@mui/icons-material/esm/GpsFixed.js ***!
@@ -11767,6 +12289,28 @@ __webpack_require__.r(__webpack_exports__);
11767
12289
 
11768
12290
  /***/ },
11769
12291
 
12292
+ /***/ "./node_modules/@mui/icons-material/esm/InsertDriveFile.js"
12293
+ /*!*****************************************************************!*\
12294
+ !*** ./node_modules/@mui/icons-material/esm/InsertDriveFile.js ***!
12295
+ \*****************************************************************/
12296
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
12297
+
12298
+ __webpack_require__.r(__webpack_exports__);
12299
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
12300
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
12301
+ /* harmony export */ });
12302
+ /* harmony import */ var _utils_createSvgIcon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/createSvgIcon.js */ "./node_modules/@mui/material/utils/createSvgIcon.js");
12303
+ /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react/jsx-runtime */ "./node_modules/react/jsx-runtime.js");
12304
+ "use client";
12305
+
12306
+
12307
+
12308
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((0,_utils_createSvgIcon_js__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__.jsx)("path", {
12309
+ d: "M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm7 7V3.5L18.5 9z"
12310
+ }), 'InsertDriveFile'));
12311
+
12312
+ /***/ },
12313
+
11770
12314
  /***/ "./node_modules/@mui/icons-material/esm/Lightbulb.js"
11771
12315
  /*!***********************************************************!*\
11772
12316
  !*** ./node_modules/@mui/icons-material/esm/Lightbulb.js ***!
@@ -11878,4 +12422,4 @@ __webpack_require__.r(__webpack_exports__);
11878
12422
  /***/ }
11879
12423
 
11880
12424
  }]);
11881
- //# sourceMappingURL=lib_index_js.90f80cb80187de8c5ae5.js.map
12425
+ //# sourceMappingURL=lib_index_js.67505497667f9c0a763d.js.map