hdsp-jupyter-extension 2.0.23__py3-none-any.whl → 2.0.26__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.
- agent_server/context_providers/__init__.py +22 -0
- agent_server/context_providers/actions.py +45 -0
- agent_server/context_providers/base.py +231 -0
- agent_server/context_providers/file.py +316 -0
- agent_server/context_providers/processor.py +150 -0
- agent_server/langchain/agent_factory.py +14 -14
- agent_server/langchain/agent_prompts/planner_prompt.py +13 -19
- agent_server/langchain/custom_middleware.py +73 -17
- agent_server/langchain/models/gpt_oss_chat.py +26 -13
- agent_server/langchain/prompts.py +11 -8
- agent_server/langchain/tools/jupyter_tools.py +43 -0
- agent_server/main.py +2 -1
- agent_server/routers/chat.py +61 -10
- agent_server/routers/context.py +168 -0
- agent_server/routers/langchain_agent.py +806 -203
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.23.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.96745acc14125453fba8.js → hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js +245 -121
- hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js.map +1 -0
- jupyter_ext/labextension/static/lib_index_js.2d5ea542350862f7c531.js → hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js +583 -39
- hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.67505497667f9c0a763d.js.map +1 -0
- hdsp_jupyter_extension-2.0.23.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.f0127d8744730f2092c1.js → hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.0fe2dcbbd176ee0efceb.js +3 -3
- hdsp_jupyter_extension-2.0.23.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.f0127d8744730f2092c1.js.map → hdsp_jupyter_extension-2.0.26.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.0fe2dcbbd176ee0efceb.js.map +1 -1
- {hdsp_jupyter_extension-2.0.23.dist-info → hdsp_jupyter_extension-2.0.26.dist-info}/METADATA +1 -1
- {hdsp_jupyter_extension-2.0.23.dist-info → hdsp_jupyter_extension-2.0.26.dist-info}/RECORD +56 -50
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +29 -0
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +2 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.96745acc14125453fba8.js → frontend_styles_index_js.b5e4416b4e07ec087aad.js} +245 -121
- jupyter_ext/labextension/static/frontend_styles_index_js.b5e4416b4e07ec087aad.js.map +1 -0
- hdsp_jupyter_extension-2.0.23.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.2d5ea542350862f7c531.js → jupyter_ext/labextension/static/lib_index_js.67505497667f9c0a763d.js +583 -39
- jupyter_ext/labextension/static/lib_index_js.67505497667f9c0a763d.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.f0127d8744730f2092c1.js → remoteEntry.0fe2dcbbd176ee0efceb.js} +3 -3
- jupyter_ext/labextension/static/{remoteEntry.f0127d8744730f2092c1.js.map → remoteEntry.0fe2dcbbd176ee0efceb.js.map} +1 -1
- hdsp_jupyter_extension-2.0.23.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.96745acc14125453fba8.js.map +0 -1
- hdsp_jupyter_extension-2.0.23.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.2d5ea542350862f7c531.js.map +0 -1
- jupyter_ext/labextension/static/frontend_styles_index_js.96745acc14125453fba8.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.2d5ea542350862f7c531.js.map +0 -1
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.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
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
- {hdsp_jupyter_extension-2.0.23.data → hdsp_jupyter_extension-2.0.26.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
- {hdsp_jupyter_extension-2.0.23.dist-info → hdsp_jupyter_extension-2.0.26.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.23.dist-info → hdsp_jupyter_extension-2.0.26.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib_index_js.67505497667f9c0a763d.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACyG;AACxC;AACK;AACtB;AAC6D;AAC5C;AACL;AACN;AACM;AACN;AACM;AACA;AAC5D;AAC0D;AAC1D;AACA,wBAAwB,8DAAO;AAC/B;AACA,YAAY,oDAAa;AACzB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,iDAAU,IAAI,6CAA6C;AAC7E;AACA,oCAAoC,+CAAQ;AAC5C,8BAA8B,+CAAQ;AACtC,sCAAsC,+CAAQ;AAC9C,0CAA0C,+CAAQ;AAClD,wDAAwD,+CAAQ;AAChE,gDAAgD,+CAAQ;AACxD,4CAA4C,+CAAQ;AACpD,sCAAsC,+CAAQ;AAC9C;AACA,gDAAgD,+CAAQ;AACxD;AACA;AACA,sCAAsC,+CAAQ;AAC9C;AACA;AACA;AACA,gCAAgC;AAChC,8EAA8E;AAC9E;AACA;AACA,4BAA4B;AAC5B;AACA,KAAK;AACL,oDAAoD,+CAAQ;AAC5D;AACA,oDAAoD,+CAAQ;AAC5D;AACA,oDAAoD,+CAAQ;AAC5D,4EAA4E,+CAAQ;AACpF;AACA,8DAA8D,+CAAQ;AACtE,0DAA0D,+CAAQ;AAClE;AACA,oDAAoD,+CAAQ;AAC5D,gEAAgE,+CAAQ;AACxE,gDAAgD,+CAAQ;AACxD,wBAAwB,6CAAM;AAC9B;AACA,6CAA6C,+CAAQ;AACrD,kDAAkD,+CAAQ;AAC1D;AACA;AACA,8BAA8B,6CAAM;AACpC,2BAA2B,6CAAM;AACjC,6BAA6B,6CAAM;AACnC;AACA,2BAA2B,kDAAW;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,8CAA8C,+CAAQ;AACtD;AACA,8BAA8B,+CAAQ;AACtC,gDAAgD,+CAAQ;AACxD;AACA,8CAA8C,+CAAQ;AACtD;AACA,+BAA+B,6CAAM;AACrC;AACA,kDAAkD,+CAAQ;AAC1D,sEAAsE,+CAAQ;AAC9E;AACA,8CAA8C,+CAAQ;AACtD,kEAAkE,+CAAQ;AAC1E,kCAAkC,6CAAM;AACxC,+BAA+B,6CAAM;AACrC,gCAAgC,6CAAM;AACtC,mCAAmC,6CAAM;AACzC;AACA;AACA;AACA,8BAA8B,+DAAa;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,kDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,kCAAkC,kDAAW;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,mCAAmC,kDAAW;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,iEAAe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,oBAAoB;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,KAAK;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,8CAA8C;AACzF;AACA;AACA;AACA,uBAAuB,cAAc,GAAG,wCAAwC;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,iCAAiC,6CAAM;AACvC,2BAA2B,6CAAM;AACjC,gCAAgC,6CAAM;AACtC,6BAA6B,6CAAM;AACnC,6BAA6B,6CAAM;AACnC,gCAAgC,6CAAM;AACtC;AACA,wBAAwB,WAAW,GAAG,uCAAuC;AAC7E,2BAA2B,KAAK,GAAG,OAAO;AAC1C;AACA;AACA,IAAI,0DAAmB;AACvB;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA,KAAK;AACL;AACA;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,uBAAuB,qEAAY;AACnC;AACA;AACA,kCAAkC,4EAAmB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qBAAqB,QAAQ,gLAA+B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qBAAqB,QAAQ,gLAA+B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,yCAAyC;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,eAAe;AACvD;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,QAAQ,GAAG,WAAW,UAAU,WAAW;AACvF;AACA;AACA;AACA,wCAAwC,2BAA2B;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,wCAAwC;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,8BAA8B;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,SAAS;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yCAAyC,SAAS;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,QAAQ;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,uEAAc;AACzD;AACA,QAAQ,sEAAa;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,qBAAqB,cAAc;AACnD;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,6CAAM;AAC/B,8BAA8B,6CAAM;AACpC,8BAA8B,6CAAM;AACpC;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6DAA6D,eAAe;AAC5E;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,qCAAqC,wDAAwD;AAC7F;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,qEAAY,MAAM,4EAAmB;AACjE;AACA;AACA;AACA,0BAA0B,uEAAc;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,sFAAsF;AAC1I;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qDAAqD,SAAS;AAC9D;AACA,yFAAyF,MAAM,IAAI,IAAI;AACvG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA;AACA,sCAAsC,QAAQ;AAC9C;AACA;AACA;AACA;AACA,iEAAiE,QAAQ;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,mBAAmB;AACjE;AACA;AACA;AACA;AACA;AACA;AACA,gEAAgE,QAAQ;AACxE;AACA,+DAA+D,SAAS;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA,+CAA+C,mBAAmB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,QAAQ,MAAM,YAAY;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8FAA8F,QAAQ;AACtG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA,mGAAmG,kBAAkB;AACrH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,QAAQ;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB;AACpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA,6DAA6D,kCAAkC;AAC/F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,WAAW,GAAG,wCAAwC;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kDAAkD,oBAAoB;AACtE;AACA,mEAAmE,GAAG;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,qBAAqB,UAAU,mBAAmB;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,UAAU;AAC9B;AACA,yCAAyC,gBAAgB,iCAAiC,qBAAqB,kBAAkB,wBAAwB;AACzJ,gBAAgB,oBAAoB,EAAE;AACtC;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,wCAAwC;AACxC,wCAAwC,gBAAgB,iBAAiB,iBAAiB;AAC1F;AACA;AACA,+BAA+B,gBAAgB,gBAAgB;AAC/D;AACA;AACA;AACA,mEAAmE;AACnE,YAAY;AACZ;AACA,mCAAmC,WAAW,0BAA0B;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,QAAQ;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,qBAAqB;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,QAAQ;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAC5D;AACA,SAAS;AACT;AACA;AACA;AACA,4EAA4E,0BAA0B;AACtG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2BAA2B,2BAA2B,QAAQ,KAAK;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAiE;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB;AACrB;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD;AAChD,0BAA0B,iDAAiD;AAC3E;AACA;AACA;AACA;AACA,oBAAoB,qBAAqB,QAAQ,gLAA+B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,OAAO,GAAG,iBAAiB;AACzE;AACA;AACA;AACA,aAAa;AACb;AACA,yBAAyB,mDAAmD,gBAAgB;AAC5F;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,eAAe;AACtF;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA,yBAAyB,2CAA2C,YAAY;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,WAAW;AAC/D,sBAAsB,MAAM,EAAE,UAAU,EAAE,4BAA4B;AACtE,SAAS;AACT;AACA;AACA;AACA,wBAAwB;AACxB;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,+BAA+B;AACtG;AACA,qBAAqB;AACrB;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA,qBAAqB,+CAA+C,YAAY;AAChF;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,yBAAyB;AACzC;AACA;AACA;AACA;AACA,iCAAiC,wBAAwB,oBAAoB,mBAAmB;AAChG;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA,iCAAiC,qBAAqB,iCAAiC,kBAAkB;AACzG;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA,iCAAiC,oCAAoC;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sFAAsF,aAAa;AACnG,iCAAiC,mBAAmB,cAAc,iBAAiB;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,oBAAoB,oBAAoB,mBAAmB;AAC5F;AACA,0DAA0D,qEAAY,MAAM,4EAAmB;AAC/F;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA,6EAA6E,YAAY,GAAG,cAAc,IAAI,UAAU;AACxH;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,yCAAyC;AACtE;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA,8EAA8E,qEAAY,MAAM,4EAAmB;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,SAAS;AAC1D;AACA,qFAAqF,MAAM,IAAI,IAAI;AACnG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,QAAQ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,WAAW;AAC3B;AACA;AACA,yCAAyC;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,qCAAqC;AAC1E;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,oCAAoC;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sGAAsG;AACtG;AACA,sEAAsE,wBAAwB;AAC9F;AACA;AACA,0DAA0D,aAAa;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,UAAU;AACpD,0CAA0C,UAAU;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,iBAAiB;AAChE;AACA;AACA;AACA,+CAA+C,YAAY;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,uCAAuC;AAC5E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,kBAAkB;AACtD;AACA;AACA;AACA;AACA,yGAAyG;AACzG;AACA,qDAAqD,MAAM;AAC3D;AACA;AACA;AACA;AACA,qDAAqD,MAAM,mBAAmB,aAAa;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,UAAU;AACpD,0CAA0C,UAAU;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,iBAAiB;AAChE;AACA;AACA;AACA,+CAA+C,YAAY;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,cAAc,gBAAgB,oBAAoB,GAAG;AACpG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,+CAA+C;AACpF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,sCAAsC;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,0CAA0C;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,gDAAgD;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qDAAqD;AAClG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,wDAAwD;AACzG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,SAAS;AAC/C;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA,8EAA8E,qEAAY,MAAM,4EAAmB;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,SAAS;AAC1D;AACA,qFAAqF,MAAM,IAAI,IAAI;AACnG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA,aAAa;AACb;AACA;AACA;AACA,kCAAkC,QAAQ;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,QAAQ;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,WAAW;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,WAAW;AACxC;AACA,qDAAqD,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB;AACA;AACA,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,2DAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,qBAAqB;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,UAAU,6BAA6B;AACtE,yBAAyB,0DAAmB,CAAC,yDAAa,IAAI,wGAAwG;AACtK,QAAQ,0DAAmB,UAAU,8BAA8B;AACnE,YAAY,0DAAmB,UAAU,8DAA8D,QAAQ,oDAAa,IAAI;AAChI,YAAY,0DAAmB,UAAU,sCAAsC;AAC/E,gBAAgB,0DAAmB,aAAa,kGAAkG;AAClJ,oBAAoB,0DAAmB,UAAU,0JAA0J;AAC3M,wBAAwB,0DAAmB,eAAe,wBAAwB;AAClF,wBAAwB,0DAAmB,WAAW,qFAAqF;AAC3I,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,gBAAgB,0DAAmB,aAAa,yGAAyG;AACzJ,oBAAoB,0DAAmB,UAAU,0JAA0J;AAC3M,wBAAwB,0DAAmB,WAAW,sCAAsC;AAC5F,wBAAwB,0DAAmB,WAAW,qCAAqC;AAC3F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,wBAAwB,0DAAmB,WAAW,sCAAsC;AAC5F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,wBAAwB,0DAAmB,WAAW,uCAAuC;AAC7F,wBAAwB,0DAAmB,WAAW,sCAAsC;AAC5F,wBAAwB,0DAAmB,WAAW,qCAAqC;AAC3F,wBAAwB,0DAAmB,WAAW,wCAAwC;AAC9F,QAAQ,0DAAmB,UAAU,2DAA2D;AAChG,qCAAqC,0DAAmB,UAAU,mCAAmC;AACrG,gBAAgB,0DAAmB;AACnC,gBAAgB,0DAAmB,QAAQ,kCAAkC;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,0DAAmB,UAAU;AACzD;AACA,mEAAmE,SAAS,EAAE,sDAAsD,GAAG;AACvI,yCAAyC,0DAAmB,UAAU,sCAAsC;AAC5G,4BAA4B,0DAAmB,WAAW,oCAAoC;AAC9F,4BAA4B,0DAAmB,WAAW,oCAAoC;AAC9F,wBAAwB,0DAAmB,UAAU,sCAAsC,kDAAkD,EAAE,uDAAuD,GAAG,sDAAsD,0DAAmB,UAAU,uCAAuC,iFAAiF,GAAG;AACvZ,4BAA4B,0DAAmB,UAAU,wCAAwC;AACjG,gCAAgC,0DAAmB,UAAU,6CAA6C;AAC1G;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6DAA6D,QAAQ,EAAE,yCAAyC;AAChH,yFAAyF,KAAK;AAC9F,yFAAyF,KAAK;AAC9F,yCAAyC;AACzC,4FAA4F,kBAAkB;AAC9G,yDAAyD,UAAU,GAAG,cAAc,cAAc,UAAU,aAAa,aAAa,EAAE,UAAU;AAClJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD,UAAU,gBAAgB,UAAU;AAC7F,qFAAqF,KAAK;AAC1F,qFAAqF,KAAK;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sGAAsG,+CAAK,gBAAgB,+CAAK;AAChI;AACA;AACA,oFAAoF,cAAc,IAAI,aAAa;AACnH;AACA;AACA;AACA;AACA,oFAAoF,+CAAK,eAAe;AACxG,kFAAkF,+CAAK,cAAc;AACrG;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,6EAAoB;AAClE,8CAA8C,6EAAoB,YAAY,SAAS,IAAI,QAAQ;AACnG;AACA;AACA,gLAAgL,SAAS;AACzL;AACA;AACA;AACA,6DAA6D,+CAAK;AAClE,gLAAgL,UAAU,EAAE,SAAS;AACrM;AACA;AACA;AACA;AACA;AACA;AACA,4FAA4F,KAAK,EAAE,WAAW;AAC9G,4FAA4F,KAAK;AACjG;AACA;AACA,8FAA8F,WAAW;AACzG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gGAAgG,+CAAK,cAAc,EAAE,gBAAgB;AACrI;AACA;AACA;AACA;AACA,qGAAqG,+CAAK,kBAAkB,EAAE,YAAY;AAC1I;AACA;AACA;AACA;AACA,qCAAqC;AACrC,4CAA4C,0DAAmB,UAAU,6CAA6C,kBAAkB,6BAA6B,sBAAsB;AAC3L;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C;AAC3C,iCAAiC;AACjC;AACA;AACA,wBAAwB,0DAAmB,CAAC,+DAAgB,IAAI,sOAAsO;AACtS;AACA,wBAAwB,0DAAmB,UAAU,SAAS,0BAA0B;AACxF;AACA;AACA,aAAa;AACb,iEAAiE,0DAAmB,UAAU,kDAAkD;AAChJ,gBAAgB,0DAAmB,UAAU,4CAA4C;AACzF,oBAAoB,0DAAmB,UAAU,uEAAuE;AACxH,wBAAwB,0DAAmB,WAAW,qPAAqP;AAC3S,oBAAoB,0DAAmB;AACvC,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F;AACA;AACA,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F,oBAAoB,0DAAmB,aAAa;AACpD;AACA,+DAA+D,iBAAiB;AAChF;AACA;AACA;AACA;AACA;AACA,2BAA2B;AAC3B,oBAAoB,0DAAmB,aAAa,wGAAwG;AAC5J,4CAA4C,0DAAmB,UAAU,kCAAkC;AAC3G,gBAAgB,0DAAmB,UAAU,yCAAyC;AACtF,oBAAoB,0DAAmB,UAAU,uEAAuE;AACxH,wBAAwB,0DAAmB,WAAW,0IAA0I;AAChM,wBAAwB,0DAAmB,WAAW,iIAAiI;AACvL,oBAAoB,0DAAmB;AACvC;AACA;AACA;AACA,gBAAgB,0DAAmB,UAAU,uCAAuC,wCAAwC,0DAAmB,UAAU,iDAAiD;AAC1M,oBAAoB,0DAAmB,UAAU,qCAAqC;AACtF,wBAAwB,0DAAmB,UAAU,uEAAuE;AAC5H,4BAA4B,0DAAmB,WAAW,8MAA8M;AACxQ,wBAAwB,0DAAmB,WAAW,qCAAqC;AAC3F,oBAAoB,0DAAmB,aAAa,kFAAkF,UAAU,YAAY;AAC5J,gBAAgB,0DAAmB,aAAa,mIAAmI;AACnL,YAAY,0DAAmB,UAAU,qBAAqB;AAC9D,qDAAqD,0DAAmB,UAAU,oCAAoC;AACtH,YAAY,0DAAmB,UAAU,8FAA8F;AACvI,gBAAgB,0DAAmB,UAAU,yCAAyC;AACtF,oBAAoB,0DAAmB,UAAU,wCAAwC,4DAA4D,0EAA0E;AAC/N,wBAAwB,0DAAmB,WAAW,oBAAoB;AAC1E;AACA;AACA;AACA;AACA,oCAAoC,0DAAmB,CAAC,uDAAc;AACtE,gCAAgC,0DAAmB,UAAU,4CAA4C;AACzG,gCAAgC,0DAAmB,WAAW,4CAA4C;AAC1G;AACA;AACA,oCAAoC,0DAAmB,CAAC,uDAAc;AACtE,gCAAgC,0DAAmB,UAAU,4CAA4C;AACzG,gCAAgC,0DAAmB,WAAW,4CAA4C;AAC1G;AACA;AACA,oCAAoC,0DAAmB,WAAW,uEAAuE,WAAW,+CAAK,QAAQ,aAAa;AAC9K;AACA,qBAAqB;AACrB,gHAAgH,0DAAmB,aAAa;AAChJ;AACA;AACA;AACA,qBAAqB,sCAAsC;AAC3D,oBAAoB,0DAAmB,UAAU,uEAAuE;AACxH,wBAAwB,0DAAmB,WAAW,oDAAoD;AAC1G,gBAAgB,0DAAmB,WAAW,6CAA6C;AAC3F;AACA;AACA;AACA,6CAA6C,0DAAmB,UAAU,oFAAoF,oEAAoE,GAAG;AACrO,gBAAgB,0DAAmB,UAAU,qCAAqC;AAClF,oBAAoB,0DAAmB,UAAU,2DAA2D;AAC5G,6CAA6C,0DAAmB,WAAW,6DAA6D,gCAAgC;AACxK,oBAAoB,0DAAmB,WAAW,iCAAiC,oEAAoE,iLAAiL;AACxU;AACA,sDAAsD,0DAAmB,WAAW,yCAAyC,oBAAoB,0DAAmB,CAAC,uEAAc,IAAI,MAAM,gBAAgB,IAAI,0DAAmB,CAAC,uEAAc,IAAI,MAAM,gBAAgB;AAC7Q,+BAA+B,0DAAmB,UAAU,qCAAqC,8BAA8B,0DAAmB,UAAU,iEAAiE,YAAY,GAAG;AAC5O,gBAAgB,0DAAmB,UAAU,2CAA2C;AACxF,oDAAoD,0DAAmB,UAAU,uEAAuE;AACxJ,wBAAwB,0DAAmB,WAAW,sIAAsI;AAC5L,qDAAqD,0DAAmB,UAAU,yCAAyC;AAC3H,iDAAiD,0DAAmB,WAAW,wCAAwC;AACvH,gBAAgB,0DAAmB,WAAW,sCAAsC,mEAAmE,GAAG;AAC1J,0CAA0C,0DAAmB,UAAU,yFAAyF,oEAAoE,GAAG;AACvO,YAAY,0DAAmB,UAAU,qCAAqC;AAC9E,yCAAyC,0DAAmB,WAAW,6DAA6D,gCAAgC;AACpK,gBAAgB,0DAAmB,WAAW,iCAAiC,oEAAoE,iLAAiL;AACpU;AACA,kDAAkD,0DAAmB,WAAW,yCAAyC,oBAAoB,0DAAmB,CAAC,uEAAc,IAAI,MAAM,gBAAgB,IAAI,0DAAmB,CAAC,uEAAc,IAAI,MAAM,gBAAgB;AACzQ,QAAQ,0DAAmB,UAAU,uCAAuC;AAC5E,YAAY,0DAAmB,UAAU,8CAA8C,wBAAwB;AAC/G,gBAAgB,0DAAmB,CAAC,qEAAmB,IAAI;AAC3D,2JAA2J,4BAA4B,mCAAmC,wGAAwG;AAClU,gBAAgB,0DAAmB,eAAe,+CAA+C,0DAA0D,EAAE,wDAAwD;AACrN;AACA;AACA;AACA,oHAAoH;AACpH,gBAAgB,0DAAmB,UAAU,wCAAwC;AACrF,4DAA4D,0DAAmB,aAAa,uGAAuG;AACnM,wBAAwB,0DAAmB,UAAU,uEAAuE;AAC5H,4BAA4B,0DAAmB,WAAW,oDAAoD;AAC9G;AACA,oBAAoB,0DAAmB,aAAa,qNAAqN;AACzQ,YAAY,0DAAmB,UAAU,gCAAgC;AACzE,gBAAgB,0DAAmB,UAAU,6CAA6C;AAC1F,oBAAoB,0DAAmB,aAAa,mCAAmC,2DAA2D,kCAAkC,yCAAyC,YAAY;AACzO,wBAAwB,0DAAmB,UAAU,kJAAkJ;AACvM;AACA,wBAAwB,0DAAmB,WAAW,kNAAkN;AACxQ;AACA,wBAAwB,0DAAmB,WAAW,uZAAuZ;AAC7c,wBAAwB,0DAAmB,WAAW,kCAAkC;AACxF,wBAAwB,0DAAmB,UAAU,2GAA2G;AAChK,4BAA4B,0DAAmB,WAAW,yHAAyH;AACnL,yCAAyC,0DAAmB,UAAU,qCAAqC;AAC3G,wBAAwB,0DAAmB,aAAa,mCAAmC,6DAA6D,oBAAoB,sBAAsB,+BAA+B;AACjO,4BAA4B,0DAAmB,UAAU,uEAAuE;AAChI,gCAAgC,0DAAmB,WAAW,kNAAkN;AAChR,4BAA4B,0DAAmB;AAC/C,4BAA4B,0DAAmB,WAAW,qCAAqC;AAC/F,wBAAwB,0DAAmB,aAAa,mCAAmC,8DAA8D,oBAAoB,uBAAuB,+BAA+B;AACnO,4BAA4B,0DAAmB,UAAU,4EAA4E;AACrI,gCAAgC,0DAAmB,WAAW,uZAAuZ;AACrd,4BAA4B,0DAAmB;AAC/C,4BAA4B,0DAAmB,WAAW,qCAAqC;AAC/F,gBAAgB,0DAAmB,UAAU,kCAAkC;AAC/E,oBAAoB,0DAAmB,WAAW,iCAAiC;AACnF,kCAAkC,0DAAmB,CAAC,qEAAmB,IAAI,uLAAuL;AACpQ,CAAC;AACD;AACA;AACA;AACA;AACO,+BAA+B,kEAAW;AACjD;AACA;AACA,4BAA4B,sDAAe;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,0DAAmB,cAAc,iIAAiI;AAClL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,+CAA+C,WAAW;AAC1D;AACA;;AAEA;AACA,YAAY,WAAW;AACvB,cAAc,gBAAgB;AAC9B,kBAAkB,iBAAiB;AACnC,kBAAkB,aAAa;;AAE/B;;AAEA;AACA;AACA,kCAAkC,WAAW,OAAO,QAAQ;AAC5D;AACA,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,WAAW,MAAM,gCAAgC;AACzF,iBAAiB;AACjB;AACA;AACA,SAAS;AACT;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA,SAAS;AACT;AACA;;;;;;;;;;;;;;;;;;;;;ACj/HA;AACA;AACA;AACA;AACA;AACA;AACwE;AACF;AAClB;AACU;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,0DAAmB,CAAC,kEAAU,IAAI,MAAM,mDAAmD;AAC1G;AACA;AACA,eAAe,0DAAmB,CAAC,2EAAmB,IAAI,MAAM,kCAAkC;AAClG;AACA;AACA,eAAe,0DAAmB,CAAC,2EAAmB,IAAI,MAAM,kCAAkC;AAClG;AACA;AACA,eAAe,0DAAmB,CAAC,2EAAmB,IAAI,MAAM,kCAAkC;AAClG;AACA;AACA,eAAe,0DAAmB,CAAC,uEAAe,IAAI,MAAM,mDAAmD;AAC/G;AACA,WAAW,0DAAmB,CAAC,2EAAmB,IAAI,MAAM,mDAAmD;AAC/G;AACO,+BAA+B,yGAAyG;AAC/I,kCAAkC,+CAAQ;AAC1C,8CAA8C,+CAAQ;AACtD,kCAAkC,+CAAQ;AAC1C,gDAAgD,+CAAQ;AACxD,yBAAyB,6CAAM;AAC/B;AACA,yBAAyB,kDAAW;AACpC;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yDAAyD;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C;AAC7C,wDAAwD;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA,8CAA8C,kBAAkB;AAChE;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,UAAU,uEAAuE;AAChH,QAAQ,0DAAmB,UAAU,6CAA6C;AAClF,YAAY,0DAAmB,WAAW,2CAA2C;AACrF,YAAY,0DAAmB,WAAW,8CAA8C;AACxF,QAAQ,0DAAmB,UAAU,2CAA2C,kCAAkC,0DAAmB,UAAU,QAAQ,UAAU,GAAG,aAAa,GAAG,MAAM,8CAA8C,wEAAwE,uFAAuF;AACvY,YAAY,0DAAmB,WAAW,gDAAgD;AAC1F,YAAY,0DAAmB,WAAW,iDAAiD;AAC3F,YAAY,0DAAmB,WAAW,uDAAuD;AACjG,QAAQ,0DAAmB,UAAU,6CAA6C;AAClF,YAAY,0DAAmB;AAC/B;AACA,YAAY,0DAAmB;AAC/B;AACA,YAAY,0DAAmB;AAC/B;AACA;AACA,iEAAe,mBAAmB,EAAC;;;;;;;;;;;;;;;;;AC7LnC;AACA;AACA;AACA;AACA;AAC0B;AACnB,+BAA+B,gDAAgD;AACtF,YAAY,0DAAmB,UAAU,oCAAoC;AAC7E,QAAQ,0DAAmB,UAAU,wDAAwD;AAC7F,QAAQ,0DAAmB,UAAU,qCAAqC;AAC1E,YAAY,0DAAmB;AAC/B,YAAY,0DAAmB,QAAQ,qCAAqC;AAC5E,YAAY,0DAAmB,UAAU,qCAAqC,gCAAgC,0DAAmB,aAAa,6EAA6E;AAC3N,gBAAgB,0DAAmB,WAAW,iCAAiC;AAC/E,gBAAgB,0DAAmB,WAAW,+BAA+B;AAC7E,YAAY,0DAAmB,UAAU,qCAAqC;AAC9E,gBAAgB,0DAAmB,aAAa,uDAAuD;AACvG,QAAQ,0DAAmB;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;AC3IA;AACA;AACA;AACA;AACwC;AACoF;AAC9D;AACJ;AAC1D;AACA;AACA;AACA;AACA;AACA;AACO,kCAAkC,2BAA2B;AACpE,gCAAgC,+CAAQ;AACxC,4CAA4C,+CAAQ;AACpD,oCAAoC,+CAAQ;AAC5C,8CAA8C,+CAAQ;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,CAAC,iDAAM,IAAI;AAC1C;AACA;AACA;AACA;AACA,WAAW;AACX,QAAQ,0DAAmB,CAAC,sDAAW;AACvC,YAAY,0DAAmB,CAAC,8CAAG,IAAI,+CAA+C;AACtF,gBAAgB,0DAAmB,CAAC,uEAAe,IAAI,kBAAkB;AACzE,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,eAAe;AACjE,QAAQ,0DAAmB,CAAC,wDAAa;AACzC,YAAY,0DAAmB,CAAC,8CAAG,IAAI,yDAAyD;AAChG,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,2CAA2C;AAC7F,gBAAgB,0DAAmB,CAAC,oDAAS,IAAI;AACjD;AACA;AACA;AACA,uBAAuB;AACvB,gBAAgB,0DAAmB,CAAC,8CAAG;AACvC,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,sEAAsE;AAC5H,oBAAoB,0DAAmB,CAAC,8CAAG,IAAI,2CAA2C,2CAA2C,0DAAmB,CAAC,+CAAI,IAAI;AACjK;AACA;AACA;AACA;AACA,2BAA2B;AAC3B,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI;AAC3C;AACA;AACA;AACA,uBAAuB;AACvB,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,qEAAqE,4CAA4C;AACvK,wBAAwB,0DAAmB,CAAC,8CAAG,IAAI,MAAM,mDAAmD;AAC5G,4BAA4B,0DAAmB,CAAC,qEAAa,IAAI,yBAAyB,oBAAoB;AAC9G;AACA,4BAA4B,0DAAmB;AAC/C,wBAAwB,0DAAmB;AAC3C;AACA,wBAAwB,0DAAmB;AAC3C;AACA,wBAAwB,0DAAmB;AAC3C;AACA,wBAAwB,0DAAmB;AAC3C;AACA,QAAQ,0DAAmB,CAAC,wDAAa,IAAI,MAAM,6BAA6B;AAChF,YAAY,0DAAmB,CAAC,iDAAM,IAAI,0CAA0C;AACpF,YAAY,0DAAmB,CAAC,iDAAM,IAAI,oGAAoG,0DAAmB,CAAC,uEAAe,SAAS;AAC1L;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnFA;AACA;AACA;AACA;AACA;AACA;AACA;AACmD;AACW;AACZ;AACkB;AACpB;AACI;AACM;AACF;AACI;AACI;AACgF;AACzI,yBAAyB,gCAAgC;AAChE;AACA,wCAAwC,sEAAY,MAAM,6EAAmB;AAC7E,oCAAoC,+CAAQ;AAC5C,sCAAsC,+CAAQ;AAC9C,0CAA0C,+CAAQ,GAAG;AACrD;AACA,gDAAgD,+CAAQ;AACxD;AACA,8CAA8C,+CAAQ;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,+CAAQ;AAClD;AACA,4CAA4C,+CAAQ;AACpD,wCAAwC,+CAAQ;AAChD,sCAAsC,+CAAQ;AAC9C,0DAA0D,+CAAQ;AAClE,kDAAkD,+CAAQ;AAC1D;AACA,4CAA4C,+CAAQ;AACpD,0CAA0C,+CAAQ;AAClD,4CAA4C,+CAAQ;AACpD,8CAA8C,+CAAQ;AACtD,0CAA0C,+CAAQ;AAClD,wDAAwD,+CAAQ;AAChE;AACA,sCAAsC,+CAAQ;AAC9C,4CAA4C,+CAAQ,8BAA8B;AAClF,gDAAgD,+CAAQ,GAAG;AAC3D;AACA,gDAAgD,+CAAQ,CAAC,0EAAgB;AACzE,oDAAoD,+CAAQ,EAAE,0EAAgB;AAC9E;AACA,IAAI,gDAAS;AACb;AACA;AACA,YAAY,6EAAmB;AAC/B;AACA;AACA,aAAa;AACb;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0DAA0D,6EAAmB;AAC7E;AACA;AACA;AACA;AACA;AACA,mCAAmC,0EAAgB;AACnD;AACA;AACA;AACA;AACA;AACA,cAAc,IAAI;AAClB;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA,6DAA6D,kBAAkB;AAC/E,2BAA2B,KAAK;AAChC,yBAAyB,aAAa;AACtC,0CAA0C,6BAA6B;AACvE;AACA;AACA,8BAA8B;AAC9B;AACA;AACA,yCAAyC,oEAAU;AACnD,8CAA8C,wDAAwD;AACtG;AACA;AACA,8CAA8C,2BAA2B;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,oEAAU;AAC/C,iCAAiC,yCAAyC;AAC1E;AACA;AACA,iCAAiC,YAAY;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ,uEAAa;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,0DAAmB,CAAC,0EAAkB,IAAI,MAAM,oCAAoC;AACvG;AACA,mBAAmB,0DAAmB,CAAC,uEAAe,IAAI,MAAM,uCAAuC;AACvG;AACA,mBAAmB,0DAAmB,CAAC,iEAAS,IAAI,MAAM,qCAAqC;AAC/F;AACA;AACA,YAAY,0DAAmB,UAAU,wCAAwC;AACjF,QAAQ,0DAAmB,UAAU,uCAAuC;AAC5E,YAAY,0DAAmB,UAAU,uCAAuC;AAChF,gBAAgB,0DAAmB;AACnC,gBAAgB,0DAAmB,aAAa,+EAA+E;AAC/H,YAAY,0DAAmB,UAAU,wCAAwC;AACjF,gBAAgB,0DAAmB,UAAU,uCAAuC;AACpF,oBAAoB,0DAAmB;AACvC,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF,oBAAoB,0DAAmB,aAAa,sGAAsG;AAC1J,wBAAwB,0DAAmB,aAAa,iBAAiB;AACzE,wBAAwB,0DAAmB,aAAa,eAAe;AACvE,wBAAwB,0DAAmB,aAAa,iBAAiB;AACzE,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,0EAA0E;AAC7H;AACA,wBAAwB,0DAAmB,YAAY,SAAS,0DAA0D;AAC1H,oBAAoB,0DAAmB,YAAY,8OAA8O;AACjS,0CAA0C,0DAAmB,UAAU,yCAAyC;AAChH,oBAAoB,0DAAmB;AACvC,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F;AACA;AACA;AACA,4BAA4B,0DAAmB,YAAY,SAAS,0DAA0D;AAC9H,2DAA2D,0DAAmB,UAAU;AACxF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B;AAC/B,4BAA4B,0DAAmB,YAAY,qOAAqO,qDAAqD;AACrV,4BAA4B,0DAAmB,WAAW;AAC1D;AACA;AACA;AACA,mCAAmC;AACnC;AACA;AACA,4BAA4B,0DAAmB,YAAY,iEAAiE,SAAS,+FAA+F;AACpO,yDAAyD,0DAAmB,WAAW,SAAS,sCAAsC;AACtI,yDAAyD,0DAAmB,aAAa;AACzF;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC;AACnC,sDAAsD,0DAAmB,aAAa,0HAA0H,oBAAoB;AACpO,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,aAAa,4GAA4G;AACpK,4BAA4B,0DAAmB,aAAa,2BAA2B;AACvF,4BAA4B,0DAAmB,aAAa,yBAAyB;AACrF,wCAAwC,0DAAmB,UAAU,yCAAyC;AAC9G,oBAAoB,0DAAmB;AACvC,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,wKAAwK;AAC/N,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,yMAAyM;AAChQ,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,mKAAmK;AAC1N,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,kNAAkN,kBAAkB;AAC3R,wBAAwB,0DAAmB,YAAY,SAAS,oCAAoC;AACpG,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,yCAAyC;AAChG,4BAA4B,0DAAmB,YAAY,2GAA2G;AACtK,4BAA4B,0DAAmB;AAC/C,wBAAwB,0DAAmB,YAAY,SAAS,qDAAqD;AACrH,0CAA0C,0DAAmB,UAAU,yCAAyC;AAChH,oBAAoB,0DAAmB;AACvC,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,YAAY,sJAAsJ;AAC7M,oBAAoB,0DAAmB,UAAU,sCAAsC;AACvF,wBAAwB,0DAAmB,YAAY,sCAAsC;AAC7F,wBAAwB,0DAAmB,aAAa,4GAA4G;AACpK,4BAA4B,0DAAmB,aAAa,gBAAgB;AAC5E,4BAA4B,0DAAmB,aAAa,sBAAsB;AAClF,4BAA4B,0DAAmB,aAAa,wBAAwB;AACpF,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF,oBAAoB,0DAAmB,YAAY,yCAAyC;AAC5F,wBAAwB,0DAAmB,YAAY,mIAAmI;AAC1L,wBAAwB,0DAAmB;AAC3C,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,wEAAwE;AAC3H;AACA,wBAAwB,0DAAmB,YAAY,SAAS,0DAA0D;AAC1H,oBAAoB,0DAAmB,YAAY,iPAAiP,gBAAgB,uCAAuC;AAC3V,gBAAgB,0DAAmB,UAAU,sCAAsC;AACnF,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF,oBAAoB,0DAAmB,aAAa,wGAAwG;AAC5J,wBAAwB,0DAAmB,aAAa,iBAAiB;AACzE,wBAAwB,0DAAmB,aAAa,gBAAgB;AACxE,oBAAoB,0DAAmB,YAAY,SAAS,qDAAqD;AACjH;AACA;AACA,2CAA2C,0DAAmB,UAAU,sCAAsC;AAC9G,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF;AACA,wBAAwB,0DAAmB,YAAY,SAAS,0DAA0D;AAC1H,oBAAoB,0DAAmB,eAAe,iJAAiJ;AACvM,oBAAoB,0DAAmB,UAAU,+CAA+C;AAChG,wBAAwB,0DAAmB,aAAa,gOAAgO;AACxR,0CAA0C,0DAAmB,UAAU,sCAAsC;AAC7G,oBAAoB,0DAAmB,YAAY,sCAAsC;AACzF;AACA,wBAAwB,0DAAmB,YAAY,SAAS,0DAA0D;AAC1H,oBAAoB,0DAAmB,UAAU,uDAAuD,oEAAoE;AAC5K,wBAAwB,0DAAmB,UAAU;AACrD;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,8CAA8C,iCAAiC,IAAI;AAChH,4BAA4B,0DAAmB,WAAW,SAAS,yEAAyE;AAC5I,gCAAgC,0DAAmB,CAAC,oEAAY,IAAI,MAAM,gBAAgB;AAC1F;AACA,4BAA4B,0DAAmB,wCAAwC,0DAAmB,CAAC,sEAAc,IAAI,MAAM,gBAAgB,IAAI,0DAAmB,CAAC,wEAAgB,IAAI,MAAM,gBAAgB;AACrN,mDAAmD,0DAAmB,UAAU,SAAS,mBAAmB;AAC5G,4BAA4B,0DAAmB,YAAY,SAAS,wDAAwD;AAC5H,4BAA4B,0DAAmB,eAAe,gJAAgJ,kCAAkC,aAAa;AAC7P,4BAA4B,0DAAmB,aAAa,oIAAoI,kBAAkB,8DAA8D,0CAA0C,gCAAgC;AAC1V,oBAAoB,0DAAmB,UAAU,uDAAuD,mEAAmE;AAC3K,wBAAwB,0DAAmB,UAAU;AACrD;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,8CAA8C,mDAAmD,IAAI;AAClI,4BAA4B,0DAAmB,WAAW,SAAS,yEAAyE;AAC5I,gCAAgC,0DAAmB,CAAC,gEAAQ,IAAI,MAAM,gBAAgB;AACtF;AACA,4BAA4B,0DAAmB,iDAAiD,0DAAmB,CAAC,sEAAc,IAAI,MAAM,gBAAgB,IAAI,0DAAmB,CAAC,wEAAgB,IAAI,MAAM,gBAAgB;AAC9N,4DAA4D,0DAAmB,UAAU,SAAS,mBAAmB;AACrH,4BAA4B,0DAAmB,YAAY,SAAS,wDAAwD;AAC5H,4BAA4B,0DAAmB,eAAe,yJAAyJ,2CAA2C,aAAa;AAC/Q,4BAA4B,0DAAmB,aAAa,oIAAoI,kBAAkB,8DAA8D,4DAA4D,gCAAgC;AAC5W,oBAAoB,0DAAmB,UAAU,uDAAuD,mEAAmE;AAC3K,wBAAwB,0DAAmB,UAAU;AACrD;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,8CAA8C,uCAAuC,IAAI;AACtH,4BAA4B,0DAAmB,WAAW,SAAS,yEAAyE;AAC5I,gCAAgC,0DAAmB,CAAC,kEAAU,IAAI,MAAM,gBAAgB;AACxF;AACA,4BAA4B,0DAAmB,2CAA2C,0DAAmB,CAAC,sEAAc,IAAI,MAAM,gBAAgB,IAAI,0DAAmB,CAAC,wEAAgB,IAAI,MAAM,gBAAgB;AACxN,sDAAsD,0DAAmB,UAAU,SAAS,mBAAmB;AAC/G,4BAA4B,0DAAmB,YAAY,SAAS,wDAAwD;AAC5H,4BAA4B,0DAAmB,eAAe,mJAAmJ,qCAAqC,aAAa;AACnQ,4BAA4B,0DAAmB,aAAa,oIAAoI,kBAAkB,8DAA8D,gDAAgD,gCAAgC;AAChW,oBAAoB,0DAAmB,UAAU,uDAAuD,mEAAmE;AAC3K,wBAAwB,0DAAmB,UAAU;AACrD;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B,8CAA8C,2CAA2C,IAAI;AAC1H,4BAA4B,0DAAmB,WAAW,SAAS,yEAAyE;AAC5I,gCAAgC,0DAAmB,CAAC,qEAAa,IAAI,MAAM,gBAAgB;AAC3F;AACA,4BAA4B,0DAAmB,6CAA6C,0DAAmB,CAAC,sEAAc,IAAI,MAAM,gBAAgB,IAAI,0DAAmB,CAAC,wEAAgB,IAAI,MAAM,gBAAgB;AAC1N,wDAAwD,0DAAmB,UAAU,SAAS,mBAAmB;AACjH,4BAA4B,0DAAmB,YAAY,SAAS,wDAAwD;AAC5H,4BAA4B,0DAAmB,eAAe,qJAAqJ,uCAAuC,aAAa;AACvQ,4BAA4B,0DAAmB,aAAa,oIAAoI,kBAAkB,8DAA8D,oDAAoD,gCAAgC;AACpW,oBAAoB,0DAAmB,UAAU,SAAS,qBAAqB;AAC/E,wBAAwB,0DAAmB,aAAa;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC;AACrC;AACA,6BAA6B,8BAA8B;AAC3D,YAAY,0DAAmB,UAAU,uCAAuC;AAChF,gBAAgB,0DAAmB,aAAa,4FAA4F;AAC5I,gBAAgB,0DAAmB,aAAa,+GAA+G;AAC/J,gBAAgB,0DAAmB,aAAa,6FAA6F;AAC7I;;;;;;;;;;;;;;;;;;;;;;ACraA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACiF;AACxC;AAC4B;AACzB;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,YAAY,MAAM,0DAAmB,UAAU,qCAAqC;AAChH,IAAI,0DAAmB,UAAU,gCAAgC;AACjE,QAAQ,0DAAmB,WAAW,+BAA+B;AACrE,QAAQ,0DAAmB,WAAW,+BAA+B;AACrE,QAAQ,0DAAmB,WAAW,+BAA+B;AACrE,oBAAoB,0DAAmB,WAAW,gCAAgC;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,4BAA4B,wKAAwK;AAC3M,yBAAyB,6CAAM;AAC/B,wBAAwB,6CAAM;AAC9B,2BAA2B,6CAAM;AACjC,kDAAkD,+CAAQ;AAC1D;AACA;AACA,8BAA8B,8CAAO;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA,YAAY,uEAAuE,EAAE,iFAAoB;AACzG;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,+BAA+B,kDAAW;AAC1C;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA,KAAK;AACL;AACA,iCAAiC,kDAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB;AACxB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,+BAA+B,8CAAO;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,YAAY,0DAAmB,UAAU,2DAA2D,oBAAoB;AACxH,yBAAyB,0DAAmB,qBAAqB,uBAAuB;AACxF,wBAAwB,0DAAmB,UAAU;AACrD,2CAA2C;AAC3C,cAAc,IAAI,qBAAqB;AACvC,mDAAmD,0DAAmB,UAAU,kCAAkC;AAClH,6DAA6D,uDAAY,CAAC,0DAAmB,CAAC,qDAAW,IAAI,8LAA8L;AAC3S;AACA,iEAAe,gBAAgB,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;AClShC;AACA;AACA;AACA;AACwC;AAC+E;AACrE;AACU;AACA;AACE;AACZ;AACE;AAC7C,8BAA8B,+CAA+C;AACpF,oCAAoC,+CAAQ;AAC5C,0CAA0C,+CAAQ;AAClD,YAAY,yDAAyD;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,0DAAmB,CAAC,uEAAe,IAAI,mBAAmB;AAC/F,kCAAkC,0DAAmB,CAAC,iEAAS,IAAI,mBAAmB;AACtF,qCAAqC,0DAAmB,CAAC,kEAAU,IAAI,mBAAmB;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,0DAAmB,CAAC,+CAAI,IAAI;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX,QAAQ,0DAAmB,CAAC,sDAAW,IAAI,MAAM,8BAA8B,sBAAsB;AACrG,YAAY,0DAAmB,CAAC,8CAAG,IAAI,+EAA+E;AACtH,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI,+CAA+C;AAC1F,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,0CAA0C;AAChG,oBAAoB,0DAAmB,CAAC,+CAAI,IAAI,sHAAsH;AACtK,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI,iBAAiB;AAC5D,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,4DAA4D,iBAAiB,0DAAmB,CAAC,sEAAc,IAAI,mBAAmB,MAAM,0DAAmB,CAAC,sEAAc,IAAI,mBAAmB;AAC3P,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,iCAAiC;AACvF,wBAAwB,0DAAmB,CAAC,iEAAS,IAAI,mBAAmB;AAC5E,wBAAwB,0DAAmB,CAAC,8CAAG,IAAI,OAAO;AAC1D,gBAAgB,0DAAmB,CAAC,yDAAc,IAAI,+CAA+C,8BAA8B;AACnI,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI,2DAA2D;AACtG,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,6CAA6C;AACnG,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,6CAA6C;AACnG;AACA;AACA,2BAA2B,0DAAmB,CAAC,8CAAG,IAAI,OAAO;AAC7D,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,+CAA+C,mDAAmD;AACpJ,oBAAoB,0DAAmB,CAAC,uEAAe,IAAI,mBAAmB;AAC9E;AACA;AACA,kCAAkC,0DAAmB,CAAC,8CAAG,IAAI,OAAO;AACpE,gBAAgB,0DAAmB,CAAC,qDAAU,IAAI,6CAA6C,mDAAmD;AAClJ,oBAAoB,0DAAmB,CAAC,iEAAS,IAAI,mBAAmB;AACxE;AACA;AACA,YAAY,0DAAmB,CAAC,mDAAQ,IAAI,iBAAiB;AAC7D,gBAAgB,0DAAmB,CAAC,8CAAG,IAAI;AAC3C;AACA;AACA;AACA;AACA,uBAAuB;AACvB,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,wEAAwE;AAC9H,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,wBAAwB,2BAA2B;AACzG,qCAAqC,0DAAmB,CAAC,uDAAc;AACvE,wBAAwB,0DAAmB,CAAC,qDAAU,IAAI,+EAA+E;AACzI,wBAAwB,0DAAmB,CAAC,qDAAU,IAAI,0BAA0B,mDAAmD;AACvI,YAAY,0DAAmB,CAAC,8CAAG,IAAI,qDAAqD;AAC5F,8BAA8B,0DAAmB,CAAC,iDAAM,IAAI,uDAAuD;AACnH,+CAA+C,0DAAmB,CAAC,iDAAM,IAAI,8DAA8D;AAC3I,2BAA2B,0DAAmB,CAAC,iDAAM,IAAI,kDAAkD;AAC3G;;;;;;;;;;;;;;;;;;;;;;;;ACtGA;AACA;AACA;AACA;AAC2C;AACuB;AACM;AACM;AAChB;AACpB;AAC1C;AACO,uBAAuB,4FAA4F;AAC1H,8BAA8B,kDAAW;AACzC;AACA,KAAK;AACL,8BAA8B,kDAAW;AACzC;AACA,KAAK;AACL,+BAA+B,kDAAW;AAC1C;AACA,KAAK;AACL;AACA,YAAY,0DAAmB,CAAC,8CAAG,IAAI;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX,qBAAqB,0DAAmB,CAAC,8CAAG,IAAI;AAChD;AACA;AACA;AACA;AACA;AACA;AACA,eAAe;AACf,sBAAsB,0DAAmB,CAAC,uDAAc;AACxD,8BAA8B,0DAAmB,CAAC,kDAAO,IAAI,uFAAuF;AACpJ,gBAAgB,0DAAmB;AACnC,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,gFAAgF,gBAAgB,4CAA4C;AAClM,wBAAwB,0DAAmB,CAAC,4EAAoB,IAAI,mBAAmB;AACvF,8BAA8B,0DAAmB,CAAC,kDAAO,IAAI,6FAA6F;AAC1J,gBAAgB,0DAAmB;AACnC,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,gFAAgF,gBAAgB,4CAA4C;AAClM,wBAAwB,0DAAmB,CAAC,+EAAuB,IAAI,mBAAmB;AAC1F,+BAA+B,0DAAmB,CAAC,kDAAO,IAAI,0EAA0E;AACxI,gBAAgB,0DAAmB;AACnC,oBAAoB,0DAAmB,CAAC,qDAAU,IAAI,iFAAiF,gBAAgB,sCAAsC;AAC7L,wBAAwB,0DAAmB,CAAC,uEAAe,IAAI,mBAAmB;AAClF,YAAY,0DAAmB,CAAC,kDAAO,IAAI,+CAA+C,WAAW;AACrG,QAAQ,0DAAmB,CAAC,mDAAU,IAAI,gBAAgB;AAC1D;AACA,iEAAe,WAAW,EAAC;;;;;;;;;;;;;;;;;;;;;;AC3D3B;AACA;AACA;AACA;AAC6D;AACT;AACU;AACZ;AAClD;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,gCAAgC;AACjC;AACA;AACA;AACA;AACA;AACA;AACO,sBAAsB,kCAAkC;AAC/D;AACA,wCAAwC,+CAAQ;AAChD,sBAAsB,6CAAM;AAC5B,iBAAiB,kDAAW;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,YAAY,0DAAmB,CAAC,kDAAO,IAAI,+CAA+C;AAC1F,QAAQ,0DAAmB,WAAW,sBAAsB;AAC5D,YAAY,0DAAmB,CAAC,qDAAU,IAAI;AAC9C;AACA;AACA;AACA;AACA;AACA,mBAAmB,cAAc,0DAAmB,CAAC,iEAAS,IAAI,mBAAmB,MAAM,0DAAmB,CAAC,uEAAe,IAAI,mBAAmB;AACrJ;AACA,iEAAe,UAAU,EAAC;;;;;;;;;;;;;;;;;;ACvD1B;AACA;AACA;AAC0C;AACE;;;;;;;;;;;;;;;;;;;ACJ5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAC0E;AACT;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB;AACvB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,mDAAmD;AAC1D,YAAY,uFAAuF;AACnG,4CAA4C,+CAAQ;AACpD,0CAA0C,+CAAQ;AAClD,uBAAuB,6CAAM;AAC7B,2BAA2B,6CAAM;AACjC,4BAA4B,6CAAM;AAClC,2BAA2B,6CAAM;AACjC;AACA,gCAAgC,8CAAO;AACvC,gCAAgC,8CAAO;AACvC;AACA,0BAA0B,kDAAW;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,gBAAgB;AACxC;AACA;AACA;AACA;AACA;AACA,yBAAyB,6EAAoB;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC;AACrC,oCAAoC;AACpC,oCAAoC;AACpC,+DAA+D,IAAI,QAAQ;AAC3E;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,IAAI,gDAAS;AACb;AACA;AACA;AACA;AACA,KAAK,kBAAkB;AACvB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,oBAAoB,EAAC;;;;;;;;;;;;;;;;;;;;;ACpSpC;AACA;AACA;AACA;AACyD;AACS;AACU;AACF;AACR;AACJ;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,kEAAa;AACjB,IAAI,2EAAiB;AACrB,IAAI,qFAAsB;AAC1B,IAAI,mFAAqB;AACzB,IAAI,2EAAiB;AACrB,IAAI,uEAAe;AACnB;AACA,iEAAe,OAAO,EAAC;;;;;;;;;;;;;;;;ACzBvB;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;ACjBA;AACA;AACA;AACA;AACA;AACwD;AAClB;AACtC;AACA;AACA;AACO;AACP;AACA;AACA,eAAe,kEAAgB;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,qCAAqC,gCAAgC;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,8CAAU;AACnC,KAAK;AACL;AACA,yBAAyB,8CAAU;AACnC,KAAK;AACL;AACA,yBAAyB,8CAAU;AACnC,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,8CAAU;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,8CAAU;AAC3B,6BAA6B,UAAU;AACvC;AACA;AACA,iBAAiB,8CAAU;AAC3B,6BAA6B,UAAU;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oBAAoB;AACxC;AACA,0BAA0B;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oBAAoB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,WAAW,GAAG,wCAAwC;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,8CAAU;AACvB;AACA,+BAA+B,UAAU;AACzC;AACA;;AAEA;AACA,EAAE;AACF;AACA;AACA,aAAa,8CAAU;AACvB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,UAAU;AAC7C;;AAEA;AACA;AACA,EAAE;AACF;;AAEA;AACA;AACA,EAAE;AACF;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,mCAAmC,UAAU;AAC7C;;AAEA;AACA;AACA,EAAE;AACF;;AAEA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sDAAsD,UAAU;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC;AACtC,uCAAuC,gBAAgB,iBAAiB,iBAAiB;AACzF,YAAY;AACZ;AACA,6BAA6B,gBAAgB,iBAAiB,iBAAiB;AAC/E,YAAY;AACZ;AACA;AACA,iCAAiC,WAAW,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC,oCAAoC,gBAAgB,iBAAiB,iBAAiB;AACtF;AACA;AACA,2BAA2B,gBAAgB,gBAAgB;AAC3D,oBAAoB,cAAc;AAClC;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,WAAW,0BAA0B;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,UAAU,QAAQ,WAAW;AAC9D;AACA,2BAA2B,WAAW,qBAAqB,YAAY;AACvE;AACA,gDAAgD,WAAW;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,8CAAU;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;ACh1BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACwD;AACA;AACL;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C,wCAAwC;AACxC;AACA,yCAAyC;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uDAAuD;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,cAAc;AACd,eAAe;AACf;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,aAAa,qBAAqB;AAClC,cAAc,wBAAwB;AACtC,eAAe,qBAAqB;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D,iBAAiB;AAC5E;AACA;AACA;AACA;AACA;AACA,6DAA6D,iBAAiB;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8CAA8C,4BAA4B,eAAe,uCAAuC,aAAa,sCAAsC,eAAe,iBAAiB;AACnN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,wBAAwB;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,6DAAU;AACtC,8BAA8B,QAAQ;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,eAAe,kEAAgB,EAAE,kEAAgB;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;ACzhBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACwD;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA,CAAC,gDAAgD;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB;AACtB,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,gCAAgC,EAAE,KAAK;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,eAAe,kEAAgB;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,eAAe,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7X/B;AACA;AACA;AACA;AACiD;AACiC;AACxB;AACN;AACX;AACf;AACiC;AACH;AACsB;AACR;AAChB;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,8DAAO;AAC5B;AACA,YAAY,wDAAW;AACvB,CAAC;AACD;AACA;AACA;AACA;AACA,gCAAgC,6DAAW;AAC3C;AACA;AACA;AACA;AACA,+BAA+B,+DAAW;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,QAAQ;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,8DAAY,uBAAuB,qCAAqC;AAC5F;AACA,qBAAqB;AACrB;AACA;AACA,oBAAoB,8DAAY,qBAAqB,aAAa;AAClE;AACA,qBAAqB;AACrB;AACA,aAAa;AACb;AACA,gBAAgB,8DAAY,uBAAuB,iBAAiB;AACpE,aAAa;AACb;AACA;AACA,aAAa;AACb;AACA,YAAY,8DAAY,0BAA0B,iBAAiB;AACnE;AACA;AACA;AACA;AACA,YAAY,8DAAY,oBAAoB,cAAc;AAC1D;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,8DAAY,sBAAsB,iBAAiB;AAC/D;AACA;AACA;AACA,YAAY,8DAAY,iBAAiB,cAAc,KAAK,iBAAiB;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,8DAAY,wBAAwB,SAAS;AACzD;AACA,aAAa;AACb;AACA;AACA;AACA,YAAY,8DAAY,qBAAqB,cAAc;AAC3D;AACA,aAAa;AACb;AACA;AACA;AACA,sBAAsB,0DAAW;AACjC;AACA,gBAAgB,0DAAmB,CAAC,wDAAa,IAAI,cAAc;AACnE,YAAY,0DAAmB,UAAU,SAAS,wDAAwD,0CAA0C,0DAAmB,UAAU;AACjL;AACA,mBAAmB;AACnB,gBAAgB,0DAAmB,CAAC,8EAAkB,IAAI;AAC1D,wEAAwE;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,6DAAW;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,0DAAW;AACjC,gBAAgB,0DAAmB,CAAC,wDAAa,IAAI,cAAc;AACnE,YAAY,0DAAmB,CAAC,sFAAsB,IAAI,kEAAkE;AAC5H;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA,eAAe,oEAAgB;AAC/B,eAAe,2DAAS,EAAE,iEAAe;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY,mDAAM;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;AC/NA;AACA;AACA;AACA;AACA;AACwD;AACD;AACvD;AACA;AACA;AACO;AACP;AACA;AACA,eAAe,kEAAgB;AAC/B,eAAe,iEAAe;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0GAA0G,MAAM;AAChH;AACA;AACA;AACA;AACA;AACA;AACA,0GAA0G,MAAM;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA;AACA,sGAAsG,MAAM;AAC5G,4BAA4B;AAC5B;AACA,6GAA6G,MAAM;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,SAAS;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC;AACtC,uCAAuC,iCAAiC,iBAAiB,iBAAiB;AAC1G;AACA;AACA,6BAA6B,iCAAiC,iBAAiB,iBAAiB;AAChG;AACA;AACA;AACA,iCAAiC,WAAW,0BAA0B;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAiE,YAAY;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,qDAAqD,cAAc;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,WAAW,GAAG,wCAAwC;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,oBAAoB;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7kBA;AACA;AACA;AACA;AAC0D;AACY;AACd;AACF;AACO;AACD;AACR;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,eAAe,oEAAe,EAAE,iEAAe,EAAE,kEAAgB,EAAE,gEAAe,EAAE,uEAAmB;AACvG;AACA;AACA;AACA;AACA,mCAAmC,4DAAU;AAC7C;AACA,mCAAmC,oEAAgB;AACnD;AACA;AACA,oCAAoC,+DAAa;AACjD;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,iDAAiD,WAAW;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,aAAa,QAAQ,gLAA+B;AACpE;AACA,gBAAgB,SAAS,QAAQ,gLAA+B;AAChE;AACA,wCAAwC,QAAQ;AAChD;AACA,oCAAoC,gBAAgB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACP;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,sEAAsE,gBAAgB;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU,IAAI,0BAA0B;AACxC;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA,cAAc,gBAAgB,KAAK,cAAc;AACjD;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA,iBAAiB;AACjB;AACA;AACA,iBAAiB,mCAAmC,EAAE;AACtD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,qBAAqB;AAC5D;AACA,oBAAoB,iBAAiB;AACrC;AACA;AACA;AACA,0DAA0D,oBAAoB,GAAG,YAAY;AAC7F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,oBAAoB,GAAG,YAAY;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;ACrYA;AACA;AACA;AACyJ;AACzJ;AAC2D;AACpD;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+BAA+B,6DAAU;AACzC;AACA;AACA,2BAA2B,yDAAM;AACjC;AACA,yEAAyE;AACzE;AACA;AACA;AACA;AACA;AACA,yBAAyB,EAAE,gBAAgB;AAC3C,qCAAqC,EAAE,KAAK;AAC5C;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa;AACzD;AACA;AACA,aAAa;AACb;AACA;AACA;AACA,iEAAiE;AACjE;AACA;AACA;AACA;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,uBAAuB;AACrD;AACA;AACA,oBAAoB,uBAAuB,oEAAoB;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA,oBAAoB,gEAAgB;AACpC;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,sEAAsE,YAAY;AAClF;AACA,0CAA0C,oEAAoB;AAC9D;AACA;AACA,qCAAqC,kEAAkB;AACvD;AACA,kDAAkD,kEAAkB;AACpE;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,0CAA0C,oEAAoB;AAC9D;AACA,qCAAqC,kEAAkB;AACvD;AACA,kDAAkD,kEAAkB;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,0BAA0B,gCAAgC;AACnH;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,2BAA2B,kCAAkC;AACtH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,uBAAuB;AACrD;AACA,oBAAoB,uBAAuB,oEAAoB;AAC/D;AACA;AACA;AACA,gBAAgB,gEAAgB;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,2EAA2E,YAAY;AACvF,0CAA0C,oEAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,4DAA4D,MAAM;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6CAA6C,qCAAqC;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,uBAAuB;AACrD;AACA;AACA,oBAAoB,uBAAuB,oEAAoB;AAC/D;AACA;AACA;AACA;AACA,gBAAgB,gEAAgB;AAChC;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,gEAAgB;AACpC,sEAAsE,YAAY;AAClF;AACA,0CAA0C,oEAAoB;AAC9D;AACA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gCAAgC,oEAAoB;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,cAAc;AAC1F,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uDAAuD,MAAM;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA,4CAA4C;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAiD,0BAA0B;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,WAAW;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D;AAC3D;AACA;AACA;AACA,yDAAyD;AACzD;AACA,iCAAiC;AACjC,wCAAwC;AACxC;AACA;AACA;AACA;AACA,6CAA6C,WAAW,kCAAkC;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6DAA6D,SAAS,gBAAgB;AACtF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+EAA+E,GAAG;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB;AACjB;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uDAAuD,MAAM;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD,WAAW;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D;AAC3D;AACA;AACA;AACA,yDAAyD;AACzD;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+EAA+E,GAAG;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,mCAAmC,qBAAqB;AACxD,SAAS;AACT;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,mCAAmC,qBAAqB;AACxD,SAAS;AACT;AACA;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,mCAAmC,6CAA6C;AAChF,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT;AACA,iEAAiE;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,6BAA6B;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,SAAS;AACT,6DAA6D;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,+DAA+D,yCAAyC;AACxG;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,8BAA8B,gDAAgD;AACvI;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,gCAAgC,gDAAgD;AACzI;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,gCAAgC,iDAAiD;AAC1I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,kCAAkC,iCAAiC;AAC5H;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,iCAAiC,qDAAqD;AAC/I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,sCAAsC,iCAAiC;AAChI;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,wDAAwD,MAAM;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC;AACA;AACA,kDAAkD,cAAc;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,aAAa,0BAA0B,gDAAgD;AACnI;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,uDAAuD,MAAM;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,sDAAsD,MAAM;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB,aAAa,uBAAuB,iDAAiD;AAC5G;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,mEAAmE,MAAM;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,iEAAiE,MAAM;AACvE;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;ACxtCA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,EAAE,gBAAgB;AAC3C,qCAAqC,EAAE,KAAK;AAC5C;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa;AACrD;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,gCAAgC,gDAAgD;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa,QAAQ,OAAO;AACpE;AACA;AACA;AACA,gCAAgC,sCAAsC;AACtE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,+CAA+C,aAAa,QAAQ,OAAO;AAC3E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,aAAa,QAAQ,OAAO;AACpE;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,gCAAgC,kCAAkC;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxIA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,CAAC,gCAAgC;AAC1B;AACP;AACA;AACA;AACA;AACA;AACA,CAAC,gCAAgC;AACjC;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,CAAC,gCAAgC;;;;;;;;;;;;;;;;;;;ACxBjC;AACA;AACA;AACA;AACA;AACA;AACA,+EAA+E,KAAK,YAAY,KAAK,8BAA8B,MAAM,iCAAiC,sBAAsB,IAAI,KAAK;AACzM;AACA,kFAAkF,KAAK,YAAY,KAAK,mCAAmC,MAAM,iCAAiC,sBAAsB,IAAI,KAAK;AACjN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iEAAe,KAAK,EAAC;;;;;;;;;;;;;;;;;;;;;;;;AC/NrB;AACA;AACA;AACA;AACwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,gBAAgB;AACpC;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA,oBAAoB,kBAAkB;AACtC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8BAA8B,IAAI;AAClC,4CAA4C,IAAI;AAChD;AACA;AACA,oBAAoB,SAAS;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,wCAAwC;AAC5F;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uEAAuE,iBAAiB;AACxF;AACA;AACA,WAAW;AACX,WAAW;AACX,WAAW;AACX;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD;AACpD;AACA;AACA;AACA;AACA,wCAAwC,wCAAwC;AAChF;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB,qEAAqE,aAAa,KAAK;AACvF;AACA,oBAAoB,cAAc;AAClC,0BAA0B;AAC1B;AACA;AACA;AACA,kFAAkF;AAClF,2BAA2B;AAC3B;AACA;AACA;AACA,sBAAsB,MAAM;AAC5B;AACA,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,iBAAiB;AACzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uBAAuB;AACvB;AACA;AACA,4BAA4B;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qCAAqC,iCAAiC;AACtE;AACA;AACA;AACA;AACA;AACA,8CAA8C,eAAe;AAC7D;AACA;AACA,uDAAuD;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB;AACrB,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,iDAAiD,iBAAiB;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4EAA4E,QAAQ;AACpF,wFAAwF,YAAY;AACpG;AACA;AACA;AACA,MAAM;AACN,MAAM;AACN;AACA,IAAI;AACJ;AACA,KAAK;AACL;AACA,wBAAwB,qDAAa;AACrC,2BAA2B,qDAAa;AACxC;AACA;AACA;AACA,wEAAwE,YAAY;AACpF;AACA,MAAM;AACN,sCAAsC,oBAAoB;AAC1D;AACA;AACA;AACA;AACA;AACA,8EAA8E,eAAe;AAC7F;AACA,MAAM;AACN;AACA;AACA,cAAc;AACd,EAAE,cAAc;AAChB;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,gBAAgB,IAAI,kBAAkB;AACxE,SAAS;AACT;AACA,KAAK;AACL;AACA,6CAA6C,EAAE;AAC/C,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,eAAe,IAAI,kBAAkB;AACvE,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,eAAe,IAAI,kBAAkB;AACvE,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA,kCAAkC,eAAe,IAAI,MAAM;AAC3D,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,QAAQ;AAC/C;AACA,8BAA8B,mBAAmB;AACjD;AACA;AACA,sCAAsC,gBAAgB,IAAI,MAAM;AAChE,aAAa;AACb;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC,QAAQ;AAC/C;AACA,8BAA8B,mBAAmB;AACjD;AACA;AACA,sCAAsC,gBAAgB,IAAI,MAAM;AAChE,aAAa;AACb;AACA,SAAS;AACT,KAAK;AACL;AACA;AACA;AACA;AACA,8BAA8B,mBAAmB;AACjD;AACA;AACA,sCAAsC,iBAAiB,IAAI,SAAS;AACpE,aAAa;AACb;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA,oEAAoE,iBAAiB;AACrF;AACA,sCAAsC,sBAAsB,gBAAgB;AAC5E;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA,gDAAgD,YAAY;AAC5D;AACA;AACA;AACA,8CAA8C,YAAY;AAC1D;AACA;AACA;AACA,6CAA6C,YAAY;AACzD;AACA;AACA;AACA,6CAA6C,YAAY;AACzD;AACA;AACA,6CAA6C,YAAY;AACzD,KAAK;AACL;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uDAAuD,gCAAgC;AACvF;AACA;AACA,mDAAmD,KAAK;AACxD;AACA;AACA;AACA;AACA,qEAAqE,gBAAgB;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oDAAoD,uDAAuD;AAC3G;AACA;AACA;AACA;AACA;AACA,oDAAoD,kCAAkC;AACtF;AACA;AACA;AACA;AACA;AACA;AACA,gDAAgD,6BAA6B;AAC7E;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,8CAA8C,OAAO,IAAI,kCAAkC;AAC3F,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;AACA;AACA,kDAAkD,OAAO,IAAI,kCAAkC;AAC/F,aAAa;AACb;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,uBAAuB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC76BA;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,eAAe,E;;;;;;;;;;;;;;;;ACNhB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,iBAAiB,E;;;;;;;;;;;;;;;;ACNlB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,YAAY,E;;;;;;;;;;;;;;;;ACNb;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,WAAW,E;;;;;;;;;;;;;;;;ACNZ;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,iBAAiB,E;;;;;;;;;;;;;;;;ACNlB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,kBAAkB,E;;;;;;;;;;;;;;;;ACNnB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,WAAW,E;;;;;;;;;;;;;;;;ACNZ;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,UAAU,E;;;;;;;;;;;;;;;;ACNX;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,iBAAiB,E;;;;;;;;;;;;;;;;ACNlB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,iBAAiB,E;;;;;;;;;;;;;;;;ACNlB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,WAAW,E;;;;;;;;;;;;;;;;ACNZ;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,gBAAgB,E;;;;;;;;;;;;;;;;ACNjB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,gBAAgB,E;;;;;;;;;;;;;;;;ACNjB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,YAAY,E;;;;;;;;;;;;;;;;ACNb;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,cAAc,E;;;;;;;;;;;;;;;;ACNf;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,oBAAoB,E;;;;;;;;;;;;;;;;ACNrB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,qBAAqB,E;;;;;;;;;;;;;;;;ACNtB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,eAAe,E;;;;;;;;;;;;;;;;ACNhB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,iBAAiB,E;;;;;;;;;;;;;;;;ACNlB;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,YAAY,E;;;;;;;;;;;;;;;;ACNb;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,yBAAyB,E;;;;;;;;;;;;;;;;ACN1B;;AAEqD;AACL;AAChD,iEAAe,mEAAa,cAAc,sDAAI;AAC9C;AACA,CAAC,sBAAsB,E","sources":["webpack://hdsp-agent/./lib/components/AgentPanel.js","webpack://hdsp-agent/./lib/components/ContextAutocomplete.js","webpack://hdsp-agent/./lib/components/FileSelectionDialog.js","webpack://hdsp-agent/./lib/components/PromptGenerationDialog.js","webpack://hdsp-agent/./lib/components/SettingsPanel.js","webpack://hdsp-agent/./lib/components/StreamingMessage.js","webpack://hdsp-agent/./lib/components/TaskProgressWidget.js","webpack://hdsp-agent/./lib/components/code-blocks/CodeToolbar.js","webpack://hdsp-agent/./lib/components/code-blocks/CopyButton.js","webpack://hdsp-agent/./lib/components/code-blocks/index.js","webpack://hdsp-agent/./lib/hooks/useStreamingMarkdown.js","webpack://hdsp-agent/./lib/index.js","webpack://hdsp-agent/./lib/logoSvg.js","webpack://hdsp-agent/./lib/plugins/cell-buttons-plugin.js","webpack://hdsp-agent/./lib/plugins/idle-monitor-plugin.js","webpack://hdsp-agent/./lib/plugins/lsp-bridge-plugin.js","webpack://hdsp-agent/./lib/plugins/prompt-generation-plugin.js","webpack://hdsp-agent/./lib/plugins/save-interceptor-plugin.js","webpack://hdsp-agent/./lib/plugins/sidebar-plugin.js","webpack://hdsp-agent/./lib/services/ApiKeyManager.js","webpack://hdsp-agent/./lib/services/ApiService.js","webpack://hdsp-agent/./lib/services/TaskService.js","webpack://hdsp-agent/./lib/types/index.js","webpack://hdsp-agent/./lib/utils/icons.js","webpack://hdsp-agent/./lib/utils/markdownRenderer.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Analytics.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/AutoFixHigh.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Cancel.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Check.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/CheckCircle.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/ChevronRight.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Close.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Code.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/ContentCopy.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Description.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Error.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/ExpandLess.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/ExpandMore.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Folder.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/GpsFixed.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/HourglassEmpty.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/InsertDriveFile.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Lightbulb.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/PlaylistAdd.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/Search.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/VerticalAlignBottom.js","webpack://hdsp-agent/./node_modules/@mui/icons-material/esm/VerticalAlignTop.js"],"sourcesContent":["/**\n * Agent Panel - Main sidebar panel for Jupyter Agent\n * Cursor AI Style: Unified Chat + Agent Interface\n */\nimport React, { useState, useEffect, useRef, useImperativeHandle, forwardRef, useCallback } from 'react';\nimport { ReactWidget, LabIcon } from '@jupyterlab/ui-components';\nimport { NotebookPanel, NotebookActions } from '@jupyterlab/notebook';\nimport { SettingsPanel } from './SettingsPanel';\nimport { getLLMConfig, saveLLMConfig, hasValidApiKey, getDefaultLLMConfig } from '../services/ApiKeyManager';\nimport { formatMarkdownToHtml } from '../utils/markdownRenderer';\nimport { FileSelectionDialog } from './FileSelectionDialog';\nimport { StreamingMessage } from './StreamingMessage';\nimport { ContextAutocomplete } from './ContextAutocomplete';\nimport { Icons, getIconByName } from '../utils/icons';\nimport ExpandLessIcon from '@mui/icons-material/ExpandLess';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\n// 로고 이미지 (SVG) - TypeScript 모듈에서 인라인 문자열로 import\nimport { headerLogoSvg, tabbarLogoSvg } from '../logoSvg';\n// 탭바 아이콘 생성\nconst hdspTabIcon = new LabIcon({\n name: 'hdsp-agent:tab-icon',\n svgstr: tabbarLogoSvg\n});\n// ═══════════════════════════════════════════════════════════════════════════\n// Python 파일 에러 감지 및 처리 유틸리티\n// ═══════════════════════════════════════════════════════════════════════════\n// Python 에러 패턴 감지\nconst detectPythonError = (text) => {\n const errorPatterns = [\n /Traceback \\(most recent call last\\)/i,\n /SyntaxError:/i,\n /NameError:/i,\n /TypeError:/i,\n /ImportError:/i,\n /ModuleNotFoundError:/i,\n /AttributeError:/i,\n /ValueError:/i,\n /KeyError:/i,\n /IndexError:/i,\n /FileNotFoundError:/i,\n /File\\s+\"[^\"]+\\.py\"/i,\n ];\n return errorPatterns.some(pattern => pattern.test(text));\n};\n// 에러 메시지에서 Python 파일 경로 추출\nconst extractFilePathsFromError = (text) => {\n const paths = [];\n // 패턴 1: python xxx.py 명령어\n const cmdMatch = text.match(/python\\s+(\\S+\\.py)/gi);\n if (cmdMatch) {\n cmdMatch.forEach(match => {\n const pathMatch = match.match(/python\\s+(\\S+\\.py)/i);\n if (pathMatch)\n paths.push(pathMatch[1]);\n });\n }\n // 패턴 2: File \"xxx.py\" 형식 (트레이스백)\n const fileMatches = text.matchAll(/File\\s+\"([^\"]+\\.py)\"/gi);\n for (const match of fileMatches) {\n if (!paths.includes(match[1])) {\n paths.push(match[1]);\n }\n }\n return paths;\n};\n// 메인 파일 경로 추출 (명령어에서 실행한 파일)\nconst extractMainFilePath = (text) => {\n // python xxx.py 명령어에서 추출\n const cmdMatch = text.match(/python\\s+(\\S+\\.py)/i);\n if (cmdMatch) {\n return cmdMatch[1];\n }\n return null;\n};\n// 에러가 발생한 실제 파일 추출 (트레이스백의 마지막 파일)\nconst extractErrorFilePath = (text) => {\n // File \"xxx.py\" 패턴들 중 마지막 것 (실제 에러 발생 위치)\n const fileMatches = [...text.matchAll(/File\\s+\"([^\"]+\\.py)\"/gi)];\n if (fileMatches.length > 0) {\n return fileMatches[fileMatches.length - 1][1];\n }\n return null;\n};\n// Python 파일에서 로컬 import 추출\nconst extractLocalImports = (content) => {\n const imports = [];\n // from xxx import yyy (로컬 모듈)\n const fromImports = content.matchAll(/^from\\s+(\\w+)\\s+import/gm);\n for (const match of fromImports) {\n const moduleName = match[1];\n // 표준 라이브러리가 아닌 것만 (간단한 휴리스틱)\n if (!isStdLibModule(moduleName)) {\n imports.push(moduleName);\n }\n }\n // import xxx (로컬 모듈)\n const directImports = content.matchAll(/^import\\s+(\\w+)/gm);\n for (const match of directImports) {\n const moduleName = match[1];\n if (!isStdLibModule(moduleName)) {\n imports.push(moduleName);\n }\n }\n return [...new Set(imports)];\n};\n// 표준 라이브러리 모듈 체크 (간단한 목록)\nconst isStdLibModule = (name) => {\n const stdLibModules = [\n 'os', 'sys', 'json', 're', 'math', 'datetime', 'time', 'random',\n 'collections', 'itertools', 'functools', 'typing', 'pathlib',\n 'subprocess', 'threading', 'multiprocessing', 'asyncio', 'socket',\n 'http', 'urllib', 'email', 'html', 'xml', 'logging', 'unittest',\n 'io', 'pickle', 'copy', 'pprint', 'traceback', 'warnings',\n 'contextlib', 'abc', 'dataclasses', 'enum', 'types',\n // 외부 라이브러리 (일반적으로 설치됨)\n 'numpy', 'pandas', 'matplotlib', 'seaborn', 'sklearn', 'scipy',\n 'torch', 'tensorflow', 'keras', 'requests', 'flask', 'django',\n ];\n return stdLibModules.includes(name.toLowerCase());\n};\nconst ChatPanel = forwardRef(({ apiService, notebookTracker, consoleTracker }, ref) => {\n // 통합 메시지 목록 (Chat + Agent 실행)\n const [messages, setMessages] = useState([]);\n const [input, setInput] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [isStreaming, setIsStreaming] = useState(false);\n const [streamingMessageId, setStreamingMessageId] = useState(null);\n const [conversationId, setConversationId] = useState('');\n const [showSettings, setShowSettings] = useState(false);\n const [llmConfig, setLlmConfig] = useState(null);\n // Agent 실행 상태\n const [isAgentRunning, setIsAgentRunning] = useState(false);\n // 입력 모드 (Cursor AI 스타일) - 로컬 스토리지에서 복원\n // Note: 'agent_v2'는 'agent'로 마이그레이션\n const [inputMode, setInputMode] = useState(() => {\n try {\n const saved = localStorage.getItem('hdsp-agent-input-mode');\n if (saved === 'agent_v2')\n return 'agent'; // 마이그레이션\n return (saved === 'agent' || saved === 'chat') ? saved : 'agent'; // 기본값: agent\n }\n catch {\n return 'agent'; // 기본값: agent\n }\n });\n const [showModeDropdown, setShowModeDropdown] = useState(false);\n // 파일 수정 관련 상태\n const [pendingFileFixes, setPendingFileFixes] = useState([]);\n // Console 에러 자동 감지 상태\n const [lastConsoleError, setLastConsoleError] = useState(null);\n const [showConsoleErrorNotification, setShowConsoleErrorNotification] = useState(false);\n // File selection state\n const [fileSelectionMetadata, setFileSelectionMetadata] = useState(null);\n const [pendingAgentRequest, setPendingAgentRequest] = useState(null);\n // Context autocomplete state (@file)\n const [showAutocomplete, setShowAutocomplete] = useState(false);\n const [hasAutocompleteOptions, setHasAutocompleteOptions] = useState(false);\n const [cursorPosition, setCursorPosition] = useState(0);\n const textareaRef = useRef(null);\n // Human-in-the-Loop state\n const [debugStatus, setDebugStatusRaw] = useState(null);\n const [isDebugExpanded, setIsDebugExpanded] = useState(false);\n // Minimum display time for status messages (2 seconds)\n const MIN_STATUS_DISPLAY_MS = 2000;\n const lastStatusTimeRef = useRef(0);\n const statusTimerRef = useRef(null);\n const pendingStatusRef = useRef(null);\n // Wrapper for setDebugStatus that ensures minimum 1 second display time\n const setDebugStatus = useCallback((status) => {\n const now = Date.now();\n const timeSinceLastStatus = now - lastStatusTimeRef.current;\n // Clear any pending timer\n if (statusTimerRef.current) {\n clearTimeout(statusTimerRef.current);\n statusTimerRef.current = null;\n }\n // If clearing status (null), allow immediately\n if (status === null) {\n // If last status was shown recently, delay the clear\n if (timeSinceLastStatus < MIN_STATUS_DISPLAY_MS && lastStatusTimeRef.current > 0) {\n const delay = MIN_STATUS_DISPLAY_MS - timeSinceLastStatus;\n statusTimerRef.current = setTimeout(() => {\n setDebugStatusRaw(null);\n lastStatusTimeRef.current = 0;\n }, delay);\n }\n else {\n setDebugStatusRaw(null);\n lastStatusTimeRef.current = 0;\n }\n return;\n }\n // If previous status was shown recently, queue this status\n if (timeSinceLastStatus < MIN_STATUS_DISPLAY_MS && lastStatusTimeRef.current > 0) {\n const delay = MIN_STATUS_DISPLAY_MS - timeSinceLastStatus;\n pendingStatusRef.current = status;\n statusTimerRef.current = setTimeout(() => {\n if (pendingStatusRef.current !== null) {\n setDebugStatusRaw(pendingStatusRef.current);\n lastStatusTimeRef.current = Date.now();\n pendingStatusRef.current = null;\n }\n }, delay);\n }\n else {\n // Show immediately\n setDebugStatusRaw(status);\n lastStatusTimeRef.current = now;\n }\n }, []);\n // Cleanup status timer on unmount\n useEffect(() => {\n return () => {\n if (statusTimerRef.current) {\n clearTimeout(statusTimerRef.current);\n }\n };\n }, []);\n const [interruptData, setInterruptData] = useState(null);\n // Todo list state (from TodoListMiddleware)\n const [todos, setTodos] = useState([]);\n const [isTodoExpanded, setIsTodoExpanded] = useState(false);\n // Agent thread ID for context persistence across cycles (SummarizationMiddleware support)\n const [agentThreadId, setAgentThreadId] = useState(null);\n // AbortController for stopping long-running tasks\n const abortControllerRef = useRef(null);\n // Rejection feedback mode - when user clicks reject, wait for optional feedback\n const [isRejectionMode, setIsRejectionMode] = useState(false);\n const [pendingRejectionInterrupt, setPendingRejectionInterrupt] = useState(null);\n // Ask user mode - when ask_user_tool is called, wait for user input\n const [isAskUserMode, setIsAskUserMode] = useState(false);\n const [pendingAskUserInterrupt, setPendingAskUserInterrupt] = useState(null);\n const interruptMessageIdRef = useRef(null);\n const approvalPendingRef = useRef(false);\n const pendingToolCallsRef = useRef([]);\n const handledToolCallKeysRef = useRef(new Set());\n const isNotebookWidget = (widget) => {\n if (!widget)\n return false;\n if (widget instanceof NotebookPanel)\n return true;\n const model = widget?.content?.model;\n return Boolean(model && (model.cells || model.sharedModel?.cells));\n };\n const getActiveNotebookPanel = () => {\n const app = window.jupyterapp;\n const currentWidget = app?.shell?.currentWidget;\n if (isNotebookWidget(currentWidget)) {\n return currentWidget;\n }\n if (notebookTracker?.currentWidget && isNotebookWidget(notebookTracker.currentWidget)) {\n return notebookTracker.currentWidget;\n }\n return null;\n };\n /**\n * Scroll notebook to show a specific cell\n */\n const scrollToNotebookCell = (notebook, cellIndex) => {\n try {\n const cell = notebook.content.widgets[cellIndex];\n if (cell?.node) {\n // Small delay to ensure the cell is rendered in DOM\n setTimeout(() => {\n cell.node.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n });\n }, 100);\n }\n }\n catch (error) {\n console.warn('[AgentPanel] Failed to scroll to cell:', error);\n }\n };\n const insertCell = (notebook, cellType, source) => {\n const model = notebook.content?.model;\n const cellCount = model?.cells?.length ?? model?.sharedModel?.cells?.length;\n if (!model?.sharedModel || cellCount === undefined) {\n console.warn('[AgentPanel] Notebook model not ready for insert');\n return null;\n }\n const insertIndex = cellCount;\n model.sharedModel.insertCell(insertIndex, {\n cell_type: cellType,\n source\n });\n notebook.content.activeCellIndex = insertIndex;\n // Scroll to the newly inserted cell\n scrollToNotebookCell(notebook, insertIndex);\n return insertIndex;\n };\n // Insert cell above active cell\n const insertCellAbove = (notebook, source) => {\n const model = notebook.content?.model;\n if (!model?.sharedModel) {\n console.warn('[AgentPanel] Notebook model not ready for insertAbove');\n return null;\n }\n const activeIndex = notebook.content.activeCellIndex;\n const insertIndex = activeIndex >= 0 ? activeIndex : 0;\n model.sharedModel.insertCell(insertIndex, {\n cell_type: 'code',\n source\n });\n notebook.content.activeCellIndex = insertIndex;\n // Scroll to the newly inserted cell\n scrollToNotebookCell(notebook, insertIndex);\n console.log('[AgentPanel] Inserted cell above at index:', insertIndex);\n return insertIndex;\n };\n // Insert cell below active cell\n const insertCellBelow = (notebook, source) => {\n const model = notebook.content?.model;\n const cellCount = model?.cells?.length ?? model?.sharedModel?.cells?.length;\n if (!model?.sharedModel || cellCount === undefined) {\n console.warn('[AgentPanel] Notebook model not ready for insertBelow');\n return null;\n }\n const activeIndex = notebook.content.activeCellIndex;\n const insertIndex = activeIndex >= 0 ? activeIndex + 1 : cellCount;\n model.sharedModel.insertCell(insertIndex, {\n cell_type: 'code',\n source\n });\n notebook.content.activeCellIndex = insertIndex;\n // Scroll to the newly inserted cell\n scrollToNotebookCell(notebook, insertIndex);\n console.log('[AgentPanel] Inserted cell below at index:', insertIndex);\n return insertIndex;\n };\n // Callbacks for code block toolbar\n const handleInsertCodeAbove = useCallback((code) => {\n const notebook = getActiveNotebookPanel();\n if (notebook) {\n insertCellAbove(notebook, code);\n }\n else {\n console.warn('[AgentPanel] No active notebook for insert above');\n }\n }, []);\n const handleInsertCodeBelow = useCallback((code) => {\n const notebook = getActiveNotebookPanel();\n if (notebook) {\n insertCellBelow(notebook, code);\n }\n else {\n console.warn('[AgentPanel] No active notebook for insert below');\n }\n }, []);\n const handleInsertCodeAsCell = useCallback((code) => {\n const notebook = getActiveNotebookPanel();\n if (notebook) {\n insertCell(notebook, 'code', code);\n }\n else {\n console.warn('[AgentPanel] No active notebook for insert as cell');\n }\n }, []);\n const deleteCell = (notebook, cellIndex) => {\n const model = notebook.content?.model;\n const cellCount = model?.cells?.length ?? model?.sharedModel?.cells?.length;\n if (!model?.sharedModel || cellCount === undefined) {\n console.warn('[AgentPanel] Notebook model not ready for delete');\n return false;\n }\n if (cellIndex < 0 || cellIndex >= cellCount) {\n console.warn('[AgentPanel] Invalid cell index for delete:', cellIndex);\n return false;\n }\n model.sharedModel.deleteCell(cellIndex);\n console.log('[AgentPanel] Deleted rejected cell at index:', cellIndex);\n return true;\n };\n const executeCell = async (notebook, cellIndex) => {\n try {\n await notebook.sessionContext.ready;\n notebook.content.activeCellIndex = cellIndex;\n // Scroll to the cell being executed\n scrollToNotebookCell(notebook, cellIndex);\n await NotebookActions.run(notebook.content, notebook.sessionContext);\n }\n catch (error) {\n console.error('[AgentPanel] Cell execution failed:', error);\n }\n };\n const captureExecutionResult = (notebook, cellIndex) => {\n const cell = notebook.content.widgets[cellIndex];\n const model = cell?.model;\n const outputs = model?.outputs;\n const rawState = model?.executionState;\n const executionState = typeof rawState === 'string'\n ? rawState\n : rawState?.get?.() ?? rawState?.value ?? null;\n const result = {\n success: true,\n output: '',\n error: undefined,\n error_type: undefined,\n traceback: undefined,\n execution_count: model?.executionCount ?? null,\n execution_state: executionState,\n kernel_status: notebook.sessionContext?.session?.kernel?.status ?? null,\n cell_type: model?.type ?? null,\n outputs: [],\n };\n if (!outputs || outputs.length === 0) {\n return result;\n }\n const stripImageData = (data) => {\n const filtered = {};\n Object.keys(data || {}).forEach((key) => {\n if (!key.toLowerCase().startsWith('image/')) {\n filtered[key] = data[key];\n }\n });\n return filtered;\n };\n const errorSignatures = [\n 'command not found',\n 'ModuleNotFoundError',\n 'No module named',\n 'Traceback (most recent call last)',\n 'Error:',\n ];\n for (let i = 0; i < outputs.length; i++) {\n const output = outputs.get(i);\n const json = output.toJSON?.() || output;\n let sanitizedOutput = json;\n if (output.type === 'error') {\n result.outputs.push(sanitizedOutput);\n result.success = false;\n result.error_type = json.ename;\n result.error = json.evalue;\n if (Array.isArray(json.traceback)) {\n result.traceback = json.traceback;\n }\n }\n else if (output.type === 'stream' && json.text) {\n result.outputs.push(sanitizedOutput);\n result.output += json.text;\n const lowerText = json.text.toLowerCase();\n if (errorSignatures.some(signature => lowerText.includes(signature.toLowerCase()))) {\n result.success = false;\n result.error_type = 'runtime_error';\n result.error = json.text.trim();\n }\n }\n else if ((output.type === 'execute_result' || output.type === 'display_data') && json.data) {\n const filteredData = stripImageData(json.data);\n if (Object.keys(filteredData).length > 0) {\n sanitizedOutput = { ...json, data: filteredData };\n result.outputs.push(sanitizedOutput);\n result.output += JSON.stringify(filteredData);\n }\n }\n }\n return result;\n };\n const executePendingApproval = async () => {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n console.warn('[AgentPanel] No active notebook to execute cell');\n return null;\n }\n const pending = pendingToolCallsRef.current;\n if (pending.length === 0) {\n approvalPendingRef.current = false;\n return null;\n }\n const next = pending.shift();\n if (!next) {\n approvalPendingRef.current = false;\n return null;\n }\n if (next.tool === 'jupyter_cell' && next.code && typeof next.cellIndex === 'number') {\n await executeCell(notebook, next.cellIndex);\n const execResult = captureExecutionResult(notebook, next.cellIndex);\n execResult.code = next.code;\n next.execution_result = execResult;\n console.log('[AgentPanel] Executed approved code cell from tool call');\n approvalPendingRef.current = pendingToolCallsRef.current.length > 0;\n return next;\n }\n approvalPendingRef.current = pendingToolCallsRef.current.length > 0;\n return next;\n };\n const getAutoApproveEnabled = (config) => (Boolean(config?.autoApprove));\n const queueApprovalCell = (code) => {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n console.warn('[AgentPanel] No active notebook to add approval cell');\n return;\n }\n const key = `jupyter_cell:${code}`;\n if (handledToolCallKeysRef.current.has(key)) {\n return;\n }\n const index = insertCell(notebook, 'code', code);\n if (index === null) {\n return;\n }\n handledToolCallKeysRef.current.add(key);\n approvalPendingRef.current = true;\n pendingToolCallsRef.current.push({ tool: 'jupyter_cell', code, cellIndex: index });\n console.log('[AgentPanel] Added code cell pending approval via interrupt');\n };\n const handleToolCall = (toolCall) => {\n const key = `${toolCall.tool}:${toolCall.code || toolCall.content || ''}`;\n if (handledToolCallKeysRef.current.has(key)) {\n return;\n }\n if (toolCall.tool === 'jupyter_cell') {\n return;\n }\n if (toolCall.tool === 'markdown' && toolCall.content) {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n console.warn('[AgentPanel] No active notebook to add markdown');\n return;\n }\n const index = insertCell(notebook, 'markdown', toolCall.content);\n if (index !== null) {\n handledToolCallKeysRef.current.add(key);\n console.log('[AgentPanel] Added markdown cell from tool call');\n }\n }\n };\n // 모드 변경 시 로컬 스토리지에 저장\n useEffect(() => {\n try {\n localStorage.setItem('hdsp-agent-input-mode', inputMode);\n }\n catch {\n // 로컬 스토리지 접근 불가 시 무시\n }\n }, [inputMode]);\n const messagesContainerRef = useRef(null);\n const messagesEndRef = useRef(null);\n const pendingLlmPromptRef = useRef(null);\n const allCodeBlocksRef = useRef([]);\n const currentCellIdRef = useRef(null);\n const currentCellIndexRef = useRef(null);\n const makeMessageId = (suffix) => {\n const base = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n return suffix ? `${base}-${suffix}` : base;\n };\n // Expose handleSendMessage via ref\n useImperativeHandle(ref, () => ({\n handleSendMessage: async () => {\n await handleSendMessage();\n },\n setInput: (value) => {\n setInput(value);\n },\n setLlmPrompt: (prompt) => {\n pendingLlmPromptRef.current = prompt;\n // Find textarea and set data attribute\n const textarea = messagesEndRef.current?.parentElement?.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.setAttribute('data-llm-prompt', prompt);\n }\n },\n setCurrentCellId: (cellId) => {\n console.log('[AgentPanel] setCurrentCellId called with:', cellId);\n currentCellIdRef.current = cellId;\n },\n setCurrentCellIndex: (cellIndex) => {\n console.log('[AgentPanel] setCurrentCellIndex called with:', cellIndex);\n currentCellIndexRef.current = cellIndex;\n },\n setInputMode: (mode) => {\n console.log('[AgentPanel] setInputMode called with:', mode);\n setInputMode(mode);\n }\n }));\n // Load config on mount\n useEffect(() => {\n loadConfig();\n }, []);\n // Reset expand state when debug status changes\n useEffect(() => {\n setIsDebugExpanded(false);\n }, [debugStatus]);\n // ═══════════════════════════════════════════════════════════════════════════\n // Console 출력 모니터링 - Python 에러 자동 감지\n // ═══════════════════════════════════════════════════════════════════════════\n useEffect(() => {\n if (!consoleTracker) {\n console.log('[AgentPanel] ConsoleTracker not available');\n return;\n }\n console.log('[AgentPanel] Setting up console output monitoring');\n // Console 출력에서 에러 감지하는 함수\n const checkConsoleForErrors = () => {\n const currentConsole = consoleTracker.currentWidget;\n if (!currentConsole) {\n return;\n }\n try {\n // Console의 출력 영역에서 텍스트 추출\n const consoleNode = currentConsole.node;\n if (!consoleNode)\n return;\n // JupyterLab Console의 출력은 .jp-OutputArea-output 클래스에 있음\n const outputAreas = consoleNode.querySelectorAll('.jp-OutputArea-output');\n if (outputAreas.length === 0)\n return;\n // 최근 출력만 확인 (마지막 몇 개)\n const recentOutputs = Array.from(outputAreas).slice(-5);\n let combinedOutput = '';\n recentOutputs.forEach((output) => {\n const text = output.textContent || '';\n combinedOutput += text + '\\n';\n });\n // Python 에러 감지\n if (detectPythonError(combinedOutput)) {\n console.log('[AgentPanel] Python error detected in console output');\n // 중복 알림 방지\n if (lastConsoleError !== combinedOutput) {\n setLastConsoleError(combinedOutput);\n setShowConsoleErrorNotification(true);\n // 5초 후 자동으로 알림 숨기기\n setTimeout(() => {\n setShowConsoleErrorNotification(false);\n }, 10000);\n }\n }\n }\n catch (e) {\n console.error('[AgentPanel] Error checking console output:', e);\n }\n };\n // MutationObserver로 Console 출력 변경 감지\n let observer = null;\n const setupObserver = () => {\n const currentConsole = consoleTracker.currentWidget;\n if (!currentConsole?.node)\n return;\n // 기존 observer 정리\n if (observer) {\n observer.disconnect();\n }\n observer = new MutationObserver((mutations) => {\n // 출력 영역에 변화가 있으면 에러 체크\n const hasOutputChange = mutations.some(mutation => mutation.type === 'childList' ||\n (mutation.type === 'characterData'));\n if (hasOutputChange) {\n // 약간의 딜레이 후 체크 (출력이 완전히 렌더링될 때까지)\n setTimeout(checkConsoleForErrors, 100);\n }\n });\n // Console 전체를 관찰\n observer.observe(currentConsole.node, {\n childList: true,\n subtree: true,\n characterData: true\n });\n console.log('[AgentPanel] MutationObserver set up for console');\n };\n // 현재 Console에 observer 설정\n setupObserver();\n // Console 변경 시 observer 재설정\n const onConsoleChanged = () => {\n console.log('[AgentPanel] Console changed, re-setting up observer');\n setupObserver();\n };\n consoleTracker.currentChanged?.connect(onConsoleChanged);\n // Cleanup\n return () => {\n if (observer) {\n observer.disconnect();\n }\n consoleTracker.currentChanged?.disconnect(onConsoleChanged);\n };\n }, [consoleTracker, lastConsoleError]);\n // Remove lastActiveNotebook state - just use currentWidget directly\n const loadConfig = () => {\n // Load from localStorage using ApiKeyManager\n const config = getLLMConfig();\n if (!config) {\n console.log('[AgentPanel] No config in localStorage, using default');\n const defaultConfig = getDefaultLLMConfig();\n setLlmConfig(defaultConfig);\n return;\n }\n setLlmConfig(config);\n // Log loaded model configuration\n console.log('=== HDSP Agent Model Configuration (localStorage) ===');\n console.log('Provider:', config.provider);\n if (config.provider === 'gemini') {\n console.log('Gemini Model:', config.gemini?.model || 'gemini-2.5-flash (default)');\n console.log('Gemini API Key:', config.gemini?.apiKey ? '✓ Configured' : '✗ Not configured');\n }\n else if (config.provider === 'vllm') {\n console.log('vLLM Model:', config.vllm?.model || 'default');\n console.log('vLLM Endpoint:', config.vllm?.endpoint || 'http://localhost:8000/v1');\n console.log('vLLM API Key:', config.vllm?.apiKey ? '✓ Configured' : '✗ Not configured');\n }\n else if (config.provider === 'openai') {\n console.log('OpenAI Model:', config.openai?.model || 'gpt-4');\n console.log('OpenAI API Key:', config.openai?.apiKey ? '✓ Configured' : '✗ Not configured');\n }\n console.log('====================================================');\n };\n // ═══════════════════════════════════════════════════════════════════════════\n // Python 파일 에러 수정 관련 함수들\n // ═══════════════════════════════════════════════════════════════════════════\n // JupyterLab Contents API를 통해 파일 내용 로드\n const loadFileContent = async (filePath) => {\n try {\n // PageConfig에서 base URL 가져오기\n const { PageConfig, URLExt } = await import('@jupyterlab/coreutils');\n const baseUrl = PageConfig.getBaseUrl();\n const apiUrl = URLExt.join(baseUrl, 'api/contents', filePath);\n console.log('[AgentPanel] Loading file:', filePath, 'from:', apiUrl);\n const response = await fetch(apiUrl, {\n method: 'GET',\n credentials: 'include',\n });\n if (!response.ok) {\n console.warn('[AgentPanel] Failed to load file:', filePath, response.status);\n return null;\n }\n const data = await response.json();\n return data.content;\n }\n catch (error) {\n console.error('[AgentPanel] Error loading file:', filePath, error);\n return null;\n }\n };\n // 파일에 수정된 코드 저장\n const saveFileContent = async (filePath, content) => {\n try {\n const { PageConfig, URLExt } = await import('@jupyterlab/coreutils');\n const baseUrl = PageConfig.getBaseUrl();\n const apiUrl = URLExt.join(baseUrl, 'api/contents', filePath);\n console.log('[AgentPanel] Saving file:', filePath);\n const response = await fetch(apiUrl, {\n method: 'PUT',\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: 'file',\n format: 'text',\n content: content,\n }),\n });\n if (!response.ok) {\n console.error('[AgentPanel] Failed to save file:', filePath, response.status);\n return false;\n }\n console.log('[AgentPanel] File saved successfully:', filePath);\n return true;\n }\n catch (error) {\n console.error('[AgentPanel] Error saving file:', filePath, error);\n return false;\n }\n };\n // Python 에러 메시지 처리 및 파일 수정 요청\n // Returns true if handled, false if should fall back to regular chat\n const handlePythonErrorFix = async (errorMessage) => {\n console.log('[AgentPanel] Handling Python error fix request');\n // 1. 에러가 발생한 파일 경로 추출\n const errorFilePath = extractErrorFilePath(errorMessage);\n const mainFilePath = extractMainFilePath(errorMessage);\n const allFilePaths = extractFilePathsFromError(errorMessage);\n console.log('[AgentPanel] Error file:', errorFilePath);\n console.log('[AgentPanel] Main file:', mainFilePath);\n console.log('[AgentPanel] All files:', allFilePaths);\n if (!errorFilePath && !mainFilePath) {\n // 파일 경로를 찾을 수 없으면 일반 채팅으로 처리\n console.log('[AgentPanel] No file path found, falling back to regular chat stream');\n return false;\n }\n // 2. 주요 파일 내용 로드\n const targetFile = errorFilePath || mainFilePath;\n const mainContent = await loadFileContent(targetFile);\n if (!mainContent) {\n console.warn('[AgentPanel] Could not load file content for:', targetFile);\n // 파일을 읽을 수 없으면 에러 메시지만으로 처리 시도\n const errorOnlyRequest = {\n action: 'fix',\n mainFile: { path: targetFile, content: '(파일 읽기 실패)' },\n errorOutput: errorMessage,\n };\n try {\n const result = await apiService.fileAction(errorOnlyRequest);\n handleFileFixResponse(result.response, result.fixedFiles);\n }\n catch (error) {\n console.error('[AgentPanel] File fix API error:', error);\n addErrorMessage('파일 수정 요청 실패: ' + error.message);\n }\n return true; // Handled (even if failed)\n }\n // 3. 관련 파일들 (imports) 로드\n const localImports = extractLocalImports(mainContent);\n const relatedFiles = [];\n // 에러 메시지에 언급된 다른 파일들도 로드\n for (const path of allFilePaths) {\n if (path !== targetFile) {\n const content = await loadFileContent(path);\n if (content) {\n relatedFiles.push({ path, content });\n }\n }\n }\n // 로컬 import 파일들도 로드\n const baseDir = targetFile.includes('/') ? targetFile.substring(0, targetFile.lastIndexOf('/')) : '';\n for (const moduleName of localImports) {\n const modulePath = baseDir ? `${baseDir}/${moduleName}.py` : `${moduleName}.py`;\n if (!allFilePaths.includes(modulePath) && !relatedFiles.some(f => f.path === modulePath)) {\n const content = await loadFileContent(modulePath);\n if (content) {\n relatedFiles.push({ path: modulePath, content });\n }\n }\n }\n console.log('[AgentPanel] Related files loaded:', relatedFiles.length);\n // 4. 파일 수정 API 호출\n const request = {\n action: 'fix',\n mainFile: { path: targetFile, content: mainContent },\n errorOutput: errorMessage,\n relatedFiles: relatedFiles.length > 0 ? relatedFiles : undefined,\n };\n try {\n setIsLoading(true);\n const result = await apiService.fileAction(request);\n handleFileFixResponse(result.response, result.fixedFiles);\n return true; // Successfully handled\n }\n catch (error) {\n console.error('[AgentPanel] File fix API error:', error);\n addErrorMessage('파일 수정 요청 실패: ' + error.message);\n return true; // Handled (even if failed)\n }\n finally {\n setIsLoading(false);\n }\n };\n // 파일 수정 응답 처리\n const handleFileFixResponse = (response, fixedFiles) => {\n console.log('[AgentPanel] File fix response received, fixed files:', fixedFiles.length);\n // Assistant 메시지로 응답 표시\n const assistantMessage = {\n id: makeMessageId('file-fix'),\n role: 'assistant',\n content: response,\n timestamp: Date.now(),\n metadata: { type: 'file_fix', fixedFiles },\n };\n setMessages(prev => [...prev, assistantMessage]);\n // 수정된 파일이 있으면 상태에 저장 (적용 버튼용)\n if (fixedFiles.length > 0) {\n setPendingFileFixes(fixedFiles);\n }\n };\n // 수정된 파일 적용\n const applyFileFix = async (fix) => {\n console.log('[AgentPanel] Applying fix to file:', fix.path);\n const success = await saveFileContent(fix.path, fix.content);\n if (success) {\n // 성공 메시지\n const successMessage = {\n id: makeMessageId('apply-success'),\n role: 'assistant',\n content: `✅ **${fix.path}** 파일이 수정되었습니다.\\n\\n파일 에디터에서 변경사항을 확인하세요.`,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, successMessage]);\n // 적용된 파일은 pending에서 제거\n setPendingFileFixes(prev => prev.filter(f => f.path !== fix.path));\n }\n else {\n addErrorMessage(`파일 저장 실패: ${fix.path}`);\n }\n };\n // 에러 메시지 추가 헬퍼\n const addErrorMessage = (message) => {\n const errorMessage = {\n id: makeMessageId('error'),\n role: 'assistant',\n content: `⚠️ ${message}`,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, errorMessage]);\n };\n const handleSaveConfig = (config) => {\n console.log('[AgentPanel] Saving config to localStorage');\n console.log('Provider:', config.provider);\n console.log('API Key configured:', hasValidApiKey(config) ? '✓ Yes' : '✗ No');\n // Save to localStorage using ApiKeyManager\n saveLLMConfig(config);\n // Update state\n setLlmConfig(config);\n console.log('[AgentPanel] Config saved successfully');\n };\n // File selection handlers (simplified - old agent mode removed)\n const handleFileSelect = async (index) => {\n console.log('[AgentPanel] File selected:', index);\n if (!fileSelectionMetadata || !pendingAgentRequest) {\n console.error('[AgentPanel] Missing file selection metadata or pending request');\n return;\n }\n const selectedFile = fileSelectionMetadata.options[index - 1];\n if (!selectedFile) {\n console.error('[AgentPanel] Invalid file selection index:', index);\n return;\n }\n console.log('[AgentPanel] Selected file:', selectedFile.path);\n setFileSelectionMetadata(null);\n setPendingAgentRequest(null);\n setIsAgentRunning(false);\n };\n const handleFileSelectCancel = () => {\n console.log('[AgentPanel] File selection cancelled');\n setFileSelectionMetadata(null);\n setPendingAgentRequest(null);\n setIsAgentRunning(false);\n };\n /**\n * Stop the currently running task.\n * This will:\n * 1. Abort any ongoing fetch requests\n * 2. Interrupt the Jupyter kernel if a cell is executing\n * 3. Reset all loading/running states\n * 4. Enable the input box for new commands\n */\n const stopCurrentTask = async () => {\n console.log('[AgentPanel] Stopping current task...');\n // 1. Cancel the server-side agent execution\n if (agentThreadId) {\n try {\n const result = await apiService.cancelAgent(agentThreadId);\n console.log('[AgentPanel] Server-side agent cancel result:', result);\n }\n catch (error) {\n console.warn('[AgentPanel] Error cancelling server-side agent:', error);\n }\n }\n // 2. Abort any ongoing fetch requests\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n console.log('[AgentPanel] Aborted fetch request');\n }\n // 3. Interrupt the Jupyter kernel if executing (try all possible kernels)\n try {\n // Try notebook kernel first\n const notebook = notebookTracker?.currentWidget;\n if (notebook?.sessionContext?.session?.kernel) {\n const kernel = notebook.sessionContext.session.kernel;\n console.log('[AgentPanel] Notebook kernel status:', kernel.status);\n // Always try to interrupt - don't check status first\n // Shell commands may show different status\n await kernel.interrupt();\n console.log('[AgentPanel] Interrupted notebook kernel');\n }\n // Also try console kernel if available\n if (consoleTracker?.currentWidget?.sessionContext?.session?.kernel) {\n const consoleKernel = consoleTracker.currentWidget.sessionContext.session.kernel;\n console.log('[AgentPanel] Console kernel status:', consoleKernel.status);\n await consoleKernel.interrupt();\n console.log('[AgentPanel] Interrupted console kernel');\n }\n }\n catch (error) {\n console.warn('[AgentPanel] Failed to interrupt kernel:', error);\n }\n // 4. Reset all states\n setIsLoading(false);\n setIsStreaming(false);\n setIsAgentRunning(false);\n setStreamingMessageId(null);\n setInterruptData(null);\n setIsRejectionMode(false);\n setPendingRejectionInterrupt(null);\n // Reset thread ID so new task starts fresh\n setAgentThreadId(null);\n // 6. Update current in_progress todo to show it was stopped\n setTodos(prev => prev.map(todo => todo.status === 'in_progress'\n ? { ...todo, content: `${todo.content} (중단됨)`, status: 'pending' }\n : todo));\n // Show notification\n showNotification('작업이 중단되었습니다.', 'info');\n console.log('[AgentPanel] Task stopped successfully');\n };\n // Auto-scroll state refs for smooth scrolling\n const scrollRafRef = useRef(null);\n const lastScrollTimeRef = useRef(0);\n const userScrolledUpRef = useRef(false);\n // Track user scroll to detect if they scrolled up\n useEffect(() => {\n const container = messagesContainerRef.current;\n if (!container)\n return;\n const handleScroll = () => {\n const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;\n userScrolledUpRef.current = !isNearBottom;\n };\n container.addEventListener('scroll', handleScroll, { passive: true });\n return () => container.removeEventListener('scroll', handleScroll);\n }, []);\n // Auto-scroll to bottom when messages change or streaming (only if near bottom)\n useEffect(() => {\n const container = messagesContainerRef.current;\n if (!container)\n return;\n // If user scrolled up, don't auto-scroll\n if (userScrolledUpRef.current && isStreaming) {\n return;\n }\n // Check if user is near bottom (within 100px threshold)\n const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;\n if (isNearBottom || !isStreaming) {\n if (isStreaming) {\n // 스트리밍 중: requestAnimationFrame으로 부드럽게 스크롤\n // 이전 RAF가 있으면 취소하고 새로 스케줄링\n if (scrollRafRef.current) {\n cancelAnimationFrame(scrollRafRef.current);\n }\n scrollRafRef.current = requestAnimationFrame(() => {\n const now = Date.now();\n // 최소 16ms 간격 유지 (60fps)\n if (now - lastScrollTimeRef.current >= 16) {\n container.scrollTop = container.scrollHeight;\n lastScrollTimeRef.current = now;\n }\n scrollRafRef.current = null;\n });\n }\n else if (isNearBottom) {\n // 일반 메시지 추가 시 부드러운 스크롤\n container.scrollTo({\n top: container.scrollHeight,\n behavior: 'smooth'\n });\n // 새 메시지 추가 시 userScrolledUp 리셋\n userScrolledUpRef.current = false;\n }\n }\n // Cleanup RAF on unmount\n return () => {\n if (scrollRafRef.current) {\n cancelAnimationFrame(scrollRafRef.current);\n }\n };\n }, [messages, isStreaming]);\n const handleNextItemSelection = async (nextText) => {\n const trimmed = nextText.trim();\n if (!trimmed)\n return;\n // Check if there's a pending interrupt (HITL approval waiting)\n const hasPendingInterrupt = interruptData !== null;\n // Check if all todos are completed\n const allTodosCompleted = todos.length === 0 || todos.every(t => t.status === 'completed');\n // Block if there's a pending interrupt OR if agent is running with incomplete todos\n // Allow if all todos are completed even if streaming is finishing up\n if (hasPendingInterrupt || (isAgentRunning && !allTodosCompleted)) {\n showNotification('다른 작업이 진행 중입니다. 완료 후 다시 시도해주세요.', 'warning');\n return;\n }\n await sendChatMessage({\n displayContent: trimmed,\n clearInput: true\n });\n };\n const sendChatMessage = async ({ displayContent, llmPrompt, textarea, clearInput = true }) => {\n const displayContentText = displayContent || (llmPrompt ? '셀 분석 요청' : '');\n if (!displayContentText && !llmPrompt)\n return;\n // Check if API key is configured before sending\n let currentConfig = llmConfig;\n if (!currentConfig) {\n // Config not loaded yet, try to load from localStorage\n currentConfig = getLLMConfig() || getDefaultLLMConfig();\n setLlmConfig(currentConfig);\n }\n // Check API key using ApiKeyManager\n const hasApiKey = hasValidApiKey(currentConfig);\n const autoApproveEnabled = getAutoApproveEnabled(currentConfig);\n if (!hasApiKey) {\n // Show error message and open settings\n const providerName = currentConfig?.provider || 'LLM';\n const errorMessage = {\n id: makeMessageId(),\n role: 'assistant',\n content: `API Key가 설정되지 않았습니다.\\n\\n${providerName === 'gemini' ? 'Gemini' : providerName === 'openai' ? 'OpenAI' : 'vLLM'} API Key를 먼저 설정해주세요.\\n\\n설정 버튼을 클릭하여 API Key를 입력하세요.`,\n timestamp: Date.now()\n };\n setMessages(prev => [...prev, errorMessage]);\n setShowSettings(true);\n return;\n }\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: displayContentText,\n timestamp: Date.now()\n };\n setMessages(prev => [...prev, userMessage]);\n if (clearInput && displayContentText) {\n setInput('');\n }\n setIsLoading(true);\n setIsStreaming(true);\n // Clear todos only when starting a new task (all completed or no todos)\n // Keep todos if there are pending/in_progress items (continuation of current task)\n const hasActiveTodos = todos.some(t => t.status === 'pending' || t.status === 'in_progress');\n if (!hasActiveTodos) {\n setTodos([]);\n }\n setDebugStatus(null);\n // Clear the data attribute and ref after using it\n if (textarea && llmPrompt) {\n textarea.removeAttribute('data-llm-prompt');\n pendingLlmPromptRef.current = null;\n }\n // Create assistant message ID for streaming updates\n const assistantMessageId = makeMessageId('assistant');\n let streamedContent = '';\n setStreamingMessageId(assistantMessageId);\n // Add empty assistant message that will be updated during streaming\n const initialAssistantMessage = {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now()\n };\n setMessages(prev => [...prev, initialAssistantMessage]);\n try {\n // Use LLM prompt if available, otherwise use the display content\n const messageToSend = llmPrompt || displayContentText;\n console.log('[AgentPanel] Sending message with mode:', inputMode, 'agentThreadId:', agentThreadId);\n // Chat 모드: 단순 Q&A (sendChatStream)\n // Agent V2 모드 또는 그 외: LangChain Deep Agent (sendAgentV2Stream)\n if (inputMode === 'chat') {\n // 단순 Chat 모드 - /chat/stream 사용\n // Create AbortController for chat streaming\n abortControllerRef.current = new AbortController();\n await apiService.sendChatStream({\n message: messageToSend,\n conversationId: conversationId || undefined,\n llmConfig: currentConfig\n }, \n // onChunk callback\n (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, \n // onMetadata callback\n (metadata) => {\n if (metadata.conversationId && !conversationId) {\n setConversationId(metadata.conversationId);\n }\n }, \n // AbortSignal for stopping the stream\n abortControllerRef.current.signal);\n }\n else {\n // Agent V2 모드 - /agent/langchain/stream 사용 (HITL, Todo, 도구 실행)\n await apiService.sendAgentV2Stream({\n message: messageToSend,\n conversationId: conversationId || undefined,\n llmConfig: currentConfig // Include API keys with request\n }, \n // onChunk callback - update message content incrementally\n (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, \n // onMetadata callback - update conversationId and metadata\n (metadata) => {\n if (metadata.conversationId && !conversationId) {\n setConversationId(metadata.conversationId);\n }\n if (metadata.provider || metadata.model) {\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? {\n ...msg,\n metadata: {\n ...msg.metadata,\n provider: metadata.provider,\n model: metadata.model\n }\n }\n : msg));\n }\n }, \n // onDebug callback - show debug status in gray\n (status) => {\n setDebugStatus(status);\n }, \n // onInterrupt callback - show approval dialog\n (interrupt) => {\n approvalPendingRef.current = true;\n // Capture threadId from interrupt for context persistence\n if (interrupt.threadId && !agentThreadId) {\n setAgentThreadId(interrupt.threadId);\n console.log('[AgentPanel] Captured agentThreadId from interrupt:', interrupt.threadId);\n }\n // Auto-approve search/file/resource tools - execute immediately without user interaction\n if (interrupt.action === 'search_notebook_cells_tool'\n || interrupt.action === 'check_resource_tool'\n || interrupt.action === 'read_file_tool'\n || interrupt.action === 'list_workspace_tool'\n || interrupt.action === 'search_files_tool') {\n void handleAutoToolInterrupt(interrupt);\n return;\n }\n // Handle ask_user_tool - show input UI instead of approval dialog\n if (interrupt.action === 'ask_user_tool') {\n console.log('[AgentPanel] ask_user_tool interrupt:', interrupt.args);\n setIsAskUserMode(true);\n setPendingAskUserInterrupt(interrupt);\n setIsLoading(false);\n setIsStreaming(false);\n // Display the question as an assistant message\n const question = interrupt.args?.question || '응답을 입력하세요...';\n const options = interrupt.args?.options;\n const inputType = interrupt.args?.input_type || 'text';\n let questionContent = `❓ **${question}**`;\n if (options && options.length > 0) {\n questionContent += '\\n\\n선택지:\\n' + options.map((opt, i) => `${i + 1}. ${opt}`).join('\\n');\n }\n if (inputType === 'file') {\n questionContent += '\\n\\n_(파일을 업로드하거나 파일 경로를 입력하세요)_';\n }\n const askUserMessage = {\n id: makeMessageId('ask_user'),\n role: 'assistant',\n content: questionContent,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, askUserMessage]);\n // Focus on input for user to provide response\n setTimeout(() => {\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.focus();\n textarea.placeholder = question;\n }\n }, 100);\n return;\n }\n if (autoApproveEnabled) {\n // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시\n upsertInterruptMessage(interrupt, true);\n // jupyter_cell_tool인 경우 먼저 셀 생성 후 승인 진행\n if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(interrupt.args.code);\n if (shouldQueue) {\n queueApprovalCell(interrupt.args.code);\n }\n }\n void resumeFromInterrupt(interrupt, 'approve');\n return;\n }\n if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(interrupt.args.code);\n if (isAutoApprovedCode(interrupt.args.code)) {\n if (shouldQueue) {\n queueApprovalCell(interrupt.args.code);\n }\n void resumeFromInterrupt(interrupt, 'approve');\n return;\n }\n if (shouldQueue) {\n queueApprovalCell(interrupt.args.code);\n }\n }\n setInterruptData(interrupt);\n upsertInterruptMessage(interrupt);\n setIsLoading(false);\n setIsStreaming(false);\n }, \n // onTodos callback - update todo list UI\n (newTodos) => {\n setTodos(newTodos);\n }, \n // onDebugClear callback - clear debug status\n () => {\n setDebugStatus(null);\n }, \n // onToolCall callback - add cells to notebook\n handleToolCall, \n // onComplete callback - capture thread_id for context persistence\n (data) => {\n if (data.threadId) {\n setAgentThreadId(data.threadId);\n console.log('[AgentPanel] Captured agentThreadId for context persistence:', data.threadId);\n }\n }, \n // threadId - pass existing thread_id to continue context\n agentThreadId || undefined);\n }\n }\n catch (error) {\n // Handle abort error gracefully (user clicked stop)\n if (error instanceof Error && error.name === 'AbortError') {\n console.log('[AgentPanel] Chat stream stopped by user');\n setDebugStatus(null);\n // Keep the streamed content as-is, just add a stopped indicator\n if (streamedContent) {\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent + '\\n\\n*(응답 중단됨)*' }\n : msg));\n }\n }\n else {\n const message = error instanceof Error ? error.message : 'Failed to send message';\n setDebugStatus(`오류: ${message}`);\n // Update the assistant message with error\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? {\n ...msg,\n content: streamedContent + `\\n\\nError: ${message}`\n }\n : msg));\n }\n }\n finally {\n setIsLoading(false);\n setIsStreaming(false);\n setStreamingMessageId(null);\n abortControllerRef.current = null;\n // Keep completed todos visible after the run\n }\n };\n // Extract and store code blocks from messages, setup button listeners\n useEffect(() => {\n // Use a small delay to ensure DOM is updated after message rendering\n const timeoutId = setTimeout(() => {\n // Find messages container - try multiple selectors\n const messagesContainer = document.querySelector('.jp-agent-messages') ||\n messagesEndRef.current?.parentElement ||\n document.querySelector('[class*=\"jp-agent-messages\"]');\n if (!messagesContainer) {\n console.log('[AgentPanel] Messages container not found');\n return;\n }\n // Extract code blocks from all assistant messages\n const codeBlocks = [];\n const containers = messagesContainer.querySelectorAll('.code-block-container');\n console.log(`[AgentPanel] Found ${containers.length} code block containers`);\n containers.forEach(container => {\n const blockId = container.getAttribute('data-block-id');\n if (!blockId) {\n console.warn('[AgentPanel] Code block container missing data-block-id');\n return;\n }\n const codeElement = container.querySelector(`#${blockId}`);\n if (!codeElement) {\n console.warn(`[AgentPanel] Code element #${blockId} not found`);\n return;\n }\n const codeText = codeElement.textContent || '';\n const langElement = container.querySelector('.code-block-language');\n const language = langElement?.textContent?.toLowerCase() || 'python';\n codeBlocks.push({\n id: blockId,\n code: codeText,\n language: language\n });\n });\n // Update code blocks ref\n allCodeBlocksRef.current = codeBlocks;\n console.log(`[AgentPanel] Stored ${codeBlocks.length} code blocks`, codeBlocks.map(b => b.id));\n // Use event delegation - attach single listener to container\n const handleContainerClick = async (e) => {\n const target = e.target;\n const nextItem = target.closest('.jp-next-items-item');\n if (nextItem) {\n e.stopPropagation();\n e.preventDefault();\n const subject = nextItem.querySelector('.jp-next-items-subject')?.textContent?.trim() || '';\n const description = nextItem.querySelector('.jp-next-items-description')?.textContent?.trim() || '';\n const nextText = subject && description\n ? `${subject}\\n\\n${description}`\n : (subject || description);\n if (nextText) {\n void handleNextItemSelection(nextText);\n }\n return;\n }\n // Handle expand/collapse button\n if (target.classList.contains('code-block-toggle') || target.closest('.code-block-toggle')) {\n const button = target.classList.contains('code-block-toggle')\n ? target\n : target.closest('.code-block-toggle');\n e.stopPropagation();\n e.preventDefault();\n const container = button.closest('.code-block-container');\n if (!container)\n return;\n const isExpanded = container.classList.toggle('is-expanded');\n button.setAttribute('aria-expanded', String(isExpanded));\n button.setAttribute('title', isExpanded ? '접기' : '전체 보기');\n button.setAttribute('aria-label', isExpanded ? '접기' : '전체 보기');\n const icon = button.querySelector('.code-block-toggle-icon');\n if (icon) {\n icon.textContent = isExpanded ? '▴' : '▾';\n }\n return;\n }\n // Handle copy button\n if (target.classList.contains('code-block-copy') || target.closest('.code-block-copy')) {\n const button = target.classList.contains('code-block-copy')\n ? target\n : target.closest('.code-block-copy');\n e.stopPropagation();\n e.preventDefault();\n const blockId = button.getAttribute('data-block-id');\n if (!blockId)\n return;\n const block = allCodeBlocksRef.current.find(b => b.id === blockId);\n if (!block)\n return;\n try {\n await navigator.clipboard.writeText(block.code);\n const originalText = button.textContent;\n button.textContent = '복사됨!';\n setTimeout(() => {\n button.textContent = originalText || '복사';\n }, 2000);\n }\n catch (error) {\n console.error('Failed to copy code:', error);\n showNotification('복사에 실패했습니다.', 'error');\n }\n return;\n }\n // Handle apply button\n if (target.classList.contains('code-block-apply') || target.closest('.code-block-apply')) {\n const button = target.classList.contains('code-block-apply')\n ? target\n : target.closest('.code-block-apply');\n e.stopPropagation();\n e.preventDefault();\n const blockId = button.getAttribute('data-block-id');\n console.log(`[AgentPanel] Apply button clicked via delegation, blockId: ${blockId}`);\n if (!blockId) {\n showNotification('코드 블록 ID를 찾을 수 없습니다.', 'error');\n return;\n }\n const block = allCodeBlocksRef.current.find(b => b.id === blockId);\n if (!block) {\n console.error('[AgentPanel] Block not found!', {\n clickedId: blockId,\n availableIds: allCodeBlocksRef.current.map(b => b.id),\n blocksCount: allCodeBlocksRef.current.length\n });\n showNotification('코드를 찾을 수 없습니다.', 'error');\n return;\n }\n console.log(`[AgentPanel] Applying code to cell via delegation, code length: ${block.code.length}`);\n await applyCodeToCell(block.code, blockId, button);\n return;\n }\n };\n // Attach single event listener to container\n messagesContainer.addEventListener('click', handleContainerClick);\n // Cleanup - store handler reference for cleanup\n messagesContainer._agentPanelClickHandler = handleContainerClick;\n }, 100); // Small delay to ensure DOM is ready\n // Cleanup\n return () => {\n clearTimeout(timeoutId);\n // Remove event listener on cleanup\n const messagesContainer = document.querySelector('.jp-agent-messages') ||\n messagesEndRef.current?.parentElement ||\n document.querySelector('[class*=\"jp-agent-messages\"]');\n if (messagesContainer && messagesContainer._agentPanelClickHandler) {\n messagesContainer.removeEventListener('click', messagesContainer._agentPanelClickHandler);\n delete messagesContainer._agentPanelClickHandler;\n }\n };\n }, [messages]);\n // Helper: Get notification background color\n const getNotificationColor = (type) => {\n switch (type) {\n case 'error': return '#f56565';\n case 'warning': return '#ed8936';\n default: return '#4299e1';\n }\n };\n // Helper: Create and show notification element\n const createNotificationElement = (message, backgroundColor) => {\n const notification = document.createElement('div');\n notification.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 16px;\n border-radius: 6px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n z-index: 10001;\n opacity: 0;\n transition: opacity 0.3s ease;\n max-width: 300px;\n word-wrap: break-word;\n background: ${backgroundColor};\n `;\n notification.textContent = message;\n return notification;\n };\n // Helper: Animate notification in and out\n const animateNotification = (notification) => {\n setTimeout(() => notification.style.opacity = '1', 10);\n setTimeout(() => {\n notification.style.opacity = '0';\n setTimeout(() => {\n if (notification.parentNode) {\n notification.parentNode.removeChild(notification);\n }\n }, 300);\n }, 3000);\n };\n // Show notification\n const showNotification = (message, type = 'info') => {\n const notification = createNotificationElement(message, getNotificationColor(type));\n document.body.appendChild(notification);\n animateNotification(notification);\n };\n // Show tooltip on a specific cell\n const showCellTooltip = (cellElement, message) => {\n // Remove any existing tooltip\n const existingTooltip = document.querySelector('.jp-agent-cell-tooltip');\n if (existingTooltip) {\n existingTooltip.remove();\n }\n const tooltip = document.createElement('div');\n tooltip.className = 'jp-agent-cell-tooltip';\n tooltip.textContent = message;\n tooltip.style.cssText = `\n position: absolute;\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 8px 16px;\n border-radius: 0 0 8px 8px;\n font-size: 13px;\n font-weight: 500;\n box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n z-index: 1000;\n opacity: 0;\n transition: opacity 0.3s ease;\n pointer-events: none;\n white-space: nowrap;\n `;\n // Make cell position relative if not already\n const originalPosition = cellElement.style.position;\n if (!originalPosition || originalPosition === 'static') {\n cellElement.style.position = 'relative';\n }\n cellElement.appendChild(tooltip);\n // Fade in\n requestAnimationFrame(() => {\n tooltip.style.opacity = '1';\n });\n // Fade out and remove after 2 seconds\n setTimeout(() => {\n tooltip.style.opacity = '0';\n setTimeout(() => {\n tooltip.remove();\n // Restore original position\n if (!originalPosition || originalPosition === 'static') {\n cellElement.style.position = originalPosition || '';\n }\n }, 300);\n }, 2000);\n };\n // Apply code to Jupyter cell\n const applyCodeToCell = async (code, blockId, button) => {\n console.log('[AgentPanel] applyCodeToCell called', { codeLength: code.length, blockId });\n const originalText = button.textContent;\n try {\n button.disabled = true;\n button.textContent = '적용 중...';\n const app = window.jupyterapp;\n if (!app) {\n console.error('[AgentPanel] jupyterapp not found in window');\n showNotification('JupyterLab을 찾을 수 없습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n console.log('[AgentPanel] Found jupyterapp');\n // Try to get notebook tracker from app\n // The notebookTracker is passed to cellButtonsPlugin, so we need to access it differently\n // Use shell to find current notebook widget\n const shell = app.shell;\n const currentWidget = shell.currentWidget;\n // Check if current widget is a notebook\n if (currentWidget && 'content' in currentWidget) {\n const notebook = currentWidget.content;\n // Check if it's a notebook (has model and cells)\n if (notebook && 'model' in notebook && notebook.model && 'cells' in notebook.model) {\n await applyCodeToNotebookCell(code, notebook, blockId, button, originalText);\n return;\n }\n }\n // Fallback: show cell selector dialog\n showCellSelectorDialog(code, button, originalText);\n }\n catch (error) {\n console.error('Failed to apply code to cell:', error);\n showNotification('코드 적용에 실패했습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n };\n // Helper: Try different methods to set cell source\n const setCellSource = (cellModel, code) => {\n if (cellModel.sharedModel && typeof cellModel.sharedModel.setSource === 'function') {\n cellModel.sharedModel.setSource(code);\n }\n else if (cellModel.setSource && typeof cellModel.setSource === 'function') {\n cellModel.setSource(code);\n }\n else if (cellModel.value && typeof cellModel.value.setText === 'function') {\n cellModel.value.setText(code);\n }\n else if (cellModel.value && cellModel.value.text !== undefined) {\n cellModel.value.text = code;\n }\n else {\n throw new Error('Unable to set cell source - no compatible method found');\n }\n };\n // Helper: Reset button state\n const resetButtonState = (button, originalText) => {\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n };\n // Apply code to a specific notebook cell\n const applyCodeToNotebookCell = async (code, notebook, blockId, button, originalText) => {\n try {\n // Get widgets from notebook\n const cells = notebook.widgets || [];\n const modelCellsLength = notebook.model?.cells?.length || 0;\n console.log('[AgentPanel] applyCodeToNotebookCell called', {\n currentCellIndex: currentCellIndexRef.current,\n currentCellId: currentCellIdRef.current,\n blockId,\n widgetsLength: cells.length,\n modelCellsLength: modelCellsLength\n });\n // Try cell index first (most reliable)\n if (currentCellIndexRef.current !== null && currentCellIndexRef.current !== undefined) {\n console.log('[AgentPanel] Using cell index:', currentCellIndexRef.current);\n // Safety check: if widgets array is empty but model has cells, there might be a rendering issue\n if (cells.length === 0 && modelCellsLength > 0) {\n console.warn('[AgentPanel] Widgets not rendered yet, model has', modelCellsLength, 'cells');\n // Fall through to show selector dialog\n }\n else if (currentCellIndexRef.current >= 0 && currentCellIndexRef.current < cells.length) {\n const cell = cells[currentCellIndexRef.current];\n const cellModel = cell.model || cell;\n console.log('[AgentPanel] Found cell at index, applying code...');\n try {\n setCellSource(cellModel, code);\n console.log('[AgentPanel] Code applied successfully!');\n // Show tooltip on the target cell\n if (cell.node) {\n showCellTooltip(cell.node, '✓ 코드가 적용되었습니다');\n }\n else {\n showNotification('코드가 셀에 적용되었습니다!', 'info');\n }\n resetButtonState(button, originalText);\n // Clear the cell index after successful application\n currentCellIndexRef.current = null;\n currentCellIdRef.current = null;\n return;\n }\n catch (updateError) {\n console.error('Failed to update cell content:', updateError);\n showNotification('셀 내용 업데이트 실패: ' + updateError.message, 'error');\n resetButtonState(button, originalText);\n return;\n }\n }\n else {\n console.error('[AgentPanel] Cell index out of bounds:', {\n currentIndex: currentCellIndexRef.current,\n widgetsLength: cells.length,\n modelCellsLength: modelCellsLength\n });\n // Don't show error, fall through to selector dialog\n currentCellIndexRef.current = null;\n currentCellIdRef.current = null;\n }\n }\n // Fallback: show selector dialog\n console.log('[AgentPanel] Showing cell selector dialog');\n showCellSelectorDialog(code, button, originalText);\n }\n catch (error) {\n console.error('Failed to apply code:', error);\n showNotification('코드 적용에 실패했습니다.', 'error');\n resetButtonState(button, originalText);\n }\n };\n // Show cell selector dialog (similar to chrome_agent)\n const showCellSelectorDialog = (code, button, originalText) => {\n try {\n const app = window.jupyterapp;\n if (!app) {\n showNotification('JupyterLab을 찾을 수 없습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n // Find current notebook from shell\n const shell = app.shell;\n const currentWidget = shell.currentWidget;\n if (!currentWidget || !('content' in currentWidget)) {\n showNotification('활성 노트북을 찾을 수 없습니다.', 'warning');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n const notebook = currentWidget.content;\n if (!notebook || !('model' in notebook) || !notebook.model || !('cells' in notebook.model)) {\n showNotification('활성 노트북을 찾을 수 없습니다.', 'warning');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n const cells = notebook.widgets || [];\n const codeCells = [];\n // Collect code cells\n cells.forEach((cell, index) => {\n const cellModel = cell.model || cell;\n if (cellModel.type === 'code') {\n const cellId = cellModel.metadata?.get?.('jupyterAgentCellId') ||\n cellModel.id ||\n `cell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n // Ensure cell has ID\n try {\n if (!cellModel.metadata?.get?.('jupyterAgentCellId')) {\n if (cellModel.metadata?.set) {\n cellModel.metadata.set('jupyterAgentCellId', cellId);\n }\n }\n }\n catch (e) {\n // Metadata might not be accessible, continue anyway\n }\n const content = (cellModel.sharedModel?.getSource?.() ||\n cellModel.value?.text ||\n '').toString();\n const preview = content.substring(0, 100).replace(/\\n/g, ' ');\n codeCells.push({\n cell,\n index,\n id: cellId,\n preview\n });\n }\n });\n if (codeCells.length === 0) {\n showNotification('코드 셀을 찾을 수 없습니다.', 'warning');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n return;\n }\n // Remove existing dialog\n const existingDialog = document.querySelector('.jp-agent-cell-selector-dialog');\n if (existingDialog) {\n existingDialog.remove();\n }\n // Create dialog overlay\n const dialogOverlay = document.createElement('div');\n dialogOverlay.className = 'jp-agent-cell-selector-dialog';\n dialogOverlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `;\n // Create dialog container\n const dialogContainer = document.createElement('div');\n dialogContainer.style.cssText = `\n background: #fafafa;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 24px;\n max-width: 500px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `;\n // Create cell list HTML\n const cellListHTML = codeCells.map(({ index, id, preview }) => {\n return `\n <div class=\"jp-agent-cell-selector-item\" data-cell-id=\"${id}\" style=\"\n padding: 12px;\n margin-bottom: 8px;\n background: white;\n border: 2px solid #e0e0e0;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.2s ease;\n \">\n <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 4px;\">\n <span style=\"\n background: #667eea;\n color: white;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n \">셀 ${index + 1}</span>\n </div>\n <div style=\"font-size: 12px; color: #757575; font-family: 'Menlo', monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n ${escapeHtml(preview)}${preview.length >= 100 ? '...' : ''}\n </div>\n </div>\n `;\n }).join('');\n // Dialog content\n dialogContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 8px 0; color: #424242; font-size: 16px; font-weight: 500;\">\n 코드를 적용할 셀 선택\n </h3>\n <p style=\"margin: 0; color: #757575; font-size: 13px;\">\n AI가 생성한 코드를 적용할 셀을 선택하세요\n </p>\n </div>\n <div class=\"jp-agent-cell-list\" style=\"margin-bottom: 20px;\">\n ${cellListHTML}\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-cell-selector-cancel-btn\" style=\"\n background: transparent;\n color: #616161;\n border: 1px solid #d1d5db;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">취소</button>\n </div>\n `;\n dialogOverlay.appendChild(dialogContainer);\n document.body.appendChild(dialogOverlay);\n // Cell item click handlers\n const cellItems = dialogContainer.querySelectorAll('.jp-agent-cell-selector-item');\n cellItems.forEach(item => {\n item.addEventListener('click', () => {\n const cellId = item.getAttribute('data-cell-id');\n if (!cellId)\n return;\n // Find the cell\n const cellInfo = codeCells.find(c => c.id === cellId);\n if (!cellInfo)\n return;\n const cellModel = cellInfo.cell.model || cellInfo.cell;\n // Apply code to cell\n try {\n setCellSource(cellModel, code);\n dialogOverlay.remove();\n // Show tooltip on the target cell\n if (cellInfo.cell.node) {\n showCellTooltip(cellInfo.cell.node, '✓ 코드가 적용되었습니다');\n }\n else {\n showNotification('코드가 셀에 적용되었습니다!', 'info');\n }\n resetButtonState(button, originalText);\n }\n catch (error) {\n console.error('Failed to apply code to cell:', error);\n showNotification('코드 적용에 실패했습니다.', 'error');\n resetButtonState(button, originalText);\n }\n });\n // Hover effects\n item.addEventListener('mouseenter', () => {\n item.style.borderColor = '#667eea';\n item.style.background = '#f8f9ff';\n });\n item.addEventListener('mouseleave', () => {\n item.style.borderColor = '#e0e0e0';\n item.style.background = 'white';\n });\n });\n // Cancel button\n const cancelBtn = dialogContainer.querySelector('.jp-agent-cell-selector-cancel-btn');\n if (cancelBtn) {\n cancelBtn.addEventListener('click', () => {\n dialogOverlay.remove();\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n });\n cancelBtn.addEventListener('mouseenter', () => {\n cancelBtn.style.background = '#f5f5f5';\n cancelBtn.style.borderColor = '#9ca3af';\n });\n cancelBtn.addEventListener('mouseleave', () => {\n cancelBtn.style.background = 'transparent';\n cancelBtn.style.borderColor = '#d1d5db';\n });\n }\n // Close on overlay click\n dialogOverlay.addEventListener('click', (e) => {\n if (e.target === dialogOverlay) {\n dialogOverlay.remove();\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n });\n // ESC key to close\n const handleEsc = (e) => {\n if (e.key === 'Escape') {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEsc);\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n };\n document.addEventListener('keydown', handleEsc);\n }\n catch (error) {\n console.error('Failed to show cell selector:', error);\n showNotification('셀 선택 다이얼로그 표시에 실패했습니다.', 'error');\n button.disabled = false;\n button.textContent = originalText || '셀에 적용';\n }\n };\n // Helper function to escape HTML\n const escapeHtml = (text) => {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n };\n const isAutoApprovedCode = (code) => {\n const lines = code\n .split('\\n')\n .map(line => line.trim())\n .filter(line => line.length > 0 && !line.startsWith('#'));\n if (lines.length === 0) {\n return true;\n }\n if (lines.some(line => (line.startsWith('!')\n || line.startsWith('%%bash')\n || line.startsWith('%%sh')\n || line.startsWith('%%shell')))) {\n return false;\n }\n const disallowedPatterns = [\n /(^|[^=!<>])=([^=]|$)/,\n /\\.read_[a-zA-Z0-9_]*\\s*\\(/,\n /\\bread_[a-zA-Z0-9_]*\\s*\\(/,\n /\\.to_[a-zA-Z0-9_]*\\s*\\(/,\n ];\n if (lines.some(line => disallowedPatterns.some(pattern => pattern.test(line)))) {\n return false;\n }\n const allowedPatterns = [\n /^print\\(.+\\)$/,\n /^display\\(.+\\)$/,\n /^df\\.(head|info|describe)\\s*\\(.*\\)$/,\n /^display\\(df\\.(head|info|describe)\\s*\\(.*\\)\\)$/,\n /^df\\.(tail|sample)\\s*\\(.*\\)$/,\n /^display\\(df\\.(tail|sample)\\s*\\(.*\\)\\)$/,\n /^df\\.(shape|columns|dtypes)$/,\n /^import\\s+pandas\\s+as\\s+pd$/,\n ];\n return lines.every(line => allowedPatterns.some(pattern => pattern.test(line)));\n };\n const userRequestedNotebookExecution = () => {\n const lastUserMessage = [...messages]\n .reverse()\n .find((msg) => isChatMessage(msg) && msg.role === 'user');\n const content = lastUserMessage?.content ?? '';\n return /노트북|셀|cell|notebook|jupyter/i.test(content);\n };\n const isShellCell = (code) => {\n const lines = code.split('\\n');\n const firstLine = lines.find(line => line.trim().length > 0)?.trim() || '';\n if (firstLine.startsWith('%%bash')\n || firstLine.startsWith('%%sh')\n || firstLine.startsWith('%%shell')) {\n return true;\n }\n return lines.some(line => line.trim().startsWith('!'));\n };\n const extractShellCommand = (code) => {\n const lines = code.split('\\n');\n const firstNonEmptyIndex = lines.findIndex(line => line.trim().length > 0);\n if (firstNonEmptyIndex === -1) {\n return '';\n }\n const firstLine = lines[firstNonEmptyIndex].trim();\n if (firstLine.startsWith('%%bash')\n || firstLine.startsWith('%%sh')\n || firstLine.startsWith('%%shell')) {\n const script = lines.slice(firstNonEmptyIndex + 1).join('\\n').trim();\n if (!script) {\n return '';\n }\n const escaped = script\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\\r?\\n/g, '\\\\n');\n return `bash -lc $'${escaped}'`;\n }\n const shellLines = lines\n .map(line => line.trim())\n .filter(line => line.startsWith('!'))\n .map(line => line.replace(/^!+/, '').trim())\n .filter(Boolean);\n return shellLines.join('\\n');\n };\n const buildPythonCommand = (code) => (`python3 -c ${JSON.stringify(code)}`);\n const shouldExecuteInNotebook = (code) => {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n return false;\n }\n if (isShellCell(code) && !userRequestedNotebookExecution()) {\n return false;\n }\n return true;\n };\n const truncateOutputLines = (output, maxLines = 2) => {\n const lines = output.split(/\\r?\\n/).filter(line => line.length > 0);\n const text = lines.slice(0, maxLines).join('\\n');\n return { text, truncated: lines.length > maxLines };\n };\n const createCommandOutputMessage = (command) => {\n const messageId = makeMessageId('command-output');\n const outputMessage = {\n id: messageId,\n role: 'system',\n content: `${command}\\n`,\n timestamp: Date.now(),\n metadata: {\n kind: 'shell-output',\n command\n }\n };\n setMessages(prev => [...prev, outputMessage]);\n return messageId;\n };\n const appendCommandOutputMessage = (messageId, text, stream) => {\n if (!text)\n return;\n const prefix = stream === 'stderr' ? '[stderr] ' : '';\n setMessages(prev => prev.map(msg => {\n if (msg.id !== messageId || !('role' in msg)) {\n return msg;\n }\n const chatMsg = msg;\n return {\n ...chatMsg,\n content: `${chatMsg.content}${prefix}${text}`\n };\n }));\n };\n const executeSubprocessCommand = async (command, timeout, onOutput, stdin) => {\n try {\n const result = await apiService.executeCommandStream(command, { timeout, onOutput, stdin });\n const stdout = typeof result.stdout === 'string' ? result.stdout : '';\n const stderr = typeof result.stderr === 'string' ? result.stderr : '';\n const combined = [stdout, stderr].filter(Boolean).join('\\n');\n const summary = truncateOutputLines(combined, 2);\n const truncated = summary.truncated || Boolean(result.truncated);\n const output = summary.text || '(no output)';\n if (result.success) {\n return {\n success: true,\n output,\n returncode: result.returncode ?? null,\n command,\n truncated\n };\n }\n const errorText = summary.text || result.error || stderr || 'Command failed';\n return {\n success: false,\n output,\n error: errorText,\n returncode: result.returncode ?? null,\n command,\n truncated\n };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Command execution failed';\n if (onOutput) {\n onOutput({ stream: 'stderr', text: `${message}\\n` });\n }\n const summary = truncateOutputLines(message, 2);\n return {\n success: false,\n output: '',\n error: summary.text || 'Command execution failed',\n returncode: null,\n command,\n truncated: summary.truncated\n };\n }\n };\n const executeCodeViaSubprocess = async (code, timeout) => {\n const isShell = isShellCell(code);\n const command = isShell ? extractShellCommand(code) : buildPythonCommand(code);\n if (!command) {\n return {\n success: false,\n output: '',\n error: isShell ? 'Shell command is empty' : 'Python command is empty',\n returncode: null,\n command,\n truncated: false,\n execution_method: 'subprocess'\n };\n }\n const result = await executeSubprocessCommand(command, timeout);\n return {\n ...result,\n execution_method: 'subprocess'\n };\n };\n const upsertInterruptMessage = (interrupt, autoApproved) => {\n const interruptMessageId = interruptMessageIdRef.current || makeMessageId('interrupt');\n interruptMessageIdRef.current = interruptMessageId;\n const interruptMessage = {\n id: interruptMessageId,\n role: 'system',\n content: interrupt.description || '코드 실행 승인이 필요합니다.',\n timestamp: Date.now(),\n metadata: {\n interrupt: {\n ...interrupt,\n resolved: autoApproved || false,\n decision: autoApproved ? 'approve' : undefined,\n autoApproved: autoApproved || false\n }\n }\n };\n setMessages(prev => {\n const hasExisting = prev.some(msg => msg.id === interruptMessageId);\n if (hasExisting) {\n return prev.map(msg => msg.id === interruptMessageId ? interruptMessage : msg);\n }\n return [...prev, interruptMessage];\n });\n };\n const clearInterruptMessage = (decision) => {\n if (!interruptMessageIdRef.current)\n return;\n const messageId = interruptMessageIdRef.current;\n interruptMessageIdRef.current = null;\n setMessages(prev => prev.map(msg => {\n // Only IChatMessage has metadata property (check via 'role' property)\n if (msg.id === messageId && 'role' in msg) {\n const chatMsg = msg;\n return {\n ...chatMsg,\n metadata: {\n ...chatMsg.metadata,\n interrupt: {\n ...(chatMsg.metadata?.interrupt || {}),\n resolved: true,\n decision: decision || 'approve' // Track approve/reject decision\n }\n }\n };\n }\n return msg;\n }));\n };\n const validateRelativePath = (path) => {\n const trimmed = path.trim();\n if (trimmed.startsWith('/') || trimmed.startsWith('\\\\') || /^[A-Za-z]:/.test(trimmed)) {\n return { valid: false, error: 'Absolute paths are not allowed' };\n }\n if (trimmed.includes('..')) {\n return { valid: false, error: 'Path traversal (..) is not allowed' };\n }\n return { valid: true };\n };\n const normalizeContentsPath = (path) => {\n const trimmed = path.trim();\n if (!trimmed || trimmed === '.' || trimmed === './') {\n return '';\n }\n return trimmed\n .replace(/^\\.\\/+/, '')\n .replace(/^\\/+/, '')\n .replace(/\\/+$/, '');\n };\n const globToRegex = (pattern) => {\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = `^${escaped.replace(/\\*/g, '.*').replace(/\\?/g, '.')}$`;\n return new RegExp(regex);\n };\n const fetchContentsModel = async (path, options) => {\n try {\n const { PageConfig, URLExt } = await import('@jupyterlab/coreutils');\n const baseUrl = PageConfig.getBaseUrl();\n const normalizedPath = normalizeContentsPath(path);\n const apiUrl = URLExt.join(baseUrl, 'api/contents', normalizedPath);\n const query = new URLSearchParams();\n if (options?.content !== undefined) {\n query.set('content', options.content ? '1' : '0');\n }\n if (options?.format) {\n query.set('format', options.format);\n }\n const url = query.toString() ? `${apiUrl}?${query.toString()}` : apiUrl;\n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n if (!response.ok) {\n return { success: false, error: `Failed to load contents: ${response.status}` };\n }\n const data = await response.json();\n return { success: true, data };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to load contents';\n return { success: false, error: message };\n }\n };\n const executeListFilesTool = async (params) => {\n const pathCheck = validateRelativePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n // Directories to skip during recursive traversal (performance optimization)\n const IGNORED_DIRS = new Set([\n 'node_modules',\n '.git',\n '__pycache__',\n '.venv',\n 'venv',\n '.tox',\n '.pytest_cache',\n '.mypy_cache',\n '.ruff_cache',\n 'dist',\n 'build',\n '.next',\n '.nuxt',\n 'coverage',\n '.coverage',\n '.ipynb_checkpoints',\n ]);\n const pattern = (params.pattern || '*').trim() || '*';\n const recursive = params.recursive ?? false;\n const matcher = globToRegex(pattern);\n const maxEntries = 500;\n const files = [];\n const pendingDirs = [normalizeContentsPath(params.path)];\n const visited = new Set();\n while (pendingDirs.length > 0 && files.length < maxEntries) {\n const dirPath = pendingDirs.shift() ?? '';\n if (visited.has(dirPath)) {\n continue;\n }\n visited.add(dirPath);\n const contentsResult = await fetchContentsModel(dirPath, { content: true });\n if (!contentsResult.success) {\n // Skip directories that return 404 (might not exist or permission denied)\n // Only fail on the root path\n if (dirPath === '' || dirPath === '.') {\n return { success: false, error: contentsResult.error };\n }\n continue; // Skip this directory and continue with others\n }\n const model = contentsResult.data;\n if (!model || model.type !== 'directory' || !Array.isArray(model.content)) {\n const displayPath = dirPath || '.';\n return { success: false, error: `Not a directory: ${displayPath}` };\n }\n for (const entry of model.content) {\n if (!entry) {\n continue;\n }\n const name = entry.name || entry.path?.split('/').pop() || '';\n const entryPath = entry.path || name;\n const isDir = entry.type === 'directory';\n // Skip ignored directories\n if (isDir && IGNORED_DIRS.has(name)) {\n continue;\n }\n if (matcher.test(name)) {\n files.push({\n path: entryPath,\n isDir,\n size: isDir ? 0 : (entry.size ?? 0),\n });\n }\n if (recursive && isDir && entryPath) {\n pendingDirs.push(entryPath);\n }\n if (files.length >= maxEntries) {\n break;\n }\n }\n }\n const formatted = files.map((file) => {\n const icon = file.isDir ? '[DIR]' : '[FILE]';\n const sizeInfo = file.isDir ? '' : ` (${file.size} bytes)`;\n return `${icon} ${file.path}${file.isDir ? '/' : sizeInfo}`;\n }).join('\\n');\n return {\n success: true,\n output: formatted || '(empty directory)',\n metadata: { count: files.length, files }\n };\n };\n const executeReadFileTool = async (params) => {\n if (!params.path) {\n return { success: false, error: 'Path is required' };\n }\n const pathCheck = validateRelativePath(params.path);\n if (!pathCheck.valid) {\n return { success: false, error: pathCheck.error };\n }\n // Support both old (maxLines) and new (offset/limit) parameters\n const offset = typeof params.offset === 'number' ? Math.max(0, params.offset) : 0;\n const limit = typeof params.limit === 'number'\n ? params.limit\n : (typeof params.maxLines === 'number' ? params.maxLines : 500);\n const safeLimit = Math.max(0, limit);\n const contentsResult = await fetchContentsModel(params.path, { content: true, format: 'text' });\n if (!contentsResult.success) {\n return { success: false, error: contentsResult.error };\n }\n const model = contentsResult.data;\n if (!model) {\n return { success: false, error: 'File not found' };\n }\n if (model.type === 'directory') {\n return { success: false, error: `Path is a directory: ${params.path}` };\n }\n let content = model.content ?? '';\n if (model.format === 'base64') {\n return { success: false, error: 'Binary file content is not supported' };\n }\n if (typeof content !== 'string') {\n content = JSON.stringify(content, null, 2);\n }\n const lines = content.split('\\n');\n const totalLines = lines.length;\n // Apply offset and limit\n const sliced = lines.slice(offset, offset + safeLimit);\n return {\n success: true,\n output: sliced.join('\\n'),\n metadata: {\n lineCount: sliced.length,\n totalLines,\n offset,\n limit: safeLimit,\n truncated: totalLines > offset + safeLimit\n }\n };\n };\n // Helper function for auto-approving search/file tools with execution results\n const handleAutoToolInterrupt = async (interrupt) => {\n const { threadId, action, args } = interrupt;\n console.log('[AgentPanel] Auto-approving tool:', action, args);\n try {\n let executionResult;\n if (action === 'search_notebook_cells_tool') {\n setDebugStatus({ status: `노트북 검색 실행 중: ${args?.pattern || ''}`, icon: 'search' });\n executionResult = await apiService.searchNotebookCells({\n pattern: args?.pattern || '',\n notebook_path: args?.notebook_path,\n cell_type: args?.cell_type,\n max_results: args?.max_results || 30,\n case_sensitive: args?.case_sensitive || false\n });\n console.log('[AgentPanel] search_notebook_cells result:', executionResult);\n }\n else if (action === 'check_resource_tool') {\n const filesList = args?.files || [];\n setDebugStatus({ status: `리소스 체크 중: ${filesList.join(', ') || 'system'}`, icon: 'check' });\n executionResult = await apiService.checkResource({\n files: filesList,\n dataframes: args?.dataframes || [],\n file_size_command: args?.file_size_command || '',\n dataframe_check_code: args?.dataframe_check_code || ''\n });\n console.log('[AgentPanel] check_resource result:', executionResult);\n }\n else if (action === 'read_file_tool') {\n setDebugStatus({ status: '파일 읽는 중...', icon: 'read' });\n const readParams = {\n path: typeof args?.path === 'string' ? args.path : '',\n encoding: typeof args?.encoding === 'string' ? args.encoding : undefined,\n offset: typeof args?.offset === 'number' ? args.offset : 0,\n limit: typeof args?.limit === 'number' ? args.limit : (args?.max_lines ?? args?.maxLines ?? 500)\n };\n executionResult = await executeReadFileTool(readParams);\n }\n else if (action === 'list_workspace_tool') {\n const searchPattern = args?.pattern && args.pattern !== '*' ? ` 패턴: ${args.pattern}` : '';\n setDebugStatus({ status: `파일 목록 조회${searchPattern}`, icon: 'tool' });\n const listParams = {\n path: typeof args?.path === 'string' ? args.path : '.',\n pattern: typeof args?.pattern === 'string' ? args.pattern : '*',\n recursive: args?.recursive === true,\n };\n executionResult = await executeListFilesTool(listParams);\n }\n else if (action === 'search_files_tool') {\n setDebugStatus({ status: `파일 검색 중: ${args?.pattern || ''}`, icon: 'search' });\n try {\n const currentLlmConfig = llmConfig || getLLMConfig() || getDefaultLLMConfig();\n const searchResult = await apiService.searchWorkspace({\n pattern: args?.pattern || '',\n file_types: args?.file_pattern ? [args.file_pattern] : ['*'],\n path: args?.path || '.',\n max_results: args?.max_results || 30,\n case_sensitive: args?.case_sensitive || false,\n workspaceRoot: currentLlmConfig.workspaceRoot\n });\n if (searchResult.success && searchResult.results) {\n const formatted = searchResult.results.map((r) => `${r.file_path}:${r.line_number}: ${r.content}`).join('\\n');\n executionResult = {\n success: true,\n output: formatted || '(no matches found)',\n metadata: { count: searchResult.total_results }\n };\n }\n else {\n executionResult = { success: false, error: searchResult.error || 'Search failed' };\n }\n }\n catch (error) {\n executionResult = { success: false, error: error.message || 'Search failed' };\n }\n console.log('[AgentPanel] search_files result:', executionResult);\n }\n else {\n console.warn('[AgentPanel] Unknown auto tool:', action);\n return;\n }\n // Resume with execution result\n const resumeArgs = {\n ...args,\n execution_result: executionResult\n };\n // Clear interrupt state (don't show approval UI for search)\n setInterruptData(null);\n // Create new assistant message for continued response\n const assistantMessageId = makeMessageId('assistant');\n setStreamingMessageId(assistantMessageId);\n setMessages(prev => [\n ...prev,\n {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now()\n }\n ]);\n let streamedContent = '';\n let interrupted = false;\n setDebugStatus({ status: 'LLM 응답 대기 중', icon: 'thinking' });\n await apiService.resumeAgent(threadId, 'edit', // Use 'edit' to pass execution_result in args\n resumeArgs, undefined, llmConfig || undefined, (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, (status) => {\n setDebugStatus(status);\n }, (nextInterrupt) => {\n interrupted = true;\n approvalPendingRef.current = true;\n const autoApproveEnabled = getAutoApproveEnabled(llmConfig || getLLMConfig() || getDefaultLLMConfig());\n // Handle next interrupt (could be another search or code execution)\n if (nextInterrupt.action === 'search_notebook_cells_tool'\n || nextInterrupt.action === 'check_resource_tool'\n || nextInterrupt.action === 'read_file_tool'\n || nextInterrupt.action === 'list_workspace_tool'\n || nextInterrupt.action === 'search_files_tool') {\n void handleAutoToolInterrupt(nextInterrupt);\n return;\n }\n // Handle ask_user_tool - show input UI\n if (nextInterrupt.action === 'ask_user_tool') {\n console.log('[AgentPanel] ask_user_tool interrupt (resume):', nextInterrupt.args);\n setIsAskUserMode(true);\n setPendingAskUserInterrupt(nextInterrupt);\n setIsLoading(false);\n setIsStreaming(false);\n // Display the question as an assistant message\n const question = nextInterrupt.args?.question || '응답을 입력하세요...';\n const options = nextInterrupt.args?.options;\n const inputType = nextInterrupt.args?.input_type || 'text';\n let questionContent = `❓ **${question}**`;\n if (options && options.length > 0) {\n questionContent += '\\n\\n선택지:\\n' + options.map((opt, i) => `${i + 1}. ${opt}`).join('\\n');\n }\n if (inputType === 'file') {\n questionContent += '\\n\\n_(파일을 업로드하거나 파일 경로를 입력하세요)_';\n }\n const askUserMessage = {\n id: makeMessageId('ask_user'),\n role: 'assistant',\n content: questionContent,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, askUserMessage]);\n setTimeout(() => {\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.focus();\n textarea.placeholder = question;\n }\n }, 100);\n return;\n }\n if (autoApproveEnabled) {\n // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시\n upsertInterruptMessage(nextInterrupt, true);\n // jupyter_cell_tool인 경우 먼저 셀 생성 후 승인 진행\n if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(nextInterrupt.args.code);\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n }\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(nextInterrupt.args.code);\n if (isAutoApprovedCode(nextInterrupt.args.code)) {\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n }\n setInterruptData(nextInterrupt);\n upsertInterruptMessage(nextInterrupt);\n setIsLoading(false);\n setIsStreaming(false);\n }, (newTodos) => {\n setTodos(newTodos);\n }, () => {\n setDebugStatus(null);\n }, handleToolCall);\n if (!interrupted) {\n setIsLoading(false);\n setIsStreaming(false);\n setStreamingMessageId(null);\n approvalPendingRef.current = false;\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Tool execution failed';\n console.error('[AgentPanel] Auto tool error:', error);\n setDebugStatus(`오류: ${message}`);\n setIsLoading(false);\n setIsStreaming(false);\n approvalPendingRef.current = false;\n }\n };\n const resumeFromInterrupt = async (interrupt, decision, feedback // Optional feedback message for rejection\n ) => {\n const { threadId } = interrupt;\n setInterruptData(null);\n setDebugStatus(null);\n clearInterruptMessage(decision); // Pass decision to track approve/reject\n let resumeDecision = decision;\n let resumeArgs = undefined;\n if (decision === 'approve') {\n // Handle write_file_tool separately - execute file write on Jupyter server\n if (interrupt.action === 'write_file_tool') {\n try {\n setDebugStatus({ status: '파일 쓰기 중...', icon: 'write' });\n const writeResult = await apiService.writeFile(interrupt.args?.path || '', interrupt.args?.content || '', {\n encoding: interrupt.args?.encoding || 'utf-8',\n overwrite: interrupt.args?.overwrite || false\n });\n console.log('[AgentPanel] write_file result:', writeResult);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: writeResult\n };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'File write failed';\n console.error('[AgentPanel] write_file error:', error);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, error: message }\n };\n }\n }\n else if (interrupt.action === 'edit_file_tool') {\n // Handle edit_file_tool - read file, perform replacement, write back\n try {\n setDebugStatus({ status: '파일 수정 중...', icon: 'edit' });\n const filePath = interrupt.args?.path || '';\n const oldString = interrupt.args?.old_string || '';\n const newString = interrupt.args?.new_string || '';\n const replaceAll = interrupt.args?.replace_all || false;\n // Read current file content\n const readResult = await apiService.readFile(filePath);\n if (!readResult.success || readResult.content === undefined) {\n throw new Error(readResult.error || 'Failed to read file');\n }\n const originalContent = readResult.content;\n const occurrences = (originalContent.match(new RegExp(oldString.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g')) || []).length;\n if (occurrences === 0) {\n throw new Error(`String not found in file: '${oldString.slice(0, 100)}...'`);\n }\n if (occurrences > 1 && !replaceAll) {\n throw new Error(`String appears ${occurrences} times. Use replace_all=True or provide more context.`);\n }\n // Perform replacement\n const newContent = replaceAll\n ? originalContent.split(oldString).join(newString)\n : originalContent.replace(oldString, newString);\n // Generate diff for logging\n const diffLines = [];\n const originalLines = originalContent.split('\\n');\n const newLines = newContent.split('\\n');\n diffLines.push(`--- ${filePath} (before)`);\n diffLines.push(`+++ ${filePath} (after)`);\n // Simple diff generation\n let i = 0, j = 0;\n while (i < originalLines.length || j < newLines.length) {\n if (i < originalLines.length && j < newLines.length && originalLines[i] === newLines[j]) {\n i++;\n j++;\n }\n else if (i < originalLines.length && (j >= newLines.length || originalLines[i] !== newLines[j])) {\n diffLines.push(`-${originalLines[i]}`);\n i++;\n }\n else {\n diffLines.push(`+${newLines[j]}`);\n j++;\n }\n }\n const diff = diffLines.join('\\n');\n // Write the modified content\n const writeResult = await apiService.writeFile(filePath, newContent, {\n encoding: 'utf-8',\n overwrite: true\n });\n console.log('[AgentPanel] edit_file result:', writeResult);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n ...writeResult,\n diff,\n occurrences,\n lines_added: newLines.length - originalLines.length > 0 ? newLines.length - originalLines.length : 0,\n lines_removed: originalLines.length - newLines.length > 0 ? originalLines.length - newLines.length : 0,\n }\n };\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'File edit failed';\n console.error('[AgentPanel] edit_file error:', error);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, error: message }\n };\n }\n }\n else if (interrupt.action === 'multiedit_file_tool') {\n // Handle multiedit_file_tool - apply multiple edits atomically (Crush 패턴)\n try {\n setDebugStatus({ status: '다중 파일 수정 중...', icon: 'edit' });\n const filePath = interrupt.args?.path || '';\n const edits = interrupt.args?.edits || [];\n if (!filePath || edits.length === 0) {\n throw new Error('File path and edits are required');\n }\n // Read current file content\n const readResult = await apiService.readFile(filePath);\n if (!readResult.success || readResult.content === undefined) {\n throw new Error(readResult.error || 'Failed to read file');\n }\n const originalContent = readResult.content;\n let currentContent = originalContent;\n let editsApplied = 0;\n let editsFailed = 0;\n const failedEdits = [];\n // Apply edits sequentially\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n const oldString = edit.old_string || '';\n const newString = edit.new_string || '';\n const replaceAll = edit.replace_all || false;\n const occurrences = (currentContent.match(new RegExp(oldString.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g')) || []).length;\n if (occurrences === 0) {\n failedEdits.push(`Edit ${i + 1}: String not found`);\n editsFailed++;\n continue;\n }\n if (occurrences > 1 && !replaceAll) {\n failedEdits.push(`Edit ${i + 1}: String appears ${occurrences} times (use replace_all)`);\n editsFailed++;\n continue;\n }\n // Apply this edit\n currentContent = replaceAll\n ? currentContent.split(oldString).join(newString)\n : currentContent.replace(oldString, newString);\n editsApplied++;\n }\n // Generate diff\n const diffLines = [];\n const originalLines = originalContent.split('\\n');\n const newLines = currentContent.split('\\n');\n diffLines.push(`--- ${filePath} (before)`);\n diffLines.push(`+++ ${filePath} (after)`);\n let i = 0, j = 0;\n while (i < originalLines.length || j < newLines.length) {\n if (i < originalLines.length && j < newLines.length && originalLines[i] === newLines[j]) {\n i++;\n j++;\n }\n else if (i < originalLines.length && (j >= newLines.length || originalLines[i] !== newLines[j])) {\n diffLines.push(`-${originalLines[i]}`);\n i++;\n }\n else {\n diffLines.push(`+${newLines[j]}`);\n j++;\n }\n }\n const diff = diffLines.join('\\n');\n // Write the modified content if any edits succeeded\n if (editsApplied > 0) {\n const writeResult = await apiService.writeFile(filePath, currentContent, {\n encoding: 'utf-8',\n overwrite: true\n });\n console.log('[AgentPanel] multiedit_file result:', writeResult, 'applied:', editsApplied, 'failed:', editsFailed);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n ...writeResult,\n diff,\n edits_applied: editsApplied,\n edits_failed: editsFailed,\n failed_details: failedEdits,\n lines_added: newLines.length - originalLines.length > 0 ? newLines.length - originalLines.length : 0,\n lines_removed: originalLines.length - newLines.length > 0 ? originalLines.length - newLines.length : 0,\n }\n };\n }\n else {\n throw new Error(`All ${edits.length} edits failed: ${failedEdits.join('; ')}`);\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Multi-edit failed';\n console.error('[AgentPanel] multiedit_file error:', error);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, error: message, edits_applied: 0, edits_failed: 0 }\n };\n }\n }\n else if (interrupt.action === 'diagnostics_tool') {\n // Handle diagnostics_tool - get LSP diagnostics via LSP Bridge\n try {\n setDebugStatus({ status: 'LSP 진단 조회 중...', icon: 'diagnostics' });\n const filePath = interrupt.args?.path || null;\n const severityFilter = interrupt.args?.severity_filter || null;\n // Get LSP Bridge instance\n const lspBridge = window._hdspLSPBridge;\n if (lspBridge && lspBridge.isLSPAvailable()) {\n const result = await lspBridge.getDiagnostics(filePath);\n // Apply severity filter if specified\n let diagnostics = result.diagnostics;\n if (severityFilter) {\n diagnostics = diagnostics.filter((d) => d.severity === severityFilter);\n }\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: true,\n lsp_available: true,\n diagnostics\n }\n };\n }\n else {\n // LSP not available - return empty result\n console.log('[AgentPanel] LSP not available, returning empty diagnostics');\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: true,\n lsp_available: false,\n diagnostics: []\n }\n };\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Diagnostics failed';\n console.error('[AgentPanel] diagnostics_tool error:', error);\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, lsp_available: false, error: message, diagnostics: [] }\n };\n }\n }\n else if (interrupt.action === 'references_tool') {\n // Handle references_tool - find symbol references\n try {\n setDebugStatus({ status: '참조 검색 중...', icon: 'search' });\n const symbol = interrupt.args?.symbol || '';\n const filePath = interrupt.args?.path || null;\n // Get LSP Bridge instance\n const lspBridge = window._hdspLSPBridge;\n if (lspBridge && lspBridge.isLSPAvailable()) {\n const result = await lspBridge.getReferences(symbol, filePath, interrupt.args?.line, interrupt.args?.character);\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: result.success,\n lsp_available: true,\n locations: result.locations,\n used_grep: false\n }\n };\n }\n else {\n // LSP not available - suggest using execute_command_tool with grep\n console.log('[AgentPanel] LSP not available for references, suggesting grep fallback');\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: {\n success: false,\n lsp_available: false,\n locations: [],\n used_grep: false\n }\n };\n }\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'References search failed';\n console.error('[AgentPanel] references_tool error:', error);\n resumeDecision = 'approve';\n resumeArgs = {\n ...interrupt.args,\n execution_result: { success: false, lsp_available: false, error: message, locations: [] }\n };\n }\n }\n else if (interrupt.action === 'execute_command_tool') {\n const command = (interrupt.args?.command || '').trim();\n // Default stdin to \"y\\n\" for interactive prompts (yes/no)\n const stdinInput = interrupt.args?.stdin ?? 'y\\n';\n setDebugStatus({ status: '셸 명령 실행 중...', icon: 'terminal' });\n const outputMessageId = command ? createCommandOutputMessage(command) : null;\n const execResult = command\n ? await executeSubprocessCommand(command, interrupt.args?.timeout, outputMessageId\n ? (chunk) => appendCommandOutputMessage(outputMessageId, chunk.text, chunk.stream)\n : undefined, stdinInput)\n : {\n success: false,\n output: '',\n error: 'Command is required',\n returncode: null,\n command,\n truncated: false\n };\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n execution_result: execResult\n };\n }\n else if (interrupt.action === 'jupyter_cell_tool' && interrupt.args?.code) {\n const code = interrupt.args.code;\n if (!shouldExecuteInNotebook(code)) {\n setDebugStatus({ status: '서브프로세스로 코드 실행 중...', icon: 'terminal' });\n const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n else {\n const executed = await executePendingApproval();\n if (executed && executed.tool === 'jupyter_cell') {\n resumeDecision = 'edit';\n resumeArgs = {\n code: executed.code,\n execution_result: executed.execution_result\n };\n }\n else {\n const notebook = getActiveNotebookPanel();\n if (!notebook) {\n setDebugStatus({ status: '노트북이 없어 서브프로세스로 실행 중...', icon: 'terminal' });\n const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n else {\n const cellIndex = insertCell(notebook, 'code', code);\n if (cellIndex === null) {\n setDebugStatus({ status: '노트북 모델이 없어 서브프로세스로 실행 중...', icon: 'terminal' });\n const execResult = await executeCodeViaSubprocess(code, interrupt.args?.timeout);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n else {\n await executeCell(notebook, cellIndex);\n const execResult = captureExecutionResult(notebook, cellIndex);\n resumeDecision = 'edit';\n resumeArgs = {\n code,\n execution_result: execResult\n };\n }\n }\n }\n }\n }\n else if (interrupt.action === 'ask_user_tool') {\n // Handle ask_user_tool - pass user's response back to the agent\n // The feedback parameter contains the user's response from handleSendMessage\n console.log('[AgentPanel] ask_user_tool approve with user response:', feedback);\n resumeDecision = 'edit';\n resumeArgs = {\n ...interrupt.args,\n user_response: feedback || ''\n };\n }\n else {\n const executed = await executePendingApproval();\n if (executed && executed.tool === 'jupyter_cell') {\n resumeDecision = 'edit';\n resumeArgs = {\n code: executed.code,\n execution_result: executed.execution_result\n };\n }\n else if (executed && executed.tool === 'markdown') {\n resumeDecision = 'edit';\n resumeArgs = {\n content: executed.content\n };\n }\n }\n }\n else {\n // Reject: delete the pending cell from notebook\n const pendingCall = pendingToolCallsRef.current[0];\n if (pendingCall && pendingCall.cellIndex !== undefined) {\n const notebook = getActiveNotebookPanel();\n if (notebook) {\n deleteCell(notebook, pendingCall.cellIndex);\n }\n }\n pendingToolCallsRef.current.shift();\n approvalPendingRef.current = pendingToolCallsRef.current.length > 0;\n }\n setIsLoading(true);\n setIsStreaming(true);\n let interrupted = false;\n // 항상 새 메시지 생성 - 승인 UI 아래에 append되도록\n const assistantMessageId = makeMessageId('assistant');\n setStreamingMessageId(assistantMessageId);\n // 새 메시지 추가 (맨 아래에 append)\n setMessages(prev => [\n ...prev,\n {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: Date.now()\n }\n ]);\n let streamedContent = '';\n // Build feedback message for rejection\n const rejectionFeedback = resumeDecision === 'reject'\n ? (feedback ? `사용자 피드백: ${feedback}` : 'User rejected this action')\n : undefined;\n try {\n await apiService.resumeAgent(threadId, resumeDecision, resumeArgs, rejectionFeedback, llmConfig || undefined, (chunk) => {\n streamedContent += chunk;\n setMessages(prev => prev.map(msg => msg.id === assistantMessageId && isChatMessage(msg)\n ? { ...msg, content: streamedContent }\n : msg));\n }, (status) => {\n setDebugStatus(status);\n }, (nextInterrupt) => {\n interrupted = true;\n approvalPendingRef.current = true;\n const autoApproveEnabled = getAutoApproveEnabled(llmConfig || getLLMConfig() || getDefaultLLMConfig());\n // Auto-approve search/file/resource tools\n if (nextInterrupt.action === 'search_notebook_cells_tool'\n || nextInterrupt.action === 'check_resource_tool'\n || nextInterrupt.action === 'read_file_tool'\n || nextInterrupt.action === 'list_workspace_tool'\n || nextInterrupt.action === 'search_files_tool') {\n void handleAutoToolInterrupt(nextInterrupt);\n return;\n }\n // Handle ask_user_tool - show input UI\n if (nextInterrupt.action === 'ask_user_tool') {\n console.log('[AgentPanel] ask_user_tool interrupt (resume callback):', nextInterrupt.args);\n setIsAskUserMode(true);\n setPendingAskUserInterrupt(nextInterrupt);\n setIsLoading(false);\n setIsStreaming(false);\n // Display the question as an assistant message\n const question = nextInterrupt.args?.question || '응답을 입력하세요...';\n const options = nextInterrupt.args?.options;\n const inputType = nextInterrupt.args?.input_type || 'text';\n let questionContent = `❓ **${question}**`;\n if (options && options.length > 0) {\n questionContent += '\\n\\n선택지:\\n' + options.map((opt, i) => `${i + 1}. ${opt}`).join('\\n');\n }\n if (inputType === 'file') {\n questionContent += '\\n\\n_(파일을 업로드하거나 파일 경로를 입력하세요)_';\n }\n const askUserMessage = {\n id: makeMessageId('ask_user'),\n role: 'assistant',\n content: questionContent,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, askUserMessage]);\n // Focus on input and set placeholder to the question\n setTimeout(() => {\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.focus();\n textarea.placeholder = question;\n }\n }, 100);\n return;\n }\n if (autoApproveEnabled) {\n // 자동 승인 모드일 때도 인터럽트 메시지를 생성하여 코드블럭으로 표시\n upsertInterruptMessage(nextInterrupt, true);\n // jupyter_cell_tool인 경우 먼저 셀 생성 후 승인 진행\n if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(nextInterrupt.args.code);\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n }\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (nextInterrupt.action === 'jupyter_cell_tool' && nextInterrupt.args?.code) {\n const shouldQueue = shouldExecuteInNotebook(nextInterrupt.args.code);\n if (isAutoApprovedCode(nextInterrupt.args.code)) {\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n void resumeFromInterrupt(nextInterrupt, 'approve');\n return;\n }\n if (shouldQueue) {\n queueApprovalCell(nextInterrupt.args.code);\n }\n }\n setInterruptData(nextInterrupt);\n upsertInterruptMessage(nextInterrupt);\n setIsLoading(false);\n setIsStreaming(false);\n }, (newTodos) => {\n setTodos(newTodos);\n }, () => {\n setDebugStatus(null);\n }, handleToolCall);\n }\n catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to resume';\n setDebugStatus(`오류: ${message}`);\n console.error('Resume failed:', error);\n setMessages(prev => [\n ...prev,\n {\n id: makeMessageId(),\n role: 'assistant',\n content: `Error: ${message}`,\n timestamp: Date.now()\n }\n ]);\n }\n finally {\n setIsLoading(false);\n setIsStreaming(false);\n setStreamingMessageId(null);\n if (!interrupted) {\n approvalPendingRef.current = false;\n }\n }\n };\n const handleSendMessage = async () => {\n // Handle rejection mode - resume with optional feedback\n if (isRejectionMode && pendingRejectionInterrupt) {\n const feedback = input.trim() || undefined; // Empty input means no feedback\n // Add user message bubble if there's feedback\n if (feedback) {\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: feedback,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, userMessage]);\n }\n setInput('');\n setIsRejectionMode(false);\n const interruptToResume = pendingRejectionInterrupt;\n setPendingRejectionInterrupt(null);\n await resumeFromInterrupt(interruptToResume, 'reject', feedback);\n return;\n }\n // Handle ask_user mode - pass user's response to the agent\n if (isAskUserMode && pendingAskUserInterrupt) {\n const userResponse = input.trim();\n if (!userResponse) {\n // User submitted empty input - prompt them\n return;\n }\n // Add user message bubble\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: userResponse,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, userMessage]);\n setInput('');\n setIsAskUserMode(false);\n const interruptToResume = pendingAskUserInterrupt;\n setPendingAskUserInterrupt(null);\n // Reset textarea placeholder\n setTimeout(() => {\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.placeholder = '메시지를 입력하세요...';\n }\n }, 100);\n // Resume with user response passed as feedback\n await resumeFromInterrupt(interruptToResume, 'approve', userResponse);\n return;\n }\n // Check if there's an LLM prompt stored (from cell action)\n const textarea = messagesEndRef.current?.parentElement?.querySelector('.jp-agent-input');\n const llmPrompt = pendingLlmPromptRef.current || textarea?.getAttribute('data-llm-prompt');\n // Allow execution if we have an LLM prompt even if input is empty (for auto-execution)\n if ((!input.trim() && !llmPrompt) || isLoading || isStreaming || isAgentRunning)\n return;\n const currentInput = input.trim();\n // Agent 모드이면 Agent 실행\n if (inputMode === 'agent') {\n // Use app.shell.currentWidget for reliable active tab detection\n const app = window.jupyterapp;\n let notebook = null;\n if (app?.shell?.currentWidget) {\n const currentWidget = app.shell.currentWidget;\n if ('content' in currentWidget && currentWidget.content?.model) {\n notebook = currentWidget;\n }\n }\n if (!notebook) {\n notebook = notebookTracker?.currentWidget;\n }\n // 노트북이 없는 경우\n if (!notebook) {\n // [DISABLED] Python 에러가 감지되면 파일 수정 모드로 전환\n // 이 레거시 기능은 /file/action API가 구현되지 않아 비활성화됨\n // if (detectPythonError(currentInput)) {\n // console.log('[AgentPanel] Agent mode: No notebook, but Python error detected - attempting file fix');\n // const handled = await handlePythonErrorFix(currentInput);\n // if (handled) {\n // const userMessage: IChatMessage = {\n // id: makeMessageId(),\n // role: 'user',\n // content: currentInput,\n // timestamp: Date.now(),\n // };\n // setMessages(prev => [...prev, userMessage]);\n // setInput('');\n // return;\n // }\n // console.log('[AgentPanel] Agent mode: No file path found, continuing...');\n // }\n // 파일 수정 관련 자연어 요청 감지 (에러, 고쳐, 수정, fix 등)\n const fileFixRequestPatterns = [\n /에러.*해결/i,\n /에러.*고쳐/i,\n /에러.*수정/i,\n /오류.*해결/i,\n /오류.*고쳐/i,\n /오류.*수정/i,\n /fix.*error/i,\n /\\.py.*에러/i,\n /\\.py.*오류/i,\n /콘솔.*에러/i,\n /console.*error/i,\n /파일.*에러/i,\n /파일.*오류/i,\n ];\n const isFileFixRequest = fileFixRequestPatterns.some(pattern => pattern.test(currentInput));\n if (isFileFixRequest) {\n console.log('[AgentPanel] Agent mode: No notebook, file fix request detected - prompting for error details');\n // User 메시지 추가\n const userMessage = {\n id: makeMessageId(),\n role: 'user',\n content: currentInput,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, userMessage]);\n setInput('');\n // 에러 메시지 요청 안내\n const guideMessage = {\n id: makeMessageId('guide'),\n role: 'assistant',\n content: `파일 에러 수정을 도와드리겠습니다!\n\n**에러 메시지를 복사해서 붙여넣어 주세요.**\n\nConsole에서 발생한 에러 전체를 복사해주시면:\n1. 에러가 발생한 파일을 자동으로 찾아서 읽고\n2. 관련된 import 파일들도 함께 분석하여\n3. 수정된 코드를 제안해 드립니다.\n\n예시:\n\\`\\`\\`\n$ python b.py\nTraceback (most recent call last):\n File \"a.py\", line 3\n def foo(\n ^\nSyntaxError: '(' was never closed\n\\`\\`\\``,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, guideMessage]);\n return;\n }\n // 일반적인 요청은 Chat 모드로 fallback (sendChatMessage 사용)\n console.log('[AgentPanel] Agent mode: No notebook - using LangChain agent without notebook context');\n }\n // Agent 모드는 sendChatMessage를 통해 sendAgentV2Stream (LangChain) 사용\n }\n // [DISABLED] Python 에러 감지 및 파일 수정 모드 (Chat 모드에서만)\n // 이 레거시 기능은 /file/action API가 구현되지 않아 비활성화됨\n // LangChain agent의 edit_file_tool을 사용하도록 변경됨\n // if (inputMode === 'chat' && detectPythonError(currentInput)) {\n // console.log('[AgentPanel] Python error detected in message, attempting file fix...');\n // const handled = await handlePythonErrorFix(currentInput);\n // if (handled) {\n // const userMessage: IChatMessage = {\n // id: makeMessageId(),\n // role: 'user',\n // content: currentInput,\n // timestamp: Date.now(),\n // };\n // setMessages(prev => [...prev, userMessage]);\n // setInput('');\n // return;\n // }\n // console.log('[AgentPanel] No file path found in error, using regular LLM stream');\n // }\n // Use the display prompt (input) for the user message, or use a fallback if input is empty\n const displayContent = currentInput || (llmPrompt ? '셀 분석 요청' : '');\n await sendChatMessage({\n displayContent,\n llmPrompt,\n textarea,\n clearInput: Boolean(currentInput)\n });\n };\n // Handle resume after user approval/rejection\n const handleResumeAgent = async (decision) => {\n if (!interruptData)\n return;\n if (decision === 'reject') {\n // Enter rejection mode - wait for user feedback before resuming\n setIsRejectionMode(true);\n setPendingRejectionInterrupt(interruptData);\n // Clear the interrupt UI but keep the data for later, mark as rejected\n setInterruptData(null);\n clearInterruptMessage('reject'); // Pass 'reject' to show \"거부됨\"\n // Focus on input for user to provide feedback\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea) {\n textarea.focus();\n }\n return;\n }\n await resumeFromInterrupt(interruptData, decision);\n };\n // Handle input change and track cursor position for autocomplete\n const handleInputChange = (e) => {\n const newValue = e.target.value;\n const newCursorPosition = e.target.selectionStart || 0;\n setInput(newValue);\n setCursorPosition(newCursorPosition);\n // Check if we should show autocomplete (when typing @ or @file:)\n const textBeforeCursor = newValue.slice(0, newCursorPosition);\n const lastAtIndex = textBeforeCursor.lastIndexOf('@');\n if (lastAtIndex >= 0) {\n // Check if there's a space between @ and cursor (means @ is complete)\n const textAfterAt = textBeforeCursor.slice(lastAtIndex);\n if (!/\\s/.test(textAfterAt) || textAfterAt.includes(':')) {\n // Show autocomplete if typing a context command\n setShowAutocomplete(true);\n }\n else {\n setShowAutocomplete(false);\n }\n }\n else {\n setShowAutocomplete(false);\n }\n };\n // Handle autocomplete selection\n // viaEnter: true = Enter key or click (final selection), false = Tab key (navigation)\n const handleAutocompleteSelect = async (option, viaEnter) => {\n // Check if this is an action command (like @reset)\n if (option.id === '@reset' && option.is_complete) {\n // Execute reset action\n await executeResetAction();\n setShowAutocomplete(false);\n setHasAutocompleteOptions(false);\n setInput('');\n return;\n }\n if (!textareaRef.current)\n return;\n const text = input;\n // Find the start of the current context command\n let start = cursorPosition;\n while (start > 0 && !/\\s/.test(text[start - 1])) {\n start--;\n }\n // Build new text with the selected option\n const beforeCommand = text.slice(0, start);\n const afterCursor = text.slice(cursorPosition);\n // For Enter (final selection): add space after complete options\n // For Tab (navigation): don't add space, allow further navigation\n const addSpace = viaEnter && option.is_complete;\n const newText = beforeCommand + option.label + (addSpace ? ' ' : '') + afterCursor;\n const newCursorPosition = beforeCommand.length + option.label.length + (addSpace ? 1 : 0);\n setInput(newText);\n setCursorPosition(newCursorPosition);\n // Close autocomplete only on Enter (final selection)\n if (viaEnter) {\n setShowAutocomplete(false);\n setHasAutocompleteOptions(false);\n }\n // For Tab on directory, keep autocomplete open - it will refetch options\n // Focus and set cursor position\n setTimeout(() => {\n if (textareaRef.current) {\n textareaRef.current.focus();\n textareaRef.current.setSelectionRange(newCursorPosition, newCursorPosition);\n }\n }, 0);\n };\n // Execute @reset action\n const executeResetAction = async () => {\n try {\n // Reset both agent thread and conversation\n const threadId = agentThreadId || conversationId;\n if (threadId) {\n const result = await apiService.resetAgent(threadId);\n console.log('[AgentPanel] Reset result:', result);\n }\n // Clear local state\n setMessages([]);\n setConversationId('');\n setAgentThreadId(null);\n setIsAgentRunning(false);\n setIsLoading(false);\n setIsStreaming(false);\n setTodos([]);\n setInterruptData(null);\n setIsRejectionMode(false);\n setIsAskUserMode(false);\n // Add system message about reset\n const resetMessage = {\n id: `reset-${Date.now()}`,\n role: 'assistant',\n content: 'Agent가 reset되었습니다. 새로운 대화를 시작하세요.',\n timestamp: Date.now(),\n };\n setMessages([resetMessage]);\n console.log('[AgentPanel] Session reset complete');\n }\n catch (error) {\n console.error('[AgentPanel] Failed to reset session:', error);\n const errorMessage = {\n id: `error-${Date.now()}`,\n role: 'assistant',\n content: `Failed to reset session: ${error}`,\n timestamp: Date.now(),\n };\n setMessages(prev => [...prev, errorMessage]);\n }\n };\n const handleKeyDown = (e) => {\n // If autocomplete is visible AND has options, let it handle Tab/Enter\n if (showAutocomplete && hasAutocompleteOptions) {\n // These keys are handled by ContextAutocomplete\n if (e.key === 'Tab' || e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n return; // Let autocomplete handle it\n }\n if (e.key === 'Enter' && !e.shiftKey) {\n return; // Let autocomplete handle it\n }\n if (e.key === 'Escape') {\n setShowAutocomplete(false);\n setHasAutocompleteOptions(false);\n return;\n }\n }\n // Escape: Stop chat streaming (only in chat mode when streaming)\n if (e.key === 'Escape' && inputMode === 'chat' && isStreaming) {\n e.preventDefault();\n stopCurrentTask();\n return;\n }\n // Enter: 전송\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSendMessage();\n }\n // Shift+Tab: 모드 전환 (chat ↔ agent)\n if (e.key === 'Tab' && e.shiftKey) {\n e.preventDefault();\n setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');\n return;\n }\n // Cmd/Ctrl + . : 모드 전환 (대체 단축키)\n if (e.key === '.' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');\n }\n // Tab (without Shift): Agent 모드일 때 드롭다운 토글\n if (e.key === 'Tab' && !e.shiftKey && inputMode !== 'chat') {\n e.preventDefault();\n setShowModeDropdown(prev => !prev);\n }\n };\n // 모드 토글 함수 (chat ↔ agent)\n const toggleMode = () => {\n setInputMode(prev => prev === 'chat' ? 'agent' : 'chat');\n setShowModeDropdown(false);\n };\n const clearChat = () => {\n setMessages([]);\n setConversationId('');\n };\n // 메시지가 Chat 메시지인지 확인 (UnifiedMessage는 이제 IChatMessage만 있음)\n const isChatMessage = (msg) => {\n return true;\n };\n const mapStatusText = (raw) => {\n const normalized = raw.toLowerCase();\n if (normalized.includes('calling tool')) {\n return raw.replace(/Calling tool:/gi, '도구 호출 중:').trim();\n }\n if (normalized.includes('waiting for user approval')) {\n return '승인 대기 중...';\n }\n if (normalized.includes('resuming execution')) {\n return '승인 반영 후 실행 재개 중...';\n }\n return raw;\n };\n // Get status text from debug status (handling both string and IDebugStatus)\n const getDebugStatusText = () => {\n if (!debugStatus)\n return '';\n if (typeof debugStatus === 'string') {\n return mapStatusText(debugStatus);\n }\n // IDebugStatus object\n if (isDebugExpanded && debugStatus.full_text) {\n return mapStatusText(debugStatus.full_text);\n }\n return mapStatusText(debugStatus.status);\n };\n // Get icon SVG for debug status (if available)\n const getDebugStatusIcon = () => {\n if (!debugStatus || typeof debugStatus === 'string')\n return '';\n if (debugStatus.icon) {\n return getIconByName(debugStatus.icon);\n }\n return '';\n };\n // Check if debug status is expandable\n const isDebugStatusExpandable = () => {\n if (!debugStatus || typeof debugStatus === 'string')\n return false;\n return debugStatus.expandable === true;\n };\n const getStatusText = () => {\n if (interruptData) {\n return `승인 대기: ${interruptData.action}`;\n }\n if (debugStatus) {\n return getDebugStatusText();\n }\n if (isLoading || isStreaming) {\n // 기본 상태 메시지 - todo 내용은 compact todo UI에서 표시\n return '요청 처리 중';\n }\n return null;\n };\n const statusText = getStatusText();\n const hasActiveTodos = todos.some(todo => todo.status === 'pending' || todo.status === 'in_progress');\n return (React.createElement(\"div\", { className: \"jp-agent-panel\" },\n showSettings && (React.createElement(SettingsPanel, { onClose: () => setShowSettings(false), onSave: handleSaveConfig, currentConfig: llmConfig || undefined })),\n React.createElement(\"div\", { className: \"jp-agent-header\" },\n React.createElement(\"div\", { className: \"jp-agent-header-logo\", dangerouslySetInnerHTML: { __html: headerLogoSvg } }),\n React.createElement(\"div\", { className: \"jp-agent-header-buttons\" },\n React.createElement(\"button\", { className: \"jp-agent-clear-button\", onClick: clearChat, title: \"\\uB300\\uD654 \\uCD08\\uAE30\\uD654\" },\n React.createElement(\"svg\", { width: \"16\", height: \"16\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", strokeWidth: \"2\", strokeLinecap: \"round\", strokeLinejoin: \"round\" },\n React.createElement(\"polyline\", { points: \"3 6 5 6 21 6\" }),\n React.createElement(\"path\", { d: \"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" }),\n React.createElement(\"line\", { x1: \"10\", y1: \"11\", x2: \"10\", y2: \"17\" }),\n React.createElement(\"line\", { x1: \"14\", y1: \"11\", x2: \"14\", y2: \"17\" }))),\n React.createElement(\"button\", { className: \"jp-agent-settings-button-icon\", onClick: () => setShowSettings(true), title: \"\\uC124\\uC815\" },\n React.createElement(\"svg\", { width: \"16\", height: \"16\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", strokeWidth: \"2\", strokeLinecap: \"round\", strokeLinejoin: \"round\" },\n React.createElement(\"line\", { x1: \"4\", y1: \"21\", x2: \"4\", y2: \"14\" }),\n React.createElement(\"line\", { x1: \"4\", y1: \"10\", x2: \"4\", y2: \"3\" }),\n React.createElement(\"line\", { x1: \"12\", y1: \"21\", x2: \"12\", y2: \"12\" }),\n React.createElement(\"line\", { x1: \"12\", y1: \"8\", x2: \"12\", y2: \"3\" }),\n React.createElement(\"line\", { x1: \"20\", y1: \"21\", x2: \"20\", y2: \"16\" }),\n React.createElement(\"line\", { x1: \"20\", y1: \"12\", x2: \"20\", y2: \"3\" }),\n React.createElement(\"line\", { x1: \"1\", y1: \"14\", x2: \"7\", y2: \"14\" }),\n React.createElement(\"line\", { x1: \"9\", y1: \"8\", x2: \"15\", y2: \"8\" }),\n React.createElement(\"line\", { x1: \"17\", y1: \"16\", x2: \"23\", y2: \"16\" }))))),\n React.createElement(\"div\", { className: \"jp-agent-messages\", ref: messagesContainerRef },\n messages.length === 0 ? (React.createElement(\"div\", { className: \"jp-agent-empty-state\" },\n React.createElement(\"p\", null, \"\\uC548\\uB155\\uD558\\uC138\\uC694! Halo \\uC785\\uB2C8\\uB2E4.\"),\n React.createElement(\"p\", { className: \"jp-agent-empty-hint\" }, inputMode === 'agent'\n ? '노트북 작업을 자연어로 요청하세요. 예: \"데이터 시각화 해줘\"'\n : '메시지를 입력하거나 아래 버튼으로 Agent/CHAT 모드를 선택하세요.'))) : (messages.map(msg => {\n if (isChatMessage(msg)) {\n // Skip empty assistant messages (except streaming ones)\n const isEmptyAssistant = msg.role === 'assistant'\n && (!msg.content || msg.content.trim() === '')\n && streamingMessageId !== msg.id;\n if (isEmptyAssistant) {\n return null;\n }\n // 일반 Chat 메시지\n const isAssistant = msg.role === 'assistant';\n const isShellOutput = msg.metadata?.kind === 'shell-output';\n const interruptAction = msg.metadata?.interrupt?.action;\n const isWriteFile = interruptAction === 'write_file_tool';\n const isEditFile = interruptAction === 'edit_file_tool';\n const isMultiEditFile = interruptAction === 'multiedit_file_tool';\n const isMarkdownTool = interruptAction === 'markdown_tool' || interruptAction === 'markdown';\n const writePath = (isWriteFile\n && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';\n const editPath = ((isEditFile || isMultiEditFile)\n && typeof msg.metadata?.interrupt?.args?.path === 'string') ? msg.metadata?.interrupt?.args?.path : '';\n const autoApproved = msg.metadata?.interrupt?.autoApproved;\n const headerRole = msg.role === 'user'\n ? '사용자'\n : msg.role === 'system'\n ? (isShellOutput ? 'shell 실행' : (autoApproved ? '자동 승인됨' : '승인 요청'))\n : 'Agent';\n return (React.createElement(\"div\", { key: msg.id, className: isAssistant\n ? 'jp-agent-message jp-agent-message-assistant-inline'\n : `jp-agent-message jp-agent-message-${msg.role}${isShellOutput ? ' jp-agent-message-shell-output' : ''}` },\n !isAssistant && (React.createElement(\"div\", { className: \"jp-agent-message-header\" },\n React.createElement(\"span\", { className: \"jp-agent-message-role\" }, headerRole),\n React.createElement(\"span\", { className: \"jp-agent-message-time\" }, new Date(msg.timestamp).toLocaleTimeString()))),\n React.createElement(\"div\", { className: `jp-agent-message-content${streamingMessageId === msg.id ? ' streaming' : ''}${isShellOutput ? ' jp-agent-message-content-shell' : ''}` }, msg.role === 'system' && msg.metadata?.interrupt ? (React.createElement(\"div\", { className: `jp-agent-interrupt-inline${msg.metadata?.interrupt?.autoApproved ? ' jp-agent-interrupt-auto-approved' : ''}` },\n React.createElement(\"div\", { className: \"jp-agent-interrupt-action\" },\n React.createElement(\"div\", { className: \"jp-agent-interrupt-action-args\" }, (() => {\n const command = msg.metadata?.interrupt?.args?.command;\n const code = msg.metadata?.interrupt?.args?.code || msg.metadata?.interrupt?.args?.content || '';\n // Handle edit_file_tool and multiedit_file_tool with diff preview\n let snippet;\n let language;\n if (isMultiEditFile) {\n // Handle multiedit_file_tool - show all edits\n const edits = msg.metadata?.interrupt?.args?.edits || [];\n const editsPreview = edits.slice(0, 5).map((edit, idx) => {\n const oldStr = edit.old_string || '';\n const newStr = edit.new_string || '';\n const oldPreview = oldStr.length > 100 ? oldStr.slice(0, 100) + '...' : oldStr;\n const newPreview = newStr.length > 100 ? newStr.slice(0, 100) + '...' : newStr;\n return `# Edit ${idx + 1}${edit.replace_all ? ' (replace_all)' : ''}\\n` +\n oldPreview.split('\\n').map((line) => `-${line}`).join('\\n') + '\\n' +\n newPreview.split('\\n').map((line) => `+${line}`).join('\\n');\n }).join('\\n\\n');\n const moreEdits = edits.length > 5 ? `\\n\\n... and ${edits.length - 5} more edits` : '';\n snippet = `--- ${editPath} (${edits.length} edits)\\n+++ ${editPath} (after)\\n\\n${editsPreview}${moreEdits}`;\n language = 'diff';\n }\n else if (isEditFile) {\n const oldStr = msg.metadata?.interrupt?.args?.old_string || '';\n const newStr = msg.metadata?.interrupt?.args?.new_string || '';\n const replaceAll = msg.metadata?.interrupt?.args?.replace_all;\n // Generate simple diff preview\n const oldPreview = oldStr.length > 500 ? oldStr.slice(0, 500) + '...' : oldStr;\n const newPreview = newStr.length > 500 ? newStr.slice(0, 500) + '...' : newStr;\n snippet = `--- ${editPath} (before)\\n+++ ${editPath} (after)\\n` +\n oldPreview.split('\\n').map((line) => `-${line}`).join('\\n') + '\\n' +\n newPreview.split('\\n').map((line) => `+${line}`).join('\\n') +\n (replaceAll ? '\\n\\n(replace_all: true)' : '');\n language = 'diff';\n }\n else if (isMarkdownTool) {\n // markdown_tool: render as HTML, not as code block\n snippet = code || '';\n language = 'markdown';\n }\n else {\n snippet = (command || code || '(no details)');\n language = command ? 'bash' : 'python';\n }\n const resolved = msg.metadata?.interrupt?.resolved;\n const decision = msg.metadata?.interrupt?.decision;\n const autoApproved = msg.metadata?.interrupt?.autoApproved;\n // 자동 승인일 때는 코드블럭 헤더에 배지가 표시되므로 actionHtml에는 표시하지 않음\n const resolvedIcon = autoApproved ? '' : (decision === 'reject' ? Icons.rejectCircle : Icons.doubleCheck);\n const resolvedClass = autoApproved ? '' : (decision === 'reject' ? 'jp-agent-interrupt-actions--rejected' : 'jp-agent-interrupt-actions--resolved');\n const actionHtml = resolved && !autoApproved\n ? `<div class=\"jp-agent-interrupt-actions ${resolvedClass}\">${resolvedIcon}</div>`\n : resolved && autoApproved\n ? '' // 자동 승인일 때는 actionHtml 비움 (코드블럭 헤더에 배지 표시)\n : `\n<div class=\"code-block-actions jp-agent-interrupt-actions\">\n <button class=\"jp-agent-interrupt-approve-btn\" data-action=\"approve\" title=\"승인\">${Icons.approveCircle}</button>\n <button class=\"jp-agent-interrupt-reject-btn\" data-action=\"reject\" title=\"거부\">${Icons.rejectCircle}</button>\n</div>\n`;\n // Get tool description from args (for jupyter_cell_tool)\n const toolDescription = msg.metadata?.interrupt?.args?.description;\n const renderedHtml = (() => {\n // markdown_tool: render content as HTML directly, not as code block\n let html = isMarkdownTool\n ? formatMarkdownToHtml(snippet)\n : formatMarkdownToHtml(`\\n\\`\\`\\`${language}\\n${snippet}\\n\\`\\`\\``);\n if (isWriteFile && writePath) {\n const safePath = escapeHtml(writePath);\n html = html.replace(/<span class=\"code-block-language\">[^<]*<\\/span>/, `<span class=\"code-block-language jp-agent-interrupt-path\">${safePath}</span>`);\n }\n if ((isEditFile || isMultiEditFile) && editPath) {\n const safePath = escapeHtml(editPath);\n const editIcon = Icons.edit;\n html = html.replace(/<span class=\"code-block-language\">[^<]*<\\/span>/, `<span class=\"code-block-language jp-agent-interrupt-path\">${editIcon} ${safePath}</span>`);\n }\n // actionHtml이 비어있지 않을 때만 추가\n let codeHtml;\n if (isMarkdownTool) {\n // markdown_tool: wrap in container with action buttons\n codeHtml = actionHtml\n ? `<div class=\"jp-agent-markdown-preview\">${html}${actionHtml}</div>`\n : `<div class=\"jp-agent-markdown-preview\">${html}</div>`;\n }\n else {\n codeHtml = actionHtml ? html.replace('</div>', `${actionHtml}</div>`) : html;\n }\n // Build the final HTML with proper ordering:\n // 1. jp-agent-code-description (detailed description) - top\n // 2. jp-agent-interrupt-description (approval message) - right above code\n // 3. code block\n let result = '';\n // 1. 코드 설명이 있으면 맨 위에 표시\n if (toolDescription) {\n // Escape HTML first, then clean up newlines\n const safeDescription = escapeHtml(toolDescription)\n .replace(/\\\\n/g, ' ') // Remove literal \\n strings\n .replace(/\\n/g, ' ') // Remove actual newlines\n .replace(/\\s+/g, ' ') // Collapse multiple spaces\n .trim();\n result += `<div class=\"jp-agent-code-description\">${Icons.description} ${safeDescription}</div>`;\n }\n // 2. 승인 메시지 (자동 승인이 아닐 때만) - 코드 블록 바로 위\n if (!msg.metadata?.interrupt?.autoApproved && msg.content) {\n const safeContent = escapeHtml(msg.content);\n result += `<div class=\"jp-agent-interrupt-description\">${Icons.approvalWarning} ${safeContent}</div>`;\n }\n // 3. 코드 블록\n result += codeHtml;\n return result;\n })();\n return (React.createElement(\"div\", { className: \"jp-RenderedHTMLCommon\", style: { padding: '0 4px' }, dangerouslySetInnerHTML: { __html: renderedHtml }, onClick: (event) => {\n const target = event.target;\n // Find the button element with data-action (handles clicks on SVG inside button)\n const button = target.closest('[data-action]');\n const action = button?.getAttribute?.('data-action');\n if (msg.metadata?.interrupt?.resolved) {\n return;\n }\n if (action === 'approve') {\n handleResumeAgent('approve');\n }\n else if (action === 'reject') {\n handleResumeAgent('reject');\n }\n } }));\n })())))) : msg.role === 'assistant' ? (\n // Assistant(AI) 메시지: 스트리밍 지원 마크다운 렌더링\n // 이벤트 위임은 상위 messages-container에서 처리됨\n React.createElement(StreamingMessage, { content: msg.content, isStreaming: streamingMessageId === msg.id, onInsertAbove: handleInsertCodeAbove, onInsertBelow: handleInsertCodeBelow, onInsertAsCell: handleInsertCodeAsCell, cellActionsEnabled: !!getActiveNotebookPanel() })) : (\n // User(사용자) 메시지: 텍스트 그대로 줄바꿈만 처리\n React.createElement(\"div\", { style: { whiteSpace: 'pre-wrap' } }, msg.content)))));\n }\n return null;\n })),\n showConsoleErrorNotification && lastConsoleError && (React.createElement(\"div\", { className: \"jp-agent-console-error-notification\" },\n React.createElement(\"div\", { className: \"jp-agent-console-error-header\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"16\", height: \"16\" },\n React.createElement(\"path\", { d: \"M8.982 1.566a1.13 1.13 0 00-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 01-1.1 0L7.1 5.995A.905.905 0 018 5zm.002 6a1 1 0 110 2 1 1 0 010-2z\" })),\n React.createElement(\"span\", null, \"Console\\uC5D0\\uC11C Python \\uC5D0\\uB7EC\\uAC00 \\uAC10\\uC9C0\\uB418\\uC5C8\\uC2B5\\uB2C8\\uB2E4\")),\n React.createElement(\"div\", { className: \"jp-agent-console-error-preview\" },\n lastConsoleError.slice(0, 200),\n lastConsoleError.length > 200 ? '...' : ''),\n React.createElement(\"div\", { className: \"jp-agent-console-error-actions\" },\n React.createElement(\"button\", { className: \"jp-agent-console-error-fix-btn\", onClick: () => {\n // 에러를 자동으로 입력창에 넣고 파일 수정 요청\n setInput(`다음 에러를 분석하고 수정해주세요:\\n\\n${lastConsoleError}`);\n setShowConsoleErrorNotification(false);\n // 입력창에 포커스\n const textarea = document.querySelector('.jp-agent-input');\n if (textarea)\n textarea.focus();\n } }, \"\\uC5D0\\uB7EC \\uBD84\\uC11D \\uC694\\uCCAD\"),\n React.createElement(\"button\", { className: \"jp-agent-console-error-dismiss-btn\", onClick: () => setShowConsoleErrorNotification(false) }, \"\\uB2EB\\uAE30\")))),\n pendingFileFixes.length > 0 && (React.createElement(\"div\", { className: \"jp-agent-file-fixes\" },\n React.createElement(\"div\", { className: \"jp-agent-file-fixes-header\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"path\", { d: \"M14 1H2a1 1 0 00-1 1v12a1 1 0 001 1h12a1 1 0 001-1V2a1 1 0 00-1-1zM2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z\" }),\n React.createElement(\"path\", { d: \"M9.5 5a.5.5 0 00-1 0v3.793L6.854 7.146a.5.5 0 10-.708.708l2.5 2.5a.5.5 0 00.708 0l2.5-2.5a.5.5 0 00-.708-.708L9.5 8.793V5z\" })),\n React.createElement(\"span\", null,\n \"\\uC218\\uC815\\uB41C \\uD30C\\uC77C (\",\n pendingFileFixes.length,\n \"\\uAC1C)\")),\n React.createElement(\"div\", { className: \"jp-agent-file-fixes-list\" }, pendingFileFixes.map((fix, index) => (React.createElement(\"div\", { key: index, className: \"jp-agent-file-fix-item\" },\n React.createElement(\"div\", { className: \"jp-agent-file-fix-info\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"path\", { d: \"M4 0a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V4.5L9.5 0H4zm5.5 1.5v2a1 1 0 001 1h2l-3-3zM4.5 8a.5.5 0 010 1h7a.5.5 0 010-1h-7zm0 2a.5.5 0 010 1h7a.5.5 0 010-1h-7zm0 2a.5.5 0 010 1h4a.5.5 0 010-1h-4z\" })),\n React.createElement(\"span\", { className: \"jp-agent-file-fix-path\" }, fix.path)),\n React.createElement(\"button\", { className: \"jp-agent-file-fix-apply\", onClick: () => applyFileFix(fix), title: `${fix.path} 파일에 수정 적용` }, \"\\uC801\\uC6A9\\uD558\\uAE30\"))))),\n React.createElement(\"button\", { className: \"jp-agent-file-fixes-dismiss\", onClick: () => setPendingFileFixes([]), title: \"\\uC218\\uC815 \\uC81C\\uC548 \\uB2EB\\uAE30\" }, \"\\uB2EB\\uAE30\"))),\n React.createElement(\"div\", { ref: messagesEndRef })),\n inputMode !== 'chat' && todos.length > 0 && (React.createElement(\"div\", { className: \"jp-agent-todo-compact\" },\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-header\", onClick: () => setIsTodoExpanded(!isTodoExpanded) },\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-left\" },\n React.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\" },\n React.createElement(\"path\", { d: \"M6 12l4-4-4-4\" })),\n (() => {\n const currentTodo = todos.find(t => t.status === 'in_progress') || todos.find(t => t.status === 'pending');\n const isStillWorking = isStreaming || isLoading || isAgentRunning;\n if (currentTodo) {\n return (React.createElement(React.Fragment, null,\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-spinner\" }),\n React.createElement(\"span\", { className: \"jp-agent-todo-compact-current\" }, currentTodo.content)));\n }\n else if (isStillWorking) {\n return (React.createElement(React.Fragment, null,\n React.createElement(\"div\", { className: \"jp-agent-todo-compact-spinner\" }),\n React.createElement(\"span\", { className: \"jp-agent-todo-compact-current\" }, \"\\uC791\\uC5C5 \\uB9C8\\uBB34\\uB9AC \\uC911...\")));\n }\n else {\n return (React.createElement(\"span\", { className: \"jp-agent-todo-compact-current\", dangerouslySetInnerHTML: { __html: `${Icons.check} 모든 작업 완료` } }));\n }\n })()),\n (isStreaming || isLoading || isAgentRunning || todos.some(t => t.status === 'in_progress')) && (React.createElement(\"button\", { className: \"jp-agent-todo-stop-btn\", onClick: (e) => {\n e.stopPropagation();\n console.log('[AgentPanel] Stop button clicked');\n stopCurrentTask();\n }, title: \"\\uC791\\uC5C5 \\uC911\\uB2E8\" },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"rect\", { x: \"3\", y: \"3\", width: \"10\", height: \"10\", rx: \"1\" })))),\n React.createElement(\"span\", { className: \"jp-agent-todo-compact-progress\" },\n todos.filter(t => t.status === 'completed').length,\n \"/\",\n todos.length)),\n statusText && hasActiveTodos && (React.createElement(\"div\", { className: `jp-agent-message jp-agent-message-debug jp-agent-message-debug--inline${statusText.startsWith('오류:') ? ' jp-agent-message-debug-error' : ''}` },\n React.createElement(\"div\", { className: \"jp-agent-debug-content\" },\n React.createElement(\"div\", { className: \"jp-agent-debug-branch\", \"aria-hidden\": \"true\" }),\n getDebugStatusIcon() && (React.createElement(\"span\", { className: \"jp-agent-debug-icon\", dangerouslySetInnerHTML: { __html: getDebugStatusIcon() } })),\n React.createElement(\"span\", { className: `jp-agent-debug-text${isDebugStatusExpandable() ? ' jp-agent-debug-text--expandable' : ''}`, onClick: isDebugStatusExpandable() ? () => setIsDebugExpanded(!isDebugExpanded) : undefined, title: isDebugStatusExpandable() ? (isDebugExpanded ? '접기' : '펼치기') : undefined },\n statusText,\n isDebugStatusExpandable() && (React.createElement(\"span\", { className: \"jp-agent-debug-expand-icon\" }, isDebugExpanded ? React.createElement(ExpandLessIcon, { sx: { fontSize: 14 } }) : React.createElement(ExpandMoreIcon, { sx: { fontSize: 14 } }))))))),\n isTodoExpanded && (React.createElement(\"div\", { className: \"jp-agent-todo-expanded\" }, todos.map((todo, index) => (React.createElement(\"div\", { key: index, className: `jp-agent-todo-item jp-agent-todo-item--${todo.status}` },\n React.createElement(\"div\", { className: \"jp-agent-todo-item-indicator\" },\n todo.status === 'completed' && (React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.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\" }))),\n todo.status === 'in_progress' && React.createElement(\"div\", { className: \"jp-agent-todo-item-spinner\" }),\n todo.status === 'pending' && React.createElement(\"span\", { className: \"jp-agent-todo-item-number\" }, index + 1)),\n React.createElement(\"span\", { className: `jp-agent-todo-item-text ${todo.status === 'completed' ? 'jp-agent-todo-item-text--done' : ''}` }, todo.content)))))))),\n statusText && !hasActiveTodos && (React.createElement(\"div\", { className: `jp-agent-message jp-agent-message-debug jp-agent-message-debug--above-input${statusText.startsWith('오류:') ? ' jp-agent-message-debug-error' : ''}` },\n React.createElement(\"div\", { className: \"jp-agent-debug-content\" },\n getDebugStatusIcon() && (React.createElement(\"span\", { className: \"jp-agent-debug-icon\", dangerouslySetInnerHTML: { __html: getDebugStatusIcon() } })),\n React.createElement(\"span\", { className: `jp-agent-debug-text${isDebugStatusExpandable() ? ' jp-agent-debug-text--expandable' : ''}`, onClick: isDebugStatusExpandable() ? () => setIsDebugExpanded(!isDebugExpanded) : undefined, title: isDebugStatusExpandable() ? (isDebugExpanded ? '접기' : '펼치기') : undefined },\n statusText,\n isDebugStatusExpandable() && (React.createElement(\"span\", { className: \"jp-agent-debug-expand-icon\" }, isDebugExpanded ? React.createElement(ExpandLessIcon, { sx: { fontSize: 14 } }) : React.createElement(ExpandMoreIcon, { sx: { fontSize: 14 } }))))))),\n React.createElement(\"div\", { className: \"jp-agent-input-container\" },\n React.createElement(\"div\", { className: \"jp-agent-input-wrapper\", style: { position: 'relative' } },\n React.createElement(ContextAutocomplete, { apiService: apiService, inputValue: input, cursorPosition: cursorPosition, baseDir: notebookTracker?.currentWidget?.context?.path ?\n notebookTracker.currentWidget.context.path.replace(/[^/]*$/, '') : undefined, onSelect: handleAutocompleteSelect, onClose: () => { setShowAutocomplete(false); setHasAutocompleteOptions(false); }, onOptionsChange: setHasAutocompleteOptions, visible: showAutocomplete, anchorEl: textareaRef.current }),\n React.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\n ? '다른 방향 제시'\n : (inputMode === 'agent'\n ? '노트북 작업을 입력하세요... (예: @file:path로 파일 참조)'\n : '메시지를 입력하세요... (@file:path로 파일 참조)'), rows: 3, disabled: isLoading || isAgentRunning }),\n React.createElement(\"div\", { className: \"jp-agent-button-container\" },\n inputMode === 'chat' && isStreaming && (React.createElement(\"button\", { className: \"jp-agent-stop-button\", onClick: stopCurrentTask, title: \"\\uC751\\uB2F5 \\uC911\\uB2E8 (Esc)\" },\n React.createElement(\"svg\", { viewBox: \"0 0 24 24\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"rect\", { x: \"6\", y: \"6\", width: \"12\", height: \"12\", rx: \"1\" })),\n \"\\uC911\\uB2E8\")),\n React.createElement(\"button\", { className: \"jp-agent-send-button\", onClick: handleSendMessage, disabled: (!input.trim() && !isRejectionMode) || isLoading || isStreaming || isAgentRunning, title: isRejectionMode ? \"거부 전송 (Enter)\" : \"전송 (Enter)\" }, isAgentRunning ? '실행 중...' : (isRejectionMode ? '거부' : '전송')))),\n React.createElement(\"div\", { className: \"jp-agent-mode-bar\" },\n React.createElement(\"div\", { className: \"jp-agent-mode-toggle-container\" },\n React.createElement(\"button\", { className: `jp-agent-mode-toggle ${inputMode !== 'chat' ? 'jp-agent-mode-toggle--active' : ''}`, onClick: toggleMode, title: `${inputMode === 'chat' ? 'Chat' : 'Agent'} 모드 (⇧Tab)` },\n React.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' ? (\n // 채팅 아이콘 (Chat 모드)\n React.createElement(\"path\", { d: \"M8 1C3.58 1 0 4.13 0 8c0 1.5.5 2.88 1.34 4.04L.5 15l3.37-.92A8.56 8.56 0 008 15c4.42 0 8-3.13 8-7s-3.58-7-8-7zM4.5 9a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2z\" })) : (\n // 로봇/AI 아이콘 (Agent 모드)\n React.createElement(\"path\", { d: \"M309-389q29 29 71 29t71-29l160-160q29-29 29-71t-29-71q-29-29-71-29t-71 29q-37-13-73-6t-61 32q-25 25-32 61t6 73q-29 29-29 71t29 71ZM240-80v-172q-57-52-88.5-121.5T120-520q0-150 105-255t255-105q125 0 221.5 73.5T827-615l52 205q5 19-7 34.5T840-360h-80v120q0 33-23.5 56.5T680-160h-80v80h-80v-160h160v-200h108l-38-155q-23-91-98-148t-172-57q-116 0-198 81t-82 197q0 60 24.5 114t69.5 96l26 24v208h-80Zm254-360Z\" }))),\n React.createElement(\"span\", { className: \"jp-agent-mode-label\" }, inputMode === 'chat' ? 'Chat' : 'Agent'),\n React.createElement(\"svg\", { className: \"jp-agent-mode-chevron\", viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"12\", height: \"12\" },\n React.createElement(\"path\", { d: \"M4.47 5.47a.75.75 0 011.06 0L8 7.94l2.47-2.47a.75.75 0 111.06 1.06l-3 3a.75.75 0 01-1.06 0l-3-3a.75.75 0 010-1.06z\" }))),\n showModeDropdown && (React.createElement(\"div\", { className: \"jp-agent-mode-dropdown\" },\n React.createElement(\"button\", { className: `jp-agent-mode-option ${inputMode === 'chat' ? 'jp-agent-mode-option--selected' : ''}`, onClick: () => { setInputMode('chat'); setShowModeDropdown(false); } },\n React.createElement(\"svg\", { viewBox: \"0 0 16 16\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"path\", { d: \"M8 1C3.58 1 0 4.13 0 8c0 1.5.5 2.88 1.34 4.04L.5 15l3.37-.92A8.56 8.56 0 008 15c4.42 0 8-3.13 8-7s-3.58-7-8-7zM4.5 9a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2zm3.5 0a1 1 0 110-2 1 1 0 010 2z\" })),\n React.createElement(\"span\", null, \"Chat\"),\n React.createElement(\"span\", { className: \"jp-agent-mode-shortcut\" }, \"\\uC77C\\uBC18 \\uB300\\uD654\")),\n React.createElement(\"button\", { className: `jp-agent-mode-option ${inputMode === 'agent' ? 'jp-agent-mode-option--selected' : ''}`, onClick: () => { setInputMode('agent'); setShowModeDropdown(false); } },\n React.createElement(\"svg\", { viewBox: \"0 -960 960 960\", fill: \"currentColor\", width: \"14\", height: \"14\" },\n React.createElement(\"path\", { d: \"M309-389q29 29 71 29t71-29l160-160q29-29 29-71t-29-71q-29-29-71-29t-71 29q-37-13-73-6t-61 32q-25 25-32 61t6 73q-29 29-29 71t29 71ZM240-80v-172q-57-52-88.5-121.5T120-520q0-150 105-255t255-105q125 0 221.5 73.5T827-615l52 205q5 19-7 34.5T840-360h-80v120q0 33-23.5 56.5T680-160h-80v80h-80v-160h160v-200h108l-38-155q-23-91-98-148t-172-57q-116 0-198 81t-82 197q0 60 24.5 114t69.5 96l26 24v208h-80Zm254-360Z\" })),\n React.createElement(\"span\", null, \"Agent\"),\n React.createElement(\"span\", { className: \"jp-agent-mode-shortcut\" }, \"\\uB178\\uD2B8\\uBD81 \\uC790\\uB3D9 \\uC2E4\\uD589\"))))),\n React.createElement(\"div\", { className: \"jp-agent-mode-hints\" },\n React.createElement(\"span\", { className: \"jp-agent-mode-hint\" }, \"\\u21E7Tab \\uBAA8\\uB4DC \\uC804\\uD658\")))),\n fileSelectionMetadata && (React.createElement(FileSelectionDialog, { filename: fileSelectionMetadata.pattern, options: fileSelectionMetadata.options, message: fileSelectionMetadata.message, onSelect: handleFileSelect, onCancel: handleFileSelectCancel }))));\n});\nChatPanel.displayName = 'ChatPanel';\n/**\n * Agent Panel Widget\n */\nexport class AgentPanelWidget extends ReactWidget {\n constructor(apiService, notebookTracker, consoleTracker) {\n super();\n this.chatPanelRef = React.createRef();\n this.apiService = apiService;\n this.notebookTracker = notebookTracker;\n this.consoleTracker = consoleTracker;\n this.id = 'hdsp-agent-panel';\n this.title.caption = 'HDSP Agent Assistant';\n this.title.icon = hdspTabIcon;\n this.addClass('jp-agent-widget');\n }\n render() {\n return (React.createElement(ChatPanel, { ref: this.chatPanelRef, apiService: this.apiService, notebookTracker: this.notebookTracker, consoleTracker: this.consoleTracker }));\n }\n /**\n * Add a message from cell action\n * @param action - The action type (explain, fix, custom_prompt)\n * @param cellContent - The cell content\n * @param displayPrompt - The user-facing prompt to show in the UI\n * @param llmPrompt - The actual prompt to send to the LLM\n * @param cellId - The cell ID for applying code (optional)\n * @param cellIndex - The cell index (0-based) for applying code (optional)\n */\n addCellActionMessage(action, cellContent, displayPrompt, llmPrompt, cellId, cellIndex) {\n console.log('[AgentPanel] Cell action:', action);\n console.log('[AgentPanel] Display prompt:', displayPrompt);\n console.log('[AgentPanel] LLM prompt:', llmPrompt);\n console.log('[AgentPanel] Cell ID:', cellId);\n console.log('[AgentPanel] Cell Index:', cellIndex);\n if (!this.chatPanelRef.current) {\n console.error('[AgentPanel] ChatPanel ref not available');\n return;\n }\n // E, F, ? 버튼 클릭 시 항상 일반 대화(chat) 모드로 전환\n if (this.chatPanelRef.current.setInputMode) {\n this.chatPanelRef.current.setInputMode('chat');\n }\n // Store cell index for code application (preferred method)\n if (cellIndex !== undefined && this.chatPanelRef.current.setCurrentCellIndex) {\n this.chatPanelRef.current.setCurrentCellIndex(cellIndex);\n }\n // Store cell ID for code application (fallback)\n if (cellId && this.chatPanelRef.current.setCurrentCellId) {\n this.chatPanelRef.current.setCurrentCellId(cellId);\n }\n // Set the display prompt in the input field\n this.chatPanelRef.current.setInput(displayPrompt);\n // Store the LLM prompt\n this.chatPanelRef.current.setLlmPrompt(llmPrompt);\n // Automatically execute after a short delay to ensure state is updated\n setTimeout(() => {\n if (this.chatPanelRef.current) {\n this.chatPanelRef.current.handleSendMessage().catch(error => {\n console.error('[AgentPanel] Failed to send message automatically:', error);\n });\n }\n }, 100);\n }\n /**\n * Analyze entire notebook before saving\n * @param cells - Array of collected cells with content, output, and imports\n * @param onComplete - Callback to execute after analysis (perform save)\n */\n analyzeNotebook(cells, onComplete) {\n console.log('[AgentPanel] analyzeNotebook called with', cells.length, 'cells');\n if (!this.chatPanelRef.current) {\n console.error('[AgentPanel] ChatPanel ref not available');\n onComplete();\n return;\n }\n // 저장 시 검수도 항상 일반 대화(chat) 모드로 전환\n if (this.chatPanelRef.current.setInputMode) {\n this.chatPanelRef.current.setInputMode('chat');\n }\n // Create summary of notebook\n const totalCells = cells.length;\n const cellsWithErrors = cells.filter(cell => cell.output && (cell.output.includes('Error') ||\n cell.output.includes('Traceback') ||\n cell.output.includes('Exception'))).length;\n const cellsWithImports = cells.filter(cell => cell.imports && cell.imports.length > 0).length;\n const localImports = cells.reduce((acc, cell) => {\n if (cell.imports) {\n return acc + cell.imports.filter((imp) => imp.isLocal).length;\n }\n return acc;\n }, 0);\n // Create display prompt\n const displayPrompt = `전체 노트북 검수 요청 (${totalCells}개 셀)`;\n // Create detailed LLM prompt with all cells\n let llmPrompt = `다음은 저장하기 전의 Jupyter 노트북 전체 내용입니다. 모든 셀을 검토하고 개선 사항을 제안해주세요.\n\n## 노트북 요약\n- 전체 셀 수: ${totalCells}개\n- 에러가 있는 셀: ${cellsWithErrors}개\n- Import가 있는 셀: ${cellsWithImports}개\n- 로컬 모듈 import: ${localImports}개\n\n## 전체 셀 내용\n\n`;\n cells.forEach((cell, index) => {\n llmPrompt += `### 셀 ${index + 1} (ID: ${cell.id})\n\\`\\`\\`python\n${cell.content}\n\\`\\`\\`\n`;\n if (cell.output) {\n llmPrompt += `\n**실행 결과:**\n\\`\\`\\`\n${cell.output}\n\\`\\`\\`\n`;\n }\n if (cell.imports && cell.imports.length > 0) {\n llmPrompt += `\n**Imports:**\n`;\n cell.imports.forEach((imp) => {\n llmPrompt += `- \\`${imp.module}\\` (${imp.isLocal ? '로컬' : '표준 라이브러리'})\\n`;\n });\n }\n llmPrompt += '\\n---\\n\\n';\n });\n llmPrompt += `\n## 검수 요청 사항\n\n다음 형식으로 응답해주세요:\n\n### 1. 전반적인 코드 품질 평가\n(노트북 전체의 코드 품질, 구조, 일관성 등을 평가)\n\n### 2. 발견된 주요 이슈\n(에러, 경고, 잠재적 문제점 등을 나열)\n\n### 3. 셀별 개선 제안\n각 셀에 대해 구체적인 개선 사항이 있다면:\n- **셀 X**: (개선 사항)\n \\`\\`\\`python\n (개선된 코드)\n \\`\\`\\`\n\n### 4. 전반적인 개선 권장사항\n(노트북 전체를 개선하기 위한 일반적인 제안)\n\n### 5. 저장 권장 여부\n- [권장] **저장 권장**: (이유)\n- [주의] **수정 후 저장 권장**: (이유)\n- [비권장] **저장 비권장**: (이유)\n`;\n // Set the display prompt in the input field\n this.chatPanelRef.current.setInput(displayPrompt);\n // Store the LLM prompt\n this.chatPanelRef.current.setLlmPrompt(llmPrompt);\n // Automatically execute after a short delay to ensure state is updated\n setTimeout(() => {\n if (this.chatPanelRef.current) {\n this.chatPanelRef.current.handleSendMessage().catch(error => {\n console.error('[AgentPanel] Failed to send message automatically:', error);\n }).finally(() => {\n // Store the onComplete callback to be executed after user reviews the analysis\n // For now, we'll execute it immediately\n // TODO: Add UI button to allow user to review and then save\n console.log('[AgentPanel] Analysis complete, executing onComplete callback');\n onComplete();\n });\n }\n }, 100);\n }\n}\n","/**\n * Context Autocomplete Component\n *\n * Provides autocomplete functionality for @file and other context commands.\n * Inspired by jupyter-ai's autocomplete implementation.\n */\nimport React, { useState, useEffect, useRef, useCallback } from 'react';\nimport InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';\nimport FolderIcon from '@mui/icons-material/Folder';\nimport DescriptionIcon from '@mui/icons-material/Description';\n/**\n * Extract the word at cursor position that might be a context command.\n * Returns the partial command (e.g., \"@file:\" or \"@file:src/\")\n */\nfunction getPartialCommand(text, cursorPosition) {\n // Find the start of the current word\n let start = cursorPosition;\n while (start > 0 && !/\\s/.test(text[start - 1])) {\n start--;\n }\n const word = text.slice(start, cursorPosition);\n // Check if it looks like a context command\n if (word.startsWith('@')) {\n return word;\n }\n return null;\n}\n/**\n * Get icon for autocomplete option\n */\nfunction getOptionIcon(option) {\n const description = option.description.toLowerCase();\n if (description.includes('directory') || description.includes('folder')) {\n return React.createElement(FolderIcon, { sx: { fontSize: 16, color: 'var(--jp-ui-font-color2)' } });\n }\n if (description.includes('python') || description.includes('.py')) {\n return React.createElement(InsertDriveFileIcon, { sx: { fontSize: 16, color: '#3572A5' } });\n }\n if (description.includes('notebook') || description.includes('.ipynb')) {\n return React.createElement(InsertDriveFileIcon, { sx: { fontSize: 16, color: '#F37626' } });\n }\n if (description.includes('javascript') || description.includes('typescript')) {\n return React.createElement(InsertDriveFileIcon, { sx: { fontSize: 16, color: '#f1e05a' } });\n }\n if (description.includes('markdown')) {\n return React.createElement(DescriptionIcon, { sx: { fontSize: 16, color: 'var(--jp-ui-font-color2)' } });\n }\n return React.createElement(InsertDriveFileIcon, { sx: { fontSize: 16, color: 'var(--jp-ui-font-color2)' } });\n}\nexport const ContextAutocomplete = ({ apiService, inputValue, cursorPosition, baseDir, onSelect, onClose, onOptionsChange, visible, anchorEl, }) => {\n const [options, setOptions] = useState([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [loading, setLoading] = useState(false);\n const [partialCommand, setPartialCommand] = useState(null);\n const containerRef = useRef(null);\n // Fetch autocomplete options\n const fetchOptions = useCallback(async (partial) => {\n setLoading(true);\n try {\n const response = await apiService.getContextAutocomplete({\n partialCommand: partial,\n baseDir,\n });\n setOptions(response.options);\n setSelectedIndex(0);\n }\n catch (error) {\n console.error('[ContextAutocomplete] Failed to fetch options:', error);\n setOptions([]);\n }\n finally {\n setLoading(false);\n }\n }, [apiService, baseDir]);\n // Update options when input changes\n useEffect(() => {\n const partial = getPartialCommand(inputValue, cursorPosition);\n setPartialCommand(partial);\n if (partial) {\n // Debounce the fetch\n const timer = setTimeout(() => {\n fetchOptions(partial);\n }, 150);\n return () => clearTimeout(timer);\n }\n else {\n setOptions([]);\n }\n }, [inputValue, cursorPosition, fetchOptions]);\n // Notify parent when options change\n useEffect(() => {\n onOptionsChange?.(options.length > 0);\n }, [options, onOptionsChange]);\n // Handle keyboard navigation\n useEffect(() => {\n if (!visible || options.length === 0)\n return;\n const handleKeyDown = (e) => {\n const selectedOption = options[selectedIndex];\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n setSelectedIndex(prev => (prev + 1) % options.length);\n break;\n case 'ArrowUp':\n e.preventDefault();\n setSelectedIndex(prev => (prev - 1 + options.length) % options.length);\n break;\n case 'Tab':\n // Tab: Only navigate into directories (is_complete=false)\n // On files (is_complete=true), do nothing\n if (selectedOption && !selectedOption.is_complete) {\n e.preventDefault();\n onSelect(selectedOption, false); // viaEnter=false (Tab navigation)\n }\n else {\n // On complete file, just prevent default but don't select\n e.preventDefault();\n }\n break;\n case 'Enter':\n // Enter: Select any option (directory or file) and close autocomplete\n if (selectedOption) {\n e.preventDefault();\n e.stopPropagation(); // Prevent AgentPanel from sending message\n onSelect(selectedOption, true); // viaEnter=true (final selection)\n }\n break;\n case 'Escape':\n e.preventDefault();\n onClose();\n break;\n }\n };\n document.addEventListener('keydown', handleKeyDown, true);\n return () => document.removeEventListener('keydown', handleKeyDown, true);\n }, [visible, options, selectedIndex, onSelect, onClose]);\n // Scroll selected item into view\n useEffect(() => {\n if (containerRef.current && options.length > 0) {\n const selectedItem = containerRef.current.querySelector('.jp-context-autocomplete-item--selected');\n if (selectedItem) {\n selectedItem.scrollIntoView({ block: 'nearest' });\n }\n }\n }, [selectedIndex, options]);\n // Close on click outside\n useEffect(() => {\n if (!visible || options.length === 0)\n return;\n const handleClickOutside = (e) => {\n if (containerRef.current && !containerRef.current.contains(e.target)) {\n // Also check if click is on the anchor element (textarea)\n if (anchorEl && !anchorEl.contains(e.target)) {\n onClose();\n }\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [visible, options, anchorEl, onClose]);\n // Don't render if not visible or no partial command\n if (!visible || !partialCommand || options.length === 0) {\n return null;\n }\n // Calculate position based on anchor element\n const style = {\n position: 'absolute',\n bottom: anchorEl ? anchorEl.offsetHeight + 4 : 'auto',\n left: 0,\n right: 0,\n zIndex: 1000,\n };\n return (React.createElement(\"div\", { ref: containerRef, className: \"jp-context-autocomplete\", style: style },\n React.createElement(\"div\", { className: \"jp-context-autocomplete-header\" },\n React.createElement(\"span\", { className: \"jp-context-autocomplete-hint\" }, partialCommand.includes(':') ? 'Select a file' : 'Context commands'),\n React.createElement(\"span\", { className: \"jp-context-autocomplete-loading\" }, loading && 'Loading...')),\n React.createElement(\"div\", { className: \"jp-context-autocomplete-list\" }, options.map((option, index) => (React.createElement(\"div\", { key: `${option.id}-${option.label}-${index}`, className: `jp-context-autocomplete-item ${index === selectedIndex ? 'jp-context-autocomplete-item--selected' : ''}`, onClick: () => onSelect(option, true), onMouseEnter: () => setSelectedIndex(index) },\n React.createElement(\"span\", { className: \"jp-context-autocomplete-item-icon\" }, getOptionIcon(option)),\n React.createElement(\"span\", { className: \"jp-context-autocomplete-item-label\" }, option.label),\n React.createElement(\"span\", { className: \"jp-context-autocomplete-item-description\" }, option.description))))),\n React.createElement(\"div\", { className: \"jp-context-autocomplete-footer\" },\n React.createElement(\"kbd\", null, \"Tab\"),\n \" navigate, \",\n React.createElement(\"kbd\", null, \"Enter\"),\n \" select, \",\n React.createElement(\"kbd\", null, \"Esc\"),\n \" close\")));\n};\nexport default ContextAutocomplete;\n","/**\n * File Selection Dialog Component\n *\n * Shows multiple file options to user when a filename is ambiguous\n */\nimport React from 'react';\nexport const FileSelectionDialog = ({ filename, options, message, onSelect, onCancel }) => {\n return (React.createElement(\"div\", { className: \"file-selection-dialog\" },\n React.createElement(\"div\", { className: \"file-selection-overlay\", onClick: onCancel }),\n React.createElement(\"div\", { className: \"file-selection-content\" },\n React.createElement(\"h3\", null, \"\\uD30C\\uC77C \\uC120\\uD0DD\"),\n React.createElement(\"p\", { className: \"file-selection-message\" }, message),\n React.createElement(\"div\", { className: \"file-selection-options\" }, options.map((option, idx) => (React.createElement(\"button\", { key: idx, className: \"file-option-button\", onClick: () => onSelect(idx + 1) },\n React.createElement(\"span\", { className: \"file-option-number\" }, idx + 1),\n React.createElement(\"span\", { className: \"file-option-path\" }, option.relative))))),\n React.createElement(\"div\", { className: \"file-selection-actions\" },\n React.createElement(\"button\", { className: \"file-selection-cancel\", onClick: onCancel }, \"\\uCDE8\\uC18C\"))),\n React.createElement(\"style\", null, `\n .file-selection-dialog {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .file-selection-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n }\n\n .file-selection-content {\n position: relative;\n background: var(--jp-layout-color1);\n border: 1px solid var(--jp-border-color1);\n border-radius: 8px;\n padding: 24px;\n max-width: 600px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n }\n\n .file-selection-content h3 {\n margin: 0 0 12px 0;\n color: var(--jp-ui-font-color1);\n font-size: 18px;\n font-weight: 600;\n }\n\n .file-selection-message {\n margin: 0 0 20px 0;\n color: var(--jp-ui-font-color2);\n font-size: 14px;\n white-space: pre-wrap;\n line-height: 1.6;\n }\n\n .file-selection-options {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 20px;\n }\n\n .file-option-button {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n background: var(--jp-layout-color2);\n border: 1px solid var(--jp-border-color2);\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n text-align: left;\n }\n\n .file-option-button:hover {\n background: var(--jp-layout-color3);\n border-color: var(--jp-brand-color1);\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n\n .file-option-number {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n background: var(--jp-brand-color1);\n color: white;\n border-radius: 50%;\n font-weight: 600;\n font-size: 14px;\n flex-shrink: 0;\n }\n\n .file-option-path {\n flex: 1;\n color: var(--jp-ui-font-color1);\n font-family: var(--jp-code-font-family);\n font-size: 13px;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .file-selection-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n }\n\n .file-selection-cancel {\n padding: 8px 16px;\n background: transparent;\n border: 1px solid var(--jp-border-color2);\n border-radius: 4px;\n color: var(--jp-ui-font-color1);\n cursor: pointer;\n font-size: 14px;\n transition: all 0.2s;\n }\n\n .file-selection-cancel:hover {\n background: var(--jp-layout-color2);\n border-color: var(--jp-border-color1);\n }\n `)));\n};\n","/**\n * Prompt Generation Dialog Component\n * Dialog for entering prompts to generate notebooks\n */\nimport React, { useState } from 'react';\nimport { Dialog, DialogTitle, DialogContent, DialogActions, TextField, Button, Typography, Box, Chip } from '@mui/material';\nimport AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';\nimport LightbulbIcon from '@mui/icons-material/Lightbulb';\nconst EXAMPLE_PROMPTS = [\n '타이타닉 생존자 예측을 dask와 lgbm으로 생성해줘',\n '주식 데이터 분석 및 시각화 노트북 만들어줘',\n 'Iris 데이터셋으로 분류 모델 학습하기',\n '시계열 데이터 분석 및 예측 모델 만들기'\n];\nexport const PromptGenerationDialog = ({ open, onClose, onGenerate }) => {\n const [prompt, setPrompt] = useState('');\n const [isGenerating, setIsGenerating] = useState(false);\n const [progress, setProgress] = useState(0);\n const [statusMessage, setStatusMessage] = useState('');\n const handleGenerate = () => {\n if (prompt.trim()) {\n setIsGenerating(true);\n onGenerate(prompt.trim());\n setPrompt('');\n setIsGenerating(false);\n onClose();\n }\n };\n const handleKeyPress = (event) => {\n if (event.key === 'Enter' && event.ctrlKey) {\n handleGenerate();\n }\n };\n const handleExampleClick = (example) => {\n setPrompt(example);\n };\n return (React.createElement(Dialog, { open: open, onClose: onClose, maxWidth: \"md\", fullWidth: true, PaperProps: {\n sx: {\n borderRadius: 2,\n minHeight: '400px'\n }\n } },\n React.createElement(DialogTitle, null,\n React.createElement(Box, { display: \"flex\", alignItems: \"center\", gap: 1 },\n React.createElement(AutoFixHighIcon, { color: \"primary\" }),\n React.createElement(Typography, { variant: \"h6\" }, \"HALO \\uD504\\uB86C\\uD504\\uD2B8\\uB85C \\uB178\\uD2B8\\uBD81 \\uC0DD\\uC131\"))),\n React.createElement(DialogContent, null,\n React.createElement(Box, { display: \"flex\", flexDirection: \"column\", gap: 2, mt: 1 },\n React.createElement(Typography, { variant: \"body2\", color: \"text.secondary\" }, \"\\uC6D0\\uD558\\uB294 \\uB178\\uD2B8\\uBD81\\uC758 \\uB0B4\\uC6A9\\uC744 \\uC790\\uC5F0\\uC5B4\\uB85C \\uC124\\uBA85\\uD574\\uC8FC\\uC138\\uC694. AI\\uAC00 \\uC790\\uB3D9\\uC73C\\uB85C \\uB178\\uD2B8\\uBD81\\uC744 \\uC0DD\\uC131\\uD569\\uB2C8\\uB2E4.\"),\n React.createElement(TextField, { autoFocus: true, multiline: true, rows: 6, fullWidth: true, variant: \"outlined\", placeholder: \"\\uC608: \\uD0C0\\uC774\\uD0C0\\uB2C9 \\uC0DD\\uC874\\uC790 \\uC608\\uCE21\\uC744 dask\\uC640 lgbm\\uC73C\\uB85C \\uC0DD\\uC131\\uD574\\uC918\", value: prompt, onChange: (e) => setPrompt(e.target.value), onKeyPress: handleKeyPress, disabled: isGenerating, sx: {\n '& .MuiOutlinedInput-root': {\n fontSize: '14px'\n }\n } }),\n React.createElement(Box, null,\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\", display: \"block\", mb: 1 }, \"\\uC608\\uC2DC \\uD504\\uB86C\\uD504\\uD2B8 (\\uD074\\uB9AD\\uD558\\uC5EC \\uC0AC\\uC6A9):\"),\n React.createElement(Box, { display: \"flex\", flexWrap: \"wrap\", gap: 1 }, EXAMPLE_PROMPTS.map((example, index) => (React.createElement(Chip, { key: index, label: example, variant: \"outlined\", size: \"small\", onClick: () => handleExampleClick(example), sx: {\n cursor: 'pointer',\n '&:hover': {\n backgroundColor: 'action.hover'\n }\n } }))))),\n React.createElement(Box, { sx: {\n backgroundColor: 'action.hover',\n borderRadius: 1,\n padding: 2\n } },\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\", component: \"div\", sx: { display: 'flex', flexDirection: 'column' } },\n React.createElement(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 } },\n React.createElement(LightbulbIcon, { fontSize: \"small\", sx: { color: '#ffc107' } }),\n \" \",\n React.createElement(\"strong\", null, \"\\uD301:\")),\n React.createElement(\"br\", null),\n \"\\u2022 \\uC0AC\\uC6A9\\uD560 \\uB77C\\uC774\\uBE0C\\uB7EC\\uB9AC\\uB97C \\uBA85\\uC2DC\\uD558\\uBA74 \\uB354 \\uC815\\uD655\\uD569\\uB2C8\\uB2E4\",\n React.createElement(\"br\", null),\n \"\\u2022 \\uBD84\\uC11D \\uBAA9\\uC801\\uACFC \\uB370\\uC774\\uD130\\uB97C \\uAD6C\\uCCB4\\uC801\\uC73C\\uB85C \\uC124\\uBA85\\uD558\\uC138\\uC694\",\n React.createElement(\"br\", null),\n \"\\u2022 Ctrl + Enter\\uB85C \\uBE60\\uB974\\uAC8C \\uC0DD\\uC131\\uD560 \\uC218 \\uC788\\uC2B5\\uB2C8\\uB2E4\",\n React.createElement(\"br\", null),\n \"\\u2022 \\uC0DD\\uC131\\uC740 \\uBC31\\uADF8\\uB77C\\uC6B4\\uB4DC\\uC5D0\\uC11C \\uC9C4\\uD589\\uB418\\uBA70, \\uC644\\uB8CC\\uB418\\uBA74 \\uC54C\\uB9BC\\uC744 \\uBC1B\\uC2B5\\uB2C8\\uB2E4\")))),\n React.createElement(DialogActions, { sx: { padding: 2, paddingTop: 0 } },\n React.createElement(Button, { onClick: onClose, disabled: isGenerating }, \"\\uCDE8\\uC18C\"),\n React.createElement(Button, { onClick: handleGenerate, variant: \"contained\", disabled: !prompt.trim() || isGenerating, startIcon: React.createElement(AutoFixHighIcon, null) }, \"\\uC0DD\\uC131 \\uC2DC\\uC791\"))));\n};\n","/**\n * Settings Panel Component\n * Allows users to configure LLM provider settings\n *\n * API keys are stored in browser localStorage and sent with each request.\n * The Agent Server does not store API keys - it receives them with each request.\n */\nimport React, { useState, useEffect } from 'react';\nimport CheckCircleIcon from '@mui/icons-material/CheckCircle';\nimport ErrorIcon from '@mui/icons-material/Error';\nimport HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';\nimport CodeIcon from '@mui/icons-material/Code';\nimport SearchIcon from '@mui/icons-material/Search';\nimport AnalyticsIcon from '@mui/icons-material/Analytics';\nimport GpsFixedIcon from '@mui/icons-material/GpsFixed';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport ChevronRightIcon from '@mui/icons-material/ChevronRight';\nimport { getLLMConfig, saveLLMConfig, testApiKey, getDefaultLLMConfig, fetchDefaultPrompts, getCachedPrompts } from '../services/ApiKeyManager';\nexport const SettingsPanel = ({ onClose, onSave, currentConfig }) => {\n // Initialize from localStorage or props\n const initConfig = currentConfig || getLLMConfig() || getDefaultLLMConfig();\n const [provider, setProvider] = useState(initConfig.provider || 'gemini');\n const [isTesting, setIsTesting] = useState(false);\n const [testResults, setTestResults] = useState({});\n // Track active key index (first valid key by default)\n const [activeKeyIndex, setActiveKeyIndex] = useState(0);\n // Gemini settings - support multiple keys (max 10)\n const [geminiApiKeys, setGeminiApiKeys] = useState(initConfig.gemini?.apiKeys?.length\n ? initConfig.gemini.apiKeys\n : initConfig.gemini?.apiKey\n ? [initConfig.gemini.apiKey]\n : ['']);\n // Legacy single key for backward compatibility\n const geminiApiKey = geminiApiKeys[0] || '';\n // Validate gemini model - only allow valid options\n const validateGeminiModel = (model) => {\n const validModels = ['gemini-2.5-pro', 'gemini-2.5-flash'];\n if (model && validModels.includes(model)) {\n return model;\n }\n return 'gemini-2.5-flash';\n };\n const [geminiModel, setGeminiModel] = useState(validateGeminiModel(initConfig.gemini?.model));\n // vLLM settings\n const [vllmEndpoint, setVllmEndpoint] = useState(initConfig.vllm?.endpoint || 'http://localhost:8000/v1');\n const [vllmApiKey, setVllmApiKey] = useState(initConfig.vllm?.apiKey || '');\n const [vllmModel, setVllmModel] = useState(initConfig.vllm?.model || 'meta-llama/Llama-2-7b-chat-hf');\n const [vllmUseResponsesApi, setVllmUseResponsesApi] = useState(Boolean(initConfig.vllm?.useResponsesApi));\n const [vllmTemperature, setVllmTemperature] = useState(initConfig.vllm?.temperature ?? 0.0);\n // OpenAI settings\n const [openaiApiKey, setOpenaiApiKey] = useState(initConfig.openai?.apiKey || '');\n const [openaiModel, setOpenaiModel] = useState(initConfig.openai?.model || 'gpt-4');\n const [systemPrompt, setSystemPrompt] = useState(initConfig.systemPrompt || '');\n const [workspaceRoot, setWorkspaceRoot] = useState(initConfig.workspaceRoot || '');\n const [autoApprove, setAutoApprove] = useState(Boolean(initConfig.autoApprove));\n const [idleTimeoutMinutes, setIdleTimeoutMinutes] = useState(initConfig.idleTimeoutMinutes ?? 60);\n // Multi-Agent settings\n const [agentMode, setAgentMode] = useState(initConfig.agentMode || 'single');\n const [agentPrompts, setAgentPrompts] = useState(initConfig.agentPrompts || {});\n const [expandedAgents, setExpandedAgents] = useState({});\n // Default prompts fetched from API\n const [defaultPrompts, setDefaultPrompts] = useState(getCachedPrompts());\n const [isLoadingPrompts, setIsLoadingPrompts] = useState(!getCachedPrompts());\n // Fetch default prompts from API on mount\n useEffect(() => {\n if (!defaultPrompts) {\n setIsLoadingPrompts(true);\n fetchDefaultPrompts().then(prompts => {\n setDefaultPrompts(prompts);\n setIsLoadingPrompts(false);\n });\n }\n }, []);\n // Update state when currentConfig changes\n useEffect(() => {\n if (currentConfig) {\n setProvider(currentConfig.provider || 'gemini');\n // Handle multiple keys\n if (currentConfig.gemini?.apiKeys?.length) {\n setGeminiApiKeys(currentConfig.gemini.apiKeys);\n }\n else if (currentConfig.gemini?.apiKey) {\n setGeminiApiKeys([currentConfig.gemini.apiKey]);\n }\n else {\n setGeminiApiKeys(['']);\n }\n setGeminiModel(validateGeminiModel(currentConfig.gemini?.model));\n setVllmEndpoint(currentConfig.vllm?.endpoint || 'http://localhost:8000/v1');\n setVllmApiKey(currentConfig.vllm?.apiKey || '');\n setVllmModel(currentConfig.vllm?.model || 'meta-llama/Llama-2-7b-chat-hf');\n setVllmUseResponsesApi(Boolean(currentConfig.vllm?.useResponsesApi));\n setVllmTemperature(currentConfig.vllm?.temperature ?? 0.0);\n setOpenaiApiKey(currentConfig.openai?.apiKey || '');\n setOpenaiModel(currentConfig.openai?.model || 'gpt-4');\n setSystemPrompt(currentConfig.systemPrompt || getDefaultLLMConfig().systemPrompt || '');\n setWorkspaceRoot(currentConfig.workspaceRoot || '');\n setAutoApprove(Boolean(currentConfig.autoApprove));\n setIdleTimeoutMinutes(currentConfig.idleTimeoutMinutes ?? 60);\n setAgentMode(currentConfig.agentMode || 'single');\n // Use currentConfig prompts, or cached defaults, or empty object\n const cachedDefaults = getCachedPrompts();\n setAgentPrompts(currentConfig.agentPrompts || (cachedDefaults ? {\n planner: cachedDefaults.planner,\n python_developer: cachedDefaults.python_developer,\n researcher: cachedDefaults.researcher,\n athena_query: cachedDefaults.athena_query,\n } : {}));\n }\n }, [currentConfig]);\n // Helper: Build LLM config from state\n const buildLLMConfig = () => ({\n provider,\n gemini: {\n apiKey: geminiApiKeys[0] || '',\n apiKeys: geminiApiKeys.filter(k => k && k.trim()),\n model: geminiModel\n },\n vllm: {\n endpoint: vllmEndpoint,\n apiKey: vllmApiKey,\n model: vllmModel,\n useResponsesApi: vllmUseResponsesApi,\n temperature: vllmTemperature\n },\n openai: {\n apiKey: openaiApiKey,\n model: openaiModel\n },\n workspaceRoot: workspaceRoot.trim() ? workspaceRoot.trim() : undefined,\n systemPrompt: systemPrompt && systemPrompt.trim() ? systemPrompt : undefined,\n agentMode,\n agentPrompts,\n autoApprove,\n idleTimeoutMinutes\n });\n // Handlers for multiple API keys\n const handleAddKey = () => {\n if (geminiApiKeys.length < 10) {\n setGeminiApiKeys([...geminiApiKeys, '']);\n }\n };\n const handleRemoveKey = (index) => {\n if (geminiApiKeys.length > 1) {\n const newKeys = geminiApiKeys.filter((_, i) => i !== index);\n setGeminiApiKeys(newKeys);\n }\n };\n const handleKeyChange = (index, value) => {\n const newKeys = [...geminiApiKeys];\n newKeys[index] = value;\n setGeminiApiKeys(newKeys);\n };\n const validKeyCount = geminiApiKeys.filter(k => k && k.trim()).length;\n const handleTest = async () => {\n setIsTesting(true);\n setTestResults({});\n if (provider === 'gemini') {\n // Test each Gemini key individually\n const validKeys = geminiApiKeys.map((k, i) => ({ key: k, index: i }))\n .filter(({ key }) => key && key.trim());\n for (const { key, index } of validKeys) {\n setTestResults(prev => ({ ...prev, [index]: 'testing' }));\n const testConfig = {\n provider: 'gemini',\n gemini: { apiKey: key, apiKeys: [key], model: geminiModel }\n };\n try {\n const result = await testApiKey(testConfig);\n setTestResults(prev => ({ ...prev, [index]: result.success ? 'success' : 'error' }));\n }\n catch {\n setTestResults(prev => ({ ...prev, [index]: 'error' }));\n }\n }\n }\n else {\n // For other providers, just test the single config\n try {\n const config = buildLLMConfig();\n const result = await testApiKey(config);\n setTestResults({ 0: result.success ? 'success' : 'error' });\n }\n catch {\n setTestResults({ 0: 'error' });\n }\n }\n setIsTesting(false);\n };\n // Handle clicking a key row to set it as active\n const handleSetActiveKey = (index) => {\n if (geminiApiKeys[index] && geminiApiKeys[index].trim()) {\n setActiveKeyIndex(index);\n }\n };\n const handleSave = () => {\n const config = buildLLMConfig();\n // Save to localStorage\n saveLLMConfig(config);\n // Notify parent component\n onSave(config);\n onClose();\n };\n // Get test status icon for a key\n const getTestStatusIcon = (index) => {\n const status = testResults[index];\n if (status === 'testing')\n return React.createElement(HourglassEmptyIcon, { sx: { fontSize: 16, color: 'info.main' } });\n if (status === 'success')\n return React.createElement(CheckCircleIcon, { sx: { fontSize: 16, color: 'success.main' } });\n if (status === 'error')\n return React.createElement(ErrorIcon, { sx: { fontSize: 16, color: 'error.main' } });\n return null;\n };\n return (React.createElement(\"div\", { className: \"jp-agent-settings-overlay\" },\n React.createElement(\"div\", { className: \"jp-agent-settings-dialog\" },\n React.createElement(\"div\", { className: \"jp-agent-settings-header\" },\n React.createElement(\"h2\", null, \"LLM \\uC124\\uC815\"),\n React.createElement(\"button\", { className: \"jp-agent-settings-close\", onClick: onClose, title: \"\\uB2EB\\uAE30\" }, \"\\u00D7\")),\n React.createElement(\"div\", { className: \"jp-agent-settings-content\" },\n React.createElement(\"div\", { className: \"jp-agent-settings-notice\" },\n React.createElement(\"span\", null, \"API \\uD0A4\\uB294 \\uBE0C\\uB77C\\uC6B0\\uC800\\uC5D0 \\uC548\\uC804\\uD558\\uAC8C \\uC800\\uC7A5\\uB418\\uBA70, \\uC694\\uCCAD \\uC2DC\\uC5D0\\uB9CC \\uC11C\\uBC84\\uB85C \\uC804\\uC1A1\\uB429\\uB2C8\\uB2E4.\")),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uD504\\uB85C\\uBC14\\uC774\\uB354\"),\n React.createElement(\"select\", { className: \"jp-agent-settings-select\", value: provider, onChange: (e) => setProvider(e.target.value) },\n React.createElement(\"option\", { value: \"gemini\" }, \"Google Gemini\"),\n React.createElement(\"option\", { value: \"vllm\" }, \"vLLM\"),\n React.createElement(\"option\", { value: \"openai\" }, \"OpenAI\"))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\", htmlFor: \"jp-agent-workspace-root\" },\n \"\\uC6CC\\uD06C\\uC2A4\\uD398\\uC774\\uC2A4 \\uB8E8\\uD2B8\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"\\uBE44\\uC6B0\\uBA74 \\uD604\\uC7AC \\uB178\\uD2B8\\uBD81 \\uD3F4\\uB354 \\uAE30\\uC900, \\uC808\\uB300/\\uC0C1\\uB300 \\uACBD\\uB85C \\uAC00\\uB2A5\")),\n React.createElement(\"input\", { id: \"jp-agent-workspace-root\", type: \"text\", className: \"jp-agent-settings-input\", value: workspaceRoot, onChange: (e) => setWorkspaceRoot(e.target.value), placeholder: \"\\uC608: /Users/you/project\", \"data-testid\": \"workspace-root-input\" })),\n provider === 'gemini' && (React.createElement(\"div\", { className: \"jp-agent-settings-provider\" },\n React.createElement(\"h3\", null, \"Gemini \\uC124\\uC815\"),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" },\n \"API \\uD0A4 (\",\n validKeyCount,\n \"/10)\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"Rate limit \\uC2DC \\uC790\\uB3D9 \\uB85C\\uD14C\\uC774\\uC158\")),\n geminiApiKeys.map((key, index) => (React.createElement(\"div\", { key: index, className: \"jp-agent-settings-key-row\", style: {\n display: 'flex',\n gap: '8px',\n marginBottom: '8px',\n alignItems: 'center',\n padding: '4px',\n borderRadius: '4px',\n background: activeKeyIndex === index && key && key.trim() ? 'rgba(66, 133, 244, 0.1)' : 'transparent',\n border: activeKeyIndex === index && key && key.trim() ? '1px solid rgba(66, 133, 244, 0.3)' : '1px solid transparent'\n } },\n React.createElement(\"input\", { type: \"radio\", name: \"activeKey\", checked: activeKeyIndex === index && key && key.trim() !== '', onChange: () => handleSetActiveKey(index), disabled: !key || !key.trim(), title: \"\\uD65C\\uC131 \\uD0A4\\uB85C \\uC124\\uC815\", style: { cursor: key && key.trim() ? 'pointer' : 'default' } }),\n React.createElement(\"span\", { style: {\n minWidth: '20px',\n color: '#666',\n fontSize: '12px'\n } },\n index + 1,\n \".\"),\n React.createElement(\"input\", { type: \"password\", className: \"jp-agent-settings-input\", style: { flex: 1 }, value: key, onChange: (e) => handleKeyChange(index, e.target.value), placeholder: \"AIza...\" }),\n getTestStatusIcon(index) && (React.createElement(\"span\", { style: { fontSize: '14px', minWidth: '20px' } }, getTestStatusIcon(index))),\n geminiApiKeys.length > 1 && (React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button-icon\", onClick: () => handleRemoveKey(index), title: \"\\uD0A4 \\uC0AD\\uC81C\", style: {\n padding: '4px 8px',\n background: '#ff4444',\n color: 'white',\n border: 'none',\n borderRadius: '4px',\n cursor: 'pointer'\n } }, \"\\u00D7\"))))),\n geminiApiKeys.length < 10 && (React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary\", onClick: handleAddKey, style: { marginTop: '8px' } }, \"+ \\uD0A4 \\uCD94\\uAC00\"))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uBAA8\\uB378\"),\n React.createElement(\"select\", { className: \"jp-agent-settings-select\", value: geminiModel, onChange: (e) => setGeminiModel(e.target.value) },\n React.createElement(\"option\", { value: \"gemini-2.5-flash\" }, \"Gemini 2.5 Flash\"),\n React.createElement(\"option\", { value: \"gemini-2.5-pro\" }, \"Gemini 2.5 Pro\"))))),\n provider === 'vllm' && (React.createElement(\"div\", { className: \"jp-agent-settings-provider\" },\n React.createElement(\"h3\", null, \"vLLM / OpenAI Compatible \\uC124\\uC815\"),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"API Base URL (\\uC804\\uCCB4 \\uACBD\\uB85C \\uC785\\uB825)\"),\n React.createElement(\"input\", { type: \"text\", className: \"jp-agent-settings-input\", value: vllmEndpoint, onChange: (e) => setVllmEndpoint(e.target.value), placeholder: \"https://openrouter.ai/api/v1\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"API \\uD0A4 (\\uC120\\uD0DD\\uC0AC\\uD56D)\"),\n React.createElement(\"input\", { type: \"password\", className: \"jp-agent-settings-input\", value: vllmApiKey, onChange: (e) => setVllmApiKey(e.target.value), placeholder: \"API \\uD0A4\\uAC00 \\uD544\\uC694\\uD55C \\uACBD\\uC6B0 \\uC785\\uB825\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uBAA8\\uB378 \\uC774\\uB984\"),\n React.createElement(\"input\", { type: \"text\", className: \"jp-agent-settings-input\", value: vllmModel, onChange: (e) => setVllmModel(e.target.value), placeholder: \"meta-llama/Llama-2-7b-chat-hf\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"Temperature\"),\n React.createElement(\"input\", { type: \"number\", className: \"jp-agent-settings-input\", value: vllmTemperature, onChange: (e) => setVllmTemperature(Math.max(0, Math.min(2, parseFloat(e.target.value) || 0))), min: 0, max: 2, step: 0.1, style: { width: '100px' } }),\n React.createElement(\"small\", { style: { color: '#666', marginLeft: '8px' } }, \"0.0 = \\uACB0\\uC815\\uC801, 1.0+ = \\uCC3D\\uC758\\uC801\")),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-checkbox\" },\n React.createElement(\"input\", { type: \"checkbox\", checked: vllmUseResponsesApi, onChange: (e) => setVllmUseResponsesApi(e.target.checked) }),\n React.createElement(\"span\", null, \"Use Responses API (/v1/responses)\")),\n React.createElement(\"small\", { style: { color: '#666', display: 'block', marginTop: '4px' } }, \"OpenAI Responses API\\uB97C \\uC9C0\\uC6D0\\uD558\\uB294 \\uC5D4\\uB4DC\\uD3EC\\uC778\\uD2B8\\uC5D0\\uC11C \\uC0AC\\uC6A9\")))),\n provider === 'openai' && (React.createElement(\"div\", { className: \"jp-agent-settings-provider\" },\n React.createElement(\"h3\", null, \"OpenAI \\uC124\\uC815\"),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"API \\uD0A4\"),\n React.createElement(\"input\", { type: \"password\", className: \"jp-agent-settings-input\", value: openaiApiKey, onChange: (e) => setOpenaiApiKey(e.target.value), placeholder: \"sk-...\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uBAA8\\uB378\"),\n React.createElement(\"select\", { className: \"jp-agent-settings-select\", value: openaiModel, onChange: (e) => setOpenaiModel(e.target.value) },\n React.createElement(\"option\", { value: \"gpt-4\" }, \"GPT-4\"),\n React.createElement(\"option\", { value: \"gpt-4-turbo\" }, \"GPT-4 Turbo\"),\n React.createElement(\"option\", { value: \"gpt-3.5-turbo\" }, \"GPT-3.5 Turbo\"))))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uC790\\uB3D9 \\uC2E4\\uD589 \\uC2B9\\uC778\"),\n React.createElement(\"label\", { className: \"jp-agent-settings-checkbox\" },\n React.createElement(\"input\", { type: \"checkbox\", checked: autoApprove, onChange: (e) => setAutoApprove(e.target.checked), \"data-testid\": \"auto-approve-checkbox\" }),\n React.createElement(\"span\", null, \"\\uC2B9\\uC778 \\uC5C6\\uC774 \\uBC14\\uB85C \\uC2E4\\uD589 (\\uCF54\\uB4DC/\\uD30C\\uC77C/\\uC178 \\uD3EC\\uD568)\"))),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\", htmlFor: \"jp-agent-idle-timeout\" },\n \"Idle Timeout (\\uBD84)\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"\\uBE44\\uD65C\\uB3D9 \\uC2DC \\uC790\\uB3D9 \\uC885\\uB8CC (0 = \\uBE44\\uD65C\\uC131\\uD654)\")),\n React.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\" })),\n React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" }, \"\\uC5D0\\uC774\\uC804\\uD2B8 \\uBAA8\\uB4DC\"),\n React.createElement(\"select\", { className: \"jp-agent-settings-select\", value: agentMode, onChange: (e) => setAgentMode(e.target.value) },\n React.createElement(\"option\", { value: \"single\" }, \"Single Agent (\\uBAA8\\uB4E0 \\uB3C4\\uAD6C \\uD1B5\\uD569)\"),\n React.createElement(\"option\", { value: \"multi\" }, \"Multi-Agent (Planner + Subagents)\")),\n React.createElement(\"small\", { style: { color: '#666', marginTop: '4px', display: 'block' } }, agentMode === 'single'\n ? '단일 에이전트가 모든 작업을 처리합니다.'\n : 'Planner가 Python Developer, Researcher 등 전문 에이전트에게 작업을 위임합니다.')),\n agentMode === 'single' && (React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" },\n \"System Prompt\",\n React.createElement(\"small\", { style: { fontWeight: 'normal', marginLeft: '8px', color: '#666' } }, \"\\uB2E8\\uC77C \\uC5D0\\uC774\\uC804\\uD2B8\\uC5D0 \\uC801\\uC6A9\\uB429\\uB2C8\\uB2E4.\")),\n React.createElement(\"textarea\", { className: \"jp-agent-settings-input jp-agent-settings-textarea\", value: systemPrompt, onChange: (e) => setSystemPrompt(e.target.value), rows: 8 }),\n React.createElement(\"div\", { className: \"jp-agent-settings-inline-actions\" },\n React.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 ? '로딩중...' : '기본값으로 되돌리기')))),\n agentMode === 'multi' && (React.createElement(\"div\", { className: \"jp-agent-settings-group\" },\n React.createElement(\"label\", { className: \"jp-agent-settings-label\" },\n \"\\uC5D0\\uC774\\uC804\\uD2B8\\uBCC4 System Prompt\",\n React.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.\")),\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-section\", style: { marginTop: '12px', border: '1px solid #ddd', borderRadius: '4px' } },\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-header\", style: {\n padding: '8px 12px',\n background: '#f5f5f5',\n cursor: 'pointer',\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }, onClick: () => setExpandedAgents(prev => ({ ...prev, planner: !prev.planner })) },\n React.createElement(\"span\", { style: { fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '6px' } },\n React.createElement(GpsFixedIcon, { sx: { fontSize: 18 } }),\n \" Planner (Supervisor)\"),\n React.createElement(\"span\", null, expandedAgents.planner ? React.createElement(ExpandMoreIcon, { sx: { fontSize: 18 } }) : React.createElement(ChevronRightIcon, { sx: { fontSize: 18 } }))),\n expandedAgents.planner && (React.createElement(\"div\", { style: { padding: '12px' } },\n React.createElement(\"small\", { style: { color: '#666', display: 'block', marginBottom: '8px' } }, \"\\uC791\\uC5C5 \\uACC4\\uD68D \\uBC0F \\uC11C\\uBE0C\\uC5D0\\uC774\\uC804\\uD2B8 \\uC704\\uC784\\uC744 \\uB2F4\\uB2F9\\uD569\\uB2C8\\uB2E4.\"),\n React.createElement(\"textarea\", { className: \"jp-agent-settings-input jp-agent-settings-textarea\", value: agentPrompts.planner || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, planner: e.target.value })), rows: 6 }),\n React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary jp-agent-settings-button-compact\", style: { marginTop: '8px' }, onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, planner: defaultPrompts.planner })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값으로 되돌리기')))),\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-section\", style: { marginTop: '8px', border: '1px solid #ddd', borderRadius: '4px' } },\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-header\", style: {\n padding: '8px 12px',\n background: '#f5f5f5',\n cursor: 'pointer',\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }, onClick: () => setExpandedAgents(prev => ({ ...prev, python_developer: !prev.python_developer })) },\n React.createElement(\"span\", { style: { fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '6px' } },\n React.createElement(CodeIcon, { sx: { fontSize: 18 } }),\n \" Python Developer\"),\n React.createElement(\"span\", null, expandedAgents.python_developer ? React.createElement(ExpandMoreIcon, { sx: { fontSize: 18 } }) : React.createElement(ChevronRightIcon, { sx: { fontSize: 18 } }))),\n expandedAgents.python_developer && (React.createElement(\"div\", { style: { padding: '12px' } },\n React.createElement(\"small\", { style: { color: '#666', display: 'block', marginBottom: '8px' } }, \"Python \\uCF54\\uB4DC \\uC2E4\\uD589, \\uB370\\uC774\\uD130 \\uBD84\\uC11D, \\uC2DC\\uAC01\\uD654, ML \\uBAA8\\uB378\\uB9C1\\uC744 \\uB2F4\\uB2F9\\uD569\\uB2C8\\uB2E4.\"),\n React.createElement(\"textarea\", { className: \"jp-agent-settings-input jp-agent-settings-textarea\", value: agentPrompts.python_developer || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, python_developer: e.target.value })), rows: 6 }),\n React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary jp-agent-settings-button-compact\", style: { marginTop: '8px' }, onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, python_developer: defaultPrompts.python_developer })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값으로 되돌리기')))),\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-section\", style: { marginTop: '8px', border: '1px solid #ddd', borderRadius: '4px' } },\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-header\", style: {\n padding: '8px 12px',\n background: '#f5f5f5',\n cursor: 'pointer',\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }, onClick: () => setExpandedAgents(prev => ({ ...prev, researcher: !prev.researcher })) },\n React.createElement(\"span\", { style: { fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '6px' } },\n React.createElement(SearchIcon, { sx: { fontSize: 18 } }),\n \" Researcher\"),\n React.createElement(\"span\", null, expandedAgents.researcher ? React.createElement(ExpandMoreIcon, { sx: { fontSize: 18 } }) : React.createElement(ChevronRightIcon, { sx: { fontSize: 18 } }))),\n expandedAgents.researcher && (React.createElement(\"div\", { style: { padding: '12px' } },\n React.createElement(\"small\", { style: { color: '#666', display: 'block', marginBottom: '8px' } }, \"\\uD30C\\uC77C \\uAC80\\uC0C9, \\uCF54\\uB4DC \\uBD84\\uC11D, Qdrant RAG \\uAC80\\uC0C9\\uC744 \\uB2F4\\uB2F9\\uD569\\uB2C8\\uB2E4 (READ-ONLY).\"),\n React.createElement(\"textarea\", { className: \"jp-agent-settings-input jp-agent-settings-textarea\", value: agentPrompts.researcher || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, researcher: e.target.value })), rows: 6 }),\n React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary jp-agent-settings-button-compact\", style: { marginTop: '8px' }, onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, researcher: defaultPrompts.researcher })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값으로 되돌리기')))),\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-section\", style: { marginTop: '8px', border: '1px solid #ddd', borderRadius: '4px' } },\n React.createElement(\"div\", { className: \"jp-agent-settings-agent-header\", style: {\n padding: '8px 12px',\n background: '#f5f5f5',\n cursor: 'pointer',\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }, onClick: () => setExpandedAgents(prev => ({ ...prev, athena_query: !prev.athena_query })) },\n React.createElement(\"span\", { style: { fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '6px' } },\n React.createElement(AnalyticsIcon, { sx: { fontSize: 18 } }),\n \" Athena Query\"),\n React.createElement(\"span\", null, expandedAgents.athena_query ? React.createElement(ExpandMoreIcon, { sx: { fontSize: 18 } }) : React.createElement(ChevronRightIcon, { sx: { fontSize: 18 } }))),\n expandedAgents.athena_query && (React.createElement(\"div\", { style: { padding: '12px' } },\n React.createElement(\"small\", { style: { color: '#666', display: 'block', marginBottom: '8px' } }, \"Qdrant RAG\\uB97C \\uD65C\\uC6A9\\uD55C Athena SQL \\uCFFC\\uB9AC \\uC0DD\\uC131\\uC744 \\uB2F4\\uB2F9\\uD569\\uB2C8\\uB2E4 (Python Developer\\uC5D0\\uC11C \\uD638\\uCD9C).\"),\n React.createElement(\"textarea\", { className: \"jp-agent-settings-input jp-agent-settings-textarea\", value: agentPrompts.athena_query || '', onChange: (e) => setAgentPrompts(prev => ({ ...prev, athena_query: e.target.value })), rows: 6 }),\n React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary jp-agent-settings-button-compact\", style: { marginTop: '8px' }, onClick: () => defaultPrompts && setAgentPrompts(prev => ({ ...prev, athena_query: defaultPrompts.athena_query })), disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '기본값으로 되돌리기')))),\n React.createElement(\"div\", { style: { marginTop: '12px' } },\n React.createElement(\"button\", { type: \"button\", className: \"jp-agent-settings-button jp-agent-settings-button-secondary\", onClick: () => {\n if (defaultPrompts) {\n // Reset single-mode prompt\n setSystemPrompt(defaultPrompts.single);\n // Reset multi-agent prompts\n setAgentPrompts({\n planner: defaultPrompts.planner,\n python_developer: defaultPrompts.python_developer,\n researcher: defaultPrompts.researcher,\n athena_query: defaultPrompts.athena_query,\n });\n }\n }, disabled: isLoadingPrompts }, isLoadingPrompts ? '로딩중...' : '모든 프롬프트 기본값으로 되돌리기'))))),\n React.createElement(\"div\", { className: \"jp-agent-settings-footer\" },\n React.createElement(\"button\", { className: \"jp-agent-settings-button jp-agent-settings-button-secondary\", onClick: onClose }, \"\\uCDE8\\uC18C\"),\n React.createElement(\"button\", { className: \"jp-agent-settings-button jp-agent-settings-button-test\", onClick: handleTest, disabled: isTesting }, isTesting ? '테스트 중...' : 'API 테스트'),\n React.createElement(\"button\", { className: \"jp-agent-settings-button jp-agent-settings-button-primary\", onClick: handleSave }, \"\\uC800\\uC7A5\")))));\n};\n","/**\n * StreamingMessage - A component that renders markdown with streaming support\n *\n * Phase 4 Implementation:\n * - Uses JupyterLab Rendermime when available for native rendering\n * - Falls back to custom formatMarkdownToHtml for compatibility\n * - Debounced rendering to reduce flickering during streaming\n * - Writing indicator while content is being generated\n * - Code block toolbar with copy/insert functionality\n */\nimport React, { useMemo, useEffect, useRef, useState, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useStreamingMarkdown } from '../hooks/useStreamingMarkdown';\nimport { CodeToolbar } from './code-blocks';\nconst MD_MIME_TYPE = 'text/markdown';\n/**\n * Get the global rendermime registry if available\n */\nfunction getGlobalRenderMimeRegistry() {\n return window._hdspRenderMimeRegistry || null;\n}\n/**\n * Writing indicator component shown during streaming\n */\nconst WritingIndicator = ({ hasContent }) => (React.createElement(\"div\", { className: \"jp-streaming-indicator\" },\n React.createElement(\"div\", { className: \"jp-streaming-dots\" },\n React.createElement(\"span\", { className: \"jp-streaming-dot\" }),\n React.createElement(\"span\", { className: \"jp-streaming-dot\" }),\n React.createElement(\"span\", { className: \"jp-streaming-dot\" })),\n !hasContent && (React.createElement(\"span\", { className: \"jp-streaming-text\" }, \"AI\\uAC00 \\uC751\\uB2F5\\uC744 \\uC0DD\\uC131\\uD558\\uACE0 \\uC788\\uC2B5\\uB2C8\\uB2E4...\"))));\n/**\n * Escape LaTeX delimiters for proper rendering with Rendermime\n */\nfunction escapeLatexDelimiters(text) {\n return text\n .replace(/\\\\\\(/g, '\\\\\\\\(')\n .replace(/\\\\\\)/g, '\\\\\\\\)')\n .replace(/\\\\\\[/g, '\\\\\\\\[')\n .replace(/\\\\\\]/g, '\\\\\\\\]');\n}\n/**\n * Close incomplete markdown blocks for streaming\n */\nfunction closeIncompleteBlocks(content) {\n let result = content;\n // Close unclosed code blocks\n const codeBlockMatches = result.match(/```/g);\n const codeBlockCount = codeBlockMatches ? codeBlockMatches.length : 0;\n if (codeBlockCount % 2 !== 0) {\n result += '\\n```';\n }\n return result;\n}\n/**\n * Check if content has incomplete code blocks\n */\nfunction hasIncompleteCodeBlock(content) {\n const codeBlockMatches = content.match(/```/g);\n const codeBlockCount = codeBlockMatches ? codeBlockMatches.length : 0;\n return codeBlockCount % 2 !== 0;\n}\n/**\n * Extract language from a code block's class name\n */\nfunction extractLanguageFromClass(className) {\n // Look for language-* class\n const match = className.match(/language-(\\w+)/);\n if (match) {\n return match[1];\n }\n // Also try hljs class format\n const hljsMatch = className.match(/hljs\\s+(\\w+)/);\n if (hljsMatch) {\n return hljsMatch[1];\n }\n return undefined;\n}\n/**\n * StreamingMessage component with hybrid rendering and code toolbars\n */\nexport const StreamingMessage = ({ content, isStreaming = false, className = '', debounceMs = 50, onClick, useRendermime = true, onInsertAbove, onInsertBelow, onInsertAsCell, cellActionsEnabled = true, }) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const lastContentRef = useRef('');\n const [codeToolbarDefs, setCodeToolbarDefs] = useState([]);\n // Synchronously determine renderer type (useMemo instead of useState+useEffect)\n // Use fallback renderer for summary JSON (native renderer doesn't parse it)\n const useNativeRenderer = useMemo(() => {\n const rmRegistry = getGlobalRenderMimeRegistry();\n const hasSummaryJson = content && content.includes('\"summary\"') && content.includes('\"next_items\"');\n const result = useRendermime && rmRegistry !== null && !hasSummaryJson;\n console.log('[StreamingMessage] useNativeRenderer (useMemo):', {\n hasSummaryJson,\n hasRegistry: rmRegistry !== null,\n result,\n contentLength: content?.length,\n });\n return result;\n }, [useRendermime, content]);\n // Use custom hook for fallback rendering\n const { renderedHtml, isRendering: isFallbackRendering, hasIncompleteBlocks, } = useStreamingMarkdown(content, {\n debounceMs,\n isStreaming,\n forceImmediate: !isStreaming,\n });\n /**\n * Attach code toolbars to all pre blocks in the rendered content\n */\n const attachCodeToolbars = useCallback((contentDiv) => {\n // Clean up old toolbar roots\n codeToolbarDefs.forEach(def => {\n if (def.root.parentNode) {\n def.root.parentNode.removeChild(def.root);\n }\n });\n const preBlocks = contentDiv.querySelectorAll('pre');\n const newDefs = [];\n preBlocks.forEach((preBlock) => {\n // Skip pre blocks that are already inside our custom code-block-container\n // (formatMarkdownToHtml already renders its own toolbar in code-block-header)\n if (preBlock.closest('.code-block-container')) {\n return;\n }\n // Create toolbar root element\n const toolbarRoot = document.createElement('div');\n toolbarRoot.className = 'jp-hdsp-code-toolbar-container';\n // Insert toolbar after the pre block\n preBlock.parentNode?.insertBefore(toolbarRoot, preBlock.nextSibling);\n // Try to extract language from code element class\n const codeElement = preBlock.querySelector('code');\n const language = codeElement\n ? extractLanguageFromClass(codeElement.className)\n : undefined;\n newDefs.push({\n root: toolbarRoot,\n content: preBlock.textContent || '',\n language\n });\n });\n setCodeToolbarDefs(newDefs);\n }, []);\n // Render content using Rendermime (native JupyterLab renderer)\n const renderWithRendermime = useCallback(async (contentToRender) => {\n const rmRegistry = getGlobalRenderMimeRegistry();\n if (!rmRegistry || !containerRef.current) {\n return false;\n }\n // Skip if content hasn't changed\n if (contentToRender === lastContentRef.current) {\n return true;\n }\n lastContentRef.current = contentToRender;\n try {\n // Process content for streaming\n let processedContent = contentToRender;\n if (isStreaming && hasIncompleteCodeBlock(processedContent)) {\n processedContent = closeIncompleteBlocks(processedContent);\n }\n // Escape LaTeX delimiters\n const mdStr = escapeLatexDelimiters(processedContent);\n // Create mime model\n const model = rmRegistry.createModel({\n data: { [MD_MIME_TYPE]: mdStr }\n });\n // Create or reuse renderer\n if (!rendererRef.current) {\n rendererRef.current = rmRegistry.createRenderer(MD_MIME_TYPE);\n }\n const renderer = rendererRef.current;\n // Render markdown\n await renderer.renderModel(model);\n if (renderer.node && containerRef.current) {\n // Find the content div\n const contentDiv = containerRef.current.querySelector('.jp-streaming-content');\n if (contentDiv) {\n contentDiv.innerHTML = '';\n contentDiv.appendChild(renderer.node.cloneNode(true));\n // Apply LaTeX typesetting if available\n if (rmRegistry.latexTypesetter) {\n rmRegistry.latexTypesetter.typeset(contentDiv);\n }\n // Attach code toolbars (only when not streaming for performance)\n if (!isStreaming) {\n attachCodeToolbars(contentDiv);\n }\n }\n }\n return true;\n }\n catch (error) {\n console.warn('[StreamingMessage] Rendermime error, falling back:', error);\n return false;\n }\n }, [isStreaming, attachCodeToolbars]);\n // Effect for rendering with Rendermime\n useEffect(() => {\n if (!useNativeRenderer || !content) {\n return;\n }\n // Debounce during streaming\n let timeoutId = null;\n if (isStreaming) {\n timeoutId = setTimeout(() => {\n void renderWithRendermime(content);\n }, debounceMs);\n }\n else {\n void renderWithRendermime(content);\n }\n return () => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n };\n }, [content, isStreaming, useNativeRenderer, debounceMs, renderWithRendermime]);\n // Attach toolbars when streaming stops (only for native renderer)\n // Note: Fallback renderer has its own code-block-header with buttons\n useEffect(() => {\n if (useNativeRenderer && !isStreaming && content && containerRef.current) {\n const contentDiv = containerRef.current.querySelector('.jp-streaming-content');\n if (contentDiv) {\n // Small delay to ensure DOM is updated\n setTimeout(() => {\n attachCodeToolbars(contentDiv);\n }, 100);\n }\n }\n }, [isStreaming, content, attachCodeToolbars, useNativeRenderer]);\n // Attach toolbars for fallback renderer when content changes\n useEffect(() => {\n if (!useNativeRenderer && renderedHtml && !isStreaming && containerRef.current) {\n const contentDiv = containerRef.current.querySelector('.jp-streaming-content');\n if (contentDiv) {\n setTimeout(() => {\n attachCodeToolbars(contentDiv);\n }, 100);\n }\n }\n }, [useNativeRenderer, renderedHtml, isStreaming, attachCodeToolbars]);\n // Cleanup renderer and toolbar roots on unmount\n useEffect(() => {\n return () => {\n if (rendererRef.current && rendererRef.current.dispose) {\n rendererRef.current.dispose();\n rendererRef.current = null;\n }\n // Clean up toolbar roots\n codeToolbarDefs.forEach(def => {\n if (def.root.parentNode) {\n def.root.parentNode.removeChild(def.root);\n }\n });\n };\n }, []);\n // Determine what to show\n const showIndicator = isStreaming && !content;\n const showContent = content && (useNativeRenderer || renderedHtml);\n const isRendering = isFallbackRendering;\n // Debug: Log render state for summary JSON\n const hasSummaryJsonContent = content && content.includes('\"summary\"') && content.includes('\"next_items\"');\n if (hasSummaryJsonContent) {\n console.log('[StreamingMessage] Render state for summary JSON:', {\n useNativeRenderer,\n hasRenderedHtml: !!renderedHtml,\n renderedHtmlPreview: renderedHtml?.slice(0, 100),\n showContent,\n contentLength: content?.length,\n });\n }\n // Combine class names\n const containerClassName = useMemo(() => {\n const classes = ['jp-RenderedHTMLCommon', 'jp-streaming-message'];\n if (className)\n classes.push(className);\n if (isStreaming)\n classes.push('streaming');\n if (hasIncompleteBlocks)\n classes.push('incomplete');\n if (useNativeRenderer)\n classes.push('jp-rendermime-native');\n return classes.join(' ');\n }, [className, isStreaming, hasIncompleteBlocks, useNativeRenderer]);\n return (React.createElement(\"div\", { ref: containerRef, className: containerClassName, style: { padding: '0 5px' } },\n showIndicator && React.createElement(WritingIndicator, { hasContent: !!content }),\n showContent && (React.createElement(\"div\", { className: \"jp-streaming-content\", ...(!useNativeRenderer && renderedHtml ? {\n dangerouslySetInnerHTML: { __html: renderedHtml }\n } : {}), onClick: onClick })),\n isStreaming && content && !isRendering && (React.createElement(\"div\", { className: \"jp-streaming-cursor\" })),\n !isStreaming && codeToolbarDefs.map((def, index) => (createPortal(React.createElement(CodeToolbar, { key: index, content: def.content, language: def.language, onInsertAbove: onInsertAbove, onInsertBelow: onInsertBelow, onInsertAsCell: onInsertAsCell, cellActionsEnabled: cellActionsEnabled }), def.root)))));\n};\nexport default StreamingMessage;\n","/**\n * Task Progress Widget Component\n * Floating widget showing notebook generation progress\n */\nimport React, { useState } from 'react';\nimport { Card, CardContent, Typography, LinearProgress, Box, IconButton, Collapse, Chip, Button } from '@mui/material';\nimport CloseIcon from '@mui/icons-material/Close';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport ExpandLessIcon from '@mui/icons-material/ExpandLess';\nimport CheckCircleIcon from '@mui/icons-material/CheckCircle';\nimport ErrorIcon from '@mui/icons-material/Error';\nimport CancelIcon from '@mui/icons-material/Cancel';\nexport const TaskProgressWidget = ({ taskStatus, onClose, onCancel, onOpenNotebook }) => {\n const [expanded, setExpanded] = useState(true);\n const [showDetails, setShowDetails] = useState(false);\n const { status, progress, message, prompt, error, notebookPath } = taskStatus;\n const isComplete = status === 'completed';\n const isFailed = status === 'failed';\n const isCancelled = status === 'cancelled';\n const isRunning = status === 'running';\n const isDone = isComplete || isFailed || isCancelled;\n // Helper: Map status to color\n const getStatusColor = (currentStatus) => {\n switch (currentStatus) {\n case 'completed': return 'success';\n case 'failed': return 'error';\n case 'cancelled': return 'default';\n default: return 'primary';\n }\n };\n // Helper: Map status to icon\n const getStatusIcon = (currentStatus) => {\n switch (currentStatus) {\n case 'completed': return React.createElement(CheckCircleIcon, { fontSize: \"small\" });\n case 'failed': return React.createElement(ErrorIcon, { fontSize: \"small\" });\n case 'cancelled': return React.createElement(CancelIcon, { fontSize: \"small\" });\n default: return null;\n }\n };\n // Helper: Map status to Korean text\n const getStatusText = (currentStatus) => {\n const statusTextMap = {\n 'pending': '대기 중',\n 'running': '생성 중',\n 'completed': '완료',\n 'failed': '실패',\n 'cancelled': '취소됨'\n };\n return statusTextMap[currentStatus] || currentStatus;\n };\n return (React.createElement(Card, { sx: {\n position: 'fixed',\n bottom: 20,\n right: 20,\n width: expanded ? 380 : 320,\n maxWidth: '90vw',\n boxShadow: 3,\n zIndex: 1300,\n transition: 'all 0.3s ease'\n } },\n React.createElement(CardContent, { sx: { padding: 2, '&:last-child': { paddingBottom: 2 } } },\n React.createElement(Box, { display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", mb: 1 },\n React.createElement(Box, { display: \"flex\", alignItems: \"center\", gap: 1 },\n React.createElement(Typography, { variant: \"subtitle2\", fontWeight: \"bold\" }, \"\\uB178\\uD2B8\\uBD81 \\uC0DD\\uC131\"),\n React.createElement(Chip, { label: getStatusText(status), size: \"small\", color: getStatusColor(status), icon: getStatusIcon(status) || undefined })),\n React.createElement(Box, { display: \"flex\" },\n React.createElement(IconButton, { size: \"small\", onClick: () => setShowDetails(!showDetails) }, showDetails ? (React.createElement(ExpandLessIcon, { fontSize: \"small\" })) : (React.createElement(ExpandMoreIcon, { fontSize: \"small\" }))),\n React.createElement(IconButton, { size: \"small\", onClick: onClose },\n React.createElement(CloseIcon, { fontSize: \"small\" })))),\n !isDone && (React.createElement(Box, { mb: 2 },\n React.createElement(LinearProgress, { variant: \"determinate\", value: progress, sx: { height: 6, borderRadius: 1 } }),\n React.createElement(Box, { display: \"flex\", justifyContent: \"space-between\", mt: 0.5 },\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\" }, message),\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\" },\n progress,\n \"%\")))),\n isComplete && (React.createElement(Box, { mb: 2 },\n React.createElement(Typography, { variant: \"body2\", color: \"success.main\", sx: { display: 'flex', alignItems: 'center', gap: 0.5 } },\n React.createElement(CheckCircleIcon, { fontSize: \"small\" }),\n \" \",\n message))),\n isFailed && error && (React.createElement(Box, { mb: 2 },\n React.createElement(Typography, { variant: \"body2\", color: \"error.main\", sx: { display: 'flex', alignItems: 'center', gap: 0.5 } },\n React.createElement(ErrorIcon, { fontSize: \"small\" }),\n \" \",\n error))),\n React.createElement(Collapse, { in: showDetails },\n React.createElement(Box, { sx: {\n backgroundColor: 'action.hover',\n borderRadius: 1,\n padding: 1.5,\n mb: 2\n } },\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\", display: \"block\", mb: 0.5 }, \"\\uD504\\uB86C\\uD504\\uD2B8:\"),\n React.createElement(Typography, { variant: \"body2\", sx: { wordBreak: 'break-word' } }, prompt),\n notebookPath && (React.createElement(React.Fragment, null,\n React.createElement(Typography, { variant: \"caption\", color: \"text.secondary\", display: \"block\", mt: 1, mb: 0.5 }, \"\\uC800\\uC7A5 \\uC704\\uCE58:\"),\n React.createElement(Typography, { variant: \"caption\", sx: { wordBreak: 'break-all', fontFamily: 'monospace' } }, notebookPath))))),\n React.createElement(Box, { display: \"flex\", gap: 1, justifyContent: \"flex-end\" },\n isRunning && (React.createElement(Button, { size: \"small\", variant: \"outlined\", onClick: onCancel }, \"\\uCDE8\\uC18C\")),\n isComplete && notebookPath && (React.createElement(Button, { size: \"small\", variant: \"contained\", onClick: onOpenNotebook }, \"\\uB178\\uD2B8\\uBD81 \\uC5F4\\uAE30\")),\n isDone && (React.createElement(Button, { size: \"small\", variant: \"text\", onClick: onClose }, \"\\uB2EB\\uAE30\"))))));\n};\n","/**\n * CodeToolbar - Toolbar for code blocks with copy/insert functionality\n * Based on jupyter-chat implementation\n */\nimport React, { useCallback } from 'react';\nimport { Box, IconButton, Tooltip, Divider } from '@mui/material';\nimport VerticalAlignTopIcon from '@mui/icons-material/VerticalAlignTop';\nimport VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom';\nimport PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';\nimport { CopyButton } from './CopyButton';\nconst CODE_TOOLBAR_CLASS = 'jp-hdsp-code-toolbar';\nexport function CodeToolbar({ content, language, onInsertAbove, onInsertBelow, onInsertAsCell, cellActionsEnabled = true }) {\n const handleInsertAbove = useCallback(() => {\n onInsertAbove?.(content);\n }, [content, onInsertAbove]);\n const handleInsertBelow = useCallback(() => {\n onInsertBelow?.(content);\n }, [content, onInsertBelow]);\n const handleInsertAsCell = useCallback(() => {\n onInsertAsCell?.(content);\n }, [content, onInsertAsCell]);\n const canInsert = cellActionsEnabled && (onInsertAbove || onInsertBelow || onInsertAsCell);\n return (React.createElement(Box, { className: CODE_TOOLBAR_CLASS, sx: {\n display: 'flex',\n justifyContent: 'flex-end',\n alignItems: 'center',\n gap: '2px',\n padding: '2px 4px',\n backgroundColor: 'var(--jp-layout-color2)',\n borderBottomLeftRadius: '4px',\n borderBottomRightRadius: '4px',\n border: '1px solid var(--jp-border-color1)',\n borderTop: 'none',\n marginBottom: '0.5em'\n } },\n language && (React.createElement(Box, { component: \"span\", sx: {\n fontSize: '10px',\n color: 'var(--jp-ui-font-color2)',\n marginRight: 'auto',\n paddingLeft: '4px',\n fontFamily: 'var(--jp-code-font-family)',\n textTransform: 'lowercase'\n } }, language)),\n canInsert && (React.createElement(React.Fragment, null,\n onInsertAbove && (React.createElement(Tooltip, { title: \"\\uD65C\\uC131 \\uC140 \\uC704\\uC5D0 \\uC0BD\\uC785\", placement: \"top\", arrow: true },\n React.createElement(\"span\", null,\n React.createElement(IconButton, { size: \"small\", onClick: handleInsertAbove, disabled: !cellActionsEnabled, sx: { padding: '4px' }, \"aria-label\": \"Insert above active cell\" },\n React.createElement(VerticalAlignTopIcon, { fontSize: \"small\" }))))),\n onInsertBelow && (React.createElement(Tooltip, { title: \"\\uD65C\\uC131 \\uC140 \\uC544\\uB798\\uC5D0 \\uC0BD\\uC785\", placement: \"top\", arrow: true },\n React.createElement(\"span\", null,\n React.createElement(IconButton, { size: \"small\", onClick: handleInsertBelow, disabled: !cellActionsEnabled, sx: { padding: '4px' }, \"aria-label\": \"Insert below active cell\" },\n React.createElement(VerticalAlignBottomIcon, { fontSize: \"small\" }))))),\n onInsertAsCell && (React.createElement(Tooltip, { title: \"\\uC0C8 \\uC140\\uB85C \\uCD94\\uAC00\", placement: \"top\", arrow: true },\n React.createElement(\"span\", null,\n React.createElement(IconButton, { size: \"small\", onClick: handleInsertAsCell, disabled: !cellActionsEnabled, sx: { padding: '4px' }, \"aria-label\": \"Insert as new cell\" },\n React.createElement(PlaylistAddIcon, { fontSize: \"small\" }))))),\n React.createElement(Divider, { orientation: \"vertical\", flexItem: true, sx: { mx: 0.5 } }))),\n React.createElement(CopyButton, { value: content })));\n}\nexport default CodeToolbar;\n","/**\n * CopyButton - Copy to clipboard button with status feedback\n * Based on jupyter-chat implementation\n */\nimport React, { useState, useCallback, useRef } from 'react';\nimport { IconButton, Tooltip } from '@mui/material';\nimport ContentCopyIcon from '@mui/icons-material/ContentCopy';\nimport CheckIcon from '@mui/icons-material/Check';\nvar CopyStatus;\n(function (CopyStatus) {\n CopyStatus[CopyStatus[\"None\"] = 0] = \"None\";\n CopyStatus[CopyStatus[\"Copying\"] = 1] = \"Copying\";\n CopyStatus[CopyStatus[\"Copied\"] = 2] = \"Copied\";\n CopyStatus[CopyStatus[\"Disabled\"] = 3] = \"Disabled\";\n})(CopyStatus || (CopyStatus = {}));\nconst COPYBTN_TEXT = {\n [CopyStatus.None]: '클립보드에 복사',\n [CopyStatus.Copying]: '복사 중...',\n [CopyStatus.Copied]: '복사됨!',\n [CopyStatus.Disabled]: '복사 기능 사용 불가'\n};\nexport function CopyButton({ value, className, size = 'small' }) {\n const isCopyDisabled = typeof navigator === 'undefined' || navigator.clipboard === undefined;\n const [copyStatus, setCopyStatus] = useState(isCopyDisabled ? CopyStatus.Disabled : CopyStatus.None);\n const timeoutId = useRef(null);\n const copy = useCallback(async () => {\n if (copyStatus === CopyStatus.Copying) {\n return;\n }\n try {\n await navigator.clipboard.writeText(value);\n }\n catch (err) {\n console.error('Failed to copy text:', err);\n setCopyStatus(CopyStatus.None);\n return;\n }\n setCopyStatus(CopyStatus.Copied);\n if (timeoutId.current) {\n clearTimeout(timeoutId.current);\n }\n timeoutId.current = window.setTimeout(() => setCopyStatus(CopyStatus.None), 1500);\n }, [copyStatus, value]);\n const tooltip = COPYBTN_TEXT[copyStatus];\n const isCopied = copyStatus === CopyStatus.Copied;\n return (React.createElement(Tooltip, { title: tooltip, placement: \"top\", arrow: true },\n React.createElement(\"span\", { className: className },\n React.createElement(IconButton, { size: size, disabled: isCopyDisabled, onClick: copy, \"aria-label\": \"Copy to clipboard\", sx: {\n padding: '4px',\n color: isCopied ? 'success.main' : 'inherit',\n '&:hover': {\n backgroundColor: 'action.hover'\n }\n } }, isCopied ? (React.createElement(CheckIcon, { fontSize: \"small\" })) : (React.createElement(ContentCopyIcon, { fontSize: \"small\" }))))));\n}\nexport default CopyButton;\n","/**\n * Code blocks components\n */\nexport { CopyButton } from './CopyButton';\nexport { CodeToolbar } from './CodeToolbar';\n","/**\n * useStreamingMarkdown - Advanced streaming markdown rendering\n *\n * Phase 2 Implementation:\n * 1. Comprehensive incomplete markdown detection\n * 2. Smart block closing for safe rendering\n * 3. Stable content extraction for smoother updates\n * 4. Debounced rendering with intelligent batching\n */\nimport { useState, useEffect, useRef, useMemo, useCallback } from 'react';\nimport { formatMarkdownToHtml } from '../utils/markdownRenderer';\n/**\n * Comprehensive detection of incomplete markdown structures\n */\nfunction analyzeIncompleteMarkdown(content) {\n const info = {\n hasUnclosedCodeBlock: false,\n hasUnclosedInlineCode: false,\n hasUnclosedBold: false,\n hasUnclosedItalic: false,\n hasUnclosedLink: false,\n hasUnclosedTable: false,\n lastStableIndex: content.length,\n };\n if (!content)\n return info;\n // 1. Check for unclosed fenced code blocks (```)\n const codeBlockMatches = content.match(/```/g);\n const codeBlockCount = codeBlockMatches ? codeBlockMatches.length : 0;\n info.hasUnclosedCodeBlock = codeBlockCount % 2 !== 0;\n // If we're inside a code block, find where it started\n if (info.hasUnclosedCodeBlock) {\n const lastCodeBlockStart = content.lastIndexOf('```');\n info.lastStableIndex = Math.min(info.lastStableIndex, lastCodeBlockStart);\n }\n // 2. Check for unclosed inline code in the last line (only matters outside code blocks)\n if (!info.hasUnclosedCodeBlock) {\n const lines = content.split('\\n');\n const lastLine = lines[lines.length - 1];\n // Count backticks that aren't part of code blocks\n let inlineBackticks = 0;\n let i = 0;\n while (i < lastLine.length) {\n if (lastLine[i] === '`') {\n // Check if it's a triple backtick (code block start)\n if (lastLine.substring(i, i + 3) === '```') {\n i += 3;\n continue;\n }\n inlineBackticks++;\n }\n i++;\n }\n info.hasUnclosedInlineCode = inlineBackticks % 2 !== 0;\n }\n // 3. Check for unclosed bold (**) in last line\n if (!info.hasUnclosedCodeBlock) {\n const lastLine = content.split('\\n').pop() || '';\n const boldMatches = lastLine.match(/\\*\\*/g);\n const boldCount = boldMatches ? boldMatches.length : 0;\n info.hasUnclosedBold = boldCount % 2 !== 0;\n }\n // 4. Check for unclosed italic (*) - more complex due to bold overlap\n if (!info.hasUnclosedCodeBlock && !info.hasUnclosedBold) {\n const lastLine = content.split('\\n').pop() || '';\n // Remove bold markers first, then count single asterisks\n const withoutBold = lastLine.replace(/\\*\\*/g, '');\n const italicMatches = withoutBold.match(/\\*/g);\n const italicCount = italicMatches ? italicMatches.length : 0;\n info.hasUnclosedItalic = italicCount % 2 !== 0;\n }\n // 5. Check for unclosed links [text](url) or [text][ref]\n const lastLine = content.split('\\n').pop() || '';\n const openBracket = lastLine.lastIndexOf('[');\n const closeBracket = lastLine.lastIndexOf(']');\n const openParen = lastLine.lastIndexOf('(');\n const closeParen = lastLine.lastIndexOf(')');\n if (openBracket > closeBracket) {\n // We have an unclosed [\n info.hasUnclosedLink = true;\n }\n else if (closeBracket > -1 && openParen > closeBracket && openParen > closeParen) {\n // We have [...]( but no closing )\n info.hasUnclosedLink = true;\n }\n // 6. Check for incomplete tables (line starts with | but doesn't end with |)\n const lines = content.split('\\n');\n const lastNonEmptyLine = lines.filter(l => l.trim()).pop() || '';\n if (lastNonEmptyLine.trim().startsWith('|') && !lastNonEmptyLine.trim().endsWith('|')) {\n info.hasUnclosedTable = true;\n }\n // Calculate last stable index based on all incomplete markers\n if (info.hasUnclosedInlineCode || info.hasUnclosedBold || info.hasUnclosedItalic || info.hasUnclosedLink) {\n // Find the start of the last line\n const lastNewline = content.lastIndexOf('\\n');\n info.lastStableIndex = Math.min(info.lastStableIndex, lastNewline > 0 ? lastNewline : 0);\n }\n return info;\n}\n/**\n * Detect if content has any incomplete blocks\n */\nfunction hasAnyIncompleteBlocks(info) {\n return (info.hasUnclosedCodeBlock ||\n info.hasUnclosedInlineCode ||\n info.hasUnclosedBold ||\n info.hasUnclosedItalic ||\n info.hasUnclosedLink ||\n info.hasUnclosedTable);\n}\n/**\n * Close incomplete markdown blocks for safer rendering during streaming\n */\nfunction closeIncompleteBlocks(content, info) {\n let result = content;\n // Close unclosed code blocks first (highest priority)\n if (info.hasUnclosedCodeBlock) {\n // Find the language hint if any\n const lastCodeBlockStart = result.lastIndexOf('```');\n const afterBackticks = result.substring(lastCodeBlockStart + 3);\n const firstNewline = afterBackticks.indexOf('\\n');\n // If there's content after the opening ```, add a closing\n if (firstNewline > -1 || afterBackticks.length > 0) {\n result += '\\n```';\n }\n return result; // Return early - code block content shouldn't be further processed\n }\n // Close inline code\n if (info.hasUnclosedInlineCode) {\n result += '`';\n }\n // Close bold\n if (info.hasUnclosedBold) {\n result += '**';\n }\n // Close italic\n if (info.hasUnclosedItalic) {\n result += '*';\n }\n // Close unclosed links - just close the bracket\n if (info.hasUnclosedLink) {\n const lastLine = result.split('\\n').pop() || '';\n const openBracket = lastLine.lastIndexOf('[');\n const closeBracket = lastLine.lastIndexOf(']');\n const openParen = lastLine.lastIndexOf('(');\n if (openBracket > closeBracket) {\n result += ']';\n }\n else if (openParen > closeBracket) {\n result += ')';\n }\n }\n // Close incomplete table row\n if (info.hasUnclosedTable) {\n result += ' |';\n }\n return result;\n}\n/**\n * Extract stable content that can be safely rendered\n * Returns content up to the last complete block/paragraph\n */\nfunction extractStableContent(content, info) {\n if (!hasAnyIncompleteBlocks(info)) {\n return { stableContent: content, unstableContent: '' };\n }\n // For code blocks, render everything with closing\n if (info.hasUnclosedCodeBlock) {\n return {\n stableContent: closeIncompleteBlocks(content, info),\n unstableContent: '',\n };\n }\n // For other incomplete blocks, split at last newline\n const lastNewline = content.lastIndexOf('\\n');\n if (lastNewline > 0) {\n const stable = content.substring(0, lastNewline);\n const unstable = content.substring(lastNewline + 1);\n // Check if stable part is actually stable\n const stableInfo = analyzeIncompleteMarkdown(stable);\n if (!hasAnyIncompleteBlocks(stableInfo)) {\n return { stableContent: stable, unstableContent: unstable };\n }\n }\n // Fallback: close blocks and render all\n return {\n stableContent: closeIncompleteBlocks(content, info),\n unstableContent: '',\n };\n}\n/**\n * Hook for debounced markdown rendering during streaming\n */\nexport function useStreamingMarkdown(content, options = {}) {\n const { debounceMs = 50, isStreaming = false, forceImmediate = false, minContentLength = 10, } = options;\n const [renderedHtml, setRenderedHtml] = useState('');\n const [isRendering, setIsRendering] = useState(false);\n const timeoutRef = useRef(null);\n const lastContentRef = useRef('');\n const lastRenderedRef = useRef('');\n const renderCountRef = useRef(0);\n // Analyze incomplete blocks\n const incompleteBlockInfo = useMemo(() => analyzeIncompleteMarkdown(content), [content]);\n const hasIncompleteBlocks = useMemo(() => hasAnyIncompleteBlocks(incompleteBlockInfo), [incompleteBlockInfo]);\n // Render function with smart content processing\n const renderContent = useCallback((contentToRender, forceRender = false) => {\n // Skip if content hasn't changed significantly\n if (!forceRender && contentToRender === lastContentRef.current && lastRenderedRef.current) {\n setIsRendering(false);\n return;\n }\n // Skip very short content during streaming (wait for more)\n if (isStreaming && !forceRender && contentToRender.length < minContentLength) {\n setIsRendering(false);\n return;\n }\n lastContentRef.current = contentToRender;\n renderCountRef.current++;\n try {\n let processedContent;\n if (isStreaming && hasIncompleteBlocks) {\n // Use smart content extraction during streaming\n const { stableContent } = extractStableContent(contentToRender, incompleteBlockInfo);\n processedContent = stableContent;\n }\n else {\n processedContent = contentToRender;\n }\n const html = formatMarkdownToHtml(processedContent);\n lastRenderedRef.current = html;\n setRenderedHtml(html);\n }\n catch (error) {\n console.warn('[useStreamingMarkdown] Render error:', error);\n // Fallback to escaped content\n const escaped = contentToRender\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n setRenderedHtml(`<pre style=\"white-space: pre-wrap;\">${escaped}</pre>`);\n }\n setIsRendering(false);\n }, [isStreaming, hasIncompleteBlocks, incompleteBlockInfo, minContentLength]);\n // Effect for debounced rendering\n useEffect(() => {\n // Clear any pending timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n // Empty content - clear immediately\n if (!content) {\n setRenderedHtml('');\n setIsRendering(false);\n lastContentRef.current = '';\n lastRenderedRef.current = '';\n return;\n }\n // Force immediate render (e.g., when streaming stops)\n if (forceImmediate || !isStreaming) {\n renderContent(content, true);\n return;\n }\n // Debounced render during streaming\n setIsRendering(true);\n // Dynamic debounce: shorter delay for longer content (user is waiting)\n const dynamicDelay = content.length > 500 ? debounceMs * 0.5 : debounceMs;\n timeoutRef.current = setTimeout(() => {\n renderContent(content);\n }, dynamicDelay);\n // Cleanup\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n };\n }, [content, isStreaming, forceImmediate, debounceMs, renderContent]);\n // Final render when streaming stops\n useEffect(() => {\n if (!isStreaming && content) {\n // Always do a final render when streaming stops to ensure complete content\n renderContent(content, true);\n }\n }, [isStreaming]); // Intentionally only depend on isStreaming\n return {\n renderedHtml,\n isRendering,\n hasIncompleteBlocks,\n incompleteBlockInfo,\n };\n}\nexport default useStreamingMarkdown;\n","/**\n * Jupyter Agent Extension Entry Point\n */\n// Import plugins\nimport { sidebarPlugin } from './plugins/sidebar-plugin';\nimport { cellButtonsPlugin } from './plugins/cell-buttons-plugin';\nimport { promptGenerationPlugin } from './plugins/prompt-generation-plugin';\nimport { saveInterceptorPlugin } from './plugins/save-interceptor-plugin';\nimport { idleMonitorPlugin } from './plugins/idle-monitor-plugin';\nimport { lspBridgePlugin } from './plugins/lsp-bridge-plugin';\n// Import styles\n// import '../style/index.css';\n/**\n * The main plugin export\n * Note: sidebarPlugin must load before cellButtonsPlugin\n * saveInterceptorPlugin loads last to intercept save operations\n */\nconst plugins = [\n sidebarPlugin,\n cellButtonsPlugin,\n promptGenerationPlugin,\n saveInterceptorPlugin,\n idleMonitorPlugin,\n lspBridgePlugin\n];\nexport default plugins;\n","/**\n * Logo SVG strings for HDSP Agent\n * These are inlined to avoid webpack module resolution issues with SVG imports\n */\nexport const headerLogoSvg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1280\" height=\"552\" viewBox=\"0 0 1280 552\">\n<g>\n<path d=\"M 509.50 487.44 C489.07,492.66 475.03,491.17 456.90,481.84 C443.64,475.02 431.71,461.09 425.78,445.49 L 423.50 439.50 L 423.50 352.50 C423.50,270.60 423.61,265.16 425.35,259.64 C429.92,245.21 434.83,237.05 444.80,227.32 C451.11,221.16 454.21,219.17 490.50,198.02 C499.30,192.89 511.23,185.89 517.00,182.47 C524.42,178.07 529.21,175.00 534.39,172.87 C546.27,167.97 560.17,167.97 612.52,168.00 C616.39,168.00 620.47,168.00 624.77,168.00 C696.61,168.00 696.69,168.00 698.60,170.10 C700.45,172.15 700.51,176.37 700.76,326.52 L 701.02 480.84 L 698.37 482.92 C695.82,484.93 694.64,485.00 662.87,485.00 C660.58,485.00 658.44,485.00 656.43,485.01 C635.73,485.03 629.05,485.04 626.93,481.94 C625.92,480.46 625.95,478.27 625.98,475.05 C625.99,474.40 626.00,473.70 626.00,472.95 C626.00,464.61 624.48,459.78 621.50,458.64 C619.31,457.80 613.27,459.19 584.00,467.25 C548.33,477.08 520.07,484.73 509.50,487.44 ZM 101.50 483.17 L 95.89 488.00 L 61.94 488.00 C29.33,488.00 27.92,487.92 26.00,486.00 C24.01,484.01 24.00,482.67 24.00,280.45 L 24.00 76.91 L 28.98 71.92 L 62.97 72.21 C96.81,72.50 96.96,72.51 99.22,74.78 L 101.50 77.05 L 102.00 159.37 C102.39,224.31 102.77,242.02 103.78,243.24 C106.88,246.97 108.55,247.04 192.33,246.77 L 274.16 246.50 L 280.00 239.97 L 280.00 159.53 C280.00,102.71 280.33,78.38 281.11,76.66 C283.11,72.27 285.27,72.00 318.40,72.00 C350.66,72.00 355.11,72.47 357.02,76.04 C357.65,77.22 358.00,150.36 358.00,280.86 L 358.00 483.85 L 355.37 485.93 C352.82,487.93 351.61,488.00 318.98,488.00 C287.23,488.00 285.08,487.89 282.86,486.09 L 280.50 484.18 L 280.00 398.62 C279.50,313.37 279.49,313.05 277.40,310.96 C276.25,309.80 273.55,308.45 271.40,307.95 C265.89,306.67 113.48,306.74 108.86,308.02 C106.86,308.58 104.61,309.77 103.86,310.67 C102.75,312.01 102.41,327.79 102.00,397.74 ZM 803.53 486.27 L 804.22 489.00 L 787.86 488.99 C776.68,488.98 770.71,488.57 769.00,487.71 L 766.50 486.44 L 766.25 262.22 C766.00,39.09 766.01,37.99 768.00,36.00 C769.92,34.08 771.32,34.00 802.44,34.00 L 834.89 34.00 L 837.69 36.41 L 840.50 38.83 L 839.87 387.35 L 834.03 395.42 C820.62,413.96 809.00,435.96 804.66,451.00 C800.58,465.15 800.32,473.46 803.53,486.27 ZM 503.02 426.72 C506.36,428.40 615.07,428.57 619.88,426.89 C620.83,426.56 621.63,426.38 622.31,425.99 C626.01,423.85 626.01,415.30 626.00,340.32 C626.00,336.25 626.00,331.99 626.00,327.52 C626.00,248.50 625.79,234.42 624.54,231.43 C624.09,230.36 623.73,229.47 623.18,228.73 C620.38,225.00 612.60,225.00 563.70,225.01 L 562.28 225.01 C528.10,225.01 507.13,225.39 504.75,226.05 C503.57,226.38 502.60,226.49 501.79,226.92 C497.98,228.96 497.98,238.13 498.01,311.07 C498.01,315.99 498.01,321.19 498.01,326.70 C498.02,329.49 498.02,332.20 498.02,334.83 C498.03,416.15 498.03,423.53 501.47,425.89 C501.92,426.20 502.44,426.42 503.02,426.72 ZM 807.50 386.63 C809.71,390.01 815.16,389.94 817.00,386.51 C818.51,383.68 817.20,379.40 814.51,378.36 C809.40,376.40 804.55,382.13 807.50,386.63 Z\" fill=\"rgb(33,33,33)\"/>\n<path d=\"M 920.88 447.94 C918.34,448.49 915.13,449.38 913.74,449.91 C910.57,451.11 908.44,449.01 904.19,440.50 C899.91,431.92 898.22,426.31 897.06,416.83 C896.19,409.61 894.33,331.10 894.94,327.00 C895.06,326.17 895.51,308.62 895.93,288.00 C896.77,246.77 896.82,246.38 903.51,234.42 C909.99,222.84 915.87,217.34 946.50,194.31 C966.26,179.45 975.52,173.41 982.84,170.64 C988.30,168.58 990.25,168.49 1037.00,168.20 C1070.48,167.98 1087.98,168.25 1093.50,169.07 C1097.90,169.71 1103.07,170.46 1105.00,170.72 C1111.37,171.59 1127.50,180.68 1133.24,186.65 C1134.74,188.22 1137.78,191.34 1139.99,193.59 C1142.19,195.85 1144.00,198.04 1144.00,198.46 C1144.00,198.89 1145.53,201.77 1147.39,204.87 C1155.34,218.08 1158.67,233.11 1159.17,258.00 L 1159.50 274.50 L 1154.09 281.00 C1143.99,293.15 1136.53,301.15 1121.14,316.34 L 1104.14 333.12 C1099.09,338.11 1090.51,345.00 1089.34,345.00 C1088.24,345.00 1088.00,335.76 1088.00,292.25 C1087.99,241.70 1087.91,239.37 1086.04,236.30 C1082.06,229.77 1083.02,229.88 1028.67,229.68 L 1025.26 229.67 C983.83,229.51 976.61,229.49 973.18,233.08 C972.36,233.93 971.76,234.99 970.96,236.30 C969.06,239.41 969.01,241.96 969.00,324.18 C969.00,375.59 969.38,409.86 969.96,411.38 C971.30,414.91 975.10,417.10 981.24,417.89 L 986.50 418.57 L 982.22 421.31 C969.10,429.74 933.62,445.14 920.88,447.94 ZM 1065.75 487.50 C1060.71,488.69 1051.19,488.95 1016.50,488.86 C976.07,488.76 961.65,488.05 959.65,486.05 C959.18,485.58 964.81,482.19 972.15,478.51 C979.49,474.83 985.95,471.47 986.50,471.06 C987.05,470.65 992.00,467.93 997.50,465.04 C1006.02,460.55 1010.72,457.80 1025.42,448.69 C1051.66,432.43 1059.19,427.34 1080.42,411.50 C1083.37,409.30 1087.05,406.60 1088.60,405.50 C1091.66,403.33 1101.28,395.70 1104.50,392.90 C1105.60,391.94 1109.65,388.56 1113.50,385.38 C1117.35,382.21 1123.19,377.34 1126.49,374.56 C1132.44,369.53 1134.33,367.82 1151.50,352.03 L 1160.50 343.74 L 1161.29 354.12 C1162.30,367.25 1161.38,391.47 1159.55,400.14 C1157.35,410.57 1152.30,421.50 1146.04,429.36 C1140.26,436.61 1123.66,450.88 1104.00,465.48 C1098.22,469.77 1091.90,474.51 1089.95,476.03 C1084.46,480.28 1072.67,485.87 1065.75,487.50 ZM 1169.50 127.53 C1167.30,128.25 1163.93,128.83 1162.00,128.80 C1158.73,128.77 1158.83,128.68 1163.50,127.48 C1170.40,125.70 1174.92,125.74 1169.50,127.53 Z\" fill=\"rgb(106,105,93)\"/>\n<path d=\"M 803.53 486.27 C800.32,473.46 800.58,465.15 804.66,451.00 C809.00,435.96 820.62,413.96 834.03,395.42 L 839.87 387.35 L 840.12 246.32 C840.05,327.94 840.28,384.00 840.74,384.00 C841.22,384.00 844.07,381.00 847.06,377.33 C856.97,365.17 869.10,351.67 882.38,338.00 L 895.04 324.98 C895.00,326.15 894.96,326.85 894.94,327.00 C894.43,330.45 895.66,386.60 896.59,408.61 C896.39,405.17 896.24,401.34 896.12,397.00 C895.78,385.17 895.15,375.50 894.73,375.50 C893.01,375.50 878.71,396.18 873.64,406.00 C868.53,415.89 864.99,426.58 865.01,432.06 C865.03,443.07 873.07,451.12 885.79,452.87 C888.93,453.30 892.85,453.52 894.50,453.35 C904.27,452.39 908.17,452.12 908.83,450.52 C909.20,449.61 908.51,448.25 907.25,446.08 C909.67,449.91 911.43,450.79 913.74,449.91 C915.13,449.38 918.34,448.49 920.88,447.94 C933.62,445.14 969.10,429.74 982.22,421.31 L 986.50 418.57 L 981.24,417.89 C979.60,417.68 978.13,417.37 976.82,416.96 C978.94,417.62 981.71,418.00 984.58,418.00 C990.13,418.00 991.42,417.55 999.08,412.92 C1015.33,403.11 1046.98,381.37 1060.00,371.06 C1070.76,362.54 1086.56,349.47 1087.23,348.53 C1087.64,347.96 1087.97,323.20 1087.98,293.50 C1087.99,248.96 1087.93,241.00 1086.73,237.69 C1087.93,240.98 1087.99,248.80 1088.00,292.25 C1088.00,335.76 1088.24,345.00 1089.34,345.00 C1090.51,345.00 1099.09,338.11 1104.14,333.12 L 1121.14 316.34 C1136.53,301.15 1143.99,293.15 1154.09,281.00 L 1159.50 274.50 L 1159.29 264.13 C1159.39,265.95 1159.51,267.46 1159.64,268.39 C1160.27,272.96 1160.32,273.02 1162.27,271.28 C1165.00,268.83 1174.79,254.08 1179.70,245.00 C1185.69,233.94 1188.00,226.29 1188.00,217.57 C1188.00,210.72 1187.75,209.82 1184.70,205.83 C1180.11,199.80 1174.31,196.83 1165.98,196.23 C1157.47,195.62 1144.00,196.83 1144.00,198.20 C1144.00,198.77 1145.53,201.77 1147.39,204.87 C1145.53,201.77 1144.00,198.89 1144.00,198.46 C1144.00,198.04 1142.19,195.85 1139.99,193.59 C1137.78,191.34 1134.74,188.22 1133.24,186.65 C1127.62,180.81 1112.05,171.97 1105.41,170.78 C1107.93,170.84 1112.45,169.46 1121.24,166.00 C1138.23,159.32 1157.80,152.80 1170.50,149.59 C1218.80,137.37 1251.17,146.42 1258.57,174.24 C1267.44,207.55 1234.44,267.03 1171.32,331.50 L 1161.53 341.50 L 1161.48 357.12 C1161.43,356.07 1161.37,355.07 1161.29,354.12 L 1160.50 343.74 L 1151.50 352.03 C1134.33,367.82 1132.44,369.53 1126.49,374.56 C1123.19,377.34 1117.35,382.21 1113.50,385.38 C1109.65,388.56 1105.60,391.94 1104.50,392.90 C1101.28,395.70 1091.66,403.33 1088.60,405.50 C1087.05,406.60 1083.37,409.30 1080.42,411.50 C1059.19,427.34 1051.66,432.43 1025.42,448.69 C1010.72,457.80 1006.02,460.55 997.50,465.04 C992.00,467.93 987.05,470.65 986.50,471.06 C985.95,471.47 979.49,474.83 972.15,478.51 C964.81,482.19 959.18,485.58 959.65,486.05 C960.30,486.69 962.24,487.21 965.94,487.61 C963.89,487.44 962.44,487.26 961.50,487.06 L 955.50 485.76 L 943.50 490.80 C921.92,499.86 902.62,505.99 883.00,510.03 C871.45,512.40 842.75,513.62 835.50,512.04 C822.79,509.28 813.50,503.48 807.62,494.62 L 803.98 489.13 L 796.50 488.99 L 804.22 489.00 ZM 905.64 533.57 C896.06,535.25 896.00,535.25 896.00,534.14 C896.00,533.66 899.26,532.48 903.25,531.52 C909.70,529.96 927.81,524.08 935.25,521.14 C937.53,520.23 938.00,519.44 938.00,516.48 C938.00,509.26 943.29,504.00 950.53,504.00 C955.94,504.00 961.54,508.58 963.07,514.25 C966.26,526.10 953.28,534.63 942.95,527.47 C939.79,525.28 938.91,525.08 936.40,526.04 C931.87,527.76 914.62,531.98 905.64,533.57 ZM 1119.13 157.43 C1117.46,158.30 1114.22,159.00 1111.93,159.00 C1108.42,159.00 1107.15,158.39 1103.88,155.12 C1100.32,151.55 1100.00,150.79 1100.00,145.75 C1100.00,139.49 1102.04,136.05 1106.98,134.01 C1110.91,132.38 1117.21,133.23 1120.54,135.85 C1122.67,137.53 1123.62,137.71 1125.82,136.87 C1133.46,133.92 1155.51,128.64 1166.41,126.80 C1165.51,126.99 1164.53,127.21 1163.50,127.48 C1158.83,128.68 1158.73,128.77 1162.00,128.80 C1163.35,128.82 1165.43,128.54 1167.30,128.12 C1166.23,128.41 1165.05,128.72 1163.79,129.02 C1154.63,131.26 1133.67,138.57 1127.94,141.53 C1125.68,142.70 1125.00,143.74 1125.00,146.06 C1125.00,150.77 1122.45,155.72 1119.13,157.43 ZM 807.50 386.63 C804.55,382.13 809.40,376.40 814.51,378.36 C817.20,379.40 818.51,383.68 817.00,386.51 C815.16,389.94 809.71,390.01 807.50,386.63 ZM 766.48 471.20 C766.05,448.49 766.00,393.72 766.00,261.88 C766.00,103.18 766.01,56.97 766.73,42.79 C766.02,56.96 766.07,103.22 766.25,262.22 ZM 1156.09 227.13 C1157.94,234.97 1158.95,243.50 1159.00,252.26 C1158.60,242.47 1157.67,234.29 1156.09,227.13 ZM 1161.13 385.96 C1160.55,399.03 1159.02,404.69 1154.94,414.49 C1153.90,417.00 1152.25,420.08 1150.43,423.03 C1154.62,416.14 1157.89,408.00 1159.55,400.14 C1160.21,397.02 1160.75,391.88 1161.13,385.96 ZM 1032.93 488.87 C1052.58,488.83 1060.25,488.53 1064.58,487.74 C1060.13,488.66 1053.62,488.94 1032.93,488.87 ZM 905.87 443.73 C902.29,437.54 900.07,432.51 898.65,426.02 C899.80,430.82 901.47,435.06 904.19,440.50 C904.79,441.70 905.34,442.77 905.87,443.73 ZM 975.01 416.25 C975.42,416.45 975.88,416.64 976.38,416.82 C973.05,415.68 970.90,413.86 969.96,411.38 C969.80,410.99 969.67,408.38 969.55,403.84 C969.73,408.97 969.97,411.38 970.28,412.00 C970.97,413.38 973.10,415.29 975.01,416.25 ZM 969.00 324.18 C969.01,257.37 969.04,243.16 970.08,238.52 C969.05,243.17 969.01,257.42 969.02,324.50 C969.02,334.90 969.03,344.15 969.04,352.36 C969.02,343.71 969.00,334.28 969.00,324.18 ZM 1170.13 127.31 C1172.20,126.57 1172.32,126.19 1171.17,126.17 C1171.98,126.12 1172.49,126.15 1172.62,126.29 C1172.75,126.42 1171.79,126.80 1170.13,127.31 ZM 769.01 487.71 C768.36,487.54 767.92,487.35 767.65,487.13 C767.60,487.09 767.55,487.03 767.51,486.95 L 769.00 487.71 Z\" fill=\"rgb(216,112,44)\"/>\n</g>\n</svg>`;\nexport const tabbarLogoSvg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1024\" height=\"1024\" viewBox=\"0 0 1024 1024\">\n<g>\n<path d=\"M 318.50 710.49 C315.20,711.26 311.55,712.17 310.40,712.52 C307.71,713.33 306.05,711.65 301.13,703.18 C293.61,690.23 288.22,675.88 285.58,661.76 C282.40,644.82 281.97,629.53 282.56,555.82 C282.88,515.84 282.77,482.31 282.32,481.32 C281.87,480.32 281.75,449.12 282.06,412.00 L 282.61 344.50 L 285.27 334.50 C288.45,322.58 295.99,307.08 304.32,295.38 C314.87,280.54 322.55,273.94 380.00,230.25 C429.66,192.50 443.04,185.40 469.17,182.96 C485.43,181.44 640.59,182.24 653.00,183.91 C663.20,185.28 679.81,189.55 681.96,191.36 C682.80,192.08 685.97,193.69 689.00,194.95 C692.03,196.20 696.97,198.70 700.00,200.49 C703.03,202.28 706.94,204.59 708.69,205.62 C720.60,212.62 725.49,217.04 745.14,238.56 C759.73,254.53 769.58,277.30 775.20,308.00 C776.94,317.54 777.38,324.66 777.74,349.72 L 778.18 379.95 L 769.84 390.09 C765.25,395.66 758.09,403.89 753.92,408.36 C749.75,412.84 743.50,419.88 740.04,424.00 C736.57,428.12 728.06,437.18 721.12,444.13 C714.18,451.08 706.91,458.50 704.97,460.63 C703.02,462.76 696.56,469.00 690.59,474.50 C676.24,487.74 665.08,498.19 657.61,505.39 C651.89,510.90 646.43,514.99 644.78,515.00 C644.38,515.00 643.93,470.79 643.78,416.75 L 643.50 318.50 L 641.33 314.46 C638.38,308.94 631.03,302.38 626.35,301.08 C623.75,300.36 593.15,300.01 531.79,300.01 C442.88,300.00 440.97,300.04 435.62,302.04 C429.58,304.30 424.10,309.57 421.38,315.73 C419.85,319.19 419.71,332.47 419.61,477.50 L 419.50 635.50 L 421.71 639.62 C425.88,647.44 433.20,652.01 442.74,652.77 C446.46,653.07 448.93,653.71 448.73,654.31 C448.19,655.93 436.40,662.87 426.50,667.40 C421.55,669.66 413.55,673.66 408.71,676.28 C403.88,678.90 396.68,682.36 392.71,683.95 C388.75,685.55 382.79,688.18 379.47,689.81 C376.15,691.44 371.26,693.51 368.60,694.41 C365.94,695.32 362.80,696.58 361.63,697.21 C360.46,697.85 356.80,699.14 353.50,700.10 C350.20,701.05 342.33,703.46 336.00,705.46 C329.67,707.45 321.80,709.72 318.50,710.49 ZM 590.03 786.96 C576.83,788.58 441.72,788.11 426.00,786.40 C412.11,784.88 402.89,782.97 402.86,781.60 C402.83,780.50 407.87,777.80 434.00,764.91 C458.43,752.86 468.21,747.76 473.23,744.45 C475.28,743.10 477.22,742.00 477.55,742.00 C478.06,742.00 490.62,734.77 495.00,731.96 C495.83,731.43 497.51,730.56 498.74,730.03 C501.03,729.03 511.25,722.59 521.00,715.98 C524.03,713.93 528.75,711.06 531.50,709.60 C534.25,708.14 540.33,704.49 545.00,701.50 C549.67,698.50 554.88,695.40 556.57,694.59 C558.25,693.78 562.75,690.70 566.57,687.73 C572.88,682.81 586.92,673.39 595.22,668.51 C599.11,666.22 605.83,661.28 611.01,656.89 C614.04,654.32 619.44,650.24 623.01,647.83 C635.20,639.59 657.62,622.53 670.00,612.08 C679.61,603.97 685.79,599.08 688.60,597.38 C690.31,596.35 696.26,591.28 701.82,586.12 C707.38,580.96 714.96,574.43 718.67,571.62 C722.37,568.80 729.70,562.41 734.95,557.42 C740.20,552.42 748.55,544.78 753.50,540.43 C758.45,536.09 765.17,529.68 768.43,526.20 C774.29,519.94 779.57,516.24 780.84,517.51 C782.11,518.78 782.23,597.03 780.98,606.00 C778.42,624.31 774.06,638.87 766.75,653.50 C760.23,666.57 754.14,674.86 742.50,686.54 C726.82,702.27 680.51,739.24 645.50,763.99 C627.99,776.36 607.61,784.80 590.03,786.96 Z\" fill=\"rgb(100,102,88)\"/>\n<path d=\"M 781.70 537.56 C781.54,526.19 781.25,517.91 780.84,517.51 C779.57,516.24 774.29,519.94 768.43,526.20 C765.17,529.68 758.45,536.09 753.50,540.43 C748.55,544.78 740.20,552.42 734.95,557.42 C729.70,562.41 722.37,568.80 718.67,571.62 C714.96,574.43 707.38,580.96 701.82,586.12 C696.26,591.28 690.31,596.35 688.60,597.38 C685.79,599.08 679.61,603.97 670.00,612.08 C657.62,622.53 635.20,639.59 623.01,647.83 C619.44,650.24 614.04,654.32 611.01,656.89 C605.83,661.28 599.11,666.22 595.22,668.51 C586.92,673.39 572.88,682.81 566.57,687.73 C562.75,690.70 558.25,693.78 556.57,694.59 C554.88,695.40 549.67,698.50 545.00,701.50 C540.33,704.49 534.25,708.14 531.50,709.60 C528.75,711.06 524.03,713.93 521.00,715.98 C511.25,722.59 501.03,729.03 498.74,730.03 C497.51,730.56 495.83,731.43 495.00,731.96 C490.62,734.77 478.06,742.00 477.55,742.00 C477.22,742.00 475.28,743.10 473.23,744.45 C468.21,747.76 458.43,752.86 434.00,764.91 C407.87,777.80 402.83,780.50 402.86,781.60 C402.88,782.29 405.26,783.13 409.41,783.98 C403.38,782.89 398.48,781.78 397.22,781.09 C396.96,780.94 396.71,780.85 396.67,780.69 C396.48,780.02 400.05,778.29 422.41,767.46 L 425.35 766.03 C436.82,760.48 449.65,753.98 453.85,751.59 C458.06,749.20 470.73,742.16 482.00,735.95 C493.27,729.74 507.23,721.66 513.00,717.99 C518.78,714.32 529.58,707.57 537.00,702.99 C552.59,693.37 587.09,670.61 597.88,662.83 C601.94,659.90 609.14,654.70 613.88,651.28 C618.62,647.86 625.79,642.69 629.81,639.78 C646.64,627.62 701.04,583.66 709.50,575.38 C711.70,573.23 716.04,569.55 719.14,567.20 C722.24,564.85 729.44,558.55 735.14,553.21 C740.84,547.86 747.97,541.38 751.00,538.81 C754.03,536.24 762.12,528.64 769.00,521.92 L 781.50 509.70 ZM 643.50 318.50 L 643.78 416.75 C643.93,470.79 644.38,515.00 644.78,515.00 C646.43,514.99 651.89,510.90 657.61,505.39 C665.08,498.19 676.24,487.74 690.59,474.50 C696.56,469.00 703.02,462.76 704.97,460.63 C706.91,458.50 714.18,451.08 721.12,444.13 C728.06,437.18 736.57,428.12 740.04,424.00 C743.50,419.88 749.75,412.84 753.92,408.36 C758.09,403.89 765.25,395.66 769.84,390.09 L 778.15 379.97 L 778.17 384.09 L 775.38 387.79 C770.92,393.70 756.87,410.31 754.47,412.50 C753.27,413.60 746.65,420.80 739.76,428.50 C732.87,436.20 725.50,444.13 723.37,446.11 C721.24,448.10 716.51,452.83 712.86,456.61 C706.74,462.96 683.75,485.05 669.50,498.26 C666.20,501.32 660.58,506.55 657.00,509.89 C653.42,513.23 650.20,515.97 649.83,515.98 C649.46,515.99 647.88,517.07 646.33,518.38 L 643.50 520.76 L 643.50 318.50 ZM 318.50 710.49 C321.80,709.72 329.67,707.45 336.00,705.46 C342.33,703.46 350.20,701.05 353.50,700.10 C356.80,699.14 360.46,697.85 361.63,697.21 C362.80,696.58 365.94,695.32 368.60,694.41 C371.26,693.51 376.15,691.44 379.47,689.81 C382.79,688.18 388.75,685.55 392.71,683.95 C396.68,682.36 403.88,678.90 408.71,676.28 C413.55,673.66 421.55,669.66 426.50,667.40 C436.40,662.87 448.19,655.93 448.73,654.31 C448.93,653.71 446.46,653.07 442.74,652.77 C442.25,652.73 441.77,652.68 441.29,652.62 C443.73,652.85 446.67,652.99 449.37,652.99 C457.15,653.00 458.09,653.19 457.00,654.50 C456.32,655.33 455.25,656.00 454.63,656.00 C454.01,656.00 449.72,658.25 445.10,661.00 C440.47,663.75 436.42,666.00 436.10,666.01 C435.77,666.01 434.15,666.72 432.50,667.58 C412.88,677.87 396.05,685.98 385.00,690.48 C381.42,691.94 376.70,693.94 374.50,694.94 C372.30,695.93 365.77,698.40 360.00,700.42 C354.23,702.44 348.47,704.52 347.21,705.04 C344.40,706.21 329.70,710.39 317.12,713.60 C306.12,716.42 305.90,716.44 306.61,714.57 C306.88,713.87 305.48,710.61 303.40,706.97 C306.65,712.16 308.17,713.19 310.40,712.52 C311.55,712.17 315.20,711.26 318.50,710.49 ZM 284.60 655.99 C284.35,654.52 284.12,653.03 283.89,651.50 C282.48,642.17 280.91,588.39 280.35,530.25 C280.07,501.54 279.52,483.00 278.95,483.00 C277.33,483.00 277.91,480.39 279.94,478.56 C281.18,477.44 281.63,476.05 281.89,456.65 C281.93,471.24 282.07,480.77 282.32,481.32 C282.77,482.31 282.88,515.84 282.56,555.82 C282.03,621.19 282.32,640.61 284.60,655.99 ZM 758.58 257.58 C754.65,250.40 750.18,244.08 745.14,238.56 C725.49,217.04 720.60,212.62 708.69,205.62 C706.94,204.59 703.03,202.28 700.00,200.49 C696.97,198.70 692.03,196.20 689.00,194.95 C685.97,193.69 682.80,192.08 681.96,191.36 C680.19,189.88 668.68,186.73 658.97,184.89 C662.69,185.57 666.66,186.40 669.44,187.12 C676.94,189.03 681.06,189.14 685.18,187.51 C686.44,187.01 686.75,187.29 686.44,188.67 C686.10,190.13 688.06,191.50 696.26,195.49 C715.66,204.95 722.25,210.17 745.24,234.29 C747.30,236.45 748.73,238.63 748.41,239.14 C748.10,239.65 750.07,243.53 752.79,247.78 C754.88,251.04 756.80,254.29 758.58,257.58 ZM 280.81 873.85 C275.48,874.48 270.19,875.00 269.06,875.00 C266.03,875.00 266.55,873.31 269.75,872.73 C271.26,872.46 276.46,871.50 281.31,870.60 C286.44,869.65 290.75,869.32 291.62,869.81 C294.39,871.36 290.93,872.65 280.81,873.85 ZM 798.26 106.91 C789.36,109.48 788.09,109.51 788.27,107.12 C788.41,105.18 789.34,104.99 806.00,103.53 C810.21,103.16 810.80,103.26 808.50,103.93 C806.85,104.42 802.24,105.76 798.26,106.91 ZM 782.43 111.02 C778.94,112.35 775.85,112.20 773.73,110.59 C772.06,109.33 772.09,109.21 774.23,108.64 C777.89,107.65 785.00,107.91 785.00,109.02 C785.00,109.59 783.85,110.49 782.43,111.02 ZM 778.14 377.74 L 777.74 349.72 C777.53,335.58 777.31,327.14 776.83,320.77 C777.69,328.88 777.97,337.95 778.05,355.62 ZM 781.40 599.34 C781.72,590.47 781.87,575.02 781.86,559.90 C781.97,579.09 781.91,590.69 781.40,599.34 ZM 432.42 650.07 C427.85,647.83 424.21,644.31 421.71,639.62 L 419.50 635.50 L 422.00 639.95 C424.15,643.79 428.28,647.69 432.42,650.07 ZM 697.02 725.24 C710.23,714.79 722.38,704.75 731.50,696.72 C725.40,702.21 718.52,708.06 711.00,714.13 C706.63,717.66 701.91,721.41 697.02,725.24 ZM 421.63 785.89 C423.03,786.06 424.49,786.23 426.00,786.40 C431.73,787.02 453.28,787.48 479.02,787.73 C453.57,787.51 432.39,787.10 427.00,786.53 C425.29,786.35 423.48,786.13 421.63,785.89 ZM 749.23 679.46 C754.57,673.54 758.57,668.13 762.28,661.81 C760.95,664.10 759.56,666.33 758.13,668.50 C756.07,671.62 753.06,675.31 749.23,679.46 ZM 299.40 700.11 C295.31,692.73 292.17,685.52 289.67,677.63 C292.19,685.32 295.47,692.91 299.40,700.11 ZM 771.50 291.24 C768.94,281.46 765.84,272.64 762.18,264.73 C766.02,272.94 769.06,281.57 771.50,291.24 ZM 774.58 634.52 C777.00,627.13 778.85,619.38 780.25,610.83 C779.68,614.64 778.93,618.27 777.96,622.51 C777.05,626.48 775.92,630.50 774.58,634.52 ZM 282.56 350.91 L 282.59 344.50 L 285.27 334.50 C286.44,330.10 288.21,325.21 290.40,320.20 C294.13,311.65 299.07,302.76 304.32,295.38 C299.07,302.75 294.13,311.64 290.40,320.20 C288.22,325.20 286.45,330.10 285.27,334.50 L 282.61 344.50 Z\" fill=\"rgb(146,104,68)\"/>\n<path d=\"M 781.70 537.56 L 781.50 509.70 L 769.00 521.92 C762.12,528.64 754.03,536.24 751.00,538.81 C747.97,541.38 740.84,547.86 735.14,553.21 C729.44,558.55 722.24,564.85 719.14,567.20 C716.04,569.55 711.70,573.23 709.50,575.38 C701.04,583.66 646.64,627.62 629.81,639.78 C625.79,642.69 618.62,647.86 613.88,651.28 C609.14,654.70 601.94,659.90 597.88,662.83 C587.09,670.61 552.59,693.37 537.00,702.99 C529.58,707.57 518.78,714.32 513.00,717.99 C507.23,721.66 493.27,729.74 482.00,735.95 C470.73,742.16 458.06,749.20 453.85,751.59 C449.65,753.98 436.82,760.48 425.35,766.03 L 422.41 767.46 C400.05,778.29 396.48,780.02 396.67,780.69 C396.71,780.85 396.96,780.94 397.22,781.09 C398.14,781.59 401.00,782.32 404.83,783.10 C400.40,782.42 396.22,782.06 394.54,782.25 C392.32,782.50 384.65,785.22 377.50,788.29 C324.99,810.83 278.31,824.81 236.50,830.51 C222.36,832.44 186.45,833.29 176.60,831.93 C146.13,827.72 122.52,813.00 112.24,791.81 C107.92,782.91 104.51,770.53 103.48,760.00 C102.78,752.89 104.89,734.09 107.55,723.75 C123.45,661.92 177.67,581.88 260.13,498.50 L 280.48 477.92 C280.32,478.19 280.14,478.38 279.94,478.56 C277.91,480.39 277.33,483.00 278.95,483.00 C279.52,483.00 280.07,501.54 280.35,530.25 C280.51,547.00 280.76,563.38 281.06,578.45 C280.72,575.03 280.29,573.32 279.80,573.62 C278.84,574.22 266.21,590.12 262.02,596.02 C245.24,619.64 235.17,638.56 228.41,659.15 C226.13,666.09 225.66,669.36 225.59,678.50 C225.51,688.55 225.74,690.02 228.30,695.50 C233.60,706.86 243.51,714.27 257.60,717.42 C269.81,720.16 304.59,718.03 306.64,714.48 C306.63,714.51 306.62,714.54 306.61,714.57 C305.90,716.44 306.12,716.42 317.12,713.60 C329.70,710.39 344.40,706.21 347.21,705.04 C348.47,704.52 354.23,702.44 360.00,700.42 C365.77,698.40 372.30,695.93 374.50,694.94 C376.70,693.94 381.42,691.94 385.00,690.48 C396.05,685.98 412.88,677.87 432.50,667.58 C434.15,666.72 435.77,666.01 436.10,666.01 C436.42,666.00 440.47,663.75 445.10,661.00 C449.72,658.25 454.01,656.00 454.63,656.00 C455.25,656.00 456.32,655.33 457.00,654.50 C458.09,653.19 457.15,653.00 449.37,652.99 C446.67,652.99 443.73,652.85 441.29,652.62 C440.58,652.54 439.89,652.43 439.20,652.29 C441.95,652.69 445.60,652.94 449.18,652.96 L 458.86 653.00 L 474.18 643.88 C499.68,628.70 540.63,601.07 574.06,576.50 C589.46,565.18 608.69,549.97 635.17,528.16 L 642.84 521.84 L 643.48 514.67 C643.49,514.61 643.49,514.54 643.50,514.46 L 643.50 520.76 L 646.33 518.38 C647.88,517.07 649.46,515.99 649.83,515.98 C650.20,515.97 653.42,513.23 657.00,509.89 C660.58,506.55 666.20,501.32 669.50,498.26 C683.75,485.05 706.74,462.96 712.86,456.61 C716.51,452.83 721.24,448.10 723.37,446.11 C725.50,444.13 732.87,436.20 739.76,428.50 C746.65,420.80 753.27,413.60 754.47,412.50 C756.87,410.31 770.92,393.70 775.38,387.79 L 778.17 384.09 L 778.15 379.97 L 778.18 379.95 L 778.14 377.74 L 778.11 370.82 C778.28,378.15 778.64,380.00 779.41,380.00 C782.68,380.00 801.47,353.32 812.87,332.48 C835.22,291.62 837.69,266.88 821.15,249.67 C812.95,241.14 802.62,237.34 785.00,236.36 C774.77,235.79 750.50,238.10 748.81,239.79 C748.42,240.18 750.14,243.65 752.64,247.50 C757.04,254.29 760.75,261.20 763.90,268.56 C763.34,267.27 762.77,266.00 762.18,264.73 C761.03,262.26 759.83,259.88 758.58,257.58 L 758.58 257.58 C756.80,254.29 754.87,251.04 752.79,247.78 C750.07,243.53 748.10,239.65 748.41,239.14 C748.73,238.63 747.30,236.45 745.24,234.29 C722.25,210.17 715.66,204.95 696.26,195.49 C688.06,191.50 686.10,190.13 686.44,188.67 C686.75,187.29 686.44,187.01 685.18,187.51 C684.02,187.96 682.87,188.28 681.64,188.47 C682.98,188.11 684.41,187.50 686.31,186.63 C691.13,184.43 700.14,180.81 724.00,171.48 C736.74,166.49 769.12,155.80 781.50,152.49 C801.94,147.02 810.10,145.06 820.67,143.08 C866.29,134.55 901.31,136.66 927.50,149.50 C956.65,163.80 970.01,190.29 966.83,227.50 C964.79,251.49 956.10,277.76 939.97,308.73 C927.29,333.08 918.62,346.99 895.55,380.00 C890.20,387.66 869.70,414.27 863.58,421.50 C840.94,448.26 822.56,468.41 798.79,492.56 L 782.00 509.63 L 781.98 555.06 C781.98,563.66 781.95,570.95 781.90,577.14 C781.92,572.16 781.90,566.48 781.86,559.90 L 781.86 559.90 C781.85,552.03 781.80,544.26 781.70,537.56 ZM 274.21 871.91 C276.59,871.40 279.42,870.88 282.50,870.38 C284.70,870.03 286.95,869.40 287.50,868.99 C288.05,868.57 290.08,867.90 292.00,867.49 C303.53,865.03 332.38,855.82 349.50,849.14 L 359.50 845.23 L 360.04 837.76 C360.78,827.49 364.43,821.92 373.50,817.20 C389.33,808.95 408.27,820.03 409.74,838.40 C410.40,846.62 408.82,851.21 403.27,857.15 C397.51,863.30 391.96,865.49 384.20,864.68 C377.10,863.94 372.26,861.99 369.08,858.59 C365.99,855.29 363.89,855.34 352.11,858.94 C333.73,864.57 308.96,869.89 286.73,873.01 C292.19,872.04 293.75,871.00 291.62,869.81 C290.75,869.32 286.44,869.65 281.31,870.60 C278.86,871.05 276.32,871.52 274.21,871.91 ZM 701.98 163.75 C697.33,166.56 695.69,166.99 690.04,166.96 C679.39,166.90 672.36,162.46 667.33,152.63 C665.21,148.47 664.91,146.79 665.32,141.13 C666.03,131.16 666.55,129.36 669.93,125.08 C675.28,118.34 686.36,114.67 694.67,116.91 C698.28,117.88 700.08,118.94 707.62,124.49 C709.56,125.93 710.29,125.86 716.62,123.64 C733.17,117.83 753.19,112.39 767.00,109.94 C770.58,109.30 774.18,108.45 775.01,108.03 C775.85,107.62 780.57,106.74 785.51,106.08 C786.77,105.92 788.12,105.73 789.49,105.53 C788.48,105.93 788.32,106.40 788.27,107.12 C788.16,108.49 788.54,109.07 790.58,108.82 L 790.00 108.97 C787.42,109.65 784.99,110.30 782.67,110.93 C783.97,110.39 785.00,109.56 785.00,109.02 C785.00,107.91 777.89,107.65 774.23,108.64 C772.09,109.21 772.06,109.33 773.73,110.59 C775.22,111.72 777.20,112.14 779.46,111.81 C763.55,116.24 752.65,120.02 734.88,127.04 C720.30,132.80 716.03,134.23 714.64,137.08 C713.93,138.53 713.98,140.36 713.79,143.34 C713.17,153.07 709.67,159.11 701.98,163.75 ZM 131.60 596.60 C127.85,600.35 124.66,600.86 119.38,598.56 C115.11,596.70 113.00,593.34 113.00,588.38 C113.00,584.83 113.54,583.73 116.66,580.99 C119.99,578.07 120.78,577.82 125.35,578.26 C131.82,578.88 134.99,582.36 135.00,588.85 C135.00,592.47 134.43,593.77 131.60,596.60 ZM 427.75 786.60 C434.16,787.14 454.63,787.52 479.02,787.73 C497.78,787.92 518.78,787.99 537.47,787.94 C494.78,788.14 440.51,787.81 431.05,786.94 C430.03,786.85 428.92,786.73 427.75,786.60 ZM 803.21 105.47 C805.51,104.81 807.52,104.22 808.50,103.93 C810.80,103.26 810.21,103.16 806.00,103.53 C802.83,103.81 800.24,104.04 798.10,104.25 C798.79,104.13 799.41,104.03 799.92,103.94 C802.90,103.42 806.72,103.05 808.42,103.10 L 811.50 103.20 L 808.50 104.05 C807.79,104.25 805.86,104.77 803.21,105.47 ZM 774.58 634.52 L 774.58 634.52 C774.87,633.64 775.15,632.77 775.43,631.90 C772.09,643.14 767.72,653.05 762.01,662.27 C762.10,662.11 762.19,661.96 762.28,661.81 C763.79,659.22 765.26,656.49 766.75,653.50 C769.87,647.26 772.45,641.03 774.58,634.52 ZM 282.08 617.93 C282.36,626.68 282.67,634.14 282.98,639.89 C282.49,633.20 282.20,625.93 282.08,617.93 ZM 776.77 320.01 C777.55,326.73 777.87,333.52 777.96,345.02 C777.82,333.96 777.50,327.07 776.83,320.77 C776.81,320.51 776.79,320.26 776.77,320.01 ZM 288.38 673.48 C288.79,674.87 289.22,676.25 289.67,677.63 C292.17,685.52 295.31,692.73 299.40,700.11 C294.63,691.51 291.03,682.98 288.38,673.48 ZM 423.56 642.32 C422.97,641.53 422.44,640.74 422.00,639.95 L 419.50 635.50 L 422.00 639.91 C422.47,640.73 422.99,641.54 423.56,642.32 ZM 780.88 606.72 C780.78,607.57 780.68,608.32 780.57,609.00 C780.44,609.79 780.31,610.56 780.17,611.34 C780.20,611.17 780.22,611.00 780.25,610.83 C780.47,609.48 780.68,608.11 780.88,606.72 ZM 670.37 187.35 C670.07,187.27 669.76,187.20 669.44,187.12 C666.66,186.40 662.69,185.57 658.97,184.89 C658.76,184.85 658.55,184.81 658.35,184.77 C662.17,185.45 666.30,186.31 669.11,187.03 C669.55,187.14 669.97,187.25 670.37,187.35 ZM 781.40 599.34 C781.44,598.68 781.47,598.01 781.51,597.32 C781.45,598.70 781.39,599.96 781.33,601.11 C781.35,600.55 781.37,599.96 781.40,599.34 ZM 267.53 874.77 C267.15,874.65 267.00,874.46 267.00,874.21 C267.00,874.16 267.03,874.11 267.08,874.05 C266.98,874.33 267.12,874.59 267.53,874.77 ZM 275.15 874.48 C273.78,874.63 272.63,874.75 271.66,874.82 C272.64,874.73 273.84,874.62 275.15,874.48 Z\" fill=\"rgb(215,111,43)\"/>\n</g>\n</svg>`;\n","/**\n * Cell Buttons Plugin\n * Injects E, F, ? action buttons into notebook cells\n * Communicates with sidebar panel instead of Chrome extension\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { CellAction } from '../types';\n/**\n * Cell Buttons Plugin\n */\nexport const cellButtonsPlugin = {\n id: '@hdsp-agent/cell-buttons',\n autoStart: true,\n requires: [INotebookTracker],\n activate: (app, notebookTracker) => {\n console.log('[CellButtonsPlugin] Activated');\n // Store app reference globally for later use\n window.jupyterapp = app;\n // Handle new notebooks\n notebookTracker.widgetAdded.connect((sender, panel) => {\n console.log('[CellButtonsPlugin] Notebook widget added:', panel.id);\n // Wait for session to be ready\n panel.sessionContext.ready.then(() => {\n observeNotebook(panel);\n }).catch(err => {\n console.error('[CellButtonsPlugin] Error waiting for session:', err);\n });\n });\n // Handle current notebook if exists\n if (notebookTracker.currentWidget) {\n observeNotebook(notebookTracker.currentWidget);\n }\n // Handle notebook focus changes\n notebookTracker.currentChanged.connect((sender, panel) => {\n if (panel) {\n observeNotebook(panel);\n }\n });\n }\n};\n/**\n * Observe a notebook for cell changes and inject buttons\n */\nfunction observeNotebook(panel) {\n const notebook = panel.content;\n // Initial injection with delay to ensure cells are rendered\n setTimeout(() => {\n injectButtonsIntoAllCells(notebook, panel);\n // Retry after longer delay for cells that weren't ready\n setTimeout(() => {\n injectButtonsIntoAllCells(notebook, panel);\n }, 500);\n }, 200);\n // Watch for cell changes (add/remove/reorder)\n const cellsChangedHandler = () => {\n setTimeout(() => {\n injectButtonsIntoAllCells(notebook, panel);\n }, 100);\n };\n // Remove any previous listeners to avoid duplicates\n try {\n notebook.model?.cells.changed.disconnect(cellsChangedHandler);\n }\n catch {\n // Ignore if not connected\n }\n // Connect new listener\n notebook.model?.cells.changed.connect(cellsChangedHandler);\n}\n/**\n * Inject buttons into all cells in a notebook\n */\nfunction injectButtonsIntoAllCells(notebook, panel) {\n for (let i = 0; i < notebook.widgets.length; i++) {\n const cell = notebook.widgets[i];\n injectButtonsIntoCell(cell, panel);\n }\n}\n/**\n * SVG Icons for cell action buttons\n */\nconst ICONS = {\n // Lightbulb icon for Explain\n explain: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 18h6\"/>\n <path d=\"M10 22h4\"/>\n <path d=\"M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14\"/>\n </svg>`,\n // Wrench icon for Fix\n fix: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z\"/>\n </svg>`,\n // Question mark icon for Custom Prompt\n question: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/>\n <path d=\"M12 17h.01\"/>\n </svg>`\n};\n/**\n * Inject buttons into a single cell's toolbar\n * Buttons are placed in the jp-cell-toolbar when it exists\n */\nfunction injectButtonsIntoCell(cell, panel) {\n // Safety checks\n if (!cell || !cell.model) {\n return;\n }\n // Only inject buttons into code cells (not markdown, raw, etc.)\n if (cell.model.type !== 'code') {\n return;\n }\n const cellNode = cell.node;\n if (!cellNode || !cellNode.classList.contains('jp-CodeCell')) {\n return;\n }\n // Try to find the cell toolbar\n const cellToolbar = cellNode.querySelector('.jp-cell-toolbar');\n if (cellToolbar) {\n // Inject into existing toolbar\n injectButtonsIntoToolbar(cellToolbar, cell);\n }\n // Set up MutationObserver to watch for toolbar creation\n if (!cellNode.hasAttribute('data-hdsp-observer')) {\n cellNode.setAttribute('data-hdsp-observer', 'true');\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const node of Array.from(mutation.addedNodes)) {\n if (node instanceof HTMLElement) {\n const toolbar = node.classList.contains('jp-cell-toolbar')\n ? node\n : node.querySelector('.jp-cell-toolbar');\n if (toolbar) {\n injectButtonsIntoToolbar(toolbar, cell);\n }\n }\n }\n }\n });\n observer.observe(cellNode, { childList: true, subtree: true });\n }\n}\n/**\n * Inject HDSP buttons into the cell toolbar\n */\nfunction injectButtonsIntoToolbar(toolbar, cell) {\n // Check if buttons already exist\n if (toolbar.querySelector('.jp-hdsp-toolbar-btn')) {\n return;\n }\n // Create a separator\n const separator = document.createElement('div');\n separator.className = 'jp-Toolbar-item jp-hdsp-separator';\n separator.style.cssText = `\n width: 1px;\n height: 20px;\n background: var(--jp-border-color1, #ccc);\n margin: 0 4px;\n `;\n // Create icon buttons\n const explainBtn = createToolbarButton(ICONS.explain, '코드 설명 (Explain)', () => {\n handleCellAction(CellAction.EXPLAIN, cell);\n });\n const fixBtn = createToolbarButton(ICONS.fix, '수정 제안 (Fix)', () => {\n handleCellAction(CellAction.FIX, cell);\n });\n const questionBtn = createToolbarButton(ICONS.question, '질문하기 (Ask)', () => {\n handleCellAction(CellAction.CUSTOM_PROMPT, cell);\n });\n // Insert at the beginning of the toolbar\n const firstChild = toolbar.firstChild;\n toolbar.insertBefore(questionBtn, firstChild);\n toolbar.insertBefore(fixBtn, firstChild);\n toolbar.insertBefore(explainBtn, firstChild);\n toolbar.insertBefore(separator, firstChild);\n}\n/**\n * Create a toolbar button matching JupyterLab's style\n */\nfunction createToolbarButton(iconSvg, title, onClick) {\n const container = document.createElement('div');\n container.className = 'lm-Widget jp-CommandToolbarButton jp-Toolbar-item jp-hdsp-toolbar-btn';\n const button = document.createElement('button');\n button.className = 'jp-ToolbarButtonComponent jp-hdsp-action-btn';\n button.title = title;\n button.setAttribute('aria-label', title);\n button.innerHTML = iconSvg;\n button.onclick = (e) => {\n e.preventDefault();\n e.stopPropagation();\n onClick();\n };\n // Style to match JupyterLab buttons\n button.style.cssText = `\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n color: var(--jp-ui-font-color1, #333);\n transition: background 0.1s ease;\n `;\n button.addEventListener('mouseenter', () => {\n button.style.background = 'var(--jp-layout-color2, #e0e0e0)';\n });\n button.addEventListener('mouseleave', () => {\n button.style.background = 'transparent';\n });\n container.appendChild(button);\n return container;\n}\n/**\n * Handle cell action button click\n */\nasync function handleCellAction(action, cell) {\n const cellContent = cell?.model?.sharedModel?.getSource() || '';\n if (!cellContent.trim()) {\n showNotification('셀 내용이 비어있습니다.', 'warning');\n return;\n }\n const cellIndex = getCellIndex(cell);\n if (action === CellAction.CUSTOM_PROMPT) {\n // For custom prompt, show dialog (no confirmation needed)\n showCustomPromptDialog(cell);\n }\n else {\n // Get cell output\n const cellOutput = getCellOutput(cell);\n // Determine confirmation dialog content based on action\n let title = '';\n let message = '';\n switch (action) {\n case CellAction.EXPLAIN:\n title = `셀 ${cellIndex}번째: 설명 요청`;\n message = '이 셀의 코드 설명을 보시겠습니까?';\n break;\n case CellAction.FIX:\n title = `셀 ${cellIndex}번째: 수정 제안 요청`;\n message = '이 셀의 코드 개선 제안을 받으시겠습니까?';\n break;\n }\n // Show confirmation dialog first\n const confirmed = await showConfirmDialog(title, message);\n if (confirmed) {\n // User confirmed, send to sidebar panel\n sendToSidebarPanel(action, cell, cellContent, cellIndex, cellOutput);\n }\n }\n}\n/**\n * Get the index of a cell in the notebook\n */\nfunction getCellIndex(cell) {\n const app = window.jupyterapp;\n const notebookTracker = app?.shell?.currentWidget?.content;\n if (!notebookTracker) {\n return -1;\n }\n const widgets = notebookTracker.widgets;\n for (let i = 0; i < widgets.length; i++) {\n if (widgets[i] === cell) {\n return i + 1; // Return 1-based index\n }\n }\n return -1;\n}\n/**\n * Extract output from a code cell\n */\nfunction getCellOutput(cell) {\n // cell.model이 null일 수 있으므로 안전하게 체크\n if (!cell?.model || cell.model.type !== 'code') {\n return '';\n }\n const codeCell = cell;\n const outputs = codeCell.model?.outputs;\n if (!outputs || outputs.length === 0) {\n return '';\n }\n const outputTexts = [];\n for (let i = 0; i < outputs.length; i++) {\n const output = outputs.get(i);\n const outputType = output.type;\n if (outputType === 'stream') {\n // stdout, stderr\n const text = output.text;\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n else if (outputType === 'execute_result' || outputType === 'display_data') {\n // Execution results or display data\n const data = output.data;\n if (data['text/plain']) {\n const text = data['text/plain'];\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n }\n else if (outputType === 'error') {\n // Error output\n const traceback = output.traceback;\n if (Array.isArray(traceback)) {\n outputTexts.push(traceback.join('\\n'));\n }\n }\n }\n return outputTexts.join('\\n');\n}\n/**\n * Get or assign cell ID to a cell\n */\nfunction getOrAssignCellId(cell) {\n const cellModel = cell.model;\n let cellId;\n // Try to get cell ID from metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.get === 'function') {\n cellId = cellModel.metadata.get('jupyterAgentCellId');\n }\n else if (cellModel.metadata?.jupyterAgentCellId) {\n cellId = cellModel.metadata.jupyterAgentCellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n if (!cellId) {\n cellId = `cell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n // Try to set cell ID in metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.set === 'function') {\n cellModel.metadata.set('jupyterAgentCellId', cellId);\n }\n else if (cellModel.metadata) {\n cellModel.metadata.jupyterAgentCellId = cellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n }\n return cellId;\n}\n/**\n * Send cell action to sidebar panel\n */\nfunction sendToSidebarPanel(action, cell, cellContent, cellIndex, cellOutput) {\n const agentPanel = window._hdspAgentPanel;\n if (!agentPanel) {\n console.error('[CellButtonsPlugin] Agent panel not found. Make sure sidebar plugin is loaded.');\n return;\n }\n // Get or assign cell ID\n const cellId = getOrAssignCellId(cell);\n // Activate the sidebar panel\n const app = window.jupyterapp;\n if (app) {\n app.shell.activateById(agentPanel.id);\n }\n // Create user-facing display prompt\n let displayPrompt = '';\n // Create actual LLM prompt (based on chrome_agent)\n // Note: Use original content, not escaped. JSON.stringify will handle escaping.\n let llmPrompt = '';\n switch (action) {\n case CellAction.EXPLAIN:\n // User sees: \"Cell x: 설명 요청\" (chrome_agent와 동일)\n displayPrompt = `${cellIndex}번째 셀: 설명 요청`;\n // LLM receives: chrome_agent와 동일한 프롬프트\n llmPrompt = `다음 Jupyter 셀의 내용을 자세히 설명해주세요:\n\n\\`\\`\\`python\n${cellContent}\n\\`\\`\\``;\n break;\n case CellAction.FIX:\n // Check if there's an error in the output\n const hasError = cellOutput && (cellOutput.includes('Error') ||\n cellOutput.includes('Traceback') ||\n cellOutput.includes('Exception') ||\n cellOutput.includes('에러') ||\n cellOutput.includes('오류'));\n if (hasError && cellOutput) {\n // 에러가 있는 경우 (chrome_agent와 동일)\n displayPrompt = `${cellIndex}번째 셀: 에러 수정 요청`;\n llmPrompt = `다음 Jupyter 셀 코드에 에러가 발생했습니다.\n\n원본 코드:\n\\`\\`\\`python\n${cellContent}\n\\`\\`\\`\n\n에러:\n\\`\\`\\`\n${cellOutput}\n\\`\\`\\`\n\n다음 형식으로 응답해주세요:\n\n## 에러 원인\n(에러가 발생한 원인을 간단히 설명)\n\n## 수정 방법\n\n### 방법 1: (수정 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(수정된 코드)\n\\`\\`\\`\n\n### 방법 2: (수정 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(수정된 코드)\n\\`\\`\\`\n\n### 방법 3: (수정 방법 제목) (있는 경우)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(수정된 코드)\n\\`\\`\\`\n\n최소 2개, 최대 3개의 다양한 수정 방법을 제안해주세요.`;\n }\n else {\n // 에러가 없는 경우 - 코드 리뷰/개선 제안 (chrome_agent와 동일)\n displayPrompt = `${cellIndex}번째 셀: 개선 제안 요청`;\n llmPrompt = `다음 Jupyter 셀 코드를 리뷰하고 개선 방법을 제안해주세요.\n\n코드:\n\\`\\`\\`python\n${cellContent}\n\\`\\`\\`\n\n다음 형식으로 응답해주세요:\n\n## 코드 분석\n(현재 코드의 기능과 특징을 간단히 설명)\n\n## 개선 방법\n\n### 방법 1: (개선 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(개선된 코드)\n\\`\\`\\`\n\n### 방법 2: (개선 방법 제목)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(개선된 코드)\n\\`\\`\\`\n\n### 방법 3: (개선 방법 제목) (있는 경우)\n(이 방법에 대한 간단한 설명)\n\\`\\`\\`python\n(개선된 코드)\n\\`\\`\\`\n\n최소 2개, 최대 3개의 다양한 개선 방법을 제안해주세요.`;\n }\n break;\n }\n // Send both prompts to panel with cell ID and cell index\n if (agentPanel.addCellActionMessage) {\n // cellIndex is 1-based (for display), convert to 0-based for array access\n const cellIndexZeroBased = cellIndex - 1;\n agentPanel.addCellActionMessage(action, cellContent, displayPrompt, llmPrompt, cellId, cellIndexZeroBased);\n }\n}\n/**\n * Helper: Remove existing dialog if present\n */\nfunction removeExistingDialog(className) {\n const existingDialog = document.querySelector(`.${className}`);\n if (existingDialog) {\n existingDialog.remove();\n }\n}\n/**\n * Helper: Create dialog overlay element\n */\nfunction createDialogOverlay(className) {\n const dialogOverlay = document.createElement('div');\n dialogOverlay.className = className;\n dialogOverlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `;\n return dialogOverlay;\n}\n/**\n * Helper: Create dialog container element\n */\nfunction createDialogContainer(maxWidth = '400px') {\n const dialogContainer = document.createElement('div');\n dialogContainer.style.cssText = `\n background: #fafafa;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 24px;\n max-width: ${maxWidth};\n width: 90%;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `;\n return dialogContainer;\n}\n/**\n * Show confirmation dialog\n * Based on chrome_agent's showConfirmDialog implementation\n */\nfunction showConfirmDialog(title, message) {\n return new Promise((resolve) => {\n removeExistingDialog('jp-agent-confirm-dialog');\n const dialogOverlay = createDialogOverlay('jp-agent-confirm-dialog');\n const dialogContainer = createDialogContainer();\n // Dialog content\n dialogContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 12px 0; color: #424242; font-size: 16px; font-weight: 500;\">\n ${escapeHtml(title)}\n </h3>\n <p style=\"margin: 0; color: #616161; font-size: 14px; line-height: 1.5;\">\n ${escapeHtml(message)}\n </p>\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-confirm-cancel-btn\" style=\"\n background: transparent;\n color: #616161;\n border: 1px solid #d1d5db;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">취소</button>\n <button class=\"jp-agent-confirm-submit-btn\" style=\"\n background: #1976d2;\n color: white;\n border: 1px solid #1976d2;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">확인</button>\n </div>\n `;\n dialogOverlay.appendChild(dialogContainer);\n document.body.appendChild(dialogOverlay);\n // Button event listeners\n const cancelBtn = dialogContainer.querySelector('.jp-agent-confirm-cancel-btn');\n const submitBtn = dialogContainer.querySelector('.jp-agent-confirm-submit-btn');\n // Cancel button\n cancelBtn.addEventListener('click', () => {\n dialogOverlay.remove();\n resolve(false);\n });\n cancelBtn.addEventListener('mouseenter', () => {\n cancelBtn.style.background = '#f5f5f5';\n });\n cancelBtn.addEventListener('mouseleave', () => {\n cancelBtn.style.background = 'transparent';\n });\n // Submit button\n submitBtn.addEventListener('click', () => {\n submitBtn.disabled = true;\n submitBtn.textContent = '처리 중...';\n // Small delay to show processing state\n setTimeout(() => {\n dialogOverlay.remove();\n resolve(true);\n }, 100);\n });\n submitBtn.addEventListener('mouseenter', () => {\n if (!submitBtn.disabled) {\n submitBtn.style.background = '#1565c0';\n }\n });\n submitBtn.addEventListener('mouseleave', () => {\n if (!submitBtn.disabled) {\n submitBtn.style.background = '#1976d2';\n }\n });\n // ESC key to close\n const handleEsc = (e) => {\n if (e.key === 'Escape') {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEsc);\n resolve(false);\n }\n };\n document.addEventListener('keydown', handleEsc);\n // Close on overlay click\n dialogOverlay.addEventListener('click', (e) => {\n if (e.target === dialogOverlay) {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEsc);\n resolve(false);\n }\n });\n });\n}\n/**\n * Escape HTML to prevent XSS\n */\nfunction escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n/**\n * Escape content for markdown code blocks\n * Based on chrome_agent's escapeContent function\n */\nfunction escapeContent(content) {\n if (!content)\n return '';\n // 백슬래시를 먼저 escape (다른 escape 처리 전에 수행)\n let escaped = content.replace(/\\\\/g, '\\\\\\\\');\n // 백틱 escape (마크다운 코드 블록 안에서 문제 방지)\n escaped = escaped.replace(/`/g, '\\\\`');\n return escaped;\n}\n/**\n * Get notification background color\n */\nfunction getNotificationColor(type) {\n switch (type) {\n case 'error': return '#f56565';\n case 'warning': return '#ed8936';\n default: return '#4299e1';\n }\n}\n/**\n * Create notification element with styles\n */\nfunction createNotificationElement(message, backgroundColor) {\n const notification = document.createElement('div');\n notification.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 16px;\n border-radius: 6px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n z-index: 10001;\n opacity: 0;\n transition: opacity 0.3s ease;\n max-width: 300px;\n word-wrap: break-word;\n background: ${backgroundColor};\n `;\n notification.textContent = message;\n return notification;\n}\n/**\n * Animate notification appearance and removal\n */\nfunction animateNotification(notification) {\n // Show animation\n setTimeout(() => notification.style.opacity = '1', 10);\n // Remove after 3 seconds\n setTimeout(() => {\n notification.style.opacity = '0';\n setTimeout(() => {\n if (notification.parentNode) {\n notification.parentNode.removeChild(notification);\n }\n }, 300);\n }, 3000);\n}\n/**\n * Show notification (simple implementation)\n */\nfunction showNotification(message, type = 'info') {\n const notification = createNotificationElement(message, getNotificationColor(type));\n document.body.appendChild(notification);\n animateNotification(notification);\n}\n/**\n * Show custom prompt dialog\n * Based on chrome_agent's showCustomPromptDialog implementation\n */\nfunction showCustomPromptDialog(cell) {\n const cellContent = cell?.model?.sharedModel?.getSource() || '';\n const cellIndex = getCellIndex(cell);\n const cellId = getOrAssignCellId(cell);\n console.log('커스텀 프롬프트 다이얼로그 표시', cellIndex, cellId);\n removeExistingDialog('jp-agent-custom-prompt-dialog');\n const dialogOverlay = createDialogOverlay('jp-agent-custom-prompt-dialog');\n const dialogContainer = createDialogContainer('500px');\n // 다이얼로그 내용\n dialogContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 8px 0; color: #424242; font-size: 16px; font-weight: 500;\">\n 셀에 대해 질문하기\n </h3>\n <p style=\"margin: 0; color: #757575; font-size: 13px;\">\n 물리적 위치: 셀 ${cellIndex + 1}번째 (위에서 아래로 카운트)\n </p>\n </div>\n <div style=\"margin-bottom: 20px;\">\n <textarea class=\"jp-agent-custom-prompt-input\"\n placeholder=\"이 셀에 대해 질문할 내용을 입력하세요...\"\n style=\"\n width: 100%;\n min-height: 100px;\n padding: 12px;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n font-size: 14px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n resize: vertical;\n box-sizing: border-box;\n \"\n ></textarea>\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-custom-prompt-cancel-btn\" style=\"\n background: transparent;\n color: #616161;\n border: 1px solid #d1d5db;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">취소</button>\n <button class=\"jp-agent-custom-prompt-submit-btn\" style=\"\n background: transparent;\n color: #1976d2;\n border: 1px solid #1976d2;\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">질문</button>\n </div>\n `;\n dialogOverlay.appendChild(dialogContainer);\n document.body.appendChild(dialogOverlay);\n // 입력 필드에 포커스\n const inputField = dialogContainer.querySelector('.jp-agent-custom-prompt-input');\n setTimeout(() => inputField?.focus(), 100);\n // 버튼 이벤트 리스너\n const cancelBtn = dialogContainer.querySelector('.jp-agent-custom-prompt-cancel-btn');\n const submitBtn = dialogContainer.querySelector('.jp-agent-custom-prompt-submit-btn');\n // 취소 버튼\n cancelBtn.addEventListener('click', () => {\n dialogOverlay.remove();\n });\n cancelBtn.addEventListener('mouseenter', () => {\n cancelBtn.style.background = '#f5f5f5';\n cancelBtn.style.borderColor = '#9ca3af';\n });\n cancelBtn.addEventListener('mouseleave', () => {\n cancelBtn.style.background = 'transparent';\n cancelBtn.style.borderColor = '#d1d5db';\n });\n // 제출 버튼\n const handleSubmit = async () => {\n const promptText = inputField?.value.trim() || '';\n if (!promptText) {\n showNotification('질문 내용을 입력해주세요.', 'warning');\n return;\n }\n dialogOverlay.remove();\n // Get cell output\n const cellOutput = getCellOutput(cell);\n // Create display prompt: \"Cell x: Custom request\"\n const displayPrompt = `${cellIndex}번째 셀: ${promptText}`;\n // Create LLM prompt with code and output\n let llmPrompt = `${promptText}\\n\\n셀 내용:\\n\\`\\`\\`\\n${cellContent}\\n\\`\\`\\``;\n if (cellOutput) {\n llmPrompt += `\\n\\n실행 결과:\\n\\`\\`\\`\\n${cellOutput}\\n\\`\\`\\``;\n }\n const agentPanel = window._hdspAgentPanel;\n if (agentPanel) {\n // Activate the sidebar panel\n const app = window.jupyterapp;\n if (app) {\n app.shell.activateById(agentPanel.id);\n }\n // Send both prompts with cell ID and cell index\n if (agentPanel.addCellActionMessage) {\n // cellIndex is 1-based (for display), convert to 0-based for array access\n const cellIndexZeroBased = cellIndex - 1;\n agentPanel.addCellActionMessage(CellAction.CUSTOM_PROMPT, cellContent, displayPrompt, llmPrompt, cellId, cellIndexZeroBased);\n }\n }\n };\n submitBtn.addEventListener('click', handleSubmit);\n submitBtn.addEventListener('mouseenter', () => {\n submitBtn.style.background = 'rgba(25, 118, 210, 0.1)';\n });\n submitBtn.addEventListener('mouseleave', () => {\n submitBtn.style.background = 'transparent';\n });\n // Enter 키로 제출 (Shift+Enter는 줄바꿈)\n inputField?.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n });\n // 오버레이 클릭 시 다이얼로그 닫기\n dialogOverlay.addEventListener('click', (e) => {\n if (e.target === dialogOverlay) {\n dialogOverlay.remove();\n }\n });\n // ESC 키로 다이얼로그 닫기\n const handleEscapeKey = (e) => {\n if (e.key === 'Escape') {\n dialogOverlay.remove();\n document.removeEventListener('keydown', handleEscapeKey);\n }\n };\n document.addEventListener('keydown', handleEscapeKey);\n}\n","/**\n * Idle Monitor Plugin\n *\n * Monitors user activity and triggers shutdown after idle timeout.\n * Activity is tracked by:\n * - Keyboard events\n * - Mouse events\n * - Notebook cell execution (kernel busy status)\n * - Terminal output\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { ITerminalTracker } from '@jupyterlab/terminal';\nimport { PageConfig } from '@jupyterlab/coreutils';\n/**\n * Plugin namespace\n */\nconst PLUGIN_ID = '@hdsp-agent/idle-monitor';\nconst CONFIG_STORAGE_KEY = 'hdsp-agent-llm-config';\n/**\n * Default idle timeout (in minutes)\n */\nconst DEFAULT_IDLE_TIMEOUT_MINUTES = 60;\n// Check intervals - must be shorter than warning period to detect it\nconst CHECK_INTERVAL_NORMAL_MS = 10 * 1000; // Check every 10 seconds (normal mode)\nconst CHECK_INTERVAL_WARNING_MS = 1000; // Check every second (warning mode for countdown)\n// Warning period: show countdown before timeout (minimum 30 seconds, or 25% of timeout)\nconst MIN_WARNING_PERIOD_MS = 30 * 1000; // Minimum 30 seconds warning\n/**\n * Idle Monitor Service\n */\nclass IdleMonitorService {\n constructor() {\n this.checkIntervalId = null;\n this.countdownElement = null;\n this.isShuttingDown = false;\n this.notebookTracker = null;\n this.terminalTracker = null;\n this.isInWarningMode = false;\n this.isDisabled = false;\n /**\n * Throttle mousemove events to reduce performance impact\n */\n this.mouseMoveThrottleTime = 0;\n this.lastActivityTime = Date.now();\n // Initialize timeout from localStorage config\n this.idleTimeoutMinutes = this.loadTimeoutFromConfig();\n this.isDisabled = this.idleTimeoutMinutes === 0;\n this.idleTimeoutMs = this.idleTimeoutMinutes * 60 * 1000;\n // Warning period: 25% of timeout or minimum 30 seconds\n const warningPeriod = Math.max(MIN_WARNING_PERIOD_MS, this.idleTimeoutMs * 0.25);\n this.warningStartMs = this.idleTimeoutMs - warningPeriod;\n this.setupActivityListeners();\n this.setupStorageListener();\n this.createCountdownElement();\n if (!this.isDisabled) {\n this.startIdleCheck();\n }\n console.log('[IdleMonitor] Service initialized. Idle timeout:', this.idleTimeoutMinutes, 'minutes, warning at:', Math.round(this.warningStartMs / 1000), 'sec', this.isDisabled ? '(DISABLED)' : '');\n }\n /**\n * Load timeout configuration from localStorage\n */\n loadTimeoutFromConfig() {\n try {\n const stored = localStorage.getItem(CONFIG_STORAGE_KEY);\n if (stored) {\n const config = JSON.parse(stored);\n if (typeof config.idleTimeoutMinutes === 'number') {\n return config.idleTimeoutMinutes;\n }\n }\n }\n catch (e) {\n console.warn('[IdleMonitor] Failed to load config from localStorage:', e);\n }\n return DEFAULT_IDLE_TIMEOUT_MINUTES;\n }\n /**\n * Setup listener for localStorage changes (config updates from settings panel)\n */\n setupStorageListener() {\n window.addEventListener('storage', (e) => {\n if (e.key === CONFIG_STORAGE_KEY) {\n const newTimeout = this.loadTimeoutFromConfig();\n if (newTimeout !== this.idleTimeoutMinutes) {\n this.updateTimeout(newTimeout);\n }\n }\n });\n // Also listen for custom event (same-tab config changes)\n window.addEventListener('hdsp-config-updated', () => {\n const newTimeout = this.loadTimeoutFromConfig();\n if (newTimeout !== this.idleTimeoutMinutes) {\n this.updateTimeout(newTimeout);\n }\n });\n }\n /**\n * Update timeout dynamically\n */\n updateTimeout(minutes) {\n const wasDisabled = this.isDisabled;\n this.idleTimeoutMinutes = minutes;\n this.isDisabled = minutes === 0;\n this.idleTimeoutMs = minutes * 60 * 1000;\n // Warning period: 25% of timeout or minimum 30 seconds\n const warningPeriod = Math.max(MIN_WARNING_PERIOD_MS, this.idleTimeoutMs * 0.25);\n this.warningStartMs = this.idleTimeoutMs - warningPeriod;\n console.log('[IdleMonitor] Timeout updated to:', minutes, 'minutes', this.isDisabled ? '(DISABLED)' : '');\n // Stop idle check if disabled\n if (this.isDisabled) {\n this.stopIdleCheck();\n this.hideCountdown();\n }\n else if (wasDisabled) {\n // Re-enable if was disabled\n this.resetIdleTimer();\n this.startIdleCheck();\n }\n }\n /**\n * Stop idle check interval\n */\n stopIdleCheck() {\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n this.checkIntervalId = null;\n }\n this.isInWarningMode = false;\n console.log('[IdleMonitor] Idle check stopped');\n }\n /**\n * Set notebook tracker for monitoring cell execution\n */\n setNotebookTracker(tracker) {\n this.notebookTracker = tracker;\n // Monitor cell execution changes\n tracker.currentChanged.connect(() => {\n this.resetIdleTimer();\n });\n // Monitor active cell changes\n tracker.activeCellChanged.connect(() => {\n this.resetIdleTimer();\n });\n console.log('[IdleMonitor] Notebook tracker connected');\n }\n /**\n * Set terminal tracker for monitoring terminal activity\n */\n setTerminalTracker(tracker) {\n this.terminalTracker = tracker;\n // Monitor terminal changes\n tracker.currentChanged.connect(() => {\n this.resetIdleTimer();\n });\n // Setup monitoring for existing terminals\n tracker.forEach(terminal => {\n this.setupTerminalMonitoring(terminal);\n });\n // Monitor new terminals\n tracker.widgetAdded.connect((_, terminal) => {\n this.setupTerminalMonitoring(terminal);\n });\n console.log('[IdleMonitor] Terminal tracker connected');\n }\n /**\n * Setup monitoring for a single terminal\n * Simply reset idle timer on any terminal output\n */\n setupTerminalMonitoring(terminal) {\n const terminalId = terminal.id || terminal.session?.name || 'unknown';\n const session = terminal.session;\n if (!session)\n return;\n // Listen for terminal output - any output resets idle timer\n session.messageReceived.connect(() => {\n this.resetIdleTimer();\n });\n console.log('[IdleMonitor] Terminal monitoring setup:', terminalId);\n }\n /**\n * Setup global activity listeners\n */\n setupActivityListeners() {\n // Keyboard events\n document.addEventListener('keydown', this.handleActivity.bind(this), true);\n document.addEventListener('keyup', this.handleActivity.bind(this), true);\n document.addEventListener('keypress', this.handleActivity.bind(this), true);\n // Mouse events\n document.addEventListener('mousedown', this.handleActivity.bind(this), true);\n document.addEventListener('mouseup', this.handleActivity.bind(this), true);\n document.addEventListener('mousemove', this.throttledMouseMove.bind(this), true);\n document.addEventListener('wheel', this.handleActivity.bind(this), true);\n document.addEventListener('click', this.handleActivity.bind(this), true);\n // Touch events (for tablet support)\n document.addEventListener('touchstart', this.handleActivity.bind(this), true);\n document.addEventListener('touchend', this.handleActivity.bind(this), true);\n console.log('[IdleMonitor] Activity listeners attached');\n }\n throttledMouseMove() {\n const now = Date.now();\n if (now - this.mouseMoveThrottleTime > 5000) { // Only update every 5 seconds for mouse move\n this.mouseMoveThrottleTime = now;\n this.handleActivity();\n }\n }\n /**\n * Handle any user activity\n */\n handleActivity() {\n this.resetIdleTimer();\n }\n /**\n * Reset the idle timer\n */\n resetIdleTimer() {\n this.lastActivityTime = Date.now();\n this.isShuttingDown = false;\n this.hideCountdown();\n // Switch back to normal mode if in warning mode\n this.switchToNormalMode();\n }\n /**\n * Create countdown display element (fixed position, top-right)\n */\n createCountdownElement() {\n // Create countdown container with fixed positioning\n this.countdownElement = document.createElement('div');\n this.countdownElement.id = 'hdsp-idle-countdown';\n this.countdownElement.className = 'hdsp-idle-countdown';\n this.countdownElement.style.cssText = `\n display: none;\n position: fixed;\n right: 20px;\n top: 10px;\n background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%);\n color: white;\n padding: 8px 16px;\n border-radius: 20px;\n font-size: 14px;\n font-weight: 600;\n box-shadow: 0 4px 12px rgba(238, 90, 90, 0.5);\n z-index: 99999;\n white-space: nowrap;\n cursor: pointer;\n user-select: none;\n `;\n // Click to dismiss and reset timer\n this.countdownElement.addEventListener('click', () => {\n this.resetIdleTimer();\n console.log('[IdleMonitor] User clicked countdown - timer reset');\n });\n // Add pulse animation\n const style = document.createElement('style');\n style.id = 'hdsp-idle-countdown-style';\n style.textContent = `\n @keyframes hdsp-pulse {\n 0% { transform: scale(1); }\n 50% { transform: scale(1.03); }\n 100% { transform: scale(1); }\n }\n\n .hdsp-idle-countdown {\n animation: hdsp-pulse 2s infinite;\n }\n\n .hdsp-idle-countdown.warning {\n background: linear-gradient(135deg, #ffa726 0%, #fb8c00 100%) !important;\n box-shadow: 0 4px 12px rgba(251, 140, 0, 0.5) !important;\n }\n\n .hdsp-idle-countdown.critical {\n background: linear-gradient(135deg, #ef5350 0%, #d32f2f 100%) !important;\n box-shadow: 0 4px 12px rgba(211, 47, 47, 0.6) !important;\n animation: hdsp-pulse-critical 0.5s infinite !important;\n }\n\n @keyframes hdsp-pulse-critical {\n 0% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.05); opacity: 0.9; }\n 100% { transform: scale(1); opacity: 1; }\n }\n `;\n // Remove existing style if any\n const existingStyle = document.getElementById('hdsp-idle-countdown-style');\n if (existingStyle) {\n existingStyle.remove();\n }\n document.head.appendChild(style);\n // Append to body for fixed positioning\n document.body.appendChild(this.countdownElement);\n console.log('[IdleMonitor] Countdown element created (fixed position)');\n }\n /**\n * Show countdown in toolbar\n */\n showCountdown(secondsRemaining) {\n console.log(`[IdleMonitor] showCountdown called: ${secondsRemaining}s remaining`);\n if (!this.countdownElement) {\n this.createCountdownElement();\n return;\n }\n this.countdownElement.style.display = 'block';\n this.countdownElement.textContent = `Idle Shutdown ${secondsRemaining}초 전`;\n // Update styling based on urgency\n this.countdownElement.classList.remove('warning', 'critical');\n if (secondsRemaining <= 10) {\n this.countdownElement.classList.add('critical');\n }\n else if (secondsRemaining <= 30) {\n this.countdownElement.classList.add('warning');\n }\n }\n /**\n * Hide countdown display\n */\n hideCountdown() {\n if (this.countdownElement) {\n this.countdownElement.style.display = 'none';\n }\n }\n /**\n * Check if any notebook cells are currently executing\n * Uses kernel status for reliability\n */\n hasRunningCells() {\n if (!this.notebookTracker)\n return false;\n // Check all open notebooks via kernel status\n const notebooks = this.notebookTracker.filter(() => true);\n for (const notebook of notebooks) {\n const session = notebook.sessionContext?.session;\n if (session) {\n const kernelStatus = session.kernel?.status;\n // Kernel is busy = cells are executing\n if (kernelStatus === 'busy') {\n return true;\n }\n }\n // Fallback: also check DOM for [*] indicator (for edge cases)\n if (notebook.content) {\n const cells = notebook.content.widgets;\n for (const cell of cells) {\n const model = cell.model;\n if (model && model.type === 'code') {\n const promptNode = cell.node.querySelector('.jp-InputPrompt');\n if (promptNode && promptNode.textContent?.includes('*')) {\n return true;\n }\n }\n }\n }\n }\n return false;\n }\n /**\n * Start idle checking interval\n * Starts in normal mode (10 min), switches to warning mode (1 sec) when needed\n */\n startIdleCheck() {\n this.checkIntervalId = window.setInterval(() => {\n this.checkIdleStatus();\n }, CHECK_INTERVAL_NORMAL_MS);\n console.log('[IdleMonitor] Started in normal mode: check every 10 seconds');\n }\n /**\n * Switch to warning mode (faster checks for countdown)\n */\n switchToWarningMode() {\n if (this.isInWarningMode)\n return;\n this.isInWarningMode = true;\n // Clear normal interval\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n }\n // Start warning interval (1 second)\n this.checkIntervalId = window.setInterval(() => {\n this.checkIdleStatus();\n }, CHECK_INTERVAL_WARNING_MS);\n console.log('[IdleMonitor] Switched to warning mode: check every', CHECK_INTERVAL_WARNING_MS / 1000, 'seconds');\n }\n /**\n * Switch back to normal mode\n */\n switchToNormalMode() {\n if (!this.isInWarningMode)\n return;\n this.isInWarningMode = false;\n // Clear warning interval\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n }\n // Start normal interval (10 minutes)\n this.checkIntervalId = window.setInterval(() => {\n this.checkIdleStatus();\n }, CHECK_INTERVAL_NORMAL_MS);\n console.log('[IdleMonitor] Switched to normal mode: check every 10 seconds');\n }\n /**\n * Check current idle status\n */\n checkIdleStatus() {\n // Skip if disabled\n if (this.isDisabled) {\n return;\n }\n // Skip if notebook cells are running (not idle)\n if (this.hasRunningCells()) {\n this.resetIdleTimer();\n return;\n }\n const now = Date.now();\n const idleTime = now - this.lastActivityTime;\n const remainingTime = this.idleTimeoutMs - idleTime;\n const secondsRemaining = Math.ceil(remainingTime / 1000);\n // Debug log every 10 seconds\n if (Math.floor(idleTime / 1000) % 10 === 0) {\n console.log(`[IdleMonitor] idle=${Math.round(idleTime / 1000)}s, warningAt=${Math.round(this.warningStartMs / 1000)}s, timeout=${Math.round(this.idleTimeoutMs / 1000)}s, remaining=${secondsRemaining}s`);\n }\n // Enter warning period - switch to fast checking for countdown\n if (idleTime >= this.warningStartMs && !this.isShuttingDown) {\n this.switchToWarningMode();\n this.showCountdown(secondsRemaining);\n }\n // Trigger shutdown if idle timeout reached\n if (idleTime >= this.idleTimeoutMs && !this.isShuttingDown) {\n this.isShuttingDown = true;\n this.triggerShutdown();\n }\n }\n /**\n * Trigger shutdown process\n */\n async triggerShutdown() {\n console.log('[IdleMonitor] Idle timeout reached. Triggering shutdown...');\n // Hide countdown\n this.hideCountdown();\n // Call shutdown API FIRST (non-blocking) - shutdown starts immediately\n this.callShutdownApi().catch(e => console.error('[IdleMonitor] Shutdown API error:', e));\n // Show notification popup (for user info only - shutdown already initiated)\n alert(`${this.idleTimeoutMinutes}분 동안 활동이 없어 세션이 종료됩니다.`);\n }\n /**\n * Call HDSP shutdown API via Jupyter server endpoint\n * Server-side call to avoid CORS issues\n */\n async callShutdownApi() {\n try {\n // Get base URL from PageConfig\n const baseUrl = PageConfig.getBaseUrl();\n const apiUrl = `${baseUrl}hdsp-agent/idle-shutdown`;\n console.log('[IdleMonitor] Calling shutdown API via server:', apiUrl);\n // Call server endpoint (which will call HDSP API)\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n const result = await response.json();\n if (response.ok && result.success) {\n console.log('[IdleMonitor] Shutdown API call successful:', result.message);\n }\n else {\n console.error('[IdleMonitor] Shutdown API call failed:', result.error || response.statusText);\n }\n }\n catch (error) {\n console.error('[IdleMonitor] Shutdown API call error:', error);\n }\n }\n /**\n * Get current idle time in milliseconds\n */\n getIdleTimeMs() {\n return Date.now() - this.lastActivityTime;\n }\n /**\n * Manually trigger activity (for external use)\n */\n triggerActivity() {\n this.resetIdleTimer();\n }\n /**\n * Cleanup service\n */\n dispose() {\n if (this.checkIntervalId !== null) {\n clearInterval(this.checkIntervalId);\n this.checkIntervalId = null;\n }\n if (this.countdownElement && this.countdownElement.parentNode) {\n this.countdownElement.parentNode.removeChild(this.countdownElement);\n }\n console.log('[IdleMonitor] Service disposed');\n }\n}\n/**\n * Global idle monitor instance\n */\nlet idleMonitor = null;\n/**\n * Get idle monitor instance\n */\nexport function getIdleMonitor() {\n return idleMonitor;\n}\n/**\n * Idle Monitor Plugin\n */\nexport const idleMonitorPlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [],\n optional: [INotebookTracker, ITerminalTracker],\n activate: (app, notebookTracker, terminalTracker) => {\n console.log('[IdleMonitorPlugin] Activating Idle Monitor');\n try {\n // Create idle monitor service\n idleMonitor = new IdleMonitorService();\n // Connect notebook tracker if available\n if (notebookTracker) {\n idleMonitor.setNotebookTracker(notebookTracker);\n }\n // Connect terminal tracker if available\n if (terminalTracker) {\n idleMonitor.setTerminalTracker(terminalTracker);\n }\n // Store reference globally for debugging\n window._hdspIdleMonitor = idleMonitor;\n console.log('[IdleMonitorPlugin] Idle Monitor activated successfully');\n }\n catch (error) {\n console.error('[IdleMonitorPlugin] Failed to activate:', error);\n }\n }\n};\n","/**\n * LSP Bridge Plugin\n *\n * jupyterlab-lsp와 HDSP Agent를 연결하는 브릿지\n * Crush 패턴 적용: Version-based 캐싱, 이벤트 기반 업데이트\n *\n * 주요 기능:\n * - LSP 진단 결과 캐싱 및 조회\n * - 심볼 참조 검색\n * - Agent 도구와 연동\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\n/**\n * Plugin namespace\n */\nconst PLUGIN_ID = '@hdsp-agent/lsp-bridge';\n/**\n * LSP Diagnostic severity levels\n */\nexport var DiagnosticSeverity;\n(function (DiagnosticSeverity) {\n DiagnosticSeverity[DiagnosticSeverity[\"Error\"] = 1] = \"Error\";\n DiagnosticSeverity[DiagnosticSeverity[\"Warning\"] = 2] = \"Warning\";\n DiagnosticSeverity[DiagnosticSeverity[\"Information\"] = 3] = \"Information\";\n DiagnosticSeverity[DiagnosticSeverity[\"Hint\"] = 4] = \"Hint\";\n})(DiagnosticSeverity || (DiagnosticSeverity = {}));\n/**\n * LSP State Cache (Crush의 VersionedMap 패턴)\n * 버전 기반 캐시 무효화로 불필요한 재계산 방지\n */\nclass LSPStateCache {\n constructor() {\n this.diagnosticsVersion = 0;\n this.diagnosticsCache = new Map();\n this.summaryCache = null;\n this.summaryVersion = -1;\n }\n /**\n * Update diagnostics for a file\n */\n updateDiagnostics(uri, diagnostics) {\n this.diagnosticsCache.set(uri, diagnostics);\n this.diagnosticsVersion++;\n }\n /**\n * Get diagnostics for a specific file or all files\n */\n getDiagnostics(uri) {\n if (uri) {\n return {\n version: this.diagnosticsVersion,\n diagnostics: this.diagnosticsCache.get(uri) || []\n };\n }\n return {\n version: this.diagnosticsVersion,\n diagnostics: this.diagnosticsCache\n };\n }\n /**\n * Get diagnostic counts with caching (Crush 패턴)\n */\n getDiagnosticSummary() {\n // Return cached if version matches\n if (this.summaryVersion === this.diagnosticsVersion && this.summaryCache) {\n return this.summaryCache;\n }\n // Recalculate\n let errors = 0;\n let warnings = 0;\n let hints = 0;\n for (const diags of this.diagnosticsCache.values()) {\n for (const d of diags) {\n switch (d.severity) {\n case DiagnosticSeverity.Error:\n errors++;\n break;\n case DiagnosticSeverity.Warning:\n warnings++;\n break;\n default:\n hints++;\n }\n }\n }\n this.summaryCache = {\n errors,\n warnings,\n hints,\n total: errors + warnings + hints,\n version: this.diagnosticsVersion\n };\n this.summaryVersion = this.diagnosticsVersion;\n return this.summaryCache;\n }\n /**\n * Clear diagnostics for a file\n */\n clearDiagnostics(uri) {\n if (this.diagnosticsCache.has(uri)) {\n this.diagnosticsCache.delete(uri);\n this.diagnosticsVersion++;\n }\n }\n /**\n * Clear all diagnostics\n */\n clearAll() {\n this.diagnosticsCache.clear();\n this.diagnosticsVersion++;\n }\n /**\n * Get current version\n */\n getVersion() {\n return this.diagnosticsVersion;\n }\n}\n/**\n * LSP Bridge Service\n * Agent 도구에서 LSP 기능을 사용할 수 있게 해주는 서비스\n */\nclass LSPBridgeService {\n constructor() {\n this.cache = new LSPStateCache();\n this.lspAvailable = false;\n this.notebookTracker = null;\n this.checkLSPAvailability();\n }\n /**\n * Check if jupyterlab-lsp is available\n */\n async checkLSPAvailability() {\n try {\n // jupyterlab-lsp가 설치되어 있는지 확인\n // 실제로는 ILSPDocumentConnectionManager 토큰을 통해 확인\n this.lspAvailable = false; // 기본값, 실제 연결 시 true로 변경\n console.log('[LSPBridge] LSP availability check - will be updated when connection established');\n }\n catch (error) {\n console.warn('[LSPBridge] LSP not available:', error);\n this.lspAvailable = false;\n }\n }\n /**\n * Set notebook tracker for document access\n */\n setNotebookTracker(tracker) {\n this.notebookTracker = tracker;\n }\n /**\n * Mark LSP as available (called when connection is established)\n */\n setLSPAvailable(available) {\n this.lspAvailable = available;\n console.log('[LSPBridge] LSP available:', available);\n }\n /**\n * Check if LSP is available\n */\n isLSPAvailable() {\n return this.lspAvailable;\n }\n /**\n * Update diagnostics from external source (e.g., jupyterlab-lsp)\n */\n updateDiagnostics(uri, diagnostics) {\n this.cache.updateDiagnostics(uri, diagnostics);\n this.notifyDiagnosticsChanged(uri);\n }\n /**\n * Get diagnostics for Agent tool\n * Returns formatted diagnostics for a file or entire project\n */\n async getDiagnostics(filePath) {\n const result = this.cache.getDiagnostics(filePath ? this.pathToUri(filePath) : undefined);\n const summary = this.cache.getDiagnosticSummary();\n const formattedDiagnostics = [];\n if (filePath) {\n // Single file\n const diags = result.diagnostics;\n for (const d of diags) {\n formattedDiagnostics.push({\n severity: this.severityToString(d.severity),\n line: d.range.start.line + 1,\n character: d.range.start.character,\n message: d.message,\n source: d.source,\n code: d.code,\n file: filePath\n });\n }\n }\n else {\n // All files\n const diagsMap = result.diagnostics;\n for (const [uri, diags] of diagsMap) {\n const file = this.uriToPath(uri);\n for (const d of diags) {\n formattedDiagnostics.push({\n severity: this.severityToString(d.severity),\n line: d.range.start.line + 1,\n character: d.range.start.character,\n message: d.message,\n source: d.source,\n code: d.code,\n file\n });\n }\n }\n }\n // Sort: errors first, then by file and line (Crush 패턴)\n formattedDiagnostics.sort((a, b) => {\n const severityOrder = { error: 0, warning: 1, information: 2, hint: 3 };\n const severityDiff = (severityOrder[a.severity] || 3) - (severityOrder[b.severity] || 3);\n if (severityDiff !== 0)\n return severityDiff;\n const fileDiff = a.file.localeCompare(b.file);\n if (fileDiff !== 0)\n return fileDiff;\n return a.line - b.line;\n });\n return {\n success: true,\n diagnostics: formattedDiagnostics,\n summary\n };\n }\n /**\n * Find references for a symbol (Crush의 Grep-then-LSP 패턴)\n * 현재는 grep 기반으로 구현, LSP 연결 시 향상 가능\n */\n async getReferences(symbol, filePath, line, character) {\n // LSP가 없으면 grep 기반으로 검색하도록 안내\n if (!this.lspAvailable) {\n return {\n success: false,\n locations: []\n };\n }\n // TODO: jupyterlab-lsp 연결 시 구현\n // 현재는 빈 결과 반환 (Agent가 execute_command_tool with grep 사용하도록)\n return {\n success: false,\n locations: []\n };\n }\n /**\n * Get diagnostic summary\n */\n getDiagnosticSummary() {\n return this.cache.getDiagnosticSummary();\n }\n /**\n * Notify diagnostics changed event\n */\n notifyDiagnosticsChanged(uri) {\n const summary = this.cache.getDiagnosticSummary();\n window.dispatchEvent(new CustomEvent('hdsp-lsp-diagnostics-changed', {\n detail: { uri, summary }\n }));\n }\n /**\n * Convert severity number to string\n */\n severityToString(severity) {\n switch (severity) {\n case DiagnosticSeverity.Error:\n return 'error';\n case DiagnosticSeverity.Warning:\n return 'warning';\n case DiagnosticSeverity.Information:\n return 'information';\n case DiagnosticSeverity.Hint:\n return 'hint';\n default:\n return 'hint';\n }\n }\n /**\n * Convert file path to URI\n */\n pathToUri(path) {\n if (path.startsWith('file://')) {\n return path;\n }\n return `file://${path.startsWith('/') ? '' : '/'}${path}`;\n }\n /**\n * Convert URI to file path\n */\n uriToPath(uri) {\n if (uri.startsWith('file://')) {\n return uri.replace('file://', '');\n }\n return uri;\n }\n /**\n * Dispose service\n */\n dispose() {\n this.cache.clearAll();\n console.log('[LSPBridge] Service disposed');\n }\n}\n/**\n * Global LSP Bridge instance\n */\nlet lspBridge = null;\n/**\n * Get LSP Bridge instance\n */\nexport function getLSPBridge() {\n return lspBridge;\n}\n/**\n * LSP Bridge Plugin\n *\n * jupyterlab-lsp가 설치되어 있으면 연동하고,\n * 없으면 기본 기능만 제공\n */\nexport const lspBridgePlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [],\n optional: [INotebookTracker],\n activate: (app, notebookTracker) => {\n console.log('[LSPBridgePlugin] Activating LSP Bridge...');\n // Create LSP Bridge service\n lspBridge = new LSPBridgeService();\n // Connect notebook tracker if available\n if (notebookTracker) {\n lspBridge.setNotebookTracker(notebookTracker);\n }\n // Store reference globally for debugging and Agent access\n window._hdspLSPBridge = lspBridge;\n // Try to connect to jupyterlab-lsp if available\n // This is done dynamically to avoid hard dependency\n tryConnectToLSP(app, lspBridge);\n console.log('[LSPBridgePlugin] LSP Bridge activated');\n }\n};\n/**\n * Try to connect to jupyterlab-lsp extension\n */\nasync function tryConnectToLSP(app, bridge) {\n try {\n // Check if ILSPDocumentConnectionManager is available\n // This token is provided by jupyterlab-lsp\n const lspToken = '@jupyterlab/lsp:ILSPDocumentConnectionManager';\n // Try to get the LSP manager from the application\n // Note: This requires jupyterlab-lsp to be installed\n const hasLSP = app.commands.hasCommand('lsp:show-diagnostics-panel');\n if (hasLSP) {\n console.log('[LSPBridgePlugin] jupyterlab-lsp detected');\n bridge.setLSPAvailable(true);\n // Listen for LSP diagnostic events\n // jupyterlab-lsp publishes diagnostics through its own mechanism\n setupLSPEventListeners(bridge);\n }\n else {\n console.log('[LSPBridgePlugin] jupyterlab-lsp not detected, using fallback mode');\n bridge.setLSPAvailable(false);\n }\n }\n catch (error) {\n console.warn('[LSPBridgePlugin] Failed to connect to jupyterlab-lsp:', error);\n bridge.setLSPAvailable(false);\n }\n}\n/**\n * Setup event listeners for LSP events\n */\nfunction setupLSPEventListeners(bridge) {\n // jupyterlab-lsp uses its own event system\n // We'll listen for changes through polling or direct integration\n // This is a placeholder for future implementation\n console.log('[LSPBridgePlugin] LSP event listeners setup (placeholder)');\n // For now, we can use a simple polling mechanism or\n // hook into JupyterLab's document change events\n}\nexport default lspBridgePlugin;\n","/**\n * Prompt Generation Plugin\n * Adds \"프롬프트 생성\" to Jupyter Launcher\n */\nimport { ILauncher } from '@jupyterlab/launcher';\nimport { ICommandPalette, ReactWidget, Notification } from '@jupyterlab/apputils';\nimport { IDocumentManager } from '@jupyterlab/docmanager';\nimport { LabIcon } from '@jupyterlab/ui-components';\nimport { Widget } from '@lumino/widgets';\nimport React from 'react';\nimport { ThemeProvider, createTheme } from '@mui/material';\nimport hdspIconSvg from '../styles/icons/hdsp-icon.svg';\nimport { PromptGenerationDialog } from '../components/PromptGenerationDialog';\nimport { TaskProgressWidget } from '../components/TaskProgressWidget';\nimport { TaskService } from '../services/TaskService';\n/**\n * Plugin constants\n */\nconst PLUGIN_ID = '@hdsp-agent/prompt-generation';\nconst COMMAND_ID = 'hdsp-agent:generate-from-prompt';\nconst CATEGORY = 'HALO Agent';\n/**\n * HALO Icon\n */\nconst hdspIcon = new LabIcon({\n name: 'hdsp-agent:hdsp-icon',\n svgstr: hdspIconSvg\n});\n/**\n * Task Manager Widget\n * Manages active notebook generation tasks\n */\nclass TaskManagerWidget extends ReactWidget {\n constructor(app, docManager) {\n super();\n this.app = app;\n this.docManager = docManager;\n this.taskService = new TaskService();\n this.activeTasks = new Map();\n this.addClass('jp-TaskManagerWidget');\n }\n /**\n * Start a new notebook generation task\n */\n async startGeneration(prompt) {\n try {\n // Start generation\n const response = await this.taskService.generateNotebook({ prompt });\n const taskId = response.taskId;\n // Subscribe to progress\n this.taskService.subscribeToTaskProgress(taskId, (status) => {\n this.activeTasks.set(taskId, status);\n this.update();\n // Show notification on completion\n if (status.status === 'completed' && status.notebookPath) {\n Notification.success(`노트북 생성 완료: ${status.notebookPath.split('/').pop()}`, {\n autoClose: 5000\n });\n }\n else if (status.status === 'failed') {\n Notification.error(`노트북 생성 실패: ${status.error}`, {\n autoClose: 10000\n });\n }\n }, (error) => {\n console.error('Task progress error:', error);\n Notification.error('진행상황 연결 실패', { autoClose: 5000 });\n }, () => {\n // Task completed, keep in list for user to review\n this.update();\n });\n // Show initial notification\n Notification.info('노트북 생성을 시작했습니다', { autoClose: 3000 });\n this.update();\n }\n catch (error) {\n console.error('Failed to start generation:', error);\n Notification.error(`생성 시작 실패: ${error.message}`, {\n autoClose: 5000\n });\n }\n }\n /**\n * Cancel a task\n */\n async cancelTask(taskId) {\n try {\n await this.taskService.cancelTask(taskId);\n this.activeTasks.delete(taskId);\n this.update();\n Notification.info('작업을 취소했습니다', { autoClose: 3000 });\n }\n catch (error) {\n console.error('Failed to cancel task:', error);\n Notification.error(`취소 실패: ${error.message}`, { autoClose: 5000 });\n }\n }\n /**\n * Close/remove task from display\n */\n closeTask(taskId) {\n this.taskService.unsubscribeFromTask(taskId);\n this.activeTasks.delete(taskId);\n this.update();\n }\n /**\n * Open generated notebook\n */\n async openNotebook(notebookPath, taskId) {\n try {\n // Extract just the filename from the path\n const filename = notebookPath.split('/').pop() || notebookPath;\n // Open the notebook\n await this.docManager.openOrReveal(filename);\n // Close the task widget after opening\n this.closeTask(taskId);\n Notification.success(`노트북을 열었습니다: ${filename}`, {\n autoClose: 3000\n });\n }\n catch (error) {\n console.error('Failed to open notebook:', error);\n Notification.error(`노트북 열기 실패: ${error.message}`, {\n autoClose: 5000\n });\n }\n }\n render() {\n const theme = createTheme();\n const tasks = Array.from(this.activeTasks.entries());\n return (React.createElement(ThemeProvider, { theme: theme },\n React.createElement(\"div\", { style: { position: 'fixed', bottom: 0, right: 0, zIndex: 1300 } }, tasks.map(([taskId, status], index) => (React.createElement(\"div\", { key: taskId, style: {\n marginBottom: index < tasks.length - 1 ? '10px' : '0'\n } },\n React.createElement(TaskProgressWidget, { taskStatus: status, onClose: () => this.closeTask(taskId), onCancel: () => this.cancelTask(taskId), onOpenNotebook: () => status.notebookPath &&\n this.openNotebook(status.notebookPath, taskId) })))))));\n }\n dispose() {\n this.taskService.dispose();\n super.dispose();\n }\n}\n/**\n * Dialog Widget for prompt input\n */\nclass PromptDialogWidget extends ReactWidget {\n constructor(onGenerate, onClose) {\n super();\n this._onGenerate = onGenerate;\n this._onClose = onClose;\n }\n render() {\n const theme = createTheme();\n return (React.createElement(ThemeProvider, { theme: theme },\n React.createElement(PromptGenerationDialog, { open: true, onClose: this._onClose, onGenerate: this._onGenerate })));\n }\n}\n/**\n * Prompt Generation Plugin\n */\nexport const promptGenerationPlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [IDocumentManager],\n optional: [ILauncher, ICommandPalette],\n activate: (app, docManager, launcher, palette) => {\n console.log('[PromptGenerationPlugin] Activating');\n try {\n // Create task manager widget\n const taskManager = new TaskManagerWidget(app, docManager);\n taskManager.id = 'hdsp-agent-task-manager';\n taskManager.title.label = 'Task Manager';\n // Add to shell but keep it as a floating overlay (not in main area)\n // The widget renders itself as a fixed position element\n Widget.attach(taskManager, document.body);\n // Add command\n app.commands.addCommand(COMMAND_ID, {\n label: '프롬프트로 노트북 만들기',\n caption: '프롬프트로 노트북 만들기',\n icon: hdspIcon,\n execute: () => {\n // Create dialog widget\n let dialogWidget = null;\n const onGenerate = async (prompt) => {\n if (dialogWidget) {\n dialogWidget.dispose();\n dialogWidget = null;\n }\n await taskManager.startGeneration(prompt);\n };\n const onClose = () => {\n if (dialogWidget) {\n dialogWidget.dispose();\n dialogWidget = null;\n }\n };\n dialogWidget = new PromptDialogWidget(onGenerate, onClose);\n dialogWidget.id = 'hdsp-agent-prompt-dialog';\n dialogWidget.title.label = '';\n app.shell.add(dialogWidget, 'main');\n }\n });\n // Add to launcher\n if (launcher) {\n launcher.add({\n command: COMMAND_ID,\n category: 'Notebook',\n rank: 1\n });\n }\n // Add to command palette\n if (palette) {\n palette.addItem({\n command: COMMAND_ID,\n category: CATEGORY\n });\n }\n console.log('[PromptGenerationPlugin] Activated successfully');\n }\n catch (error) {\n console.error('[PromptGenerationPlugin] Failed to activate:', error);\n }\n }\n};\n","/**\n * Save Interceptor Plugin\n * Intercepts notebook save operations to offer code review before saving\n * Based on chrome_agent's save interception functionality\n */\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { ICommandPalette } from '@jupyterlab/apputils';\n/**\n * Save Interceptor Plugin\n */\nexport const saveInterceptorPlugin = {\n id: '@hdsp-agent/save-interceptor',\n autoStart: true,\n requires: [INotebookTracker],\n optional: [ICommandPalette],\n activate: (app, notebookTracker, palette) => {\n console.log('[SaveInterceptorPlugin] Activated');\n let isSaving = false;\n let originalSaveCommand = null;\n // Store original save command and wrap it\n if (app.commands.hasCommand('docmanager:save')) {\n console.log('[SaveInterceptorPlugin] Found docmanager:save command');\n const commandRegistry = app.commands;\n const commandId = 'docmanager:save';\n // Get the original command's execute function\n const originalCommand = commandRegistry._commands.get(commandId);\n if (originalCommand) {\n originalSaveCommand = originalCommand.execute.bind(originalCommand);\n console.log('[SaveInterceptorPlugin] Stored original save command');\n // Replace the execute function\n originalCommand.execute = async (args) => {\n // Use app.shell.currentWidget to get the actually focused widget\n const currentWidget = app.shell.currentWidget;\n console.log('[SaveInterceptorPlugin] Save command triggered', {\n hasCurrentWidget: !!currentWidget,\n isSaving: isSaving,\n widgetType: currentWidget?.constructor?.name\n });\n // Only intercept for Python files (.ipynb, .py)\n if (!currentWidget) {\n console.log('[SaveInterceptorPlugin] No current widget, allowing default save');\n return originalSaveCommand(args);\n }\n // Check if the current widget has a context (is a document)\n const context = currentWidget.context;\n if (!context) {\n console.log('[SaveInterceptorPlugin] No context, allowing default save');\n return originalSaveCommand(args);\n }\n // Check if the current document is a Python file\n const path = context?.path || '';\n const isPythonFile = path.endsWith('.ipynb') || path.endsWith('.py');\n if (!isPythonFile) {\n console.log('[SaveInterceptorPlugin] Not a Python file, allowing default save', { path });\n return originalSaveCommand(args);\n }\n if (isSaving) {\n console.log('[SaveInterceptorPlugin] Already in save process, executing actual save');\n return originalSaveCommand(args);\n }\n console.log('[SaveInterceptorPlugin] Intercepting Python file save, showing modal', { path });\n await handleSaveIntercept(currentWidget, app, originalSaveCommand, args, () => isSaving = true, () => isSaving = false);\n };\n console.log('[SaveInterceptorPlugin] Wrapped save command installed');\n }\n else {\n console.error('[SaveInterceptorPlugin] Could not find original save command');\n }\n }\n // Also intercept keyboard shortcut as backup\n document.addEventListener('keydown', async (e) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 's') {\n // Use app.shell.currentWidget instead of notebookTracker to get the actually focused widget\n const currentWidget = app.shell.currentWidget;\n if (!currentWidget) {\n return; // Not in a document, let default save happen\n }\n // Check if the current widget has a context (is a document)\n const context = currentWidget.context;\n if (!context) {\n return; // Not a document widget, let default save happen\n }\n // Check if the current document is a Python file\n const path = context?.path || '';\n const isPythonFile = path.endsWith('.ipynb') || path.endsWith('.py');\n if (!isPythonFile) {\n console.log('[SaveInterceptorPlugin] Not a Python file, allowing default save', { path });\n return; // Not a Python file, let default save happen\n }\n console.log('[SaveInterceptorPlugin] Save shortcut (Ctrl/Cmd+S) detected for Python file', { path });\n e.preventDefault();\n e.stopPropagation();\n if (isSaving) {\n console.log('[SaveInterceptorPlugin] Already saving, ignoring duplicate request');\n return;\n }\n if (!originalSaveCommand) {\n console.error('[SaveInterceptorPlugin] No original save command stored');\n return;\n }\n await handleSaveIntercept(currentWidget, app, originalSaveCommand, undefined, () => isSaving = true, () => isSaving = false);\n }\n }, true); // Use capture phase to intercept before JupyterLab\n console.log('[SaveInterceptorPlugin] Save interception enabled');\n }\n};\n/**\n * Handle save intercept - show modal and collect cells if needed\n */\nasync function handleSaveIntercept(panel, app, originalSaveCommand, args, setSaving, clearSaving) {\n // Show confirmation modal\n const shouldAnalyze = await showSaveConfirmModal();\n if (shouldAnalyze === null) {\n // User cancelled\n clearSaving();\n return;\n }\n if (shouldAnalyze) {\n // Collect all cells and send for analysis\n await performAnalysisSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n else {\n // Direct save without analysis\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n}\n/**\n * Show save confirmation modal\n * Returns: true for analysis, false for direct save, null for cancel\n */\nfunction showSaveConfirmModal() {\n return new Promise((resolve) => {\n // Remove existing modal if any\n const existingModal = document.querySelector('.jp-agent-save-confirm-modal');\n if (existingModal) {\n existingModal.remove();\n }\n // Create modal overlay\n const modalOverlay = document.createElement('div');\n modalOverlay.className = 'jp-agent-save-confirm-modal';\n modalOverlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n `;\n // Create modal container\n const modalContainer = document.createElement('div');\n modalContainer.style.cssText = `\n background: var(--jp-layout-color1);\n border: 1px solid var(--jp-border-color1);\n border-radius: 4px;\n padding: 24px;\n max-width: 400px;\n width: 90%;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n font-family: var(--jp-ui-font-family);\n `;\n // Modal content\n modalContainer.innerHTML = `\n <div style=\"margin-bottom: 20px;\">\n <h3 style=\"margin: 0 0 12px 0; color: var(--jp-ui-font-color1); font-size: 16px; font-weight: 500;\">\n 저장 전에 코드 검수를 하겠습니까?\n </h3>\n <p style=\"margin: 0; color: var(--jp-ui-font-color2); font-size: 14px; line-height: 1.5;\">\n 노트북을 저장하기 전에 모든 셀의 코드와 출력을 분석하여 최적화 제안을 받으시겠습니까?\n </p>\n </div>\n <div style=\"display: flex; gap: 12px; justify-content: flex-end;\">\n <button class=\"jp-agent-save-direct-btn\" style=\"\n background: transparent;\n color: var(--jp-ui-font-color2);\n border: 1px solid var(--jp-border-color2);\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">그냥 저장하기</button>\n <button class=\"jp-agent-save-analysis-btn\" style=\"\n background: var(--jp-brand-color1);\n color: white;\n border: 1px solid var(--jp-brand-color1);\n border-radius: 3px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n \">검수 후 저장하기</button>\n </div>\n `;\n modalOverlay.appendChild(modalContainer);\n document.body.appendChild(modalOverlay);\n // Button event listeners\n const directSaveBtn = modalContainer.querySelector('.jp-agent-save-direct-btn');\n const analysisSaveBtn = modalContainer.querySelector('.jp-agent-save-analysis-btn');\n // Direct save button\n directSaveBtn.addEventListener('click', () => {\n console.log('[SaveInterceptorPlugin] User chose direct save');\n modalOverlay.remove();\n resolve(false);\n });\n directSaveBtn.addEventListener('mouseenter', () => {\n directSaveBtn.style.background = 'var(--jp-layout-color2)';\n });\n directSaveBtn.addEventListener('mouseleave', () => {\n directSaveBtn.style.background = 'transparent';\n });\n // Analysis save button\n analysisSaveBtn.addEventListener('click', () => {\n console.log('[SaveInterceptorPlugin] User chose analysis save');\n modalOverlay.remove();\n resolve(true);\n });\n analysisSaveBtn.addEventListener('mouseenter', () => {\n analysisSaveBtn.style.opacity = '0.9';\n });\n analysisSaveBtn.addEventListener('mouseleave', () => {\n analysisSaveBtn.style.opacity = '1';\n });\n // Close on overlay click\n modalOverlay.addEventListener('click', (e) => {\n if (e.target === modalOverlay) {\n console.log('[SaveInterceptorPlugin] User cancelled save');\n modalOverlay.remove();\n resolve(null);\n }\n });\n // ESC key to close\n const handleEscapeKey = (e) => {\n if (e.key === 'Escape') {\n console.log('[SaveInterceptorPlugin] User cancelled save with ESC');\n modalOverlay.remove();\n document.removeEventListener('keydown', handleEscapeKey);\n resolve(null);\n }\n };\n document.addEventListener('keydown', handleEscapeKey);\n });\n}\n/**\n * Perform direct save without analysis\n */\nasync function performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving) {\n console.log('[SaveInterceptorPlugin] Performing direct save');\n setSaving();\n try {\n // Use the original save command\n if (originalSaveCommand) {\n await originalSaveCommand(args);\n showNotification('저장이 완료되었습니다.', 'success');\n }\n else {\n // Fallback: Use JupyterLab's document manager to save\n const context = panel.context;\n if (context) {\n await context.save();\n showNotification('저장이 완료되었습니다.', 'success');\n }\n }\n }\n catch (error) {\n console.error('[SaveInterceptorPlugin] Save failed:', error);\n showNotification('저장에 실패했습니다.', 'error');\n }\n finally {\n clearSaving();\n }\n}\n/**\n * Perform analysis save - collect cells and send to AgentPanel\n */\nasync function performAnalysisSave(panel, app, originalSaveCommand, args, setSaving, clearSaving) {\n console.log('[SaveInterceptorPlugin] Performing analysis save');\n try {\n // Collect all code cells\n const cells = await collectAllCodeCells(panel);\n if (cells.length === 0) {\n showNotification('분석할 코드 셀이 없습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n return;\n }\n showNotification('코드 분석을 시작합니다...', 'info');\n // Get AgentPanel instance\n const agentPanel = window._hdspAgentPanel;\n if (!agentPanel) {\n console.error('[SaveInterceptorPlugin] Agent panel not found');\n showNotification('Agent panel을 찾을 수 없습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n return;\n }\n // Activate the sidebar panel\n if (app) {\n app.shell.activateById(agentPanel.id);\n }\n // Send cells to AgentPanel for analysis\n if (agentPanel.analyzeNotebook) {\n agentPanel.analyzeNotebook(cells, async () => {\n // Callback after analysis complete - perform save\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n });\n }\n else {\n console.error('[SaveInterceptorPlugin] analyzeNotebook method not found on AgentPanel');\n showNotification('분석 기능을 사용할 수 없습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n }\n catch (error) {\n console.error('[SaveInterceptorPlugin] Analysis save failed:', error);\n showNotification('분석에 실패했습니다. 그냥 저장합니다.', 'warning');\n await performDirectSave(panel, app, originalSaveCommand, args, setSaving, clearSaving);\n }\n}\n/**\n * Collect all code cells with content, output, and imports\n * Based on chrome_agent's collectAllCodeCells implementation\n */\nasync function collectAllCodeCells(panel) {\n const notebook = panel.content;\n const cells = [];\n for (let i = 0; i < notebook.widgets.length; i++) {\n const cell = notebook.widgets[i];\n // Only collect code cells - null safety 추가\n if (!cell?.model || cell.model.type !== 'code') {\n continue;\n }\n const content = cell.model.sharedModel?.getSource() || '';\n if (!content.trim()) {\n continue;\n }\n // Get or assign cell ID\n const cellId = getOrAssignCellId(cell);\n // Extract output\n const output = getCellOutput(cell);\n // Extract imports\n const imports = extractImports(content);\n // Get local module sources (simplified - would need kernel API access for full implementation)\n const localImports = imports.filter(imp => imp.isLocal);\n const localModuleSources = {};\n // Note: Full module source extraction would require kernel websocket connection\n // This is a simplified version for now\n for (const imp of localImports) {\n if (!imp.module.startsWith('.')) {\n // Could implement kernel-based source extraction here\n localModuleSources[imp.module] = `# Source for ${imp.module} would be fetched from kernel`;\n }\n }\n cells.push({\n index: i,\n id: cellId,\n content: content,\n type: 'code',\n output: output,\n imports: imports,\n localModuleSources: Object.keys(localModuleSources).length > 0 ? localModuleSources : undefined\n });\n }\n console.log(`[SaveInterceptorPlugin] Collected ${cells.length} code cells`);\n return cells;\n}\n/**\n * Get or assign cell ID to a cell\n */\nfunction getOrAssignCellId(cell) {\n const cellModel = cell.model;\n let cellId;\n // Try to get cell ID from metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.get === 'function') {\n cellId = cellModel.metadata.get('jupyterAgentCellId');\n }\n else if (cellModel.metadata?.jupyterAgentCellId) {\n cellId = cellModel.metadata.jupyterAgentCellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n if (!cellId) {\n cellId = `cell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n // Try to set cell ID in metadata\n try {\n if (cellModel.metadata && typeof cellModel.metadata.set === 'function') {\n cellModel.metadata.set('jupyterAgentCellId', cellId);\n }\n else if (cellModel.metadata) {\n cellModel.metadata.jupyterAgentCellId = cellId;\n }\n }\n catch (e) {\n // Ignore errors\n }\n }\n return cellId;\n}\n/**\n * Extract output from a code cell\n */\nfunction getCellOutput(cell) {\n // cell.model이 null일 수 있으므로 안전하게 체크\n if (!cell?.model || cell.model.type !== 'code') {\n return '';\n }\n const codeCell = cell;\n const outputs = codeCell.model?.outputs;\n if (!outputs || outputs.length === 0) {\n return '';\n }\n const outputTexts = [];\n for (let i = 0; i < outputs.length; i++) {\n const output = outputs.get(i);\n const outputType = output.type;\n if (outputType === 'stream') {\n // stdout, stderr\n const text = output.text;\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n else if (outputType === 'execute_result' || outputType === 'display_data') {\n // Execution results or display data\n const data = output.data;\n if (data['text/plain']) {\n const text = data['text/plain'];\n if (Array.isArray(text)) {\n outputTexts.push(text.join(''));\n }\n else {\n outputTexts.push(text);\n }\n }\n }\n else if (outputType === 'error') {\n // Error output\n const traceback = output.traceback;\n if (Array.isArray(traceback)) {\n outputTexts.push(traceback.join('\\n'));\n }\n }\n }\n return outputTexts.join('\\n');\n}\n/**\n * Extract imports from Python code\n * Based on chrome_agent's extractImports implementation\n */\nfunction extractImports(cellContent) {\n const imports = [];\n const lines = cellContent.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip comments and empty lines\n if (trimmed.startsWith('#') || trimmed === '')\n continue;\n // Relative import: from . import ... or from .. import ...\n const relativeMatch = trimmed.match(/^from\\s+(\\.+[\\w.]*)\\s+import\\s+([\\w,\\s*]+)/);\n if (relativeMatch) {\n imports.push({\n module: relativeMatch[1],\n items: relativeMatch[2],\n isLocal: true\n });\n continue;\n }\n // from X import Y form\n const fromImportMatch = trimmed.match(/^from\\s+([\\w.]+)\\s+import\\s+([\\w,\\s*]+)/);\n if (fromImportMatch) {\n const moduleName = fromImportMatch[1];\n imports.push({\n module: moduleName,\n items: fromImportMatch[2],\n isLocal: isLocalImport(moduleName)\n });\n continue;\n }\n // import X, Y, Z form\n const importMatch = trimmed.match(/^import\\s+([\\w,\\s.]+)/);\n if (importMatch) {\n const modules = importMatch[1].split(',').map(m => m.trim().split(' as ')[0]);\n modules.forEach(moduleName => {\n imports.push({\n module: moduleName,\n items: moduleName,\n isLocal: isLocalImport(moduleName)\n });\n });\n }\n }\n return imports;\n}\n/**\n * Determine if a module import is local (not a standard library)\n */\nfunction isLocalImport(moduleName) {\n // Relative paths are always local\n if (moduleName.startsWith('.')) {\n return true;\n }\n // Common Python libraries (not exhaustive)\n const commonLibraries = [\n 'numpy', 'np', 'pandas', 'pd', 'matplotlib', 'sklearn', 'scipy',\n 'torch', 'tensorflow', 'tf', 'keras', 'PIL', 'cv2', 'requests',\n 'json', 'os', 'sys', 'datetime', 'time', 'math', 'random',\n 'collections', 'itertools', 'functools', 're', 'pathlib',\n 'typing', 'dataclasses', 'abc', 'argparse', 'logging',\n 'pickle', 'csv', 'sqlite3', 'http', 'urllib', 'email',\n 'plotly', 'seaborn', 'statsmodels', 'xgboost', 'lightgbm',\n 'transformers', 'datasets', 'accelerate', 'peft',\n 'openai', 'anthropic', 'langchain', 'llama_index'\n ];\n // Get top-level module name\n const topLevelModule = moduleName.split('.')[0];\n // If not in common libraries, consider it local\n return !commonLibraries.includes(topLevelModule);\n}\n/**\n * Get notification background color based on type\n */\nfunction getNotificationColor(type) {\n const colors = {\n success: '#48bb78',\n error: '#f56565',\n warning: '#ed8936',\n info: '#4299e1'\n };\n return colors[type] || colors.info;\n}\n/**\n * Create notification element with styles\n */\nfunction createNotificationElement(message, backgroundColor) {\n const notification = document.createElement('div');\n notification.style.cssText = `\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 20px;\n border-radius: 8px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n z-index: 10001;\n opacity: 0;\n transition: opacity 0.3s ease;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n max-width: 300px;\n word-wrap: break-word;\n background: ${backgroundColor};\n `;\n notification.textContent = message;\n return notification;\n}\n/**\n * Animate notification appearance and removal\n */\nfunction animateNotification(notification) {\n // Show animation\n setTimeout(() => {\n notification.style.opacity = '1';\n }, 10);\n // Remove after 3 seconds\n setTimeout(() => {\n notification.style.opacity = '0';\n setTimeout(() => {\n if (notification.parentNode) {\n notification.parentNode.removeChild(notification);\n }\n }, 300);\n }, 3000);\n}\n/**\n * Show notification\n */\nfunction showNotification(message, type = 'info') {\n const notification = createNotificationElement(message, getNotificationColor(type));\n document.body.appendChild(notification);\n animateNotification(notification);\n}\n","/**\n * Sidebar Plugin\n * Adds Agent panel to JupyterLab sidebar\n */\nimport { ILayoutRestorer } from '@jupyterlab/application';\nimport { ICommandPalette, WidgetTracker } from '@jupyterlab/apputils';\nimport { INotebookTracker } from '@jupyterlab/notebook';\nimport { IConsoleTracker } from '@jupyterlab/console';\nimport { IRenderMimeRegistry } from '@jupyterlab/rendermime';\nimport { AgentPanelWidget } from '../components/AgentPanel';\nimport { ApiService } from '../services/ApiService';\n/**\n * Sidebar plugin namespace\n */\nconst PLUGIN_ID = '@hdsp-agent/sidebar';\nconst COMMAND_ID = 'hdsp-agent:toggle-sidebar';\n/**\n * Agent Sidebar Plugin\n */\nexport const sidebarPlugin = {\n id: PLUGIN_ID,\n autoStart: true,\n requires: [],\n optional: [ILayoutRestorer, ICommandPalette, INotebookTracker, IConsoleTracker, IRenderMimeRegistry],\n activate: (app, restorer, palette, notebookTracker, consoleTracker, rmRegistry) => {\n console.log('[SidebarPlugin] Activating Jupyter Agent Sidebar');\n try {\n // Create API service\n const apiService = new ApiService();\n // Create agent panel widget with notebook tracker and console tracker\n const agentPanel = new AgentPanelWidget(apiService, notebookTracker, consoleTracker);\n // Create tracker for panel state restoration if restorer available\n if (restorer) {\n const tracker = new WidgetTracker({\n namespace: 'hdsp-agent'\n });\n // Add panel to tracker\n tracker.add(agentPanel);\n // Restore panel state\n restorer.restore(tracker, {\n command: COMMAND_ID,\n name: () => 'hdsp-agent'\n });\n }\n // Add panel to right sidebar\n app.shell.add(agentPanel, 'right', { rank: 100 });\n // Add command to toggle sidebar\n app.commands.addCommand(COMMAND_ID, {\n label: 'HALO Agent 사이드바 토글',\n caption: 'HALO Agent 패널 표시/숨기기',\n execute: () => {\n if (agentPanel.isVisible) {\n agentPanel.close();\n }\n else {\n app.shell.activateById(agentPanel.id);\n }\n }\n });\n // Add command to palette\n if (palette) {\n palette.addItem({\n command: COMMAND_ID,\n category: 'HALO Agent',\n args: {}\n });\n }\n // Store reference globally for cell buttons to access\n window._hdspAgentPanel = agentPanel;\n // Store rendermime registry globally for markdown rendering\n if (rmRegistry) {\n window._hdspRenderMimeRegistry = rmRegistry;\n console.log('[SidebarPlugin] RenderMime registry stored for markdown rendering');\n }\n console.log('[SidebarPlugin] HALO Agent Sidebar activated successfully');\n }\n catch (error) {\n console.error('[SidebarPlugin] Failed to activate:', error);\n }\n }\n};\n","/**\n * API Key Manager Service\n *\n * Manages LLM API keys in browser localStorage.\n * Keys are stored locally and sent with each request to the Agent Server.\n *\n * IMPORTANT (Financial Security Compliance):\n * - All API keys are stored ONLY in browser localStorage\n * - Agent Server receives ONE key per request (no key storage on server)\n * - Key rotation on rate limit (429) is handled by frontend\n */\nconst STORAGE_KEY = 'hdsp-agent-llm-config';\n/** Cached prompts from API */\nlet cachedPrompts = null;\n/** Fallback prompt when API is unavailable */\nconst FALLBACK_PROMPT = '(프롬프트를 불러오는 중...)';\n/**\n * Fetch default prompts from backend API.\n * Results are cached for the session.\n */\nexport async function fetchDefaultPrompts() {\n // Return cached if available\n if (cachedPrompts) {\n return cachedPrompts;\n }\n try {\n // Use PageConfig to get correct base URL for Jupyter proxy\n const { PageConfig } = await import('@jupyterlab/coreutils');\n const serverRoot = PageConfig.getBaseUrl();\n const { URLExt } = await import('@jupyterlab/coreutils');\n const baseUrl = URLExt.join(serverRoot, 'hdsp-agent');\n const response = await fetch(`${baseUrl}/config/prompts`);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n cachedPrompts = await response.json();\n console.log('[ApiKeyManager] Default prompts fetched from API');\n return cachedPrompts;\n }\n catch (error) {\n console.error('[ApiKeyManager] Failed to fetch default prompts:', error);\n // Return fallback\n return {\n single: FALLBACK_PROMPT,\n planner: FALLBACK_PROMPT,\n python_developer: FALLBACK_PROMPT,\n researcher: FALLBACK_PROMPT,\n athena_query: FALLBACK_PROMPT,\n };\n }\n}\n/**\n * Get cached prompts (sync) - returns null if not yet fetched\n */\nexport function getCachedPrompts() {\n return cachedPrompts;\n}\n/**\n * Clear cached prompts (useful for refresh)\n */\nexport function clearCachedPrompts() {\n cachedPrompts = null;\n}\n// Legacy exports for backward compatibility (will be populated after fetch)\nexport let DEFAULT_LANGCHAIN_SYSTEM_PROMPT = FALLBACK_PROMPT;\nexport let DEFAULT_PLANNER_PROMPT = FALLBACK_PROMPT;\nexport let DEFAULT_PYTHON_DEVELOPER_PROMPT = FALLBACK_PROMPT;\nexport let DEFAULT_RESEARCHER_PROMPT = FALLBACK_PROMPT;\nexport let DEFAULT_ATHENA_QUERY_PROMPT = FALLBACK_PROMPT;\n// Default agent prompts collection (populated after fetch)\nexport let DEFAULT_AGENT_PROMPTS = {\n planner: FALLBACK_PROMPT,\n python_developer: FALLBACK_PROMPT,\n researcher: FALLBACK_PROMPT,\n athena_query: FALLBACK_PROMPT,\n};\n/**\n * Initialize prompts from API (call this on app startup)\n */\nexport async function initializePrompts() {\n const prompts = await fetchDefaultPrompts();\n DEFAULT_LANGCHAIN_SYSTEM_PROMPT = prompts.single;\n DEFAULT_PLANNER_PROMPT = prompts.planner;\n DEFAULT_PYTHON_DEVELOPER_PROMPT = prompts.python_developer;\n DEFAULT_RESEARCHER_PROMPT = prompts.researcher;\n DEFAULT_ATHENA_QUERY_PROMPT = prompts.athena_query;\n DEFAULT_AGENT_PROMPTS = {\n planner: prompts.planner,\n python_developer: prompts.python_developer,\n researcher: prompts.researcher,\n athena_query: prompts.athena_query,\n };\n}\n// ═══════════════════════════════════════════════════════════════════════════\n// Key Rotation State (in-memory, not persisted)\n// ═══════════════════════════════════════════════════════════════════════════\n/** Current key index for rotation (per-session, not persisted) */\nlet currentKeyIndex = 0;\n/** Set of rate-limited key indices (reset after successful request) */\nconst rateLimitedKeys = new Set();\n/** Maximum retry attempts with different keys */\nconst MAX_KEY_ROTATION_ATTEMPTS = 10;\n/**\n * Get the current LLM configuration from localStorage\n */\nexport function getLLMConfig() {\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (!stored) {\n return null;\n }\n return JSON.parse(stored);\n }\n catch (e) {\n console.error('[ApiKeyManager] Failed to parse stored config:', e);\n return null;\n }\n}\n/**\n * Save LLM configuration to localStorage\n */\nexport function saveLLMConfig(config) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(config));\n console.log('[ApiKeyManager] Config saved to localStorage');\n // Dispatch custom event for same-tab listeners (e.g., idle monitor)\n window.dispatchEvent(new CustomEvent('hdsp-config-updated', { detail: config }));\n }\n catch (e) {\n console.error('[ApiKeyManager] Failed to save config:', e);\n }\n}\n/**\n * Clear stored LLM configuration\n */\nexport function clearLLMConfig() {\n localStorage.removeItem(STORAGE_KEY);\n console.log('[ApiKeyManager] Config cleared from localStorage');\n}\n/**\n * Check if API key is configured for the current provider\n */\nexport function hasValidApiKey(config) {\n if (!config)\n return false;\n switch (config.provider) {\n case 'gemini':\n // Check both single key and multiple keys\n const hasMainKey = !!(config.gemini?.apiKey && config.gemini.apiKey.trim());\n const hasArrayKeys = !!(config.gemini?.apiKeys && config.gemini.apiKeys.some(k => k && k.trim()));\n return hasMainKey || hasArrayKeys;\n case 'openai':\n return !!(config.openai?.apiKey && config.openai.apiKey.trim());\n case 'vllm':\n // vLLM may not require API key\n return true;\n default:\n return false;\n }\n}\n/**\n * Get default LLM configuration\n * Uses cached prompts from API if available, otherwise uses placeholders\n */\nexport function getDefaultLLMConfig() {\n const prompts = getCachedPrompts();\n return {\n provider: 'gemini',\n gemini: {\n apiKey: '',\n apiKeys: [],\n model: 'gemini-2.5-flash'\n },\n openai: {\n apiKey: '',\n model: 'gpt-4'\n },\n vllm: {\n endpoint: 'http://localhost:8000/v1',\n model: 'default'\n },\n systemPrompt: prompts?.single || DEFAULT_LANGCHAIN_SYSTEM_PROMPT,\n agentMode: 'single',\n agentPrompts: prompts ? {\n planner: prompts.planner,\n python_developer: prompts.python_developer,\n researcher: prompts.researcher,\n athena_query: prompts.athena_query,\n } : { ...DEFAULT_AGENT_PROMPTS },\n autoApprove: false\n };\n}\n/**\n * Get a random API key from the list (for load balancing)\n */\nexport function getRandomApiKey(config) {\n if (config.provider === 'gemini' && config.gemini) {\n const keys = config.gemini.apiKeys.filter(k => k && k.trim());\n if (keys.length === 0) {\n return config.gemini.apiKey || null;\n }\n // Random selection for load balancing\n const randomIndex = Math.floor(Math.random() * keys.length);\n return keys[randomIndex];\n }\n if (config.provider === 'openai' && config.openai) {\n return config.openai.apiKey || null;\n }\n if (config.provider === 'vllm' && config.vllm) {\n return config.vllm.apiKey || null;\n }\n return null;\n}\n/**\n * Get all valid API keys count\n */\nexport function getValidApiKeysCount(config) {\n if (config.provider === 'gemini' && config.gemini) {\n return config.gemini.apiKeys.filter(k => k && k.trim()).length;\n }\n if (config.provider === 'openai' && config.openai) {\n return config.openai.apiKey && config.openai.apiKey.trim() ? 1 : 0;\n }\n if (config.provider === 'vllm') {\n return 1; // vLLM doesn't require API key\n }\n return 0;\n}\n/**\n * Mask API key for display (show first 4 and last 4 characters)\n */\nexport function maskApiKey(key) {\n if (!key || key.length < 10)\n return '***';\n return `${key.slice(0, 4)}...${key.slice(-4)}`;\n}\n/**\n * Test API key by making a simple request\n */\nexport async function testApiKey(config) {\n try {\n // Simple validation - just check if key exists\n if (!hasValidApiKey(config)) {\n return { success: false, message: 'API key not configured' };\n }\n // For more thorough testing, you could make a minimal API call here\n // But for now, just validate format\n const key = config[config.provider];\n if (config.provider === 'gemini' && key?.apiKey) {\n if (!key.apiKey.startsWith('AIza')) {\n return { success: false, message: 'Invalid Gemini API key format (should start with AIza)' };\n }\n }\n if (config.provider === 'openai' && key?.apiKey) {\n if (!key.apiKey.startsWith('sk-')) {\n return { success: false, message: 'Invalid OpenAI API key format (should start with sk-)' };\n }\n }\n return { success: true, message: 'API key format is valid' };\n }\n catch (e) {\n return { success: false, message: `Error: ${e}` };\n }\n}\n// ═══════════════════════════════════════════════════════════════════════════\n// Key Rotation Functions (Financial Security Compliance)\n// ═══════════════════════════════════════════════════════════════════════════\n/**\n * Get all valid Gemini API keys from config\n */\nexport function getValidGeminiKeys(config) {\n if (config.provider !== 'gemini' || !config.gemini) {\n return [];\n }\n const keys = config.gemini.apiKeys?.filter(k => k && k.trim()) || [];\n // Fallback to single apiKey if no array\n if (keys.length === 0 && config.gemini.apiKey && config.gemini.apiKey.trim()) {\n return [config.gemini.apiKey];\n }\n return keys;\n}\n/**\n * Get current key index for rotation tracking\n */\nexport function getCurrentKeyIndex() {\n return currentKeyIndex;\n}\n/**\n * Reset key rotation state (call after successful request)\n */\nexport function resetKeyRotation() {\n rateLimitedKeys.clear();\n console.log('[ApiKeyManager] Key rotation state reset');\n}\n/**\n * Mark current key as rate-limited and rotate to next available key\n * @returns true if rotation successful, false if all keys exhausted\n */\nexport function rotateToNextKey(config) {\n const keys = getValidGeminiKeys(config);\n if (keys.length <= 1) {\n console.log('[ApiKeyManager] Cannot rotate - only one key available');\n return false;\n }\n // Mark current key as rate-limited\n rateLimitedKeys.add(currentKeyIndex);\n console.log(`[ApiKeyManager] Key ${currentKeyIndex + 1} marked as rate-limited`);\n // Find next available key\n for (let i = 0; i < keys.length; i++) {\n const nextIndex = (currentKeyIndex + 1 + i) % keys.length;\n if (!rateLimitedKeys.has(nextIndex)) {\n currentKeyIndex = nextIndex;\n console.log(`[ApiKeyManager] Rotated to key ${currentKeyIndex + 1}/${keys.length}`);\n return true;\n }\n }\n // All keys rate-limited\n console.log('[ApiKeyManager] All keys rate-limited');\n return false;\n}\n/**\n * Check if there are available keys (not all rate-limited)\n */\nexport function hasAvailableKeys(config) {\n const keys = getValidGeminiKeys(config);\n return keys.length > rateLimitedKeys.size;\n}\n/**\n * Get count of remaining available keys\n */\nexport function getAvailableKeyCount(config) {\n const keys = getValidGeminiKeys(config);\n return Math.max(0, keys.length - rateLimitedKeys.size);\n}\n/**\n * Build config with SINGLE current API key for server request.\n * Server receives only one key - rotation is handled by frontend.\n */\nexport function buildSingleKeyConfig(config) {\n if (config.provider !== 'gemini' || !config.gemini) {\n // Non-Gemini providers - return as-is (no rotation)\n return config;\n }\n const keys = getValidGeminiKeys(config);\n if (keys.length === 0) {\n return config;\n }\n // Ensure currentKeyIndex is within bounds\n if (currentKeyIndex >= keys.length) {\n currentKeyIndex = 0;\n }\n const currentKey = keys[currentKeyIndex];\n console.log(`[ApiKeyManager] Using key ${currentKeyIndex + 1}/${keys.length}`);\n // Return config with single apiKey (server only uses apiKey field)\n return {\n ...config,\n gemini: {\n ...config.gemini,\n apiKey: currentKey,\n // Don't send apiKeys array to server (security)\n apiKeys: [],\n },\n };\n}\n/**\n * Handle rate limit error with automatic key rotation\n * @returns New config with rotated key, or null if all keys exhausted\n */\nexport function handleRateLimitError(config) {\n if (config.provider !== 'gemini') {\n // Non-Gemini providers don't support rotation\n return null;\n }\n const rotated = rotateToNextKey(config);\n if (!rotated) {\n console.log('[ApiKeyManager] Rate limit: All keys exhausted');\n return null;\n }\n return buildSingleKeyConfig(config);\n}\n/**\n * Check if error is a rate limit error (429)\n */\nexport function isRateLimitError(error) {\n const errorMsg = typeof error === 'string' ? error : error.message;\n return errorMsg.includes('RATE_LIMIT_EXCEEDED') ||\n errorMsg.includes('429') ||\n errorMsg.toLowerCase().includes('quota exceeded') ||\n errorMsg.toLowerCase().includes('rate limit');\n}\n","/**\n * API Service Layer for REST communication with backend\n */\nimport { buildSingleKeyConfig, handleRateLimitError, isRateLimitError, resetKeyRotation, getValidGeminiKeys, getCurrentKeyIndex } from './ApiKeyManager';\n// ✅ 핵심 변경 1: ServerConnection 대신 PageConfig 임포트\nimport { URLExt, PageConfig } from '@jupyterlab/coreutils';\nexport class ApiService {\n // 생성자에서 baseUrl을 선택적으로 받도록 하되, 없으면 자동으로 계산\n constructor(baseUrl) {\n this.resourceUsageCache = null;\n this.resourceUsageCacheMs = 15000;\n if (baseUrl) {\n this.baseUrl = baseUrl;\n }\n else {\n // ✅ 핵심 변경 2: ServerConnection 대신 PageConfig로 URL 가져오기\n // PageConfig.getBaseUrl()은 '/user/아이디/프로젝트/' 형태의 주소를 정확히 가져옵니다.\n const serverRoot = PageConfig.getBaseUrl();\n // 3. 경로 합치기\n // 결과: /user/453467/pl2wadmprj/hdsp-agent\n this.baseUrl = URLExt.join(serverRoot, 'hdsp-agent');\n }\n console.log('[ApiService] Base URL initialized:', this.baseUrl); // 디버깅용 로그\n }\n /**\n * Get cookie value by name\n */\n getCookie(name) {\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) {\n return parts.pop()?.split(';').shift() || '';\n }\n return '';\n }\n /**\n * Get CSRF token from cookie\n */\n getCsrfToken() {\n return this.getCookie('_xsrf');\n }\n /**\n * Get headers with CSRF token for POST requests\n */\n getHeaders() {\n return {\n 'Content-Type': 'application/json',\n 'X-XSRFToken': this.getCsrfToken()\n };\n }\n async getResourceUsageSnapshot() {\n const now = Date.now();\n if (this.resourceUsageCache\n && now - this.resourceUsageCache.timestamp < this.resourceUsageCacheMs) {\n return this.resourceUsageCache.resource;\n }\n try {\n const response = await fetch(`${this.baseUrl}/resource-usage`, {\n method: 'GET',\n credentials: 'include'\n });\n if (!response.ok) {\n return null;\n }\n const payload = await response.json().catch(() => ({}));\n const candidate = payload?.resource ?? payload;\n const snapshot = candidate && typeof candidate === 'object' && !Array.isArray(candidate)\n ? candidate\n : null;\n if (snapshot) {\n this.resourceUsageCache = { resource: snapshot, timestamp: now };\n }\n return snapshot;\n }\n catch (error) {\n console.warn('[ApiService] Resource usage fetch failed:', error);\n return null;\n }\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Global Rate Limit Handling with Key Rotation\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Fetch wrapper with automatic API key rotation on rate limit (429)\n *\n * NOTE (Financial Security Compliance):\n * - API key rotation is handled by frontend (not server)\n * - Server receives ONLY ONE key per request\n * - On 429 rate limit, frontend rotates key and retries with next key\n */\n async fetchWithKeyRotation(url, request, options) {\n const MAX_RETRIES = 10;\n const originalConfig = request.llmConfig;\n let lastError = null;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n // Build request with single key for this attempt\n const requestToSend = originalConfig\n ? { ...request, llmConfig: buildSingleKeyConfig(originalConfig) }\n : request;\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(requestToSend)\n });\n if (response.ok) {\n // Success - reset key rotation state\n resetKeyRotation();\n return response.json();\n }\n // Handle error response\n const errorText = await response.text();\n // Check if rate limit error\n if (isRateLimitError(errorText) && originalConfig) {\n console.log(`[ApiService] Rate limit on attempt ${attempt + 1}, rotating key...`);\n // Try to rotate to next key\n const rotatedConfig = handleRateLimitError(originalConfig);\n if (rotatedConfig) {\n // Notify UI about key rotation\n const keys = getValidGeminiKeys(originalConfig);\n if (options?.onKeyRotation) {\n options.onKeyRotation(getCurrentKeyIndex(), keys.length);\n }\n continue; // Try next key\n }\n else {\n // All keys exhausted\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n }\n // Not a rate limit error - parse and throw\n let errorMessage = options?.defaultErrorMessage || 'API 요청 실패';\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.detail || errorJson.error || errorJson.message || errorMessage;\n }\n catch (e) {\n errorMessage = errorText || errorMessage;\n }\n throw new Error(errorMessage);\n }\n catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n lastError = error instanceof Error ? error : new Error(errorMsg);\n // If it's a rate limit error from the catch block, check rotation\n if (isRateLimitError(errorMsg) && originalConfig) {\n const rotatedConfig = handleRateLimitError(originalConfig);\n if (rotatedConfig) {\n const keys = getValidGeminiKeys(originalConfig);\n if (options?.onKeyRotation) {\n options.onKeyRotation(getCurrentKeyIndex(), keys.length);\n }\n continue;\n }\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n // Not a rate limit error, throw immediately\n throw error;\n }\n }\n throw lastError || new Error('Maximum retry attempts exceeded');\n }\n /**\n * Execute cell action (explain, fix, custom)\n * Uses global rate limit handling with key rotation\n */\n async cellAction(request) {\n console.log('[ApiService] cellAction request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/cell/action`, request, { defaultErrorMessage: '셀 액션 실패' });\n }\n /**\n * Send chat message (non-streaming)\n * Uses global rate limit handling with key rotation\n */\n async sendMessage(request) {\n console.log('[ApiService] sendMessage request');\n return this.fetchWithKeyRotation(`${this.baseUrl}/chat/message`, request, { defaultErrorMessage: '메시지 전송 실패' });\n }\n /**\n * Send chat message with streaming response\n *\n * NOTE (Financial Security Compliance):\n * - API key rotation is handled by frontend (not server)\n * - Server receives ONLY ONE key per request\n * - On 429 rate limit, frontend rotates key and retries with next key\n */\n /**\n * 단순 Chat용 스트리밍 - /chat/stream 사용\n * 원래 main 브랜치의 구현 복원 - LangChain 에이전트 없이 단순 Q&A\n */\n async sendChatStream(request, onChunk, onMetadata, abortSignal) {\n const MAX_RETRIES = 10;\n let currentConfig = request.llmConfig;\n let lastError = null;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n const requestToSend = currentConfig\n ? { ...request, llmConfig: buildSingleKeyConfig(currentConfig) }\n : request;\n try {\n await this.sendChatStreamInternal(requestToSend, onChunk, onMetadata, abortSignal);\n resetKeyRotation();\n return;\n }\n catch (error) {\n // Check if it's an abort error\n if (error instanceof Error && error.name === 'AbortError') {\n console.log('[ApiService] Chat stream aborted by user');\n throw error;\n }\n const errorMsg = error instanceof Error ? error.message : String(error);\n lastError = error instanceof Error ? error : new Error(errorMsg);\n if (isRateLimitError(errorMsg) && request.llmConfig) {\n console.log(`[ApiService] Chat rate limit on attempt ${attempt + 1}, trying next key...`);\n const rotatedConfig = handleRateLimitError(request.llmConfig);\n if (rotatedConfig) {\n currentConfig = request.llmConfig;\n continue;\n }\n else {\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n }\n throw error;\n }\n }\n throw lastError || new Error('Maximum retry attempts exceeded');\n }\n /**\n * 단순 Chat 스트리밍 내부 구현 - /chat/stream 엔드포인트 사용\n */\n async sendChatStreamInternal(request, onChunk, onMetadata, abortSignal) {\n const response = await fetch(`${this.baseUrl}/chat/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(request),\n signal: abortSignal\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to send chat message: ${error}`);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n if (data.error) {\n throw new Error(data.error);\n }\n if (data.content) {\n onChunk(data.content);\n }\n if (data.done && data.conversationId && onMetadata) {\n onMetadata({ conversationId: data.conversationId });\n }\n }\n catch (e) {\n if (!(e instanceof SyntaxError)) {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n }\n /**\n * Agent V2용 스트리밍 - /agent/langchain/stream 사용\n * LangChain Deep Agent (HITL, Todo, 도구 실행 등)\n */\n async sendAgentV2Stream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, // Callback to capture thread_id for context persistence\n threadId // Optional thread_id to continue existing conversation\n ) {\n // Maximum retry attempts (should match number of keys)\n const MAX_RETRIES = 10;\n let currentConfig = request.llmConfig;\n let lastError = null;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n // Build request with single key for this attempt\n const requestToSend = currentConfig\n ? { ...request, llmConfig: buildSingleKeyConfig(currentConfig) }\n : request;\n try {\n await this.sendAgentV2StreamInternal(requestToSend, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId);\n // Success - reset key rotation state\n resetKeyRotation();\n return;\n }\n catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n lastError = error instanceof Error ? error : new Error(errorMsg);\n // Check if rate limit error and we have config to rotate\n if (isRateLimitError(errorMsg) && request.llmConfig) {\n console.log(`[ApiService] Rate limit on attempt ${attempt + 1}, trying next key...`);\n // Try to rotate to next key using ORIGINAL config (with all keys)\n const rotatedConfig = handleRateLimitError(request.llmConfig);\n if (rotatedConfig) {\n // Update currentConfig with the rotated key for next attempt\n // Note: rotatedConfig already has single key, but we need full config for next rotation\n currentConfig = request.llmConfig;\n continue; // Try next key\n }\n else {\n // All keys exhausted\n throw new Error('모든 API 키가 Rate Limit 상태입니다. 잠시 후 다시 시도해주세요.');\n }\n }\n // Not a rate limit error, throw immediately\n throw error;\n }\n }\n // Should not reach here, but just in case\n throw lastError || new Error('Maximum retry attempts exceeded');\n }\n /**\n * Build request with single API key for server\n * (Key rotation is managed by frontend for financial security compliance)\n */\n buildRequestWithSingleKey(request) {\n if (!request.llmConfig) {\n return request;\n }\n // Build config with single key (server only uses apiKey field)\n const singleKeyConfig = buildSingleKeyConfig(request.llmConfig);\n return {\n ...request,\n llmConfig: singleKeyConfig\n };\n }\n /**\n * 기존 sendMessageStream 유지 (하위 호환성)\n * 내부적으로 sendAgentV2Stream 호출\n * @deprecated sendChatStream 또는 sendAgentV2Stream을 직접 사용하세요\n */\n async sendMessageStream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId) {\n return this.sendAgentV2Stream(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId);\n }\n /**\n * Agent V2 스트리밍 내부 구현 - /agent/langchain/stream 사용\n * (기존 sendMessageStreamInternal에서 이름 변경)\n */\n async sendAgentV2StreamInternal(request, onChunk, onMetadata, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall, onComplete, threadId) {\n // Convert IChatRequest to LangChain AgentRequest format\n // Frontend's context has limited fields, map what's available\n const resourceContext = await this.getResourceUsageSnapshot();\n const requestConfig = resourceContext && request.llmConfig\n ? { ...request.llmConfig, resourceContext }\n : request.llmConfig;\n const langchainRequest = {\n request: request.message,\n threadId: threadId,\n notebookContext: request.context ? {\n notebook_path: request.context.notebookPath,\n cell_count: 0,\n imported_libraries: [],\n defined_variables: [],\n recent_cells: request.context.selectedCells?.map(cell => ({ source: cell })) || []\n } : undefined,\n llmConfig: requestConfig,\n workspaceRoot: request.llmConfig?.workspaceRoot || '.',\n // Agent mode: single (default) or multi (planner + subagents)\n agentMode: request.llmConfig?.agentMode || 'single'\n };\n // Debug: log request size\n const requestBody = JSON.stringify(langchainRequest);\n console.log('[ApiService] Sending langchain request:', {\n messageLength: request.message.length,\n bodyLength: requestBody.length,\n threadId: threadId,\n });\n // Use LangChain streaming endpoint\n const response = await fetch(`${this.baseUrl}/agent/langchain/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: requestBody\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to send message: ${error}`);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n let properlyTerminated = false; // Track if stream ended with complete/done/interrupt event\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n // Process complete SSE messages\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // Keep incomplete line in buffer\n let currentEventType = '';\n for (const line of lines) {\n // Handle SSE event type: \"event: type\"\n if (line.startsWith('event: ')) {\n currentEventType = line.slice(7).trim();\n continue;\n }\n // Handle SSE data: \"data: json\"\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n // Handle based on event type\n if (currentEventType === 'todos' && data.todos && onTodos) {\n onTodos(data.todos);\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'debug_clear' && onDebugClear) {\n onDebugClear();\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'complete' || currentEventType === 'done') {\n properlyTerminated = true;\n if (onDebugClear) {\n onDebugClear();\n }\n // Capture thread_id for context persistence across cycles\n console.log('[ApiService] Complete/Done event received, data:', data);\n if (onComplete && data.thread_id) {\n console.log('[ApiService] Calling onComplete with threadId:', data.thread_id);\n onComplete({ threadId: data.thread_id });\n }\n else {\n console.log('[ApiService] onComplete not called - onComplete:', !!onComplete, 'thread_id:', data.thread_id);\n }\n return;\n }\n // Handle errors\n if (data.error) {\n if (onDebug) {\n onDebug(`오류: ${data.error}`);\n }\n throw new Error(data.error);\n }\n // Handle debug events (display in gray)\n if (data.status && onDebug) {\n // Pass full object if expandable or has icon, otherwise just status string\n if (data.expandable !== undefined || data.icon) {\n onDebug({\n status: data.status,\n expandable: data.expandable,\n full_text: data.full_text,\n icon: data.icon\n });\n }\n else {\n onDebug(data.status);\n }\n }\n // Handle interrupt events (Human-in-the-Loop)\n if (data.thread_id && data.action && onInterrupt) {\n properlyTerminated = true; // Interrupt is a valid termination\n onInterrupt({\n threadId: data.thread_id,\n action: data.action,\n args: data.args || {},\n description: data.description || ''\n });\n return; // Stop processing, wait for user decision\n }\n // Handle token events (streaming LLM response)\n if (data.content) {\n // Filter out raw todos JSON that shouldn't be displayed to users\n // Pattern: {\"todos\": [{\"content\": \"...\", \"status\": \"...\"}, ...]}\n const contentStr = String(data.content).trim();\n const isSummaryJson = contentStr.includes('\"summary\"') && contentStr.includes('\"next_items\"');\n // Check if this is a raw todos JSON object (not summary JSON)\n let isRawTodosJson = false;\n if (!isSummaryJson && contentStr.includes('\"todos\"')) {\n try {\n const parsed = JSON.parse(contentStr);\n // Check structure: {todos: [{content, status}, ...]}\n if (parsed && Array.isArray(parsed.todos) && parsed.todos.length > 0) {\n const firstTodo = parsed.todos[0];\n if (firstTodo && 'content' in firstTodo && 'status' in firstTodo) {\n isRawTodosJson = true;\n }\n }\n }\n catch {\n // Not valid JSON, check for partial pattern\n isRawTodosJson = /\"todos\"\\s*:\\s*\\[\\s*\\{[^}]*\"content\"\\s*:/.test(contentStr);\n }\n }\n if (isRawTodosJson) {\n console.log('[ApiService] Filtered raw todos JSON from display');\n }\n else {\n onChunk(data.content);\n }\n }\n // Handle tool_call events - pass to handler for cell creation\n if (currentEventType === 'tool_call' && data.tool && onToolCall) {\n onToolCall({\n tool: data.tool,\n code: data.code,\n content: data.content,\n command: data.command,\n timeout: data.timeout\n });\n currentEventType = '';\n continue;\n }\n // Handle tool_result events - skip displaying raw output\n if (data.output) {\n // Don't add raw tool output to chat\n }\n // Handle completion\n if (data.final_answer) {\n onChunk(data.final_answer);\n }\n // Handle metadata\n if (data.conversationId && onMetadata) {\n onMetadata({\n conversationId: data.conversationId,\n messageId: data.messageId,\n provider: data.metadata?.provider,\n model: data.metadata?.model\n });\n }\n // Final metadata update\n if (data.done && data.metadata && onMetadata) {\n onMetadata({\n provider: data.metadata.provider,\n model: data.metadata.model\n });\n }\n currentEventType = '';\n }\n catch (e) {\n if (e instanceof SyntaxError) {\n console.warn('Failed to parse SSE data:', line);\n }\n else {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n // Clear debug status if stream ended unexpectedly (no complete/done/interrupt event)\n if (!properlyTerminated && onDebugClear) {\n console.warn('[ApiService] Stream ended without proper termination, clearing debug status');\n onDebugClear();\n }\n }\n }\n /**\n * Resume interrupted agent execution with user decision\n */\n async resumeAgent(threadId, decision, args, feedback, llmConfig, onChunk, onDebug, onInterrupt, onTodos, onDebugClear, onToolCall) {\n const resourceContext = await this.getResourceUsageSnapshot();\n const requestConfig = resourceContext && llmConfig\n ? { ...llmConfig, resourceContext }\n : llmConfig;\n const resumeRequest = {\n threadId,\n decisions: [{\n type: decision,\n args,\n feedback\n }],\n llmConfig: requestConfig,\n workspaceRoot: llmConfig?.workspaceRoot || '.',\n // Agent mode: single (default) or multi (planner + subagents)\n agentMode: llmConfig?.agentMode || 'single'\n };\n const response = await fetch(`${this.baseUrl}/agent/langchain/resume`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(resumeRequest)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to resume agent: ${error}`);\n }\n // Process SSE stream (same as sendMessageStream)\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n let properlyTerminated = false; // Track if stream ended with complete/done event\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n let currentEventType = '';\n for (const line of lines) {\n // Handle SSE event type\n if (line.startsWith('event: ')) {\n currentEventType = line.slice(7).trim();\n continue;\n }\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n if (data.error) {\n if (onDebug) {\n onDebug(`오류: ${data.error}`);\n }\n throw new Error(data.error);\n }\n // Handle todos event\n if (currentEventType === 'todos' && data.todos && onTodos) {\n onTodos(data.todos);\n currentEventType = '';\n continue;\n }\n // Handle debug_clear event\n if (currentEventType === 'debug_clear' && onDebugClear) {\n onDebugClear();\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'complete' || currentEventType === 'done') {\n properlyTerminated = true;\n if (onDebugClear) {\n onDebugClear();\n }\n return;\n }\n // Debug events\n if (data.status && onDebug) {\n // Pass full object if expandable or has icon, otherwise just status string\n if (data.expandable !== undefined || data.icon) {\n onDebug({\n status: data.status,\n expandable: data.expandable,\n full_text: data.full_text,\n icon: data.icon\n });\n }\n else {\n onDebug(data.status);\n }\n }\n // Another interrupt\n if (data.thread_id && data.action && onInterrupt) {\n properlyTerminated = true; // Interrupt is a valid termination\n onInterrupt({\n threadId: data.thread_id,\n action: data.action,\n args: data.args || {},\n description: data.description || ''\n });\n return;\n }\n // Content chunks\n if (data.content && onChunk) {\n // Filter out raw todos JSON that shouldn't be displayed to users\n const contentStr = String(data.content).trim();\n const isSummaryJson = contentStr.includes('\"summary\"') && contentStr.includes('\"next_items\"');\n let isRawTodosJson = false;\n if (!isSummaryJson && contentStr.includes('\"todos\"')) {\n try {\n const parsed = JSON.parse(contentStr);\n if (parsed && Array.isArray(parsed.todos) && parsed.todos.length > 0) {\n const firstTodo = parsed.todos[0];\n if (firstTodo && 'content' in firstTodo && 'status' in firstTodo) {\n isRawTodosJson = true;\n }\n }\n }\n catch {\n isRawTodosJson = /\"todos\"\\s*:\\s*\\[\\s*\\{[^}]*\"content\"\\s*:/.test(contentStr);\n }\n }\n if (isRawTodosJson) {\n console.log('[ApiService] Filtered raw todos JSON from display');\n }\n else {\n onChunk(data.content);\n }\n }\n // Tool call events during resume\n if (currentEventType === 'tool_call' && data.tool && onToolCall) {\n onToolCall({\n tool: data.tool,\n code: data.code,\n content: data.content,\n command: data.command,\n timeout: data.timeout\n });\n currentEventType = '';\n continue;\n }\n currentEventType = '';\n }\n catch (e) {\n if (!(e instanceof SyntaxError)) {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n // Clear debug status if stream ended unexpectedly (no complete/done/interrupt event)\n if (!properlyTerminated && onDebugClear) {\n console.warn('[ApiService] Stream ended without proper termination, clearing debug status');\n onDebugClear();\n }\n }\n }\n /**\n * Cancel a running agent execution by thread ID\n */\n async cancelAgent(threadId) {\n const response = await fetch(`${this.baseUrl}/agent/langchain/cancel`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({ thread_id: threadId })\n });\n if (!response.ok) {\n const error = await response.text();\n console.warn('[ApiService] Failed to cancel agent:', error);\n return { status: 'error', message: error };\n }\n return response.json();\n }\n /**\n * Reset an agent thread (clear session and recreate agent)\n */\n async resetAgent(threadId) {\n const response = await fetch(`${this.baseUrl}/agent/langchain/reset`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({ thread_id: threadId })\n });\n if (!response.ok) {\n const error = await response.text();\n console.warn('[ApiService] Failed to reset agent:', error);\n return { status: 'error', message: error };\n }\n return response.json();\n }\n async executeCommand(command, timeout, cwd, stdin, workspaceRoot) {\n const response = await fetch(`${this.baseUrl}/execute-command`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({ command, timeout, cwd, stdin, workspaceRoot })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to execute command';\n throw new Error(errorMessage);\n }\n return payload;\n }\n async executeCommandStream(command, options) {\n const response = await fetch(`${this.baseUrl}/execute-command/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n command,\n timeout: options?.timeout,\n cwd: options?.cwd,\n stdin: options?.stdin,\n workspaceRoot: options?.workspaceRoot\n })\n });\n if (!response.ok) {\n const payload = await response.json().catch(() => ({}));\n const errorMessage = payload.error || 'Failed to execute command';\n throw new Error(errorMessage);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n let result = null;\n let streamError = null;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n let currentEventType = '';\n for (const line of lines) {\n if (line.startsWith('event: ')) {\n currentEventType = line.slice(7).trim();\n continue;\n }\n if (!line.startsWith('data: ')) {\n continue;\n }\n let data;\n try {\n data = JSON.parse(line.slice(6));\n }\n catch (e) {\n continue;\n }\n if (currentEventType === 'output') {\n if (typeof data.text === 'string' && options?.onOutput) {\n options.onOutput({\n stream: data.stream === 'stderr' ? 'stderr' : 'stdout',\n text: data.text\n });\n }\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'error') {\n streamError = data.error || 'Command execution failed';\n currentEventType = '';\n continue;\n }\n if (currentEventType === 'result') {\n result = data;\n return result;\n }\n currentEventType = '';\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n if (streamError) {\n throw new Error(streamError);\n }\n if (!result) {\n throw new Error('No command result received');\n }\n return result;\n }\n async readFile(path, options) {\n const response = await fetch(`${this.baseUrl}/read-file`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n path,\n encoding: options?.encoding || 'utf-8',\n cwd: options?.cwd,\n workspaceRoot: options?.workspaceRoot\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to read file';\n return { success: false, error: errorMessage };\n }\n return payload;\n }\n async writeFile(path, content, options) {\n const response = await fetch(`${this.baseUrl}/write-file`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n path,\n content,\n encoding: options?.encoding,\n overwrite: options?.overwrite,\n cwd: options?.cwd,\n workspaceRoot: options?.workspaceRoot\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to write file';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Search workspace for files matching pattern\n * Executed on Jupyter server using grep/ripgrep\n */\n async searchWorkspace(options) {\n const response = await fetch(`${this.baseUrl}/search-workspace`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n pattern: options.pattern,\n file_types: options.file_types || ['*.py', '*.ipynb'],\n path: options.path || '.',\n max_results: options.max_results || 50,\n case_sensitive: options.case_sensitive || false,\n workspaceRoot: options.workspaceRoot\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to search workspace';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Search notebook cells for pattern\n * Executed on Jupyter server\n */\n async searchNotebookCells(options) {\n const response = await fetch(`${this.baseUrl}/search-notebook-cells`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n pattern: options.pattern,\n notebook_path: options.notebook_path,\n cell_type: options.cell_type,\n max_results: options.max_results || 30,\n case_sensitive: options.case_sensitive || false,\n workspaceRoot: options.workspaceRoot\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const errorMessage = payload.error || 'Failed to search notebook cells';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Check system resources and file sizes before data processing\n * Executed on Jupyter server\n */\n async checkResource(options) {\n const response = await fetch(`${this.baseUrl}/check-resource`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify({\n files: options.files || [],\n dataframes: options.dataframes || [],\n file_size_command: options.file_size_command || '',\n dataframe_check_code: options.dataframe_check_code || '',\n workspaceRoot: options.workspaceRoot\n })\n });\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n if (response.status === 404) {\n return {\n success: false,\n files: [],\n dataframes: [],\n error: 'Resource check endpoint is not available on this Jupyter server.'\n };\n }\n const errorMessage = payload.error || 'Failed to check resources';\n throw new Error(errorMessage);\n }\n return payload;\n }\n /**\n * Save configuration\n */\n async saveConfig(config) {\n const response = await fetch(`${this.baseUrl}/config`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(config)\n });\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: 'Failed to save configuration' }));\n throw new Error(error.message || 'Failed to save configuration');\n }\n }\n /**\n * Get current configuration\n */\n async getConfig() {\n const response = await fetch(`${this.baseUrl}/config`);\n if (!response.ok) {\n throw new Error('Failed to load configuration');\n }\n return response.json();\n }\n /**\n * Get health status\n */\n async getStatus() {\n const response = await fetch(`${this.baseUrl}/status`);\n if (!response.ok) {\n throw new Error('Failed to get status');\n }\n return response.json();\n }\n /**\n * Get available models\n */\n async getModels() {\n const response = await fetch(`${this.baseUrl}/models`);\n if (!response.ok) {\n throw new Error('Failed to load models');\n }\n return response.json();\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Auto-Agent API Methods (All use global rate limit handling)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Generate execution plan for auto-agent task\n */\n async generateExecutionPlan(request, onKeyRotation) {\n console.log('[ApiService] generateExecutionPlan request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/plan`, request, { onKeyRotation, defaultErrorMessage: '계획 생성 실패' });\n }\n /**\n * Refine step code after error (Self-Healing)\n */\n async refineStepCode(request, onKeyRotation) {\n console.log('[ApiService] refineStepCode request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/refine`, request, { onKeyRotation, defaultErrorMessage: '코드 수정 실패' });\n }\n /**\n * Adaptive Replanning - 에러 분석 후 계획 재수립\n */\n async replanExecution(request, onKeyRotation) {\n console.log('[ApiService] replanExecution request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/replan`, request, { onKeyRotation, defaultErrorMessage: '계획 재수립 실패' });\n }\n /**\n * Validate code before execution - 사전 코드 품질 검증 (Pyflakes/AST 기반)\n * Note: This is a local validation, no LLM call, but uses wrapper for consistency\n */\n async validateCode(request) {\n console.log('[ApiService] validateCode request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/validate`, request, { defaultErrorMessage: '코드 검증 실패' });\n }\n /**\n * Reflect on step execution - 실행 결과 분석 및 적응적 조정\n */\n async reflectOnExecution(request, onKeyRotation) {\n console.log('[ApiService] reflectOnExecution request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/reflect`, request, { onKeyRotation, defaultErrorMessage: 'Reflection 실패' });\n }\n /**\n * Verify state after step execution - 상태 검증 (Phase 1)\n * Note: This is a local verification, no LLM call, but uses wrapper for consistency\n */\n async verifyState(request) {\n console.log('[ApiService] verifyState request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/auto-agent/verify-state`, request, { defaultErrorMessage: '상태 검증 실패' });\n }\n /**\n * Stream execution plan generation with real-time updates\n */\n async generateExecutionPlanStream(request, onPlanUpdate, onReasoning) {\n const response = await fetch(`${this.baseUrl}/auto-agent/plan/stream`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to generate plan: ${error}`);\n }\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n const decoder = new TextDecoder();\n let buffer = '';\n let finalPlan = null;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done)\n break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6));\n if (data.error) {\n throw new Error(data.error);\n }\n if (data.reasoning && onReasoning) {\n onReasoning(data.reasoning);\n }\n if (data.plan) {\n onPlanUpdate(data.plan);\n if (data.done) {\n finalPlan = data.plan;\n }\n }\n }\n catch (e) {\n if (!(e instanceof SyntaxError)) {\n throw e;\n }\n }\n }\n }\n }\n }\n finally {\n reader.releaseLock();\n }\n if (!finalPlan) {\n throw new Error('No plan received from server');\n }\n return finalPlan;\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // File Action API Methods (Python 파일 에러 수정)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Python 파일 에러 수정/분석/설명 요청\n */\n async fileAction(request, onKeyRotation) {\n console.log('[ApiService] fileAction request:', request);\n return this.fetchWithKeyRotation(`${this.baseUrl}/file/action`, request, { onKeyRotation, defaultErrorMessage: '파일 액션 실패' });\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // File Resolution API Methods (파일 경로 해결)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Resolve file path - 파일명 또는 패턴으로 경로 검색\n */\n async resolveFile(request) {\n console.log('[ApiService] resolveFile request:', request);\n const response = await fetch(`${this.baseUrl}/file/resolve`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to resolve file: ${error}`);\n }\n return response.json();\n }\n /**\n * Select file from multiple options - 사용자 선택 처리\n */\n async selectFile(request) {\n console.log('[ApiService] selectFile request:', request);\n const response = await fetch(`${this.baseUrl}/file/select`, {\n method: 'POST',\n headers: this.getHeaders(),\n credentials: 'include',\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to select file: ${error}`);\n }\n return response.json();\n }\n // ═══════════════════════════════════════════════════════════════════════════\n // Context Provider APIs (@file autocomplete)\n // ═══════════════════════════════════════════════════════════════════════════\n /**\n * Get autocomplete options for context commands (@file, etc.)\n */\n async getContextAutocomplete(options) {\n const params = new URLSearchParams();\n if (options?.partialCommand) {\n params.append('partialCommand', options.partialCommand);\n }\n if (options?.baseDir) {\n params.append('baseDir', options.baseDir);\n }\n const url = `${this.baseUrl}/context/autocomplete${params.toString() ? '?' + params.toString() : ''}`;\n console.log('[ApiService] getContextAutocomplete:', url);\n const response = await fetch(url, {\n method: 'GET',\n headers: this.getHeaders(),\n credentials: 'include'\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to get autocomplete options: ${error}`);\n }\n return response.json();\n }\n /**\n * List available context providers\n */\n async listContextProviders() {\n const response = await fetch(`${this.baseUrl}/context/providers`, {\n method: 'GET',\n headers: this.getHeaders(),\n credentials: 'include'\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to list context providers: ${error}`);\n }\n const data = await response.json();\n return data.providers;\n }\n}\n","/**\n * Task Service - Handles notebook generation tasks and progress tracking\n */\nexport class TaskService {\n constructor(baseUrl = '/hdsp-agent') {\n this.baseUrl = baseUrl;\n this.eventSources = new Map();\n }\n /**\n * Get cookie value by name\n */\n getCookie(name) {\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) {\n return parts.pop()?.split(';').shift() || '';\n }\n return '';\n }\n /**\n * Get CSRF token from cookie\n */\n getCsrfToken() {\n return this.getCookie('_xsrf');\n }\n /**\n * Get headers with CSRF token for POST requests\n */\n getHeaders() {\n return {\n 'Content-Type': 'application/json',\n 'X-XSRFToken': this.getCsrfToken()\n };\n }\n /**\n * Start a new notebook generation task\n */\n async generateNotebook(request) {\n const response = await fetch(`${this.baseUrl}/notebook/generate`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify(request)\n });\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ message: 'Failed to start notebook generation' }));\n throw new Error(error.message || 'Failed to start notebook generation');\n }\n return response.json();\n }\n /**\n * Get current task status\n */\n async getTaskStatus(taskId) {\n const response = await fetch(`${this.baseUrl}/task/${taskId}/status`);\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ message: 'Failed to get task status' }));\n throw new Error(error.message || 'Failed to get task status');\n }\n return response.json();\n }\n /**\n * Subscribe to task progress updates via Server-Sent Events\n */\n subscribeToTaskProgress(taskId, onProgress, onError, onComplete) {\n // Close existing connection if any\n this.unsubscribeFromTask(taskId);\n const eventSource = new EventSource(`${this.baseUrl}/task/${taskId}/stream`);\n eventSource.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data);\n onProgress(data);\n // Close connection if task is done\n if (['completed', 'failed', 'cancelled'].includes(data.status)) {\n this.unsubscribeFromTask(taskId);\n if (onComplete) {\n onComplete();\n }\n }\n }\n catch (error) {\n console.error('Failed to parse SSE message:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n eventSource.onerror = (event) => {\n console.error('SSE connection error:', event);\n this.unsubscribeFromTask(taskId);\n if (onError) {\n onError(new Error('Connection to server lost'));\n }\n };\n this.eventSources.set(taskId, eventSource);\n // Return unsubscribe function\n return () => this.unsubscribeFromTask(taskId);\n }\n /**\n * Unsubscribe from task progress updates\n */\n unsubscribeFromTask(taskId) {\n const eventSource = this.eventSources.get(taskId);\n if (eventSource) {\n eventSource.close();\n this.eventSources.delete(taskId);\n }\n }\n /**\n * Cancel a running task\n */\n async cancelTask(taskId) {\n const response = await fetch(`${this.baseUrl}/task/${taskId}/cancel`, {\n method: 'POST',\n headers: this.getHeaders()\n });\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ message: 'Failed to cancel task' }));\n throw new Error(error.message || 'Failed to cancel task');\n }\n // Close SSE connection\n this.unsubscribeFromTask(taskId);\n }\n /**\n * Clean up all active connections\n */\n dispose() {\n for (const [taskId, _] of this.eventSources) {\n this.unsubscribeFromTask(taskId);\n }\n }\n}\n","/**\n * Core type definitions for Jupyter Agent extension\n */\nexport var CellAction;\n(function (CellAction) {\n CellAction[\"EXPLAIN\"] = \"explain\";\n CellAction[\"FIX\"] = \"fix\";\n CellAction[\"CUSTOM_PROMPT\"] = \"custom_prompt\";\n})(CellAction || (CellAction = {}));\nexport var AgentEvent;\n(function (AgentEvent) {\n AgentEvent[\"CELL_ACTION\"] = \"hdsp-agent:cell-action\";\n AgentEvent[\"CONFIG_CHANGED\"] = \"hdsp-agent:config-changed\";\n AgentEvent[\"MESSAGE_SENT\"] = \"hdsp-agent:message-sent\";\n AgentEvent[\"MESSAGE_RECEIVED\"] = \"hdsp-agent:message-received\";\n})(AgentEvent || (AgentEvent = {}));\n// ═══════════════════════════════════════════════════════════════════════════\n// File Action Types (Python 파일 에러 수정용)\n// ═══════════════════════════════════════════════════════════════════════════\nexport var FileAction;\n(function (FileAction) {\n FileAction[\"FIX\"] = \"fix\";\n FileAction[\"EXPLAIN\"] = \"explain\";\n FileAction[\"CUSTOM\"] = \"custom\";\n})(FileAction || (FileAction = {}));\n","/**\n * Material Design Icons as inline SVG strings\n * These can be safely embedded in HTML strings without escaping issues\n */\n// Base SVG wrapper with consistent styling (no xmlns for HTML5 inline embedding)\n// Standard Material Design Icons (viewBox 0 0 24 24)\nconst svgWrapper = (path, color = 'currentColor', size = 16) => `<svg width=\"${size}\" height=\"${size}\" viewBox=\"0 0 24 24\" fill=\"${color}\" style=\"vertical-align: middle; display: inline-block;\">${path}</svg>`;\n// Google Material Symbols wrapper (viewBox 0 -960 960 960)\nconst svgWrapper960 = (path, color = 'currentColor', size = 16) => `<svg width=\"${size}\" height=\"${size}\" viewBox=\"0 -960 960 960\" fill=\"${color}\" style=\"vertical-align: middle; display: inline-block;\">${path}</svg>`;\n// Material Design icon paths (viewBox 0 0 24 24)\nconst ICON_PATHS = {\n // File system\n folder: '<path d=\"M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z\"/>',\n file: '<path d=\"M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z\"/>',\n explore: '<path d=\"M12 10.9c-.61 0-1.1.49-1.1 1.1s.49 1.1 1.1 1.1c.61 0 1.1-.49 1.1-1.1s-.49-1.1-1.1-1.1zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm2.19 12.19L6 18l3.81-8.19L18 6l-3.81 8.19z\"/>',\n read: '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\"/>',\n write: '<path d=\"M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z\"/>',\n // Status\n success: '<path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/>',\n error: '<path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/>',\n warning: '<path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/>',\n info: '<path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/>',\n check: '<path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/>',\n // Actions\n edit: '<path d=\"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z\"/>',\n search: '<path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/>',\n save: '<path d=\"M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z\"/>',\n diagnostics: '<path d=\"M19.5 12c0-.23-.01-.45-.03-.68l1.86-1.41c.4-.3.51-.86.26-1.3l-1.87-3.23c-.25-.44-.79-.62-1.25-.42l-2.15.91c-.37-.26-.76-.49-1.17-.68l-.29-2.31C14.8 2.38 14.37 2 13.87 2h-3.73c-.5 0-.93.38-.99.88l-.29 2.31c-.41.19-.8.42-1.17.68l-2.15-.91c-.46-.2-1-.02-1.25.42L2.41 8.62c-.25.44-.14.99.26 1.3l1.86 1.41c-.02.22-.03.44-.03.67s.01.45.03.68l-1.86 1.41c-.4.3-.51.86-.26 1.3l1.87 3.23c.25.44.79.62 1.25.42l2.15-.91c.37.26.76.49 1.17.68l.29 2.31c.06.5.49.88.99.88h3.73c.5 0 .93-.38.99-.88l.29-2.31c.41-.19.8-.42 1.17-.68l2.15.91c.46.2 1 .02 1.25-.42l1.87-3.23c.25-.44.14-.99-.26-1.3l-1.86-1.41c.03-.23.04-.45.04-.68zm-7.46 3.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z\"/>',\n // Other\n question: '<path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\"/>',\n code: '<path d=\"M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z\"/>',\n terminal: '<path d=\"M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V8h16v10zm-2-1h-6v-2h6v2zM7.5 17l-1.41-1.41L8.67 13l-2.59-2.59L7.5 9l4 4-4 4z\"/>',\n analytics: '<path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>',\n skip: '<path d=\"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z\"/>',\n // Agents/Roles\n planner: '<path d=\"M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z\"/>',\n build: '<path d=\"M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z\"/>',\n person: '<path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\"/>',\n researcher: '<path d=\"M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z\"/>',\n // UI elements\n expandMore: '<path d=\"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\"/>',\n expandLess: '<path d=\"M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\"/>',\n chevronRight: '<path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/>',\n // Summary/Tasks\n summary: '<path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z\"/>',\n listAlt: '<path d=\"M19 5v14H5V5h14m1.1-2H3.9c-.5 0-.9.4-.9.9v16.2c0 .4.4.9.9.9h16.2c.4 0 .9-.5.9-.9V3.9c0-.5-.5-.9-.9-.9zM11 7h6v2h-6V7zm0 4h6v2h-6v-2zm0 4h6v2h-6v-2zM7 7h2v2H7V7zm0 4h2v2H7v-2zm0 4h2v2H7v-2z\"/>',\n};\n// Google Material Symbols paths (viewBox 0 -960 960 960)\nconst ICON_PATHS_960 = {\n // Code terminal icon (for jp-agent-code-description)\n description: '<path d=\"m384-336 56-57-87-87 87-87-56-57-144 144 144 144Zm192 0 144-144-144-144-56 57 87 87-87 87 56 57ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z\"/>',\n // Next steps icon (for 다음 단계 제안)\n assignment: '<path d=\"M240-400h80q0-59 43-99.5T466-540q36 0 67 16.5t51 43.5h-64v80h200v-200h-80v62q-32-38-76.5-60T466-620q-95 0-160.5 64T240-400ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/>',\n // Thinking/Loading icon (for LLM 응답 대기중)\n thinking: '<path d=\"M480-40q-112 0-206-51T120-227v107H40v-240h240v80h-99q48 72 126.5 116T480-120q75 0 140.5-28.5t114-77q48.5-48.5 77-114T840-480h80q0 91-34.5 171T791-169q-60 60-140 94.5T480-40ZM40-480q0-91 34.5-171T169-791q60-60 140-94.5T480-920q112 0 206 51t154 136v-107h80v240H680v-80h99q-48-72-126.5-116T480-840q-75 0-140.5 28.5t-114 77q-48.5 48.5-77 114T120-480H40Zm440 240q21 0 35.5-14.5T530-290q0-21-14.5-36T480-341q-21 0-35.5 14.5T430-291q0 21 14.5 36t35.5 15Zm-36-152h73q0-36 8.5-54t34.5-44q35-35 46.5-56.5T618-598q0-56-40-89t-98-33q-50 0-86 26t-52 74l66 28q7-26 26.5-43t45.5-17q27 0 45.5 15.5T544-595q0 17-8 34t-34 40q-33 29-45.5 56.5T444-392Z\"/>',\n // Tool execution icon (for Tool 실행: ...)\n tool: '<path d=\"M754-81q-8 0-15-2.5T726-92L522-296q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l85-85q6-6 13-8.5t15-2.5q8 0 15 2.5t13 8.5l204 204q6 6 8.5 13t2.5 15q0 8-2.5 15t-8.5 13l-85 85q-6 6-13 8.5T754-81Zm0-95 29-29-147-147-29 29 147 147ZM205-80q-8 0-15.5-3T176-92l-84-84q-6-6-9-13.5T80-205q0-8 3-15t9-13l212-212h85l34-34-165-165h-57L80-765l113-113 121 121v57l165 165 116-116-43-43 56-56H495l-28-28 142-142 28 28v113l56-56 142 142q17 17 26 38.5t9 45.5q0 24-9 46t-26 39l-85-85-56 56-42-42-207 207v84L233-92q-6 6-13 9t-15 3Zm0-96 170-170v-29h-29L176-205l29 29Zm0 0-29-29 15 14 14 15Zm549 0 29-29-29 29Z\"/>',\n // Subagent complete icon (person with checkmark)\n subagentComplete: '<path d=\"M702-480 560-622l57-56 85 85 170-170 56 57-226 226Zm-342 0q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM40-160v-112q0-34 17.5-62.5T104-378q62-31 126-46.5T360-440q66 0 130 15.5T616-378q29 15 46.5 43.5T680-272v112H40Zm80-80h480v-32q0-11-5.5-20T580-306q-54-27-109-40.5T360-360q-56 0-111 13.5T140-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T440-640q0-33-23.5-56.5T360-720q-33 0-56.5 23.5T280-640q0 33 23.5 56.5T360-560Zm0 260Zm0-340Z\"/>',\n // Subagent start icon (person with edit)\n subagentStart: '<path d=\"M480-240Zm-320 80v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q37 0 73 4.5t72 14.5l-67 68q-20-3-39-5t-39-2q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32h240v80H160Zm400 40v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q8 9 12.5 20t4.5 22q0 11-4 22.5T903-340L683-120H560Zm300-263-37-37 37 37ZM620-180h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19ZM480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm0-80q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Z\"/>',\n // Agent execution icon (robot/AI head)\n agent: '<path d=\"M309-389q29 29 71 29t71-29l160-160q29-29 29-71t-29-71q-29-29-71-29t-71 29q-37-13-73-6t-61 32q-25 25-32 61t6 73q-29 29-29 71t29 71ZM240-80v-172q-57-52-88.5-121.5T120-520q0-150 105-255t255-105q125 0 221.5 73.5T827-615l52 205q5 19-7 34.5T840-360h-80v120q0 33-23.5 56.5T680-160h-80v80h-80v-160h160v-200h108l-38-155q-23-91-98-148t-172-57q-116 0-198 81t-82 197q0 60 24.5 114t69.5 96l26 24v208h-80Zm254-360Z\"/>',\n // Pause icon (for 사용자 승인 대기)\n pause: '<path d=\"M520-200v-560h240v560H520Zm-320 0v-560h240v560H200Zm400-80h80v-400h-80v400Zm-320 0h80v-400h-80v400Zm0-400v400-400Zm320 0v400-400Z\"/>',\n // Play icon (for 실행 재개)\n play: '<path d=\"M320-200v-560l440 280-440 280Zm80-280Zm0 134 210-134-210-134v268Z\"/>',\n // Lightbulb icon (for tips)\n lightbulb: '<path d=\"M480-80q-34 0-57.5-23.5T399-161h162q0 34-23.5 57.5T480-80ZM318-223v-80h324v80H318Zm5-120q-66-43-104.5-107.5T180-597q0-122 89-211t211-89q122 0 211 89t89 211q0 82-38.5 146.5T637-343H323Zm22-80h270q44-29 69.5-71.5T710-597q0-97-66.5-163.5T480-827q-97 0-163.5 66.5T250-597q0 60 25.5 102.5T345-423Zm135 0Z\"/>',\n // Approve circle icon (checkmark in circle)\n approveCircle: '<path d=\"m424-296 282-282-56-56-226 226-114-114-56 56 170 170Zm56 216q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/>',\n // Reject circle icon (prohibited sign)\n rejectCircle: '<path d=\"M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q54 0 104-17.5t92-50.5L228-676q-33 42-50.5 92T160-480q0 134 93 227t227 93Zm252-124q33-42 50.5-92T800-480q0-134-93-227t-227-93q-54 0-104 17.5T284-732l448 448ZM480-480Z\"/>',\n // Double check icon (approved status)\n doubleCheck: '<path d=\"m381-240 424-424-57-56-368 367-169-170-57 57 227 226Zm0 113L42-466l169-170 170 170 366-367 172 168-538 538Z\"/>',\n // Approval warning icon (exclamation point)\n approvalWarning: '<path d=\"M480-120q-33 0-56.5-23.5T400-200q0-33 23.5-56.5T480-280q33 0 56.5 23.5T560-200q0 33-23.5 56.5T480-120Zm-80-240v-480h160v480H400Z\"/>',\n};\n// Color constants (Material Design color palette)\nconst COLORS = {\n folder: '#ffc107',\n file: '#2196f3',\n explore: '#757575',\n read: '#757575',\n write: '#757575',\n success: '#4caf50',\n error: '#f44336',\n warning: '#ff9800',\n info: '#2196f3',\n check: '#757575',\n edit: '#757575',\n search: '#757575',\n save: '#4caf50',\n diagnostics: '#607d8b',\n question: '#2196f3',\n description: '#2196f3',\n code: '#9c27b0',\n terminal: '#607d8b',\n analytics: '#00bcd4',\n skip: '#9e9e9e',\n planner: '#e91e63',\n build: '#ff5722',\n person: '#795548',\n researcher: '#3f51b5',\n expandMore: '#757575',\n expandLess: '#757575',\n chevronRight: '#757575',\n summary: '#4caf50',\n assignment: '#2196f3',\n listAlt: '#ff9800',\n // Status message icons - unified grey color for clean look\n tool: '#757575',\n agent: '#757575',\n subagentComplete: '#757575',\n subagentStart: '#757575',\n pause: '#757575',\n play: '#757575',\n thinking: '#757575',\n lightbulb: '#ffc107',\n // Approval action icons\n approveCircle: '#4caf50',\n rejectCircle: '#f44336',\n doubleCheck: '#4caf50',\n approvalWarning: '#ff9800', // Orange for warning\n};\n/**\n * Get inline SVG icon string\n */\nexport function getIcon(name, size = 16, customColor) {\n const path = ICON_PATHS[name];\n const color = customColor || COLORS[name] || 'currentColor';\n return svgWrapper(path, color, size);\n}\n/**\n * Get inline SVG icon string for 960x960 icons\n */\nexport function getIcon960(name, size = 16, customColor) {\n const path = ICON_PATHS_960[name];\n const color = customColor || COLORS[name] || 'currentColor';\n return svgWrapper960(path, color, size);\n}\n/**\n * Get icon by string name (for dynamic icon rendering)\n * Returns empty string if icon not found\n */\nexport function getIconByName(name, size = 16, customColor) {\n // Check 960 icons first (description, assignment, thinking)\n if (name in ICON_PATHS_960) {\n const path = ICON_PATHS_960[name];\n const color = customColor || COLORS[name] || 'currentColor';\n return svgWrapper960(path, color, size);\n }\n // Then check standard icons\n if (name in ICON_PATHS) {\n const path = ICON_PATHS[name];\n const color = customColor || COLORS[name] || 'currentColor';\n return svgWrapper(path, color, size);\n }\n return '';\n}\n/**\n * Pre-built icon strings for common use cases\n */\nexport const Icons = {\n // File system\n folder: getIcon('folder'),\n file: getIcon('file'),\n explore: getIcon('explore'),\n read: getIcon('read'),\n write: getIcon('write'),\n // Status\n success: getIcon('success'),\n error: getIcon('error'),\n warning: getIcon('warning'),\n info: getIcon('info'),\n check: getIcon('check'),\n thinking: getIcon960('thinking'),\n // Actions\n edit: getIcon('edit'),\n search: getIcon('search'),\n save: getIcon('save'),\n diagnostics: getIcon('diagnostics'),\n // Other\n question: getIcon('question'),\n description: getIcon960('description'),\n code: getIcon('code'),\n terminal: getIcon('terminal'),\n analytics: getIcon('analytics'),\n skip: getIcon('skip'),\n // Agents/Roles\n planner: getIcon('planner'),\n build: getIcon('build'),\n person: getIcon('person'),\n researcher: getIcon('researcher'),\n // UI elements\n expandMore: getIcon('expandMore'),\n expandLess: getIcon('expandLess'),\n chevronRight: getIcon('chevronRight'),\n // Summary/Tasks\n summary: getIcon('summary'),\n assignment: getIcon960('assignment'),\n listAlt: getIcon('listAlt'),\n // Tool execution\n tool: getIcon960('tool'),\n // Subagent icons\n agent: getIcon960('agent'),\n subagentComplete: getIcon960('subagentComplete'),\n subagentStart: getIcon960('subagentStart'),\n // Status icons\n pause: getIcon960('pause'),\n play: getIcon960('play'),\n lightbulb: getIcon960('lightbulb'),\n // Risk levels (colored circles)\n riskLow: getIcon('success', 16, '#4caf50'),\n riskMedium: getIcon('info', 16, '#2196f3'),\n riskHigh: getIcon('warning', 16, '#ff9800'),\n riskCritical: getIcon('error', 16, '#f44336'),\n // Approval action icons\n approveCircle: getIcon960('approveCircle', 20),\n rejectCircle: getIcon960('rejectCircle', 20),\n doubleCheck: getIcon960('doubleCheck', 16),\n approvalWarning: getIcon960('approvalWarning', 16),\n};\nexport default Icons;\n","/**\n * Markdown to HTML converter with syntax highlighting\n * Based on chrome_agent's formatMarkdownToHtml implementation\n */\nimport { getIconByName } from './icons';\n/**\n * Simple hash function for generating stable IDs from content\n * Uses djb2 algorithm for fast string hashing\n */\nfunction simpleHash(str) {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash) + str.charCodeAt(i);\n hash = hash & hash; // Convert to 32-bit integer\n }\n // Convert to positive hex string\n return Math.abs(hash).toString(36);\n}\n/**\n * Escape HTML special characters\n */\nexport function escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n/**\n * Normalize indentation in code blocks\n */\nexport function normalizeIndentation(code) {\n const lines = code.split('\\n');\n // Find minimum indent from non-empty lines\n let minIndent = Infinity;\n const nonEmptyLines = [];\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().length > 0) {\n const match = line.match(/^(\\s*)/);\n const indent = match ? match[1].length : 0;\n minIndent = Math.min(minIndent, indent);\n nonEmptyLines.push(i);\n }\n }\n // If no indent or no non-empty lines, return original\n if (minIndent === Infinity || minIndent === 0) {\n return code;\n }\n // Remove minimum indent from all lines\n const normalized = lines.map(line => {\n if (line.trim().length === 0) {\n return '';\n }\n return line.substring(minIndent);\n });\n return normalized.join('\\n');\n}\nfunction extractNextItemsBlock(text) {\n if (!text.trim()) {\n return null;\n }\n // Debug: Check if text contains summary JSON markers\n const hasSummaryMarker = text.includes('\"summary\"');\n const hasNextItemsMarker = text.includes('\"next_items\"');\n if (hasSummaryMarker && hasNextItemsMarker) {\n console.log('[extractNextItemsBlock] Found summary JSON markers, text length:', text.length);\n console.log('[extractNextItemsBlock] Text preview:', text.slice(0, 200));\n }\n // Try multiple regex patterns for fenced code blocks:\n // 1. Standard: ```json\\n...\\n``` (with language and newline)\n // 2. No newline: ```json{...}``` (no newline after language)\n // 3. No language: ```\\n...\\n``` or ```{...}```\n const fencedPatterns = [\n /```(\\w+)?\\n([\\s\\S]*?)```/g,\n /```(\\w+)?({[\\s\\S]*?})```/g, // Without newline, content starts with {\n ];\n for (const fencedRegex of fencedPatterns) {\n let match;\n while ((match = fencedRegex.exec(text)) !== null) {\n const lang = (match[1] || '').toLowerCase();\n const content = match[2].trim();\n if (!content)\n continue;\n if (lang && lang !== 'json' && !content.includes('\"next_items\"')) {\n continue;\n }\n const parsed = parseNextItemsPayload(content);\n if (parsed) {\n const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;\n const updated = text.slice(0, match.index) + placeholder + text.slice(match.index + match[0].length);\n return { items: parsed.items, summary: parsed.summary, placeholder, text: updated };\n }\n }\n // Reset lastIndex for next pattern\n fencedRegex.lastIndex = 0;\n }\n const range = findNextItemsJsonRange(text);\n if (!range) {\n console.log('[extractNextItemsBlock] findNextItemsJsonRange returned null');\n return null;\n }\n console.log('[extractNextItemsBlock] Found JSON range:', range.start, '-', range.end);\n const candidate = text.slice(range.start, range.end + 1);\n console.log('[extractNextItemsBlock] Candidate JSON length:', candidate.length);\n console.log('[extractNextItemsBlock] Candidate JSON preview:', candidate.slice(0, 200));\n const parsed = parseNextItemsPayload(candidate);\n if (!parsed) {\n console.log('[extractNextItemsBlock] parseNextItemsPayload returned null');\n return null;\n }\n // Calculate replacement range\n let replacementStart = range.start;\n let replacementEnd = range.end;\n const lineStart = text.lastIndexOf('\\n', range.start - 1) + 1;\n const linePrefix = text.slice(lineStart, range.start).trim();\n const normalizedPrefix = linePrefix.replace(/[::]$/, '').toLowerCase();\n if (normalizedPrefix === 'json') {\n replacementStart = lineStart;\n }\n // CRITICAL FIX: Handle nested/malformed JSON wrappers\n // Check if there's orphaned JSON wrapper before our JSON (e.g., '{\"summary\":' or '{\"summary\": ')\n const textBeforeJson = text.slice(0, replacementStart);\n const wrapperPatterns = [\n /\\{\\s*\"summary\"\\s*:\\s*$/,\n /\\{\\s*\"next_items\"\\s*:\\s*$/,\n /\\{\\s*$/, // just {\n ];\n for (const pattern of wrapperPatterns) {\n const wrapperMatch = textBeforeJson.match(pattern);\n if (wrapperMatch) {\n // Found an orphaned wrapper, extend replacement to include it\n const wrapperStart = textBeforeJson.lastIndexOf(wrapperMatch[0]);\n if (wrapperStart !== -1) {\n console.log('[extractNextItemsBlock] Found orphaned wrapper:', wrapperMatch[0]);\n replacementStart = wrapperStart;\n break;\n }\n }\n }\n // Check if there's orphaned closing brace after our JSON\n const textAfterJson = text.slice(range.end + 1);\n const closingMatch = textAfterJson.match(/^\\s*\\}/);\n if (closingMatch) {\n console.log('[extractNextItemsBlock] Found orphaned closing brace');\n replacementEnd = range.end + closingMatch[0].length;\n }\n const placeholder = `__NEXT_ITEMS_${Math.random().toString(36).slice(2, 11)}__`;\n const updated = text.slice(0, replacementStart) + placeholder + text.slice(replacementEnd + 1);\n console.log('[extractNextItemsBlock] Replacement range:', replacementStart, '-', replacementEnd);\n console.log('[extractNextItemsBlock] Updated text preview:', updated.slice(0, 100));\n return { items: parsed.items, summary: parsed.summary, placeholder, text: updated };\n}\nfunction findNextItemsJsonRange(text) {\n const key = '\"next_items\"';\n const keyIndex = text.indexOf(key);\n if (keyIndex === -1)\n return null;\n // Find all { before \"next_items\" and try from EARLIEST (outermost) first\n // This handles cases where summary contains nested braces like {\"summary\": \"{test}\", ...}\n const candidates = [];\n for (let i = 0; i < keyIndex; i++) {\n if (text[i] === '{') {\n candidates.push(i);\n }\n }\n console.log('[findNextItemsJsonRange] Found', candidates.length, 'candidate { positions before \"next_items\"');\n // Try from earliest { (most likely to be the outer JSON object)\n for (const start of candidates) {\n const end = findMatchingBrace(text, start);\n if (end !== -1 && end > keyIndex) {\n // This { ... } contains \"next_items\"\n console.log('[findNextItemsJsonRange] Found valid range:', start, '-', end);\n return { start, end };\n }\n }\n return null;\n}\nfunction findMatchingBrace(text, start) {\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = start; i < text.length; i++) {\n const char = text[i];\n if (inString) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\') {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = false;\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n continue;\n }\n if (char === '{') {\n depth += 1;\n }\n else if (char === '}') {\n depth -= 1;\n if (depth === 0) {\n return i;\n }\n }\n }\n return -1;\n}\nfunction parseNextItemsPayload(payload) {\n // Trim whitespace to handle trailing newlines\n const trimmedPayload = payload.trim();\n if (!trimmedPayload.startsWith('{') || !trimmedPayload.endsWith('}')) {\n console.log('[parseNextItemsPayload] Invalid JSON structure - starts with:', trimmedPayload.slice(0, 20), 'ends with:', trimmedPayload.slice(-20));\n return null;\n }\n // Use trimmed payload for parsing\n payload = trimmedPayload;\n // Check if JSON is escaped (starts with {\\\" instead of {\")\n // This can happen when LLM outputs JSON inside another context\n let payloadToparse = payload;\n if (payload.includes('\\\\\"') && !payload.includes('{\"')) {\n console.log('[parseNextItemsPayload] Detected escaped JSON, attempting to unescape');\n // Unescape: \\\" -> \", \\\\ -> \\\n payloadToparse = payload.replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, '\\\\');\n console.log('[parseNextItemsPayload] Unescaped JSON preview:', payloadToparse.slice(0, 100));\n }\n try {\n console.log('[parseNextItemsPayload] Attempting to parse JSON, length:', payloadToparse.length);\n const parsed = JSON.parse(payloadToparse);\n console.log('[parseNextItemsPayload] JSON parsed successfully, keys:', Object.keys(parsed));\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return null;\n }\n const nextItemsRaw = parsed.next_items;\n if (!Array.isArray(nextItemsRaw)) {\n return null;\n }\n const items = nextItemsRaw\n .map((item) => {\n if (!item || typeof item !== 'object')\n return null;\n const subject = typeof item.subject === 'string'\n ? item.subject.trim()\n : '';\n const description = typeof item.description === 'string'\n ? item.description.trim()\n : '';\n if (!subject && !description)\n return null;\n return { subject, description };\n })\n .filter((item) => Boolean(item));\n // Extract summary field if present\n const summaryRaw = parsed.summary;\n const summary = typeof summaryRaw === 'string' && summaryRaw.trim() ? summaryRaw.trim() : null;\n // Return if either summary or items exist\n return (items.length > 0 || summary) ? { items, summary } : null;\n }\n catch (e) {\n console.error('[parseNextItemsPayload] JSON parse error:', e);\n console.error('[parseNextItemsPayload] Failed payload:', payload.slice(0, 500));\n return null;\n }\n}\nfunction renderNextItemsList(items, summary) {\n // Simple arrow icon for next items\n const arrowSvg = `\n<svg class=\"jp-next-items-icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z\"/>\n</svg>`;\n // Checkmark icon for summary\n const checkSvg = `\n<svg class=\"jp-summary-icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\"/>\n</svg>`;\n const listItems = items.map((item) => {\n const subject = escapeHtml(item.subject);\n const description = escapeHtml(item.description);\n const subjectHtml = subject ? `<div class=\"jp-next-items-subject\">${subject}</div>` : '';\n const descriptionHtml = description ? `<div class=\"jp-next-items-description\">${description}</div>` : '';\n return `\n<li class=\"jp-next-items-item\" data-next-item=\"true\" role=\"button\" tabindex=\"0\">\n <div class=\"jp-next-items-text\">\n ${subjectHtml}\n ${descriptionHtml}\n </div>\n ${arrowSvg}\n</li>`;\n }).join('');\n // Get icons for headers\n const summaryIcon = getIconByName('summary', 18);\n const assignmentIcon = getIconByName('assignment', 18);\n // Render summary as separate block above next items\n const summaryHtml = summary ? `\n<div class=\"jp-summary-block\">\n <div class=\"jp-summary-header\"><span class=\"jp-summary-header-icon\">${summaryIcon}</span>작업 요약</div>\n <div class=\"jp-summary-body\">\n ${checkSvg}\n <div class=\"jp-summary-content\">${escapeHtml(summary)}</div>\n </div>\n</div>` : '';\n // Only show next items section if there are items\n const nextItemsHtml = items.length > 0 ? `\n<div class=\"jp-next-items\" data-next-items=\"true\">\n <div class=\"jp-next-items-header\"><span class=\"jp-next-items-header-icon\">${assignmentIcon}</span>다음 단계 제안</div>\n <ul class=\"jp-next-items-list\" role=\"list\">\n ${listItems}\n </ul>\n</div>` : '';\n return `${summaryHtml}\n${nextItemsHtml}`;\n}\n/**\n * Highlight Python code with CSS classes for theme support\n */\nexport function highlightPython(code) {\n let highlighted = code;\n // Use CSS classes instead of inline styles for theme support\n const classes = {\n COMMENT: 'syntax-comment',\n STRING: 'syntax-string',\n NUMBER: 'syntax-number',\n KEYWORD: 'syntax-keyword',\n BUILTIN: 'syntax-builtin',\n FUNCTION: 'syntax-function',\n OPERATOR: 'syntax-operator',\n BRACKET: 'syntax-bracket'\n };\n // Use placeholders to preserve order\n const placeholders = [];\n let placeholderIndex = 0;\n // Comments (process first)\n highlighted = highlighted.replace(/(#.*$)/gm, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span class=\"${classes.COMMENT}\">${escapeHtml(match)}</span>`\n });\n return id;\n });\n // Triple-quoted strings\n highlighted = highlighted.replace(/(['\"]{3})([\\s\\S]*?)(\\1)/g, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span class=\"${classes.STRING}\">${escapeHtml(match)}</span>`\n });\n return id;\n });\n // Regular strings\n highlighted = highlighted.replace(/(['\"])([^'\"]*?)(\\1)/g, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span class=\"${classes.STRING}\">${escapeHtml(match)}</span>`\n });\n return id;\n });\n // Numbers\n highlighted = highlighted.replace(/\\b(\\d+\\.?\\d*)\\b/g, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span class=\"${classes.NUMBER}\">${match}</span>`\n });\n return id;\n });\n // Python keywords\n const keywords = [\n 'def', 'class', 'if', 'elif', 'else', 'for', 'while', 'return', 'import',\n 'from', 'as', 'try', 'except', 'finally', 'with', 'lambda', 'yield',\n 'async', 'await', 'pass', 'break', 'continue', 'raise', 'assert', 'del',\n 'global', 'nonlocal', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is'\n ];\n keywords.forEach(keyword => {\n const regex = new RegExp(`\\\\b${keyword}\\\\b`, 'g');\n highlighted = highlighted.replace(regex, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span class=\"${classes.KEYWORD}\">${match}</span>`\n });\n return id;\n });\n });\n // Built-in functions\n const builtins = [\n 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'tuple',\n 'set', 'bool', 'type', 'isinstance', 'issubclass', 'hasattr', 'getattr',\n 'setattr', 'delattr', 'dir', 'vars', 'locals', 'globals', 'input', 'open',\n 'file', 'abs', 'all', 'any', 'bin', 'chr', 'ord', 'hex', 'oct', 'pow',\n 'round', 'sum', 'min', 'max', 'sorted', 'reversed', 'enumerate', 'zip',\n 'map', 'filter', 'reduce'\n ];\n builtins.forEach(builtin => {\n const regex = new RegExp(`\\\\b${builtin}\\\\b`, 'g');\n highlighted = highlighted.replace(regex, (match) => {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span class=\"${classes.BUILTIN}\">${match}</span>`\n });\n return id;\n });\n });\n // Function definitions - def keyword followed by function name\n highlighted = highlighted.replace(/(__PH\\d+__\\s+)([a-zA-Z_][a-zA-Z0-9_]*)/g, (match, defPart, funcName) => {\n const defPlaceholder = placeholders.find(p => p.id === defPart.trim());\n if (defPlaceholder && defPlaceholder.html.includes('def')) {\n const id = `__PH${placeholderIndex++}__`;\n placeholders.push({\n id,\n html: `<span class=\"${classes.FUNCTION}\">${funcName}</span>`\n });\n return defPart + id;\n }\n return match;\n });\n // Process remaining text - escape HTML and handle operators/brackets\n highlighted = highlighted.split(/(__PH\\d+__)/g).map(part => {\n if (part.match(/^__PH\\d+__$/)) {\n return part; // Keep placeholder as is\n }\n // Escape HTML\n part = escapeHtml(part);\n // Operators\n part = part.replace(/([+\\-*/%=<>!&|^~]+)/g, `<span class=\"${classes.OPERATOR}\">$1</span>`);\n // Brackets and delimiters\n part = part.replace(/([()[\\]{}])/g, `<span class=\"${classes.BRACKET}\">$1</span>`);\n return part;\n }).join('');\n // Replace placeholders with actual HTML\n placeholders.forEach(ph => {\n highlighted = highlighted.replace(ph.id, ph.html);\n });\n return highlighted;\n}\n/**\n * Highlight diff output with colors for additions/deletions\n */\nexport function highlightDiff(code) {\n const lines = code.split('\\n');\n const highlightedLines = lines.map(line => {\n const escapedLine = escapeHtml(line);\n // File headers (--- and +++)\n if (line.startsWith('---') || line.startsWith('+++')) {\n return `<span class=\"diff-header\">${escapedLine}</span>`;\n }\n // Hunk headers (@@ ... @@)\n if (line.startsWith('@@')) {\n return `<span class=\"diff-hunk\">${escapedLine}</span>`;\n }\n // Added lines\n if (line.startsWith('+')) {\n return `<span class=\"diff-add\">${escapedLine}</span>`;\n }\n // Removed lines\n if (line.startsWith('-')) {\n return `<span class=\"diff-del\">${escapedLine}</span>`;\n }\n // Context lines (unchanged)\n return `<span class=\"diff-context\">${escapedLine}</span>`;\n });\n return highlightedLines.join('\\n');\n}\n/**\n * Count additions and deletions in diff code\n */\nexport function countDiffLines(code) {\n const lines = code.split('\\n');\n let additions = 0;\n let deletions = 0;\n for (const line of lines) {\n // Skip file headers\n if (line.startsWith('+++') || line.startsWith('---'))\n continue;\n if (line.startsWith('+'))\n additions++;\n else if (line.startsWith('-'))\n deletions++;\n }\n return { additions, deletions };\n}\n/**\n * Highlight JavaScript code\n */\nexport function highlightJavaScript(code) {\n const escaped = escapeHtml(code);\n const lines = escaped.split('\\n');\n const keywords = [\n 'function', 'const', 'let', 'var', 'if', 'else', 'for', 'while',\n 'return', 'class', 'import', 'export', 'from', 'async', 'await',\n 'new', 'this', 'null', 'undefined', 'true', 'false', 'typeof'\n ];\n let inMultilineComment = false;\n const highlightedLines = lines.map(line => {\n // Check for multiline comment continuation\n if (inMultilineComment) {\n const endIndex = line.indexOf('*/');\n if (endIndex !== -1) {\n inMultilineComment = false;\n return `<span class=\"syntax-comment\">${line.substring(0, endIndex + 2)}</span>` +\n highlightJSTokens(line.substring(endIndex + 2), keywords);\n }\n return `<span class=\"syntax-comment\">${line}</span>`;\n }\n // Single-line comment\n const commentMatch = line.match(/^(\\s*)(\\/\\/.*)$/);\n if (commentMatch) {\n return commentMatch[1] + `<span class=\"syntax-comment\">${commentMatch[2]}</span>`;\n }\n // Multiline comment start\n const multiCommentStart = line.indexOf('/*');\n if (multiCommentStart !== -1) {\n const multiCommentEnd = line.indexOf('*/', multiCommentStart);\n if (multiCommentEnd !== -1) {\n return highlightJSTokens(line.substring(0, multiCommentStart), keywords) +\n `<span class=\"syntax-comment\">${line.substring(multiCommentStart, multiCommentEnd + 2)}</span>` +\n highlightJSTokens(line.substring(multiCommentEnd + 2), keywords);\n }\n else {\n inMultilineComment = true;\n return highlightJSTokens(line.substring(0, multiCommentStart), keywords) +\n `<span class=\"syntax-comment\">${line.substring(multiCommentStart)}</span>`;\n }\n }\n // Comment in middle of line\n const commentIndex = line.indexOf('//');\n if (commentIndex !== -1) {\n return highlightJSTokens(line.substring(0, commentIndex), keywords) +\n `<span class=\"syntax-comment\">${line.substring(commentIndex)}</span>`;\n }\n return highlightJSTokens(line, keywords);\n });\n return highlightedLines.join('\\n');\n}\n/**\n * Highlight JavaScript tokens (keywords, strings, numbers)\n * Uses CSS classes for theme support\n */\nfunction highlightJSTokens(line, keywords) {\n const container = document.createElement('span');\n let i = 0;\n while (i < line.length) {\n // String check (template literal, double quote, single quote)\n if (line[i] === '`') {\n let j = i + 1;\n let escaped = false;\n while (j < line.length) {\n if (line[j] === '\\\\' && !escaped) {\n escaped = true;\n j++;\n continue;\n }\n if (line[j] === '`' && !escaped)\n break;\n escaped = false;\n j++;\n }\n if (j < line.length && line[j] === '`') {\n const span = document.createElement('span');\n span.className = 'syntax-string';\n span.textContent = line.substring(i, j + 1);\n container.appendChild(span);\n i = j + 1;\n continue;\n }\n }\n if (line[i] === '\"') {\n let j = i + 1;\n let escaped = false;\n while (j < line.length) {\n if (line[j] === '\\\\' && !escaped) {\n escaped = true;\n j++;\n continue;\n }\n if (line[j] === '\"' && !escaped)\n break;\n escaped = false;\n j++;\n }\n if (j < line.length && line[j] === '\"') {\n const span = document.createElement('span');\n span.className = 'syntax-string';\n span.textContent = line.substring(i, j + 1);\n container.appendChild(span);\n i = j + 1;\n continue;\n }\n // Unclosed string - treat rest as string\n const span = document.createElement('span');\n span.className = 'syntax-string';\n span.textContent = line.substring(i);\n container.appendChild(span);\n break;\n }\n if (line[i] === '\\'') {\n let j = i + 1;\n let escaped = false;\n while (j < line.length) {\n if (line[j] === '\\\\' && !escaped) {\n escaped = true;\n j++;\n continue;\n }\n if (line[j] === '\\'' && !escaped)\n break;\n escaped = false;\n j++;\n }\n if (j < line.length && line[j] === '\\'') {\n const span = document.createElement('span');\n span.className = 'syntax-string';\n span.textContent = line.substring(i, j + 1);\n container.appendChild(span);\n i = j + 1;\n continue;\n }\n // Unclosed string\n const span = document.createElement('span');\n span.className = 'syntax-string';\n span.textContent = line.substring(i);\n container.appendChild(span);\n break;\n }\n // Number check\n if (/\\d/.test(line[i])) {\n let j = i;\n while (j < line.length && /[\\d.]/.test(line[j])) {\n j++;\n }\n const span = document.createElement('span');\n span.className = 'syntax-number';\n span.textContent = line.substring(i, j);\n container.appendChild(span);\n i = j;\n continue;\n }\n // Word check (keyword or identifier)\n if (/[a-zA-Z_$]/.test(line[i])) {\n let j = i;\n while (j < line.length && /[a-zA-Z0-9_$]/.test(line[j])) {\n j++;\n }\n const word = line.substring(i, j);\n if (keywords.includes(word)) {\n const span = document.createElement('span');\n span.className = 'syntax-keyword';\n span.textContent = word;\n container.appendChild(span);\n }\n else {\n const textNode = document.createTextNode(word);\n container.appendChild(textNode);\n }\n i = j;\n continue;\n }\n // Regular character\n const textNode = document.createTextNode(line[i]);\n container.appendChild(textNode);\n i++;\n }\n return container.innerHTML;\n}\n/**\n * Format inline markdown (bold, italic, inline code) within text\n */\nexport function formatInlineMarkdown(text) {\n let html = escapeHtml(text);\n // Inline code first (to protect from other transformations)\n html = html.replace(/`([^`]+)`/g, '<code class=\"inline-code\">$1</code>');\n // Bold text (**text**)\n html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');\n // Italic text (*text*)\n html = html.replace(/\\*([^*]+)\\*/g, '<em>$1</em>');\n return html;\n}\n/**\n * Parse markdown table to HTML\n */\nexport function parseMarkdownTable(tableText) {\n const lines = tableText.trim().split('\\n');\n if (lines.length < 2)\n return escapeHtml(tableText);\n // Check if it's a valid table (has header separator)\n const separatorIndex = lines.findIndex(line => /^\\|?\\s*[-:]+[-|\\s:]+\\s*\\|?$/.test(line));\n if (separatorIndex === -1 || separatorIndex === 0)\n return escapeHtml(tableText);\n const headerLines = lines.slice(0, separatorIndex);\n const separatorLine = lines[separatorIndex];\n const bodyLines = lines.slice(separatorIndex + 1);\n // Parse alignment from separator\n const alignments = [];\n const separatorCells = separatorLine.split('|').filter(cell => cell.trim());\n separatorCells.forEach(cell => {\n const trimmed = cell.trim();\n if (trimmed.startsWith(':') && trimmed.endsWith(':')) {\n alignments.push('center');\n }\n else if (trimmed.endsWith(':')) {\n alignments.push('right');\n }\n else {\n alignments.push('left');\n }\n });\n // Build HTML table with wrapper for horizontal scroll\n let html = '<div class=\"markdown-table-wrapper\"><table class=\"markdown-table\">';\n // Header\n html += '<thead>';\n headerLines.forEach(line => {\n html += '<tr>';\n const cells = line.split('|').filter((cell, idx, arr) => {\n // Filter out empty cells from leading/trailing |\n if (idx === 0 && cell.trim() === '')\n return false;\n if (idx === arr.length - 1 && cell.trim() === '')\n return false;\n return true;\n });\n cells.forEach((cell, idx) => {\n const align = alignments[idx] || 'left';\n html += `<th style=\"text-align: ${align};\">${formatInlineMarkdown(cell.trim())}</th>`;\n });\n html += '</tr>';\n });\n html += '</thead>';\n // Body\n if (bodyLines.length > 0) {\n html += '<tbody>';\n bodyLines.forEach(line => {\n if (!line.trim())\n return;\n html += '<tr>';\n const cells = line.split('|').filter((cell, idx, arr) => {\n if (idx === 0 && cell.trim() === '')\n return false;\n if (idx === arr.length - 1 && cell.trim() === '')\n return false;\n return true;\n });\n cells.forEach((cell, idx) => {\n const align = alignments[idx] || 'left';\n html += `<td style=\"text-align: ${align};\">${formatInlineMarkdown(cell.trim())}</td>`;\n });\n html += '</tr>';\n });\n html += '</tbody>';\n }\n html += '</table></div>';\n return html;\n}\n/**\n * Format markdown text to HTML with syntax highlighting\n */\nexport function formatMarkdownToHtml(text) {\n console.log('[formatMarkdownToHtml] Input text length:', text.length);\n console.log('[formatMarkdownToHtml] Has \"next_items\":', text.includes('\"next_items\"'));\n console.log('[formatMarkdownToHtml] Text preview:', text.slice(0, 150));\n const nextItemsBlock = extractNextItemsBlock(text);\n console.log('[formatMarkdownToHtml] nextItemsBlock result:', nextItemsBlock ? 'found' : 'null');\n // Decode HTML entities if present\n const textarea = document.createElement('textarea');\n textarea.innerHTML = nextItemsBlock ? nextItemsBlock.text : text;\n let html = textarea.value;\n // Step 0.5: Protect DataFrame HTML tables (must be before code blocks)\n const dataframeHtmlPlaceholders = [];\n html = html.replace(/<!--DFHTML-->([\\s\\S]*?)<!--\\/DFHTML-->/g, (match, tableHtml) => {\n const placeholder = '__DATAFRAME_HTML_' + Math.random().toString(36).substr(2, 9) + '__';\n dataframeHtmlPlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return placeholder;\n });\n // Step 1: Protect code blocks by replacing with placeholders\n const codeBlocks = [];\n const codeBlockPlaceholders = [];\n html = html.replace(/```(\\w+)?\\n([\\s\\S]*?)```/g, (match, language, code) => {\n const lang = (language || 'python').toLowerCase();\n const trimmedCode = normalizeIndentation(code.trim());\n // Use content-based hash for stable ID across re-renders\n const contentHash = simpleHash(trimmedCode + lang);\n const blockId = 'code-block-' + contentHash;\n const placeholder = '__CODE_BLOCK_' + blockId + '__';\n codeBlocks.push({\n id: blockId,\n code: trimmedCode,\n language: lang\n });\n // Create HTML for code block\n const isDiff = lang === 'diff';\n const highlightedCode = isDiff\n ? highlightDiff(trimmedCode)\n : lang === 'python' || lang === 'py'\n ? highlightPython(trimmedCode)\n : lang === 'javascript' || lang === 'js'\n ? highlightJavaScript(trimmedCode)\n : escapeHtml(trimmedCode);\n // For diff blocks, calculate and show line counts\n let diffLineCountHtml = '';\n if (isDiff) {\n const { additions, deletions } = countDiffLines(trimmedCode);\n diffLineCountHtml = '<span class=\"diff-line-counts\">' +\n '<span class=\"diff-additions\">+' + additions + '</span>' +\n '<span class=\"diff-deletions\">-' + deletions + '</span>' +\n '</span>';\n }\n const htmlBlock = '<div class=\"code-block-container' + (isDiff ? ' diff-block' : '') + '\" data-block-id=\"' + blockId + '\">' +\n '<div class=\"code-block-header\">' +\n '<span class=\"code-block-language\">' + escapeHtml(lang) + '</span>' +\n diffLineCountHtml +\n '<div class=\"code-block-actions\">' +\n (isDiff ? '' : '<button class=\"code-block-apply\" data-block-id=\"' + blockId + '\" title=\"셀에 적용\">셀에 적용</button>') +\n '<button class=\"code-block-copy\" data-block-id=\"' + blockId + '\" title=\"복사\">복사</button>' +\n '</div>' +\n '</div>' +\n '<pre class=\"code-block language-' + escapeHtml(lang) + '\"><code id=\"' + blockId + '\">' + highlightedCode + '</code></pre>' +\n '<button class=\"code-block-toggle\" data-block-id=\"' + blockId + '\" title=\"전체 보기\" aria-label=\"전체 보기\" aria-expanded=\"false\">' +\n '<span class=\"code-block-toggle-icon\" aria-hidden=\"true\">▾</span>' +\n '</button>' +\n '</div>';\n codeBlockPlaceholders.push({\n placeholder: placeholder,\n html: htmlBlock\n });\n return placeholder;\n });\n // Step 2: Protect inline code\n const inlineCodePlaceholders = [];\n html = html.replace(/`([^`]+)`/g, (match, code) => {\n const placeholder = '__INLINE_CODE_' + Math.random().toString(36).substr(2, 9) + '__';\n inlineCodePlaceholders.push({\n placeholder: placeholder,\n html: '<code class=\"inline-code\">' + escapeHtml(code) + '</code>'\n });\n return placeholder;\n });\n // Step 3: Parse and protect markdown tables\n const tablePlaceholders = [];\n // Improved table detection: look for lines with | separators and a separator row\n // Match pattern: header row(s), separator row (with ---), body rows\n // More flexible regex to handle various table formats\n const tableRegex = /(?:^|\\n)((?:\\|[^\\n]+\\|\\n?)+\\|[-:| ]+\\|(?:\\n\\|[^\\n]+\\|)*)/gm;\n html = html.replace(tableRegex, (match, tableBlock) => {\n const placeholder = '__TABLE_' + Math.random().toString(36).substr(2, 9) + '__';\n const tableHtml = parseMarkdownTable(tableBlock.trim());\n tablePlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return '\\n' + placeholder + '\\n';\n });\n // Alternative: tables without leading/trailing | (GFM style)\n // Pattern: \"header | header\\n---|---\\ndata | data\"\n const gfmTableRegex = /(?:^|\\n)((?:[^\\n|]+\\|[^\\n]+\\n)+[-:|\\s]+[-|]+\\n(?:[^\\n|]+\\|[^\\n]+\\n?)*)/gm;\n html = html.replace(gfmTableRegex, (match, tableBlock) => {\n // Skip if already processed (contains placeholder)\n if (tableBlock.includes('__TABLE_'))\n return match;\n const placeholder = '__TABLE_' + Math.random().toString(36).substr(2, 9) + '__';\n const tableHtml = parseMarkdownTable(tableBlock.trim());\n tablePlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return '\\n' + placeholder + '\\n';\n });\n // Third pattern: catch any remaining tables with | characters and --- separator\n const fallbackTableRegex = /(?:^|\\n)(\\|[^\\n]*\\|\\n\\|[-:| ]*\\|(?:\\n\\|[^\\n]*\\|)*)/gm;\n html = html.replace(fallbackTableRegex, (match, tableBlock) => {\n if (tableBlock.includes('__TABLE_'))\n return match;\n const placeholder = '__TABLE_' + Math.random().toString(36).substr(2, 9) + '__';\n const tableHtml = parseMarkdownTable(tableBlock.trim());\n tablePlaceholders.push({\n placeholder: placeholder,\n html: tableHtml\n });\n return '\\n' + placeholder + '\\n';\n });\n // Step 4: Escape HTML for non-placeholder text\n html = html.split(/(__(?:DATAFRAME_HTML|CODE_BLOCK|INLINE_CODE|TABLE|NEXT_ITEMS)_[a-z0-9-]+__)/gi)\n .map((part, index) => {\n // Odd indices are placeholders - keep as is\n if (index % 2 === 1)\n return part;\n // Even indices are regular text - escape HTML\n return escapeHtml(part);\n })\n .join('');\n // Step 5: Convert markdown to HTML\n // Headings (process from h6 to h1 to avoid conflicts)\n html = html.replace(/^###### (.*$)/gim, '<h6>$1</h6>');\n html = html.replace(/^##### (.*$)/gim, '<h5>$1</h5>');\n html = html.replace(/^#### (.*$)/gim, '<h4>$1</h4>');\n html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');\n html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');\n html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');\n // Horizontal rule (---)\n html = html.replace(/^---+$/gim, '<hr>');\n // Links [text](url)\n html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>');\n // Lists - process BEFORE bold/italic to handle \"* item\" correctly\n // Unordered lists: - or * at start of line\n html = html.replace(/^[\\-\\*]\\s+(.*$)/gim, '<li>$1</li>');\n // Numbered lists: 1. 2. etc\n html = html.replace(/^\\d+\\.\\s+(.*$)/gim, '<li>$1</li>');\n // Bold text (must be before italic)\n html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');\n // Italic text - only match single * not at line start (to avoid conflict with lists)\n html = html.replace(/(?<!\\*)\\*([^*\\n]+)\\*(?!\\*)/g, '<em>$1</em>');\n // Line breaks\n html = html.replace(/\\n/g, '<br>');\n // Step 6: Restore table placeholders\n tablePlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 6.5: Restore DataFrame HTML tables\n dataframeHtmlPlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 7: Restore inline code placeholders\n inlineCodePlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 8: Restore code block placeholders\n codeBlockPlaceholders.forEach(item => {\n html = html.replace(item.placeholder, item.html);\n });\n // Step 8.5: Restore next items list placeholders\n if (nextItemsBlock) {\n html = html.split(nextItemsBlock.placeholder).join(renderNextItemsList(nextItemsBlock.items, nextItemsBlock.summary));\n }\n return html;\n}\n","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2M9 17H7v-5h2zm4 0h-2v-3h2zm0-5h-2v-2h2zm4 5h-2V7h2z\"\n}), 'Analytics');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M7.5 5.6 10 7 8.6 4.5 10 2 7.5 3.4 5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17 19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22 7l-1.4-2.5zm-7.63 5.29a.996.996 0 0 0-1.41 0L1.29 18.96c-.39.39-.39 1.02 0 1.41l2.34 2.34c.39.39 1.02.39 1.41 0L16.7 11.05c.39-.39.39-1.02 0-1.41zm-1.03 5.49-2.12-2.12 2.44-2.44 2.12 2.12z\"\n}), 'AutoFixHigh');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2m5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12z\"\n}), 'Cancel');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"\n}), 'Check');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8z\"\n}), 'CheckCircle');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"\n}), 'ChevronRight');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n}), 'Close');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M9.4 16.6 4.8 12l4.6-4.6L8 6l-6 6 6 6zm5.2 0 4.6-4.6-4.6-4.6L16 6l6 6-6 6z\"\n}), 'Code');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m0 16H8V7h11z\"\n}), 'ContentCopy');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8zm2 16H8v-2h8zm0-4H8v-2h8zm-3-5V3.5L18.5 9z\"\n}), 'Description');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-2h2zm0-4h-2V7h2z\"\n}), 'Error');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"m12 8-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\"\n}), 'ExpandLess');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z\"\n}), 'ExpandMore');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8z\"\n}), 'Folder');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4m8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7\"\n}), 'GpsFixed');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2zm10 14.5V20H8v-3.5l4-4zm-4-5-4-4V4h8v3.5z\"\n}), 'HourglassEmpty');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm7 7V3.5L18.5 9z\"\n}), 'InsertDriveFile');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M9 21c0 .5.4 1 1 1h4c.6 0 1-.5 1-1v-1H9zm3-19C8.1 2 5 5.1 5 9c0 2.4 1.2 4.5 3 5.7V17c0 .5.4 1 1 1h6c.6 0 1-.5 1-1v-2.3c1.8-1.3 3-3.4 3-5.7 0-3.9-3.1-7-7-7\"\n}), 'Lightbulb');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M14 10H3v2h11zm0-4H3v2h11zm4 8v-4h-2v4h-4v2h4v4h2v-4h4v-2zM3 16h7v-2H3z\"\n}), 'PlaylistAdd');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14\"\n}), 'Search');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M16 13h-3V3h-2v10H8l4 4zM4 19v2h16v-2z\"\n}), 'VerticalAlignBottom');","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"M8 11h3v10h2V11h3l-4-4zM4 3v2h16V3z\"\n}), 'VerticalAlignTop');"],"names":[],"ignoreList":[],"sourceRoot":""}
|