hdsp-jupyter-extension 2.0.26__py3-none-any.whl → 2.0.28__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 (71) hide show
  1. agent_server/context_providers/__init__.py +4 -2
  2. agent_server/context_providers/actions.py +73 -7
  3. agent_server/context_providers/file.py +23 -23
  4. agent_server/langchain/__init__.py +2 -2
  5. agent_server/langchain/agent.py +18 -251
  6. agent_server/langchain/agent_factory.py +26 -4
  7. agent_server/langchain/agent_prompts/planner_prompt.py +22 -35
  8. agent_server/langchain/custom_middleware.py +278 -43
  9. agent_server/langchain/llm_factory.py +102 -54
  10. agent_server/langchain/logging_utils.py +1 -1
  11. agent_server/langchain/middleware/__init__.py +5 -0
  12. agent_server/langchain/middleware/code_history_middleware.py +126 -37
  13. agent_server/langchain/middleware/content_injection_middleware.py +110 -0
  14. agent_server/langchain/middleware/subagent_events.py +88 -9
  15. agent_server/langchain/middleware/subagent_middleware.py +518 -240
  16. agent_server/langchain/prompts.py +5 -22
  17. agent_server/langchain/state_schema.py +44 -0
  18. agent_server/langchain/tools/jupyter_tools.py +4 -5
  19. agent_server/langchain/tools/tool_registry.py +6 -0
  20. agent_server/routers/chat.py +305 -2
  21. agent_server/routers/config.py +193 -8
  22. agent_server/routers/config_schema.py +254 -0
  23. agent_server/routers/context.py +31 -8
  24. agent_server/routers/langchain_agent.py +310 -153
  25. hdsp_agent_core/managers/config_manager.py +100 -1
  26. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  27. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  28. hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js → hdsp_jupyter_extension-2.0.28.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.55727265b00191e68d9a.js +479 -15
  29. hdsp_jupyter_extension-2.0.28.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.55727265b00191e68d9a.js.map +1 -0
  30. jupyter_ext/labextension/static/lib_index_js.67505497667f9c0a763d.js → hdsp_jupyter_extension-2.0.28.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.df05d90f366bfd5fa023.js +1287 -190
  31. hdsp_jupyter_extension-2.0.28.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.df05d90f366bfd5fa023.js.map +1 -0
  32. hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.0fe2dcbbd176ee0efceb.js → hdsp_jupyter_extension-2.0.28.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.08fce819ee32e9d25175.js +3 -3
  33. jupyter_ext/labextension/static/remoteEntry.0fe2dcbbd176ee0efceb.js.map → hdsp_jupyter_extension-2.0.28.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.08fce819ee32e9d25175.js.map +1 -1
  34. {hdsp_jupyter_extension-2.0.26.dist-info → hdsp_jupyter_extension-2.0.28.dist-info}/METADATA +1 -1
  35. {hdsp_jupyter_extension-2.0.26.dist-info → hdsp_jupyter_extension-2.0.28.dist-info}/RECORD +66 -64
  36. jupyter_ext/_version.py +1 -1
  37. jupyter_ext/handlers.py +41 -0
  38. jupyter_ext/labextension/build_log.json +1 -1
  39. jupyter_ext/labextension/package.json +2 -2
  40. jupyter_ext/labextension/static/{frontend_styles_index_js.b5e4416b4e07ec087aad.js → frontend_styles_index_js.55727265b00191e68d9a.js} +479 -15
  41. jupyter_ext/labextension/static/frontend_styles_index_js.55727265b00191e68d9a.js.map +1 -0
  42. hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js → jupyter_ext/labextension/static/lib_index_js.df05d90f366bfd5fa023.js +1287 -190
  43. jupyter_ext/labextension/static/lib_index_js.df05d90f366bfd5fa023.js.map +1 -0
  44. jupyter_ext/labextension/static/{remoteEntry.0fe2dcbbd176ee0efceb.js → remoteEntry.08fce819ee32e9d25175.js} +3 -3
  45. hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.0fe2dcbbd176ee0efceb.js.map → jupyter_ext/labextension/static/remoteEntry.08fce819ee32e9d25175.js.map +1 -1
  46. agent_server/langchain/middleware/description_injector.py +0 -150
  47. hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js.map +0 -1
  48. hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js.map +0 -1
  49. jupyter_ext/labextension/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js.map +0 -1
  50. jupyter_ext/labextension/static/lib_index_js.67505497667f9c0a763d.js.map +0 -1
  51. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  52. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  53. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  54. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  55. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  56. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  57. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  58. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  59. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  60. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
  61. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  62. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
  63. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  64. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  65. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.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
  66. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  67. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  68. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
  69. {hdsp_jupyter_extension-2.0.26.data → hdsp_jupyter_extension-2.0.28.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
  70. {hdsp_jupyter_extension-2.0.26.dist-info → hdsp_jupyter_extension-2.0.28.dist-info}/WHEEL +0 -0
  71. {hdsp_jupyter_extension-2.0.26.dist-info → hdsp_jupyter_extension-2.0.28.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,560 @@
1
1
  "use strict";
2
2
  (self["webpackChunkhdsp_agent"] = self["webpackChunkhdsp_agent"] || []).push([["lib_index_js"],{
3
3
 
4
+ /***/ "./lib/components/AdminSettingsPanel.js"
5
+ /*!**********************************************!*\
6
+ !*** ./lib/components/AdminSettingsPanel.js ***!
7
+ \**********************************************/
8
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
9
+
10
+ __webpack_require__.r(__webpack_exports__);
11
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+ /* harmony export */ AdminSettingsPanel: () => (/* binding */ AdminSettingsPanel)
13
+ /* harmony export */ });
14
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "webpack/sharing/consume/default/react");
15
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
16
+ /* harmony import */ var _mui_icons_material_CheckCircle__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @mui/icons-material/CheckCircle */ "./node_modules/@mui/icons-material/esm/CheckCircle.js");
17
+ /* harmony import */ var _mui_icons_material_Error__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @mui/icons-material/Error */ "./node_modules/@mui/icons-material/esm/Error.js");
18
+ /* harmony import */ var _mui_icons_material_HourglassEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @mui/icons-material/HourglassEmpty */ "./node_modules/@mui/icons-material/esm/HourglassEmpty.js");
19
+ /* harmony import */ var _mui_icons_material_Code__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @mui/icons-material/Code */ "./node_modules/@mui/icons-material/esm/Code.js");
20
+ /* harmony import */ var _mui_icons_material_Search__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @mui/icons-material/Search */ "./node_modules/@mui/icons-material/esm/Search.js");
21
+ /* harmony import */ var _mui_icons_material_Analytics__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @mui/icons-material/Analytics */ "./node_modules/@mui/icons-material/esm/Analytics.js");
22
+ /* harmony import */ var _mui_icons_material_GpsFixed__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @mui/icons-material/GpsFixed */ "./node_modules/@mui/icons-material/esm/GpsFixed.js");
23
+ /* harmony import */ var _mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @mui/icons-material/ExpandMore */ "./node_modules/@mui/icons-material/esm/ExpandMore.js");
24
+ /* harmony import */ var _mui_icons_material_ChevronRight__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @mui/icons-material/ChevronRight */ "./node_modules/@mui/icons-material/esm/ChevronRight.js");
25
+ /* harmony import */ var _services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../services/ApiKeyManager */ "./lib/services/ApiKeyManager.js");
26
+ /**
27
+ * Admin Settings Panel Component
28
+ *
29
+ * Comprehensive configuration for administrators:
30
+ * - LLM Provider settings (API keys, models, endpoints)
31
+ * - Summarization LLM settings
32
+ * - Embedding configuration
33
+ * - RAG/Qdrant settings
34
+ * - User preferences (also editable by admin)
35
+ * - Agent prompts
36
+ * - Server settings
37
+ */
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+ const AdminSettingsPanel = ({ onClose, onSave, apiService }) => {
50
+ const [isLoading, setIsLoading] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(true);
51
+ const [isSaving, setIsSaving] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
52
+ const [error, setError] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
53
+ // Collapsible sections
54
+ const [expandedSections, setExpandedSections] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({
55
+ llm: true,
56
+ summarization: false,
57
+ embedding: false,
58
+ rag: false,
59
+ user: true,
60
+ server: false,
61
+ prompts: false
62
+ });
63
+ // ═══════════════════════════════════════════════════════════════════════════
64
+ // LLM Provider Settings
65
+ // ═══════════════════════════════════════════════════════════════════════════
66
+ const [provider, setProvider] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('gemini');
67
+ // Gemini
68
+ const [geminiApiKeys, setGeminiApiKeys] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(['']);
69
+ const [geminiModel, setGeminiModel] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('gemini-2.5-flash');
70
+ // vLLM / OpenRouter
71
+ const [vllmEndpoint, setVllmEndpoint] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('https://openrouter.ai/api/v1');
72
+ const [vllmApiKey, setVllmApiKey] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
73
+ const [vllmModel, setVllmModel] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('openai/gpt-4o');
74
+ const [vllmUseResponsesApi, setVllmUseResponsesApi] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
75
+ // OpenAI
76
+ const [openaiApiKey, setOpenaiApiKey] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
77
+ const [openaiModel, setOpenaiModel] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('gpt-4o');
78
+ // ═══════════════════════════════════════════════════════════════════════════
79
+ // Summarization LLM
80
+ // ═══════════════════════════════════════════════════════════════════════════
81
+ const [summarizationEnabled, setSummarizationEnabled] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
82
+ const [summarizationProvider, setSummarizationProvider] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('gemini');
83
+ const [summarizationModel, setSummarizationModel] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
84
+ const [summarizationEndpoint, setSummarizationEndpoint] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
85
+ const [summarizationApiKey, setSummarizationApiKey] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
86
+ // ═══════════════════════════════════════════════════════════════════════════
87
+ // Embedding Settings
88
+ // ═══════════════════════════════════════════════════════════════════════════
89
+ const [embeddingProvider, setEmbeddingProvider] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('openai');
90
+ const [embeddingModel, setEmbeddingModel] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('text-embedding-3-small');
91
+ const [embeddingApiKey, setEmbeddingApiKey] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
92
+ const [embeddingEndpoint, setEmbeddingEndpoint] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
93
+ // ═══════════════════════════════════════════════════════════════════════════
94
+ // RAG Settings
95
+ // ═══════════════════════════════════════════════════════════════════════════
96
+ const [qdrantUrl, setQdrantUrl] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('http://localhost:6333');
97
+ // ═══════════════════════════════════════════════════════════════════════════
98
+ // User Settings (also editable by admin)
99
+ // ═══════════════════════════════════════════════════════════════════════════
100
+ const [workspaceRoot, setWorkspaceRoot] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
101
+ const [temperature, setTemperature] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(0.7);
102
+ const [autoApprove, setAutoApprove] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
103
+ // ═══════════════════════════════════════════════════════════════════════════
104
+ // Server Settings
105
+ // ═══════════════════════════════════════════════════════════════════════════
106
+ const [agentServerUrl, setAgentServerUrl] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('http://localhost:8000');
107
+ const [agentServerTimeout, setAgentServerTimeout] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(120);
108
+ const [idleTimeout, setIdleTimeout] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(300);
109
+ // ═══════════════════════════════════════════════════════════════════════════
110
+ // Agent Prompts
111
+ // ═══════════════════════════════════════════════════════════════════════════
112
+ const [agentPrompts, setAgentPrompts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
113
+ const [expandedAgents, setExpandedAgents] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
114
+ const [defaultPrompts, setDefaultPrompts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)((0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__.getCachedPrompts)());
115
+ const [isLoadingPrompts, setIsLoadingPrompts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(!(0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__.getCachedPrompts)());
116
+ // Test state
117
+ const [isTesting, setIsTesting] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
118
+ const [testResults, setTestResults] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
119
+ // ═══════════════════════════════════════════════════════════════════════════
120
+ // Load Config
121
+ // ═══════════════════════════════════════════════════════════════════════════
122
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
123
+ loadConfig();
124
+ if (!defaultPrompts) {
125
+ setIsLoadingPrompts(true);
126
+ (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__.fetchDefaultPrompts)().then(prompts => {
127
+ setDefaultPrompts(prompts);
128
+ setIsLoadingPrompts(false);
129
+ });
130
+ }
131
+ }, []);
132
+ const loadConfig = async () => {
133
+ try {
134
+ setIsLoading(true);
135
+ // Load admin config
136
+ const adminConfig = await apiService.getAdminConfig();
137
+ // Provider
138
+ setProvider(adminConfig.provider);
139
+ // Gemini
140
+ if (adminConfig.gemini) {
141
+ const keys = adminConfig.gemini.apiKeys?.length
142
+ ? adminConfig.gemini.apiKeys
143
+ : adminConfig.gemini.apiKey ? [adminConfig.gemini.apiKey] : [''];
144
+ setGeminiApiKeys(keys);
145
+ setGeminiModel(adminConfig.gemini.model || 'gemini-2.5-flash');
146
+ }
147
+ // vLLM
148
+ if (adminConfig.vllm) {
149
+ setVllmEndpoint(adminConfig.vllm.endpoint || 'https://openrouter.ai/api/v1');
150
+ setVllmApiKey(adminConfig.vllm.apiKey || '');
151
+ setVllmModel(adminConfig.vllm.model || 'openai/gpt-4o');
152
+ setVllmUseResponsesApi(Boolean(adminConfig.vllm.useResponsesApi));
153
+ }
154
+ // OpenAI
155
+ if (adminConfig.openai) {
156
+ setOpenaiApiKey(adminConfig.openai.apiKey || '');
157
+ setOpenaiModel(adminConfig.openai.model || 'gpt-4o');
158
+ }
159
+ // Summarization
160
+ if (adminConfig.summarization) {
161
+ setSummarizationEnabled(Boolean(adminConfig.summarization.enabled));
162
+ setSummarizationProvider(adminConfig.summarization.provider || 'gemini');
163
+ setSummarizationModel(adminConfig.summarization.model || '');
164
+ setSummarizationEndpoint(adminConfig.summarization.endpoint || '');
165
+ setSummarizationApiKey(adminConfig.summarization.apiKey || '');
166
+ }
167
+ // Embedding
168
+ if (adminConfig.embedding) {
169
+ setEmbeddingProvider(adminConfig.embedding.provider || 'openai');
170
+ setEmbeddingModel(adminConfig.embedding.model || 'text-embedding-3-small');
171
+ setEmbeddingApiKey(adminConfig.embedding.apiKey || '');
172
+ setEmbeddingEndpoint(adminConfig.embedding.endpoint || '');
173
+ }
174
+ // RAG
175
+ if (adminConfig.rag) {
176
+ setQdrantUrl(adminConfig.rag.qdrantUrl || 'http://localhost:6333');
177
+ }
178
+ // Server
179
+ setAgentServerUrl(adminConfig.agentServerUrl || 'http://localhost:8000');
180
+ setAgentServerTimeout(adminConfig.agentServerTimeout || 120);
181
+ setIdleTimeout(adminConfig.idleTimeout || 300);
182
+ // Prompts
183
+ if (adminConfig.prompts) {
184
+ setAgentPrompts(adminConfig.prompts);
185
+ }
186
+ // Load user config
187
+ const userConfig = await apiService.getUserConfig();
188
+ setWorkspaceRoot(userConfig.workspaceRoot || '');
189
+ setTemperature(userConfig.temperature ?? 0.7);
190
+ setAutoApprove(Boolean(userConfig.autoApprove));
191
+ setError(null);
192
+ }
193
+ catch (err) {
194
+ setError(`설정을 불러오는데 실패했습니다: ${err}`);
195
+ }
196
+ finally {
197
+ setIsLoading(false);
198
+ }
199
+ };
200
+ // ═══════════════════════════════════════════════════════════════════════════
201
+ // Save Config
202
+ // ═══════════════════════════════════════════════════════════════════════════
203
+ const handleSave = async () => {
204
+ try {
205
+ setIsSaving(true);
206
+ // Admin config
207
+ const adminConfig = {
208
+ provider,
209
+ gemini: {
210
+ apiKey: geminiApiKeys[0] || '',
211
+ apiKeys: geminiApiKeys.filter(k => k && k.trim()),
212
+ model: geminiModel
213
+ },
214
+ openai: {
215
+ apiKey: openaiApiKey,
216
+ model: openaiModel
217
+ },
218
+ vllm: {
219
+ endpoint: vllmEndpoint,
220
+ apiKey: vllmApiKey,
221
+ model: vllmModel,
222
+ useResponsesApi: vllmUseResponsesApi
223
+ },
224
+ summarization: {
225
+ enabled: summarizationEnabled,
226
+ provider: summarizationProvider,
227
+ model: summarizationModel || undefined,
228
+ ...(summarizationProvider === 'vllm' ? {
229
+ endpoint: summarizationEndpoint,
230
+ apiKey: summarizationApiKey
231
+ } : {})
232
+ },
233
+ embedding: {
234
+ provider: embeddingProvider,
235
+ model: embeddingModel,
236
+ apiKey: embeddingApiKey || undefined,
237
+ ...(embeddingProvider === 'vllm' ? { endpoint: embeddingEndpoint } : {})
238
+ },
239
+ rag: {
240
+ qdrantUrl,
241
+ collectionName: '' // Will be set by agent
242
+ },
243
+ agentServerUrl,
244
+ agentServerTimeout,
245
+ prompts: agentPrompts,
246
+ idleTimeout
247
+ };
248
+ await apiService.updateAdminConfig(adminConfig);
249
+ // User config
250
+ await apiService.updateUserConfig({
251
+ workspaceRoot: workspaceRoot.trim(),
252
+ temperature,
253
+ autoApprove
254
+ });
255
+ // Also save to localStorage for Chat mode compatibility
256
+ const llmConfigForLocalStorage = {
257
+ provider,
258
+ gemini: {
259
+ apiKey: geminiApiKeys[0] || '',
260
+ apiKeys: geminiApiKeys.filter(k => k && k.trim()),
261
+ model: geminiModel
262
+ },
263
+ openai: {
264
+ apiKey: openaiApiKey,
265
+ model: openaiModel
266
+ },
267
+ vllm: {
268
+ endpoint: vllmEndpoint,
269
+ apiKey: vllmApiKey,
270
+ model: vllmModel
271
+ },
272
+ workspaceRoot: workspaceRoot.trim(),
273
+ autoApprove,
274
+ agentPrompts
275
+ };
276
+ (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__.saveLLMConfig)(llmConfigForLocalStorage);
277
+ console.log('[AdminSettingsPanel] Config saved to localStorage for Chat mode');
278
+ onSave(adminConfig);
279
+ onClose();
280
+ }
281
+ catch (err) {
282
+ setError(`설정 저장에 실패했습니다: ${err}`);
283
+ }
284
+ finally {
285
+ setIsSaving(false);
286
+ }
287
+ };
288
+ // ═══════════════════════════════════════════════════════════════════════════
289
+ // Helpers
290
+ // ═══════════════════════════════════════════════════════════════════════════
291
+ const toggleSection = (key) => {
292
+ setExpandedSections(prev => ({ ...prev, [key]: !prev[key] }));
293
+ };
294
+ const handleAddKey = () => {
295
+ if (geminiApiKeys.length < 10) {
296
+ setGeminiApiKeys([...geminiApiKeys, '']);
297
+ }
298
+ };
299
+ const handleRemoveKey = (index) => {
300
+ if (geminiApiKeys.length > 1) {
301
+ setGeminiApiKeys(geminiApiKeys.filter((_, i) => i !== index));
302
+ }
303
+ };
304
+ const handleKeyChange = (index, value) => {
305
+ const newKeys = [...geminiApiKeys];
306
+ newKeys[index] = value;
307
+ setGeminiApiKeys(newKeys);
308
+ };
309
+ const validKeyCount = geminiApiKeys.filter(k => k && k.trim()).length;
310
+ const handleTest = async () => {
311
+ setIsTesting(true);
312
+ setTestResults({});
313
+ if (provider === 'gemini') {
314
+ geminiApiKeys.forEach((key, index) => {
315
+ if (key && key.trim()) {
316
+ setTestResults(prev => ({ ...prev, [index]: key.startsWith('AIza') ? 'success' : 'error' }));
317
+ }
318
+ });
319
+ }
320
+ else if (provider === 'openai') {
321
+ setTestResults({ 0: openaiApiKey.startsWith('sk-') ? 'success' : 'error' });
322
+ }
323
+ else {
324
+ setTestResults({ 0: vllmEndpoint ? 'success' : 'error' });
325
+ }
326
+ setIsTesting(false);
327
+ };
328
+ const getTestStatusIcon = (index) => {
329
+ const status = testResults[index];
330
+ if (status === 'testing')
331
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_HourglassEmpty__WEBPACK_IMPORTED_MODULE_3__["default"], { sx: { fontSize: 16, color: 'info.main' } });
332
+ if (status === 'success')
333
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_CheckCircle__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { fontSize: 16, color: 'success.main' } });
334
+ if (status === 'error')
335
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_Error__WEBPACK_IMPORTED_MODULE_2__["default"], { sx: { fontSize: 16, color: 'error.main' } });
336
+ return null;
337
+ };
338
+ // ═══════════════════════════════════════════════════════════════════════════
339
+ // Section Header Component
340
+ // ═══════════════════════════════════════════════════════════════════════════
341
+ const SectionHeader = ({ title, sectionKey, subtitle }) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-header", onClick: () => toggleSection(sectionKey) },
342
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-title" },
343
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-section-icon" }, expandedSections[sectionKey] ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_8__["default"], null) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ChevronRight__WEBPACK_IMPORTED_MODULE_9__["default"], null)),
344
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, title),
345
+ subtitle && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-section-subtitle" }, subtitle))));
346
+ // ═══════════════════════════════════════════════════════════════════════════
347
+ // Render
348
+ // ═══════════════════════════════════════════════════════════════════════════
349
+ if (isLoading) {
350
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-overlay" },
351
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-dialog jp-admin-dialog" },
352
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-content", style: { textAlign: 'center', padding: '40px' } },
353
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_HourglassEmpty__WEBPACK_IMPORTED_MODULE_3__["default"], { sx: { fontSize: 48, color: 'info.main' } }),
354
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("p", null, "\uC124\uC815\uC744 \uBD88\uB7EC\uC624\uB294 \uC911...")))));
355
+ }
356
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-overlay" },
357
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-dialog jp-admin-dialog" },
358
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-header" },
359
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("h2", null, "\uAD00\uB9AC\uC790 \uC124\uC815"),
360
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-close", onClick: onClose, title: "\uB2EB\uAE30" }, "\u00D7")),
361
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-content" },
362
+ error && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-error" }, error)),
363
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section" },
364
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(SectionHeader, { title: "LLM \uC124\uC815", sectionKey: "llm", subtitle: "\uBA54\uC778 \uC5B8\uC5B4 \uBAA8\uB378" }),
365
+ expandedSections.llm && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-content" },
366
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
367
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uD504\uB85C\uBC14\uC774\uB354"),
368
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", { className: "jp-admin-select", value: provider, onChange: (e) => setProvider(e.target.value) },
369
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gemini" }, "Google Gemini"),
370
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "vllm" }, "vLLM / OpenRouter"),
371
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "openai" }, "OpenAI"))),
372
+ provider === 'gemini' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
373
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
374
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" },
375
+ "API \uD0A4 (",
376
+ validKeyCount,
377
+ "/10)",
378
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-label-hint" }, "Rate limit \uC2DC \uC790\uB3D9 \uB85C\uD14C\uC774\uC158")),
379
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-keys-container" },
380
+ geminiApiKeys.map((key, index) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: index, className: "jp-admin-key-row" },
381
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-key-index" },
382
+ index + 1,
383
+ "."),
384
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "password", className: "jp-admin-input", value: key, onChange: (e) => handleKeyChange(index, e.target.value), placeholder: "AIza..." }),
385
+ getTestStatusIcon(index),
386
+ geminiApiKeys.length > 1 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-admin-btn-remove", onClick: () => handleRemoveKey(index) }, "\u00D7"))))),
387
+ geminiApiKeys.length < 10 && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-admin-btn-add", onClick: handleAddKey }, "+ \uD0A4 \uCD94\uAC00")))),
388
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
389
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uBAA8\uB378"),
390
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", { className: "jp-admin-select", value: geminiModel, onChange: (e) => setGeminiModel(e.target.value) },
391
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gemini-2.5-flash" }, "Gemini 2.5 Flash"),
392
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gemini-2.5-pro" }, "Gemini 2.5 Pro"))))),
393
+ provider === 'vllm' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
394
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
395
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "API Base URL"),
396
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: vllmEndpoint, onChange: (e) => setVllmEndpoint(e.target.value), placeholder: "https://openrouter.ai/api/v1" })),
397
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
398
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "API \uD0A4"),
399
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "password", className: "jp-admin-input", value: vllmApiKey, onChange: (e) => setVllmApiKey(e.target.value), placeholder: "sk-or-..." })),
400
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
401
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uBAA8\uB378"),
402
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: vllmModel, onChange: (e) => setVllmModel(e.target.value), placeholder: "openai/gpt-4o" })),
403
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
404
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-checkbox" },
405
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "checkbox", checked: vllmUseResponsesApi, onChange: (e) => setVllmUseResponsesApi(e.target.checked) }),
406
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Use Responses API (/v1/responses)"))))),
407
+ provider === 'openai' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
408
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
409
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "API \uD0A4"),
410
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "password", className: "jp-admin-input", value: openaiApiKey, onChange: (e) => setOpenaiApiKey(e.target.value), placeholder: "sk-..." })),
411
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
412
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uBAA8\uB378"),
413
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", { className: "jp-admin-select", value: openaiModel, onChange: (e) => setOpenaiModel(e.target.value) },
414
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gpt-4o" }, "GPT-4o"),
415
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gpt-4o-mini" }, "GPT-4o Mini"),
416
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gpt-4-turbo" }, "GPT-4 Turbo")))))))),
417
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section" },
418
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(SectionHeader, { title: "\uC0AC\uC6A9\uC790 \uC124\uC815", sectionKey: "user", subtitle: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4, \uC628\uB3C4, \uC790\uB3D9 \uC2B9\uC778" }),
419
+ expandedSections.user && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-content" },
420
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
421
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" },
422
+ "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8",
423
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-label-hint" }, "\uBE44\uC6B0\uBA74 \uD604\uC7AC \uB178\uD2B8\uBD81 \uD3F4\uB354 \uAE30\uC900")),
424
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: workspaceRoot, onChange: (e) => setWorkspaceRoot(e.target.value), placeholder: "/path/to/project" })),
425
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
426
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" },
427
+ "Temperature",
428
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-label-hint" }, "0.0 = \uACB0\uC815\uC801, 1.0+ = \uCC3D\uC758\uC801")),
429
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-slider-row" },
430
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "range", min: 0, max: 2, step: 0.1, value: temperature, onChange: (e) => setTemperature(parseFloat(e.target.value)), className: "jp-admin-slider" }),
431
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "number", className: "jp-admin-input jp-admin-input-small", value: temperature, onChange: (e) => setTemperature(Math.max(0, Math.min(2, parseFloat(e.target.value) || 0))), min: 0, max: 2, step: 0.1 }))),
432
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
433
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-checkbox" },
434
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "checkbox", checked: autoApprove, onChange: (e) => setAutoApprove(e.target.checked) }),
435
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\uC790\uB3D9 \uC2E4\uD589 \uC2B9\uC778 (\uCF54\uB4DC/\uD30C\uC77C/\uC178 \uD3EC\uD568)")),
436
+ autoApprove && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-warning" }, "\uC8FC\uC758: \uC5D0\uC774\uC804\uD2B8\uAC00 \uC0DD\uC131\uD55C \uCF54\uB4DC\uAC00 \uC790\uB3D9\uC73C\uB85C \uC2E4\uD589\uB429\uB2C8\uB2E4.")))))),
437
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section" },
438
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(SectionHeader, { title: "\uC694\uC57D\uC6A9 LLM", sectionKey: "summarization", subtitle: "\uB300\uD654 \uC555\uCD95 \uC2DC \uC0AC\uC6A9" }),
439
+ expandedSections.summarization && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-content" },
440
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
441
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-checkbox" },
442
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "checkbox", checked: summarizationEnabled, onChange: (e) => setSummarizationEnabled(e.target.checked) }),
443
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\uBCC4\uB3C4 LLM\uC73C\uB85C \uC694\uC57D \uC218\uD589 (\uBBF8\uC124\uC815 \uC2DC \uBA54\uC778 LLM \uC0AC\uC6A9)"))),
444
+ summarizationEnabled && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
445
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
446
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uD504\uB85C\uBC14\uC774\uB354"),
447
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", { className: "jp-admin-select", value: summarizationProvider, onChange: (e) => setSummarizationProvider(e.target.value) },
448
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gemini" }, "Gemini (\uBA54\uC778 \uC124\uC815 \uC0AC\uC6A9)"),
449
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "openai" }, "OpenAI (\uBA54\uC778 \uC124\uC815 \uC0AC\uC6A9)"),
450
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "vllm" }, "vLLM / OpenRouter (\uBCC4\uB3C4 \uC124\uC815)"))),
451
+ summarizationProvider === 'vllm' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
452
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
453
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "API Base URL"),
454
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: summarizationEndpoint, onChange: (e) => setSummarizationEndpoint(e.target.value), placeholder: "https://openrouter.ai/api/v1" })),
455
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
456
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "API \uD0A4"),
457
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "password", className: "jp-admin-input", value: summarizationApiKey, onChange: (e) => setSummarizationApiKey(e.target.value), placeholder: "sk-or-..." })))),
458
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
459
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" },
460
+ "\uBAA8\uB378",
461
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-label-hint" }, "\uBE44\uC6B0\uBA74 \uAE30\uBCF8 \uBAA8\uB378 \uC0AC\uC6A9")),
462
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: summarizationModel, onChange: (e) => setSummarizationModel(e.target.value), placeholder: summarizationProvider === 'vllm' ? 'openai/gpt-4o-mini' : '' }))))))),
463
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section" },
464
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(SectionHeader, { title: "\uC784\uBCA0\uB529 \uC124\uC815", sectionKey: "embedding", subtitle: "RAG \uBCA1\uD130 \uAC80\uC0C9\uC6A9" }),
465
+ expandedSections.embedding && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-content" },
466
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
467
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uD504\uB85C\uBC14\uC774\uB354"),
468
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", { className: "jp-admin-select", value: embeddingProvider, onChange: (e) => setEmbeddingProvider(e.target.value) },
469
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "openai" }, "OpenAI"),
470
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gemini" }, "Gemini"),
471
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "vllm" }, "vLLM / OpenRouter (\uBCC4\uB3C4 \uC124\uC815)"))),
472
+ embeddingProvider === 'vllm' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
473
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "API Base URL"),
474
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: embeddingEndpoint, onChange: (e) => setEmbeddingEndpoint(e.target.value), placeholder: "https://api.openai.com/v1" }))),
475
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
476
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "API \uD0A4"),
477
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "password", className: "jp-admin-input", value: embeddingApiKey, onChange: (e) => setEmbeddingApiKey(e.target.value), placeholder: embeddingProvider === 'openai' ? 'sk-... (비우면 메인 OpenAI 키 사용)' : 'API 키' })),
478
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
479
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uBAA8\uB378"),
480
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: embeddingModel, onChange: (e) => setEmbeddingModel(e.target.value), placeholder: "text-embedding-3-small" }))))),
481
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section" },
482
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(SectionHeader, { title: "RAG / Qdrant", sectionKey: "rag", subtitle: "\uBCA1\uD130 \uB370\uC774\uD130\uBCA0\uC774\uC2A4" }),
483
+ expandedSections.rag && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-content" },
484
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
485
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "Qdrant URL"),
486
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: qdrantUrl, onChange: (e) => setQdrantUrl(e.target.value), placeholder: "http://localhost:6333" }))))),
487
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section" },
488
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(SectionHeader, { title: "\uC11C\uBC84 \uC124\uC815", sectionKey: "server", subtitle: "Agent \uC11C\uBC84, \uD0C0\uC784\uC544\uC6C3" }),
489
+ expandedSections.server && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-content" },
490
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row" },
491
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "Agent Server URL"),
492
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-admin-input", value: agentServerUrl, onChange: (e) => setAgentServerUrl(e.target.value), placeholder: "http://localhost:8000" })),
493
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-row jp-admin-row-inline" },
494
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-inline-field" },
495
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "\uC694\uCCAD Timeout (\uCD08)"),
496
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "number", className: "jp-admin-input jp-admin-input-small", value: agentServerTimeout, onChange: (e) => setAgentServerTimeout(parseInt(e.target.value) || 120), min: 10, max: 600 })),
497
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-inline-field" },
498
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-admin-label" }, "Idle Timeout (\uCD08)"),
499
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "number", className: "jp-admin-input jp-admin-input-small", value: idleTimeout, onChange: (e) => setIdleTimeout(Math.max(0, parseInt(e.target.value) || 0)), min: 0, max: 86400 })))))),
500
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section" },
501
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(SectionHeader, { title: "\uC5D0\uC774\uC804\uD2B8 \uD504\uB86C\uD504\uD2B8", sectionKey: "prompts", subtitle: "\uC2DC\uC2A4\uD15C \uD504\uB86C\uD504\uD2B8 \uCEE4\uC2A4\uD130\uB9C8\uC774\uC9D5" }),
502
+ expandedSections.prompts && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-section-content" },
503
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-card" },
504
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-header", onClick: () => setExpandedAgents(prev => ({ ...prev, planner: !prev.planner })) },
505
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-icon" },
506
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_GpsFixed__WEBPACK_IMPORTED_MODULE_7__["default"], { sx: { fontSize: 18 } })),
507
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Planner (Supervisor)"),
508
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-chevron" }, expandedAgents.planner ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_8__["default"], null) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ChevronRight__WEBPACK_IMPORTED_MODULE_9__["default"], null))),
509
+ expandedAgents.planner && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-body" },
510
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: "jp-admin-textarea", value: agentPrompts.planner || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, planner: e.target.value })), rows: 6 }),
511
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-admin-btn-reset", onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, planner: defaultPrompts.planner })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값 복원')))),
512
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-card" },
513
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-header", onClick: () => setExpandedAgents(prev => ({ ...prev, python_developer: !prev.python_developer })) },
514
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-icon" },
515
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_Code__WEBPACK_IMPORTED_MODULE_4__["default"], { sx: { fontSize: 18 } })),
516
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Python Developer"),
517
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-chevron" }, expandedAgents.python_developer ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_8__["default"], null) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ChevronRight__WEBPACK_IMPORTED_MODULE_9__["default"], null))),
518
+ expandedAgents.python_developer && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-body" },
519
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: "jp-admin-textarea", value: agentPrompts.python_developer || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, python_developer: e.target.value })), rows: 6 }),
520
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-admin-btn-reset", onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, python_developer: defaultPrompts.python_developer })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값 복원')))),
521
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-card" },
522
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-header", onClick: () => setExpandedAgents(prev => ({ ...prev, researcher: !prev.researcher })) },
523
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-icon" },
524
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_Search__WEBPACK_IMPORTED_MODULE_5__["default"], { sx: { fontSize: 18 } })),
525
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Researcher"),
526
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-chevron" }, expandedAgents.researcher ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_8__["default"], null) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ChevronRight__WEBPACK_IMPORTED_MODULE_9__["default"], null))),
527
+ expandedAgents.researcher && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-body" },
528
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: "jp-admin-textarea", value: agentPrompts.researcher || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, researcher: e.target.value })), rows: 6 }),
529
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-admin-btn-reset", onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, researcher: defaultPrompts.researcher })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값 복원')))),
530
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-card" },
531
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-header", onClick: () => setExpandedAgents(prev => ({ ...prev, athena_query: !prev.athena_query })) },
532
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-icon" },
533
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_Analytics__WEBPACK_IMPORTED_MODULE_6__["default"], { sx: { fontSize: 18 } })),
534
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Athena Query"),
535
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-admin-prompt-chevron" }, expandedAgents.athena_query ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_8__["default"], null) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ChevronRight__WEBPACK_IMPORTED_MODULE_9__["default"], null))),
536
+ expandedAgents.athena_query && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-admin-prompt-body" },
537
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: "jp-admin-textarea", value: agentPrompts.athena_query || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, athena_query: e.target.value })), rows: 6 }),
538
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-admin-btn-reset", onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, athena_query: defaultPrompts.athena_query })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값 복원')))),
539
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-admin-btn-reset-all", onClick: () => {
540
+ if (defaultPrompts) {
541
+ setAgentPrompts({
542
+ planner: defaultPrompts.planner,
543
+ python_developer: defaultPrompts.python_developer,
544
+ researcher: defaultPrompts.researcher,
545
+ athena_query: defaultPrompts.athena_query,
546
+ });
547
+ }
548
+ }, disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '모든 프롬프트 기본값 복원'))))),
549
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-footer" },
550
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-button jp-agent-settings-button-secondary", onClick: onClose }, "\uCDE8\uC18C"),
551
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-button jp-agent-settings-button-test", onClick: handleTest, disabled: isTesting }, isTesting ? '테스트 중...' : 'API 테스트'),
552
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-button jp-agent-settings-button-primary", onClick: handleSave, disabled: isSaving }, isSaving ? '저장 중...' : '저장')))));
553
+ };
554
+
555
+
556
+ /***/ },
557
+
4
558
  /***/ "./lib/components/AgentPanel.js"
