octo-agent 0.11.2
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.
- checksums.yaml +7 -0
- data/.clacky/skills/commit/SKILL.md +423 -0
- data/.clacky/skills/gem-release/SKILL.md +199 -0
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/.clacky/skills/oss-upload/SKILL.md +47 -0
- data/.octorules +106 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +76 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +92 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +22 -0
- data/POSITIONING.md +46 -0
- data/README.md +134 -0
- data/README_CN.md +134 -0
- data/Rakefile +34 -0
- data/benchmark/fixtures/sample_project/Gemfile +3 -0
- data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
- data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
- data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
- data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
- data/benchmark/results/EVALUATION_REPORT.md +165 -0
- data/benchmark/results/baseline_20260511_174424.json +128 -0
- data/benchmark/results/report_20260511_175256.json +271 -0
- data/benchmark/results/report_20260511_175444.json +271 -0
- data/benchmark/results/treatment_20260511_175103.json +130 -0
- data/benchmark/runner.rb +441 -0
- data/bin/octo +7 -0
- data/docs/agent-first-ui-design.md +77 -0
- data/docs/billing-system.md +318 -0
- data/docs/channel-architecture.md +235 -0
- data/docs/engineering-article.md +343 -0
- data/docs/session-skill-invocation.md +69 -0
- data/docs/time_machine_design.md +247 -0
- data/docs/ui2-architecture.md +124 -0
- data/homebrew/README.md +96 -0
- data/homebrew/openocto.rb +24 -0
- data/lib/octo/agent/hook_manager.rb +61 -0
- data/lib/octo/agent/llm_caller.rb +800 -0
- data/lib/octo/agent/memory_updater.rb +246 -0
- data/lib/octo/agent/message_compressor.rb +225 -0
- data/lib/octo/agent/message_compressor_helper.rb +869 -0
- data/lib/octo/agent/next_message_suggester.rb +215 -0
- data/lib/octo/agent/session_serializer.rb +685 -0
- data/lib/octo/agent/skill_auto_creator.rb +114 -0
- data/lib/octo/agent/skill_evolution.rb +61 -0
- data/lib/octo/agent/skill_manager.rb +466 -0
- data/lib/octo/agent/skill_reflector.rb +89 -0
- data/lib/octo/agent/system_prompt_builder.rb +101 -0
- data/lib/octo/agent/time_machine.rb +214 -0
- data/lib/octo/agent/tool_executor.rb +454 -0
- data/lib/octo/agent/tool_registry.rb +150 -0
- data/lib/octo/agent.rb +2180 -0
- data/lib/octo/agent_config.rb +989 -0
- data/lib/octo/agent_profile.rb +112 -0
- data/lib/octo/anthropic_stream_aggregator.rb +137 -0
- data/lib/octo/background_task_registry.rb +324 -0
- data/lib/octo/banner.rb +34 -0
- data/lib/octo/bedrock_stream_aggregator.rb +137 -0
- data/lib/octo/block_font.rb +331 -0
- data/lib/octo/cli.rb +968 -0
- data/lib/octo/client.rb +623 -0
- data/lib/octo/default_agents/SOUL.md +3 -0
- data/lib/octo/default_agents/USER.md +1 -0
- data/lib/octo/default_agents/base_prompt.md +66 -0
- data/lib/octo/default_agents/coding/profile.yml +2 -0
- data/lib/octo/default_agents/coding/system_prompt.md +67 -0
- data/lib/octo/default_agents/general/profile.yml +2 -0
- data/lib/octo/default_agents/general/system_prompt.md +16 -0
- data/lib/octo/default_parsers/doc_parser.rb +69 -0
- data/lib/octo/default_parsers/docx_parser.rb +188 -0
- data/lib/octo/default_parsers/pdf_parser.rb +120 -0
- data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/octo/default_parsers/pptx_parser.rb +140 -0
- data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
- data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
- data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
- data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
- data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
- data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
- data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
- data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
- data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
- data/lib/octo/default_skills/onboard/SKILL.md +578 -0
- data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
- data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
- data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
- data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
- data/lib/octo/default_skills/personal-website/publish.rb +235 -0
- data/lib/octo/default_skills/product-help/SKILL.md +123 -0
- data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
- data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
- data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
- data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
- data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
- data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
- data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
- data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
- data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
- data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
- data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
- data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
- data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
- data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
- data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
- data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
- data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
- data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
- data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
- data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
- data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
- data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
- data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
- data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
- data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
- data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
- data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
- data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
- data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
- data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
- data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
- data/lib/octo/idle_compression_timer.rb +115 -0
- data/lib/octo/json_ui_controller.rb +204 -0
- data/lib/octo/message_format/anthropic.rb +409 -0
- data/lib/octo/message_format/bedrock.rb +361 -0
- data/lib/octo/message_format/open_ai.rb +222 -0
- data/lib/octo/message_history.rb +373 -0
- data/lib/octo/openai_stream_aggregator.rb +130 -0
- data/lib/octo/plain_ui_controller.rb +166 -0
- data/lib/octo/providers.rb +534 -0
- data/lib/octo/server/browser_manager.rb +397 -0
- data/lib/octo/server/channel/adapters/base.rb +82 -0
- data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
- data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
- data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
- data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
- data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
- data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
- data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
- data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
- data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
- data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
- data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
- data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
- data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
- data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
- data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
- data/lib/octo/server/channel/channel_config.rb +178 -0
- data/lib/octo/server/channel/channel_manager.rb +468 -0
- data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
- data/lib/octo/server/channel.rb +33 -0
- data/lib/octo/server/discover.rb +77 -0
- data/lib/octo/server/epipe_safe_io.rb +105 -0
- data/lib/octo/server/http_server.rb +3554 -0
- data/lib/octo/server/scheduler.rb +317 -0
- data/lib/octo/server/server_master.rb +325 -0
- data/lib/octo/server/session_registry.rb +431 -0
- data/lib/octo/server/web_ui_controller.rb +487 -0
- data/lib/octo/session_manager.rb +385 -0
- data/lib/octo/skill.rb +466 -0
- data/lib/octo/skill_loader.rb +328 -0
- data/lib/octo/tools/base.rb +118 -0
- data/lib/octo/tools/browser.rb +625 -0
- data/lib/octo/tools/edit.rb +165 -0
- data/lib/octo/tools/file_reader.rb +549 -0
- data/lib/octo/tools/glob.rb +162 -0
- data/lib/octo/tools/grep.rb +356 -0
- data/lib/octo/tools/invoke_skill.rb +96 -0
- data/lib/octo/tools/list_tasks.rb +54 -0
- data/lib/octo/tools/redo_task.rb +41 -0
- data/lib/octo/tools/request_user_feedback.rb +84 -0
- data/lib/octo/tools/security.rb +333 -0
- data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
- data/lib/octo/tools/terminal/persistent_session.rb +268 -0
- data/lib/octo/tools/terminal/safe_rm.sh +106 -0
- data/lib/octo/tools/terminal/session_manager.rb +213 -0
- data/lib/octo/tools/terminal.rb +1828 -0
- data/lib/octo/tools/todo_manager.rb +374 -0
- data/lib/octo/tools/trash_manager.rb +388 -0
- data/lib/octo/tools/undo_task.rb +35 -0
- data/lib/octo/tools/web_fetch.rb +242 -0
- data/lib/octo/tools/web_search.rb +260 -0
- data/lib/octo/tools/write.rb +77 -0
- data/lib/octo/ui2/block_font.rb +10 -0
- data/lib/octo/ui2/components/base_component.rb +163 -0
- data/lib/octo/ui2/components/command_suggestions.rb +290 -0
- data/lib/octo/ui2/components/common_component.rb +96 -0
- data/lib/octo/ui2/components/inline_input.rb +226 -0
- data/lib/octo/ui2/components/input_area.rb +1338 -0
- data/lib/octo/ui2/components/message_component.rb +99 -0
- data/lib/octo/ui2/components/modal_component.rb +419 -0
- data/lib/octo/ui2/components/todo_area.rb +149 -0
- data/lib/octo/ui2/components/tool_component.rb +107 -0
- data/lib/octo/ui2/components/welcome_banner.rb +139 -0
- data/lib/octo/ui2/layout_manager.rb +807 -0
- data/lib/octo/ui2/line_editor.rb +363 -0
- data/lib/octo/ui2/markdown_renderer.rb +100 -0
- data/lib/octo/ui2/output_buffer.rb +370 -0
- data/lib/octo/ui2/progress_handle.rb +362 -0
- data/lib/octo/ui2/progress_indicator.rb +55 -0
- data/lib/octo/ui2/screen_buffer.rb +273 -0
- data/lib/octo/ui2/terminal_detector.rb +119 -0
- data/lib/octo/ui2/theme_manager.rb +85 -0
- data/lib/octo/ui2/themes/base_theme.rb +105 -0
- data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
- data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
- data/lib/octo/ui2/thinking_verbs.rb +26 -0
- data/lib/octo/ui2/ui_controller.rb +1625 -0
- data/lib/octo/ui2/view_renderer.rb +177 -0
- data/lib/octo/ui2.rb +40 -0
- data/lib/octo/ui_interface.rb +154 -0
- data/lib/octo/utils/arguments_parser.rb +191 -0
- data/lib/octo/utils/browser_detector.rb +195 -0
- data/lib/octo/utils/encoding.rb +92 -0
- data/lib/octo/utils/environment_detector.rb +140 -0
- data/lib/octo/utils/file_ignore_helper.rb +170 -0
- data/lib/octo/utils/file_processor.rb +601 -0
- data/lib/octo/utils/gitignore_parser.rb +154 -0
- data/lib/octo/utils/limit_stack.rb +152 -0
- data/lib/octo/utils/logger.rb +124 -0
- data/lib/octo/utils/login_shell.rb +72 -0
- data/lib/octo/utils/model_pricing.rb +646 -0
- data/lib/octo/utils/parser_manager.rb +165 -0
- data/lib/octo/utils/path_helper.rb +15 -0
- data/lib/octo/utils/scripts_manager.rb +59 -0
- data/lib/octo/utils/string_matcher.rb +158 -0
- data/lib/octo/utils/trash_directory.rb +112 -0
- data/lib/octo/utils/workspace_rules.rb +46 -0
- data/lib/octo/version.rb +5 -0
- data/lib/octo/web/app.css +7141 -0
- data/lib/octo/web/app.js +543 -0
- data/lib/octo/web/apple-touch-icon.png +0 -0
- data/lib/octo/web/auth.js +150 -0
- data/lib/octo/web/channels.js +276 -0
- data/lib/octo/web/datepicker.js +205 -0
- data/lib/octo/web/favicon.png +0 -0
- data/lib/octo/web/i18n.js +1073 -0
- data/lib/octo/web/icon-512.png +0 -0
- data/lib/octo/web/icon-dark.svg +25 -0
- data/lib/octo/web/icon.svg +29 -0
- data/lib/octo/web/index.html +871 -0
- data/lib/octo/web/marked.min.js +69 -0
- data/lib/octo/web/onboard.js +491 -0
- data/lib/octo/web/profile.js +442 -0
- data/lib/octo/web/sessions.js +4421 -0
- data/lib/octo/web/settings.js +913 -0
- data/lib/octo/web/sidebar.js +32 -0
- data/lib/octo/web/skills.js +885 -0
- data/lib/octo/web/tasks.js +297 -0
- data/lib/octo/web/theme.js +105 -0
- data/lib/octo/web/trash.js +343 -0
- data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
- data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/katex.min.css +1 -0
- data/lib/octo/web/vendor/katex/katex.min.js +1 -0
- data/lib/octo/web/version.js +449 -0
- data/lib/octo/web/weixin-qr.html +209 -0
- data/lib/octo/web/ws-dispatcher.js +357 -0
- data/lib/octo/web/ws.js +128 -0
- data/lib/octo.rb +145 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +56 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +157 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +174 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.ps1 +532 -0
- data/scripts/install.sh +567 -0
- data/scripts/install_browser.sh +479 -0
- data/scripts/install_full.sh +838 -0
- data/scripts/install_rails_deps.sh +746 -0
- data/scripts/install_system_deps.sh +518 -0
- data/scripts/uninstall.sh +287 -0
- data/sig/octo.rbs +4 -0
- metadata +614 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// channels.js — Channels panel (Agent-First design)
|
|
2
|
+
//
|
|
3
|
+
// Design principle: no configuration forms here.
|
|
4
|
+
// This page shows platform status only. All setup is done via Agent with browser automation.
|
|
5
|
+
// "Auto Setup" opens a chat session with /channel-manager pre-filled — the Agent will use
|
|
6
|
+
// browser automation to complete the entire setup on the platform's web console.
|
|
7
|
+
// "Test" runs /channel-manager doctor via the Agent and streams results.
|
|
8
|
+
|
|
9
|
+
const Channels = (() => {
|
|
10
|
+
|
|
11
|
+
// Platform display metadata (use accessor to pick up runtime language)
|
|
12
|
+
// SVG sources: dashboard-icons (Lark, multi-color brand mark),
|
|
13
|
+
// TDesign icons (WeCom/WeChat, single-color), simpleicons (Discord/Telegram),
|
|
14
|
+
// ant-design/ant-design-icons outlined (DingTalk).
|
|
15
|
+
function PLATFORM_META() {
|
|
16
|
+
return {
|
|
17
|
+
feishu: {
|
|
18
|
+
logo: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="62.16 94.5 407.87 324.19" aria-hidden="true"><path d="M274.18 264.785q.515-.517 1.03-1.027c.685-.688 1.372-1.258 2.056-1.945l1.37-1.372 4.118-4.113 5.598-5.601 4.8-4.797 4.575-4.457 4.796-4.688 4.344-4.344 6.059-6.054c1.14-1.145 2.285-2.29 3.543-3.317 2.168-2.054 4.457-4 6.855-5.828 2.172-1.715 4.344-3.312 6.516-4.914 3.082-2.172 6.398-4.344 9.71-6.285 3.204-1.941 6.63-3.656 10.06-5.371 3.199-1.602 6.515-2.973 9.827-4.23 1.829-.684 3.774-1.372 5.602-2.055.914-.344 1.941-.688 2.856-.914-8.57-33.715-24.227-64.575-45.258-90.86-4.114-5.14-10.399-8.113-17.028-8.113H130.754c-3.203 0-4.457 4-1.945 5.941 59.543 43.66 109.144 99.887 145.03 164.801 0-.226.227-.34.34-.457m0 0" fill="#00d6b9"/><path d="M204.79 418.691c90.288 0 169.03-49.828 210.058-123.543 1.488-2.628 2.859-5.257 4.23-7.882q-3.087 6-6.86 11.312l-2.741 3.77c-1.141 1.488-2.399 2.972-3.657 4.457-1.03 1.144-2.058 2.285-3.086 3.316-2.058 2.172-4.343 4.227-6.629 6.172a53 53 0 0 1-3.886 3.2c-1.598 1.144-3.086 2.284-4.684 3.429-1.031.683-2.058 1.371-3.086 1.941-1.144.684-2.172 1.258-3.316 1.942a131 131 0 0 1-6.969 3.543c-2.059.918-4.117 1.828-6.289 2.515-2.285.801-4.57 1.602-6.969 2.285-3.543.914-7.086 1.715-10.742 2.286-2.629.457-5.258.687-8 .914-2.86.23-5.601.23-8.457.23-3.086 0-6.289-.23-9.488-.57a83 83 0 0 1-7.086-1.031c-2.055-.34-4.113-.801-6.168-1.258-1.031-.227-2.176-.57-3.203-.797-2.973-.8-6.055-1.602-9.028-2.516-1.488-.457-2.972-.914-4.457-1.258-2.172-.683-4.457-1.37-6.629-2.058-1.828-.57-3.656-1.14-5.37-1.711q-2.573-.86-5.145-1.715c-1.14-.344-2.285-.8-3.543-1.144-1.371-.457-2.856-1.028-4.227-1.485-1.027-.344-2.058-.687-2.972-1.027-1.942-.688-4-1.488-5.942-2.172-1.144-.457-2.285-.914-3.43-1.258-1.484-.57-3.085-1.144-4.57-1.828-1.601-.687-3.203-1.258-4.8-1.945-1.028-.457-2.06-.797-3.087-1.258-1.257-.57-2.628-1.027-3.886-1.598-1.028-.457-1.942-.8-2.969-1.258l-3.086-1.37c-.914-.344-1.832-.801-2.746-1.145a44 44 0 0 1-2.512-1.14c-.8-.345-1.715-.802-2.515-1.145-.914-.344-1.715-.801-2.512-1.141-1.031-.457-2.172-1.031-3.203-1.484-1.14-.575-2.285-1.032-3.426-1.602-1.258-.574-2.402-1.144-3.66-1.715-1.027-.457-2.055-1.027-3.082-1.484-54.172-26.973-102.172-63.086-143.09-106.746-2.055-2.172-5.71-.684-5.71 2.289l.112 154.398v12.57c0 7.317 3.543 14.06 9.598 18.172 38.172 24.801 83.773 39.543 132.914 39.543m0 0" fill="#3370ff"/><path d="M414.84 295.188c0 .113-.113.113-.113.226zl.8-1.489c-.343.457-.574 1.028-.8 1.488m3.793-7.05.226-.457.114-.23q-.17.513-.34.687m0 0" fill="#133c9a"/><path d="M470.035 201.121c-18.285-9.031-38.86-14.059-60.687-14.059-12.914 0-25.485 1.829-37.371 5.141-1.372.344-2.743.8-4.114 1.258-.914.344-1.941.574-2.855.914-1.945.688-3.774 1.375-5.602 2.059-3.316 1.257-6.629 2.742-9.828 4.23-3.43 1.598-6.742 3.426-10.058 5.371a128 128 0 0 0-9.715 6.285c-2.285 1.602-4.457 3.2-6.512 4.914a154 154 0 0 0-6.86 5.828c-1.14 1.141-2.398 2.172-3.542 3.313l-6.055 6.059-4.344 4.343-4.8 4.684-4.57 4.46-4.802 4.798-11.086 11.086c-.687.687-1.37 1.37-2.058 1.945l-1.028 1.027c-.457.457-1.027 1.028-1.601 1.485-.57.57-1.14 1.031-1.711 1.601a244.4 244.4 0 0 1-49.828 35.313c1.027.457 2.168 1.027 3.199 1.488.8.34 1.715.797 2.512 1.14.8.344 1.715.801 2.515 1.145.801.344 1.602.684 2.516 1.14.914.345 1.828.802 2.742 1.145l3.086 1.371c1.027.457 1.942.801 2.969 1.258 1.258.57 2.629 1.028 3.887 1.598 1.03.46 2.058.8 3.086 1.258 1.601.687 3.199 1.258 4.8 1.945 1.485.57 3.086 1.14 4.57 1.828 1.145.457 2.286.914 3.43 1.258 1.946.684 4 1.484 5.946 2.172a81 81 0 0 1 2.968 1.027c1.371.457 2.856 1.028 4.23 1.485 1.141.343 2.286.8 3.544 1.14q2.567.86 5.14 1.719c1.829.57 3.657 1.14 5.372 1.71 2.171.688 4.457 1.376 6.628 2.06 1.489.457 2.973.914 4.457 1.257 2.973.914 5.942 1.715 9.032 2.512 1.027.344 2.168.574 3.199.8 2.055.458 4.113.915 6.172 1.259 2.398.457 4.683.8 7.082 1.03 3.203.34 6.402.571 9.488.571 2.856 0 5.715 0 8.457-.23 2.63-.227 5.371-.457 8-.914 3.656-.57 7.2-1.371 10.742-2.286 2.399-.683 4.688-1.37 6.973-2.285 2.172-.8 4.227-1.601 6.285-2.515 2.399-1.028 4.684-2.285 6.973-3.543 1.14-.57 2.168-1.258 3.312-1.942 1.028-.687 2.059-1.257 3.086-1.945 1.602-1.027 3.2-2.168 4.684-3.426a52 52 0 0 0 3.887-3.203c2.289-1.941 4.457-4 6.628-6.168 1.032-1.031 2.06-2.172 3.086-3.316 1.258-1.485 2.516-2.969 3.657-4.457.918-1.258 1.828-2.512 2.742-3.77 2.515-3.543 4.8-7.316 6.86-11.199l2.284-4.688 21.145-42.171v.113c6.742-14.742 16.226-28.113 27.656-39.426m0 0" fill="#133c9a"/></svg>`,
|
|
19
|
+
logoClass: "channel-logo-feishu",
|
|
20
|
+
name: "Feishu / Lark",
|
|
21
|
+
desc: I18n.t("channels.feishu.desc"),
|
|
22
|
+
setupCmd: "/channel-manager setup feishu",
|
|
23
|
+
testCmd: "/channel-manager doctor",
|
|
24
|
+
},
|
|
25
|
+
wecom: {
|
|
26
|
+
logo: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="m17.326 8.158l-.003-.007a6.6 6.6 0 0 0-1.178-1.674c-1.266-1.307-3.067-2.19-5.102-2.417a9.3 9.3 0 0 0-2.124 0h-.001c-2.061.228-3.882 1.107-5.14 2.405a6.7 6.7 0 0 0-1.194 1.682A5.7 5.7 0 0 0 2 10.657c0 1.106.332 2.218.988 3.201l.006.01c.391.594 1.092 1.39 1.637 1.83l.983.793l-.208.875l.527-.267l.708-.358l.761.225c.467.137.955.227 1.517.29h.005q.515.06 1.026.059c.355 0 .724-.02 1.095-.06a9 9 0 0 0 1.346-.258c.095.7.43 1.337.932 1.81c-.658.208-1.352.358-2.061.436c-.442.048-.883.072-1.312.072q-.627 0-1.253-.072a10.7 10.7 0 0 1-1.861-.36l-2.84 1.438s-.29.131-.44.131c-.418 0-.702-.285-.702-.704c0-.252.067-.598.128-.84l.394-1.653c-.728-.586-1.563-1.544-2.052-2.287A7.76 7.76 0 0 1 0 10.658a7.7 7.7 0 0 1 .787-3.39a8.7 8.7 0 0 1 1.551-2.19c1.61-1.665 3.878-2.73 6.359-3.006a11.3 11.3 0 0 1 2.565 0c2.47.275 4.712 1.353 6.323 3.017a8.6 8.6 0 0 1 1.539 2.192c.466.945.769 1.937.769 2.978a3.06 3.06 0 0 0-2-.005c-.001-.644-.189-1.329-.564-2.09zm4.125 6.977l-.024-.024l-.024-.018l-.024-.018l-.096-.095a4.24 4.24 0 0 1-1.169-2.192q0-.038-.006-.075l-.006-.056l-.035-.144a1.3 1.3 0 0 0-.358-.61a1.386 1.386 0 0 0-1.957 0a1.4 1.4 0 0 0 0 1.963c.191.191.418.311.668.371c.024.012.06.012.084.012q.019 0 .041.006q.023.005.042.006a4.24 4.24 0 0 1 2.231 1.186c.048.048.096.095.131.143a.323.323 0 0 0 .466 0a.35.35 0 0 0 .036-.455m-1.05 4.37l-.025.025c-.119.096-.31.096-.453-.036a.326.326 0 0 1 0-.467c.047-.036.094-.083.141-.13l.002-.002a4.27 4.27 0 0 0 1.187-2.28q.005-.024.006-.043c0-.024 0-.06.012-.084a1.386 1.386 0 0 1 2.326-.67a1.4 1.4 0 0 1 0 1.964c-.167.18-.382.299-.608.359l-.143.036l-.057.005q-.035.006-.075.007a4.2 4.2 0 0 0-2.183 1.173l-.095.096q-.009.01-.018.024t-.018.024m-4.392-1.053l.024.024l.024.018q.015.009.024.018l.096.096a4.25 4.25 0 0 1 1.169 2.19q0 .04.006.076q.005.03.006.057l.035.143c.06.228.18.443.358.611c.537.539 1.42.539 1.957 0a1.4 1.4 0 0 0 0-1.964a1.4 1.4 0 0 0-.668-.371c-.024-.012-.06-.012-.084-.012q-.018 0-.041-.006l-.042-.006a4.25 4.25 0 0 1-2.231-1.185a1.4 1.4 0 0 1-.131-.144a.323.323 0 0 0-.466 0a.325.325 0 0 0-.036.455m1.039-4.358l.024-.024a.32.32 0 0 1 .453.035a.326.326 0 0 1 0 .467c-.047.036-.094.083-.141.13l-.002.002a4.27 4.27 0 0 0-1.187 2.281l-.006.042c0 .024 0 .06-.012.084a1.386 1.386 0 0 1-2.326.67a1.4 1.4 0 0 1 0-1.963c.166-.18.381-.3.608-.36l.143-.035q.026 0 .056-.006q.037-.005.075-.006a4.2 4.2 0 0 0 2.183-1.174l.096-.095l.018-.025z"/></svg>`,
|
|
27
|
+
logoClass: "channel-logo-wecom",
|
|
28
|
+
name: "WeCom",
|
|
29
|
+
desc: I18n.t("channels.wecom.desc"),
|
|
30
|
+
setupCmd: "/channel-manager setup wecom",
|
|
31
|
+
testCmd: "/channel-manager doctor",
|
|
32
|
+
},
|
|
33
|
+
weixin: {
|
|
34
|
+
logo: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M8.796 17.027H8.75c-1.153 0-2.254-.188-3.262-.53L2.65 17.92l.352-2.712C1.162 13.855 0 11.861 0 9.64c0-4.083 3.918-7.39 8.75-7.39c4.174 0 7.665 2.468 8.54 5.77a9 9 0 0 0-.6-.02c-4.364 0-8.19 3.037-8.19 7.11c0 .67.104 1.312.296 1.917M6 8a1 1 0 1 0 0-2a1 1 0 0 0 0 2m5.5.007a1 1 0 1 0 0-2a1 1 0 0 0 0 2"/><path fill="#fff" d="M21.874 19.52C23.187 18.405 24 16.863 24 15.16C24 11.758 20.754 9 16.75 9S9.5 11.758 9.5 15.161s3.246 6.161 7.25 6.161c.95 0 1.856-.155 2.686-.437l2.438 1.407zm-7.564-5.362a1 1 0 1 1 0-2a1 1 0 0 1 0 2m4.88 0a1 1 0 1 1 0-2a1 1 0 0 1 0 2"/></svg>`,
|
|
35
|
+
logoClass: "channel-logo-weixin",
|
|
36
|
+
name: "Weixin",
|
|
37
|
+
desc: I18n.t("channels.weixin.desc"),
|
|
38
|
+
setupCmd: "/channel-manager setup weixin",
|
|
39
|
+
testCmd: "/channel-manager doctor",
|
|
40
|
+
},
|
|
41
|
+
dingtalk: {
|
|
42
|
+
logo: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" aria-hidden="true"><path fill="#fff" d="M573.7 252.5C422.5 197.4 201.3 96.7 201.3 96.7c-15.7-4.1-17.9 11.1-17.9 11.1c-5 61.1 33.6 160.5 53.6 182.8c19.9 22.3 319.1 113.7 319.1 113.7S326 357.9 270.5 341.9c-55.6-16-37.9 17.8-37.9 17.8c11.4 61.7 64.9 131.8 107.2 138.4c42.2 6.6 220.1 4 220.1 4s-35.5 4.1-93.2 11.9c-42.7 5.8-97 12.5-111.1 17.8c-33.1 12.5 24 62.6 24 62.6c84.7 76.8 129.7 50.5 129.7 50.5c33.3-10.7 61.4-18.5 85.2-24.2L565 743.1h84.6L603 928l205.3-271.9H700.8l22.3-38.7c.3.5.4.8.4.8S799.8 496.1 829 433.8l.6-1h-.1c5-10.8 8.6-19.7 10-25.8c17-71.3-114.5-99.4-265.8-154.5"/></svg>`,
|
|
43
|
+
logoClass: "channel-logo-dingtalk",
|
|
44
|
+
name: "DingTalk",
|
|
45
|
+
desc: I18n.t("channels.dingtalk.desc"),
|
|
46
|
+
setupCmd: "/channel-manager setup dingtalk",
|
|
47
|
+
testCmd: "/channel-manager doctor",
|
|
48
|
+
},
|
|
49
|
+
discord: {
|
|
50
|
+
logo: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>`,
|
|
51
|
+
logoClass: "channel-logo-discord",
|
|
52
|
+
name: "Discord",
|
|
53
|
+
desc: I18n.t("channels.discord.desc"),
|
|
54
|
+
setupCmd: "/channel-manager setup discord",
|
|
55
|
+
testCmd: "/channel-manager doctor",
|
|
56
|
+
},
|
|
57
|
+
telegram: {
|
|
58
|
+
logo: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>`,
|
|
59
|
+
logoClass: "channel-logo-telegram",
|
|
60
|
+
name: "Telegram",
|
|
61
|
+
desc: I18n.t("channels.telegram.desc"),
|
|
62
|
+
setupCmd: "/channel-manager setup telegram",
|
|
63
|
+
testCmd: "/channel-manager doctor",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
async function onPanelShow() {
|
|
71
|
+
await _load();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Data Loading ─────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
async function _load({ silent = false } = {}) {
|
|
77
|
+
const container = $("channels-list");
|
|
78
|
+
if (!container) return;
|
|
79
|
+
if (!silent) {
|
|
80
|
+
container.innerHTML = `<div class="channel-loading">${I18n.t("channels.loading")}</div>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch("/api/channels");
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
_render(data.channels || []);
|
|
87
|
+
} catch (e) {
|
|
88
|
+
container.innerHTML = `<div class="channel-error">${I18n.t("channels.loadError", { msg: _esc(e.message) })}</div>`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Rendering ─────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
function _render(channels) {
|
|
95
|
+
const container = $("channels-list");
|
|
96
|
+
if (!container) return;
|
|
97
|
+
container.innerHTML = "";
|
|
98
|
+
|
|
99
|
+
// Merge server data with display metadata, show all known platforms
|
|
100
|
+
const meta = PLATFORM_META();
|
|
101
|
+
const platformIds = Object.keys(meta);
|
|
102
|
+
platformIds.forEach(pid => {
|
|
103
|
+
const serverData = channels.find(c => c.platform == pid) || {};
|
|
104
|
+
container.appendChild(_renderCard(pid, serverData, meta[pid]));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function _renderCard(platform, data, meta) {
|
|
109
|
+
const enabled = !!data.enabled;
|
|
110
|
+
const running = !!data.running;
|
|
111
|
+
const hasConfig = !!data.has_config;
|
|
112
|
+
|
|
113
|
+
const card = document.createElement("div");
|
|
114
|
+
card.className = "channel-card";
|
|
115
|
+
card.id = `channel-card-${platform}`;
|
|
116
|
+
|
|
117
|
+
card.innerHTML = `
|
|
118
|
+
<div class="channel-card-header">
|
|
119
|
+
<div class="channel-card-identity">
|
|
120
|
+
<span class="channel-logo ${_esc(meta.logoClass)}">${meta.logo}</span>
|
|
121
|
+
<div>
|
|
122
|
+
<div class="channel-card-name">${_esc(meta.name)}</div>
|
|
123
|
+
<div class="channel-card-desc">${_esc(meta.desc)}</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="channel-card-status">
|
|
127
|
+
${hasConfig ? _toggleHtml(platform, enabled) : ""}
|
|
128
|
+
<span class="channel-status-badge" id="channel-badge-${_esc(platform)}">${_badgeHtml(enabled, running, hasConfig)}</span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="channel-card-body">
|
|
133
|
+
${_statusHint(enabled, running, hasConfig)}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="channel-card-footer">
|
|
137
|
+
<div class="channel-card-actions">
|
|
138
|
+
<button class="btn-channel-test btn-secondary" id="btn-test-${_esc(platform)}">
|
|
139
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
140
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
141
|
+
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
142
|
+
</svg>
|
|
143
|
+
${I18n.t("channels.btn.test")}
|
|
144
|
+
</button>
|
|
145
|
+
<button class="btn-channel-configure btn-primary" id="btn-configure-${_esc(platform)}">
|
|
146
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
147
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
148
|
+
</svg>
|
|
149
|
+
${hasConfig ? I18n.t("channels.btn.reconfigure") : I18n.t("channels.btn.setup")}
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
// Bind events
|
|
156
|
+
card.querySelector(`#btn-test-${platform}`)?.addEventListener("click", () => _runTest(platform));
|
|
157
|
+
card.querySelector(`#btn-configure-${platform}`)?.addEventListener("click", () => _openSetup(platform));
|
|
158
|
+
card.querySelector(`#toggle-${platform}`)?.addEventListener("change", (ev) => _onToggle(platform, ev.target));
|
|
159
|
+
|
|
160
|
+
return card;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function _toggleHtml(platform, enabled) {
|
|
164
|
+
const aria = I18n.t(enabled ? "channels.toggle.on" : "channels.toggle.off");
|
|
165
|
+
return `
|
|
166
|
+
<label class="toggle-switch" title="${_esc(aria)}">
|
|
167
|
+
<input type="checkbox" id="toggle-${_esc(platform)}" ${enabled ? "checked" : ""} aria-label="${_esc(aria)}">
|
|
168
|
+
<span class="toggle-slider"></span>
|
|
169
|
+
</label>
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Badge & status hint helpers ───────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
function _badgeHtml(enabled, running, hasConfig) {
|
|
176
|
+
if (running) return `<span class="badge-running">● ${I18n.t("channels.badge.running")}</span>`;
|
|
177
|
+
if (enabled) return `<span class="badge-enabled">● ${I18n.t("channels.badge.enabled")}</span>`;
|
|
178
|
+
if (hasConfig) return `<span class="badge-disabled">○ ${I18n.t("channels.badge.disabled")}</span>`;
|
|
179
|
+
return `<span class="badge-disabled">○ ${I18n.t("channels.badge.notConfigured")}</span>`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function _statusHint(enabled, running, hasConfig) {
|
|
183
|
+
if (running) {
|
|
184
|
+
return `<p class="channel-status-hint hint-ok">✓ ${I18n.t("channels.hint.running")}</p>`;
|
|
185
|
+
}
|
|
186
|
+
if (enabled) {
|
|
187
|
+
return `<p class="channel-status-hint hint-warn">⚠ ${I18n.t("channels.hint.enabledNotRunning")}</p>`;
|
|
188
|
+
}
|
|
189
|
+
if (hasConfig) {
|
|
190
|
+
return `<p class="channel-status-hint hint-idle">${I18n.t("channels.hint.disabled")}</p>`;
|
|
191
|
+
}
|
|
192
|
+
return `<p class="channel-status-hint hint-idle">${I18n.t("channels.hint.notConfigured")}</p>`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Toggle handler ───────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
async function _onToggle(platform, checkbox) {
|
|
198
|
+
const desired = checkbox.checked;
|
|
199
|
+
checkbox.disabled = true;
|
|
200
|
+
try {
|
|
201
|
+
const res = await fetch(`/api/channels/${encodeURIComponent(platform)}/enabled`, {
|
|
202
|
+
method: "PATCH",
|
|
203
|
+
headers: { "Content-Type": "application/json" },
|
|
204
|
+
body: JSON.stringify({ enabled: desired }),
|
|
205
|
+
});
|
|
206
|
+
const data = await res.json();
|
|
207
|
+
if (!res.ok || !data.ok) throw new Error(data.error || "toggle failed");
|
|
208
|
+
await _load({ silent: true });
|
|
209
|
+
} catch (e) {
|
|
210
|
+
checkbox.checked = !desired;
|
|
211
|
+
alert("Error: " + e.message);
|
|
212
|
+
} finally {
|
|
213
|
+
checkbox.disabled = false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Actions ───────────────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
// Run E2E test: open a session and send /channel-manager doctor
|
|
220
|
+
async function _runTest(platform) {
|
|
221
|
+
const meta = PLATFORM_META()[platform];
|
|
222
|
+
await _sendToAgent(meta.testCmd, `Channel E2E Test — ${meta.name}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Open setup: open a session and send /channel-manager setup <platform>
|
|
226
|
+
async function _openSetup(platform) {
|
|
227
|
+
const meta = PLATFORM_META()[platform];
|
|
228
|
+
await _sendToAgent(meta.setupCmd, `Channel Setup — ${meta.name}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Create a session, add it to the list, navigate to it, and send the given command.
|
|
232
|
+
// Follows the same pattern as Skills.createInSession().
|
|
233
|
+
async function _sendToAgent(command, sessionName) {
|
|
234
|
+
try {
|
|
235
|
+
// Pick a session name in "Session N" style, consistent with other modules
|
|
236
|
+
const maxN = Sessions.all.reduce((max, s) => {
|
|
237
|
+
const m = s.name.match(/^Session (\d+)$/);
|
|
238
|
+
return m ? Math.max(max, parseInt(m[1], 10)) : max;
|
|
239
|
+
}, 0);
|
|
240
|
+
const name = sessionName || ("Session " + (maxN + 1));
|
|
241
|
+
|
|
242
|
+
const res = await fetch("/api/sessions", {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: { "Content-Type": "application/json" },
|
|
245
|
+
body: JSON.stringify({ name, source: "setup" }),
|
|
246
|
+
});
|
|
247
|
+
const data = await res.json();
|
|
248
|
+
if (!res.ok) throw new Error(data.error || I18n.t("channels.sessionError"));
|
|
249
|
+
const session = data.session;
|
|
250
|
+
if (!session) throw new Error(I18n.t("channels.noSession"));
|
|
251
|
+
|
|
252
|
+
// Register in Sessions, refresh sidebar, queue command, then navigate
|
|
253
|
+
Sessions.add(session);
|
|
254
|
+
Sessions.renderList();
|
|
255
|
+
Sessions.setPendingMessage(session.id, command);
|
|
256
|
+
Sessions.select(session.id);
|
|
257
|
+
} catch (e) {
|
|
258
|
+
alert("Error: " + e.message);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
function _esc(str) {
|
|
265
|
+
return String(str || "")
|
|
266
|
+
.replace(/&/g, "&")
|
|
267
|
+
.replace(/</g, "<")
|
|
268
|
+
.replace(/>/g, ">")
|
|
269
|
+
.replace(/"/g, """);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
onPanelShow,
|
|
274
|
+
init() {}, // no static DOM to bind; events bound per-render
|
|
275
|
+
};
|
|
276
|
+
})();
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// datepicker.js — Lightweight custom calendar picker
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// Any <button data-value=""> with class "datepicker-trigger" gets a calendar
|
|
5
|
+
// popup on click. When a date is selected the button's data-value is set to
|
|
6
|
+
// an ISO string (YYYY-MM-DD) and a "datepicker:change" CustomEvent bubbles up.
|
|
7
|
+
//
|
|
8
|
+
// DatePicker.clear(el) — clear value and label
|
|
9
|
+
// DatePicker.init(el) — programmatically attach (optional; click delegation
|
|
10
|
+
// handles all .datepicker-trigger elements automatically)
|
|
11
|
+
|
|
12
|
+
const DatePicker = (() => {
|
|
13
|
+
const LOCALE = {
|
|
14
|
+
en: {
|
|
15
|
+
days: ["Su","Mo","Tu","We","Th","Fr","Sa"],
|
|
16
|
+
months: ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
|
|
17
|
+
title: (y, m, months) => `${months[m]} ${y}`,
|
|
18
|
+
clear: "Clear",
|
|
19
|
+
today: "Today",
|
|
20
|
+
label: (y, m, d) => `${y}/${String(m + 1).padStart(2,"0")}/${String(d).padStart(2,"0")}`,
|
|
21
|
+
},
|
|
22
|
+
zh: {
|
|
23
|
+
days: ["日","一","二","三","四","五","六"],
|
|
24
|
+
months: ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],
|
|
25
|
+
title: (y, m, months) => `${y}年${months[m]}`,
|
|
26
|
+
clear: "清除",
|
|
27
|
+
today: "今天",
|
|
28
|
+
label: (y, m, d) => `${y}/${String(m + 1).padStart(2,"0")}/${String(d).padStart(2,"0")}`,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function _loc() {
|
|
33
|
+
const code = (typeof I18n !== "undefined") ? I18n.lang() : "en";
|
|
34
|
+
return LOCALE[code] || LOCALE.en;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function _pad(n) { return String(n).padStart(2, "0"); }
|
|
38
|
+
function _toISO(y, m, d) { return `${y}-${_pad(m + 1)}-${_pad(d)}`; }
|
|
39
|
+
|
|
40
|
+
let _popup = null;
|
|
41
|
+
let _anchor = null;
|
|
42
|
+
let _year = 0;
|
|
43
|
+
let _month = 0;
|
|
44
|
+
|
|
45
|
+
function _ensurePopup() {
|
|
46
|
+
if (_popup) return;
|
|
47
|
+
_popup = document.createElement("div");
|
|
48
|
+
_popup.className = "dp-popup";
|
|
49
|
+
_popup.hidden = true;
|
|
50
|
+
document.body.appendChild(_popup);
|
|
51
|
+
|
|
52
|
+
_popup.addEventListener("click", (e) => {
|
|
53
|
+
const nav = e.target.closest(".dp-nav");
|
|
54
|
+
if (nav) {
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
_month += parseInt(nav.dataset.dir);
|
|
57
|
+
if (_month < 0) { _month = 11; _year--; }
|
|
58
|
+
if (_month > 11) { _month = 0; _year++; }
|
|
59
|
+
_render();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const day = e.target.closest("[data-date]");
|
|
63
|
+
if (day) { _pick(day.dataset.date); return; }
|
|
64
|
+
if (e.target.closest(".dp-btn-clear")) {
|
|
65
|
+
clear(_anchor); _dispatch(); _close(); return;
|
|
66
|
+
}
|
|
67
|
+
if (e.target.closest(".dp-btn-today")) {
|
|
68
|
+
const t = new Date();
|
|
69
|
+
_pick(_toISO(t.getFullYear(), t.getMonth(), t.getDate()));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _render() {
|
|
75
|
+
const loc = _loc();
|
|
76
|
+
const selVal = _anchor ? (_anchor.dataset.value || "") : "";
|
|
77
|
+
const today = new Date();
|
|
78
|
+
const tY = today.getFullYear(), tM = today.getMonth(), tD = today.getDate();
|
|
79
|
+
|
|
80
|
+
const firstWeekday = new Date(_year, _month, 1).getDay();
|
|
81
|
+
const daysInMonth = new Date(_year, _month + 1, 0).getDate();
|
|
82
|
+
|
|
83
|
+
let html = `<div class="dp-header">
|
|
84
|
+
<button class="dp-nav" data-dir="-12" type="button">≪</button>
|
|
85
|
+
<button class="dp-nav" data-dir="-1" type="button">‹</button>
|
|
86
|
+
<span class="dp-title">${loc.title(_year, _month, loc.months)}</span>
|
|
87
|
+
<button class="dp-nav" data-dir="1" type="button">›</button>
|
|
88
|
+
<button class="dp-nav" data-dir="12" type="button">≫</button>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="dp-grid">`;
|
|
91
|
+
|
|
92
|
+
loc.days.forEach(d => { html += `<div class="dp-dow">${d}</div>`; });
|
|
93
|
+
for (let i = 0; i < firstWeekday; i++) html += `<div></div>`;
|
|
94
|
+
|
|
95
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
96
|
+
const iso = _toISO(_year, _month, d);
|
|
97
|
+
const cls = ["dp-day",
|
|
98
|
+
iso === selVal ? "dp-day--selected" : "",
|
|
99
|
+
(_year === tY && _month === tM && d === tD) ? "dp-day--today" : "",
|
|
100
|
+
].filter(Boolean).join(" ");
|
|
101
|
+
html += `<button class="${cls}" data-date="${iso}" type="button">${d}</button>`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
html += `</div>
|
|
105
|
+
<div class="dp-footer">
|
|
106
|
+
<button class="dp-btn-clear" type="button">${loc.clear}</button>
|
|
107
|
+
<button class="dp-btn-today" type="button">${loc.today}</button>
|
|
108
|
+
</div>`;
|
|
109
|
+
|
|
110
|
+
_popup.innerHTML = html;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function _pick(iso) {
|
|
114
|
+
if (!_anchor) return;
|
|
115
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
116
|
+
const loc = _loc();
|
|
117
|
+
_anchor.dataset.value = iso;
|
|
118
|
+
_anchor.textContent = loc.label(y, m - 1, d);
|
|
119
|
+
_anchor.dataset.active = "true";
|
|
120
|
+
_anchor.removeAttribute("data-i18n");
|
|
121
|
+
_dispatch();
|
|
122
|
+
_close();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function _dispatch() {
|
|
126
|
+
if (!_anchor) return;
|
|
127
|
+
_anchor.dispatchEvent(new CustomEvent("datepicker:change", { bubbles: true }));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function _position() {
|
|
131
|
+
if (!_anchor || !_popup || _popup.hidden) return;
|
|
132
|
+
const rect = _anchor.getBoundingClientRect();
|
|
133
|
+
_popup.style.left = rect.left + "px";
|
|
134
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
135
|
+
if (spaceBelow >= 290) {
|
|
136
|
+
_popup.style.top = (rect.bottom + 4) + "px";
|
|
137
|
+
_popup.style.bottom = "auto";
|
|
138
|
+
} else {
|
|
139
|
+
_popup.style.top = "auto";
|
|
140
|
+
_popup.style.bottom = (window.innerHeight - rect.top + 4) + "px";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function _open(anchorEl) {
|
|
145
|
+
_ensurePopup();
|
|
146
|
+
_anchor = anchorEl;
|
|
147
|
+
const val = anchorEl.dataset.value;
|
|
148
|
+
if (val) {
|
|
149
|
+
const d = new Date(val + "T00:00:00");
|
|
150
|
+
_year = d.getFullYear(); _month = d.getMonth();
|
|
151
|
+
} else {
|
|
152
|
+
const now = new Date();
|
|
153
|
+
_year = now.getFullYear(); _month = now.getMonth();
|
|
154
|
+
}
|
|
155
|
+
_render();
|
|
156
|
+
_popup.hidden = false;
|
|
157
|
+
_position();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function _close() {
|
|
161
|
+
if (_popup) _popup.hidden = true;
|
|
162
|
+
_anchor = null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Click delegation — open on trigger, close on outside click
|
|
166
|
+
document.addEventListener("click", (e) => {
|
|
167
|
+
const trigger = e.target.closest(".datepicker-trigger");
|
|
168
|
+
if (trigger) {
|
|
169
|
+
e.stopPropagation();
|
|
170
|
+
if (_anchor === trigger) { _close(); return; }
|
|
171
|
+
_open(trigger);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (_popup && !_popup.hidden && !e.target.closest(".dp-popup")) _close();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
window.addEventListener("scroll", _position, true);
|
|
178
|
+
window.addEventListener("resize", _position);
|
|
179
|
+
|
|
180
|
+
// Re-render popup label when language changes
|
|
181
|
+
document.addEventListener("langchange", () => {
|
|
182
|
+
if (_popup && !_popup.hidden && _anchor) _render();
|
|
183
|
+
_refreshTriggers();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
function _refreshTriggers() {
|
|
187
|
+
const loc = _loc();
|
|
188
|
+
document.querySelectorAll(".datepicker-trigger[data-value]").forEach(el => {
|
|
189
|
+
const val = el.dataset.value;
|
|
190
|
+
if (!val) return;
|
|
191
|
+
const [y, m, d] = val.split("-").map(Number);
|
|
192
|
+
el.textContent = loc.label(y, m - 1, d);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function clear(el) {
|
|
197
|
+
if (!el) return;
|
|
198
|
+
el.dataset.value = "";
|
|
199
|
+
el.dataset.active = "false";
|
|
200
|
+
el.setAttribute("data-i18n", "sessions.search.datePlaceholder");
|
|
201
|
+
if (typeof I18n !== "undefined") el.textContent = I18n.t("sessions.search.datePlaceholder");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { clear };
|
|
205
|
+
})();
|
|
Binary file
|