myagent-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +30 -0
- package/README.md +333 -0
- package/agents/__init__.py +6 -0
- package/agents/__pycache__/main_agent.cpython-312.pyc +0 -0
- package/agents/base.py +115 -0
- package/agents/main_agent.py +695 -0
- package/agents/memory_agent.py +313 -0
- package/agents/tool_agent.py +248 -0
- package/chatbot/__init__.py +5 -0
- package/chatbot/base.py +124 -0
- package/chatbot/discord_bot.py +146 -0
- package/chatbot/feishu_bot.py +548 -0
- package/chatbot/manager.py +164 -0
- package/chatbot/qq_bot.py +189 -0
- package/chatbot/telegram_bot.py +167 -0
- package/chatbot/wechat_bot.py +558 -0
- package/communication/__init__.py +66 -0
- package/communication/channel.py +576 -0
- package/communication/crypto.py +347 -0
- package/communication/manager.py +397 -0
- package/communication/peer.py +156 -0
- package/config.py +464 -0
- package/core/__init__.py +10 -0
- package/core/config_broadcast.py +276 -0
- package/core/llm.py +878 -0
- package/core/logger.py +241 -0
- package/core/task_queue.py +362 -0
- package/core/utils.py +184 -0
- package/executor/__init__.py +4 -0
- package/executor/__pycache__/engine.cpython-312.pyc +0 -0
- package/executor/engine.py +1215 -0
- package/groups/__init__.py +15 -0
- package/groups/manager.py +724 -0
- package/knowledge/__init__.py +4 -0
- package/knowledge/rag.py +444 -0
- package/main.py +801 -0
- package/memory/__init__.py +4 -0
- package/memory/manager.py +840 -0
- package/organization/__init__.py +4 -0
- package/organization/manager.py +350 -0
- package/package.json +58 -0
- package/requirements.txt +59 -0
- package/setup.py +40 -0
- package/skills/ASR/LICENSE.txt +21 -0
- package/skills/ASR/SKILL.md +580 -0
- package/skills/ASR/scripts/asr.ts +27 -0
- package/skills/LLM/LICENSE.txt +21 -0
- package/skills/LLM/SKILL.md +856 -0
- package/skills/LLM/scripts/chat.ts +32 -0
- package/skills/TTS/LICENSE.txt +21 -0
- package/skills/TTS/SKILL.md +735 -0
- package/skills/TTS/tts.ts +25 -0
- package/skills/VLM/LICENSE.txt +21 -0
- package/skills/VLM/SKILL.md +588 -0
- package/skills/VLM/scripts/vlm.ts +57 -0
- package/skills/__init__.py +5 -0
- package/skills/agent-browser/SKILL.md +328 -0
- package/skills/ai-news-collectors/SKILL.md +157 -0
- package/skills/ai-news-collectors/_meta.json +6 -0
- package/skills/ai-news-collectors/references/sources.md +128 -0
- package/skills/aminer-open-academic/SKILL.md +312 -0
- package/skills/aminer-open-academic/_meta.json +6 -0
- package/skills/aminer-open-academic/evals/evals.json +46 -0
- package/skills/aminer-open-academic/references/api-catalog.md +1032 -0
- package/skills/aminer-open-academic/scripts/__pycache__/aminer_client.cpython-312.pyc +0 -0
- package/skills/aminer-open-academic/scripts/aminer_client.py +875 -0
- package/skills/auto-target-tracker/SKILL.md +317 -0
- package/skills/base.py +147 -0
- package/skills/blog-writer/2024-02-17-radical-transparency-sales.md +35 -0
- package/skills/blog-writer/2024-02-17-raycast-spotlight-superpowers.md +33 -0
- package/skills/blog-writer/2024-02-17-short-form-content-marketing.md +47 -0
- package/skills/blog-writer/2024-02-17-typing-speed-benefits.md +33 -0
- package/skills/blog-writer/2024-03-14-effective-ai-prompts.md +55 -0
- package/skills/blog-writer/2024-11-08-ai-revolutionizing-entry-level-sales.md +43 -0
- package/skills/blog-writer/2025-11-12-why-ai-art-is-useless.md +49 -0
- package/skills/blog-writer/README.md +2 -0
- package/skills/blog-writer/SKILL.md +158 -0
- package/skills/blog-writer/__pycache__/manage_examples.cpython-312.pyc +0 -0
- package/skills/blog-writer/_meta.json +6 -0
- package/skills/blog-writer/manage_examples.py +90 -0
- package/skills/blog-writer/style-guide.md +160 -0
- package/skills/browser_skill.py +146 -0
- package/skills/coding-agent/SKILL.md +120 -0
- package/skills/coding-agent/_meta.json +6 -0
- package/skills/coding-agent/criteria.md +48 -0
- package/skills/coding-agent/execution.md +42 -0
- package/skills/coding-agent/memory-template.md +38 -0
- package/skills/coding-agent/planning.md +31 -0
- package/skills/coding-agent/state.md +60 -0
- package/skills/coding-agent/verification.md +39 -0
- package/skills/content-strategy/SKILL.md +181 -0
- package/skills/content-strategy/_meta.json +6 -0
- package/skills/contentanalysis/ExtractWisdom/SKILL.md +229 -0
- package/skills/contentanalysis/ExtractWisdom/Workflows/Extract.md +60 -0
- package/skills/contentanalysis/SKILL.md +14 -0
- package/skills/docx/CHANGELOG.md +85 -0
- package/skills/docx/LICENSE.txt +30 -0
- package/skills/docx/SKILL.md +455 -0
- package/skills/docx/docx-js.md +681 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/docx/ooxml/scripts/__pycache__/pack.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/__pycache__/unpack.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/__pycache__/validate.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/pack.py +159 -0
- package/skills/docx/ooxml/scripts/unpack.py +29 -0
- package/skills/docx/ooxml/scripts/validate.py +69 -0
- package/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/skills/docx/ooxml/scripts/validation/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/validation/__pycache__/base.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/validation/__pycache__/docx.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/validation/__pycache__/pptx.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/validation/__pycache__/redlining.cpython-312.pyc +0 -0
- package/skills/docx/ooxml/scripts/validation/base.py +951 -0
- package/skills/docx/ooxml/scripts/validation/docx.py +274 -0
- package/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/skills/docx/ooxml.md +615 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/docx/scripts/__pycache__/add_toc_placeholders.cpython-312.pyc +0 -0
- package/skills/docx/scripts/__pycache__/document.cpython-312.pyc +0 -0
- package/skills/docx/scripts/__pycache__/utilities.cpython-312.pyc +0 -0
- package/skills/docx/scripts/add_toc_placeholders.py +220 -0
- package/skills/docx/scripts/document.py +1302 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/docx/scripts/utilities.py +374 -0
- package/skills/dream-interpreter/SKILL.md +88 -0
- package/skills/dream-interpreter/assets/example_asset.txt +24 -0
- package/skills/dream-interpreter/references/api_reference.md +34 -0
- package/skills/dream-interpreter/references/interpretation-guide.md +83 -0
- package/skills/dream-interpreter/references/output-schema.md +65 -0
- package/skills/dream-interpreter/references/questioning-strategy.md +62 -0
- package/skills/dream-interpreter/references/visual-mapping.md +81 -0
- package/skills/dream-interpreter/scripts/__pycache__/example.cpython-312.pyc +0 -0
- package/skills/dream-interpreter/scripts/example.py +19 -0
- package/skills/dream-interpreter/skill.json +7 -0
- package/skills/file_skill.py +246 -0
- package/skills/finance/Finance_API_Doc.md +445 -0
- package/skills/finance/SKILL.md +53 -0
- package/skills/fullstack-dev/SKILL.md +205 -0
- package/skills/get-fortune-analysis/SKILL.md +370 -0
- package/skills/get-fortune-analysis/lunar_python.py +91 -0
- package/skills/gift-evaluator/SKILL.md +83 -0
- package/skills/gift-evaluator/__pycache__/html_tools.cpython-312.pyc +0 -0
- package/skills/gift-evaluator/html_tools.py +268 -0
- package/skills/image-edit/LICENSE.txt +21 -0
- package/skills/image-edit/SKILL.md +896 -0
- package/skills/image-edit/scripts/image-edit.ts +36 -0
- package/skills/image-generation/LICENSE.txt +21 -0
- package/skills/image-generation/SKILL.md +583 -0
- package/skills/image-generation/scripts/image-generation.ts +28 -0
- package/skills/image-understand/LICENSE.txt +21 -0
- package/skills/image-understand/SKILL.md +855 -0
- package/skills/image-understand/scripts/image-understand.ts +41 -0
- package/skills/interview-designer/README.md +70 -0
- package/skills/interview-designer/SKILL.md +53 -0
- package/skills/interview-designer/_meta.json +6 -0
- package/skills/interview-designer/references/design_rationale.md +43 -0
- package/skills/interview-designer/templates/interview_guide_template.md +62 -0
- package/skills/market-research-reports/SKILL.md +901 -0
- package/skills/market-research-reports/assets/FORMATTING_GUIDE.md +428 -0
- package/skills/market-research-reports/assets/market_report_template.tex +1380 -0
- package/skills/market-research-reports/assets/market_research.sty +564 -0
- package/skills/market-research-reports/references/data_analysis_patterns.md +548 -0
- package/skills/market-research-reports/references/report_structure_guide.md +999 -0
- package/skills/market-research-reports/references/visual_generation_guide.md +1077 -0
- package/skills/market-research-reports/scripts/__pycache__/generate_market_visuals.cpython-312.pyc +0 -0
- package/skills/market-research-reports/scripts/generate_market_visuals.py +529 -0
- package/skills/marketing-mode/README.md +49 -0
- package/skills/marketing-mode/SKILL.md +693 -0
- package/skills/marketing-mode/_meta.json +6 -0
- package/skills/marketing-mode/mode-prompt.md +39 -0
- package/skills/marketing-mode/skill.json +51 -0
- package/skills/mindfulness-meditation/SKILL.md +65 -0
- package/skills/mindfulness-meditation/_meta.json +6 -0
- package/skills/multi-search-engine/CHANGELOG.md +15 -0
- package/skills/multi-search-engine/CHANNELLOG.md +48 -0
- package/skills/multi-search-engine/SKILL.md +78 -0
- package/skills/multi-search-engine/_meta.json +6 -0
- package/skills/multi-search-engine/config.json +14 -0
- package/skills/multi-search-engine/metadata.json +7 -0
- package/skills/multi-search-engine/references/international-search.md +651 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +1534 -0
- package/skills/pdf/forms.md +205 -0
- package/skills/pdf/reference.md +765 -0
- package/skills/pdf/scripts/__pycache__/add_zai_metadata.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/check_bounding_boxes.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/check_bounding_boxes_test.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/check_fillable_fields.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/convert_pdf_to_images.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/create_validation_image.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/extract_form_field_info.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/fill_fillable_fields.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/fill_pdf_form_with_annotations.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/__pycache__/sanitize_code.cpython-312.pyc +0 -0
- package/skills/pdf/scripts/add_zai_metadata.py +172 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/skills/pdf/scripts/create_validation_image.py +41 -0
- package/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/skills/pdf/scripts/sanitize_code.py +110 -0
- package/skills/podcast-generate/LICENSE.txt +21 -0
- package/skills/podcast-generate/SKILL.md +198 -0
- package/skills/podcast-generate/generate.ts +661 -0
- package/skills/podcast-generate/package.json +30 -0
- package/skills/podcast-generate/readme.md +177 -0
- package/skills/podcast-generate/test_data/segments.jsonl +3 -0
- package/skills/podcast-generate/tsconfig.json +26 -0
- package/skills/pptx/LICENSE.txt +30 -0
- package/skills/pptx/SKILL.md +507 -0
- package/skills/pptx/html2pptx.md +625 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/ooxml/scripts/__pycache__/pack.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/__pycache__/unpack.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/__pycache__/validate.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/pack.py +159 -0
- package/skills/pptx/ooxml/scripts/unpack.py +29 -0
- package/skills/pptx/ooxml/scripts/validate.py +69 -0
- package/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/skills/pptx/ooxml/scripts/validation/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/validation/__pycache__/base.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/validation/__pycache__/docx.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/validation/__pycache__/pptx.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/validation/__pycache__/redlining.cpython-312.pyc +0 -0
- package/skills/pptx/ooxml/scripts/validation/base.py +951 -0
- package/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/skills/pptx/ooxml.md +427 -0
- package/skills/pptx/scripts/__pycache__/inventory.cpython-312.pyc +0 -0
- package/skills/pptx/scripts/__pycache__/inventory.cpython-313.pyc +0 -0
- package/skills/pptx/scripts/__pycache__/rearrange.cpython-312.pyc +0 -0
- package/skills/pptx/scripts/__pycache__/replace.cpython-312.pyc +0 -0
- package/skills/pptx/scripts/__pycache__/thumbnail.cpython-312.pyc +0 -0
- package/skills/pptx/scripts/html2pptx.js +1044 -0
- package/skills/pptx/scripts/inventory.py +1020 -0
- package/skills/pptx/scripts/rearrange.py +231 -0
- package/skills/pptx/scripts/replace.py +385 -0
- package/skills/pptx/scripts/thumbnail.py +450 -0
- package/skills/qingyan-research/SKILL.md +294 -0
- package/skills/qingyan-research/__pycache__/generate_html.cpython-312.pyc +0 -0
- package/skills/qingyan-research/generate_html.py +33 -0
- package/skills/registry.py +344 -0
- package/skills/search_skill.py +228 -0
- package/skills/seo-content-writer/SKILL.md +661 -0
- package/skills/seo-content-writer/_meta.json +6 -0
- package/skills/seo-content-writer/references/content-structure-templates.md +875 -0
- package/skills/seo-content-writer/references/title-formulas.md +339 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/__pycache__/generate_review.cpython-312.pyc +0 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/package_skill.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-312.pyc +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +236 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/skills/skill-finder-cn/SKILL.md +66 -0
- package/skills/skill-finder-cn/_meta.json +6 -0
- package/skills/skill-finder-cn/package.json +5 -0
- package/skills/skill-finder-cn/scripts/search.sh +15 -0
- package/skills/skill-vetter/SKILL.md +137 -0
- package/skills/stock-analysis-skill/SKILL.md +156 -0
- package/skills/stock-analysis-skill/package.json +21 -0
- package/skills/stock-analysis-skill/src/analyzer.ts +264 -0
- package/skills/stock-analysis-skill/src/dataFetcher.ts +130 -0
- package/skills/stock-analysis-skill/src/dividend.ts +226 -0
- package/skills/stock-analysis-skill/src/index.ts +327 -0
- package/skills/stock-analysis-skill/src/rumorScanner.ts +200 -0
- package/skills/stock-analysis-skill/src/types.ts +167 -0
- package/skills/stock-analysis-skill/src/watchlist.ts +292 -0
- package/skills/stock-analysis-skill/tsconfig.json +15 -0
- package/skills/storyboard-manager/SKILL.md +532 -0
- package/skills/storyboard-manager/index.js +9 -0
- package/skills/storyboard-manager/package.json +11 -0
- package/skills/storyboard-manager/references/character_development.md +232 -0
- package/skills/storyboard-manager/references/story_structures.md +148 -0
- package/skills/storyboard-manager/scripts/__pycache__/consistency_checker.cpython-312.pyc +0 -0
- package/skills/storyboard-manager/scripts/__pycache__/timeline_tracker.cpython-312.pyc +0 -0
- package/skills/storyboard-manager/scripts/consistency_checker.py +391 -0
- package/skills/storyboard-manager/scripts/timeline_tracker.py +352 -0
- package/skills/system_skill.py +249 -0
- package/skills/ui-ux-pro-max/SKILL.md +43 -0
- package/skills/ui-ux-pro-max/_meta.json +6 -0
- package/skills/ui-ux-pro-max/assets/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/assets/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/assets/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/assets/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/assets/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/assets/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/assets/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/assets/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/assets/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/assets/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/assets/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/assets/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/references/upstream-README.md +488 -0
- package/skills/ui-ux-pro-max/references/upstream-skill-content.md +288 -0
- package/skills/ui-ux-pro-max/scripts/__init__.py +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/search.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1071 -0
- package/skills/ui-ux-pro-max/scripts/search.py +111 -0
- package/skills/video-generation/LICENSE.txt +21 -0
- package/skills/video-generation/SKILL.md +1082 -0
- package/skills/video-generation/scripts/video.ts +168 -0
- package/skills/video-understand/LICENSE.txt +21 -0
- package/skills/video-understand/SKILL.md +916 -0
- package/skills/video-understand/scripts/video-understand.ts +41 -0
- package/skills/visual-design-foundations/SKILL.md +318 -0
- package/skills/visual-design-foundations/references/color-systems.md +417 -0
- package/skills/visual-design-foundations/references/spacing-iconography.md +425 -0
- package/skills/visual-design-foundations/references/typography-systems.md +432 -0
- package/skills/web-reader/LICENSE.txt +21 -0
- package/skills/web-reader/SKILL.md +1140 -0
- package/skills/web-reader/scripts/web-reader.ts +37 -0
- package/skills/web-search/LICENSE.txt +21 -0
- package/skills/web-search/SKILL.md +912 -0
- package/skills/web-search/scripts/web_search.ts +44 -0
- package/skills/web-shader-extractor/SKILL.md +145 -0
- package/skills/web-shader-extractor/references/config-extraction.md +50 -0
- package/skills/web-shader-extractor/references/encoded-definitions.md +53 -0
- package/skills/web-shader-extractor/references/extraction-workflow.md +61 -0
- package/skills/web-shader-extractor/references/porting-strategy.md +164 -0
- package/skills/web-shader-extractor/references/shader-injection.md +126 -0
- package/skills/web-shader-extractor/references/shaders-com.md +190 -0
- package/skills/web-shader-extractor/references/tech-signatures.md +54 -0
- package/skills/web-shader-extractor/references/tsl-extraction.md +41 -0
- package/skills/web-shader-extractor/references/unicorn-studio.md +353 -0
- package/skills/web-shader-extractor/scripts/fetch-rendered-dom.mjs +153 -0
- package/skills/web-shader-extractor/scripts/scan-bundle.sh +76 -0
- package/skills/writing-plans/SKILL.md +116 -0
- package/skills/writing-plans/_meta.json +6 -0
- package/skills/xlsx/LICENSE.txt +30 -0
- package/skills/xlsx/SKILL.md +496 -0
- package/skills/xlsx/__pycache__/recalc.cpython-312.pyc +0 -0
- package/skills/xlsx/recalc.py +178 -0
- package/start.sh +36 -0
- package/web/__init__.py +1 -0
- package/web/__pycache__/api_server.cpython-312.pyc +0 -0
- package/web/api_server.py +2043 -0
- package/web/ui/chat.html +3235 -0
- package/web/ui/index.html +458 -0
package/web/ui/chat.html
ADDED
|
@@ -0,0 +1,3235 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>MyAgent - AI 助手</title>
|
|
7
|
+
<style>
|
|
8
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
9
|
+
:root{
|
|
10
|
+
--bg:#f7f8fa;--bg2:#ffffff;--bg3:#f0f1f3;--bg4:#e5e7eb;--bg5:#d1d5db;
|
|
11
|
+
--text:#1a1a2e;--text2:#6b7280;--text3:#9ca3af;
|
|
12
|
+
--accent:#4f46e5;--accent2:#6366f1;--accent-light:#eef2ff;--accent-dark:#3730a3;
|
|
13
|
+
--user-bubble:#4f46e5;--user-text:#ffffff;
|
|
14
|
+
--bot-bubble:#ffffff;--bot-text:#1a1a2e;
|
|
15
|
+
--ok:#10b981;--warn:#f59e0b;--danger:#ef4444;--info:#3b82f6;
|
|
16
|
+
--border:#e5e7eb;--border-light:#f0f1f3;
|
|
17
|
+
--radius:12px;--radius-sm:8px;--radius-xs:6px;
|
|
18
|
+
--shadow-sm:0 1px 2px rgba(0,0,0,.05);
|
|
19
|
+
--shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.04);
|
|
20
|
+
--shadow-lg:0 10px 25px rgba(0,0,0,.08);
|
|
21
|
+
--sidebar-w:280px;--header-h:60px;--agent-panel-w:300px;
|
|
22
|
+
--transition:all .2s ease;
|
|
23
|
+
}
|
|
24
|
+
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,'Noto Sans SC',sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.6;overflow:hidden}
|
|
25
|
+
a{color:var(--accent);text-decoration:none}
|
|
26
|
+
button{font:inherit;cursor:pointer;border:none;background:none;color:inherit}
|
|
27
|
+
input,textarea,select{font:inherit}
|
|
28
|
+
|
|
29
|
+
/* ── Layout ── */
|
|
30
|
+
.app{display:flex;height:100vh;width:100vw;overflow:hidden}
|
|
31
|
+
|
|
32
|
+
/* ── Sidebar ── */
|
|
33
|
+
.sidebar{
|
|
34
|
+
width:var(--sidebar-w);min-width:var(--sidebar-w);height:100vh;
|
|
35
|
+
background:var(--bg2);border-right:1px solid var(--border);
|
|
36
|
+
display:flex;flex-direction:column;z-index:20;
|
|
37
|
+
transition:transform .3s ease;
|
|
38
|
+
}
|
|
39
|
+
.sidebar.hidden{transform:translateX(-100%);position:absolute;left:0;top:0}
|
|
40
|
+
|
|
41
|
+
.sidebar-header{
|
|
42
|
+
padding:16px 20px;border-bottom:1px solid var(--border-light);
|
|
43
|
+
display:flex;align-items:center;gap:12px;min-height:var(--header-h);
|
|
44
|
+
}
|
|
45
|
+
.sidebar-logo{
|
|
46
|
+
width:36px;height:36px;border-radius:10px;
|
|
47
|
+
background:linear-gradient(135deg,var(--accent),#7c3aed);
|
|
48
|
+
display:grid;place-items:center;flex-shrink:0;
|
|
49
|
+
}
|
|
50
|
+
.sidebar-logo svg{width:20px;height:20px;color:#fff}
|
|
51
|
+
.sidebar-title{font-size:16px;font-weight:700;color:var(--text)}
|
|
52
|
+
.sidebar-subtitle{font-size:11px;color:var(--text3)}
|
|
53
|
+
|
|
54
|
+
.new-chat-btn{
|
|
55
|
+
margin:12px 16px 8px;padding:10px 16px;border-radius:var(--radius-sm);
|
|
56
|
+
background:var(--accent);color:#fff;font-size:13px;font-weight:600;
|
|
57
|
+
display:flex;align-items:center;gap:8px;transition:var(--transition);
|
|
58
|
+
}
|
|
59
|
+
.new-chat-btn:hover{background:var(--accent2);transform:translateY(-1px)}
|
|
60
|
+
.new-chat-btn svg{width:16px;height:16px}
|
|
61
|
+
|
|
62
|
+
.sidebar-search{
|
|
63
|
+
margin:4px 16px 8px;position:relative;
|
|
64
|
+
}
|
|
65
|
+
.sidebar-search input{
|
|
66
|
+
width:100%;padding:8px 12px 8px 34px;border-radius:var(--radius-xs);
|
|
67
|
+
border:1px solid var(--border);background:var(--bg);font-size:13px;
|
|
68
|
+
outline:none;transition:border-color .2s;
|
|
69
|
+
}
|
|
70
|
+
.sidebar-search input:focus{border-color:var(--accent)}
|
|
71
|
+
.sidebar-search svg{
|
|
72
|
+
position:absolute;left:10px;top:50%;transform:translateY(-50%);
|
|
73
|
+
width:16px;height:16px;color:var(--text3);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.session-list{padding:4px 8px}
|
|
77
|
+
.session-item{
|
|
78
|
+
display:flex;align-items:center;gap:10px;padding:10px 12px;
|
|
79
|
+
border-radius:var(--radius-sm);cursor:pointer;transition:var(--transition);
|
|
80
|
+
position:relative;
|
|
81
|
+
}
|
|
82
|
+
.session-item:hover{background:var(--bg3)}
|
|
83
|
+
.session-item.active{background:var(--accent-light);color:var(--accent2)}
|
|
84
|
+
.session-item.active::before{
|
|
85
|
+
content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);
|
|
86
|
+
width:3px;height:24px;background:var(--accent);border-radius:0 2px 2px 0;
|
|
87
|
+
}
|
|
88
|
+
.session-icon{width:32px;height:32px;border-radius:8px;background:var(--bg3);
|
|
89
|
+
display:grid;place-items:center;flex-shrink:0;font-size:14px}
|
|
90
|
+
.session-item.active .session-icon{background:var(--accent-light);color:var(--accent)}
|
|
91
|
+
.session-info{flex:1;min-width:0}
|
|
92
|
+
.session-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
93
|
+
.session-preview{font-size:11px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
|
|
94
|
+
.session-meta{font-size:10px;color:var(--text3);flex-shrink:0;text-align:right}
|
|
95
|
+
.session-delete{
|
|
96
|
+
opacity:0;position:absolute;right:8px;top:50%;transform:translateY(-50%);
|
|
97
|
+
width:24px;height:24px;border-radius:4px;display:grid;place-items:center;
|
|
98
|
+
background:var(--bg2);transition:var(--transition);font-size:12px;color:var(--text3);
|
|
99
|
+
}
|
|
100
|
+
.session-item:hover .session-delete{opacity:1}
|
|
101
|
+
.session-delete:hover{background:var(--danger);color:#fff}
|
|
102
|
+
|
|
103
|
+
.sidebar-footer{
|
|
104
|
+
padding:12px 16px;border-top:1px solid var(--border-light);
|
|
105
|
+
display:flex;flex-direction:column;gap:6px;
|
|
106
|
+
}
|
|
107
|
+
.sidebar-footer-btn{
|
|
108
|
+
display:flex;align-items:center;gap:10px;padding:8px 12px;
|
|
109
|
+
border-radius:var(--radius-xs);font-size:13px;color:var(--text2);
|
|
110
|
+
transition:var(--transition);
|
|
111
|
+
}
|
|
112
|
+
.sidebar-footer-btn:hover{background:var(--bg3);color:var(--text)}
|
|
113
|
+
.sidebar-footer-btn svg{width:16px;height:16px}
|
|
114
|
+
.model-badge{
|
|
115
|
+
margin-left:auto;font-size:10px;padding:2px 8px;border-radius:10px;
|
|
116
|
+
background:var(--accent-light);color:var(--accent);font-weight:600;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ── Main ── */
|
|
120
|
+
.main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0;background:var(--bg)}
|
|
121
|
+
.main-header{
|
|
122
|
+
height:var(--header-h);min-height:var(--header-h);
|
|
123
|
+
display:flex;align-items:center;gap:12px;padding:0 20px;
|
|
124
|
+
background:var(--bg2);border-bottom:1px solid var(--border-light);
|
|
125
|
+
}
|
|
126
|
+
.toggle-sidebar{width:36px;height:36px;border-radius:var(--radius-xs);
|
|
127
|
+
display:grid;place-items:center;color:var(--text2);transition:var(--transition)}
|
|
128
|
+
.toggle-sidebar:hover{background:var(--bg3);color:var(--text)}
|
|
129
|
+
.toggle-sidebar svg{width:20px;height:20px}
|
|
130
|
+
.main-title{flex:1;font-size:15px;font-weight:600;display:flex;align-items:center;gap:8px}
|
|
131
|
+
.main-title .dot{width:8px;height:8px;border-radius:50%;background:var(--ok)}
|
|
132
|
+
.header-actions{display:flex;gap:6px}
|
|
133
|
+
.header-btn{width:36px;height:36px;border-radius:var(--radius-xs);
|
|
134
|
+
display:grid;place-items:center;color:var(--text2);transition:var(--transition)}
|
|
135
|
+
.header-btn:hover{background:var(--bg3);color:var(--text)}
|
|
136
|
+
.header-btn svg{width:18px;height:18px}
|
|
137
|
+
|
|
138
|
+
/* ── Messages ── */
|
|
139
|
+
.messages-container{flex:1;overflow-y:auto;padding:20px}
|
|
140
|
+
.messages-inner{max-width:800px;margin:0 auto;display:flex;flex-direction:column;gap:16px}
|
|
141
|
+
|
|
142
|
+
.message-row{display:flex;gap:10px;animation:msgIn .25s ease-out}
|
|
143
|
+
@keyframes msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
144
|
+
.message-row.user{flex-direction:row-reverse}
|
|
145
|
+
|
|
146
|
+
.message-avatar{
|
|
147
|
+
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
148
|
+
display:grid;place-items:center;font-size:14px;
|
|
149
|
+
}
|
|
150
|
+
.message-row.user .message-avatar{background:var(--accent);color:#fff}
|
|
151
|
+
.message-row.assistant .message-avatar{background:linear-gradient(135deg,#7c3aed,#4f46e5);color:#fff}
|
|
152
|
+
|
|
153
|
+
.message-bubble{
|
|
154
|
+
max-width:70%;padding:12px 16px;border-radius:var(--radius);
|
|
155
|
+
font-size:14px;line-height:1.7;word-break:break-word;
|
|
156
|
+
box-shadow:var(--shadow-sm);
|
|
157
|
+
}
|
|
158
|
+
.message-row.user .message-bubble{
|
|
159
|
+
background:var(--user-bubble);color:var(--user-text);
|
|
160
|
+
border-bottom-right-radius:4px;
|
|
161
|
+
}
|
|
162
|
+
.message-row.assistant .message-bubble{
|
|
163
|
+
background:var(--bot-bubble);color:var(--bot-text);
|
|
164
|
+
border:1px solid var(--border-light);
|
|
165
|
+
border-bottom-left-radius:4px;
|
|
166
|
+
}
|
|
167
|
+
.message-bubble p{margin-bottom:8px}
|
|
168
|
+
.message-bubble p:last-child{margin-bottom:0}
|
|
169
|
+
.message-bubble code{
|
|
170
|
+
background:rgba(0,0,0,.06);padding:2px 6px;border-radius:4px;
|
|
171
|
+
font-family:'SF Mono','Fira Code','Cascadia Code',monospace;font-size:12.5px;
|
|
172
|
+
}
|
|
173
|
+
.message-row.user .message-bubble code{background:rgba(255,255,255,.2)}
|
|
174
|
+
.message-bubble pre{
|
|
175
|
+
background:#1e1e2e;color:#cdd6f4;padding:14px 16px;border-radius:var(--radius-sm);
|
|
176
|
+
overflow-x:auto;margin:8px 0;font-size:12.5px;line-height:1.5;
|
|
177
|
+
font-family:'SF Mono','Fira Code','Cascadia Code',monospace;
|
|
178
|
+
}
|
|
179
|
+
.message-bubble pre code{background:none;padding:0;font-size:inherit}
|
|
180
|
+
.message-bubble strong{font-weight:600}
|
|
181
|
+
.message-bubble em{font-style:italic}
|
|
182
|
+
.message-bubble ul,.message-bubble ol{padding-left:20px;margin:6px 0}
|
|
183
|
+
.message-bubble li{margin:3px 0}
|
|
184
|
+
.message-bubble blockquote{
|
|
185
|
+
border-left:3px solid var(--accent);padding:4px 12px;margin:8px 0;
|
|
186
|
+
color:var(--text2);font-style:italic;
|
|
187
|
+
}
|
|
188
|
+
.message-time{font-size:10px;color:var(--text3);margin-top:4px}
|
|
189
|
+
|
|
190
|
+
/* Execution Progress Timer */
|
|
191
|
+
.exec-timer{
|
|
192
|
+
display:flex;align-items:center;gap:10px;
|
|
193
|
+
padding:8px 14px;margin:4px 0 8px 0;
|
|
194
|
+
background:var(--bg2);border:1px solid var(--border-light);
|
|
195
|
+
border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);
|
|
196
|
+
animation:msgIn .2s ease-out;
|
|
197
|
+
max-width:400px;
|
|
198
|
+
}
|
|
199
|
+
.exec-timer-icon{
|
|
200
|
+
width:28px;height:28px;border-radius:6px;
|
|
201
|
+
background:linear-gradient(135deg,var(--accent),#7c3aed);
|
|
202
|
+
display:grid;place-items:center;flex-shrink:0;
|
|
203
|
+
font-size:14px;
|
|
204
|
+
}
|
|
205
|
+
.exec-timer-body{flex:1;min-width:0}
|
|
206
|
+
.exec-timer-title{
|
|
207
|
+
font-size:11px;font-weight:600;color:var(--text2);
|
|
208
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
209
|
+
margin-bottom:3px;display:flex;align-items:center;gap:6px;
|
|
210
|
+
}
|
|
211
|
+
.exec-timer-title .pulse-dot{
|
|
212
|
+
width:6px;height:6px;border-radius:50%;background:var(--ok);
|
|
213
|
+
animation:pulseDot 1.2s infinite;
|
|
214
|
+
}
|
|
215
|
+
.exec-timer-title .pulse-dot.timeout{background:var(--danger)}
|
|
216
|
+
@keyframes pulseDot{0%,100%{opacity:1}50%{opacity:.3}}
|
|
217
|
+
.exec-timer-times{
|
|
218
|
+
display:flex;gap:12px;font-size:11px;color:var(--text3);
|
|
219
|
+
}
|
|
220
|
+
.exec-timer-time{
|
|
221
|
+
display:flex;align-items:baseline;gap:3px;
|
|
222
|
+
}
|
|
223
|
+
.exec-timer-time .time-val{
|
|
224
|
+
font-size:18px;font-weight:700;font-variant-numeric:tabular-nums;
|
|
225
|
+
color:var(--text);
|
|
226
|
+
min-width:48px;
|
|
227
|
+
}
|
|
228
|
+
.exec-timer-time .time-unit{font-size:10px;color:var(--text3)}
|
|
229
|
+
.exec-timer-time.timeout .time-val{color:var(--danger);animation:pulseDot 1s infinite}
|
|
230
|
+
.exec-timer-bar{
|
|
231
|
+
height:3px;background:var(--bg4);border-radius:2px;
|
|
232
|
+
margin-top:4px;overflow:hidden;
|
|
233
|
+
}
|
|
234
|
+
.exec-timer-bar-fill{
|
|
235
|
+
height:100%;border-radius:2px;
|
|
236
|
+
background:linear-gradient(90deg,var(--ok),var(--info));
|
|
237
|
+
transition:width .3s ease;
|
|
238
|
+
}
|
|
239
|
+
.exec-timer-bar-fill.warning{background:linear-gradient(90deg,var(--warn),var(--danger))}
|
|
240
|
+
.exec-timer-bar-fill.danger{background:var(--danger);animation:barPulse 1s infinite}
|
|
241
|
+
@keyframes barPulse{0%,100%{opacity:1}50%{opacity:.6}}
|
|
242
|
+
.exec-timer-code{
|
|
243
|
+
font-family:'SF Mono','Fira Code','Cascadia Code',monospace;
|
|
244
|
+
font-size:10px;color:var(--text3);margin-top:3px;
|
|
245
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
246
|
+
background:var(--bg);padding:2px 6px;border-radius:3px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Typing indicator */
|
|
250
|
+
.typing-indicator{
|
|
251
|
+
display:flex;gap:10px;animation:msgIn .25s ease-out;
|
|
252
|
+
}
|
|
253
|
+
.typing-bubble{
|
|
254
|
+
background:var(--bot-bubble);border:1px solid var(--border-light);
|
|
255
|
+
border-radius:var(--radius);border-bottom-left-radius:4px;
|
|
256
|
+
padding:14px 20px;display:flex;align-items:center;gap:5px;
|
|
257
|
+
box-shadow:var(--shadow-sm);
|
|
258
|
+
}
|
|
259
|
+
.typing-dot{
|
|
260
|
+
width:8px;height:8px;border-radius:50%;background:var(--text3);
|
|
261
|
+
animation:typingBounce 1.4s infinite;
|
|
262
|
+
}
|
|
263
|
+
.typing-dot:nth-child(2){animation-delay:.2s}
|
|
264
|
+
.typing-dot:nth-child(3){animation-delay:.4s}
|
|
265
|
+
@keyframes typingBounce{
|
|
266
|
+
0%,60%,100%{transform:translateY(0);opacity:.4}
|
|
267
|
+
30%{transform:translateY(-6px);opacity:1}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Empty state */
|
|
271
|
+
.empty-state{
|
|
272
|
+
flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
273
|
+
padding:40px 20px;color:var(--text3);text-align:center;
|
|
274
|
+
}
|
|
275
|
+
.empty-icon{font-size:56px;margin-bottom:16px;opacity:.5}
|
|
276
|
+
.empty-title{font-size:18px;font-weight:600;color:var(--text2);margin-bottom:8px}
|
|
277
|
+
.empty-desc{font-size:13px;max-width:400px;line-height:1.6;margin-bottom:24px}
|
|
278
|
+
.quick-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
|
|
279
|
+
.quick-action{
|
|
280
|
+
padding:8px 16px;border-radius:20px;background:var(--bg2);
|
|
281
|
+
border:1px solid var(--border);font-size:13px;color:var(--text2);
|
|
282
|
+
transition:var(--transition);
|
|
283
|
+
}
|
|
284
|
+
.quick-action:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-light)}
|
|
285
|
+
|
|
286
|
+
/* ── Input Area ── */
|
|
287
|
+
.input-area{
|
|
288
|
+
padding:16px 20px;background:var(--bg2);border-top:1px solid var(--border-light);
|
|
289
|
+
}
|
|
290
|
+
.input-wrapper{max-width:800px;margin:0 auto}
|
|
291
|
+
.input-box{
|
|
292
|
+
display:flex;align-items:flex-end;gap:10px;
|
|
293
|
+
background:var(--bg);border:1px solid var(--border);
|
|
294
|
+
border-radius:var(--radius);padding:8px 12px;
|
|
295
|
+
transition:border-color .2s,box-shadow .2s;
|
|
296
|
+
}
|
|
297
|
+
.input-box:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-light)}
|
|
298
|
+
.input-box textarea{
|
|
299
|
+
flex:1;border:none;background:none;outline:none;resize:none;
|
|
300
|
+
font-size:14px;line-height:1.5;max-height:150px;min-height:24px;
|
|
301
|
+
padding:4px 0;color:var(--text);
|
|
302
|
+
}
|
|
303
|
+
.input-box textarea::placeholder{color:var(--text3)}
|
|
304
|
+
|
|
305
|
+
.send-btn{
|
|
306
|
+
width:36px;height:36px;border-radius:var(--radius-sm);
|
|
307
|
+
background:var(--accent);color:#fff;
|
|
308
|
+
display:grid;place-items:center;flex-shrink:0;
|
|
309
|
+
transition:var(--transition);
|
|
310
|
+
}
|
|
311
|
+
.send-btn:hover{background:var(--accent2)}
|
|
312
|
+
.send-btn:disabled{opacity:.35;cursor:default}
|
|
313
|
+
.send-btn svg{width:18px;height:18px}
|
|
314
|
+
|
|
315
|
+
.stop-btn{
|
|
316
|
+
width:36px;height:36px;border-radius:var(--radius-sm);
|
|
317
|
+
background:var(--danger);color:#fff;
|
|
318
|
+
display:grid;place-items:center;flex-shrink:0;
|
|
319
|
+
transition:var(--transition);animation:pulse 1.5s infinite;
|
|
320
|
+
}
|
|
321
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.6}}
|
|
322
|
+
.stop-btn svg{width:14px;height:14px}
|
|
323
|
+
|
|
324
|
+
.input-hint{font-size:11px;color:var(--text3);text-align:center;margin-top:6px}
|
|
325
|
+
|
|
326
|
+
/* ── Toast ── */
|
|
327
|
+
.toast-container{position:fixed;top:20px;right:20px;z-index:200;display:flex;flex-direction:column;gap:8px}
|
|
328
|
+
.toast{
|
|
329
|
+
padding:12px 20px;border-radius:var(--radius-sm);font-size:13px;
|
|
330
|
+
animation:toastIn .3s ease;box-shadow:var(--shadow-lg);max-width:360px;
|
|
331
|
+
display:flex;align-items:center;gap:8px;
|
|
332
|
+
}
|
|
333
|
+
@keyframes toastIn{from{transform:translateX(20px);opacity:0}to{transform:translateX(0);opacity:1}}
|
|
334
|
+
.toast-error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}
|
|
335
|
+
.toast-success{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}
|
|
336
|
+
.toast-info{background:#eff6ff;color:#1e3a5f;border:1px solid #bfdbfe}
|
|
337
|
+
|
|
338
|
+
/* ── Confirm Dialog ── */
|
|
339
|
+
.modal-overlay{
|
|
340
|
+
position:fixed;inset:0;background:rgba(0,0,0,.3);z-index:100;
|
|
341
|
+
display:flex;align-items:center;justify-content:center;
|
|
342
|
+
animation:fadeIn .15s ease;
|
|
343
|
+
}
|
|
344
|
+
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
|
345
|
+
.modal{
|
|
346
|
+
background:var(--bg2);border-radius:var(--radius);padding:24px;
|
|
347
|
+
width:90%;max-width:400px;box-shadow:var(--shadow-lg);
|
|
348
|
+
}
|
|
349
|
+
.modal h3{font-size:16px;font-weight:600;margin-bottom:8px}
|
|
350
|
+
.modal p{font-size:13px;color:var(--text2);margin-bottom:20px}
|
|
351
|
+
.modal-actions{display:flex;gap:8px;justify-content:flex-end}
|
|
352
|
+
.modal-btn{
|
|
353
|
+
padding:8px 20px;border-radius:var(--radius-sm);font-size:13px;font-weight:500;
|
|
354
|
+
transition:var(--transition);
|
|
355
|
+
}
|
|
356
|
+
.modal-btn-cancel{background:var(--bg3);color:var(--text2)}
|
|
357
|
+
.modal-btn-cancel:hover{background:var(--bg4)}
|
|
358
|
+
.modal-btn-danger{background:var(--danger);color:#fff}
|
|
359
|
+
.modal-btn-danger:hover{background:#dc2626}
|
|
360
|
+
|
|
361
|
+
/* Scrollbar */
|
|
362
|
+
::-webkit-scrollbar{width:6px}
|
|
363
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
364
|
+
::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:3px}
|
|
365
|
+
::-webkit-scrollbar-thumb:hover{background:var(--bg5)}
|
|
366
|
+
|
|
367
|
+
/* ── Welcome Message ── */
|
|
368
|
+
.welcome-card{
|
|
369
|
+
background:var(--bg2);border:1px solid var(--border-light);
|
|
370
|
+
border-radius:var(--radius);padding:24px;margin:8px 0;
|
|
371
|
+
}
|
|
372
|
+
.welcome-card h2{font-size:18px;font-weight:700;margin-bottom:4px;display:flex;align-items:center;gap:8px}
|
|
373
|
+
.welcome-card h2 .emoji{font-size:24px}
|
|
374
|
+
.welcome-card .subtitle{font-size:13px;color:var(--text2);margin-bottom:16px}
|
|
375
|
+
.capabilities{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
|
|
376
|
+
.capability{
|
|
377
|
+
display:flex;align-items:center;gap:8px;padding:8px 12px;
|
|
378
|
+
border-radius:var(--radius-xs);background:var(--bg);font-size:12px;color:var(--text2);
|
|
379
|
+
}
|
|
380
|
+
.capability .cap-icon{font-size:16px}
|
|
381
|
+
|
|
382
|
+
/* ══════════════════════════════════════════════════════
|
|
383
|
+
── Agent Panel (RIGHT) ──
|
|
384
|
+
══════════════════════════════════════════════════════ */
|
|
385
|
+
.agent-panel{
|
|
386
|
+
width:var(--agent-panel-w);min-width:0;height:100vh;
|
|
387
|
+
background:var(--bg2);border-left:1px solid var(--border);
|
|
388
|
+
display:flex;flex-direction:column;z-index:15;
|
|
389
|
+
transition:width .35s cubic-bezier(.4,0,.2,1),min-width .35s cubic-bezier(.4,0,.2,1),opacity .25s ease;
|
|
390
|
+
overflow:hidden;
|
|
391
|
+
}
|
|
392
|
+
.agent-panel.collapsed{
|
|
393
|
+
width:0;min-width:0;border-left:none;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.agent-panel-header{
|
|
397
|
+
padding:16px 16px 12px;border-bottom:1px solid var(--border-light);
|
|
398
|
+
display:flex;align-items:center;gap:10px;min-height:var(--header-h);
|
|
399
|
+
flex-shrink:0;
|
|
400
|
+
}
|
|
401
|
+
.agent-panel-header h3{
|
|
402
|
+
flex:1;font-size:15px;font-weight:700;display:flex;align-items:center;gap:8px;
|
|
403
|
+
white-space:nowrap;
|
|
404
|
+
}
|
|
405
|
+
.agent-panel-header h3 .agents-emoji{font-size:18px}
|
|
406
|
+
|
|
407
|
+
.agent-create-btn{
|
|
408
|
+
width:30px;height:30px;border-radius:var(--radius-xs);
|
|
409
|
+
background:var(--accent);color:#fff;
|
|
410
|
+
display:grid;place-items:center;flex-shrink:0;
|
|
411
|
+
transition:var(--transition);font-size:16px;font-weight:600;
|
|
412
|
+
}
|
|
413
|
+
.agent-create-btn:hover{background:var(--accent2);transform:scale(1.05)}
|
|
414
|
+
.agent-create-btn svg{width:16px;height:16px}
|
|
415
|
+
|
|
416
|
+
/* Agent list */
|
|
417
|
+
.agent-list{flex:1;overflow-y:auto;padding:8px 12px}
|
|
418
|
+
|
|
419
|
+
.agent-card{
|
|
420
|
+
display:flex;align-items:flex-start;gap:10px;padding:10px 12px;
|
|
421
|
+
border-radius:var(--radius-sm);cursor:pointer;transition:var(--transition);
|
|
422
|
+
position:relative;margin-bottom:4px;
|
|
423
|
+
border:1px solid transparent;
|
|
424
|
+
}
|
|
425
|
+
.agent-card:hover{background:var(--bg3);border-color:var(--border-light)}
|
|
426
|
+
.agent-card.active{
|
|
427
|
+
background:var(--accent-light);
|
|
428
|
+
border-color:var(--accent);
|
|
429
|
+
}
|
|
430
|
+
.agent-card.active::after{
|
|
431
|
+
content:'';position:absolute;right:0;top:50%;transform:translateY(-50%);
|
|
432
|
+
width:3px;height:24px;background:var(--accent);border-radius:2px 0 0 2px;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* Agent tree indent */
|
|
436
|
+
.agent-tree-children{margin-left:16px;border-left:1px dashed var(--border-light);padding-left:4px}
|
|
437
|
+
.agent-tree-toggle{
|
|
438
|
+
width:18px;height:18px;border-radius:3px;
|
|
439
|
+
display:inline-grid;place-items:center;font-size:10px;
|
|
440
|
+
color:var(--text3);flex-shrink:0;transition:var(--transition)
|
|
441
|
+
}
|
|
442
|
+
.agent-tree-toggle:hover{background:var(--bg4);color:var(--text)}
|
|
443
|
+
.agent-tree-toggle.expanded{transform:rotate(90deg)}
|
|
444
|
+
|
|
445
|
+
/* Execution mode badge */
|
|
446
|
+
.exec-badge{
|
|
447
|
+
font-size:9px;font-weight:600;padding:1px 5px;border-radius:4px;
|
|
448
|
+
white-space:nowrap;flex-shrink:0;
|
|
449
|
+
}
|
|
450
|
+
.exec-badge.local{background:#dcfce7;color:#166534}
|
|
451
|
+
.exec-badge.sandbox{background:#fef3c7;color:#92400e}
|
|
452
|
+
|
|
453
|
+
/* Agent add-child button (shows on hover) */
|
|
454
|
+
.agent-add-child-btn{
|
|
455
|
+
opacity:0;position:absolute;bottom:2px;right:6px;
|
|
456
|
+
width:20px;height:20px;border-radius:4px;
|
|
457
|
+
display:grid;place-items:center;font-size:12px;
|
|
458
|
+
color:var(--text3);transition:var(--transition)
|
|
459
|
+
}
|
|
460
|
+
.agent-card:hover .agent-add-child-btn{opacity:1}
|
|
461
|
+
.agent-add-child-btn:hover{background:var(--accent-light);color:var(--accent)}
|
|
462
|
+
|
|
463
|
+
.agent-avatar{
|
|
464
|
+
width:38px;height:38px;border-radius:10px;flex-shrink:0;
|
|
465
|
+
display:grid;place-items:center;font-size:16px;font-weight:700;color:#fff;
|
|
466
|
+
position:relative;
|
|
467
|
+
}
|
|
468
|
+
.agent-avatar .status-dot{
|
|
469
|
+
position:absolute;bottom:-1px;right:-1px;width:11px;height:11px;
|
|
470
|
+
border-radius:50%;background:var(--ok);border:2px solid var(--bg2);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.agent-info{flex:1;min-width:0;padding-top:2px}
|
|
474
|
+
.agent-name{
|
|
475
|
+
font-size:13px;font-weight:600;color:var(--text);
|
|
476
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
477
|
+
display:flex;align-items:center;gap:6px;
|
|
478
|
+
}
|
|
479
|
+
.agent-name .model-tag{
|
|
480
|
+
font-size:9px;font-weight:500;padding:1px 6px;border-radius:8px;
|
|
481
|
+
background:var(--bg3);color:var(--text3);white-space:nowrap;
|
|
482
|
+
max-width:90px;overflow:hidden;text-overflow:ellipsis;
|
|
483
|
+
}
|
|
484
|
+
.agent-card.active .agent-name{color:var(--accent-dark)}
|
|
485
|
+
.agent-desc{
|
|
486
|
+
font-size:11px;color:var(--text3);margin-top:2px;
|
|
487
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
488
|
+
line-height:1.4;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.agent-settings-btn{
|
|
492
|
+
position:absolute;top:10px;right:8px;
|
|
493
|
+
width:26px;height:26px;border-radius:6px;
|
|
494
|
+
display:grid;place-items:center;
|
|
495
|
+
color:var(--text3);opacity:0;
|
|
496
|
+
transition:var(--transition);
|
|
497
|
+
}
|
|
498
|
+
.agent-card:hover .agent-settings-btn{opacity:1}
|
|
499
|
+
.agent-settings-btn:hover{background:var(--bg4);color:var(--text2)}
|
|
500
|
+
.agent-settings-btn svg{width:14px;height:14px}
|
|
501
|
+
|
|
502
|
+
/* Agent panel footer / collapse toggle */
|
|
503
|
+
.agent-panel-footer{
|
|
504
|
+
padding:10px 16px;border-top:1px solid var(--border-light);
|
|
505
|
+
flex-shrink:0;
|
|
506
|
+
}
|
|
507
|
+
.agent-collapse-btn{
|
|
508
|
+
width:100%;padding:8px;border-radius:var(--radius-xs);
|
|
509
|
+
display:flex;align-items:center;justify-content:center;gap:6px;
|
|
510
|
+
font-size:12px;color:var(--text3);
|
|
511
|
+
transition:var(--transition);
|
|
512
|
+
}
|
|
513
|
+
.agent-collapse-btn:hover{background:var(--bg3);color:var(--text2)}
|
|
514
|
+
.agent-collapse-btn svg{width:14px;height:14px;transition:transform .35s ease}
|
|
515
|
+
.agent-panel.collapsed ~ .main .agent-expand-float svg{
|
|
516
|
+
transform:rotate(180deg);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* Floating expand button (visible when panel collapsed) */
|
|
520
|
+
.agent-expand-float{
|
|
521
|
+
position:fixed;right:12px;top:50%;transform:translateY(-50%);
|
|
522
|
+
width:32px;height:48px;border-radius:8px;
|
|
523
|
+
background:var(--bg2);border:1px solid var(--border);
|
|
524
|
+
box-shadow:var(--shadow);z-index:16;
|
|
525
|
+
display:grid;place-items:center;
|
|
526
|
+
color:var(--text2);transition:var(--transition);
|
|
527
|
+
cursor:pointer;
|
|
528
|
+
}
|
|
529
|
+
.agent-expand-float:hover{background:var(--bg3);color:var(--text);box-shadow:var(--shadow-lg)}
|
|
530
|
+
.agent-expand-float svg{width:16px;height:16px}
|
|
531
|
+
.agent-expand-float.hidden{display:none}
|
|
532
|
+
|
|
533
|
+
/* ── Create / Edit Agent Modal ── */
|
|
534
|
+
.agent-modal{
|
|
535
|
+
background:var(--bg2);border-radius:var(--radius);padding:28px;
|
|
536
|
+
width:90%;max-width:520px;box-shadow:var(--shadow-lg);
|
|
537
|
+
max-height:85vh;overflow-y:auto;
|
|
538
|
+
}
|
|
539
|
+
.agent-modal h3{font-size:18px;font-weight:700;margin-bottom:4px;display:flex;align-items:center;gap:8px}
|
|
540
|
+
.agent-modal .modal-subtitle{font-size:12px;color:var(--text3);margin-bottom:20px}
|
|
541
|
+
|
|
542
|
+
.agent-form-group{margin-bottom:16px}
|
|
543
|
+
.agent-form-group label{
|
|
544
|
+
display:block;font-size:12px;font-weight:600;color:var(--text2);
|
|
545
|
+
margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px;
|
|
546
|
+
}
|
|
547
|
+
.agent-form-group input,
|
|
548
|
+
.agent-form-group textarea,
|
|
549
|
+
.agent-form-group select{
|
|
550
|
+
width:100%;padding:10px 14px;border-radius:var(--radius-xs);
|
|
551
|
+
border:1px solid var(--border);background:var(--bg);font-size:13px;
|
|
552
|
+
outline:none;transition:border-color .2s;color:var(--text);
|
|
553
|
+
}
|
|
554
|
+
.agent-form-group input:focus,
|
|
555
|
+
.agent-form-group textarea:focus,
|
|
556
|
+
.agent-form-group select:focus{border-color:var(--accent)}
|
|
557
|
+
.agent-form-group textarea{resize:vertical;min-height:80px;line-height:1.5}
|
|
558
|
+
.agent-form-group .hint{font-size:11px;color:var(--text3);margin-top:4px}
|
|
559
|
+
|
|
560
|
+
.agent-avatar-picker{
|
|
561
|
+
display:flex;gap:8px;margin-top:6px;flex-wrap:wrap;
|
|
562
|
+
}
|
|
563
|
+
.agent-avatar-option{
|
|
564
|
+
width:36px;height:36px;border-radius:8px;
|
|
565
|
+
display:grid;place-items:center;font-size:16px;cursor:pointer;
|
|
566
|
+
border:2px solid transparent;transition:var(--transition);
|
|
567
|
+
}
|
|
568
|
+
.agent-avatar-option:hover{transform:scale(1.1)}
|
|
569
|
+
.agent-avatar-option.selected{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-light)}
|
|
570
|
+
|
|
571
|
+
.agent-modal-actions{
|
|
572
|
+
display:flex;gap:8px;justify-content:flex-end;margin-top:24px;
|
|
573
|
+
padding-top:16px;border-top:1px solid var(--border-light);
|
|
574
|
+
}
|
|
575
|
+
.agent-modal-btn{
|
|
576
|
+
padding:10px 24px;border-radius:var(--radius-sm);font-size:13px;font-weight:600;
|
|
577
|
+
transition:var(--transition);
|
|
578
|
+
}
|
|
579
|
+
.agent-modal-btn-cancel{background:var(--bg3);color:var(--text2)}
|
|
580
|
+
.agent-modal-btn-cancel:hover{background:var(--bg4)}
|
|
581
|
+
.agent-modal-btn-primary{background:var(--accent);color:#fff}
|
|
582
|
+
.agent-modal-btn-primary:hover{background:var(--accent2)}
|
|
583
|
+
.agent-modal-btn-delete{background:var(--danger);color:#fff;margin-right:auto}
|
|
584
|
+
.agent-modal-btn-delete:hover{background:#dc2626}
|
|
585
|
+
|
|
586
|
+
/* ── Active agent indicator in header ── */
|
|
587
|
+
.active-agent-badge{
|
|
588
|
+
display:inline-flex;align-items:center;gap:6px;
|
|
589
|
+
padding:3px 10px 3px 6px;border-radius:20px;
|
|
590
|
+
background:var(--accent-light);font-size:12px;color:var(--accent);
|
|
591
|
+
font-weight:500;
|
|
592
|
+
}
|
|
593
|
+
.active-agent-badge .badge-avatar{
|
|
594
|
+
width:20px;height:20px;border-radius:50%;
|
|
595
|
+
display:grid;place-items:center;font-size:10px;color:#fff;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* ── Agent color themes ── */
|
|
599
|
+
.agent-color-0{background:linear-gradient(135deg,#6366f1,#8b5cf6)}
|
|
600
|
+
.agent-color-1{background:linear-gradient(135deg,#ec4899,#f43f5e)}
|
|
601
|
+
.agent-color-2{background:linear-gradient(135deg,#f59e0b,#f97316)}
|
|
602
|
+
.agent-color-3{background:linear-gradient(135deg,#10b981,#059669)}
|
|
603
|
+
.agent-color-4{background:linear-gradient(135deg,#3b82f6,#0ea5e9)}
|
|
604
|
+
.agent-color-5{background:linear-gradient(135deg,#8b5cf6,#a855f7)}
|
|
605
|
+
.agent-color-6{background:linear-gradient(135deg,#ef4444,#dc2626)}
|
|
606
|
+
.agent-color-7{background:linear-gradient(135deg,#14b8a6,#06b6d4)}
|
|
607
|
+
|
|
608
|
+
/* ── No agents empty state ── */
|
|
609
|
+
.agent-empty{
|
|
610
|
+
flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
611
|
+
padding:24px 16px;color:var(--text3);text-align:center;
|
|
612
|
+
}
|
|
613
|
+
.agent-empty-icon{font-size:40px;margin-bottom:10px;opacity:.4}
|
|
614
|
+
.agent-empty-text{font-size:12px;line-height:1.5}
|
|
615
|
+
|
|
616
|
+
/* ── Tree node indent per depth ── */
|
|
617
|
+
.agent-tree-node[data-depth="1"]{margin-left:12px}
|
|
618
|
+
.agent-tree-node[data-depth="2"]{margin-left:24px}
|
|
619
|
+
.agent-tree-node[data-depth="3"]{margin-left:36px}
|
|
620
|
+
.agent-tree-node[data-depth="4"]{margin-left:48px}
|
|
621
|
+
|
|
622
|
+
/* ── Platform badge in agent cards ── */
|
|
623
|
+
.platform-badge{
|
|
624
|
+
font-size:11px;white-space:nowrap;flex-shrink:0;
|
|
625
|
+
display:inline-flex;align-items:center;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* ── Agent form section (e.g. platform binding) ── */
|
|
629
|
+
.agent-form-section{
|
|
630
|
+
margin:16px 0;padding:14px 16px;border-radius:var(--radius-sm);
|
|
631
|
+
background:var(--bg);border:1px solid var(--border-light);
|
|
632
|
+
}
|
|
633
|
+
.agent-form-section-title{
|
|
634
|
+
font-size:12px;font-weight:700;color:var(--text);
|
|
635
|
+
margin-bottom:12px;text-transform:uppercase;letter-spacing:.5px;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* ══════════════════════════════════════════════════════
|
|
639
|
+
── Config Modal ──
|
|
640
|
+
══════════════════════════════════════════════════════ */
|
|
641
|
+
.config-modal{
|
|
642
|
+
background:var(--bg2);border-radius:var(--radius);padding:28px;
|
|
643
|
+
width:90%;max-width:560px;box-shadow:var(--shadow-lg);
|
|
644
|
+
max-height:85vh;overflow-y:auto;
|
|
645
|
+
}
|
|
646
|
+
.config-modal h3{font-size:18px;font-weight:700;margin-bottom:4px;display:flex;align-items:center;gap:8px}
|
|
647
|
+
.config-modal .modal-subtitle{font-size:12px;color:var(--text3);margin-bottom:20px}
|
|
648
|
+
|
|
649
|
+
.config-section{
|
|
650
|
+
margin-bottom:20px;padding:16px;border-radius:var(--radius-sm);
|
|
651
|
+
background:var(--bg);border:1px solid var(--border-light);
|
|
652
|
+
}
|
|
653
|
+
.config-section h4{
|
|
654
|
+
font-size:13px;font-weight:600;color:var(--text);margin-bottom:12px;
|
|
655
|
+
display:flex;align-items:center;gap:8px;
|
|
656
|
+
}
|
|
657
|
+
.config-section h4 .section-icon{font-size:16px}
|
|
658
|
+
|
|
659
|
+
.config-action-row{
|
|
660
|
+
display:flex;gap:8px;flex-wrap:wrap;
|
|
661
|
+
}
|
|
662
|
+
.config-action-btn{
|
|
663
|
+
padding:10px 18px;border-radius:var(--radius-sm);font-size:13px;font-weight:500;
|
|
664
|
+
display:flex;align-items:center;gap:8px;transition:var(--transition);
|
|
665
|
+
border:1px solid var(--border);
|
|
666
|
+
}
|
|
667
|
+
.config-action-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-light)}
|
|
668
|
+
.config-action-btn.primary{background:var(--accent);color:#fff;border-color:var(--accent)}
|
|
669
|
+
.config-action-btn.primary:hover{background:var(--accent2)}
|
|
670
|
+
.config-action-btn.danger{border-color:var(--danger);color:var(--danger)}
|
|
671
|
+
.config-action-btn.danger:hover{background:var(--danger);color:#fff}
|
|
672
|
+
.config-action-btn svg{width:16px;height:16px}
|
|
673
|
+
|
|
674
|
+
.config-action-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
675
|
+
|
|
676
|
+
.config-preview{
|
|
677
|
+
margin-top:12px;padding:12px;border-radius:var(--radius-xs);
|
|
678
|
+
background:var(--bg2);border:1px solid var(--border-light);
|
|
679
|
+
font-family:'SF Mono','Fira Code','Cascadia Code',monospace;
|
|
680
|
+
font-size:11px;color:var(--text2);max-height:200px;overflow-y:auto;
|
|
681
|
+
white-space:pre-wrap;word-break:break-all;line-height:1.5;
|
|
682
|
+
}
|
|
683
|
+
.config-preview .key{color:var(--accent);font-weight:600}
|
|
684
|
+
.config-preview .val{color:var(--ok)}
|
|
685
|
+
.config-preview .changed{color:var(--warn);font-weight:600}
|
|
686
|
+
|
|
687
|
+
.config-status{
|
|
688
|
+
margin-top:8px;padding:8px 12px;border-radius:var(--radius-xs);
|
|
689
|
+
font-size:12px;display:none;align-items:center;gap:6px;
|
|
690
|
+
}
|
|
691
|
+
.config-status.show{display:flex}
|
|
692
|
+
.config-status.success{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}
|
|
693
|
+
.config-status.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}
|
|
694
|
+
.config-status.loading{background:#eff6ff;color:#1e3a5f;border:1px solid #bfdbfe}
|
|
695
|
+
|
|
696
|
+
.config-status .spinner{
|
|
697
|
+
width:14px;height:14px;border:2px solid var(--info);border-top-color:transparent;
|
|
698
|
+
border-radius:50%;animation:spin .6s linear infinite;
|
|
699
|
+
}
|
|
700
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
701
|
+
|
|
702
|
+
/* Config import file drop area */
|
|
703
|
+
.config-import-area{
|
|
704
|
+
margin-top:12px;padding:24px;border:2px dashed var(--border);
|
|
705
|
+
border-radius:var(--radius-sm);text-align:center;
|
|
706
|
+
transition:var(--transition);cursor:pointer;
|
|
707
|
+
}
|
|
708
|
+
.config-import-area:hover,.config-import-area.dragover{
|
|
709
|
+
border-color:var(--accent);background:var(--accent-light)
|
|
710
|
+
}
|
|
711
|
+
.config-import-area .upload-icon{font-size:28px;margin-bottom:8px;opacity:.5}
|
|
712
|
+
.config-import-area .upload-text{font-size:13px;color:var(--text2)}
|
|
713
|
+
.config-import-area .upload-hint{font-size:11px;color:var(--text3);margin-top:4px}
|
|
714
|
+
.config-import-area input[type=file]{display:none}
|
|
715
|
+
|
|
716
|
+
.config-checkbox-row{
|
|
717
|
+
display:flex;align-items:center;gap:8px;margin-top:8px;font-size:13px;color:var(--text2);
|
|
718
|
+
}
|
|
719
|
+
.config-checkbox-row input[type=checkbox]{width:16px;height:16px;accent-color:var(--accent)}
|
|
720
|
+
|
|
721
|
+
/* ══════════════════════════════════════════════════════
|
|
722
|
+
── Mode Toggle (聊天/执行) ──
|
|
723
|
+
══════════════════════════════════════════════════════ */
|
|
724
|
+
.mode-toggle{
|
|
725
|
+
display:flex;align-items:center;gap:4px;
|
|
726
|
+
background:var(--bg3);border-radius:var(--radius-sm);
|
|
727
|
+
padding:3px;margin-bottom:10px;
|
|
728
|
+
width:fit-content;
|
|
729
|
+
}
|
|
730
|
+
.mode-btn{\n padding:6px 18px;border-radius:var(--radius-xs);font-size:12px;font-weight:500;
|
|
731
|
+
color:var(--text3);transition:var(--transition);display:flex;align-items:center;gap:5px;
|
|
732
|
+
}
|
|
733
|
+
.mode-btn:hover{color:var(--text)}
|
|
734
|
+
.mode-btn.active-chat{background:var(--bg2);color:var(--accent);box-shadow:var(--shadow-sm)}
|
|
735
|
+
.mode-btn.active-exec{background:var(--accent);color:#fff;box-shadow:var(--shadow-sm)}
|
|
736
|
+
.mode-btn svg{width:14px;height:14px}
|
|
737
|
+
|
|
738
|
+
/* ══════════════════════════════════════════════════════
|
|
739
|
+
── Task Plan Panel ──
|
|
740
|
+
══════════════════════════════════════════════════════ */
|
|
741
|
+
.task-panel{
|
|
742
|
+
max-width:800px;margin:0 auto 10px;
|
|
743
|
+
border:1px solid var(--border);border-radius:var(--radius);
|
|
744
|
+
background:var(--bg2);overflow:hidden;
|
|
745
|
+
transition:all .25s ease;
|
|
746
|
+
box-shadow:var(--shadow-sm);
|
|
747
|
+
}
|
|
748
|
+
.task-panel.hidden{display:none}
|
|
749
|
+
.task-panel-header{
|
|
750
|
+
display:flex;align-items:center;gap:8px;padding:8px 14px;
|
|
751
|
+
background:var(--bg3);cursor:pointer;user-select:none;
|
|
752
|
+
transition:var(--transition);
|
|
753
|
+
}
|
|
754
|
+
.task-panel-header:hover{background:var(--bg4)}
|
|
755
|
+
.task-panel-header .tp-icon{font-size:14px}
|
|
756
|
+
.task-panel-header .tp-title{flex:1;font-size:12px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.5px}
|
|
757
|
+
.task-panel-header .tp-count{
|
|
758
|
+
font-size:11px;color:var(--text3);background:var(--bg);padding:2px 8px;
|
|
759
|
+
border-radius:10px;font-weight:500;
|
|
760
|
+
}
|
|
761
|
+
.task-panel-header .tp-toggle{
|
|
762
|
+
width:22px;height:22px;border-radius:4px;display:grid;place-items:center;
|
|
763
|
+
color:var(--text3);transition:var(--transition);
|
|
764
|
+
}
|
|
765
|
+
.task-panel-header .tp-toggle:hover{background:var(--bg4);color:var(--text)}
|
|
766
|
+
.task-panel-header .tp-toggle svg{width:14px;height:14px;transition:transform .25s ease}
|
|
767
|
+
.task-panel-header .tp-toggle.expanded svg{transform:rotate(180deg)}
|
|
768
|
+
.task-panel-body{
|
|
769
|
+
max-height:0;overflow:hidden;transition:max-height .3s ease;
|
|
770
|
+
}
|
|
771
|
+
.task-panel-body.expanded{max-height:400px;overflow-y:auto}
|
|
772
|
+
.task-list{padding:6px 0}
|
|
773
|
+
.task-item{
|
|
774
|
+
display:flex;align-items:center;gap:8px;padding:6px 14px;
|
|
775
|
+
transition:var(--transition);position:relative;
|
|
776
|
+
}
|
|
777
|
+
.task-item:hover{background:var(--bg3)}
|
|
778
|
+
.task-checkbox{
|
|
779
|
+
width:18px;height:18px;border-radius:5px;border:2px solid var(--bg5);
|
|
780
|
+
display:grid;place-items:center;flex-shrink:0;cursor:pointer;
|
|
781
|
+
transition:var(--transition);font-size:11px;color:transparent;
|
|
782
|
+
}
|
|
783
|
+
.task-checkbox:hover{border-color:var(--accent)}
|
|
784
|
+
.task-checkbox.checked{
|
|
785
|
+
background:var(--ok);border-color:var(--ok);color:#fff}
|
|
786
|
+
.task-text{
|
|
787
|
+
flex:1;font-size:13px;color:var(--text);line-height:1.4;transition:var(--transition);
|
|
788
|
+
}
|
|
789
|
+
.task-text.done{text-decoration:line-through;color:var(--text3)}
|
|
790
|
+
.task-delete{
|
|
791
|
+
width:22px;height:22px;border-radius:4px;display:grid;place-items:center;
|
|
792
|
+
font-size:13px;color:var(--text3);opacity:0;transition:var(--transition);flex-shrink:0;
|
|
793
|
+
}
|
|
794
|
+
.task-item:hover .task-delete{opacity:1}
|
|
795
|
+
.task-delete:hover{background:#fef2f2;color:var(--danger)}
|
|
796
|
+
.task-empty{
|
|
797
|
+
padding:20px;text-align:center;color:var(--text3);font-size:12px;
|
|
798
|
+
}
|
|
799
|
+
.task-empty-icon{font-size:24px;margin-bottom:6px;opacity:.4}
|
|
800
|
+
.task-add-row{
|
|
801
|
+
display:flex;gap:6px;padding:8px 12px;border-top:1px solid var(--border-light);
|
|
802
|
+
}
|
|
803
|
+
.task-add-input{
|
|
804
|
+
flex:1;padding:7px 10px;border:1px solid var(--border);border-radius:var(--radius-xs);
|
|
805
|
+
background:var(--bg);font-size:12px;outline:none;transition:border-color .2s;
|
|
806
|
+
}
|
|
807
|
+
.task-add-input:focus{border-color:var(--accent)}
|
|
808
|
+
.task-add-input::placeholder{color:var(--text3)}
|
|
809
|
+
.task-add-btn{
|
|
810
|
+
padding:7px 14px;border-radius:var(--radius-xs);background:var(--accent);color:#fff;
|
|
811
|
+
font-size:12px;font-weight:500;transition:var(--transition);white-space:nowrap;
|
|
812
|
+
}
|
|
813
|
+
.task-add-btn:hover{background:var(--accent2)}
|
|
814
|
+
.task-add-btn:disabled{opacity:.4;cursor:default}
|
|
815
|
+
|
|
816
|
+
/* ══════════════════════════════════════════════════════
|
|
817
|
+
── Group Chat ──
|
|
818
|
+
══════════════════════════════════════════════════════ */
|
|
819
|
+
.sidebar-scroll{flex:1;overflow-y:auto;display:flex;flex-direction:column}
|
|
820
|
+
|
|
821
|
+
/* Groups Section in Sidebar */
|
|
822
|
+
.groups-section{border-top:1px solid var(--border-light);flex-shrink:0}
|
|
823
|
+
.groups-section-header{
|
|
824
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
825
|
+
padding:10px 16px 6px;cursor:pointer;user-select:none;
|
|
826
|
+
}
|
|
827
|
+
.groups-section-title{font-size:12px;font-weight:600;color:var(--text2);display:flex;align-items:center;gap:6px}
|
|
828
|
+
.group-create-btn{
|
|
829
|
+
width:24px;height:24px;border-radius:6px;
|
|
830
|
+
display:grid;place-items:center;
|
|
831
|
+
color:var(--text3);transition:var(--transition);font-size:14px;
|
|
832
|
+
}
|
|
833
|
+
.group-create-btn:hover{background:var(--bg3);color:var(--accent)}
|
|
834
|
+
.group-create-btn svg{width:14px;height:14px}
|
|
835
|
+
.group-list{padding:0 8px 8px}
|
|
836
|
+
|
|
837
|
+
.group-item{
|
|
838
|
+
display:flex;align-items:center;gap:10px;padding:10px 12px;
|
|
839
|
+
border-radius:var(--radius-sm);cursor:pointer;transition:var(--transition);
|
|
840
|
+
position:relative;
|
|
841
|
+
}
|
|
842
|
+
.group-item:hover{background:var(--bg3)}
|
|
843
|
+
.group-item.active{background:var(--accent-light);color:var(--accent2)}
|
|
844
|
+
.group-item.active::before{
|
|
845
|
+
content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);
|
|
846
|
+
width:3px;height:24px;background:var(--accent);border-radius:0 2px 2px 0;
|
|
847
|
+
}
|
|
848
|
+
.group-icon{
|
|
849
|
+
width:32px;height:32px;border-radius:8px;background:var(--bg3);
|
|
850
|
+
display:grid;place-items:center;flex-shrink:0;font-size:14px;
|
|
851
|
+
}
|
|
852
|
+
.group-item.active .group-icon{background:var(--accent-light);color:var(--accent)}
|
|
853
|
+
.group-info{flex:1;min-width:0}
|
|
854
|
+
.group-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
855
|
+
.group-preview{font-size:11px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
|
|
856
|
+
.group-badge{
|
|
857
|
+
font-size:10px;padding:1px 7px;border-radius:10px;
|
|
858
|
+
background:var(--bg3);color:var(--text3);font-weight:500;flex-shrink:0;
|
|
859
|
+
}
|
|
860
|
+
.group-item.active .group-badge{background:var(--accent-light);color:var(--accent)}
|
|
861
|
+
.group-delete{
|
|
862
|
+
opacity:0;position:absolute;right:8px;top:50%;transform:translateY(-50%);
|
|
863
|
+
width:24px;height:24px;border-radius:4px;display:grid;place-items:center;
|
|
864
|
+
background:var(--bg2);transition:var(--transition);font-size:12px;color:var(--text3);
|
|
865
|
+
}
|
|
866
|
+
.group-item:hover .group-delete{opacity:1}
|
|
867
|
+
.group-delete:hover{background:var(--danger);color:#fff}
|
|
868
|
+
|
|
869
|
+
/* Group Chat Messages */
|
|
870
|
+
.group-msg-system{
|
|
871
|
+
text-align:center;padding:8px 0;
|
|
872
|
+
font-size:12px;color:var(--text3);
|
|
873
|
+
animation:msgIn .25s ease-out;
|
|
874
|
+
}
|
|
875
|
+
.group-msg-row{display:flex;gap:10px;animation:msgIn .25s ease-out}
|
|
876
|
+
.group-msg-avatar{
|
|
877
|
+
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
878
|
+
display:grid;place-items:center;font-size:14px;
|
|
879
|
+
}
|
|
880
|
+
.group-msg-name{
|
|
881
|
+
font-size:11px;font-weight:600;color:var(--text2);margin-bottom:4px;
|
|
882
|
+
display:flex;align-items:center;gap:6px;
|
|
883
|
+
}
|
|
884
|
+
.group-msg-name .role-badge{
|
|
885
|
+
font-size:9px;padding:1px 5px;border-radius:4px;
|
|
886
|
+
background:var(--bg3);color:var(--text3);font-weight:500;
|
|
887
|
+
}
|
|
888
|
+
.group-msg-bubble{
|
|
889
|
+
max-width:70%;padding:12px 16px;border-radius:var(--radius);
|
|
890
|
+
font-size:14px;line-height:1.7;word-break:break-word;
|
|
891
|
+
box-shadow:var(--shadow-sm);background:var(--bot-bubble);color:var(--bot-text);
|
|
892
|
+
border:1px solid var(--border-light);border-bottom-left-radius:4px;
|
|
893
|
+
border-left:3px solid var(--accent);
|
|
894
|
+
}
|
|
895
|
+
.group-msg-bubble p{margin-bottom:8px}
|
|
896
|
+
.group-msg-bubble p:last-child{margin-bottom:0}
|
|
897
|
+
.group-msg-bubble code{
|
|
898
|
+
background:rgba(0,0,0,.06);padding:2px 6px;border-radius:4px;
|
|
899
|
+
font-family:'SF Mono','Fira Code','Cascadia Code',monospace;font-size:12.5px;
|
|
900
|
+
}
|
|
901
|
+
.group-msg-bubble pre{
|
|
902
|
+
background:#1e1e2e;color:#cdd6f4;padding:14px 16px;border-radius:var(--radius-sm);
|
|
903
|
+
overflow-x:auto;margin:8px 0;font-size:12.5px;line-height:1.5;
|
|
904
|
+
font-family:'SF Mono','Fira Code','Cascadia Code',monospace;
|
|
905
|
+
}
|
|
906
|
+
.group-msg-bubble pre code{background:none;padding:0;font-size:inherit}
|
|
907
|
+
.group-msg-bubble strong{font-weight:600}
|
|
908
|
+
.group-msg-bubble em{font-style:italic}
|
|
909
|
+
.group-msg-bubble ul,.group-msg-bubble ol{padding-left:20px;margin:6px 0}
|
|
910
|
+
.group-msg-bubble li{margin:3px 0}
|
|
911
|
+
.group-msg-time{font-size:10px;color:var(--text3);margin-top:4px}
|
|
912
|
+
|
|
913
|
+
/* Group Modal */
|
|
914
|
+
.group-modal{
|
|
915
|
+
background:var(--bg2);border-radius:var(--radius);padding:28px;
|
|
916
|
+
width:90%;max-width:560px;box-shadow:var(--shadow-lg);
|
|
917
|
+
max-height:85vh;overflow-y:auto;
|
|
918
|
+
}
|
|
919
|
+
.group-modal h3{font-size:18px;font-weight:700;margin-bottom:4px;display:flex;align-items:center;gap:8px}
|
|
920
|
+
.group-modal .modal-subtitle{font-size:12px;color:var(--text3);margin-bottom:20px}
|
|
921
|
+
|
|
922
|
+
/* Group member list in settings */
|
|
923
|
+
.group-member-item{
|
|
924
|
+
display:flex;align-items:center;gap:10px;padding:10px 0;
|
|
925
|
+
border-bottom:1px solid var(--border-light);
|
|
926
|
+
}
|
|
927
|
+
.group-member-item:last-child{border-bottom:none}
|
|
928
|
+
.group-member-info{flex:1;min-width:0}
|
|
929
|
+
.group-member-name{font-size:13px;font-weight:600;display:flex;align-items:center;gap:6px}
|
|
930
|
+
.group-member-role{
|
|
931
|
+
font-size:10px;padding:1px 6px;border-radius:4px;font-weight:500;
|
|
932
|
+
}
|
|
933
|
+
.group-member-role.owner{background:#fef3c7;color:#92400e}
|
|
934
|
+
.group-member-role.admin{background:#dbeafe;color:#1e40af}
|
|
935
|
+
.group-member-role.member{background:var(--bg3);color:var(--text3)}
|
|
936
|
+
.group-member-remove{
|
|
937
|
+
width:28px;height:28px;border-radius:6px;display:grid;place-items:center;
|
|
938
|
+
color:var(--text3);transition:var(--transition);font-size:14px;
|
|
939
|
+
}
|
|
940
|
+
.group-member-remove:hover:not(:disabled){background:#fef2f2;color:var(--danger)}
|
|
941
|
+
.group-member-remove:disabled{opacity:.3;cursor:not-allowed}
|
|
942
|
+
|
|
943
|
+
/* Group color picker */
|
|
944
|
+
.color-picker-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px}
|
|
945
|
+
.color-option{
|
|
946
|
+
width:32px;height:32px;border-radius:8px;cursor:pointer;
|
|
947
|
+
border:2px solid transparent;transition:var(--transition);
|
|
948
|
+
}
|
|
949
|
+
.color-option:hover{transform:scale(1.1)}
|
|
950
|
+
.color-option.selected{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-light)}
|
|
951
|
+
|
|
952
|
+
/* Group member picker in create modal */
|
|
953
|
+
.group-member-picker{max-height:200px;overflow-y:auto;padding:4px 0}
|
|
954
|
+
.group-member-pick-item{
|
|
955
|
+
display:flex;align-items:center;gap:8px;padding:8px 10px;
|
|
956
|
+
border-radius:var(--radius-xs);cursor:pointer;transition:var(--transition);
|
|
957
|
+
}
|
|
958
|
+
.group-member-pick-item:hover{background:var(--bg3)}
|
|
959
|
+
.group-member-pick-item input[type=checkbox]{width:16px;height:16px;accent-color:var(--accent)}
|
|
960
|
+
.group-member-pick-item .pick-agent-info{flex:1;min-width:0}
|
|
961
|
+
.group-member-pick-item .pick-agent-name{font-size:13px;font-weight:500}
|
|
962
|
+
.group-member-pick-item .pick-agent-desc{font-size:11px;color:var(--text3)}
|
|
963
|
+
</style>
|
|
964
|
+
</head>
|
|
965
|
+
<body>
|
|
966
|
+
<div class="app">
|
|
967
|
+
<!-- Sidebar (Sessions) -->
|
|
968
|
+
<div class="sidebar" id="sidebar">
|
|
969
|
+
<div class="sidebar-header">
|
|
970
|
+
<div class="sidebar-logo">
|
|
971
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
972
|
+
</div>
|
|
973
|
+
<div>
|
|
974
|
+
<div class="sidebar-title">MyAgent</div>
|
|
975
|
+
<div class="sidebar-subtitle">AI 助手</div>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
|
|
979
|
+
<button class="new-chat-btn" onclick="newChat()">
|
|
980
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
981
|
+
新对话
|
|
982
|
+
</button>
|
|
983
|
+
|
|
984
|
+
<div class="sidebar-search">
|
|
985
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
986
|
+
<input type="text" placeholder="搜索对话..." id="searchInput" oninput="filterSessions()">
|
|
987
|
+
</div>
|
|
988
|
+
|
|
989
|
+
<div class="sidebar-scroll" id="sidebarScroll">
|
|
990
|
+
<div class="session-list" id="sessionList">
|
|
991
|
+
<!-- sessions loaded dynamically -->
|
|
992
|
+
</div>
|
|
993
|
+
<div class="groups-section" id="groupsSection">
|
|
994
|
+
<div class="groups-section-header" onclick="toggleGroupsSection()">
|
|
995
|
+
<span class="groups-section-title">👥 群聊</span>
|
|
996
|
+
<button class="group-create-btn" onclick="event.stopPropagation();showCreateGroupModal()" title="新建群聊">
|
|
997
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
998
|
+
</button>
|
|
999
|
+
</div>
|
|
1000
|
+
<div class="group-list" id="groupList">
|
|
1001
|
+
<!-- groups loaded dynamically -->
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
|
|
1006
|
+
<div class="sidebar-footer">
|
|
1007
|
+
<button class="sidebar-footer-btn" onclick="window.open('/ui/index.html','_blank')">
|
|
1008
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
1009
|
+
管理后台
|
|
1010
|
+
</button>
|
|
1011
|
+
<button class="sidebar-footer-btn" onclick="showStatus()">
|
|
1012
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
|
|
1013
|
+
系统状态
|
|
1014
|
+
<span class="model-badge" id="modelBadge">...</span>
|
|
1015
|
+
</button>
|
|
1016
|
+
<button class="sidebar-footer-btn" onclick="showConfigModal()">
|
|
1017
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
1018
|
+
配置管理
|
|
1019
|
+
</button>
|
|
1020
|
+
</div>
|
|
1021
|
+
</div>
|
|
1022
|
+
|
|
1023
|
+
<!-- Main (Chat) -->
|
|
1024
|
+
<div class="main" id="mainArea">
|
|
1025
|
+
<div class="main-header">
|
|
1026
|
+
<button class="toggle-sidebar" onclick="toggleSidebar()" title="切换侧栏">
|
|
1027
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
|
1028
|
+
</button>
|
|
1029
|
+
<div class="main-title">
|
|
1030
|
+
<span class="dot"></span>
|
|
1031
|
+
<span id="headerTitle">新对话</span>
|
|
1032
|
+
<span class="active-agent-badge" id="activeAgentBadge">
|
|
1033
|
+
<span class="badge-avatar" id="badgeAvatar">D</span>
|
|
1034
|
+
<span id="badgeName">default</span>
|
|
1035
|
+
</span>
|
|
1036
|
+
</div>
|
|
1037
|
+
<div class="header-actions">
|
|
1038
|
+
<button class="header-btn" id="groupBackBtn" onclick="exitGroupChat()" title="返回对话" style="display:none">
|
|
1039
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
|
|
1040
|
+
</button>
|
|
1041
|
+
<button class="header-btn" id="groupSettingsBtn" onclick="showGroupSettingsModal()" title="群聊设置" style="display:none">
|
|
1042
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
1043
|
+
</button>
|
|
1044
|
+
<button class="header-btn" id="clearChatBtn" onclick="clearCurrentChat()" title="清空当前对话">
|
|
1045
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><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"/></svg>
|
|
1046
|
+
</button>
|
|
1047
|
+
</div>
|
|
1048
|
+
</div>
|
|
1049
|
+
|
|
1050
|
+
<div class="messages-container" id="messagesContainer">
|
|
1051
|
+
<div class="messages-inner" id="messagesInner">
|
|
1052
|
+
<!-- Welcome card shown when no messages -->
|
|
1053
|
+
<div class="welcome-card" id="welcomeCard">
|
|
1054
|
+
<h2><span class="emoji" id="welcomeEmoji">👋</span> <span id="welcomeTitle">你好,我是 MyAgent</span></h2>
|
|
1055
|
+
<p class="subtitle" id="welcomeSubtitle">本地桌面端执行型 AI 助手 — 我可以帮你写代码、搜索信息、管理文件等</p>
|
|
1056
|
+
<div class="capabilities">
|
|
1057
|
+
<div class="capability"><span class="cap-icon">💻</span> 代码编写与执行</div>
|
|
1058
|
+
<div class="capability"><span class="cap-icon">🔍</span> 网络搜索与阅读</div>
|
|
1059
|
+
<div class="capability"><span class="cap-icon">📁</span> 文件管理与操作</div>
|
|
1060
|
+
<div class="capability"><span class="cap-icon">🧠</span> 长期记忆系统</div>
|
|
1061
|
+
<div class="capability"><span class="cap-icon">🌐</span> 多平台接入</div>
|
|
1062
|
+
<div class="capability"><span class="cap-icon">🛠️</span> 系统命令执行</div>
|
|
1063
|
+
</div>
|
|
1064
|
+
</div>
|
|
1065
|
+
</div>
|
|
1066
|
+
</div>
|
|
1067
|
+
|
|
1068
|
+
<!-- Task Plan Panel -->
|
|
1069
|
+
<div class="task-panel hidden" id="taskPanel">
|
|
1070
|
+
<div class="task-panel-header" onclick="toggleTaskPanel()">
|
|
1071
|
+
<span class="tp-icon">📋</span>
|
|
1072
|
+
<span class="tp-title">任务计划 (task.md)</span>
|
|
1073
|
+
<span class="tp-count" id="taskCount">0</span>
|
|
1074
|
+
<span class="tp-toggle" id="taskToggle"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></span>
|
|
1075
|
+
</div>
|
|
1076
|
+
<div class="task-panel-body" id="taskBody">
|
|
1077
|
+
<div class="task-list" id="taskList"></div>
|
|
1078
|
+
<div class="task-add-row">
|
|
1079
|
+
<input class="task-add-input" id="taskAddInput" placeholder="添加新任务..." onkeydown="if(event.key==='Enter')addTaskItem()">
|
|
1080
|
+
<button class="task-add-btn" onclick="addTaskItem()">+ 添加</button>
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
|
|
1085
|
+
<!-- Input -->
|
|
1086
|
+
<div class="input-area">
|
|
1087
|
+
<div class="input-wrapper">
|
|
1088
|
+
<!-- Mode Toggle -->
|
|
1089
|
+
<div class="mode-toggle">
|
|
1090
|
+
<button class="mode-btn active-chat" id="modeChatBtn" onclick="setMode('chat')">
|
|
1091
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
1092
|
+
💬 聊天
|
|
1093
|
+
</button>
|
|
1094
|
+
<button class="mode-btn" id="modeExecBtn" onclick="setMode('exec')">
|
|
1095
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
|
1096
|
+
⚡ 执行
|
|
1097
|
+
</button>
|
|
1098
|
+
</div>
|
|
1099
|
+
<div class="input-box">
|
|
1100
|
+
<textarea id="userInput" placeholder="输入消息... (Enter 发送, Shift+Enter 换行)" rows="1" onkeydown="handleKeyDown(event)" oninput="autoResize(this)"></textarea>
|
|
1101
|
+
<button class="send-btn" id="sendBtn" onclick="sendMessage()" disabled>
|
|
1102
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
|
1103
|
+
</button>
|
|
1104
|
+
<button class="stop-btn" id="stopBtn" onclick="stopGenerating()" style="display:none">
|
|
1105
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
|
|
1106
|
+
</button>
|
|
1107
|
+
</div>
|
|
1108
|
+
<div class="input-hint">MyAgent 可能会出错,请核实重要信息</div>
|
|
1109
|
+
</div>
|
|
1110
|
+
</div>
|
|
1111
|
+
</div>
|
|
1112
|
+
|
|
1113
|
+
<!-- Agent Panel (RIGHT) -->
|
|
1114
|
+
<div class="agent-panel" id="agentPanel">
|
|
1115
|
+
<div class="agent-panel-header">
|
|
1116
|
+
<h3><span class="agents-emoji">🤖</span> Agents</h3>
|
|
1117
|
+
<button class="agent-create-btn" onclick="showCreateAgentModal()" title="创建新 Agent">
|
|
1118
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
1119
|
+
</button>
|
|
1120
|
+
</div>
|
|
1121
|
+
|
|
1122
|
+
<div class="agent-list" id="agentList">
|
|
1123
|
+
<!-- agent cards loaded dynamically -->
|
|
1124
|
+
</div>
|
|
1125
|
+
|
|
1126
|
+
<div class="agent-panel-footer">
|
|
1127
|
+
<button class="agent-collapse-btn" onclick="toggleAgentPanel()">
|
|
1128
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
|
|
1129
|
+
收起面板
|
|
1130
|
+
</button>
|
|
1131
|
+
</div>
|
|
1132
|
+
</div>
|
|
1133
|
+
</div>
|
|
1134
|
+
|
|
1135
|
+
<!-- Floating expand button for collapsed agent panel -->
|
|
1136
|
+
<button class="agent-expand-float hidden" id="agentExpandFloat" onclick="toggleAgentPanel()" title="展开 Agent 面板">
|
|
1137
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
1138
|
+
</button>
|
|
1139
|
+
|
|
1140
|
+
<!-- Toast Container -->
|
|
1141
|
+
<div class="toast-container" id="toastContainer"></div>
|
|
1142
|
+
|
|
1143
|
+
<!-- Agent Modal Container -->
|
|
1144
|
+
<div id="agentModalContainer"></div>
|
|
1145
|
+
|
|
1146
|
+
<!-- Config Modal Container -->
|
|
1147
|
+
<div id="configModalContainer"></div>
|
|
1148
|
+
|
|
1149
|
+
<!-- Group Modal Container -->
|
|
1150
|
+
<div id="groupModalContainer"></div>
|
|
1151
|
+
|
|
1152
|
+
<script>
|
|
1153
|
+
// ── State ──
|
|
1154
|
+
const state = {
|
|
1155
|
+
sessions: [], // [{id, name, messages: int, last: string}]
|
|
1156
|
+
chatMode: 'chat', // 'chat' | 'exec'
|
|
1157
|
+
taskPanelExpanded: true,
|
|
1158
|
+
taskItems: [], // [{text, done}]
|
|
1159
|
+
activeSessionId: null,
|
|
1160
|
+
messages: [], // [{role, content, time}]
|
|
1161
|
+
isGenerating: false,
|
|
1162
|
+
abortController: null,
|
|
1163
|
+
execTimerInterval: null, // polling interval ID for execution progress
|
|
1164
|
+
systemStatus: null,
|
|
1165
|
+
// ── Agent state (层级体系) ──
|
|
1166
|
+
agentTree: [], // 树形结构 [{path, name, children: [...], ...}]
|
|
1167
|
+
agentsFlat: [], // 扁平列表 [{path, name, parent, depth, ...}]
|
|
1168
|
+
activeAgent: 'default',
|
|
1169
|
+
agentPanelOpen: true,
|
|
1170
|
+
agentSessions: {}, // {agentPath: [{id, name, messages, last}]}
|
|
1171
|
+
agentDetails: {}, // {agentPath: {soul, identity, model, ...}}
|
|
1172
|
+
expandedNodes: new Set(['default']), // 树形展开状态
|
|
1173
|
+
modelsLibrary: [], // 模型库 [{id, name, provider, model, base_url, enabled, has_api_key, ...}]
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
// ── Group Chat State ──
|
|
1177
|
+
var currentView = 'chat'; // 'chat' | 'group'
|
|
1178
|
+
var currentGroupId = null;
|
|
1179
|
+
var groups = [];
|
|
1180
|
+
var groupMessages = [];
|
|
1181
|
+
var groupsSectionExpanded = true;
|
|
1182
|
+
|
|
1183
|
+
// ── 平台图标映射 ──
|
|
1184
|
+
const PLATFORM_ICONS = {
|
|
1185
|
+
telegram: '✈️', discord: '🎮', feishu: '🐦', qq: '🐧', wechat: '💬',
|
|
1186
|
+
};
|
|
1187
|
+
const PLATFORM_LABELS = {
|
|
1188
|
+
telegram: 'Telegram', discord: 'Discord', feishu: '飞书', qq: 'QQ', wechat: '微信',
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
// ── Utility: deterministic color from agent name ──
|
|
1192
|
+
function hashStr(str) {
|
|
1193
|
+
let h = 0;
|
|
1194
|
+
for (let i = 0; i < str.length; i++) {
|
|
1195
|
+
h = ((h << 5) - h + str.charCodeAt(i)) | 0;
|
|
1196
|
+
}
|
|
1197
|
+
return Math.abs(h);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function getAgentColorClass(name) {
|
|
1201
|
+
return 'agent-color-' + (hashStr(name) % 8);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function getAgentInitials(name) {
|
|
1205
|
+
if (!name) return '?';
|
|
1206
|
+
const parts = name.replace(/[-_]/g, ' ').split(/\s+/);
|
|
1207
|
+
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
1208
|
+
return name.slice(0, 2).toUpperCase();
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function getAgentGradient(name) {
|
|
1212
|
+
const palettes = [
|
|
1213
|
+
['#6366f1','#8b5cf6'],['#ec4899','#f43f5e'],['#f59e0b','#f97316'],
|
|
1214
|
+
['#10b981','#059669'],['#3b82f6','#0ea5e9'],['#8b5cf6','#a855f7'],
|
|
1215
|
+
['#ef4444','#dc2626'],['#14b8a6','#06b6d4']
|
|
1216
|
+
];
|
|
1217
|
+
const idx = hashStr(name) % palettes.length;
|
|
1218
|
+
return `linear-gradient(135deg,${palettes[idx][0]},${palettes[idx][1]})`;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// ── Init ──
|
|
1222
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1223
|
+
loadModels();
|
|
1224
|
+
loadAgents();
|
|
1225
|
+
loadStatus();
|
|
1226
|
+
fetchGroups();
|
|
1227
|
+
// Enable/disable send button
|
|
1228
|
+
document.getElementById('userInput').addEventListener('input', function() {
|
|
1229
|
+
document.getElementById('sendBtn').disabled = !this.value.trim();
|
|
1230
|
+
});
|
|
1231
|
+
// Load task plan if in exec mode
|
|
1232
|
+
if (state.chatMode === 'exec') {
|
|
1233
|
+
document.getElementById('taskPanel').classList.remove('hidden');
|
|
1234
|
+
loadTaskPlan();
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
// ══════════════════════════════════════════════════════
|
|
1239
|
+
// ── Mode Toggle & Task Plan ──
|
|
1240
|
+
// ══════════════════════════════════════════════════════
|
|
1241
|
+
|
|
1242
|
+
function setMode(mode) {
|
|
1243
|
+
state.chatMode = mode;
|
|
1244
|
+
var chatBtn = document.getElementById('modeChatBtn');
|
|
1245
|
+
var execBtn = document.getElementById('modeExecBtn');
|
|
1246
|
+
var taskPanel = document.getElementById('taskPanel');
|
|
1247
|
+
var input = document.getElementById('userInput');
|
|
1248
|
+
if (mode === 'chat') {
|
|
1249
|
+
chatBtn.className = 'mode-btn active-chat';
|
|
1250
|
+
execBtn.className = 'mode-btn';
|
|
1251
|
+
taskPanel.classList.add('hidden');
|
|
1252
|
+
input.placeholder = '输入消息... (Enter 发送, Shift+Enter 换行)';
|
|
1253
|
+
} else {
|
|
1254
|
+
chatBtn.className = 'mode-btn';
|
|
1255
|
+
execBtn.className = 'mode-btn active-exec';
|
|
1256
|
+
taskPanel.classList.remove('hidden');
|
|
1257
|
+
input.placeholder = '执行模式 — 描述任务或输入指令...';
|
|
1258
|
+
loadTaskPlan();
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function toggleTaskPanel() {
|
|
1263
|
+
state.taskPanelExpanded = !state.taskPanelExpanded;
|
|
1264
|
+
var body = document.getElementById('taskBody');
|
|
1265
|
+
var toggle = document.getElementById('taskToggle');
|
|
1266
|
+
if (state.taskPanelExpanded) {
|
|
1267
|
+
body.classList.add('expanded');
|
|
1268
|
+
toggle.classList.add('expanded');
|
|
1269
|
+
} else {
|
|
1270
|
+
body.classList.remove('expanded');
|
|
1271
|
+
toggle.classList.remove('expanded');
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
async function loadTaskPlan() {
|
|
1276
|
+
try {
|
|
1277
|
+
var agent = state.activeAgent || 'default';
|
|
1278
|
+
var data = await api('/api/task-plan?agent=' + encodeURIComponent(agent));
|
|
1279
|
+
state.taskItems = data.tasks || [];
|
|
1280
|
+
renderTaskList();
|
|
1281
|
+
} catch (e) {
|
|
1282
|
+
state.taskItems = [];
|
|
1283
|
+
renderTaskList();
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function renderTaskList() {
|
|
1288
|
+
var listEl = document.getElementById('taskList');
|
|
1289
|
+
var countEl = document.getElementById('taskCount');
|
|
1290
|
+
var doneCount = state.taskItems.filter(function(t) { return t.done; }).length;
|
|
1291
|
+
countEl.textContent = doneCount + '/' + state.taskItems.length;
|
|
1292
|
+
if (!state.taskItems.length) {
|
|
1293
|
+
listEl.innerHTML = '<div class="task-empty"><div class="task-empty-icon">📋</div>暂无任务<br>输入任务描述后点击添加</div>';
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
var html = '';
|
|
1297
|
+
for (var i = 0; i < state.taskItems.length; i++) {
|
|
1298
|
+
var t = state.taskItems[i];
|
|
1299
|
+
html += '<div class="task-item" data-idx="' + i + '">' +
|
|
1300
|
+
'<div class="task-checkbox' + (t.done ? ' checked' : '') + '" onclick="toggleTaskDone(' + i + ')">✓</div>' +
|
|
1301
|
+
'<span class="task-text' + (t.done ? ' done' : '') + '">' + escapeHtml(t.text) + '</span>' +
|
|
1302
|
+
'<button class="task-delete" onclick="deleteTaskItem(' + i + ')" title="删除">×</button>' +
|
|
1303
|
+
'</div>';
|
|
1304
|
+
}
|
|
1305
|
+
listEl.innerHTML = html;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function escapeHtml(text) {
|
|
1309
|
+
var div = document.createElement('div');
|
|
1310
|
+
div.appendChild(document.createTextNode(text));
|
|
1311
|
+
return div.innerHTML;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
async function toggleTaskDone(idx) {
|
|
1315
|
+
if (idx < 0 || idx >= state.taskItems.length) return;
|
|
1316
|
+
state.taskItems[idx].done = !state.taskItems[idx].done;
|
|
1317
|
+
try {
|
|
1318
|
+
var agent = state.activeAgent || 'default';
|
|
1319
|
+
await api('/api/task-plan', {
|
|
1320
|
+
method: 'PUT',
|
|
1321
|
+
body: JSON.stringify({agent: agent, tasks: state.taskItems})
|
|
1322
|
+
});
|
|
1323
|
+
renderTaskList();
|
|
1324
|
+
} catch (e) {
|
|
1325
|
+
toast('更新任务失败: ' + e.message, 'error');
|
|
1326
|
+
state.taskItems[idx].done = !state.taskItems[idx].done;
|
|
1327
|
+
renderTaskList();
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
async function addTaskItem() {
|
|
1332
|
+
var input = document.getElementById('taskAddInput');
|
|
1333
|
+
var text = input.value.trim();
|
|
1334
|
+
if (!text) return;
|
|
1335
|
+
input.value = '';
|
|
1336
|
+
try {
|
|
1337
|
+
var agent = state.activeAgent || 'default';
|
|
1338
|
+
var data = await api('/api/task-plan', {
|
|
1339
|
+
method: 'POST',
|
|
1340
|
+
body: JSON.stringify({agent: agent, text: text})
|
|
1341
|
+
});
|
|
1342
|
+
state.taskItems = data.tasks || [];
|
|
1343
|
+
renderTaskList();
|
|
1344
|
+
toast('任务已添加', 'success');
|
|
1345
|
+
} catch (e) {
|
|
1346
|
+
toast('添加任务失败: ' + e.message, 'error');
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
async function deleteTaskItem(idx) {
|
|
1351
|
+
if (idx < 0 || idx >= state.taskItems.length) return;
|
|
1352
|
+
try {
|
|
1353
|
+
var agent = state.activeAgent || 'default';
|
|
1354
|
+
var data = await api('/api/task-plan/' + idx + '?agent=' + encodeURIComponent(agent), {
|
|
1355
|
+
method: 'DELETE'
|
|
1356
|
+
});
|
|
1357
|
+
state.taskItems = data.tasks || [];
|
|
1358
|
+
renderTaskList();
|
|
1359
|
+
toast('任务已删除', 'info');
|
|
1360
|
+
} catch (e) {
|
|
1361
|
+
toast('删除任务失败: ' + e.message, 'error');
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// ── Model Library ──
|
|
1366
|
+
async function loadModels() {
|
|
1367
|
+
try {
|
|
1368
|
+
var data = await api('/api/models');
|
|
1369
|
+
state.modelsLibrary = data || [];
|
|
1370
|
+
} catch (e) {
|
|
1371
|
+
state.modelsLibrary = [];
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// ── API Calls ──
|
|
1376
|
+
async function api(url, options = {}) {
|
|
1377
|
+
try {
|
|
1378
|
+
const resp = await fetch(url, {
|
|
1379
|
+
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
1380
|
+
...options,
|
|
1381
|
+
});
|
|
1382
|
+
const data = await resp.json();
|
|
1383
|
+
if (!resp.ok) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
1384
|
+
return data;
|
|
1385
|
+
} catch (e) {
|
|
1386
|
+
if (e.name === 'AbortError') throw e;
|
|
1387
|
+
console.error('API error:', e);
|
|
1388
|
+
throw e;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// ── Status ──
|
|
1393
|
+
async function loadStatus() {
|
|
1394
|
+
try {
|
|
1395
|
+
const data = await api('/api/status');
|
|
1396
|
+
state.systemStatus = data;
|
|
1397
|
+
document.getElementById('modelBadge').textContent =
|
|
1398
|
+
`${(data.provider || '?').split('/')[0]}/${(data.model || '?').split('/').pop()}`;
|
|
1399
|
+
} catch (e) {
|
|
1400
|
+
document.getElementById('modelBadge').textContent = '离线';
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function showStatus() {
|
|
1405
|
+
const s = state.systemStatus;
|
|
1406
|
+
if (!s) { toast('无法获取系统状态', 'error'); return; }
|
|
1407
|
+
const info = `🤖 MyAgent 状态\n━━━━━━━━━━━━\n状态: ${s.running ? '✅ 运行中' : '❌ 停止'}\n模型: ${s.provider}/${s.model}\n技能: ${s.skills} 个\n记忆: ${s.memory?.short_term_count || 0} 短期 / ${s.memory?.long_term_count || 0} 长期`;
|
|
1408
|
+
alert(info);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// ══════════════════════════════════════════════════════
|
|
1412
|
+
// ── Agent Management (层级体系) ──
|
|
1413
|
+
// ══════════════════════════════════════════════════════
|
|
1414
|
+
|
|
1415
|
+
function flattenTree(nodes, depth, parent) {
|
|
1416
|
+
const result = [];
|
|
1417
|
+
for (const n of nodes) {
|
|
1418
|
+
const flat = Object.assign({}, n, {depth: depth, parent: parent});
|
|
1419
|
+
const children = flat.children || [];
|
|
1420
|
+
result.push(Object.assign({}, flat, {children: undefined}));
|
|
1421
|
+
if (children.length > 0) {
|
|
1422
|
+
result.push.apply(result, flattenTree(children, depth + 1, n.path));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return result;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
function findAgentByPath(path) {
|
|
1429
|
+
return state.agentsFlat.find(function(a) { return a.path === path; });
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
async function loadAgents() {
|
|
1433
|
+
try {
|
|
1434
|
+
var treeData = await api('/api/agents/tree');
|
|
1435
|
+
state.agentTree = treeData || [];
|
|
1436
|
+
state.agentsFlat = flattenTree(state.agentTree, 0, null);
|
|
1437
|
+
|
|
1438
|
+
if (!state.agentsFlat.find(function(a) { return a.path === 'default'; })) {
|
|
1439
|
+
var defAgent = {path:'default', name:'default', depth:0, parent:null, description:'默认助手', avatar_emoji:'🤖', execution_mode:'local', avatar_color:getAgentColorClass('default'), session_count:0};
|
|
1440
|
+
state.agentsFlat.unshift(defAgent);
|
|
1441
|
+
state.agentTree.unshift(Object.assign({}, defAgent, {children:[]}));
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
for (var i = 0; i < state.agentsFlat.length; i++) {
|
|
1445
|
+
state.expandedNodes.add(state.agentsFlat[i].path);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if (!state.activeAgent || !findAgentByPath(state.activeAgent)) {
|
|
1449
|
+
state.activeAgent = 'default';
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
renderAgentTree();
|
|
1453
|
+
updateActiveAgentBadge();
|
|
1454
|
+
await loadSessions();
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
var defAgent = {path:'default', name:'default', depth:0, parent:null, description:'默认助手', avatar_emoji:'🤖', execution_mode:'local', avatar_color:getAgentColorClass('default'), session_count:0};
|
|
1457
|
+
state.agentsFlat = [defAgent];
|
|
1458
|
+
state.agentTree = [Object.assign({}, defAgent, {children:[]})];
|
|
1459
|
+
state.activeAgent = 'default';
|
|
1460
|
+
renderAgentTree();
|
|
1461
|
+
updateActiveAgentBadge();
|
|
1462
|
+
await loadSessions();
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
async function loadAgentDetails(agentPath) {
|
|
1467
|
+
if (state.agentDetails[agentPath]) return state.agentDetails[agentPath];
|
|
1468
|
+
try {
|
|
1469
|
+
var data = await api('/api/agents/' + encodeURIComponent(agentPath));
|
|
1470
|
+
state.agentDetails[agentPath] = data || {};
|
|
1471
|
+
return data;
|
|
1472
|
+
} catch (e) {
|
|
1473
|
+
return {};
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function renderAgentTree() {
|
|
1478
|
+
var list = document.getElementById('agentList');
|
|
1479
|
+
if (state.agentTree.length === 0) {
|
|
1480
|
+
list.innerHTML = '<div class="agent-empty"><div class="agent-empty-icon">🤖</div><div class="agent-empty-text">暂无 Agent<br>点击上方 + 创建</div></div>';
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
list.innerHTML = renderTreeNodes(state.agentTree, 0);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function renderTreeNodes(nodes, depth) {
|
|
1487
|
+
if (!nodes || nodes.length === 0) return '';
|
|
1488
|
+
var html = '';
|
|
1489
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
1490
|
+
var n = nodes[i];
|
|
1491
|
+
var isActive = n.path === state.activeAgent;
|
|
1492
|
+
var hasChildren = n.children && n.children.length > 0;
|
|
1493
|
+
var isExpanded = state.expandedNodes.has(n.path);
|
|
1494
|
+
var initials = getAgentInitials(n.name);
|
|
1495
|
+
var emoji = n.avatar_emoji || '';
|
|
1496
|
+
var avatarContent = emoji || initials;
|
|
1497
|
+
var colorStyle = n.avatar_color ? 'background:' + n.avatar_color : '';
|
|
1498
|
+
var colorClass = n.avatar_color ? '' : getAgentColorClass(n.name);
|
|
1499
|
+
// 模型名称:优先使用 model_info.name,其次 model 字段
|
|
1500
|
+
var modelDisplayName = '';
|
|
1501
|
+
if (n.model_info && n.model_info.name) {
|
|
1502
|
+
modelDisplayName = n.model_info.name;
|
|
1503
|
+
} else if (n.model) {
|
|
1504
|
+
modelDisplayName = n.model.split('/').pop();
|
|
1505
|
+
} else if (n.model_id) {
|
|
1506
|
+
var found = state.modelsLibrary.find(function(m) { return m.id === n.model_id; });
|
|
1507
|
+
if (found) modelDisplayName = found.name || found.id;
|
|
1508
|
+
else modelDisplayName = n.model_id;
|
|
1509
|
+
}
|
|
1510
|
+
// 平台图标
|
|
1511
|
+
var platformBadge = '';
|
|
1512
|
+
if (n.platform && PLATFORM_ICONS[n.platform]) {
|
|
1513
|
+
platformBadge = '<span class="platform-badge" title="' + escapeHtml(PLATFORM_LABELS[n.platform] || n.platform) + '">' + PLATFORM_ICONS[n.platform] + '</span>';
|
|
1514
|
+
}
|
|
1515
|
+
var execMode = n.execution_mode || 'sandbox';
|
|
1516
|
+
var execLabel = execMode === 'local' ? '本机' : '沙盒';
|
|
1517
|
+
var execClass = execMode === 'local' ? 'local' : 'sandbox';
|
|
1518
|
+
var avatarSize = depth > 0 ? 'width:32px;height:32px;font-size:13px' : '';
|
|
1519
|
+
var dotSize = depth > 0 ? 'width:9px;height:9px' : '';
|
|
1520
|
+
|
|
1521
|
+
html += '<div class="agent-tree-node" data-depth="' + depth + '" data-path="' + escapeHtml(n.path) + '">';
|
|
1522
|
+
html += '<div class="agent-card ' + (isActive ? 'active' : '') + '" onclick="selectAgent(\'' + escapeHtml(n.path) + '\')">';
|
|
1523
|
+
if (hasChildren) {
|
|
1524
|
+
html += '<button class="agent-tree-toggle ' + (isExpanded ? 'expanded' : '') + '" onclick="event.stopPropagation();toggleTreeNode(\'' + escapeHtml(n.path) + '\')" title="展开/折叠">▶</button>';
|
|
1525
|
+
} else {
|
|
1526
|
+
html += '<span style="width:18px;flex-shrink:0"></span>';
|
|
1527
|
+
}
|
|
1528
|
+
html += '<div class="agent-avatar ' + colorClass + '" style="' + colorStyle + avatarSize + '">' + avatarContent + '<span class="status-dot" style="' + dotSize + '"></span></div>';
|
|
1529
|
+
html += '<div class="agent-info"><div class="agent-name">' + escapeHtml(n.name);
|
|
1530
|
+
if (modelDisplayName) html += '<span class="model-tag">' + escapeHtml(modelDisplayName) + '</span>';
|
|
1531
|
+
if (platformBadge) html += platformBadge;
|
|
1532
|
+
html += '<span class="exec-badge ' + execClass + '" title="执行模式: ' + execLabel + '">' + execLabel + '</span>';
|
|
1533
|
+
html += '</div>';
|
|
1534
|
+
if (n.description) html += '<div class="agent-desc">' + escapeHtml(n.description) + '</div>';
|
|
1535
|
+
html += '</div>';
|
|
1536
|
+
html += '<button class="agent-settings-btn" onclick="event.stopPropagation();showEditAgentModal(\'' + escapeHtml(n.path) + '\')" title="设置"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg></button>';
|
|
1537
|
+
html += '<button class="agent-add-child-btn" onclick="event.stopPropagation();showCreateChildModal(\'' + escapeHtml(n.path) + '\')" title="添加子 Agent">+</button>';
|
|
1538
|
+
html += '</div>';
|
|
1539
|
+
if (hasChildren && isExpanded) {
|
|
1540
|
+
html += '<div class="agent-tree-children">' + renderTreeNodes(n.children, depth + 1) + '</div>';
|
|
1541
|
+
}
|
|
1542
|
+
html += '</div>';
|
|
1543
|
+
}
|
|
1544
|
+
return html;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function toggleTreeNode(path) {
|
|
1548
|
+
if (state.expandedNodes.has(path)) {
|
|
1549
|
+
state.expandedNodes.delete(path);
|
|
1550
|
+
} else {
|
|
1551
|
+
state.expandedNodes.add(path);
|
|
1552
|
+
}
|
|
1553
|
+
renderAgentTree();
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
async function selectAgent(agentPath) {
|
|
1557
|
+
if (agentPath === state.activeAgent) return;
|
|
1558
|
+
state.activeAgent = agentPath;
|
|
1559
|
+
state.activeSessionId = null;
|
|
1560
|
+
state.messages = [];
|
|
1561
|
+
var parts = agentPath.split('/');
|
|
1562
|
+
var cumPath = '';
|
|
1563
|
+
for (var i = 0; i < parts.length; i++) {
|
|
1564
|
+
cumPath = cumPath ? cumPath + '/' + parts[i] : parts[i];
|
|
1565
|
+
state.expandedNodes.add(cumPath);
|
|
1566
|
+
}
|
|
1567
|
+
renderAgentTree();
|
|
1568
|
+
updateActiveAgentBadge();
|
|
1569
|
+
await loadSessions();
|
|
1570
|
+
var details = await loadAgentDetails(agentPath);
|
|
1571
|
+
updateWelcomeCard(agentPath, details);
|
|
1572
|
+
document.getElementById('headerTitle').textContent = '新对话';
|
|
1573
|
+
renderMessages();
|
|
1574
|
+
document.getElementById('userInput').focus();
|
|
1575
|
+
// Reload task plan if in exec mode
|
|
1576
|
+
if (state.chatMode === 'exec') loadTaskPlan();
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function updateActiveAgentBadge() {
|
|
1580
|
+
var badge = document.getElementById('activeAgentBadge');
|
|
1581
|
+
var avatar = document.getElementById('badgeAvatar');
|
|
1582
|
+
var nameEl = document.getElementById('badgeName');
|
|
1583
|
+
var agent = findAgentByPath(state.activeAgent);
|
|
1584
|
+
if (agent) {
|
|
1585
|
+
var emoji = agent.avatar_emoji || '';
|
|
1586
|
+
avatar.textContent = emoji || getAgentInitials(agent.name);
|
|
1587
|
+
avatar.style.background = agent.avatar_color || getAgentGradient(agent.name);
|
|
1588
|
+
avatar.style.borderRadius = '50%';
|
|
1589
|
+
nameEl.textContent = agent.path;
|
|
1590
|
+
badge.style.display = 'inline-flex';
|
|
1591
|
+
} else {
|
|
1592
|
+
badge.style.display = 'none';
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function updateWelcomeCard(agentPath, details) {
|
|
1597
|
+
var agent = findAgentByPath(agentPath);
|
|
1598
|
+
var emojiEl = document.getElementById('welcomeEmoji');
|
|
1599
|
+
var titleEl = document.getElementById('welcomeTitle');
|
|
1600
|
+
var subEl = document.getElementById('welcomeSubtitle');
|
|
1601
|
+
if (agent) {
|
|
1602
|
+
emojiEl.textContent = agent.avatar_emoji || '👋';
|
|
1603
|
+
var execMode = agent.execution_mode || 'sandbox';
|
|
1604
|
+
var execLabel = execMode === 'local' ? '🖥️ 本机模式' : '📦 沙盒模式';
|
|
1605
|
+
titleEl.textContent = '你好,我是 ' + agent.path;
|
|
1606
|
+
var desc = agent.description || (details && details.identity) || '你的 AI 助手';
|
|
1607
|
+
subEl.textContent = desc + ' (' + execLabel + ')';
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
function toggleAgentPanel() {
|
|
1612
|
+
const panel = document.getElementById('agentPanel');
|
|
1613
|
+
const floatBtn = document.getElementById('agentExpandFloat');
|
|
1614
|
+
state.agentPanelOpen = !state.agentPanelOpen;
|
|
1615
|
+
panel.classList.toggle('collapsed', !state.agentPanelOpen);
|
|
1616
|
+
floatBtn.classList.toggle('hidden', state.agentPanelOpen);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// ── Agent Create/Edit Modal ──
|
|
1620
|
+
function showCreateAgentModal() {
|
|
1621
|
+
showAgentModal(null, null);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
function showCreateChildModal(parentPath) {
|
|
1625
|
+
showAgentModal(null, parentPath);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
async function showEditAgentModal(agentPath) {
|
|
1629
|
+
// 刷新模型库以确保下拉框最新
|
|
1630
|
+
await loadModels();
|
|
1631
|
+
// 清除缓存以获取最新数据
|
|
1632
|
+
delete state.agentDetails[agentPath];
|
|
1633
|
+
var details = await loadAgentDetails(agentPath);
|
|
1634
|
+
var agent = findAgentByPath(agentPath);
|
|
1635
|
+
var merged = agent ? Object.assign({}, agent, details) : details;
|
|
1636
|
+
showAgentModal(agentPath, null, merged);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function showAgentModal(editPath, parentPath, data) {
|
|
1640
|
+
data = data || {};
|
|
1641
|
+
var isEdit = !!editPath;
|
|
1642
|
+
var isChild = !!parentPath && !isEdit;
|
|
1643
|
+
var path = data.path || editPath || '';
|
|
1644
|
+
var name = data.name || '';
|
|
1645
|
+
var model = data.model || '';
|
|
1646
|
+
var model_id = data.model_id || '';
|
|
1647
|
+
var description = data.description || '';
|
|
1648
|
+
var soul = data.soul || '';
|
|
1649
|
+
var identity = data.identity || '';
|
|
1650
|
+
var avatar_emoji = data.avatar_emoji || '';
|
|
1651
|
+
var execution_mode = data.execution_mode || (isEdit ? 'sandbox' : (parentPath ? 'sandbox' : 'local'));
|
|
1652
|
+
var platform = data.platform || '';
|
|
1653
|
+
var platform_token = data.platform_token || '';
|
|
1654
|
+
var platform_app_id = data.platform_app_id || '';
|
|
1655
|
+
var platform_app_secret = data.platform_app_secret || '';
|
|
1656
|
+
|
|
1657
|
+
var emojis = ['🤖','🧠','💡','🎯','🔮','⚡','🌟','🎨','📝','🔧','🎭','🌐','🦊','🐱','🐶','🦁'];
|
|
1658
|
+
var title = isEdit ? '⚙️ 编辑 Agent' : (isChild ? '👶 创建子 Agent (' + escapeHtml(parentPath) + ')' : '✨ 创建新 Agent');
|
|
1659
|
+
var subtitle = isEdit ? '修改 ' + escapeHtml(editPath) + ' 的配置' : (isChild ? '在 ' + escapeHtml(parentPath) + ' 下创建子 Agent' : '配置一个新的顶级 AI Agent');
|
|
1660
|
+
|
|
1661
|
+
// 构建模型选择下拉框
|
|
1662
|
+
var modelsOptions = '<option value="">系统默认</option>';
|
|
1663
|
+
var enabledModels = state.modelsLibrary.filter(function(m) { return m.enabled; });
|
|
1664
|
+
for (var mi = 0; mi < enabledModels.length; mi++) {
|
|
1665
|
+
var m = enabledModels[mi];
|
|
1666
|
+
var selected = (model_id === m.id) ? ' selected' : '';
|
|
1667
|
+
modelsOptions += '<option value="' + escapeHtml(m.id) + '"' + selected + '>' + escapeHtml(m.name) + ' (' + escapeHtml(m.provider) + ')</option>';
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
var container = document.getElementById('agentModalContainer');
|
|
1671
|
+
container.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeAgentModal()"><div class="agent-modal">'
|
|
1672
|
+
+ '<h3>' + title + '</h3>'
|
|
1673
|
+
+ '<p class="modal-subtitle">' + subtitle + '</p>'
|
|
1674
|
+
+ '<div class="agent-form-group"><label>名称 *</label>'
|
|
1675
|
+
+ '<input type="text" id="agentFormName" placeholder="例如: translator, coder, tutor" value="' + escapeHtml(name) + '" ' + (isEdit ? 'readonly style="opacity:.6;cursor:not-allowed"' : '') + '>'
|
|
1676
|
+
+ '<div class="hint">' + (isEdit ? '名称不可修改' : '使用英文小写,可用连字符') + '</div></div>'
|
|
1677
|
+
+ '<div class="agent-form-group"><label>模型</label>'
|
|
1678
|
+
+ '<select id="agentFormModelId" onchange="onModelSelect()">' + modelsOptions + '</select>'
|
|
1679
|
+
+ '<div class="hint" id="modelHint">从系统模型库中选择,留空使用系统默认模型</div></div>'
|
|
1680
|
+
+ '<div class="agent-form-group"><label>头像</label>'
|
|
1681
|
+
+ '<div class="agent-avatar-picker" id="avatarPicker">'
|
|
1682
|
+
+ emojis.map(function(e) { return '<div class="agent-avatar-option ' + (avatar_emoji === e ? 'selected' : '') + '" onclick="pickAvatar(this,\'' + e + '\')" data-emoji="' + e + '">' + e + '</div>'; }).join('')
|
|
1683
|
+
+ '</div><input type="hidden" id="agentFormEmoji" value="' + escapeHtml(avatar_emoji) + '"></div>'
|
|
1684
|
+
+ '<div class="agent-form-group"><label>简介</label>'
|
|
1685
|
+
+ '<input type="text" id="agentFormDesc" placeholder="简短描述这个 Agent 的用途" value="' + escapeHtml(description) + '" maxlength="100">'
|
|
1686
|
+
+ '<div class="hint">一句话描述,最多 100 字</div></div>'
|
|
1687
|
+
+ '<div class="agent-form-group"><label>执行模式</label>'
|
|
1688
|
+
+ '<select id="agentFormExecMode">'
|
|
1689
|
+
+ '<option value="local"' + (execution_mode === 'local' ? ' selected' : '') + '>🖥️ 本机执行 (无隔离,功能完整)</option>'
|
|
1690
|
+
+ '<option value="sandbox"' + (execution_mode === 'sandbox' ? ' selected' : '') + '>📦 沙盒执行 (Docker 隔离,更安全)</option>'
|
|
1691
|
+
+ '</select>'
|
|
1692
|
+
+ '<div class="hint">默认 Agent 为本机模式,其他默认沙盒模式</div></div>'
|
|
1693
|
+
+ '<div class="agent-form-section"><div class="agent-form-section-title">🔗 聊天平台绑定</div>'
|
|
1694
|
+
+ '<div class="agent-form-group"><label>聊天平台</label>'
|
|
1695
|
+
+ '<select id="agentFormPlatform" onchange="onPlatformSelect()">'
|
|
1696
|
+
+ '<option value=""' + (!platform ? ' selected' : '') + '>无 (本地 Web)</option>'
|
|
1697
|
+
+ '<option value="telegram"' + (platform === 'telegram' ? ' selected' : '') + '>✈️ Telegram</option>'
|
|
1698
|
+
+ '<option value="discord"' + (platform === 'discord' ? ' selected' : '') + '>🎮 Discord</option>'
|
|
1699
|
+
+ '<option value="feishu"' + (platform === 'feishu' ? ' selected' : '') + '>🐦 飞书</option>'
|
|
1700
|
+
+ '<option value="qq"' + (platform === 'qq' ? ' selected' : '') + '>🐧 QQ</option>'
|
|
1701
|
+
+ '<option value="wechat"' + (platform === 'wechat' ? ' selected' : '') + '>💬 微信</option>'
|
|
1702
|
+
+ '</select>'
|
|
1703
|
+
+ '<div class="hint">选择 Agent 对接的聊天平台</div></div>'
|
|
1704
|
+
+ '<div class="agent-form-group" id="platformTokenGroup" style="display:' + (platform ? '' : 'none') + '"><label>平台 Token</label>'
|
|
1705
|
+
+ '<input type="text" id="agentFormPlatformToken" placeholder="Bot Token" value="' + escapeHtml(platform_token) + '">'
|
|
1706
|
+
+ '<div class="hint">聊天平台的 Bot Token</div></div>'
|
|
1707
|
+
+ '<div class="agent-form-group" id="platformAppIdGroup" style="display:' + (platform === 'feishu' ? '' : 'none') + '"><label>App ID</label>'
|
|
1708
|
+
+ '<input type="text" id="agentFormPlatformAppId" placeholder="飞书 App ID" value="' + escapeHtml(platform_app_id) + '">'
|
|
1709
|
+
+ '<div class="hint">飞书应用的 App ID</div></div>'
|
|
1710
|
+
+ '<div class="agent-form-group" id="platformAppSecretGroup" style="display:' + (platform === 'feishu' ? '' : 'none') + '"><label>App Secret</label>'
|
|
1711
|
+
+ '<input type="text" id="agentFormPlatformAppSecret" placeholder="飞书 App Secret" value="' + escapeHtml(platform_app_secret) + '">'
|
|
1712
|
+
+ '<div class="hint">飞书应用的 App Secret</div></div>'
|
|
1713
|
+
+ '</div>'
|
|
1714
|
+
+ '<div class="agent-form-group"><label>Identity (身份)</label>'
|
|
1715
|
+
+ '<textarea id="agentFormIdentity" placeholder="你是一个... 你擅长... 你的风格是..." rows="3">' + escapeHtml(identity) + '</textarea>'
|
|
1716
|
+
+ '<div class="hint">定义 Agent 的身份和角色</div></div>'
|
|
1717
|
+
+ '<div class="agent-form-group"><label>Soul (灵魂/人格)</label>'
|
|
1718
|
+
+ '<textarea id="agentFormSoul" placeholder="核心指令、行为准则、约束..." rows="4">' + escapeHtml(soul) + '</textarea>'
|
|
1719
|
+
+ '<div class="hint">深层人格定义,影响 Agent 的思维方式</div></div>'
|
|
1720
|
+
+ '<div class="agent-modal-actions">'
|
|
1721
|
+
+ (isEdit ? '<button class="agent-modal-btn agent-modal-btn-delete" onclick="deleteAgent(\'' + escapeHtml(editPath) + '\')">🗑️ 删除</button>' : '')
|
|
1722
|
+
+ '<button class="agent-modal-btn agent-modal-btn-cancel" onclick="closeAgentModal()">取消</button>'
|
|
1723
|
+
+ '<button class="agent-modal-btn agent-modal-btn-primary" onclick="saveAgentModal(\'' + escapeHtml(editPath || '') + '\',\'' + escapeHtml(parentPath || '') + '\')">'
|
|
1724
|
+
+ (isEdit ? '保存修改' : (isChild ? '创建子 Agent' : '创建 Agent'))
|
|
1725
|
+
+ '</button></div></div></div>';
|
|
1726
|
+
// Store context in hidden field
|
|
1727
|
+
var hidden = document.createElement('input');
|
|
1728
|
+
hidden.type = 'hidden';
|
|
1729
|
+
hidden.id = 'agentFormContext';
|
|
1730
|
+
hidden.value = JSON.stringify({editPath: editPath || '', parentPath: parentPath || '', isEdit: isEdit, isChild: isChild});
|
|
1731
|
+
container.querySelector('.agent-modal').appendChild(hidden);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function pickAvatar(el, emoji) {
|
|
1735
|
+
document.querySelectorAll('.agent-avatar-option').forEach(function(e) { e.classList.remove('selected'); });
|
|
1736
|
+
el.classList.add('selected');
|
|
1737
|
+
document.getElementById('agentFormEmoji').value = emoji;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
function onModelSelect() {
|
|
1741
|
+
var sel = document.getElementById('agentFormModelId');
|
|
1742
|
+
var hint = document.getElementById('modelHint');
|
|
1743
|
+
if (!sel || !hint) return;
|
|
1744
|
+
var modelId = sel.value;
|
|
1745
|
+
if (!modelId) {
|
|
1746
|
+
hint.textContent = '从系统模型库中选择,留空使用系统默认模型';
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
var found = state.modelsLibrary.find(function(m) { return m.id === modelId; });
|
|
1750
|
+
if (found) {
|
|
1751
|
+
var details = found.provider;
|
|
1752
|
+
if (found.base_url) details += ' | ' + found.base_url;
|
|
1753
|
+
if (found.model && found.model !== found.id) details += ' | 模型: ' + found.model;
|
|
1754
|
+
hint.textContent = '提供商: ' + details;
|
|
1755
|
+
} else {
|
|
1756
|
+
hint.textContent = '模型: ' + modelId;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function onPlatformSelect() {
|
|
1761
|
+
var sel = document.getElementById('agentFormPlatform');
|
|
1762
|
+
var platform = sel ? sel.value : '';
|
|
1763
|
+
var tokenGroup = document.getElementById('platformTokenGroup');
|
|
1764
|
+
var appIdGroup = document.getElementById('platformAppIdGroup');
|
|
1765
|
+
var appSecretGroup = document.getElementById('platformAppSecretGroup');
|
|
1766
|
+
if (tokenGroup) tokenGroup.style.display = platform ? '' : 'none';
|
|
1767
|
+
if (appIdGroup) appIdGroup.style.display = (platform === 'feishu') ? '' : 'none';
|
|
1768
|
+
if (appSecretGroup) appSecretGroup.style.display = (platform === 'feishu') ? '' : 'none';
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
function closeAgentModal() {
|
|
1772
|
+
document.getElementById('agentModalContainer').innerHTML = '';
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
async function saveAgentModal(editPath, parentPath) {
|
|
1776
|
+
var ctxEl = document.getElementById('agentFormContext');
|
|
1777
|
+
var ctx = ctxEl ? JSON.parse(ctxEl.value) : {};
|
|
1778
|
+
var isEdit = ctx.isEdit;
|
|
1779
|
+
var isChild = ctx.isChild;
|
|
1780
|
+
|
|
1781
|
+
var nameEl = document.getElementById('agentFormName');
|
|
1782
|
+
var modelIdEl = document.getElementById('agentFormModelId');
|
|
1783
|
+
var descEl = document.getElementById('agentFormDesc');
|
|
1784
|
+
var identityEl = document.getElementById('agentFormIdentity');
|
|
1785
|
+
var soulEl = document.getElementById('agentFormSoul');
|
|
1786
|
+
var emojiEl = document.getElementById('agentFormEmoji');
|
|
1787
|
+
var execEl = document.getElementById('agentFormExecMode');
|
|
1788
|
+
var platformEl = document.getElementById('agentFormPlatform');
|
|
1789
|
+
var platformTokenEl = document.getElementById('agentFormPlatformToken');
|
|
1790
|
+
var platformAppIdEl = document.getElementById('agentFormPlatformAppId');
|
|
1791
|
+
var platformAppSecretEl = document.getElementById('agentFormPlatformAppSecret');
|
|
1792
|
+
|
|
1793
|
+
var name = nameEl.value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
1794
|
+
if (!name) { toast('请输入有效的 Agent 名称', 'error'); return; }
|
|
1795
|
+
if (!isEdit && name.length < 2) { toast('名称至少 2 个字符', 'error'); return; }
|
|
1796
|
+
|
|
1797
|
+
var payload = {
|
|
1798
|
+
name: name,
|
|
1799
|
+
description: descEl.value.trim(),
|
|
1800
|
+
identity: identityEl.value.trim(),
|
|
1801
|
+
soul: soulEl.value.trim(),
|
|
1802
|
+
avatar_emoji: emojiEl.value,
|
|
1803
|
+
execution_mode: execEl.value,
|
|
1804
|
+
};
|
|
1805
|
+
// 模型选择
|
|
1806
|
+
if (modelIdEl && modelIdEl.value) {
|
|
1807
|
+
payload.model_id = modelIdEl.value;
|
|
1808
|
+
}
|
|
1809
|
+
// 平台绑定
|
|
1810
|
+
if (platformEl && platformEl.value) {
|
|
1811
|
+
payload.platform = platformEl.value;
|
|
1812
|
+
if (platformTokenEl && platformTokenEl.value.trim()) {
|
|
1813
|
+
payload.platform_token = platformTokenEl.value.trim();
|
|
1814
|
+
}
|
|
1815
|
+
if (platformAppIdEl && platformAppIdEl.value.trim()) {
|
|
1816
|
+
payload.platform_app_id = platformAppIdEl.value.trim();
|
|
1817
|
+
}
|
|
1818
|
+
if (platformAppSecretEl && platformAppSecretEl.value.trim()) {
|
|
1819
|
+
payload.platform_app_secret = platformAppSecretEl.value.trim();
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
try {
|
|
1824
|
+
if (isEdit) {
|
|
1825
|
+
await api('/api/agents/' + encodeURIComponent(editPath), {
|
|
1826
|
+
method: 'PUT',
|
|
1827
|
+
body: JSON.stringify(payload),
|
|
1828
|
+
});
|
|
1829
|
+
toast('Agent 已更新', 'success');
|
|
1830
|
+
} else if (isChild) {
|
|
1831
|
+
var resp = await api('/api/agents/' + encodeURIComponent(parentPath) + '/children', {
|
|
1832
|
+
method: 'POST',
|
|
1833
|
+
body: JSON.stringify(payload),
|
|
1834
|
+
});
|
|
1835
|
+
toast('子 Agent 已创建: ' + (resp.path || name), 'success');
|
|
1836
|
+
} else {
|
|
1837
|
+
await api('/api/agents', {
|
|
1838
|
+
method: 'POST',
|
|
1839
|
+
body: JSON.stringify(payload),
|
|
1840
|
+
});
|
|
1841
|
+
toast('Agent 已创建', 'success');
|
|
1842
|
+
}
|
|
1843
|
+
closeAgentModal();
|
|
1844
|
+
await loadAgents();
|
|
1845
|
+
} catch (e) {
|
|
1846
|
+
toast('保存失败: ' + e.message, 'error');
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
async function deleteAgent(agentPath) {
|
|
1851
|
+
if (agentPath === 'default') { toast('不能删除默认 Agent', 'error'); return; }
|
|
1852
|
+
if (!confirm('确定删除 Agent "' + agentPath + '" 及其所有子 Agent?')) return;
|
|
1853
|
+
try {
|
|
1854
|
+
await api('/api/agents/' + encodeURIComponent(agentPath), { method: 'DELETE' });
|
|
1855
|
+
toast('Agent 已删除', 'success');
|
|
1856
|
+
closeAgentModal();
|
|
1857
|
+
if (state.activeAgent === agentPath || state.activeAgent.startsWith(agentPath + '/')) {
|
|
1858
|
+
state.activeAgent = 'default';
|
|
1859
|
+
}
|
|
1860
|
+
await loadAgents();
|
|
1861
|
+
} catch (e) {
|
|
1862
|
+
toast('删除失败: ' + e.message, 'error');
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// ══════════════════════════════════════════════════════
|
|
1867
|
+
// ── Sessions (scoped to active agent) ──
|
|
1868
|
+
// ══════════════════════════════════════════════════════
|
|
1869
|
+
|
|
1870
|
+
async function loadSessions() {
|
|
1871
|
+
try {
|
|
1872
|
+
const url = `/api/sessions?agent=${encodeURIComponent(state.activeAgent)}`;
|
|
1873
|
+
const data = await api(url);
|
|
1874
|
+
state.sessions = (data || []).map(s => ({
|
|
1875
|
+
id: s.id,
|
|
1876
|
+
name: formatSessionName(s.id),
|
|
1877
|
+
messages: s.messages || 0,
|
|
1878
|
+
last: s.last || '',
|
|
1879
|
+
}));
|
|
1880
|
+
// Cache per agent
|
|
1881
|
+
state.agentSessions[state.activeAgent] = [...state.sessions];
|
|
1882
|
+
} catch (e) {
|
|
1883
|
+
// Offline - try cache
|
|
1884
|
+
state.sessions = state.agentSessions[state.activeAgent] || [];
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// Add "new chat" option
|
|
1888
|
+
if (state.sessions.length === 0 || state.sessions[0].id !== '__new__') {
|
|
1889
|
+
state.sessions.unshift({ id: '__new__', name: '新对话', messages: 0, last: '' });
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
renderSessions();
|
|
1893
|
+
|
|
1894
|
+
// Auto-select most recent session if none selected
|
|
1895
|
+
if (!state.activeSessionId && state.sessions.length > 1) {
|
|
1896
|
+
selectSession(state.sessions[1].id);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
function formatSessionName(id) {
|
|
1901
|
+
if (id.startsWith('web_')) return id.replace('web_', '').replace(/_/g, ' ');
|
|
1902
|
+
if (id.startsWith('cli_')) return 'CLI: ' + id.replace('cli_', '');
|
|
1903
|
+
// Strip agent prefix if present (e.g., "default_web_2024..." -> "web_2024...")
|
|
1904
|
+
return id.length > 20 ? id.slice(0, 20) + '...' : id;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
function renderSessions(filter = '') {
|
|
1908
|
+
const list = document.getElementById('sessionList');
|
|
1909
|
+
const fl = filter.toLowerCase();
|
|
1910
|
+
const filtered = state.sessions.filter(s =>
|
|
1911
|
+
!fl || s.name.toLowerCase().includes(fl) || s.id.toLowerCase().includes(fl)
|
|
1912
|
+
);
|
|
1913
|
+
|
|
1914
|
+
list.innerHTML = filtered.map(s => `
|
|
1915
|
+
<div class="session-item ${s.id === state.activeSessionId ? 'active' : ''}"
|
|
1916
|
+
onclick="selectSession('${escapeHtml(s.id)}')" title="${escapeHtml(s.id)}">
|
|
1917
|
+
<div class="session-icon">${s.id === '__new__' ? '✨' : '💬'}</div>
|
|
1918
|
+
<div class="session-info">
|
|
1919
|
+
<div class="session-name">${escapeHtml(s.name)}</div>
|
|
1920
|
+
<div class="session-preview">${s.messages > 0 ? s.messages + ' 条消息' : '暂无消息'}</div>
|
|
1921
|
+
</div>
|
|
1922
|
+
${s.id !== '__new__' ? `
|
|
1923
|
+
<button class="session-delete" onclick="event.stopPropagation();deleteSession('${escapeHtml(s.id)}')" title="删除">✕</button>
|
|
1924
|
+
` : ''}
|
|
1925
|
+
</div>
|
|
1926
|
+
`).join('');
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function filterSessions() {
|
|
1930
|
+
renderSessions(document.getElementById('searchInput').value);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function newChat() {
|
|
1934
|
+
state.activeSessionId = '__new__';
|
|
1935
|
+
state.messages = [];
|
|
1936
|
+
document.getElementById('headerTitle').textContent = '新对话';
|
|
1937
|
+
const agent = findAgentByPath(state.activeAgent);
|
|
1938
|
+
if (agent) {
|
|
1939
|
+
document.getElementById('welcomeEmoji').textContent = agent.avatar_emoji || '👋';
|
|
1940
|
+
document.getElementById('welcomeTitle').textContent = `你好,我是 ${agent.name}`;
|
|
1941
|
+
document.getElementById('welcomeSubtitle').textContent = agent.description || '你的 AI 助手';
|
|
1942
|
+
}
|
|
1943
|
+
document.getElementById('welcomeCard').style.display = '';
|
|
1944
|
+
renderMessages();
|
|
1945
|
+
renderSessions();
|
|
1946
|
+
document.getElementById('userInput').focus();
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
async function selectSession(id) {
|
|
1950
|
+
if (id === '__new__') { newChat(); return; }
|
|
1951
|
+
|
|
1952
|
+
state.activeSessionId = id;
|
|
1953
|
+
document.getElementById('headerTitle').textContent = formatSessionName(id);
|
|
1954
|
+
document.getElementById('welcomeCard').style.display = 'none';
|
|
1955
|
+
renderSessions();
|
|
1956
|
+
|
|
1957
|
+
// Load messages
|
|
1958
|
+
try {
|
|
1959
|
+
const data = await api(`/api/sessions/${encodeURIComponent(id)}/messages`);
|
|
1960
|
+
state.messages = (data || []).map(m => ({
|
|
1961
|
+
role: m.role,
|
|
1962
|
+
content: m.content,
|
|
1963
|
+
time: m.time || '',
|
|
1964
|
+
}));
|
|
1965
|
+
} catch (e) {
|
|
1966
|
+
state.messages = [];
|
|
1967
|
+
toast('加载消息失败', 'error');
|
|
1968
|
+
}
|
|
1969
|
+
renderMessages();
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
async function deleteSession(id) {
|
|
1973
|
+
if (!confirm('确定删除此对话?')) return;
|
|
1974
|
+
try {
|
|
1975
|
+
await api(`/api/sessions/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
1976
|
+
if (state.activeSessionId === id) newChat();
|
|
1977
|
+
await loadSessions();
|
|
1978
|
+
toast('对话已删除', 'success');
|
|
1979
|
+
} catch (e) {
|
|
1980
|
+
toast('删除失败', 'error');
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
function clearCurrentChat() {
|
|
1985
|
+
if (!state.activeSessionId || state.activeSessionId === '__new__') return;
|
|
1986
|
+
if (!confirm('确定清空当前对话?')) return;
|
|
1987
|
+
api(`/api/sessions/${encodeURIComponent(state.activeSessionId)}`, { method: 'DELETE' })
|
|
1988
|
+
.then(() => {
|
|
1989
|
+
state.messages = [];
|
|
1990
|
+
renderMessages();
|
|
1991
|
+
toast('对话已清空', 'success');
|
|
1992
|
+
loadSessions();
|
|
1993
|
+
})
|
|
1994
|
+
.catch(() => toast('清空失败', 'error'));
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// ── Messages ──
|
|
1998
|
+
function renderMessages() {
|
|
1999
|
+
if (currentView === 'group') {
|
|
2000
|
+
renderGroupMessages();
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const container = document.getElementById('messagesInner');
|
|
2004
|
+
if (state.messages.length === 0) {
|
|
2005
|
+
// Show welcome card
|
|
2006
|
+
const agent = findAgentByPath(state.activeAgent);
|
|
2007
|
+
container.innerHTML = `
|
|
2008
|
+
<div class="welcome-card" id="welcomeCard">
|
|
2009
|
+
<h2><span class="emoji" id="welcomeEmoji">${agent ? (agent.avatar_emoji || '👋') : '👋'}</span>
|
|
2010
|
+
<span id="welcomeTitle">你好,我是 ${agent ? escapeHtml(agent.name) : 'MyAgent'}</span></h2>
|
|
2011
|
+
<p class="subtitle" id="welcomeSubtitle">${agent ? escapeHtml(agent.description || '你的 AI 助手') : '本地桌面端执行型 AI 助手 — 我可以帮你写代码、搜索信息、管理文件等'}</p>
|
|
2012
|
+
<div class="capabilities">
|
|
2013
|
+
<div class="capability"><span class="cap-icon">💻</span> 代码编写与执行</div>
|
|
2014
|
+
<div class="capability"><span class="cap-icon">🔍</span> 网络搜索与阅读</div>
|
|
2015
|
+
<div class="capability"><span class="cap-icon">📁</span> 文件管理与操作</div>
|
|
2016
|
+
<div class="capability"><span class="cap-icon">🧠</span> 长期记忆系统</div>
|
|
2017
|
+
<div class="capability"><span class="cap-icon">🌐</span> 多平台接入</div>
|
|
2018
|
+
<div class="capability"><span class="cap-icon">🛠️</span> 系统命令执行</div>
|
|
2019
|
+
</div>
|
|
2020
|
+
</div>`;
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
const welcomeCard = document.getElementById('welcomeCard');
|
|
2025
|
+
if (welcomeCard) welcomeCard.style.display = 'none';
|
|
2026
|
+
|
|
2027
|
+
const agent = findAgentByPath(state.activeAgent);
|
|
2028
|
+
const botEmoji = agent ? (agent.avatar_emoji || '🤖') : '🤖';
|
|
2029
|
+
|
|
2030
|
+
let html = '';
|
|
2031
|
+
for (const msg of state.messages) {
|
|
2032
|
+
const isUser = msg.role === 'user';
|
|
2033
|
+
const avatar = isUser ? '👤' : botEmoji;
|
|
2034
|
+
const content = renderMarkdown(msg.content);
|
|
2035
|
+
html += `
|
|
2036
|
+
<div class="message-row ${msg.role}">
|
|
2037
|
+
<div class="message-avatar">${avatar}</div>
|
|
2038
|
+
<div>
|
|
2039
|
+
<div class="message-bubble">${content}</div>
|
|
2040
|
+
${msg.time ? `<div class="message-time">${formatTime(msg.time)}</div>` : ''}
|
|
2041
|
+
</div>
|
|
2042
|
+
</div>`;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
// Keep welcome card in DOM but hidden
|
|
2046
|
+
container.innerHTML = html +
|
|
2047
|
+
'<div class="welcome-card" id="welcomeCard" style="display:none"></div>';
|
|
2048
|
+
|
|
2049
|
+
scrollToBottom();
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function renderMarkdown(text) {
|
|
2053
|
+
if (!text) return '';
|
|
2054
|
+
// Code blocks
|
|
2055
|
+
text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
2056
|
+
return `<pre><code>${escapeHtml(code.trim())}</code></pre>`;
|
|
2057
|
+
});
|
|
2058
|
+
// Inline code
|
|
2059
|
+
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
2060
|
+
// Bold
|
|
2061
|
+
text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
2062
|
+
// Italic
|
|
2063
|
+
text = text.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
2064
|
+
// Lists
|
|
2065
|
+
text = text.replace(/^[-•]\s+(.+)$/gm, '<li>$1</li>');
|
|
2066
|
+
text = text.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');
|
|
2067
|
+
text = text.replace(/^\d+\.\s+(.+)$/gm, '<li>$1</li>');
|
|
2068
|
+
// Blockquote
|
|
2069
|
+
text = text.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>');
|
|
2070
|
+
// Paragraphs
|
|
2071
|
+
text = text.replace(/\n\n+/g, '</p><p>');
|
|
2072
|
+
text = text.replace(/\n/g, '<br>');
|
|
2073
|
+
text = '<p>' + text + '</p>';
|
|
2074
|
+
// Clean up empty paragraphs
|
|
2075
|
+
text = text.replace(/<p>\s*<\/p>/g, '');
|
|
2076
|
+
return text;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function escapeHtml(text) {
|
|
2080
|
+
const div = document.createElement('div');
|
|
2081
|
+
div.textContent = text;
|
|
2082
|
+
return div.innerHTML;
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
function formatTime(timeStr) {
|
|
2086
|
+
if (!timeStr) return '';
|
|
2087
|
+
try {
|
|
2088
|
+
const d = new Date(timeStr);
|
|
2089
|
+
const now = new Date();
|
|
2090
|
+
const diff = now - d;
|
|
2091
|
+
if (diff < 60000) return '刚刚';
|
|
2092
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + ' 分钟前';
|
|
2093
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + ' 小时前';
|
|
2094
|
+
return d.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
2095
|
+
} catch {
|
|
2096
|
+
return timeStr;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
function scrollToBottom() {
|
|
2101
|
+
const c = document.getElementById('messagesContainer');
|
|
2102
|
+
requestAnimationFrame(() => { c.scrollTop = c.scrollHeight; });
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// ── Send Message ──
|
|
2106
|
+
async function sendMessage() {
|
|
2107
|
+
if (currentView === 'group') {
|
|
2108
|
+
return sendGroupChat();
|
|
2109
|
+
}
|
|
2110
|
+
const input = document.getElementById('userInput');
|
|
2111
|
+
const text = input.value.trim();
|
|
2112
|
+
if (!text || state.isGenerating) return;
|
|
2113
|
+
|
|
2114
|
+
// Create session if needed (incorporate agent name)
|
|
2115
|
+
let sessionId = state.activeSessionId;
|
|
2116
|
+
if (!sessionId || sessionId === '__new__') {
|
|
2117
|
+
const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
|
2118
|
+
sessionId = `${state.activeAgent}_web_${ts}`;
|
|
2119
|
+
state.activeSessionId = sessionId;
|
|
2120
|
+
document.getElementById('headerTitle').textContent = formatSessionName(sessionId);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// Add user message
|
|
2124
|
+
state.messages.push({ role: 'user', content: text, time: new Date().toISOString() });
|
|
2125
|
+
renderMessages();
|
|
2126
|
+
|
|
2127
|
+
// Clear input
|
|
2128
|
+
input.value = '';
|
|
2129
|
+
input.style.height = 'auto';
|
|
2130
|
+
document.getElementById('sendBtn').disabled = true;
|
|
2131
|
+
|
|
2132
|
+
// Show typing
|
|
2133
|
+
state.isGenerating = true;
|
|
2134
|
+
showTypingIndicator();
|
|
2135
|
+
document.getElementById('sendBtn').style.display = 'none';
|
|
2136
|
+
document.getElementById('stopBtn').style.display = '';
|
|
2137
|
+
|
|
2138
|
+
// Start execution progress polling (in exec mode)
|
|
2139
|
+
if (state.chatMode === 'exec') {
|
|
2140
|
+
startExecTimerPolling();
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
try {
|
|
2144
|
+
state.abortController = new AbortController();
|
|
2145
|
+
const data = await api('/api/chat', {
|
|
2146
|
+
method: 'POST',
|
|
2147
|
+
body: JSON.stringify({
|
|
2148
|
+
message: text,
|
|
2149
|
+
session_id: sessionId,
|
|
2150
|
+
agent_name: state.activeAgent,
|
|
2151
|
+
agent_path: state.activeAgent,
|
|
2152
|
+
mode: state.chatMode,
|
|
2153
|
+
}),
|
|
2154
|
+
signal: state.abortController.signal,
|
|
2155
|
+
});
|
|
2156
|
+
|
|
2157
|
+
// Add assistant response
|
|
2158
|
+
state.messages.push({
|
|
2159
|
+
role: 'assistant',
|
|
2160
|
+
content: data.response || '(无回复)',
|
|
2161
|
+
time: new Date().toISOString(),
|
|
2162
|
+
});
|
|
2163
|
+
|
|
2164
|
+
// Update session in list
|
|
2165
|
+
const existing = state.sessions.find(s => s.id === sessionId);
|
|
2166
|
+
if (existing) {
|
|
2167
|
+
existing.messages = (existing.messages || 0) + 2;
|
|
2168
|
+
existing.last = new Date().toISOString();
|
|
2169
|
+
} else {
|
|
2170
|
+
state.sessions.splice(1, 0, {
|
|
2171
|
+
id: sessionId,
|
|
2172
|
+
name: formatSessionName(sessionId),
|
|
2173
|
+
messages: 2,
|
|
2174
|
+
last: new Date().toISOString(),
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
// Update cache
|
|
2178
|
+
state.agentSessions[state.activeAgent] = [...state.sessions];
|
|
2179
|
+
renderSessions();
|
|
2180
|
+
} catch (e) {
|
|
2181
|
+
if (e.name === 'AbortError') {
|
|
2182
|
+
state.messages.push({ role: 'assistant', content: '⏹️ 已停止生成', time: new Date().toISOString() });
|
|
2183
|
+
} else {
|
|
2184
|
+
toast('发送失败: ' + e.message, 'error');
|
|
2185
|
+
state.messages.push({ role: 'assistant', content: '❌ 请求失败: ' + e.message, time: new Date().toISOString() });
|
|
2186
|
+
}
|
|
2187
|
+
} finally {
|
|
2188
|
+
state.isGenerating = false;
|
|
2189
|
+
state.abortController = null;
|
|
2190
|
+
hideTypingIndicator();
|
|
2191
|
+
stopExecTimerPolling();
|
|
2192
|
+
document.getElementById('sendBtn').style.display = '';
|
|
2193
|
+
document.getElementById('sendBtn').disabled = true;
|
|
2194
|
+
document.getElementById('stopBtn').style.display = 'none';
|
|
2195
|
+
renderMessages();
|
|
2196
|
+
input.focus();
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
function stopGenerating() {
|
|
2201
|
+
if (state.abortController) {
|
|
2202
|
+
state.abortController.abort();
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function stopGenerating() {
|
|
2207
|
+
if (state.abortController) {
|
|
2208
|
+
state.abortController.abort();
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// ── Execution Progress Timer ──
|
|
2213
|
+
function startExecTimerPolling() {
|
|
2214
|
+
stopExecTimerPolling();
|
|
2215
|
+
state.execTimerInterval = setInterval(updateExecTimer, 500);
|
|
2216
|
+
updateExecTimer(); // immediate first call
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
function stopExecTimerPolling() {
|
|
2220
|
+
if (state.execTimerInterval) {
|
|
2221
|
+
clearInterval(state.execTimerInterval);
|
|
2222
|
+
state.execTimerInterval = null;
|
|
2223
|
+
}
|
|
2224
|
+
// Keep timer visible for a moment to show final status, then fade out
|
|
2225
|
+
const timer = document.getElementById('execTimer');
|
|
2226
|
+
if (timer) {
|
|
2227
|
+
timer.style.opacity = '0';
|
|
2228
|
+
timer.style.transition = 'opacity .5s ease';
|
|
2229
|
+
setTimeout(() => {
|
|
2230
|
+
const t = document.getElementById('execTimer');
|
|
2231
|
+
if (t) t.remove();
|
|
2232
|
+
}, 600);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
async function updateExecTimer() {
|
|
2237
|
+
try {
|
|
2238
|
+
const data = await fetch('/api/execution/progress');
|
|
2239
|
+
const result = await data.json();
|
|
2240
|
+
renderExecTimer(result);
|
|
2241
|
+
} catch (e) {
|
|
2242
|
+
// Silently ignore polling errors
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function renderExecTimer(data) {
|
|
2247
|
+
let timer = document.getElementById('execTimer');
|
|
2248
|
+
const active = data.active || [];
|
|
2249
|
+
|
|
2250
|
+
if (active.length === 0) {
|
|
2251
|
+
if (timer) {
|
|
2252
|
+
// Show completed state briefly
|
|
2253
|
+
timer.style.opacity = '0.6';
|
|
2254
|
+
setTimeout(() => {
|
|
2255
|
+
const t = document.getElementById('execTimer');
|
|
2256
|
+
if (t) t.remove();
|
|
2257
|
+
}, 2000);
|
|
2258
|
+
}
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
// Pick the most recent running execution
|
|
2263
|
+
const exec = active.find(a => a.status === 'running') || active[active.length - 1];
|
|
2264
|
+
if (!exec) return;
|
|
2265
|
+
|
|
2266
|
+
const elapsed = exec.elapsed || 0;
|
|
2267
|
+
const timeout = exec.timeout || 0;
|
|
2268
|
+
const pct = timeout > 0 ? Math.min((elapsed / timeout) * 100, 100) : 0;
|
|
2269
|
+
const isTimeout = pct >= 95;
|
|
2270
|
+
const isWarning = pct >= 70;
|
|
2271
|
+
const langLabel = {python: 'Python', shell: 'Shell', bash: 'Bash', powershell: 'PowerShell', system: 'System'}[exec.language] || exec.language;
|
|
2272
|
+
const codePreview = exec.code_preview ? escapeHtml(exec.code_preview) : '';
|
|
2273
|
+
const statusLabel = exec.status === 'done' ? '✅ 完成' : exec.status === 'timeout' ? '⏰ 超时' : '⏳ 执行中';
|
|
2274
|
+
|
|
2275
|
+
const barClass = isTimeout ? 'danger' : isWarning ? 'warning' : '';
|
|
2276
|
+
const timeClass = isTimeout ? 'timeout' : '';
|
|
2277
|
+
|
|
2278
|
+
const html = `
|
|
2279
|
+
<div class="exec-timer" id="execTimer">
|
|
2280
|
+
<div class="exec-timer-icon">⚡</div>
|
|
2281
|
+
<div class="exec-timer-body">
|
|
2282
|
+
<div class="exec-timer-title">
|
|
2283
|
+
<span class="pulse-dot ${isTimeout ? 'timeout' : ''}"></span>
|
|
2284
|
+
<span>${statusLabel} · ${langLabel}</span>
|
|
2285
|
+
</div>
|
|
2286
|
+
<div class="exec-timer-times">
|
|
2287
|
+
<div class="exec-timer-time ${timeClass}">
|
|
2288
|
+
<span class="time-val">${elapsed.toFixed(1)}</span>
|
|
2289
|
+
<span class="time-unit">s</span>
|
|
2290
|
+
</div>
|
|
2291
|
+
<div style="font-size:10px;color:var(--text3)">
|
|
2292
|
+
上限: ${timeout}s
|
|
2293
|
+
</div>
|
|
2294
|
+
</div>
|
|
2295
|
+
<div class="exec-timer-bar">
|
|
2296
|
+
<div class="exec-timer-bar-fill ${barClass}" style="width:${pct.toFixed(1)}%"></div>
|
|
2297
|
+
</div>
|
|
2298
|
+
${codePreview ? `<div class="exec-timer-code">${langLabel}: ${codePreview}</div>` : ''}
|
|
2299
|
+
</div>
|
|
2300
|
+
</div>`;
|
|
2301
|
+
|
|
2302
|
+
if (timer) {
|
|
2303
|
+
timer.outerHTML = html;
|
|
2304
|
+
} else {
|
|
2305
|
+
// Insert after typing indicator
|
|
2306
|
+
const typing = document.getElementById('typingIndicator');
|
|
2307
|
+
const wrapper = document.createElement('div');
|
|
2308
|
+
wrapper.id = 'execTimerWrapper';
|
|
2309
|
+
wrapper.innerHTML = html;
|
|
2310
|
+
wrapper.style.cssText = 'display:none';
|
|
2311
|
+
if (typing) {
|
|
2312
|
+
typing.parentNode.insertBefore(wrapper, typing.nextSibling);
|
|
2313
|
+
} else {
|
|
2314
|
+
const container = document.getElementById('messagesInner');
|
|
2315
|
+
container.appendChild(wrapper);
|
|
2316
|
+
}
|
|
2317
|
+
// Now show it
|
|
2318
|
+
wrapper.style.display = '';
|
|
2319
|
+
// Update the reference
|
|
2320
|
+
document.getElementById('execTimer') || document.getElementById('execTimerWrapper').querySelector('.exec-timer');
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
function showTypingIndicator() {
|
|
2325
|
+
const container = document.getElementById('messagesInner');
|
|
2326
|
+
const agent = findAgentByPath(state.activeAgent);
|
|
2327
|
+
const botEmoji = agent ? (agent.avatar_emoji || '🤖') : '🤖';
|
|
2328
|
+
const el = document.createElement('div');
|
|
2329
|
+
el.className = 'typing-indicator';
|
|
2330
|
+
el.id = 'typingIndicator';
|
|
2331
|
+
el.innerHTML = `
|
|
2332
|
+
<div class="message-avatar" style="background:linear-gradient(135deg,#7c3aed,#4f46e5);color:#fff;border-radius:8px;width:32px;height:32px;display:grid;place-items:center;font-size:14px">${botEmoji}</div>
|
|
2333
|
+
<div class="typing-bubble">
|
|
2334
|
+
<div class="typing-dot"></div>
|
|
2335
|
+
<div class="typing-dot"></div>
|
|
2336
|
+
<div class="typing-dot"></div>
|
|
2337
|
+
</div>`;
|
|
2338
|
+
container.appendChild(el);
|
|
2339
|
+
scrollToBottom();
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
function hideTypingIndicator() {
|
|
2343
|
+
const el = document.getElementById('typingIndicator');
|
|
2344
|
+
if (el) el.remove();
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// ── Input Handling ──
|
|
2348
|
+
function handleKeyDown(e) {
|
|
2349
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
2350
|
+
e.preventDefault();
|
|
2351
|
+
sendMessage();
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
function autoResize(el) {
|
|
2356
|
+
el.style.height = 'auto';
|
|
2357
|
+
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// ── Sidebar Toggle ──
|
|
2361
|
+
function toggleSidebar() {
|
|
2362
|
+
document.getElementById('sidebar').classList.toggle('hidden');
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// ── Toast ──
|
|
2366
|
+
function toast(message, type = 'info') {
|
|
2367
|
+
const container = document.getElementById('toastContainer');
|
|
2368
|
+
const el = document.createElement('div');
|
|
2369
|
+
el.className = `toast toast-${type}`;
|
|
2370
|
+
el.textContent = message;
|
|
2371
|
+
container.appendChild(el);
|
|
2372
|
+
setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 300); }, 3000);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// ══════════════════════════════════════════════════════
|
|
2376
|
+
// ── Config Management (热重载 / 导入 / 导出) ──
|
|
2377
|
+
// ══════════════════════════════════════════════════════
|
|
2378
|
+
|
|
2379
|
+
function showConfigModal() {
|
|
2380
|
+
var container = document.getElementById('configModalContainer');
|
|
2381
|
+
container.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeConfigModal()"><div class="config-modal">'
|
|
2382
|
+
+ '<h3>⚙️ 配置管理</h3>'
|
|
2383
|
+
+ '<p class="modal-subtitle">热重载、导入导出系统配置(无需重启服务)</p>'
|
|
2384
|
+
|
|
2385
|
+
// ── 热重载 ──
|
|
2386
|
+
+ '<div class="config-section">'
|
|
2387
|
+
+ '<h4><span class="section-icon">🔄</span> 热重载</h4>'
|
|
2388
|
+
+ '<p style="font-size:12px;color:var(--text2);margin-bottom:12px">从配置文件重新加载所有设置,立即生效,无需重启服务。适用于直接编辑了 <code style="background:var(--bg3);padding:1px 4px;border-radius:3px">~/.myagent/config.json</code> 后的场景。</p>'
|
|
2389
|
+
+ '<div class="config-action-row">'
|
|
2390
|
+
+ '<button class="config-action-btn primary" onclick="hotReloadConfig()" id="reloadBtn">'
|
|
2391
|
+
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>'
|
|
2392
|
+
+ '热重载配置'</button>'
|
|
2393
|
+
+ '</div>'
|
|
2394
|
+
+ '<div class="config-status" id="reloadStatus"></div>'
|
|
2395
|
+
+ '</div>'
|
|
2396
|
+
|
|
2397
|
+
// ── 导出 ──
|
|
2398
|
+
+ '<div class="config-section">'
|
|
2399
|
+
+ '<h4><span class="section-icon">📤</span> 导出备份</h4>'
|
|
2400
|
+
+ '<p style="font-size:12px;color:var(--text2);margin-bottom:12px">将当前完整配置导出为 JSON 文件,用于备份或迁移到其他设备。</p>'
|
|
2401
|
+
+ '<div class="config-action-row">'
|
|
2402
|
+
+ '<button class="config-action-btn" onclick="exportConfig(false)" id="exportSafeBtn">'
|
|
2403
|
+
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>'
|
|
2404
|
+
+ '导出(脱敏)</button>'
|
|
2405
|
+
+ '<button class="config-action-btn danger" onclick="exportConfig(true)" id="exportFullBtn">'
|
|
2406
|
+
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>'
|
|
2407
|
+
+ '导出(含密钥)</button>'
|
|
2408
|
+
+ '</div>'
|
|
2409
|
+
+ '<div class="config-status" id="exportStatus"></div>'
|
|
2410
|
+
+ '</div>'
|
|
2411
|
+
|
|
2412
|
+
// ── 导入 ──
|
|
2413
|
+
+ '<div class="config-section">'
|
|
2414
|
+
+ '<h4><span class="section-icon">📥</span> 导入配置</h4>'
|
|
2415
|
+
+ '<p style="font-size:12px;color:var(--text2);margin-bottom:12px">从之前导出的 JSON 备份文件恢复配置。导入后自动热重载生效。</p>'
|
|
2416
|
+
+ '<div class="config-import-area" id="importDropArea" onclick="document.getElementById(\'configFileInput\').click()" ondragover="event.preventDefault();this.classList.add(\'dragover\')" ondragleave="this.classList.remove(\'dragover\')" ondrop="handleConfigDrop(event)">'
|
|
2417
|
+
+ '<div class="upload-icon">📁</div>'
|
|
2418
|
+
+ '<div class="upload-text">点击选择文件或拖拽 JSON 到此处</div>'
|
|
2419
|
+
+ '<div class="upload-hint">支持 myagent_config_*.json 格式</div>'
|
|
2420
|
+
+ '<input type="file" id="configFileInput" accept=".json" onchange="handleConfigFile(this)" style="display:none">'
|
|
2421
|
+
+ '</div>'
|
|
2422
|
+
+ '<div class="config-checkbox-row">'
|
|
2423
|
+
+ '<input type="checkbox" id="importOverwrite">'
|
|
2424
|
+
+ '<label for="importOverwrite">完全覆盖当前配置(否则智能合并)</label>'
|
|
2425
|
+
+ '</div>'
|
|
2426
|
+
+ '<div class="config-status" id="importStatus"></div>'
|
|
2427
|
+
+ '</div>'
|
|
2428
|
+
|
|
2429
|
+
// ── 当前配置预览 ──
|
|
2430
|
+
+ '<div class="config-section">'
|
|
2431
|
+
+ '<h4><span class="section-icon">👁️</span> 当前配置预览</h4>'
|
|
2432
|
+
+ '<div class="config-preview" id="configPreview">加载中...</div>'
|
|
2433
|
+
+ '</div>'
|
|
2434
|
+
|
|
2435
|
+
// ── 关闭按钮 ──
|
|
2436
|
+
+ '<div class="agent-modal-actions" style="margin-top:20px;padding-top:16px;border-top:1px solid var(--border-light)">'
|
|
2437
|
+
+ '<button class="agent-modal-btn agent-modal-btn-cancel" onclick="closeConfigModal()">关闭</button>'
|
|
2438
|
+
+ '</div></div></div>';
|
|
2439
|
+
|
|
2440
|
+
// 加载当前配置预览
|
|
2441
|
+
loadConfigPreview();
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
function closeConfigModal() {
|
|
2445
|
+
document.getElementById('configModalContainer').innerHTML = '';
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
function setConfigStatus(id, type, message) {
|
|
2449
|
+
var el = document.getElementById(id);
|
|
2450
|
+
if (!el) return;
|
|
2451
|
+
el.className = 'config-status show ' + type;
|
|
2452
|
+
if (type === 'loading') {
|
|
2453
|
+
el.innerHTML = '<div class="spinner"></div> ' + escapeHtml(message);
|
|
2454
|
+
} else {
|
|
2455
|
+
el.textContent = message;
|
|
2456
|
+
}
|
|
2457
|
+
if (type !== 'loading') {
|
|
2458
|
+
setTimeout(function() { el.className = 'config-status'; }, 5000);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
async function loadConfigPreview() {
|
|
2463
|
+
var el = document.getElementById('configPreview');
|
|
2464
|
+
if (!el) return;
|
|
2465
|
+
try {
|
|
2466
|
+
var cfg = await api('/api/config');
|
|
2467
|
+
var c = cfg.config || cfg;
|
|
2468
|
+
var lines = [];
|
|
2469
|
+
if (c.llm) {
|
|
2470
|
+
lines.push('<span class="key">LLM:</span> ' + escapeHtml(c.llm.provider + '/' + c.llm.model));
|
|
2471
|
+
lines.push('<span class="key">Base URL:</span> ' + escapeHtml(c.llm.base_url || 'default'));
|
|
2472
|
+
lines.push('<span class="key">Temperature:</span> ' + c.llm.temperature + ' | Max Tokens: ' + c.llm.max_tokens);
|
|
2473
|
+
}
|
|
2474
|
+
if (c.executor) {
|
|
2475
|
+
lines.push('<span class="key">执行引擎:</span> ' + escapeHtml(c.executor.execution_mode || 'local') + ' | Timeout: ' + (c.executor.timeout || 300) + 's');
|
|
2476
|
+
}
|
|
2477
|
+
if (c.memory) {
|
|
2478
|
+
lines.push('<span class="key">记忆:</span> 短期 ' + (c.memory.max_short_term || 50) + ' 轮 | 自动总结: ' + (c.memory.auto_summarize ? '开' : '关'));
|
|
2479
|
+
}
|
|
2480
|
+
if (c.agent) {
|
|
2481
|
+
lines.push('<span class="key">Agent:</span> 最大迭代 ' + (c.agent.max_iterations || 30) + ' | 并行 ' + (c.agent.max_parallel || 3));
|
|
2482
|
+
}
|
|
2483
|
+
if (c.chat_platforms && c.chat_platforms.length > 0) {
|
|
2484
|
+
var platforms = c.chat_platforms.filter(function(p) { return p.enabled; }).map(function(p) { return p.platform; });
|
|
2485
|
+
if (platforms.length > 0) {
|
|
2486
|
+
lines.push('<span class="key">聊天平台:</span> ' + escapeHtml(platforms.join(', ')));
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
if (c.log_level) {
|
|
2490
|
+
lines.push('<span class="key">日志级别:</span> ' + escapeHtml(c.log_level));
|
|
2491
|
+
}
|
|
2492
|
+
if (cfg._meta) {
|
|
2493
|
+
lines.push('');
|
|
2494
|
+
lines.push('<span class="key">备份信息:</span> ' + escapeHtml(cfg._meta.exported_at || 'N/A'));
|
|
2495
|
+
}
|
|
2496
|
+
el.innerHTML = lines.join('\n') || '暂无配置信息';
|
|
2497
|
+
} catch (e) {
|
|
2498
|
+
el.textContent = '加载配置失败: ' + e.message;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
async function hotReloadConfig() {
|
|
2503
|
+
var btn = document.getElementById('reloadBtn');
|
|
2504
|
+
btn.disabled = true;
|
|
2505
|
+
setConfigStatus('reloadStatus', 'loading', '正在热重载配置...');
|
|
2506
|
+
try {
|
|
2507
|
+
var result = await api('/api/config/reload', { method: 'POST', body: JSON.stringify({}) });
|
|
2508
|
+
if (result.ok) {
|
|
2509
|
+
var changes = result.changes || [];
|
|
2510
|
+
var msg = '✅ 配置已热重载';
|
|
2511
|
+
if (changes.length > 0) {
|
|
2512
|
+
msg += ' — 变更: ' + changes.join(', ');
|
|
2513
|
+
} else {
|
|
2514
|
+
msg += ' — 无显著变更';
|
|
2515
|
+
}
|
|
2516
|
+
setConfigStatus('reloadStatus', 'success', msg);
|
|
2517
|
+
// 刷新配置预览
|
|
2518
|
+
loadConfigPreview();
|
|
2519
|
+
// 刷新状态栏
|
|
2520
|
+
loadStatus();
|
|
2521
|
+
toast('配置已热重载', 'success');
|
|
2522
|
+
} else {
|
|
2523
|
+
setConfigStatus('reloadStatus', 'error', '❌ 热重载失败: ' + (result.error || '未知错误'));
|
|
2524
|
+
}
|
|
2525
|
+
} catch (e) {
|
|
2526
|
+
setConfigStatus('reloadStatus', 'error', '❌ 热重载失败: ' + e.message);
|
|
2527
|
+
}
|
|
2528
|
+
btn.disabled = false;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
async function exportConfig(includeSecrets) {
|
|
2532
|
+
var btnId = includeSecrets ? 'exportFullBtn' : 'exportSafeBtn';
|
|
2533
|
+
var statusId = 'exportStatus';
|
|
2534
|
+
var btn = document.getElementById(btnId);
|
|
2535
|
+
btn.disabled = true;
|
|
2536
|
+
setConfigStatus(statusId, 'loading', '正在导出配置...');
|
|
2537
|
+
try {
|
|
2538
|
+
var resp = await fetch('/api/config/export', {
|
|
2539
|
+
method: 'POST',
|
|
2540
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2541
|
+
body: JSON.stringify({ include_secrets: includeSecrets }),
|
|
2542
|
+
});
|
|
2543
|
+
if (!resp.ok) {
|
|
2544
|
+
var errData = await resp.json();
|
|
2545
|
+
throw new Error(errData.error || '导出失败');
|
|
2546
|
+
}
|
|
2547
|
+
// 下载文件
|
|
2548
|
+
var blob = await resp.blob();
|
|
2549
|
+
var disposition = resp.headers.get('Content-Disposition') || '';
|
|
2550
|
+
var filenameMatch = disposition.match(/filename="([^"]+)"/);
|
|
2551
|
+
var filename = filenameMatch ? filenameMatch[1] : 'myagent_config.json';
|
|
2552
|
+
var url = URL.createObjectURL(blob);
|
|
2553
|
+
var a = document.createElement('a');
|
|
2554
|
+
a.href = url; a.download = filename;
|
|
2555
|
+
document.body.appendChild(a); a.click();
|
|
2556
|
+
document.body.removeChild(a);
|
|
2557
|
+
URL.revokeObjectURL(url);
|
|
2558
|
+
setConfigStatus(statusId, 'success', '✅ 配置已导出: ' + filename);
|
|
2559
|
+
toast('配置已导出: ' + filename, 'success');
|
|
2560
|
+
} catch (e) {
|
|
2561
|
+
setConfigStatus(statusId, 'error', '❌ 导出失败: ' + e.message);
|
|
2562
|
+
}
|
|
2563
|
+
btn.disabled = false;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
function handleConfigDrop(event) {
|
|
2567
|
+
event.preventDefault();
|
|
2568
|
+
event.stopPropagation();
|
|
2569
|
+
document.getElementById('importDropArea').classList.remove('dragover');
|
|
2570
|
+
var files = event.dataTransfer.files;
|
|
2571
|
+
if (files.length > 0 && files[0].name.endsWith('.json')) {
|
|
2572
|
+
importConfigFromFile(files[0]);
|
|
2573
|
+
} else {
|
|
2574
|
+
toast('请拖入 JSON 文件', 'error');
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
function handleConfigFile(input) {
|
|
2579
|
+
if (input.files.length > 0) {
|
|
2580
|
+
importConfigFromFile(input.files[0]);
|
|
2581
|
+
}
|
|
2582
|
+
input.value = ''; // reset for re-select
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
async function importConfigFromFile(file) {
|
|
2586
|
+
var overwrite = document.getElementById('importOverwrite').checked;
|
|
2587
|
+
setConfigStatus('importStatus', 'loading', '正在读取 ' + file.name + '...');
|
|
2588
|
+
try {
|
|
2589
|
+
var text = await file.text();
|
|
2590
|
+
var data = JSON.parse(text);
|
|
2591
|
+
setConfigStatus('importStatus', 'loading', '正在导入配置...');
|
|
2592
|
+
if (overwrite) {
|
|
2593
|
+
data._overwrite = true;
|
|
2594
|
+
}
|
|
2595
|
+
var result = await api('/api/config/import', {
|
|
2596
|
+
method: 'POST',
|
|
2597
|
+
body: JSON.stringify(data),
|
|
2598
|
+
});
|
|
2599
|
+
if (result.ok) {
|
|
2600
|
+
var changed = result.changed_keys || [];
|
|
2601
|
+
var msg = '✅ ' + result.message;
|
|
2602
|
+
if (changed.length > 0) {
|
|
2603
|
+
msg += ' (' + changed.slice(0, 5).join(', ') + (changed.length > 5 ? '...' : '') + ')';
|
|
2604
|
+
}
|
|
2605
|
+
setConfigStatus('importStatus', 'success', msg);
|
|
2606
|
+
loadConfigPreview();
|
|
2607
|
+
loadStatus();
|
|
2608
|
+
toast(result.message, 'success');
|
|
2609
|
+
} else {
|
|
2610
|
+
setConfigStatus('importStatus', 'error', '❌ 导入失败: ' + (result.message || '未知错误'));
|
|
2611
|
+
}
|
|
2612
|
+
} catch (e) {
|
|
2613
|
+
if (e instanceof SyntaxError) {
|
|
2614
|
+
setConfigStatus('importStatus', 'error', '❌ 文件格式错误: 不是有效的 JSON');
|
|
2615
|
+
} else {
|
|
2616
|
+
setConfigStatus('importStatus', 'error', '❌ 导入失败: ' + e.message);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// ══════════════════════════════════════════════════════
|
|
2622
|
+
// ── Group Chat ──
|
|
2623
|
+
// ══════════════════════════════════════════════════════
|
|
2624
|
+
|
|
2625
|
+
// ── Group API ──
|
|
2626
|
+
async function fetchGroups() {
|
|
2627
|
+
try {
|
|
2628
|
+
var data = await api('/api/groups');
|
|
2629
|
+
groups = data || [];
|
|
2630
|
+
renderGroups();
|
|
2631
|
+
} catch (e) {
|
|
2632
|
+
groups = [];
|
|
2633
|
+
renderGroups();
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
async function createGroupApi(name, description, emoji, color, members) {
|
|
2638
|
+
return await api('/api/groups', {
|
|
2639
|
+
method: 'POST',
|
|
2640
|
+
body: JSON.stringify({name: name, description: description, emoji: emoji, color: color, members: members})
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
async function getGroup(gid) {
|
|
2645
|
+
return await api('/api/groups/' + encodeURIComponent(gid));
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
async function updateGroup(gid, data) {
|
|
2649
|
+
return await api('/api/groups/' + encodeURIComponent(gid), {
|
|
2650
|
+
method: 'PUT',
|
|
2651
|
+
body: JSON.stringify(data)
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
async function deleteGroup(gid) {
|
|
2656
|
+
return await api('/api/groups/' + encodeURIComponent(gid), {
|
|
2657
|
+
method: 'DELETE'
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
async function getGroupMessages(gid) {
|
|
2662
|
+
return await api('/api/groups/' + encodeURIComponent(gid) + '/messages');
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
async function sendGroupMessageApi(gid, message) {
|
|
2666
|
+
return await api('/api/groups/' + encodeURIComponent(gid) + '/messages', {
|
|
2667
|
+
method: 'POST',
|
|
2668
|
+
body: JSON.stringify({message: message})
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
async function addGroupMember(gid, agentPath, role) {
|
|
2673
|
+
return await api('/api/groups/' + encodeURIComponent(gid) + '/members', {
|
|
2674
|
+
method: 'POST',
|
|
2675
|
+
body: JSON.stringify({agent_path: agentPath, role: role || 'member'})
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
async function removeGroupMemberApi(gid, agentPath) {
|
|
2680
|
+
return await api('/api/groups/' + encodeURIComponent(gid) + '/members/' + encodeURIComponent(agentPath), {
|
|
2681
|
+
method: 'DELETE'
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
async function setMemberRole(gid, agentPath, role) {
|
|
2686
|
+
return await api('/api/groups/' + encodeURIComponent(gid) + '/members/' + encodeURIComponent(agentPath) + '/role', {
|
|
2687
|
+
method: 'PUT',
|
|
2688
|
+
body: JSON.stringify({role: role})
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
// ── Group Sidebar ──
|
|
2693
|
+
function renderGroups() {
|
|
2694
|
+
var listEl = document.getElementById('groupList');
|
|
2695
|
+
if (!listEl) return;
|
|
2696
|
+
|
|
2697
|
+
if (!groupsSectionExpanded) {
|
|
2698
|
+
listEl.style.display = 'none';
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
listEl.style.display = '';
|
|
2702
|
+
|
|
2703
|
+
if (groups.length === 0) {
|
|
2704
|
+
listEl.innerHTML = '<div style="padding:12px 16px;font-size:12px;color:var(--text3);text-align:center">暂无群聊<br>点击 + 创建</div>';
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
var html = '';
|
|
2709
|
+
for (var i = 0; i < groups.length; i++) {
|
|
2710
|
+
var g = groups[i];
|
|
2711
|
+
var memberCount = (g.members || []).length;
|
|
2712
|
+
var isActive = g.id === currentGroupId;
|
|
2713
|
+
html += '<div class="group-item ' + (isActive ? 'active' : '') + '" onclick="selectGroup(\'' + escapeHtml(g.id) + '\')" title="' + escapeHtml(g.name) + '">';
|
|
2714
|
+
html += '<div class="group-icon">' + escapeHtml(g.emoji || '👥') + '</div>';
|
|
2715
|
+
html += '<div class="group-info">';
|
|
2716
|
+
html += '<div class="group-name">' + escapeHtml(g.name) + '</div>';
|
|
2717
|
+
html += '<div class="group-preview">' + escapeHtml(g.description || memberCount + ' 位成员') + '</div>';
|
|
2718
|
+
html += '</div>';
|
|
2719
|
+
html += '<span class="group-badge">' + memberCount + '</span>';
|
|
2720
|
+
html += '<button class="group-delete" onclick="event.stopPropagation();deleteGroupConfirm(\'' + escapeHtml(g.id) + '\')" title="删除">✕</button>';
|
|
2721
|
+
html += '</div>';
|
|
2722
|
+
}
|
|
2723
|
+
listEl.innerHTML = html;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
function toggleGroupsSection() {
|
|
2727
|
+
groupsSectionExpanded = !groupsSectionExpanded;
|
|
2728
|
+
renderGroups();
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// ── Select Group ──
|
|
2732
|
+
async function selectGroup(gid) {
|
|
2733
|
+
if (gid === currentGroupId) return;
|
|
2734
|
+
currentView = 'group';
|
|
2735
|
+
currentGroupId = gid;
|
|
2736
|
+
|
|
2737
|
+
// Update sidebar
|
|
2738
|
+
renderSessions();
|
|
2739
|
+
renderGroups();
|
|
2740
|
+
|
|
2741
|
+
// Load group data
|
|
2742
|
+
try {
|
|
2743
|
+
var groupData = await getGroup(gid);
|
|
2744
|
+
|
|
2745
|
+
// Update header
|
|
2746
|
+
document.getElementById('headerTitle').textContent = (groupData.emoji || '👥') + ' ' + groupData.name;
|
|
2747
|
+
document.getElementById('activeAgentBadge').style.display = 'none';
|
|
2748
|
+
document.getElementById('groupBackBtn').style.display = '';
|
|
2749
|
+
document.getElementById('groupSettingsBtn').style.display = '';
|
|
2750
|
+
document.getElementById('clearChatBtn').style.display = 'none';
|
|
2751
|
+
var dot = document.querySelector('.main-title .dot');
|
|
2752
|
+
if (dot) dot.style.display = 'none';
|
|
2753
|
+
|
|
2754
|
+
// Update input placeholder
|
|
2755
|
+
document.getElementById('userInput').placeholder = '发送到群聊... (Enter 发送, Shift+Enter 换行)';
|
|
2756
|
+
|
|
2757
|
+
// Load messages
|
|
2758
|
+
var msgsData = await getGroupMessages(gid);
|
|
2759
|
+
groupMessages = msgsData || [];
|
|
2760
|
+
renderGroupMessages();
|
|
2761
|
+
} catch (e) {
|
|
2762
|
+
toast('加载群聊失败: ' + e.message, 'error');
|
|
2763
|
+
exitGroupChat();
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
function exitGroupChat() {
|
|
2768
|
+
currentView = 'chat';
|
|
2769
|
+
currentGroupId = null;
|
|
2770
|
+
groupMessages = [];
|
|
2771
|
+
|
|
2772
|
+
// Restore header
|
|
2773
|
+
document.getElementById('headerTitle').textContent = '新对话';
|
|
2774
|
+
document.getElementById('activeAgentBadge').style.display = '';
|
|
2775
|
+
document.getElementById('groupBackBtn').style.display = 'none';
|
|
2776
|
+
document.getElementById('groupSettingsBtn').style.display = 'none';
|
|
2777
|
+
document.getElementById('clearChatBtn').style.display = '';
|
|
2778
|
+
var dot = document.querySelector('.main-title .dot');
|
|
2779
|
+
if (dot) dot.style.display = '';
|
|
2780
|
+
|
|
2781
|
+
// Restore input placeholder
|
|
2782
|
+
document.getElementById('userInput').placeholder = '输入消息... (Enter 发送, Shift+Enter 换行)';
|
|
2783
|
+
|
|
2784
|
+
// Update sidebar
|
|
2785
|
+
renderSessions();
|
|
2786
|
+
renderGroups();
|
|
2787
|
+
|
|
2788
|
+
// Show normal messages
|
|
2789
|
+
renderMessages();
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
// ── Render Group Messages ──
|
|
2793
|
+
function renderGroupMessages() {
|
|
2794
|
+
var container = document.getElementById('messagesInner');
|
|
2795
|
+
var html = '';
|
|
2796
|
+
|
|
2797
|
+
if (groupMessages.length === 0) {
|
|
2798
|
+
var group = groups.find(function(g) { return g.id === currentGroupId; });
|
|
2799
|
+
html = '<div class="welcome-card">'
|
|
2800
|
+
+ '<h2><span class="emoji">' + escapeHtml((group && group.emoji) || '👥') + '</span>'
|
|
2801
|
+
+ ' <span>群聊: ' + escapeHtml((group && group.name) || '') + '</span></h2>'
|
|
2802
|
+
+ '<p class="subtitle">向所有成员发送消息,每个 Agent 会分别回复</p>'
|
|
2803
|
+
+ '</div>';
|
|
2804
|
+
container.innerHTML = html;
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
for (var i = 0; i < groupMessages.length; i++) {
|
|
2809
|
+
var msg = groupMessages[i];
|
|
2810
|
+
if (msg.type === 'system') {
|
|
2811
|
+
html += '<div class="group-msg-system">' + escapeHtml(msg.content || msg.text || '') + '</div>';
|
|
2812
|
+
} else if (msg.role === 'user') {
|
|
2813
|
+
html += '<div class="message-row user" style="animation:msgIn .25s ease-out">'
|
|
2814
|
+
+ '<div class="message-avatar" style="background:var(--accent);color:#fff">👤</div>'
|
|
2815
|
+
+ '<div><div class="message-bubble">' + renderMarkdown(msg.content || '') + '</div>'
|
|
2816
|
+
+ (msg.time ? '<div class="message-time" style="text-align:right">' + formatTime(msg.time) + '</div>' : '')
|
|
2817
|
+
+ '</div></div>';
|
|
2818
|
+
} else if (msg.role === 'assistant' || msg.agent) {
|
|
2819
|
+
var agentName = msg.agent_name || msg.agent || 'Agent';
|
|
2820
|
+
var agentEmoji = msg.agent_emoji || '🤖';
|
|
2821
|
+
var agentColor = msg.agent_color || 'var(--accent)';
|
|
2822
|
+
var agentRole = msg.agent_role || '';
|
|
2823
|
+
html += '<div class="group-msg-row">'
|
|
2824
|
+
+ '<div class="group-msg-avatar" style="background:' + agentColor + ';color:#fff">' + agentEmoji + '</div>'
|
|
2825
|
+
+ '<div>'
|
|
2826
|
+
+ '<div class="group-msg-name">' + escapeHtml(agentName)
|
|
2827
|
+
+ (agentRole ? ' <span class="role-badge">' + escapeHtml(agentRole) + '</span>' : '')
|
|
2828
|
+
+ '</div>'
|
|
2829
|
+
+ '<div class="group-msg-bubble" style="border-left-color:' + agentColor + '">' + renderMarkdown(msg.content || '') + '</div>'
|
|
2830
|
+
+ (msg.time ? '<div class="group-msg-time">' + formatTime(msg.time) + '</div>' : '')
|
|
2831
|
+
+ '</div></div>';
|
|
2832
|
+
} else if (msg.responses) {
|
|
2833
|
+
// Multiple agent responses (broadcast response)
|
|
2834
|
+
for (var j = 0; j < msg.responses.length; j++) {
|
|
2835
|
+
var r = msg.responses[j];
|
|
2836
|
+
var rName = r.agent_name || r.agent || 'Agent';
|
|
2837
|
+
var rEmoji = r.agent_emoji || '🤖';
|
|
2838
|
+
var rColor = r.agent_color || 'var(--accent)';
|
|
2839
|
+
html += '<div class="group-msg-row">'
|
|
2840
|
+
+ '<div class="group-msg-avatar" style="background:' + rColor + ';color:#fff">' + rEmoji + '</div>'
|
|
2841
|
+
+ '<div>'
|
|
2842
|
+
+ '<div class="group-msg-name">' + escapeHtml(rName) + '</div>'
|
|
2843
|
+
+ '<div class="group-msg-bubble" style="border-left-color:' + rColor + '">' + renderMarkdown(r.content || r.response || '') + '</div>'
|
|
2844
|
+
+ '</div></div>';
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
container.innerHTML = html;
|
|
2850
|
+
scrollToBottom();
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// ── Send Group Message ──
|
|
2854
|
+
async function sendGroupChat() {
|
|
2855
|
+
var input = document.getElementById('userInput');
|
|
2856
|
+
var text = input.value.trim();
|
|
2857
|
+
if (!text || state.isGenerating || !currentGroupId) return;
|
|
2858
|
+
|
|
2859
|
+
// Add user message
|
|
2860
|
+
groupMessages.push({role: 'user', content: text, time: new Date().toISOString()});
|
|
2861
|
+
renderGroupMessages();
|
|
2862
|
+
|
|
2863
|
+
// Clear input
|
|
2864
|
+
input.value = '';
|
|
2865
|
+
input.style.height = 'auto';
|
|
2866
|
+
document.getElementById('sendBtn').disabled = true;
|
|
2867
|
+
|
|
2868
|
+
// Show typing
|
|
2869
|
+
state.isGenerating = true;
|
|
2870
|
+
showTypingIndicator();
|
|
2871
|
+
document.getElementById('sendBtn').style.display = 'none';
|
|
2872
|
+
document.getElementById('stopBtn').style.display = '';
|
|
2873
|
+
|
|
2874
|
+
try {
|
|
2875
|
+
state.abortController = new AbortController();
|
|
2876
|
+
var data = await sendGroupMessageApi(currentGroupId, text);
|
|
2877
|
+
|
|
2878
|
+
// Handle response - support multiple response formats
|
|
2879
|
+
if (data.responses && data.responses.length > 0) {
|
|
2880
|
+
groupMessages.push({role: 'broadcast', responses: data.responses, time: new Date().toISOString()});
|
|
2881
|
+
} else if (data.response || data.content) {
|
|
2882
|
+
groupMessages.push({role: 'assistant', content: data.response || data.content, agent_name: data.agent_name, agent_emoji: data.agent_emoji, agent_color: data.agent_color, time: new Date().toISOString()});
|
|
2883
|
+
}
|
|
2884
|
+
if (data.system_message) {
|
|
2885
|
+
groupMessages.push({type: 'system', content: data.system_message, time: new Date().toISOString()});
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
renderGroupMessages();
|
|
2889
|
+
fetchGroups();
|
|
2890
|
+
} catch (e) {
|
|
2891
|
+
if (e.name !== 'AbortError') {
|
|
2892
|
+
toast('发送失败: ' + e.message, 'error');
|
|
2893
|
+
groupMessages.push({type: 'system', content: '❌ 发送失败: ' + e.message});
|
|
2894
|
+
renderGroupMessages();
|
|
2895
|
+
}
|
|
2896
|
+
} finally {
|
|
2897
|
+
state.isGenerating = false;
|
|
2898
|
+
state.abortController = null;
|
|
2899
|
+
hideTypingIndicator();
|
|
2900
|
+
document.getElementById('sendBtn').style.display = '';
|
|
2901
|
+
document.getElementById('sendBtn').disabled = true;
|
|
2902
|
+
document.getElementById('stopBtn').style.display = 'none';
|
|
2903
|
+
input.focus();
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
// ── Create Group Modal ──
|
|
2908
|
+
async function showCreateGroupModal() {
|
|
2909
|
+
var agentsList = state.agentsFlat || [];
|
|
2910
|
+
var emojis = ['👥','🚀','💡','🎨','🔧','📚','🎯','🎮','🎬','🎵'];
|
|
2911
|
+
var colors = ['#4f46e5','#ec4899','#f59e0b','#10b981','#3b82f6','#8b5cf6','#ef4444','#14b8a6','#f97316','#06b6d4'];
|
|
2912
|
+
|
|
2913
|
+
var agentCheckboxes = '';
|
|
2914
|
+
for (var i = 0; i < agentsList.length; i++) {
|
|
2915
|
+
var a = agentsList[i];
|
|
2916
|
+
var checked = i === 0 ? ' checked' : '';
|
|
2917
|
+
var emoji = a.avatar_emoji || '🤖';
|
|
2918
|
+
agentCheckboxes += '<label class="group-member-pick-item">'
|
|
2919
|
+
+ '<input type="checkbox" class="group-member-checkbox" value="' + escapeHtml(a.path) + '"' + checked + '>'
|
|
2920
|
+
+ '<div class="group-msg-avatar" style="background:' + (a.avatar_color || getAgentGradient(a.name)) + ';color:#fff;width:28px;height:28px;border-radius:6px;font-size:12px;display:grid;place-items:center">' + emoji + '</div>'
|
|
2921
|
+
+ '<div class="pick-agent-info">'
|
|
2922
|
+
+ '<div class="pick-agent-name">' + escapeHtml(a.name) + '</div>'
|
|
2923
|
+
+ '<div class="pick-agent-desc">' + escapeHtml(a.description || a.path) + '</div>'
|
|
2924
|
+
+ '</div></label>';
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
var container = document.getElementById('groupModalContainer');
|
|
2928
|
+
container.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeGroupModal()"><div class="group-modal">'
|
|
2929
|
+
+ '<h3>👥 创建群聊</h3>'
|
|
2930
|
+
+ '<p class="modal-subtitle">选择多个 Agent 组成群聊,广播消息给所有成员</p>'
|
|
2931
|
+
+ '<div class="agent-form-group"><label>群名称 *</label>'
|
|
2932
|
+
+ '<input type="text" id="groupFormName" placeholder="例如: 开发团队, 创意工坊" maxlength="30">'
|
|
2933
|
+
+ '</div>'
|
|
2934
|
+
+ '<div class="agent-form-group"><label>群描述</label>'
|
|
2935
|
+
+ '<textarea id="groupFormDesc" placeholder="描述这个群聊的用途..." rows="2" style="min-height:60px"></textarea>'
|
|
2936
|
+
+ '</div>'
|
|
2937
|
+
+ '<div class="agent-form-group"><label>头像</label>'
|
|
2938
|
+
+ '<div class="agent-avatar-picker" id="groupEmojiPicker">'
|
|
2939
|
+
+ emojis.map(function(e, idx) { return '<div class="agent-avatar-option ' + (idx === 0 ? 'selected' : '') + '" onclick="pickGroupEmoji(this,\'' + e + '\')" data-emoji="' + e + '">' + e + '</div>'; }).join('')
|
|
2940
|
+
+ '</div><input type="hidden" id="groupFormEmoji" value="👥"></div>'
|
|
2941
|
+
+ '<div class="agent-form-group"><label>主题色</label>'
|
|
2942
|
+
+ '<div class="color-picker-row" id="groupColorPicker">'
|
|
2943
|
+
+ colors.map(function(c, idx) { return '<div class="color-option ' + (idx === 0 ? 'selected' : '') + '" style="background:' + c + '" onclick="pickGroupColor(this,\'' + c + '\')" data-color="' + c + '"></div>'; }).join('')
|
|
2944
|
+
+ '</div><input type="hidden" id="groupFormColor" value="#4f46e5"></div>'
|
|
2945
|
+
+ '<div class="agent-form-group"><label>选择成员</label>'
|
|
2946
|
+
+ '<div class="hint">勾选要加入群聊的 Agent(至少 2 个)</div>'
|
|
2947
|
+
+ '<div class="group-member-picker">' + agentCheckboxes + '</div>'
|
|
2948
|
+
+ '</div>'
|
|
2949
|
+
+ '<div class="agent-modal-actions">'
|
|
2950
|
+
+ '<button class="agent-modal-btn agent-modal-btn-cancel" onclick="closeGroupModal()">取消</button>'
|
|
2951
|
+
+ '<button class="agent-modal-btn agent-modal-btn-primary" onclick="saveCreateGroup()">创建群聊</button>'
|
|
2952
|
+
+ '</div></div></div>';
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
function pickGroupEmoji(el, emoji) {
|
|
2956
|
+
document.querySelectorAll('#groupEmojiPicker .agent-avatar-option').forEach(function(e) { e.classList.remove('selected'); });
|
|
2957
|
+
el.classList.add('selected');
|
|
2958
|
+
document.getElementById('groupFormEmoji').value = emoji;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
function pickGroupColor(el, color) {
|
|
2962
|
+
document.querySelectorAll('#groupColorPicker .color-option').forEach(function(e) { e.classList.remove('selected'); });
|
|
2963
|
+
el.classList.add('selected');
|
|
2964
|
+
document.getElementById('groupFormColor').value = color;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
function closeGroupModal() {
|
|
2968
|
+
document.getElementById('groupModalContainer').innerHTML = '';
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
async function saveCreateGroup() {
|
|
2972
|
+
var name = document.getElementById('groupFormName').value.trim();
|
|
2973
|
+
if (!name) { toast('请输入群名称', 'error'); return; }
|
|
2974
|
+
|
|
2975
|
+
var desc = document.getElementById('groupFormDesc').value.trim();
|
|
2976
|
+
var emoji = document.getElementById('groupFormEmoji').value;
|
|
2977
|
+
var color = document.getElementById('groupFormColor').value;
|
|
2978
|
+
|
|
2979
|
+
var members = [];
|
|
2980
|
+
document.querySelectorAll('.group-member-checkbox:checked').forEach(function(cb) {
|
|
2981
|
+
members.push(cb.value);
|
|
2982
|
+
});
|
|
2983
|
+
|
|
2984
|
+
if (members.length < 2) { toast('至少选择 2 个成员', 'error'); return; }
|
|
2985
|
+
|
|
2986
|
+
try {
|
|
2987
|
+
var result = await createGroupApi(name, desc, emoji, color, members);
|
|
2988
|
+
toast('群聊创建成功: ' + name, 'success');
|
|
2989
|
+
closeGroupModal();
|
|
2990
|
+
await fetchGroups();
|
|
2991
|
+
if (result && result.id) {
|
|
2992
|
+
selectGroup(result.id);
|
|
2993
|
+
}
|
|
2994
|
+
} catch (e) {
|
|
2995
|
+
toast('创建失败: ' + e.message, 'error');
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
async function deleteGroupConfirm(gid) {
|
|
3000
|
+
var group = groups.find(function(g) { return g.id === gid; });
|
|
3001
|
+
if (!confirm('确定删除群聊 "' + (group ? group.name : gid) + '"?')) return;
|
|
3002
|
+
try {
|
|
3003
|
+
await deleteGroup(gid);
|
|
3004
|
+
toast('群聊已删除', 'success');
|
|
3005
|
+
if (currentGroupId === gid) exitGroupChat();
|
|
3006
|
+
await fetchGroups();
|
|
3007
|
+
} catch (e) {
|
|
3008
|
+
toast('删除失败: ' + e.message, 'error');
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
// ── Group Settings Modal ──
|
|
3013
|
+
async function showGroupSettingsModal() {
|
|
3014
|
+
if (!currentGroupId) return;
|
|
3015
|
+
|
|
3016
|
+
try {
|
|
3017
|
+
var groupData = await getGroup(currentGroupId);
|
|
3018
|
+
var members = groupData.members || [];
|
|
3019
|
+
var emojis = ['👥','🚀','💡','🎨','🔧','📚','🎯','🎮','🎬','🎵'];
|
|
3020
|
+
|
|
3021
|
+
var memberListHtml = '';
|
|
3022
|
+
for (var i = 0; i < members.length; i++) {
|
|
3023
|
+
var m = members[i];
|
|
3024
|
+
var roleClass = m.role || 'member';
|
|
3025
|
+
var roleLabel = {owner: '群主', admin: '管理员', member: '成员'}[m.role] || m.role;
|
|
3026
|
+
var emoji = m.avatar_emoji || '🤖';
|
|
3027
|
+
var agentName = m.agent_name || m.agent_path || m.name || 'Unknown';
|
|
3028
|
+
var agentPath = m.agent_path || m.path || '';
|
|
3029
|
+
var isSelf = m.role === 'owner';
|
|
3030
|
+
|
|
3031
|
+
memberListHtml += '<div class="group-member-item">'
|
|
3032
|
+
+ '<div class="group-msg-avatar" style="background:' + (m.agent_color || getAgentGradient(agentName)) + ';color:#fff">' + emoji + '</div>'
|
|
3033
|
+
+ '<div class="group-member-info">'
|
|
3034
|
+
+ '<div class="group-member-name">' + escapeHtml(agentName) + ' <span class="group-member-role ' + roleClass + '">' + escapeHtml(roleLabel) + '</span></div>'
|
|
3035
|
+
+ '</div>'
|
|
3036
|
+
+ '<select class="group-member-role-select" style="padding:4px 8px;border:1px solid var(--border);border-radius:var(--radius-xs);font-size:12px;background:var(--bg)" onchange="changeMemberRole(\'' + escapeHtml(currentGroupId) + '\',\'' + escapeHtml(agentPath) + '\',this.value)" ' + (isSelf ? 'disabled' : '') + '>'
|
|
3037
|
+
+ '<option value="member"' + (m.role === 'member' ? ' selected' : '') + '>成员</option>'
|
|
3038
|
+
+ '<option value="admin"' + (m.role === 'admin' ? ' selected' : '') + '>管理员</option>'
|
|
3039
|
+
+ (isSelf ? '<option value="owner" selected disabled>群主</option>' : '')
|
|
3040
|
+
+ '</select>'
|
|
3041
|
+
+ '<button class="group-member-remove" onclick="removeGroupMemberConfirm(\'' + escapeHtml(currentGroupId) + '\',\'' + escapeHtml(agentPath) + '\',\'' + escapeHtml(agentName) + '\')" ' + (isSelf ? 'disabled title="不能移除群主"' : 'title="移除成员"') + '>✕</button>'
|
|
3042
|
+
+ '</div>';
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
var container = document.getElementById('groupModalContainer');
|
|
3046
|
+
container.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeGroupModal()"><div class="group-modal">'
|
|
3047
|
+
+ '<h3>⚙️ 群聊设置</h3>'
|
|
3048
|
+
+ '<p class="modal-subtitle">' + escapeHtml(groupData.name || '') + '</p>'
|
|
3049
|
+
+ '<div class="agent-form-group"><label>群名称</label>'
|
|
3050
|
+
+ '<input type="text" id="groupSettingsName" value="' + escapeHtml(groupData.name || '') + '" maxlength="30">'
|
|
3051
|
+
+ '</div>'
|
|
3052
|
+
+ '<div class="agent-form-group"><label>群描述</label>'
|
|
3053
|
+
+ '<textarea id="groupSettingsDesc" rows="2" style="min-height:60px">' + escapeHtml(groupData.description || '') + '</textarea>'
|
|
3054
|
+
+ '</div>'
|
|
3055
|
+
+ '<div class="agent-form-group"><label>头像</label>'
|
|
3056
|
+
+ '<div class="agent-avatar-picker" id="groupSettingsEmojiPicker">'
|
|
3057
|
+
+ emojis.map(function(e) { return '<div class="agent-avatar-option ' + ((groupData.emoji || '👥') === e ? 'selected' : '') + '" onclick="pickGroupSettingsEmoji(this,\'' + e + '\')" data-emoji="' + e + '">' + e + '</div>'; }).join('')
|
|
3058
|
+
+ '</div><input type="hidden" id="groupSettingsEmoji" value="' + escapeHtml(groupData.emoji || '👥') + '"></div>'
|
|
3059
|
+
+ '<div class="agent-form-group"><label>成员 (' + members.length + ')</label>'
|
|
3060
|
+
+ '<div style="margin-bottom:8px">'
|
|
3061
|
+
+ '<button class="config-action-btn" onclick="showAddMemberToGroup()" style="font-size:12px;padding:6px 12px">'
|
|
3062
|
+
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>'
|
|
3063
|
+
+ ' 添加成员</button>'
|
|
3064
|
+
+ '</div>'
|
|
3065
|
+
+ '<div style="background:var(--bg);border:1px solid var(--border-light);border-radius:var(--radius-sm);padding:8px 12px">'
|
|
3066
|
+
+ memberListHtml
|
|
3067
|
+
+ '</div>'
|
|
3068
|
+
+ '</div>'
|
|
3069
|
+
+ '<div class="agent-modal-actions">'
|
|
3070
|
+
+ '<button class="agent-modal-btn agent-modal-btn-delete" onclick="dissolveGroup()">🗑️ 解散群聊</button>'
|
|
3071
|
+
+ '<button class="agent-modal-btn agent-modal-btn-cancel" onclick="closeGroupModal()">取消</button>'
|
|
3072
|
+
+ '<button class="agent-modal-btn agent-modal-btn-primary" onclick="saveGroupSettings()">保存</button>'
|
|
3073
|
+
+ '</div></div></div>';
|
|
3074
|
+
} catch (e) {
|
|
3075
|
+
toast('加载群聊设置失败: ' + e.message, 'error');
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
function pickGroupSettingsEmoji(el, emoji) {
|
|
3080
|
+
document.querySelectorAll('#groupSettingsEmojiPicker .agent-avatar-option').forEach(function(e) { e.classList.remove('selected'); });
|
|
3081
|
+
el.classList.add('selected');
|
|
3082
|
+
document.getElementById('groupSettingsEmoji').value = emoji;
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
async function saveGroupSettings() {
|
|
3086
|
+
if (!currentGroupId) return;
|
|
3087
|
+
var name = document.getElementById('groupSettingsName').value.trim();
|
|
3088
|
+
if (!name) { toast('群名称不能为空', 'error'); return; }
|
|
3089
|
+
|
|
3090
|
+
try {
|
|
3091
|
+
await updateGroup(currentGroupId, {
|
|
3092
|
+
name: name,
|
|
3093
|
+
description: document.getElementById('groupSettingsDesc').value.trim(),
|
|
3094
|
+
emoji: document.getElementById('groupSettingsEmoji').value
|
|
3095
|
+
});
|
|
3096
|
+
toast('群聊设置已保存', 'success');
|
|
3097
|
+
closeGroupModal();
|
|
3098
|
+
await fetchGroups();
|
|
3099
|
+
var group = groups.find(function(g) { return g.id === currentGroupId; });
|
|
3100
|
+
if (group) {
|
|
3101
|
+
document.getElementById('headerTitle').textContent = (group.emoji || '👥') + ' ' + group.name;
|
|
3102
|
+
}
|
|
3103
|
+
} catch (e) {
|
|
3104
|
+
toast('保存失败: ' + e.message, 'error');
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
async function dissolveGroup() {
|
|
3109
|
+
if (!currentGroupId) return;
|
|
3110
|
+
if (!confirm('确定解散此群聊?此操作不可恢复。')) return;
|
|
3111
|
+
try {
|
|
3112
|
+
await deleteGroup(currentGroupId);
|
|
3113
|
+
toast('群聊已解散', 'success');
|
|
3114
|
+
closeGroupModal();
|
|
3115
|
+
exitGroupChat();
|
|
3116
|
+
await fetchGroups();
|
|
3117
|
+
} catch (e) {
|
|
3118
|
+
toast('解散失败: ' + e.message, 'error');
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
async function removeGroupMemberConfirm(gid, agentPath, agentName) {
|
|
3123
|
+
if (!confirm('确定将 ' + agentName + ' 移出群聊?')) return;
|
|
3124
|
+
try {
|
|
3125
|
+
await removeGroupMemberApi(gid, agentPath);
|
|
3126
|
+
toast(agentName + ' 已移出群聊', 'success');
|
|
3127
|
+
showGroupSettingsModal();
|
|
3128
|
+
await fetchGroups();
|
|
3129
|
+
} catch (e) {
|
|
3130
|
+
toast('移除失败: ' + e.message, 'error');
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
async function changeMemberRole(gid, agentPath, newRole) {
|
|
3135
|
+
try {
|
|
3136
|
+
await setMemberRole(gid, agentPath, newRole);
|
|
3137
|
+
toast('角色已更新', 'success');
|
|
3138
|
+
} catch (e) {
|
|
3139
|
+
toast('更新角色失败: ' + e.message, 'error');
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
async function showAddMemberToGroup() {
|
|
3144
|
+
if (!currentGroupId) return;
|
|
3145
|
+
try {
|
|
3146
|
+
var groupData = await getGroup(currentGroupId);
|
|
3147
|
+
var existingMembers = (groupData.members || []).map(function(m) { return m.agent_path || m.path; });
|
|
3148
|
+
var available = state.agentsFlat.filter(function(a) { return existingMembers.indexOf(a.path) === -1; });
|
|
3149
|
+
|
|
3150
|
+
if (available.length === 0) {
|
|
3151
|
+
toast('没有可添加的 Agent', 'info');
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
var checkboxes = '';
|
|
3156
|
+
for (var i = 0; i < available.length; i++) {
|
|
3157
|
+
var a = available[i];
|
|
3158
|
+
checkboxes += '<label class="group-member-pick-item">'
|
|
3159
|
+
+ '<input type="checkbox" class="add-member-checkbox" value="' + escapeHtml(a.path) + '">'
|
|
3160
|
+
+ '<div class="group-msg-avatar" style="background:' + (a.avatar_color || getAgentGradient(a.name)) + ';color:#fff;width:28px;height:28px;border-radius:6px;font-size:12px;display:grid;place-items:center">' + (a.avatar_emoji || '🤖') + '</div>'
|
|
3161
|
+
+ '<div class="pick-agent-info">'
|
|
3162
|
+
+ '<div class="pick-agent-name">' + escapeHtml(a.name) + '</div>'
|
|
3163
|
+
+ '<div class="pick-agent-desc">' + escapeHtml(a.description || a.path) + '</div>'
|
|
3164
|
+
+ '</div></label>';
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
var container = document.getElementById('groupModalContainer');
|
|
3168
|
+
container.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeGroupModal()"><div class="group-modal">'
|
|
3169
|
+
+ '<h3>➕ 添加成员</h3>'
|
|
3170
|
+
+ '<p class="modal-subtitle">选择要添加到群聊的 Agent</p>'
|
|
3171
|
+
+ '<div class="agent-form-group"><label>可用 Agent</label>'
|
|
3172
|
+
+ '<div class="group-member-picker" style="max-height:300px">' + checkboxes + '</div>'
|
|
3173
|
+
+ '</div>'
|
|
3174
|
+
+ '<div class="agent-modal-actions">'
|
|
3175
|
+
+ '<button class="agent-modal-btn agent-modal-btn-cancel" onclick="showGroupSettingsModal()">返回</button>'
|
|
3176
|
+
+ '<button class="agent-modal-btn agent-modal-btn-primary" onclick="confirmAddMembers()">添加</button>'
|
|
3177
|
+
+ '</div></div></div>';
|
|
3178
|
+
} catch (e) {
|
|
3179
|
+
toast('加载失败: ' + e.message, 'error');
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
async function confirmAddMembers() {
|
|
3184
|
+
if (!currentGroupId) return;
|
|
3185
|
+
var selected = [];
|
|
3186
|
+
document.querySelectorAll('.add-member-checkbox:checked').forEach(function(cb) {
|
|
3187
|
+
selected.push(cb.value);
|
|
3188
|
+
});
|
|
3189
|
+
|
|
3190
|
+
if (selected.length === 0) { toast('请选择至少一个 Agent', 'error'); return; }
|
|
3191
|
+
|
|
3192
|
+
try {
|
|
3193
|
+
for (var i = 0; i < selected.length; i++) {
|
|
3194
|
+
await addGroupMember(currentGroupId, selected[i]);
|
|
3195
|
+
}
|
|
3196
|
+
toast(selected.length + ' 个成员已添加', 'success');
|
|
3197
|
+
showGroupSettingsModal();
|
|
3198
|
+
await fetchGroups();
|
|
3199
|
+
} catch (e) {
|
|
3200
|
+
toast('添加失败: ' + e.message, 'error');
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// ── Keyboard Shortcuts ──
|
|
3205
|
+
document.addEventListener('keydown', (e) => {
|
|
3206
|
+
// Ctrl/Cmd + N: new chat
|
|
3207
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
|
|
3208
|
+
e.preventDefault();
|
|
3209
|
+
newChat();
|
|
3210
|
+
}
|
|
3211
|
+
// Ctrl/Cmd + B: toggle sidebar
|
|
3212
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
|
|
3213
|
+
e.preventDefault();
|
|
3214
|
+
toggleSidebar();
|
|
3215
|
+
}
|
|
3216
|
+
// Ctrl/Cmd + /: focus input
|
|
3217
|
+
if ((e.ctrlKey || e.metaKey) && e.key === '/') {
|
|
3218
|
+
e.preventDefault();
|
|
3219
|
+
document.getElementById('userInput').focus();
|
|
3220
|
+
}
|
|
3221
|
+
// Ctrl/Cmd + ]: toggle agent panel
|
|
3222
|
+
if ((e.ctrlKey || e.metaKey) && e.key === ']') {
|
|
3223
|
+
e.preventDefault();
|
|
3224
|
+
toggleAgentPanel();
|
|
3225
|
+
}
|
|
3226
|
+
// Escape: close modals
|
|
3227
|
+
if (e.key === 'Escape') {
|
|
3228
|
+
closeAgentModal();
|
|
3229
|
+
closeConfigModal();
|
|
3230
|
+
closeGroupModal();
|
|
3231
|
+
}
|
|
3232
|
+
});
|
|
3233
|
+
</script>
|
|
3234
|
+
</body>
|
|
3235
|
+
</html>
|