5
559
  /*!**************************************!*\
6
560
  !*** ./lib/components/AgentPanel.js ***!
@@ -18,15 +572,17 @@ __webpack_require__.r(__webpack_exports__);
18
572
  /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jupyterlab/notebook */ "webpack/sharing/consume/default/@jupyterlab/notebook");
19
573
  /* harmony import */ var _jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_notebook__WEBPACK_IMPORTED_MODULE_2__);
20
574
  /* harmony import */ var _SettingsPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./SettingsPanel */ "./lib/components/SettingsPanel.js");
21
- /* harmony import */ var _services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../services/ApiKeyManager */ "./lib/services/ApiKeyManager.js");
22
- /* harmony import */ var _utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/markdownRenderer */ "./lib/utils/markdownRenderer.js");
23
- /* harmony import */ var _FileSelectionDialog__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./FileSelectionDialog */ "./lib/components/FileSelectionDialog.js");
24
- /* harmony import */ var _StreamingMessage__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./StreamingMessage */ "./lib/components/StreamingMessage.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");
575
+ /* harmony import */ var _AdminSettingsPanel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AdminSettingsPanel */ "./lib/components/AdminSettingsPanel.js");
576
+ /* harmony import */ var _UserSettingsPanel__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./UserSettingsPanel */ "./lib/components/UserSettingsPanel.js");
577
+ /* harmony import */ var _services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../services/ApiKeyManager */ "./lib/services/ApiKeyManager.js");
578
+ /* harmony import */ var _utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../utils/markdownRenderer */ "./lib/utils/markdownRenderer.js");
579
+ /* harmony import */ var _FileSelectionDialog__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./FileSelectionDialog */ "./lib/components/FileSelectionDialog.js");
580
+ /* harmony import */ var _StreamingMessage__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./StreamingMessage */ "./lib/components/StreamingMessage.js");
581
+ /* harmony import */ var _ContextAutocomplete__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./ContextAutocomplete */ "./lib/components/ContextAutocomplete.js");
582
+ /* harmony import */ var _utils_icons__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../utils/icons */ "./lib/utils/icons.js");
583
+ /* harmony import */ var _mui_icons_material_ExpandLess__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @mui/icons-material/ExpandLess */ "./node_modules/@mui/icons-material/esm/ExpandLess.js");
584
+ /* harmony import */ var _mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! @mui/icons-material/ExpandMore */ "./node_modules/@mui/icons-material/esm/ExpandMore.js");
585
+ /* harmony import */ var _logoSvg__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ../logoSvg */ "./lib/logoSvg.js");
30
586
  /**
31
587
  * Agent Panel - Main sidebar panel for Jupyter Agent
32
588
  * Cursor AI Style: Unified Chat + Agent Interface
@@ -34,6 +590,8 @@ __webpack_require__.r(__webpack_exports__);
34
590
 
35
591
 
36
592
 
593
+ // Fallback for legacy mode
594
+
37
595
 
38
596
 
39
597
 
@@ -48,7 +606,7 @@ __webpack_require__.r(__webpack_exports__);
48
606
  // 탭바 아이콘 생성
49
607
  const hdspTabIcon = new _jupyterlab_ui_components__WEBPACK_IMPORTED_MODULE_1__.LabIcon({
50
608
  name: 'hdsp-agent:tab-icon',
51
- svgstr: _logoSvg__WEBPACK_IMPORTED_MODULE_12__.tabbarLogoSvg
609
+ svgstr: _logoSvg__WEBPACK_IMPORTED_MODULE_14__.tabbarLogoSvg
52
610
  });
53
611
  // ═══════════════════════════════════════════════════════════════════════════
54
612
  // Python 파일 에러 감지 및 처리 유틸리티
@@ -156,9 +714,12 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
156
714
  const [streamingMessageId, setStreamingMessageId] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
157
715
  const [conversationId, setConversationId] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
158
716
  const [showSettings, setShowSettings] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
717
+ const [isAdmin, setIsAdmin] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null); // null = loading, false/true = resolved
159
718
  const [llmConfig, setLlmConfig] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
160
719
  // Agent 실행 상태
161
720
  const [isAgentRunning, setIsAgentRunning] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
721
+ // Compact 압축 상태
722
+ const [isCompacting, setIsCompacting] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
162
723
  // 입력 모드 (Cursor AI 스타일) - 로컬 스토리지에서 복원
163
724
  // Note: 'agent_v2'는 'agent'로 마이그레이션
164
725
  const [inputMode, setInputMode] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(() => {
@@ -249,6 +810,8 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
249
810
  // Todo list state (from TodoListMiddleware)
250
811
  const [todos, setTodos] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
251
812
  const [isTodoExpanded, setIsTodoExpanded] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
813
+ // When true, render in_progress todos as cancelled (survives late SSE overwrites)
814
+ const [isTodoStopped, setIsTodoStopped] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
252
815
  // Agent thread ID for context persistence across cycles (SummarizationMiddleware support)
253
816
  const [agentThreadId, setAgentThreadId] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
254
817
  // AbortController for stopping long-running tasks
@@ -603,10 +1166,30 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
603
1166
  setInputMode(mode);
604
1167
  }
605
1168
  }));
606
- // Load config on mount
1169
+ // Load config and check admin mode on mount
607
1170
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
608
1171
  loadConfig();
1172
+ // Check admin mode for settings panel selection
1173
+ const checkAdminMode = async () => {
1174
+ try {
1175
+ const result = await apiService.checkIsAdmin();
1176
+ setIsAdmin(result.isAdmin);
1177
+ console.log('[AgentPanel] Admin mode check:', result.isAdmin, result.reason);
1178
+ }
1179
+ catch (err) {
1180
+ console.warn('[AgentPanel] Failed to check admin mode, defaulting to user mode:', err);
1181
+ setIsAdmin(false); // Default to user mode on error
1182
+ }
1183
+ };
1184
+ checkAdminMode();
609
1185
  }, []);
1186
+ // Force chat mode for non-admin users (agent mode is admin-only for now)
1187
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
1188
+ if (isAdmin === false && inputMode !== 'chat') {
1189
+ console.log('[AgentPanel] Non-admin user detected, forcing chat mode');
1190
+ setInputMode('chat');
1191
+ }
1192
+ }, [isAdmin]);
610
1193
  // Reset expand state when debug status changes
611
1194
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
612
1195
  setIsDebugExpanded(false);
@@ -706,10 +1289,10 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
706
1289
  // Remove lastActiveNotebook state - just use currentWidget directly
707
1290
  const loadConfig = () => {
708
1291
  // Load from localStorage using ApiKeyManager
709
- const config = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)();
1292
+ const config = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getLLMConfig)();
710
1293
  if (!config) {
711
1294
  console.log('[AgentPanel] No config in localStorage, using default');
712
- const defaultConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)();
1295
+ const defaultConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getDefaultLLMConfig)();
713
1296
  setLlmConfig(defaultConfig);
714
1297
  return;
715
1298
  }
@@ -923,9 +1506,9 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
923
1506
  const handleSaveConfig = (config) => {
924
1507
  console.log('[AgentPanel] Saving config to localStorage');
925
1508
  console.log('Provider:', config.provider);
926
- console.log('API Key configured:', (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.hasValidApiKey)(config) ? '✓ Yes' : '✗ No');
1509
+ console.log('API Key configured:', (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.hasValidApiKey)(config) ? '✓ Yes' : '✗ No');
927
1510
  // Save to localStorage using ApiKeyManager
928
- (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.saveLLMConfig)(config);
1511
+ (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.saveLLMConfig)(config);
929
1512
  // Update state
930
1513
  setLlmConfig(config);
931
1514
  console.log('[AgentPanel] Config saved successfully');
@@ -963,7 +1546,15 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
963
1546
  */
964
1547
  const stopCurrentTask = async () => {
965
1548
  console.log('[AgentPanel] Stopping current task...');
966
- // 1. Cancel the server-side agent execution
1549
+ // 1. Mark todos as stopped — rendering will show in_progress as cancelled
1550
+ setIsTodoStopped(true);
1551
+ // 2. Abort any ongoing fetch requests
1552
+ if (abortControllerRef.current) {
1553
+ abortControllerRef.current.abort();
1554
+ abortControllerRef.current = null;
1555
+ console.log('[AgentPanel] Aborted fetch request');
1556
+ }
1557
+ // 2. Cancel the server-side agent execution
967
1558
  if (agentThreadId) {
968
1559
  try {
969
1560
  const result = await apiService.cancelAgent(agentThreadId);
@@ -973,12 +1564,6 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
973
1564
  console.warn('[AgentPanel] Error cancelling server-side agent:', error);
974
1565
  }
975
1566
  }
976
- // 2. Abort any ongoing fetch requests
977
- if (abortControllerRef.current) {
978
- abortControllerRef.current.abort();
979
- abortControllerRef.current = null;
980
- console.log('[AgentPanel] Aborted fetch request');
981
- }
982
1567
  // 3. Interrupt the Jupyter kernel if executing (try all possible kernels)
983
1568
  try {
984
1569
  // Try notebook kernel first
@@ -1014,7 +1599,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1014
1599
  setAgentThreadId(null);
1015
1600
  // 6. Update current in_progress todo to show it was stopped
1016
1601
  setTodos(prev => prev.map(todo => todo.status === 'in_progress'
1017
- ? { ...todo, content: `${todo.content} (중단됨)`, status: 'pending' }
1602
+ ? { ...todo, status: 'cancelled' }
1018
1603
  : todo));
1019
1604
  // Show notification
1020
1605
  showNotification('작업이 중단되었습니다.', 'info');
@@ -1022,15 +1607,25 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1022
1607
  };
1023
1608
  // Auto-scroll state refs for smooth scrolling
1024
1609
  const scrollRafRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
1025
- const lastScrollTimeRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(0);
1610
+ const lastScrollHeightRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(0);
1611
+ const wasStreamingRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);
1026
1612
  const userScrolledUpRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);
1613
+ // Flag to suppress scroll-event detection during programmatic scrolls
1614
+ const isProgrammaticScrollRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);
1027
1615
  // Track user scroll to detect if they scrolled up
1616
+ // During streaming, only real user scroll (wheel/touch) should disable auto-scroll.
1617
+ // Programmatic scrolls and rendering-induced scroll events are ignored.
1028
1618
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
1029
1619
  const container = messagesContainerRef.current;
1030
1620
  if (!container)
1031
1621
  return;
1032
1622
  const handleScroll = () => {
1033
- const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
1623
+ // Skip detection if this was triggered by our programmatic scroll
1624
+ if (isProgrammaticScrollRef.current) {
1625
+ return;
1626
+ }
1627
+ // Use wider threshold (200px) to tolerate rendering height fluctuations
1628
+ const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 200;
1034
1629
  userScrolledUpRef.current = !isNearBottom;
1035
1630
  };
1036
1631
  container.addEventListener('scroll', handleScroll, { passive: true });
@@ -1041,38 +1636,58 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1041
1636
  const container = messagesContainerRef.current;
1042
1637
  if (!container)
1043
1638
  return;
1044
- // If user scrolled up, don't auto-scroll
1639
+ // 스트리밍 종료 감지: 이전에 스트리밍 중이었고 지금은 아닌 경우
1640
+ const streamingJustEnded = wasStreamingRef.current && !isStreaming;
1641
+ wasStreamingRef.current = isStreaming;
1642
+ // If user scrolled up, don't auto-scroll (단, 스트리밍 종료 시는 예외)
1045
1643
  if (userScrolledUpRef.current && isStreaming) {
1046
1644
  return;
1047
1645
  }
1048
- // Check if user is near bottom (within 100px threshold)
1049
- const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
1050
- if (isNearBottom || !isStreaming) {
1051
- if (isStreaming) {
1052
- // 스트리밍 중: requestAnimationFrame으로 부드럽게 스크롤
1053
- // 이전 RAF가 있으면 취소하고 새로 스케줄링
1646
+ // Use wider threshold during streaming (rendering can cause temporary height changes)
1647
+ const threshold = isStreaming ? 200 : 100;
1648
+ const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
1649
+ if (isStreaming) {
1650
+ // 스트리밍 중: 높이가 20px 이상 변화했을 때만 스크롤
1651
+ const scrollHeight = container.scrollHeight;
1652
+ const heightChanged = scrollHeight - lastScrollHeightRef.current > 20;
1653
+ if (heightChanged) {
1054
1654
  if (scrollRafRef.current) {
1055
1655
  cancelAnimationFrame(scrollRafRef.current);
1056
1656
  }
1057
1657
  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
- }
1658
+ isProgrammaticScrollRef.current = true;
1659
+ container.scrollTo({
1660
+ top: container.scrollHeight,
1661
+ behavior: 'smooth'
1662
+ });
1064
1663
  scrollRafRef.current = null;
1664
+ setTimeout(() => {
1665
+ isProgrammaticScrollRef.current = false;
1666
+ }, 150);
1065
1667
  });
1668
+ lastScrollHeightRef.current = scrollHeight;
1066
1669
  }
1067
- else if (isNearBottom) {
1068
- // 일반 메시지 추가 시 부드러운 스크롤
1069
- container.scrollTo({
1070
- top: container.scrollHeight,
1071
- behavior: 'smooth'
1072
- });
1073
- // 새 메시지 추가 시 userScrolledUp 리셋
1074
- userScrolledUpRef.current = false;
1075
- }
1670
+ }
1671
+ else if (streamingJustEnded) {
1672
+ // 스트리밍 종료 시: 약간의 딜레이 후 최종 스크롤 (렌더링 완료 대기)
1673
+ console.log('[AutoScroll] Streaming ended, scrolling to bottom');
1674
+ setTimeout(() => {
1675
+ if (container) {
1676
+ container.scrollTo({
1677
+ top: container.scrollHeight,
1678
+ behavior: 'smooth'
1679
+ });
1680
+ }
1681
+ }, 100);
1682
+ userScrolledUpRef.current = false;
1683
+ lastScrollHeightRef.current = 0; // 리셋
1684
+ }
1685
+ else if (isNearBottom) {
1686
+ // 하단 근처일 때: 스크롤
1687
+ container.scrollTo({
1688
+ top: container.scrollHeight,
1689
+ behavior: 'smooth'
1690
+ });
1076
1691
  }
1077
1692
  // Cleanup RAF on unmount
1078
1693
  return () => {
@@ -1108,11 +1723,11 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1108
1723
  let currentConfig = llmConfig;
1109
1724
  if (!currentConfig) {
1110
1725
  // Config not loaded yet, try to load from localStorage
1111
- currentConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)();
1726
+ currentConfig = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getDefaultLLMConfig)();
1112
1727
  setLlmConfig(currentConfig);
1113
1728
  }
1114
1729
  // Check API key using ApiKeyManager
1115
- const hasApiKey = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.hasValidApiKey)(currentConfig);
1730
+ const hasApiKey = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.hasValidApiKey)(currentConfig);
1116
1731
  const autoApproveEnabled = getAutoApproveEnabled(currentConfig);
1117
1732
  if (!hasApiKey) {
1118
1733
  // Show error message and open settings
@@ -1139,6 +1754,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1139
1754
  }
1140
1755
  setIsLoading(true);
1141
1756
  setIsStreaming(true);
1757
+ setIsTodoStopped(false);
1142
1758
  // Clear todos only when starting a new task (all completed or no todos)
1143
1759
  // Keep todos if there are pending/in_progress items (continuation of current task)
1144
1760
  const hasActiveTodos = todos.some(t => t.status === 'pending' || t.status === 'in_progress');
@@ -1147,9 +1763,11 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1147
1763
  }
1148
1764
  setDebugStatus(null);
1149
1765
  // Clear the data attribute and ref after using it
1150
- if (textarea && llmPrompt) {
1151
- textarea.removeAttribute('data-llm-prompt');
1766
+ if (llmPrompt) {
1152
1767
  pendingLlmPromptRef.current = null;
1768
+ if (textarea) {
1769
+ textarea.removeAttribute('data-llm-prompt');
1770
+ }
1153
1771
  }
1154
1772
  // Create assistant message ID for streaming updates
1155
1773
  const assistantMessageId = makeMessageId('assistant');
@@ -1192,7 +1810,11 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
1192
1810
  }
1193
1811
  },
1194
1812
  // AbortSignal for stopping the stream
1195
- abortControllerRef.current.signal);
1813
+ abortControllerRef.current.signal,
1814
+ // onDebug callback for status messages (auto-compact, etc.)
1815
+ (status) => {
1816
+ setDebugStatus(status);
1817
+ });
1196
1818
  }
1197
1819
  else {
1198
1820
  // Agent V2 모드 - /agent/langchain/stream 사용 (HITL, Todo, 도구 실행)
@@ -2159,7 +2781,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2159
2781
  };
2160
2782
  }
2161
2783
  };
2162
- const executeCodeViaSubprocess = async (code, timeout) => {
2784
+ const executeCodeViaSubprocess = async (code, timeout, onOutput) => {
2163
2785
  const isShell = isShellCell(code);
2164
2786
  const command = isShell ? extractShellCommand(code) : buildPythonCommand(code);
2165
2787
  if (!command) {
@@ -2173,7 +2795,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2173
2795
  execution_method: 'subprocess'
2174
2796
  };
2175
2797
  }
2176
- const result = await executeSubprocessCommand(command, timeout);
2798
+ const result = await executeSubprocessCommand(command, timeout, onOutput);
2177
2799
  return {
2178
2800
  ...result,
2179
2801
  execution_method: 'subprocess'
@@ -2469,7 +3091,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2469
3091
  else if (action === 'search_files_tool') {
2470
3092
  setDebugStatus({ status: `파일 검색 중: ${args?.pattern || ''}`, icon: 'search' });
2471
3093
  try {
2472
- const currentLlmConfig = llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)();
3094
+ const currentLlmConfig = llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getDefaultLLMConfig)();
2473
3095
  const searchResult = await apiService.searchWorkspace({
2474
3096
  pattern: args?.pattern || '',
2475
3097
  file_types: args?.file_pattern ? [args.file_pattern] : ['*'],
@@ -2532,7 +3154,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2532
3154
  }, (nextInterrupt) => {
2533
3155
  interrupted = true;
2534
3156
  approvalPendingRef.current = true;
2535
- const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)());
3157
+ const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getDefaultLLMConfig)());
2536
3158
  // Handle next interrupt (could be another search or code execution)
2537
3159
  if (nextInterrupt.action === 'search_notebook_cells_tool'
2538
3160
  || nextInterrupt.action === 'check_resource_tool'
@@ -2959,7 +3581,11 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2959
3581
  const code = interrupt.args.code;
2960
3582
  if (!shouldExecuteInNotebook(code)) {
2961
3583
  setDebugStatus({ status: '서브프로세스로 코드 실행 중...', icon: 'terminal' });
2962
- const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);
3584
+ const subCmd = isShellCell(code) ? extractShellCommand(code) : buildPythonCommand(code);
3585
+ const subOutputId = subCmd ? createCommandOutputMessage(subCmd) : null;
3586
+ const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout, subOutputId
3587
+ ? (chunk) => appendCommandOutputMessage(subOutputId, chunk.text, chunk.stream)
3588
+ : undefined);
2963
3589
  resumeDecision = 'edit';
2964
3590
  resumeArgs = {
2965
3591
  code,
@@ -2979,7 +3605,11 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2979
3605
  const notebook = getActiveNotebookPanel();
2980
3606
  if (!notebook) {
2981
3607
  setDebugStatus({ status: '노트북이 없어 서브프로세스로 실행 중...', icon: 'terminal' });
2982
- const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);
3608
+ const nbCmd = isShellCell(code) ? extractShellCommand(code) : buildPythonCommand(code);
3609
+ const nbOutputId = nbCmd ? createCommandOutputMessage(nbCmd) : null;
3610
+ const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout, nbOutputId
3611
+ ? (chunk) => appendCommandOutputMessage(nbOutputId, chunk.text, chunk.stream)
3612
+ : undefined);
2983
3613
  resumeDecision = 'edit';
2984
3614
  resumeArgs = {
2985
3615
  code,
@@ -2990,7 +3620,11 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
2990
3620
  const cellIndex = insertCell(notebook, 'code', code);
2991
3621
  if (cellIndex === null) {
2992
3622
  setDebugStatus({ status: '노트북 모델이 없어 서브프로세스로 실행 중...', icon: 'terminal' });
2993
- const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);
3623
+ const mdlCmd = isShellCell(code) ? extractShellCommand(code) : buildPythonCommand(code);
3624
+ const mdlOutputId = mdlCmd ? createCommandOutputMessage(mdlCmd) : null;
3625
+ const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout, mdlOutputId
3626
+ ? (chunk) => appendCommandOutputMessage(mdlOutputId, chunk.text, chunk.stream)
3627
+ : undefined);
2994
3628
  resumeDecision = 'edit';
2995
3629
  resumeArgs = {
2996
3630
  code,
@@ -3081,7 +3715,7 @@ const ChatPanel = (0,react__WEBPACK_IMPORTED_MODULE_0__.forwardRef)(({ apiServic
3081
3715
  }, (nextInterrupt) => {
3082
3716
  interrupted = true;
3083
3717
  approvalPendingRef.current = true;
3084
- const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_4__.getDefaultLLMConfig)());
3718
+ const autoApproveEnabled = getAutoApproveEnabled(llmConfig || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getLLMConfig)() || (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.getDefaultLLMConfig)());
3085
3719
  // Auto-approve search/file/resource tools
3086
3720
  if (nextInterrupt.action === 'search_notebook_cells_tool'
3087
3721
  || nextInterrupt.action === 'check_resource_tool'
@@ -3391,19 +4025,16 @@ SyntaxError: '(' was never closed
3391
4025
  const newCursorPosition = e.target.selectionStart || 0;
3392
4026
  setInput(newValue);
3393
4027
  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
- }
4028
+ // Check if we should show autocomplete (when typing @ or / commands)
4029
+ // Find the start of the current word (from cursor back to whitespace)
4030
+ let wordStart = newCursorPosition;
4031
+ while (wordStart > 0 && !/\s/.test(newValue[wordStart - 1])) {
4032
+ wordStart--;
4033
+ }
4034
+ const currentWord = newValue.slice(wordStart, newCursorPosition);
4035
+ // Show autocomplete if current word starts with @ or /
4036
+ if (currentWord.startsWith('@') || currentWord.startsWith('/')) {
4037
+ setShowAutocomplete(true);
3407
4038
  }
3408
4039
  else {
3409
4040
  setShowAutocomplete(false);
@@ -3412,8 +4043,8 @@ SyntaxError: '(' was never closed
3412
4043
  // Handle autocomplete selection
3413
4044
  // viaEnter: true = Enter key or click (final selection), false = Tab key (navigation)
3414
4045
  const handleAutocompleteSelect = async (option, viaEnter) => {
3415
- // Check if this is an action command (like @reset)
3416
- if (option.id === '@reset' && option.is_complete) {
4046
+ // Check if this is an action command (like /reset, /compact)
4047
+ if (option.id === '/reset' && option.is_complete) {
3417
4048
  // Execute reset action
3418
4049
  await executeResetAction();
3419
4050
  setShowAutocomplete(false);
@@ -3421,6 +4052,23 @@ SyntaxError: '(' was never closed
3421
4052
  setInput('');
3422
4053
  return;
3423
4054
  }
4055
+ if (option.id === '/compact' && option.is_complete) {
4056
+ // Hide autocomplete immediately before async operation
4057
+ setShowAutocomplete(false);
4058
+ setHasAutocompleteOptions(false);
4059
+ setInput('');
4060
+ // Execute compact action
4061
+ await executeCompactAction();
4062
+ return;
4063
+ }
4064
+ if (option.id === '/help' && option.is_complete) {
4065
+ // Execute help action (no async, just display help message)
4066
+ executeHelpAction();
4067
+ setShowAutocomplete(false);
4068
+ setHasAutocompleteOptions(false);
4069
+ setInput('');
4070
+ return;
4071
+ }
3424
4072
  if (!textareaRef.current)
3425
4073
  return;
3426
4074
  const text = input;
@@ -3453,7 +4101,7 @@ SyntaxError: '(' was never closed
3453
4101
  }
3454
4102
  }, 0);
3455
4103
  };
3456
- // Execute @reset action
4104
+ // Execute /reset action
3457
4105
  const executeResetAction = async () => {
3458
4106
  try {
3459
4107
  // Reset both agent thread and conversation
@@ -3494,6 +4142,141 @@ SyntaxError: '(' was never closed
3494
4142
  setMessages(prev => [...prev, errorMessage]);
3495
4143
  }
3496
4144
  };
4145
+ // Execute /compact action - LLM-based conversation summarization
4146
+ const executeCompactAction = async () => {
4147
+ const convId = agentThreadId || conversationId;
4148
+ if (!convId) {
4149
+ const noSessionMessage = {
4150
+ id: `compact-info-${Date.now()}`,
4151
+ role: 'assistant',
4152
+ content: '세션이 없습니다. 대화를 먼저 시작해주세요.',
4153
+ timestamp: Date.now(),
4154
+ };
4155
+ setMessages(prev => [...prev, noSessionMessage]);
4156
+ return;
4157
+ }
4158
+ try {
4159
+ setIsCompacting(true);
4160
+ // Show compacting status (same as auto-compact)
4161
+ setDebugStatus({ status: '대화 컨텍스트 요약 중...', icon: 'thinking' });
4162
+ // Call compact API
4163
+ const result = await apiService.compactConversation(convId, llmConfig || undefined);
4164
+ if (result.success) {
4165
+ // Show completion status with message count (Claude Code style - status only, no chat message)
4166
+ const statusMsg = result.originalMessages && result.compressedMessages
4167
+ ? `대화가 압축되었습니다. (${result.originalMessages}개 → ${result.compressedMessages}개 메시지)`
4168
+ : '대화가 압축되었습니다.';
4169
+ setDebugStatus({ status: statusMsg, icon: 'check' });
4170
+ // Clear status after delay (summary stays in context only, not shown to user)
4171
+ setTimeout(() => setDebugStatus(null), 3000);
4172
+ }
4173
+ else {
4174
+ // Add error message
4175
+ setDebugStatus(null);
4176
+ const errorMessage = {
4177
+ id: `compact-error-${Date.now()}`,
4178
+ role: 'assistant',
4179
+ content: result.message,
4180
+ timestamp: Date.now(),
4181
+ };
4182
+ setMessages(prev => [...prev, errorMessage]);
4183
+ }
4184
+ console.log('[AgentPanel] Compact complete:', result);
4185
+ }
4186
+ catch (error) {
4187
+ console.error('[AgentPanel] Failed to compact conversation:', error);
4188
+ // Clear status and show error
4189
+ setDebugStatus(null);
4190
+ const errorMessage = {
4191
+ id: `compact-error-${Date.now()}`,
4192
+ role: 'assistant',
4193
+ content: `압축 실패: ${error}`,
4194
+ timestamp: Date.now(),
4195
+ };
4196
+ setMessages(prev => [...prev, errorMessage]);
4197
+ }
4198
+ finally {
4199
+ setIsCompacting(false);
4200
+ }
4201
+ };
4202
+ // Execute /help action - Display help guide (no LLM call)
4203
+ const executeHelpAction = () => {
4204
+ const helpContent = `## HDSP Agent 사용 가이드
4205
+
4206
+ ### 사용 가능한 명령어
4207
+
4208
+ | 명령어 | 설명 |
4209
+ |--------|------|
4210
+ | \`/help\` | 이 도움말 표시 |
4211
+ | \`/reset\` | 세션 초기화 및 대화 기록 삭제 |
4212
+ | \`/compact\` | 대화 기록 요약 (컨텍스트 절약) |
4213
+ | \`@file:경로\` | 파일 내용을 프롬프트에 포함 |
4214
+
4215
+ ### 현재 사용 가능한 기능
4216
+
4217
+ 현재는 **Chat 모드**만 사용 가능합니다. Chat 모드에서는 AI와 자유롭게 대화하며 질문하고 답변을 받을 수 있습니다.
4218
+
4219
+ ### Agent 모드 (출시 예정!)
4220
+
4221
+ **Agent 모드**가 곧 오픈될 예정입니다! Agent 모드에서는 AI가 단순 대화를 넘어 실제 작업을 수행합니다:
4222
+
4223
+ - **코드 자동 실행**: 데이터 분석, 시각화, 모델 학습 등을 직접 실행
4224
+ - **파일 읽기/수정**: 프로젝트 파일을 읽고 수정하여 코드 개선
4225
+ - **셸 명령 실행**: 패키지 설치, 스크립트 실행 등 터미널 작업 수행
4226
+ - **멀티스텝 작업**: 복잡한 작업을 여러 단계로 나눠 자동 처리
4227
+ - **오류 자동 수정**: 코드 실행 중 오류 발생 시 자동으로 분석하고 수정
4228
+
4229
+ 기대해 주세요!
4230
+
4231
+ ### 노트북 셀 아이콘 버튼
4232
+
4233
+ Jupyter 노트북의 각 코드 셀 옆에 있는 아이콘 버튼을 활용하세요:
4234
+
4235
+ | 아이콘 | 기능 | 설명 |
4236
+ |--------|------|------|
4237
+ | 💬 | **질문하기** | 선택한 셀 코드에 대해 자유롭게 질문 |
4238
+ | ✏️ | **수정 제안** | 코드 개선, 버그 수정, 최적화 제안 요청 |
4239
+ | 📖 | **코드 설명** | 코드의 동작 원리와 로직을 상세히 설명 |
4240
+
4241
+ 버튼 클릭 시 해당 셀의 코드가 자동으로 대화창에 포함됩니다.
4242
+
4243
+ ### 파일 참조 사용법
4244
+
4245
+ 입력창에 \`@file:\`을 입력하면 자동완성으로 파일을 선택할 수 있습니다.
4246
+
4247
+ **예시:**
4248
+ - \`@file:main.py 이 코드를 분석해줘\`
4249
+ - \`@file:src/utils.py 테스트 코드 작성해줘\`
4250
+
4251
+ ### 키보드 단축키
4252
+
4253
+ | 단축키 | 동작 |
4254
+ |--------|------|
4255
+ | \`Enter\` | 메시지 전송 |
4256
+ | \`Shift+Enter\` | 줄바꿈 |
4257
+ | \`Escape\` | 스트리밍 중지 / 자동완성 닫기 |
4258
+
4259
+ ### 설정
4260
+
4261
+ - 우측 상단 ⚙️ 아이콘으로 설정 패널 열기
4262
+ - **워크스페이스**: 파일 경로 기준 폴더 설정
4263
+ - **Temperature**: LLM 응답 다양성 조절 (0.0 = 일관됨, 1.0+ = 창의적)
4264
+
4265
+ ### 팁
4266
+
4267
+ - 긴 대화 시 \`/compact\`로 컨텍스트 절약
4268
+ - 에러 발생 시 \`/reset\`으로 세션 초기화
4269
+ - 코드 분석 시 \`@file:\`로 파일 직접 참조
4270
+ - 노트북 셀의 아이콘 버튼으로 빠르게 코드 질문하기`;
4271
+ const helpMessage = {
4272
+ id: `help-${Date.now()}`,
4273
+ role: 'assistant',
4274
+ content: helpContent,
4275
+ timestamp: Date.now(),
4276
+ };
4277
+ setMessages(prev => [...prev, helpMessage]);
4278
+ console.log('[AgentPanel] Help displayed');
4279
+ };
3497
4280
  const handleKeyDown = (e) => {
3498
4281
  // If autocomplete is visible AND has options, let it handle Tab/Enter
3499
4282
  if (showAutocomplete && hasAutocompleteOptions) {
@@ -3521,25 +4304,27 @@ SyntaxError: '(' was never closed
3521
4304
  e.preventDefault();
3522
4305
  handleSendMessage();
3523
4306
  }
3524
- // Shift+Tab: 모드 전환 (chat ↔ agent)
3525
- if (e.key === 'Tab' && e.shiftKey) {
4307
+ // Shift+Tab: 모드 전환 (chat ↔ agent) - admin only
4308
+ if (e.key === 'Tab' && e.shiftKey && isAdmin) {
3526
4309
  e.preventDefault();
3527
4310
  setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');
3528
4311
  return;
3529
4312
  }
3530
- // Cmd/Ctrl + . : 모드 전환 (대체 단축키)
3531
- if (e.key === '.' && (e.metaKey || e.ctrlKey)) {
4313
+ // Cmd/Ctrl + . : 모드 전환 (대체 단축키) - admin only
4314
+ if (e.key === '.' && (e.metaKey || e.ctrlKey) && isAdmin) {
3532
4315
  e.preventDefault();
3533
4316
  setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');
3534
4317
  }
3535
- // Tab (without Shift): Agent 모드일 때 드롭다운 토글
3536
- if (e.key === 'Tab' && !e.shiftKey && inputMode !== 'chat') {
4318
+ // Tab (without Shift): Agent 모드일 때 드롭다운 토글 - admin only
4319
+ if (e.key === 'Tab' && !e.shiftKey && inputMode !== 'chat' && isAdmin) {
3537
4320
  e.preventDefault();
3538
4321
  setShowModeDropdown(prev => !prev);
3539
4322
  }
3540
4323
  };
3541
- // 모드 토글 함수 (chat ↔ agent)
4324
+ // 모드 토글 함수 (chat ↔ agent) - admin only
3542
4325
  const toggleMode = () => {
4326
+ if (!isAdmin)
4327
+ return; // Non-admin users can only use chat mode
3543
4328
  setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');
3544
4329
  setShowModeDropdown(false);
3545
4330
  };
@@ -3582,7 +4367,7 @@ SyntaxError: '(' was never closed
3582
4367
  if (!debugStatus || typeof debugStatus === 'string')
3583
4368
  return '';
3584
4369
  if (debugStatus.icon) {
3585
- return (0,_utils_icons__WEBPACK_IMPORTED_MODULE_9__.getIconByName)(debugStatus.icon);
4370
+ return (0,_utils_icons__WEBPACK_IMPORTED_MODULE_11__.getIconByName)(debugStatus.icon);
3586
4371
  }
3587
4372
  return '';
3588
4373
  };
@@ -3606,11 +4391,33 @@ SyntaxError: '(' was never closed
3606
4391
  return null;
3607
4392
  };
3608
4393
  const statusText = getStatusText();
3609
- const hasActiveTodos = todos.some(todo => todo.status === 'pending' || todo.status === 'in_progress');
4394
+ // When stopped, treat in_progress as cancelled for rendering
4395
+ const displayTodos = isTodoStopped
4396
+ ? todos.map(t => t.status === 'in_progress' ? { ...t, status: 'cancelled' } : t)
4397
+ : todos;
4398
+ const hasActiveTodos = displayTodos.some(todo => todo.status === 'pending' || todo.status === 'in_progress');
3610
4399
  return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-panel" },
3611
- showSettings && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_SettingsPanel__WEBPACK_IMPORTED_MODULE_3__.SettingsPanel, { onClose: () => setShowSettings(false), onSave: handleSaveConfig, currentConfig: llmConfig || undefined })),
4400
+ showSettings && (isAdmin === true ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_AdminSettingsPanel__WEBPACK_IMPORTED_MODULE_4__.AdminSettingsPanel, { onClose: () => setShowSettings(false), onSave: (config) => {
4401
+ // Refresh llmConfig from localStorage after admin save
4402
+ loadConfig();
4403
+ console.log('[AgentPanel] Admin config saved');
4404
+ }, apiService: apiService })) : isAdmin === false ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_UserSettingsPanel__WEBPACK_IMPORTED_MODULE_5__.UserSettingsPanel, { onClose: () => setShowSettings(false), onSave: (config) => {
4405
+ // Update llmConfig with user settings
4406
+ if (llmConfig) {
4407
+ const updatedConfig = {
4408
+ ...llmConfig,
4409
+ workspaceRoot: config.workspaceRoot,
4410
+ autoApprove: config.autoApprove
4411
+ };
4412
+ (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_6__.saveLLMConfig)(updatedConfig);
4413
+ setLlmConfig(updatedConfig);
4414
+ }
4415
+ console.log('[AgentPanel] User config saved');
4416
+ }, apiService: apiService })) : (
4417
+ // Fallback to legacy SettingsPanel while checking admin mode
4418
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_SettingsPanel__WEBPACK_IMPORTED_MODULE_3__.SettingsPanel, { onClose: () => setShowSettings(false), onSave: handleSaveConfig, currentConfig: llmConfig || undefined }))),
3612
4419
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header" },
3613
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-logo", dangerouslySetInnerHTML: { __html: _logoSvg__WEBPACK_IMPORTED_MODULE_12__.headerLogoSvg } }),
4420
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-logo", dangerouslySetInnerHTML: { __html: _logoSvg__WEBPACK_IMPORTED_MODULE_14__.headerLogoSvg } }),
3614
4421
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-header-buttons" },
3615
4422
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-clear-button", onClick: clearChat, title: "\uB300\uD654 \uCD08\uAE30\uD654" },
3616
4423
  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" },
@@ -3717,7 +4524,7 @@ SyntaxError: '(' was never closed
3717
4524
  const decision = msg.metadata?.interrupt?.decision;
3718
4525
  const autoApproved = msg.metadata?.interrupt?.autoApproved;
3719
4526
  // 자동 승인일 때는 코드블럭 헤더에 배지가 표시되므로 actionHtml에는 표시하지 않음
3720
- const resolvedIcon = autoApproved ? '' : (decision === 'reject' ? _utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.rejectCircle : _utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.doubleCheck);
4527
+ const resolvedIcon = autoApproved ? '' : (decision === 'reject' ? _utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.rejectCircle : _utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.doubleCheck);
3721
4528
  const resolvedClass = autoApproved ? '' : (decision === 'reject' ? 'jp-agent-interrupt-actions--rejected' : 'jp-agent-interrupt-actions--resolved');
3722
4529
  const actionHtml = resolved && !autoApproved
3723
4530
  ? `<div class="jp-agent-interrupt-actions ${resolvedClass}">${resolvedIcon}</div>`
@@ -3725,8 +4532,8 @@ SyntaxError: '(' was never closed
3725
4532
  ? '' // 자동 승인일 때는 actionHtml 비움 (코드블럭 헤더에 배지 표시)
3726
4533
  : `
3727
4534
  <div class="code-block-actions jp-agent-interrupt-actions">
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>
4535
+ <button class="jp-agent-interrupt-approve-btn" data-action="approve" title="승인">${_utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.approveCircle}</button>
4536
+ <button class="jp-agent-interrupt-reject-btn" data-action="reject" title="거부">${_utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.rejectCircle}</button>
3730
4537
  </div>
3731
4538
  `;
3732
4539
  // Get tool description from args (for jupyter_cell_tool)
@@ -3734,15 +4541,15 @@ SyntaxError: '(' was never closed
3734
4541
  const renderedHtml = (() => {
3735
4542
  // markdown_tool: render content as HTML directly, not as code block
3736
4543
  let html = isMarkdownTool
3737
- ? (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_5__.formatMarkdownToHtml)(snippet)
3738
- : (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_5__.formatMarkdownToHtml)(`\n\`\`\`${language}\n${snippet}\n\`\`\``);
4544
+ ? (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_7__.formatMarkdownToHtml)(snippet)
4545
+ : (0,_utils_markdownRenderer__WEBPACK_IMPORTED_MODULE_7__.formatMarkdownToHtml)(`\n\`\`\`${language}\n${snippet}\n\`\`\``);
3739
4546
  if (isWriteFile && writePath) {
3740
4547
  const safePath = escapeHtml(writePath);
3741
4548
  html = html.replace(/<span class="code-block-language">[^<]*<\/span>/, `<span class="code-block-language jp-agent-interrupt-path">${safePath}</span>`);
3742
4549
  }
3743
4550
  if ((isEditFile || isMultiEditFile) && editPath) {
3744
4551
  const safePath = escapeHtml(editPath);
3745
- const editIcon = _utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.edit;
4552
+ const editIcon = _utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.edit;
3746
4553
  html = html.replace(/<span class="code-block-language">[^<]*<\/span>/, `<span class="code-block-language jp-agent-interrupt-path">${editIcon} ${safePath}</span>`);
3747
4554
  }
3748
4555
  // actionHtml이 비어있지 않을 때만 추가
@@ -3769,12 +4576,12 @@ SyntaxError: '(' was never closed
3769
4576
  .replace(/\n/g, ' ') // Remove actual newlines
3770
4577
  .replace(/\s+/g, ' ') // Collapse multiple spaces
3771
4578
  .trim();
3772
- result += `<div class="jp-agent-code-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.description} ${safeDescription}</div>`;
4579
+ result += `<div class="jp-agent-code-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.description} ${safeDescription}</div>`;
3773
4580
  }
3774
4581
  // 2. 승인 메시지 (자동 승인이 아닐 때만) - 코드 블록 바로 위
3775
4582
  if (!msg.metadata?.interrupt?.autoApproved && msg.content) {
3776
4583
  const safeContent = escapeHtml(msg.content);
3777
- result += `<div class="jp-agent-interrupt-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_9__.Icons.approvalWarning} ${safeContent}</div>`;
4584
+ result += `<div class="jp-agent-interrupt-description">${_utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.approvalWarning} ${safeContent}</div>`;
3778
4585
  }
3779
4586
  // 3. 코드 블록
3780
4587
  result += codeHtml;
@@ -3798,7 +4605,7 @@ SyntaxError: '(' was never closed
3798
4605
  })())))) : msg.role === 'assistant' ? (
3799
4606
  // Assistant(AI) 메시지: 스트리밍 지원 마크다운 렌더링
3800
4607
  // 이벤트 위임은 상위 messages-container에서 처리됨
3801
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_StreamingMessage__WEBPACK_IMPORTED_MODULE_7__.StreamingMessage, { content: msg.content, isStreaming: streamingMessageId === msg.id, onInsertAbove: handleInsertCodeAbove, onInsertBelow: handleInsertCodeBelow, onInsertAsCell: handleInsertCodeAsCell, cellActionsEnabled: !!getActiveNotebookPanel() })) : (
4608
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_StreamingMessage__WEBPACK_IMPORTED_MODULE_9__.StreamingMessage, { content: msg.content, isStreaming: streamingMessageId === msg.id, onInsertAbove: handleInsertCodeAbove, onInsertBelow: handleInsertCodeBelow, onInsertAsCell: handleInsertCodeAsCell, cellActionsEnabled: !!getActiveNotebookPanel() })) : (
3802
4609
  // User(사용자) 메시지: 텍스트 그대로 줄바꿈만 처리
3803
4610
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { style: { whiteSpace: 'pre-wrap' } }, msg.content)))));
3804
4611
  }
@@ -3846,8 +4653,11 @@ SyntaxError: '(' was never closed
3846
4653
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { className: `jp-agent-todo-expand-icon ${isTodoExpanded ? 'jp-agent-todo-expand-icon--expanded' : ''}`, viewBox: "0 0 16 16", fill: "currentColor", width: "12", height: "12" },
3847
4654
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M6 12l4-4-4-4" })),
3848
4655
  (() => {
3849
- const currentTodo = todos.find(t => t.status === 'in_progress') || todos.find(t => t.status === 'pending');
3850
4656
  const isStillWorking = isStreaming || isLoading || isAgentRunning;
4657
+ if (isTodoStopped) {
4658
+ return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current" }, "\uC791\uC5C5 \uC911\uB2E8\uB428");
4659
+ }
4660
+ const currentTodo = displayTodos.find(t => t.status === 'in_progress') || displayTodos.find(t => t.status === 'pending');
3851
4661
  if (currentTodo) {
3852
4662
  return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null,
3853
4663
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-compact-spinner" }),
@@ -3859,10 +4669,10 @@ SyntaxError: '(' was never closed
3859
4669
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current" }, "\uC791\uC5C5 \uB9C8\uBB34\uB9AC \uC911...")));
3860
4670
  }
3861
4671
  else {
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} 모든 작업 완료` } }));
4672
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-compact-current", dangerouslySetInnerHTML: { __html: `${_utils_icons__WEBPACK_IMPORTED_MODULE_11__.Icons.check} 모든 작업 완료` } }));
3863
4673
  }
3864
4674
  })()),
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) => {
4675
+ !isTodoStopped && (isStreaming || isLoading || isAgentRunning || displayTodos.some(t => t.status === 'in_progress')) && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-todo-stop-btn", onClick: (e) => {
3866
4676
  e.stopPropagation();
3867
4677
  console.log('[AgentPanel] Stop button clicked');
3868
4678
  stopCurrentTask();
@@ -3879,12 +4689,14 @@ SyntaxError: '(' was never closed
3879
4689
  getDebugStatusIcon() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-icon", dangerouslySetInnerHTML: { __html: getDebugStatusIcon() } })),
3880
4690
  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 },
3881
4691
  statusText,
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 } }))))))),
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}` },
4692
+ 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_12__["default"], { sx: { fontSize: 14 } }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_13__["default"], { sx: { fontSize: 14 } }))))))),
4693
+ isTodoExpanded && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-expanded" }, displayTodos.map((todo, index) => (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { key: index, className: `jp-agent-todo-item jp-agent-todo-item--${todo.status}` },
3884
4694
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-item-indicator" },
3885
4695
  todo.status === 'completed' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "12", height: "12" },
3886
4696
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z" }))),
3887
4697
  todo.status === 'in_progress' && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-todo-item-spinner" }),
4698
+ todo.status === 'cancelled' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "12", height: "12" },
4699
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("path", { d: "M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z" }))),
3888
4700
  todo.status === 'pending' && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-todo-item-number" }, index + 1)),
3889
4701
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: `jp-agent-todo-item-text ${todo.status === 'completed' ? 'jp-agent-todo-item-text--done' : ''}` }, todo.content)))))))),
3890
4702
  statusText && !hasActiveTodos && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `jp-agent-message jp-agent-message-debug jp-agent-message-debug--above-input${statusText.startsWith('오류:') ? ' jp-agent-message-debug-error' : ''}` },
@@ -3892,23 +4704,21 @@ SyntaxError: '(' was never closed
3892
4704
  getDebugStatusIcon() && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-debug-icon", dangerouslySetInnerHTML: { __html: getDebugStatusIcon() } })),
3893
4705
  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 },
3894
4706
  statusText,
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 } }))))))),
4707
+ 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_12__["default"], { sx: { fontSize: 14 } }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_ExpandMore__WEBPACK_IMPORTED_MODULE_13__["default"], { sx: { fontSize: 14 } }))))))),
3896
4708
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-input-container" },
3897
4709
  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 ?
4710
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_ContextAutocomplete__WEBPACK_IMPORTED_MODULE_10__.ContextAutocomplete, { apiService: apiService, inputValue: input, cursorPosition: cursorPosition, baseDir: notebookTracker?.currentWidget?.context?.path ?
3899
4711
  notebookTracker.currentWidget.context.path.replace(/[^/]*$/, '') : undefined, onSelect: handleAutocompleteSelect, onClose: () => { setShowAutocomplete(false); setHasAutocompleteOptions(false); }, onOptionsChange: setHasAutocompleteOptions, visible: showAutocomplete, anchorEl: textareaRef.current }),
3900
4712
  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
3901
4713
  ? '다른 방향 제시'
3902
4714
  : (inputMode === 'agent'
3903
4715
  ? '노트북 작업을 입력하세요... (예: @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 ? '거부' : '전송')))),
3911
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-bar" },
4716
+ : '메시지를 입력하세요... (@file:path로 파일 참조)'), disabled: isLoading || isAgentRunning }),
4717
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-button-container" }, inputMode === 'chat' && isStreaming ? (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-stop-button", onClick: stopCurrentTask, title: "\uC751\uB2F5 \uC911\uB2E8 (Esc)" },
4718
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "14", height: "14" },
4719
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1" })),
4720
+ "\uC911\uB2E8")) : (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 ? '거부' : '전송'))))),
4721
+ isAdmin && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-bar" },
3912
4722
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-toggle-container" },
3913
4723
  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)` },
3914
4724
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("svg", { className: "jp-agent-mode-icon", viewBox: inputMode === 'chat' ? "0 0 16 16" : "0 -960 960 960", fill: "currentColor", width: "14", height: "14" }, inputMode === 'chat' ? (
@@ -3931,8 +4741,8 @@ SyntaxError: '(' was never closed
3931
4741
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "Agent"),
3932
4742
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-shortcut" }, "\uB178\uD2B8\uBD81 \uC790\uB3D9 \uC2E4\uD589"))))),
3933
4743
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-mode-hints" },
3934
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-hint" }, "\u21E7Tab \uBAA8\uB4DC \uC804\uD658")))),
3935
- fileSelectionMetadata && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_FileSelectionDialog__WEBPACK_IMPORTED_MODULE_6__.FileSelectionDialog, { filename: fileSelectionMetadata.pattern, options: fileSelectionMetadata.options, message: fileSelectionMetadata.message, onSelect: handleFileSelect, onCancel: handleFileSelectCancel }))));
4744
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "jp-agent-mode-hint" }, "\u21E7Tab \uBAA8\uB4DC \uC804\uD658"))))),
4745
+ fileSelectionMetadata && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_FileSelectionDialog__WEBPACK_IMPORTED_MODULE_8__.FileSelectionDialog, { filename: fileSelectionMetadata.pattern, options: fileSelectionMetadata.options, message: fileSelectionMetadata.message, onSelect: handleFileSelect, onCancel: handleFileSelectCancel }))));
3936
4746
  });
3937
4747
  ChatPanel.displayName = 'ChatPanel';
3938
4748
  /**
@@ -4150,8 +4960,8 @@ function getPartialCommand(text, cursorPosition) {
4150
4960
  start--;
4151
4961
  }
4152
4962
  const word = text.slice(start, cursorPosition);
4153
- // Check if it looks like a context command
4154
- if (word.startsWith('@')) {
4963
+ // Check if it looks like a context command (@ for context, / for actions)
4964
+ if (word.startsWith('@') || word.startsWith('/')) {
4155
4965
  return word;
4156
4966
  }
4157
4967
  return null;
@@ -4662,9 +5472,12 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
4662
5472
  const [autoApprove, setAutoApprove] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(Boolean(initConfig.autoApprove));
4663
5473
  const [idleTimeoutMinutes, setIdleTimeoutMinutes] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.idleTimeoutMinutes ?? 60);
4664
5474
  // Multi-Agent settings
4665
- const [agentMode, setAgentMode] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.agentMode || 'single');
4666
5475
  const [agentPrompts, setAgentPrompts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.agentPrompts || {});
4667
5476
  const [expandedAgents, setExpandedAgents] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
5477
+ // Summarization LLM settings
5478
+ const [summarizationEnabled, setSummarizationEnabled] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(Boolean(initConfig.summarization?.enabled));
5479
+ const [summarizationProvider, setSummarizationProvider] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.summarization?.provider || 'gemini');
5480
+ const [summarizationModel, setSummarizationModel] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(initConfig.summarization?.model || '');
4668
5481
  // Default prompts fetched from API
4669
5482
  const [defaultPrompts, setDefaultPrompts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)((0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__.getCachedPrompts)());
4670
5483
  const [isLoadingPrompts, setIsLoadingPrompts] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(!(0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__.getCachedPrompts)());
@@ -4704,7 +5517,6 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
4704
5517
  setWorkspaceRoot(currentConfig.workspaceRoot || '');
4705
5518
  setAutoApprove(Boolean(currentConfig.autoApprove));
4706
5519
  setIdleTimeoutMinutes(currentConfig.idleTimeoutMinutes ?? 60);
4707
- setAgentMode(currentConfig.agentMode || 'single');
4708
5520
  // Use currentConfig prompts, or cached defaults, or empty object
4709
5521
  const cachedDefaults = (0,_services_ApiKeyManager__WEBPACK_IMPORTED_MODULE_10__.getCachedPrompts)();
4710
5522
  setAgentPrompts(currentConfig.agentPrompts || (cachedDefaults ? {
@@ -4713,6 +5525,10 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
4713
5525
  researcher: cachedDefaults.researcher,
4714
5526
  athena_query: cachedDefaults.athena_query,
4715
5527
  } : {}));
5528
+ // Summarization settings
5529
+ setSummarizationEnabled(Boolean(currentConfig.summarization?.enabled));
5530
+ setSummarizationProvider(currentConfig.summarization?.provider || 'gemini');
5531
+ setSummarizationModel(currentConfig.summarization?.model || '');
4716
5532
  }
4717
5533
  }, [currentConfig]);
4718
5534
  // Helper: Build LLM config from state
@@ -4736,8 +5552,12 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
4736
5552
  },
4737
5553
  workspaceRoot: workspaceRoot.trim() ? workspaceRoot.trim() : undefined,
4738
5554
  systemPrompt: systemPrompt && systemPrompt.trim() ? systemPrompt : undefined,
4739
- agentMode,
4740
5555
  agentPrompts,
5556
+ summarization: summarizationEnabled ? {
5557
+ enabled: true,
5558
+ provider: summarizationProvider,
5559
+ model: summarizationModel.trim() || undefined,
5560
+ } : undefined,
4741
5561
  autoApprove,
4742
5562
  idleTimeoutMinutes
4743
5563
  });
@@ -4911,6 +5731,28 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
4911
5731
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gpt-4" }, "GPT-4"),
4912
5732
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gpt-4-turbo" }, "GPT-4 Turbo"),
4913
5733
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gpt-3.5-turbo" }, "GPT-3.5 Turbo"))))),
5734
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-summarization" },
5735
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-summarization-header" },
5736
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label", style: { marginBottom: 0 } }, "\uC694\uC57D\uC6A9 LLM \uC124\uC815"),
5737
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", null, "\uB300\uD654 \uC555\uCD95 (/compact) \uBC0F Agent \uC694\uC57D\uC5D0 \uC0AC\uC6A9")),
5738
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-checkbox" },
5739
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "checkbox", checked: summarizationEnabled, onChange: (e) => setSummarizationEnabled(e.target.checked) }),
5740
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\uBCC4\uB3C4 LLM\uC73C\uB85C \uC694\uC57D \uC218\uD589 (\uBBF8\uC124\uC815 \uC2DC \uAE30\uBCF8 LLM \uC0AC\uC6A9)")),
5741
+ summarizationEnabled && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-summarization-content" },
5742
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
5743
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" }, "\uD504\uB85C\uBC14\uC774\uB354"),
5744
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", { className: "jp-agent-settings-select", value: summarizationProvider, onChange: (e) => setSummarizationProvider(e.target.value) },
5745
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "gemini" }, "Gemini (gemini-2.5-flash)"),
5746
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "openai" }, "OpenAI (gpt-4o-mini)"),
5747
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "vllm" }, "vLLM / OpenRouter")),
5748
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { className: "jp-agent-settings-summarization-hint" }, "\uAE30\uBCF8 \uC124\uC815\uC758 API \uD0A4\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4")),
5749
+ summarizationProvider === 'vllm' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
5750
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" }, "\uBAA8\uB378\uBA85 (\uC120\uD0DD)"),
5751
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "text", className: "jp-agent-settings-input", value: summarizationModel, onChange: (e) => setSummarizationModel(e.target.value), placeholder: "\uC608: mistralai/mistral-7b-instruct" }),
5752
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { className: "jp-agent-settings-summarization-hint" }, "\uBE44\uC6CC\uB450\uBA74 \uAE30\uBCF8 vLLM \uBAA8\uB378 \uC0AC\uC6A9"))),
5753
+ summarizationProvider !== 'vllm' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-summarization-default-model" },
5754
+ "\uAE30\uBCF8 \uBAA8\uB378: ",
5755
+ summarizationProvider === 'gemini' ? 'gemini-2.5-flash' : 'gpt-4o-mini'))))),
4914
5756
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
4915
5757
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" }, "\uC790\uB3D9 \uC2E4\uD589 \uC2B9\uC778"),
4916
5758
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-checkbox" },
@@ -4922,21 +5764,6 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
4922
5764
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, "\uBE44\uD65C\uB3D9 \uC2DC \uC790\uB3D9 \uC885\uB8CC (0 = \uBE44\uD65C\uC131\uD654)")),
4923
5765
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { id: "jp-agent-idle-timeout", type: "number", className: "jp-agent-settings-input", value: idleTimeoutMinutes, onChange: (e) => setIdleTimeoutMinutes(Math.max(0, parseInt(e.target.value) || 0)), min: 0, max: 1440, placeholder: "60", style: { width: '120px' }, "data-testid": "idle-timeout-input" })),
4924
5766
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
4925
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" }, "\uC5D0\uC774\uC804\uD2B8 \uBAA8\uB4DC"),
4926
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("select", { className: "jp-agent-settings-select", value: agentMode, onChange: (e) => setAgentMode(e.target.value) },
4927
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "single" }, "Single Agent (\uBAA8\uB4E0 \uB3C4\uAD6C \uD1B5\uD569)"),
4928
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("option", { value: "multi" }, "Multi-Agent (Planner + Subagents)")),
4929
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { color: '#666', marginTop: '4px', display: 'block' } }, agentMode === 'single'
4930
- ? '단일 에이전트가 모든 작업을 처리합니다.'
4931
- : 'Planner가 Python Developer, Researcher 등 전문 에이전트에게 작업을 위임합니다.')),
4932
- agentMode === 'single' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
4933
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" },
4934
- "System Prompt",
4935
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, "\uB2E8\uC77C \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC801\uC6A9\uB429\uB2C8\uB2E4.")),
4936
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("textarea", { className: "jp-agent-settings-input jp-agent-settings-textarea", value: systemPrompt, onChange: (e) => setSystemPrompt(e.target.value), rows: 8 }),
4937
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-inline-actions" },
4938
- react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-agent-settings-button jp-agent-settings-button-secondary jp-agent-settings-button-compact", onClick: () => defaultPrompts && setSystemPrompt(defaultPrompts.single), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값으로 되돌리기')))),
4939
- agentMode === 'multi' && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
4940
5767
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" },
4941
5768
  "\uC5D0\uC774\uC804\uD2B8\uBCC4 System Prompt",
4942
5769
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, "\uAC01 \uC5D0\uC774\uC804\uD2B8\uC758 \uC5ED\uD560\uACFC \uB3D9\uC791\uC744 \uC815\uC758\uD569\uB2C8\uB2E4.")),
@@ -5011,9 +5838,6 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
5011
5838
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { style: { marginTop: '12px' } },
5012
5839
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { type: "button", className: "jp-agent-settings-button jp-agent-settings-button-secondary", onClick: () => {
5013
5840
  if (defaultPrompts) {
5014
- // Reset single-mode prompt
5015
- setSystemPrompt(defaultPrompts.single);
5016
- // Reset multi-agent prompts
5017
5841
  setAgentPrompts({
5018
5842
  planner: defaultPrompts.planner,
5019
5843
  python_developer: defaultPrompts.python_developer,
@@ -5021,7 +5845,7 @@ const SettingsPanel = ({ onClose, onSave, currentConfig }) => {
5021
5845
  athena_query: defaultPrompts.athena_query,
5022
5846
  });
5023
5847
  }
5024
- }, disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '모든 프롬프트 기본값으로 되돌리기'))))),
5848
+ }, disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '모든 프롬프트 기본값으로 되돌리기')))),
5025
5849
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-footer" },
5026
5850
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-button jp-agent-settings-button-secondary", onClick: onClose }, "\uCDE8\uC18C"),
5027
5851
  react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-button jp-agent-settings-button-test", onClick: handleTest, disabled: isTesting }, isTesting ? '테스트 중...' : 'API 테스트'),
@@ -5133,6 +5957,9 @@ const StreamingMessage = ({ content, isStreaming = false, className = '', deboun
5133
5957
  const rendererRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
5134
5958
  const lastContentRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)('');
5135
5959
  const [codeToolbarDefs, setCodeToolbarDefs] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
5960
+ // Throttle refs for native Rendermime streaming render
5961
+ const pendingRenderRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
5962
+ const latestContentForRender = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)('');
5136
5963
  // Synchronously determine renderer type (useMemo instead of useState+useEffect)
5137
5964
  // Use fallback renderer for summary JSON (native renderer doesn't parse it)
5138
5965
  const useNativeRenderer = (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(() => {
@@ -5189,7 +6016,11 @@ const StreamingMessage = ({ content, isStreaming = false, className = '', deboun
5189
6016
  });
5190
6017
  setCodeToolbarDefs(newDefs);
5191
6018
  }, []);
6019
+ // Track whether renderer node is already mounted in DOM
6020
+ const rendererMountedRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);
5192
6021
  // Render content using Rendermime (native JupyterLab renderer)
6022
+ // Key optimization: renderer.node is inserted into the DOM once and stays there.
6023
+ // Subsequent renderModel() calls update it in-place — no DOM replacement, no height collapse.
5193
6024
  const renderWithRendermime = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(async (contentToRender) => {
5194
6025
  const rmRegistry = getGlobalRenderMimeRegistry();
5195
6026
  if (!rmRegistry || !containerRef.current) {
@@ -5215,16 +6046,19 @@ const StreamingMessage = ({ content, isStreaming = false, className = '', deboun
5215
6046
  // Create or reuse renderer
5216
6047
  if (!rendererRef.current) {
5217
6048
  rendererRef.current = rmRegistry.createRenderer(MD_MIME_TYPE);
6049
+ rendererMountedRef.current = false;
5218
6050
  }
5219
6051
  const renderer = rendererRef.current;
5220
- // Render markdown
6052
+ // renderModel() updates renderer.node in-place
5221
6053
  await renderer.renderModel(model);
5222
6054
  if (renderer.node && containerRef.current) {
5223
- // Find the content div
5224
6055
  const contentDiv = containerRef.current.querySelector('.jp-streaming-content');
5225
6056
  if (contentDiv) {
5226
- contentDiv.innerHTML = '';
5227
- contentDiv.appendChild(renderer.node.cloneNode(true));
6057
+ // Mount renderer node once — subsequent renderModel() calls update it in-place
6058
+ if (!rendererMountedRef.current || !contentDiv.contains(renderer.node)) {
6059
+ contentDiv.replaceChildren(renderer.node);
6060
+ rendererMountedRef.current = true;
6061
+ }
5228
6062
  // Apply LaTeX typesetting if available
5229
6063
  if (rmRegistry.latexTypesetter) {
5230
6064
  rmRegistry.latexTypesetter.typeset(contentDiv);
@@ -5242,26 +6076,38 @@ const StreamingMessage = ({ content, isStreaming = false, className = '', deboun
5242
6076
  return false;
5243
6077
  }
5244
6078
  }, [isStreaming, attachCodeToolbars]);
5245
- // Effect for rendering with Rendermime
6079
+ // Effect for rendering with Rendermime (throttle pattern during streaming)
6080
+ // Uses throttle instead of debounce: if tokens arrive faster than debounceMs,
6081
+ // debounce would keep resetting the timer and never render. Throttle ensures
6082
+ // rendering happens at most once per debounceMs window.
5246
6083
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
5247
6084
  if (!useNativeRenderer || !content) {
6085
+ // Clear pending render when not using native renderer or no content
6086
+ if (pendingRenderRef.current) {
6087
+ clearTimeout(pendingRenderRef.current);
6088
+ pendingRenderRef.current = null;
6089
+ }
5248
6090
  return;
5249
6091
  }
5250
- // Debounce during streaming
5251
- let timeoutId = null;
5252
- if (isStreaming) {
5253
- timeoutId = setTimeout(() => {
5254
- void renderWithRendermime(content);
5255
- }, debounceMs);
5256
- }
5257
- else {
6092
+ if (!isStreaming) {
6093
+ // Not streaming: render immediately, clear any pending throttle
6094
+ if (pendingRenderRef.current) {
6095
+ clearTimeout(pendingRenderRef.current);
6096
+ pendingRenderRef.current = null;
6097
+ }
5258
6098
  void renderWithRendermime(content);
6099
+ return;
5259
6100
  }
5260
- return () => {
5261
- if (timeoutId) {
5262
- clearTimeout(timeoutId);
5263
- }
5264
- };
6101
+ // Streaming: throttle — always track latest content, schedule only if none pending
6102
+ latestContentForRender.current = content;
6103
+ if (!pendingRenderRef.current) {
6104
+ pendingRenderRef.current = setTimeout(() => {
6105
+ pendingRenderRef.current = null;
6106
+ void renderWithRendermime(latestContentForRender.current);
6107
+ }, debounceMs);
6108
+ }
6109
+ // No cleanup that clears the timeout — throttle pattern intentionally
6110
+ // lets the scheduled render fire even when content updates again
5265
6111
  }, [content, isStreaming, useNativeRenderer, debounceMs, renderWithRendermime]);
5266
6112
  // Attach toolbars when streaming stops (only for native renderer)
5267
6113
  // Note: Fallback renderer has its own code-block-header with buttons
@@ -5287,13 +6133,18 @@ const StreamingMessage = ({ content, isStreaming = false, className = '', deboun
5287
6133
  }
5288
6134
  }
5289
6135
  }, [useNativeRenderer, renderedHtml, isStreaming, attachCodeToolbars]);
5290
- // Cleanup renderer and toolbar roots on unmount
6136
+ // Cleanup renderer, throttle timer, and toolbar roots on unmount
5291
6137
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
5292
6138
  return () => {
5293
6139
  if (rendererRef.current && rendererRef.current.dispose) {
5294
6140
  rendererRef.current.dispose();
5295
6141
  rendererRef.current = null;
5296
6142
  }
6143
+ rendererMountedRef.current = false;
6144
+ if (pendingRenderRef.current) {
6145
+ clearTimeout(pendingRenderRef.current);
6146
+ pendingRenderRef.current = null;
6147
+ }
5297
6148
  // Clean up toolbar roots
5298
6149
  codeToolbarDefs.forEach(def => {
5299
6150
  if (def.root.parentNode) {
@@ -5468,6 +6319,122 @@ const TaskProgressWidget = ({ taskStatus, onClose, onCancel, onOpenNotebook }) =
5468
6319
  };
5469
6320
 
5470
6321
 
6322
+ /***/ },
6323
+
6324
+ /***/ "./lib/components/UserSettingsPanel.js"
6325
+ /*!*********************************************!*\
6326
+ !*** ./lib/components/UserSettingsPanel.js ***!
6327
+ \*********************************************/
6328
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
6329
+
6330
+ __webpack_require__.r(__webpack_exports__);
6331
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
6332
+ /* harmony export */ UserSettingsPanel: () => (/* binding */ UserSettingsPanel)
6333
+ /* harmony export */ });
6334
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "webpack/sharing/consume/default/react");
6335
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
6336
+ /* harmony import */ var _mui_icons_material_HourglassEmpty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @mui/icons-material/HourglassEmpty */ "./node_modules/@mui/icons-material/esm/HourglassEmpty.js");
6337
+ /**
6338
+ * User Settings Panel Component
6339
+ *
6340
+ * User preferences configuration:
6341
+ * - Workspace root path
6342
+ * - Temperature setting
6343
+ * - Auto-approve toggle
6344
+ */
6345
+
6346
+
6347
+ const UserSettingsPanel = ({ onClose, onSave, apiService }) => {
6348
+ const [isLoading, setIsLoading] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(true);
6349
+ const [isSaving, setIsSaving] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
6350
+ const [error, setError] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
6351
+ // User settings
6352
+ const [workspaceRoot, setWorkspaceRoot] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)('');
6353
+ const [temperature, setTemperature] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(0.7);
6354
+ const [autoApprove, setAutoApprove] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
6355
+ // Load config on mount
6356
+ (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
6357
+ loadConfig();
6358
+ }, []);
6359
+ const loadConfig = async () => {
6360
+ try {
6361
+ setIsLoading(true);
6362
+ const config = await apiService.getUserConfig();
6363
+ setWorkspaceRoot(config.workspaceRoot || '');
6364
+ setTemperature(config.temperature ?? 0.7);
6365
+ setAutoApprove(Boolean(config.autoApprove));
6366
+ setError(null);
6367
+ }
6368
+ catch (err) {
6369
+ setError(`설정을 불러오는데 실패했습니다: ${err}`);
6370
+ }
6371
+ finally {
6372
+ setIsLoading(false);
6373
+ }
6374
+ };
6375
+ const handleSave = async () => {
6376
+ try {
6377
+ setIsSaving(true);
6378
+ const config = {
6379
+ workspaceRoot: workspaceRoot.trim() || '',
6380
+ temperature,
6381
+ autoApprove
6382
+ };
6383
+ await apiService.updateUserConfig(config);
6384
+ onSave(config);
6385
+ onClose();
6386
+ }
6387
+ catch (err) {
6388
+ setError(`설정 저장에 실패했습니다: ${err}`);
6389
+ }
6390
+ finally {
6391
+ setIsSaving(false);
6392
+ }
6393
+ };
6394
+ if (isLoading) {
6395
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-overlay" },
6396
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-dialog", style: { maxWidth: '400px' } },
6397
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-content", style: { textAlign: 'center', padding: '40px' } },
6398
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_mui_icons_material_HourglassEmpty__WEBPACK_IMPORTED_MODULE_1__["default"], { sx: { fontSize: 48, color: 'info.main' } }),
6399
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("p", null, "\uC124\uC815\uC744 \uBD88\uB7EC\uC624\uB294 \uC911...")))));
6400
+ }
6401
+ return (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-overlay" },
6402
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-dialog", style: { maxWidth: '400px' } },
6403
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-header" },
6404
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("h2", null, "\uC0AC\uC6A9\uC790 \uC124\uC815"),
6405
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-close", onClick: onClose, title: "\uB2EB\uAE30" }, "\u00D7")),
6406
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-content" },
6407
+ error && (react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-error", style: { color: 'red', marginBottom: '12px' } }, error)),
6408
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-notice" },
6409
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\uAC1C\uC778 \uD658\uACBD\uC124\uC815\uC744 \uAD00\uB9AC\uD569\uB2C8\uB2E4.")),
6410
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
6411
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label", htmlFor: "jp-user-workspace-root" },
6412
+ "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8",
6413
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, "\uBE44\uC6B0\uBA74 \uD604\uC7AC \uB178\uD2B8\uBD81 \uD3F4\uB354 \uAE30\uC900")),
6414
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { id: "jp-user-workspace-root", type: "text", className: "jp-agent-settings-input", value: workspaceRoot, onChange: (e) => setWorkspaceRoot(e.target.value), placeholder: "\uC608: /Users/you/project \uB610\uB294 ./my-project" }),
6415
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { color: '#666', display: 'block', marginTop: '4px' } }, "\uC808\uB300 \uACBD\uB85C \uB610\uB294 \uC0C1\uB300 \uACBD\uB85C \uBAA8\uB450 \uC0AC\uC6A9 \uAC00\uB2A5\uD569\uB2C8\uB2E4.")),
6416
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
6417
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label", htmlFor: "jp-user-temperature" },
6418
+ "Temperature",
6419
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, "LLM \uC751\uB2F5\uC758 \uCC3D\uC758\uC131 \uC870\uC808")),
6420
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '12px' } },
6421
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { id: "jp-user-temperature", type: "range", min: 0, max: 2, step: 0.1, value: temperature, onChange: (e) => setTemperature(parseFloat(e.target.value)), style: { flex: 1 } }),
6422
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "number", className: "jp-agent-settings-input", value: temperature, onChange: (e) => setTemperature(Math.max(0, Math.min(2, parseFloat(e.target.value) || 0))), min: 0, max: 2, step: 0.1, style: { width: '70px' } })),
6423
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { color: '#666', display: 'block', marginTop: '4px' } }, "0.0 = \uACB0\uC815\uC801 (\uC77C\uAD00\uB41C \uC751\uB2F5), 1.0+ = \uCC3D\uC758\uC801 (\uB2E4\uC591\uD55C \uC751\uB2F5)")),
6424
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-group" },
6425
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-label" }, "\uC790\uB3D9 \uC2E4\uD589 \uC2B9\uC778"),
6426
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { className: "jp-agent-settings-checkbox" },
6427
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", { type: "checkbox", checked: autoApprove, onChange: (e) => setAutoApprove(e.target.checked) }),
6428
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, "\uC2B9\uC778 \uC5C6\uC774 \uBC14\uB85C \uC2E4\uD589 (\uCF54\uB4DC/\uD30C\uC77C/\uC178 \uD3EC\uD568)")),
6429
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("small", { style: { color: '#666', display: 'block', marginTop: '4px' } }, autoApprove
6430
+ ? '주의: 에이전트가 생성한 코드가 자동으로 실행됩니다.'
6431
+ : '에이전트가 코드 실행, 파일 수정, 셸 명령 실행 전 승인을 요청합니다.'))),
6432
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "jp-agent-settings-footer" },
6433
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-button jp-agent-settings-button-secondary", onClick: onClose }, "\uCDE8\uC18C"),
6434
+ react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "jp-agent-settings-button jp-agent-settings-button-primary", onClick: handleSave, disabled: isSaving }, isSaving ? '저장 중...' : '저장')))));
6435
+ };
6436
+
6437
+
5471
6438
  /***/ },
5472
6439
 
5473
6440
  /***/ "./lib/components/code-blocks/CodeToolbar.js"
@@ -5867,6 +6834,8 @@ function useStreamingMarkdown(content, options = {}) {
5867
6834
  const lastContentRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)('');
5868
6835
  const lastRenderedRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)('');
5869
6836
  const renderCountRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(0);
6837
+ // Ref to track latest streaming content for throttle pattern
6838
+ const latestStreamContentRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)('');
5870
6839
  // Analyze incomplete blocks
5871
6840
  const incompleteBlockInfo = (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(() => analyzeIncompleteMarkdown(content), [content]);
5872
6841
  const hasIncompleteBlocks = (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(() => hasAnyIncompleteBlocks(incompleteBlockInfo), [incompleteBlockInfo]);
@@ -5909,40 +6878,48 @@ function useStreamingMarkdown(content, options = {}) {
5909
6878
  }
5910
6879
  setIsRendering(false);
5911
6880
  }, [isStreaming, hasIncompleteBlocks, incompleteBlockInfo, minContentLength]);
5912
- // Effect for debounced rendering
6881
+ // Effect for throttled rendering during streaming
6882
+ // Uses throttle instead of debounce: debounce resets the timer on every content
6883
+ // change, so if tokens arrive faster than debounceMs, rendering never fires.
6884
+ // Throttle schedules a render and lets it fire even when content updates again,
6885
+ // ensuring at least one render per debounceMs window.
5913
6886
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
5914
- // Clear any pending timeout
5915
- if (timeoutRef.current) {
5916
- clearTimeout(timeoutRef.current);
5917
- timeoutRef.current = null;
5918
- }
5919
6887
  // Empty content - clear immediately
5920
6888
  if (!content) {
5921
6889
  setRenderedHtml('');
5922
6890
  setIsRendering(false);
5923
6891
  lastContentRef.current = '';
5924
6892
  lastRenderedRef.current = '';
6893
+ latestStreamContentRef.current = '';
6894
+ if (timeoutRef.current) {
6895
+ clearTimeout(timeoutRef.current);
6896
+ timeoutRef.current = null;
6897
+ }
5925
6898
  return;
5926
6899
  }
5927
6900
  // Force immediate render (e.g., when streaming stops)
5928
6901
  if (forceImmediate || !isStreaming) {
6902
+ if (timeoutRef.current) {
6903
+ clearTimeout(timeoutRef.current);
6904
+ timeoutRef.current = null;
6905
+ }
5929
6906
  renderContent(content, true);
5930
6907
  return;
5931
6908
  }
5932
- // Debounced render during streaming
6909
+ // Streaming: throttle pattern — always track latest content,
6910
+ // only schedule a new render if none is pending
5933
6911
  setIsRendering(true);
5934
- // Dynamic debounce: shorter delay for longer content (user is waiting)
5935
- const dynamicDelay = content.length > 500 ? debounceMs * 0.5 : debounceMs;
5936
- timeoutRef.current = setTimeout(() => {
5937
- renderContent(content);
5938
- }, dynamicDelay);
5939
- // Cleanup
5940
- return () => {
5941
- if (timeoutRef.current) {
5942
- clearTimeout(timeoutRef.current);
6912
+ latestStreamContentRef.current = content;
6913
+ if (!timeoutRef.current) {
6914
+ // Dynamic delay: shorter for longer content (user is waiting)
6915
+ const dynamicDelay = content.length > 500 ? debounceMs * 0.5 : debounceMs;
6916
+ timeoutRef.current = setTimeout(() => {
5943
6917
  timeoutRef.current = null;
5944
- }
5945
- };
6918
+ renderContent(latestStreamContentRef.current);
6919
+ }, dynamicDelay);
6920
+ }
6921
+ // No cleanup that clears the timeout — throttle pattern intentionally
6922
+ // lets the scheduled render fire even when content updates again
5946
6923
  }, [content, isStreaming, forceImmediate, debounceMs, renderContent]);
5947
6924
  // Final render when streaming stops
5948
6925
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
@@ -8849,7 +9826,6 @@ __webpack_require__.r(__webpack_exports__);
8849
9826
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
8850
9827
  /* harmony export */ DEFAULT_AGENT_PROMPTS: () => (/* binding */ DEFAULT_AGENT_PROMPTS),
8851
9828
  /* harmony export */ DEFAULT_ATHENA_QUERY_PROMPT: () => (/* binding */ DEFAULT_ATHENA_QUERY_PROMPT),
8852
- /* harmony export */ DEFAULT_LANGCHAIN_SYSTEM_PROMPT: () => (/* binding */ DEFAULT_LANGCHAIN_SYSTEM_PROMPT),
8853
9829
  /* harmony export */ DEFAULT_PLANNER_PROMPT: () => (/* binding */ DEFAULT_PLANNER_PROMPT),
8854
9830
  /* harmony export */ DEFAULT_PYTHON_DEVELOPER_PROMPT: () => (/* binding */ DEFAULT_PYTHON_DEVELOPER_PROMPT),
8855
9831
  /* harmony export */ DEFAULT_RESEARCHER_PROMPT: () => (/* binding */ DEFAULT_RESEARCHER_PROMPT),
@@ -8919,7 +9895,6 @@ async function fetchDefaultPrompts() {
8919
9895
  console.error('[ApiKeyManager] Failed to fetch default prompts:', error);
8920
9896
  // Return fallback
8921
9897
  return {
8922
- single: FALLBACK_PROMPT,
8923
9898
  planner: FALLBACK_PROMPT,
8924
9899
  python_developer: FALLBACK_PROMPT,
8925
9900
  researcher: FALLBACK_PROMPT,
@@ -8940,7 +9915,6 @@ function clearCachedPrompts() {
8940
9915
  cachedPrompts = null;
8941
9916
  }
8942
9917
  // Legacy exports for backward compatibility (will be populated after fetch)
8943
- let DEFAULT_LANGCHAIN_SYSTEM_PROMPT = FALLBACK_PROMPT;
8944
9918
  let DEFAULT_PLANNER_PROMPT = FALLBACK_PROMPT;
8945
9919
  let DEFAULT_PYTHON_DEVELOPER_PROMPT = FALLBACK_PROMPT;
8946
9920
  let DEFAULT_RESEARCHER_PROMPT = FALLBACK_PROMPT;
@@ -8957,7 +9931,6 @@ let DEFAULT_AGENT_PROMPTS = {
8957
9931
  */
8958
9932
  async function initializePrompts() {
8959
9933
  const prompts = await fetchDefaultPrompts();
8960
- DEFAULT_LANGCHAIN_SYSTEM_PROMPT = prompts.single;
8961
9934
  DEFAULT_PLANNER_PROMPT = prompts.planner;
8962
9935
  DEFAULT_PYTHON_DEVELOPER_PROMPT = prompts.python_developer;
8963
9936
  DEFAULT_RESEARCHER_PROMPT = prompts.researcher;
@@ -9057,8 +10030,6 @@ function getDefaultLLMConfig() {
9057
10030
  endpoint: 'http://localhost:8000/v1',
9058
10031
  model: 'default'
9059
10032
  },
9060
- systemPrompt: prompts?.single || DEFAULT_LANGCHAIN_SYSTEM_PROMPT,
9061
- agentMode: 'single',
9062
10033
  agentPrompts: prompts ? {
9063
10034
  planner: prompts.planner,
9064
10035
  python_developer: prompts.python_developer,
@@ -9474,7 +10445,7 @@ class ApiService {
9474
10445
  * 단순 Chat용 스트리밍 - /chat/stream 사용
9475
10446
  * 원래 main 브랜치의 구현 복원 - LangChain 에이전트 없이 단순 Q&A
9476
10447
  */
9477
- async sendChatStream(request, onChunk, onMetadata, abortSignal) {
10448
+ async sendChatStream(request, onChunk, onMetadata, abortSignal, onDebug) {
9478
10449
  const MAX_RETRIES = 10;
9479
10450
  let currentConfig = request.llmConfig;
9480
10451
  let lastError = null;
@@ -9483,7 +10454,7 @@ class ApiService {
9483
10454
  ? { ...request, llmConfig: (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.buildSingleKeyConfig)(currentConfig) }
9484
10455
  : request;
9485
10456
  try {
9486
- await this.sendChatStreamInternal(requestToSend, onChunk, onMetadata, abortSignal);
10457
+ await this.sendChatStreamInternal(requestToSend, onChunk, onMetadata, abortSignal, onDebug);
9487
10458
  (0,_ApiKeyManager__WEBPACK_IMPORTED_MODULE_0__.resetKeyRotation)();
9488
10459
  return;
9489
10460
  }
@@ -9514,7 +10485,7 @@ class ApiService {
9514
10485
  /**
9515
10486
  * 단순 Chat 스트리밍 내부 구현 - /chat/stream 엔드포인트 사용
9516
10487
  */
9517
- async sendChatStreamInternal(request, onChunk, onMetadata, abortSignal) {
10488
+ async sendChatStreamInternal(request, onChunk, onMetadata, abortSignal, onDebug) {
9518
10489
  const response = await fetch(`${this.baseUrl}/chat/stream`, {
9519
10490
  method: 'POST',
9520
10491
  headers: this.getHeaders(),
@@ -9547,6 +10518,15 @@ class ApiService {
9547
10518
  if (data.error) {
9548
10519
  throw new Error(data.error);
9549
10520
  }
10521
+ // Handle status events (auto-compact, etc.)
10522
+ if (data.status && onDebug) {
10523
+ if (data.icon) {
10524
+ onDebug({ status: data.status, icon: data.icon });
10525
+ }
10526
+ else {
10527
+ onDebug(data.status);
10528
+ }
10529
+ }
9550
10530
  if (data.content) {
9551
10531
  onChunk(data.content);
9552
10532
  }
@@ -9660,9 +10640,7 @@ class ApiService {
9660
10640
  recent_cells: request.context.selectedCells?.map(cell => ({ source: cell })) || []
9661
10641
  } : undefined,
9662
10642
  llmConfig: requestConfig,
9663
- workspaceRoot: request.llmConfig?.workspaceRoot || '.',
9664
- // Agent mode: single (default) or multi (planner + subagents)
9665
- agentMode: request.llmConfig?.agentMode || 'single'
10643
+ workspaceRoot: request.llmConfig?.workspaceRoot || '.'
9666
10644
  };
9667
10645
  // Debug: log request size
9668
10646
  const requestBody = JSON.stringify(langchainRequest);
@@ -9720,6 +10698,15 @@ class ApiService {
9720
10698
  currentEventType = '';
9721
10699
  continue;
9722
10700
  }
10701
+ // Handle summary event (final_summary_tool result)
10702
+ if (currentEventType === 'summary' && data.summary !== undefined) {
10703
+ if (onChunk) {
10704
+ // Convert to JSON string so StreamingMessage can detect and render as summary card
10705
+ onChunk(JSON.stringify(data));
10706
+ }
10707
+ currentEventType = '';
10708
+ continue;
10709
+ }
9723
10710
  if (currentEventType === 'complete' || currentEventType === 'done') {
9724
10711
  properlyTerminated = true;
9725
10712
  if (onDebugClear) {
@@ -9875,9 +10862,7 @@ class ApiService {
9875
10862
  feedback
9876
10863
  }],
9877
10864
  llmConfig: requestConfig,
9878
- workspaceRoot: llmConfig?.workspaceRoot || '.',
9879
- // Agent mode: single (default) or multi (planner + subagents)
9880
- agentMode: llmConfig?.agentMode || 'single'
10865
+ workspaceRoot: llmConfig?.workspaceRoot || '.'
9881
10866
  };
9882
10867
  const response = await fetch(`${this.baseUrl}/agent/langchain/resume`, {
9883
10868
  method: 'POST',
@@ -9933,6 +10918,15 @@ class ApiService {
9933
10918
  currentEventType = '';
9934
10919
  continue;
9935
10920
  }
10921
+ // Handle summary event (final_summary_tool result)
10922
+ if (currentEventType === 'summary' && data.summary !== undefined) {
10923
+ if (onChunk) {
10924
+ // Convert to JSON string so StreamingMessage can detect and render as summary card
10925
+ onChunk(JSON.stringify(data));
10926
+ }
10927
+ currentEventType = '';
10928
+ continue;
10929
+ }
9936
10930
  if (currentEventType === 'complete' || currentEventType === 'done') {
9937
10931
  properlyTerminated = true;
9938
10932
  if (onDebugClear) {
@@ -10304,6 +11298,81 @@ class ApiService {
10304
11298
  }
10305
11299
  return response.json();
10306
11300
  }
11301
+ // ═══════════════════════════════════════════════════════════════════════════
11302
+ // Admin/User Config APIs (Settings Split)
11303
+ // ═══════════════════════════════════════════════════════════════════════════
11304
+ /**
11305
+ * Check if current environment is in admin mode
11306
+ */
11307
+ async checkIsAdmin() {
11308
+ const response = await fetch(`${this.baseUrl}/config/is-admin`, {
11309
+ method: 'GET',
11310
+ credentials: 'include'
11311
+ });
11312
+ if (!response.ok) {
11313
+ throw new Error('Failed to check admin mode');
11314
+ }
11315
+ return response.json();
11316
+ }
11317
+ /**
11318
+ * Get admin configuration (LLM settings, prompts, server URLs)
11319
+ * API keys are masked for security
11320
+ */
11321
+ async getAdminConfig() {
11322
+ const response = await fetch(`${this.baseUrl}/config/admin`, {
11323
+ method: 'GET',
11324
+ credentials: 'include'
11325
+ });
11326
+ if (!response.ok) {
11327
+ throw new Error('Failed to load admin configuration');
11328
+ }
11329
+ return response.json();
11330
+ }
11331
+ /**
11332
+ * Update admin configuration
11333
+ */
11334
+ async updateAdminConfig(updates) {
11335
+ const response = await fetch(`${this.baseUrl}/config/admin`, {
11336
+ method: 'POST',
11337
+ headers: this.getHeaders(),
11338
+ credentials: 'include',
11339
+ body: JSON.stringify(updates)
11340
+ });
11341
+ if (!response.ok) {
11342
+ const error = await response.text();
11343
+ throw new Error(`Failed to update admin configuration: ${error}`);
11344
+ }
11345
+ return response.json();
11346
+ }
11347
+ /**
11348
+ * Get user configuration (preferences)
11349
+ */
11350
+ async getUserConfig() {
11351
+ const response = await fetch(`${this.baseUrl}/config/user`, {
11352
+ method: 'GET',
11353
+ credentials: 'include'
11354
+ });
11355
+ if (!response.ok) {
11356
+ throw new Error('Failed to load user configuration');
11357
+ }
11358
+ return response.json();
11359
+ }
11360
+ /**
11361
+ * Update user configuration
11362
+ */
11363
+ async updateUserConfig(updates) {
11364
+ const response = await fetch(`${this.baseUrl}/config/user`, {
11365
+ method: 'POST',
11366
+ headers: this.getHeaders(),
11367
+ credentials: 'include',
11368
+ body: JSON.stringify(updates)
11369
+ });
11370
+ if (!response.ok) {
11371
+ const error = await response.text();
11372
+ throw new Error(`Failed to update user configuration: ${error}`);
11373
+ }
11374
+ return response.json();
11375
+ }
10307
11376
  /**
10308
11377
  * Get health status
10309
11378
  */
@@ -10481,6 +11550,34 @@ class ApiService {
10481
11550
  return response.json();
10482
11551
  }
10483
11552
  // ═══════════════════════════════════════════════════════════════════════════
11553
+ // Chat Compact API (Conversation Summarization)
11554
+ // ═══════════════════════════════════════════════════════════════════════════
11555
+ /**
11556
+ * Compact conversation history by summarizing older messages.
11557
+ *
11558
+ * Strategy (Claude Code benchmark):
11559
+ * - Keep the last 3 messages intact
11560
+ * - Summarize all older messages using LLM
11561
+ * - Replace history with [summary] + [last 3 messages]
11562
+ */
11563
+ async compactConversation(conversationId, llmConfig) {
11564
+ console.log('[ApiService] compactConversation:', conversationId);
11565
+ const response = await fetch(`${this.baseUrl}/chat/compact`, {
11566
+ method: 'POST',
11567
+ headers: this.getHeaders(),
11568
+ credentials: 'include',
11569
+ body: JSON.stringify({
11570
+ conversationId,
11571
+ llmConfig
11572
+ })
11573
+ });
11574
+ if (!response.ok) {
11575
+ const error = await response.text();
11576
+ throw new Error(`Failed to compact conversation: ${error}`);
11577
+ }
11578
+ return response.json();
11579
+ }
11580
+ // ═══════════════════════════════════════════════════════════════════════════
10484
11581
  // Context Provider APIs (@file autocomplete)
10485
11582
  // ═══════════════════════════════════════════════════════════════════════════
10486
11583
  /**
@@ -12422,4 +13519,4 @@ __webpack_require__.r(__webpack_exports__);
12422
13519
  /***/ }
12423
13520
 
12424
13521
  }]);
12425
- //# sourceMappingURL=lib_index_js.67505497667f9c0a763d.js.map
13522
+ //# sourceMappingURL=lib_index_js.df05d90f366bfd5fa023.js.map