entari-plugin-hyw 4.0.0rc3__tar.gz → 4.0.0rc5__tar.gz

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.

Potentially problematic release.


This version of entari-plugin-hyw might be problematic. Click here for more details.

Files changed (111) hide show
  1. {entari_plugin_hyw-4.0.0rc3/src/entari_plugin_hyw.egg-info → entari_plugin_hyw-4.0.0rc5}/PKG-INFO +4 -4
  2. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/pyproject.toml +4 -4
  3. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/__init__.py +174 -73
  4. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/index.html +70 -79
  5. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/browser/__init__.py +10 -0
  6. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/browser/engines/base.py +13 -0
  7. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/browser/engines/bing.py +95 -0
  8. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/browser/engines/searxng.py +137 -0
  9. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/browser/landing.html +172 -0
  10. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/browser/manager.py +153 -0
  11. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/browser/service.py +275 -0
  12. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/card-ui/src/App.vue +756 -0
  13. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +7 -11
  14. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/components/StageCard.vue +33 -30
  15. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/types.ts +9 -0
  16. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/definitions.py +130 -0
  17. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/history.py +248 -0
  18. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/modular_pipeline.py +351 -0
  19. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/render_vue.py +401 -0
  20. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/search.py +116 -0
  21. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/stage_base.py +88 -0
  22. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/stage_instruct.py +328 -0
  23. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/stage_instruct_review.py +92 -0
  24. entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw/stage_summary.py +164 -0
  25. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5/src/entari_plugin_hyw.egg-info}/PKG-INFO +4 -4
  26. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw.egg-info/SOURCES.txt +13 -2
  27. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw.egg-info/requires.txt +3 -3
  28. entari_plugin_hyw-4.0.0rc3/src/entari_plugin_hyw/card-ui/src/App.vue +0 -412
  29. entari_plugin_hyw-4.0.0rc3/src/entari_plugin_hyw/history.py +0 -170
  30. entari_plugin_hyw-4.0.0rc3/src/entari_plugin_hyw/pipeline.py +0 -1240
  31. entari_plugin_hyw-4.0.0rc3/src/entari_plugin_hyw/prompts.py +0 -47
  32. entari_plugin_hyw-4.0.0rc3/src/entari_plugin_hyw/render_vue.py +0 -314
  33. entari_plugin_hyw-4.0.0rc3/src/entari_plugin_hyw/search.py +0 -735
  34. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/MANIFEST.in +0 -0
  35. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/README.md +0 -0
  36. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/setup.cfg +0 -0
  37. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/anthropic.svg +0 -0
  38. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/cerebras.svg +0 -0
  39. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/deepseek.png +0 -0
  40. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/gemini.svg +0 -0
  41. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/google.svg +0 -0
  42. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/grok.png +0 -0
  43. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/huggingface.png +0 -0
  44. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/microsoft.svg +0 -0
  45. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/minimax.png +0 -0
  46. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/mistral.png +0 -0
  47. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/nvida.png +0 -0
  48. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/openai.svg +0 -0
  49. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/openrouter.png +0 -0
  50. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/perplexity.svg +0 -0
  51. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/qwen.png +0 -0
  52. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/xai.png +0 -0
  53. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/xiaomi.png +0 -0
  54. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/logos/zai.png +0 -0
  55. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/card-dist/vite.svg +0 -0
  56. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/anthropic.svg +0 -0
  57. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/cerebras.svg +0 -0
  58. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/deepseek.png +0 -0
  59. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/gemini.svg +0 -0
  60. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/google.svg +0 -0
  61. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/grok.png +0 -0
  62. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/huggingface.png +0 -0
  63. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/microsoft.svg +0 -0
  64. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/minimax.png +0 -0
  65. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/mistral.png +0 -0
  66. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/nvida.png +0 -0
  67. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/openai.svg +0 -0
  68. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/openrouter.png +0 -0
  69. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/perplexity.svg +0 -0
  70. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/qwen.png +0 -0
  71. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/xai.png +0 -0
  72. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
  73. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/assets/icon/zai.png +0 -0
  74. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/.gitignore +0 -0
  75. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/README.md +0 -0
  76. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/index.html +0 -0
  77. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/package-lock.json +0 -0
  78. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/package.json +0 -0
  79. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/anthropic.svg +0 -0
  80. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/cerebras.svg +0 -0
  81. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/deepseek.png +0 -0
  82. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/gemini.svg +0 -0
  83. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/google.svg +0 -0
  84. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/grok.png +0 -0
  85. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/huggingface.png +0 -0
  86. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/microsoft.svg +0 -0
  87. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/minimax.png +0 -0
  88. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/mistral.png +0 -0
  89. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/nvida.png +0 -0
  90. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/openai.svg +0 -0
  91. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/openrouter.png +0 -0
  92. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/perplexity.svg +0 -0
  93. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/qwen.png +0 -0
  94. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/xai.png +0 -0
  95. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/xiaomi.png +0 -0
  96. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/logos/zai.png +0 -0
  97. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/public/vite.svg +0 -0
  98. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/assets/vue.svg +0 -0
  99. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/components/HelloWorld.vue +0 -0
  100. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/components/SectionCard.vue +0 -0
  101. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/main.ts +0 -0
  102. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/style.css +0 -0
  103. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/src/test_regex.js +0 -0
  104. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/tsconfig.app.json +0 -0
  105. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/tsconfig.json +0 -0
  106. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/tsconfig.node.json +0 -0
  107. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/card-ui/vite.config.ts +0 -0
  108. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/image_cache.py +0 -0
  109. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw/misc.py +0 -0
  110. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw.egg-info/dependency_links.txt +0 -0
  111. {entari_plugin_hyw-4.0.0rc3 → entari_plugin_hyw-4.0.0rc5}/src/entari_plugin_hyw.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entari_plugin_hyw
3
- Version: 4.0.0rc3
3
+ Version: 4.0.0rc5
4
4
  Summary: Use large language models to interpret chat messages
5
5
  Author-email: kumoSleeping <zjr2992@outlook.com>
6
6
  License: MIT
@@ -20,9 +20,9 @@ Requires-Dist: arclet-entari[full]>=0.16.5
20
20
  Requires-Dist: openai
21
21
  Requires-Dist: httpx
22
22
  Requires-Dist: markdown>=3.10
23
- Requires-Dist: crawl4ai>=0.7.8
24
- Requires-Dist: jinja2>=3.0
25
- Requires-Dist: ddgs>=9.10.0
23
+ Requires-Dist: DrissionPage>=4.1.1.2
24
+ Requires-Dist: trafilatura>=1.6.0
25
+ Requires-Dist: json-repair>=0.55.0
26
26
  Provides-Extra: dev
27
27
  Requires-Dist: entari-plugin-server>=0.5.0; extra == "dev"
28
28
  Requires-Dist: satori-python-adapter-onebot11>=0.2.5; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "entari_plugin_hyw"
7
- version = "4.0.0-rc3"
7
+ version = "4.0.0-rc5"
8
8
  description = "Use large language models to interpret chat messages"
9
9
  authors = [{name = "kumoSleeping", email = "zjr2992@outlook.com"}]
10
10
  dependencies = [
@@ -12,9 +12,9 @@ dependencies = [
12
12
  "openai",
13
13
  "httpx",
14
14
  "markdown>=3.10",
15
- "crawl4ai>=0.7.8",
16
- "jinja2>=3.0",
17
- "ddgs>=9.10.0",
15
+ "DrissionPage>=4.1.1.2",
16
+ "trafilatura>=1.6.0",
17
+ "json-repair>=0.55.0",
18
18
  ]
19
19
  requires-python = ">=3.10,<3.13"
20
20
  readme = "README.md"
@@ -19,9 +19,9 @@ from loguru import logger
19
19
  import arclet.letoderea as leto
20
20
  from arclet.entari.event.command import CommandReceive
21
21
 
22
- from .pipeline import ProcessingPipeline
22
+ from .modular_pipeline import ModularPipeline
23
23
  from .history import HistoryManager
24
- from .render_vue import ContentRenderer
24
+ from .render_vue import ContentRenderer, get_content_renderer
25
25
  from .misc import process_onebot_json, process_images, resolve_model_name, render_refuse_answer, REFUSE_ANSWER_MARKDOWN
26
26
  from arclet.entari.event.lifespan import Cleanup
27
27
 
@@ -83,86 +83,129 @@ class _RecentEventDeduper:
83
83
 
84
84
  _event_deduper = _RecentEventDeduper()
85
85
 
86
+ @dataclass
87
+ class ModelConfig:
88
+ """Model configuration for a specific stage."""
89
+ model_name: Optional[str] = None
90
+ api_key: Optional[str] = None
91
+ base_url: Optional[str] = None
92
+ extra_body: Optional[Dict[str, Any]] = None
93
+ model_provider: Optional[str] = None
94
+ input_price: Optional[float] = None
95
+ output_price: Optional[float] = None
96
+
97
+
86
98
  @dataclass
87
99
  class HywConfig(BasicConfModel):
100
+ # Core Settings
88
101
  admins: List[str] = field(default_factory=list)
89
102
  models: List[Dict[str, Any]] = field(default_factory=list)
90
103
  question_command: str = "/q"
104
+ language: str = "Simplified Chinese"
105
+ temperature: float = 0.4
106
+
107
+ # Root-level defaults (backward compatible)
91
108
  model_name: Optional[str] = None
92
109
  api_key: Optional[str] = None
93
110
  base_url: str = "https://openrouter.ai/api/v1"
94
- vision_model_name: Optional[str] = None
95
- vision_api_key: Optional[str] = None
96
- language: str = "Simplified Chinese"
97
- vision_base_url: Optional[str] = None
98
- instruct_model_name: Optional[str] = None
99
- instruct_api_key: Optional[str] = None
100
- instruct_base_url: Optional[str] = None
101
- search_base_url: str = "https://lite.duckduckgo.com/lite/?q={query}"
102
- image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images"
111
+ extra_body: Optional[Dict[str, Any]] = None
112
+ model_provider: Optional[str] = None
113
+ input_price: Optional[float] = None
114
+ output_price: Optional[float] = None
115
+
116
+ # Nested Stage Configs
117
+ instruct: Optional[ModelConfig] = None
118
+ qa: Optional[ModelConfig] = None
119
+ main: Optional[ModelConfig] = None # Summary stage
120
+
121
+ # Search/Fetch Settings
122
+ search_engine: str = "bing"
123
+ enable_domain_blocking: bool = True
124
+ page_content_mode: str = "text"
125
+
126
+ # Rendering Settings
103
127
  headless: bool = False
104
- save_conversation: bool = False
105
- icon: str = "openai"
106
128
  render_timeout_ms: int = 6000
107
129
  render_image_timeout_ms: int = 3000
108
- extra_body: Optional[Dict[str, Any]] = None
109
- vision_extra_body: Optional[Dict[str, Any]] = None
110
- instruct_extra_body: Optional[Dict[str, Any]] = None
111
- enable_browser_fallback: bool = False
130
+
131
+ # Bot Behavior
132
+ save_conversation: bool = False
112
133
  reaction: bool = False
113
134
  quote: bool = True
114
- temperature: float = 0.4
115
- # Billing configuration (price per million tokens)
116
- input_price: Optional[float] = None # $ per 1M input tokens
117
- output_price: Optional[float] = None # $ per 1M output tokens
118
- # Vision model pricing overrides (defaults to main model pricing if not set)
119
- vision_input_price: Optional[float] = None
120
- vision_output_price: Optional[float] = None
121
- # Instruct model pricing overrides (defaults to main model pricing if not set)
122
- instruct_input_price: Optional[float] = None
123
- instruct_output_price: Optional[float] = None
124
- # Provider Names
125
- search_name: str = "DuckDuckGo"
126
- search_provider: str = "crawl4ai" # crawl4ai | httpx | ddgs
127
- fetch_provider: str = "crawl4ai" # crawl4ai | jinaai
128
- jina_api_key: Optional[str] = None # Optional API key for Jina AI
129
- model_provider: Optional[str] = None
130
- vision_model_provider: Optional[str] = None
131
- instruct_model_provider: Optional[str] = None
132
135
 
133
- # Search/Fetch Settings
134
- search_timeout: float = 10.0
135
- search_retries: int = 2
136
- fetch_timeout: float = 15.0
137
- fetch_max_results: int = 5
138
- fetch_blocked_domains: Optional[List[str]] = None
139
-
140
- # Fetch Model Config
141
- fetch_model_name: Optional[str] = None
142
- fetch_api_key: Optional[str] = None
143
- fetch_base_url: Optional[str] = None
144
- fetch_extra_body: Optional[Dict[str, Any]] = None
145
- fetch_input_price: Optional[float] = None
146
- fetch_output_price: Optional[float] = None
147
- # Summary Model Config
148
- summary_model_name: Optional[str] = None
149
- summary_api_key: Optional[str] = None
150
- summary_base_url: Optional[str] = None
151
- summary_extra_body: Optional[Dict[str, Any]] = None
152
- summary_input_price: Optional[float] = None
153
- summary_output_price: Optional[float] = None
154
136
  # UI Theme
155
- theme_color: str = "#ef4444" # Tailwind red-500, supports hex/RGB/color names
137
+ theme_color: str = "#ef4444"
156
138
 
157
139
  def __post_init__(self):
158
140
  """Parse and normalize theme color after initialization."""
159
141
  self.theme_color = parse_color(self.theme_color)
160
-
142
+ # Convert dicts to ModelConfig if needed
143
+ if isinstance(self.instruct, dict):
144
+ self.instruct = ModelConfig(**self.instruct)
145
+ if isinstance(self.qa, dict):
146
+ self.qa = ModelConfig(**self.qa)
147
+ if isinstance(self.main, dict):
148
+ self.main = ModelConfig(**self.main)
149
+
150
+ def get_model_config(self, stage: str) -> Dict[str, Any]:
151
+ """
152
+ Get resolved model config for a stage.
153
+
154
+ Args:
155
+ stage: "instruct", "qa", or "main" (summary)
156
+
157
+ Returns:
158
+ Dict with model_name, api_key, base_url, extra_body, etc.
159
+ """
160
+ # Determine primary and secondary config sources
161
+ primary = None
162
+ secondary = None
163
+
164
+ if stage == "instruct":
165
+ primary = self.instruct
166
+ secondary = self.main # Fallback to main
167
+ elif stage == "qa":
168
+ # QA fallback to main as well if ever used
169
+ primary = self.qa
170
+ secondary = self.main
171
+ elif stage == "main":
172
+ primary = self.main
173
+
174
+ # Build result with fallback logic
175
+ def resolve(field_name: str, is_essential: bool = True):
176
+ """Resolve a field with fallback: Primary -> Secondary -> Root."""
177
+ # 1. Try Primary
178
+ val = getattr(primary, field_name, None) if primary else None
179
+
180
+ # 2. Try Secondary (if value missing)
181
+ if val is None and secondary:
182
+ val = getattr(secondary, field_name, None)
183
+
184
+ # 3. Try Root (if value still missing)
185
+ if val is None:
186
+ val = getattr(self, field_name, None)
187
+ return val
188
+
189
+ return {
190
+ "model_name": resolve("model_name"),
191
+ "api_key": resolve("api_key"),
192
+ "base_url": resolve("base_url"),
193
+ "extra_body": resolve("extra_body", is_essential=False),
194
+ "model_provider": resolve("model_provider", is_essential=False),
195
+ "input_price": resolve("input_price", is_essential=False),
196
+ "output_price": resolve("output_price", is_essential=False),
197
+ }
161
198
 
162
199
 
163
200
  conf = plugin_config(HywConfig)
164
201
  history_manager = HistoryManager()
165
- renderer = ContentRenderer()
202
+ renderer = ContentRenderer(headless=conf.headless)
203
+ from .render_vue import set_global_renderer
204
+ set_global_renderer(renderer)
205
+
206
+ # Pre-start Crawl4AI browser for fast fetching/screenshots
207
+ from .browser.service import prestart_browser, close_screenshot_service
208
+ # prestart_browser(headless=conf.headless) # Removed to avoid RuntimeError: no running event loop
166
209
 
167
210
 
168
211
  class GlobalCache:
@@ -170,6 +213,18 @@ class GlobalCache:
170
213
 
171
214
  global_cache = GlobalCache()
172
215
 
216
+
217
+ @listen(Cleanup)
218
+ async def cleanup_screenshot_service():
219
+ """Cleanup shared browser on shutdown."""
220
+ try:
221
+ await close_screenshot_service()
222
+ # Also close the shared browser manager
223
+ from .browser.manager import close_shared_browser
224
+ await close_shared_browser()
225
+ except Exception as e:
226
+ logger.warning(f"Failed to cleanup browser services: {e}")
227
+
173
228
  async def react(session: Session, emoji: str):
174
229
  if not conf.reaction: return
175
230
  try:
@@ -185,9 +240,7 @@ async def process_request(
185
240
  conversation_key_override: Optional[str] = None,
186
241
  local_mode: bool = False,
187
242
  ) -> None:
188
- logger.info(f"Processing request: {all_param}")
189
243
  mc = MessageChain(all_param)
190
- logger.info(f"reply: {session.reply}")
191
244
  if session.reply:
192
245
  try:
193
246
  # Check if reply is from self (the bot)
@@ -197,12 +250,10 @@ async def process_request(
197
250
 
198
251
  if reply_msg_id and history_manager.is_bot_message(reply_msg_id):
199
252
  is_bot = True
200
- logger.info(f"Reply target {reply_msg_id} identified as bot message via history")
201
253
 
202
254
  if is_bot:
203
- logger.info("Reply is from me - ignoring content")
255
+ pass # Reply is from bot - ignoring
204
256
  else:
205
- logger.info(f"Reply is from user (or unknown) - including content")
206
257
  mc.extend(MessageChain(" ") + session.reply.origin.message)
207
258
  except Exception as e:
208
259
  logger.warning(f"Failed to process reply origin: {e}")
@@ -211,7 +262,7 @@ async def process_request(
211
262
  # Filter and reconstruct MessageChain
212
263
  filtered_elements = mc.get(Text) + mc.get(Image) + mc.get(Custom)
213
264
  mc = MessageChain(filtered_elements)
214
- logger.info(f"mc: {mc}")
265
+
215
266
 
216
267
  text_content = str(mc.get(Text)).strip()
217
268
  # Remove HTML image tags from text content to prevent "unreasonable code behavior"
@@ -263,10 +314,15 @@ async def process_request(
263
314
  logger.warning(f"Vision model resolution warning for {vision_model}: {err_v}")
264
315
 
265
316
  images, err = await process_images(mc, vision_model)
317
+
318
+ # Start preparing render tab (async)
319
+ renderer = await get_content_renderer()
320
+ render_tab_task = asyncio.create_task(renderer.prepare_tab())
321
+ tab_id = None
266
322
 
267
323
  # Call Pipeline directly
268
324
  safe_input = msg_text
269
- pipeline = ProcessingPipeline(conf)
325
+ pipeline = ModularPipeline(conf)
270
326
  try:
271
327
  resp = await pipeline.execute(
272
328
  safe_input,
@@ -294,6 +350,13 @@ async def process_request(
294
350
  content = final_resp.get("llm_response", "")
295
351
  structured = final_resp.get("structured_response", {})
296
352
 
353
+ # Wait for tab preparation if needed (should be ready by now)
354
+ try:
355
+ tab_id = await render_tab_task
356
+ except Exception as e:
357
+ logger.warning(f"Failed to prepare render tab: {e}")
358
+ tab_id = None
359
+
297
360
  # Render
298
361
  import tempfile
299
362
  with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tf:
@@ -319,23 +382,24 @@ async def process_request(
319
382
  output_path=output_path,
320
383
  reason=final_resp.get('refuse_reason', 'Instruct 专家分配此任务流程失败,请尝试提出其他问题~'),
321
384
  theme_color=conf.theme_color,
385
+ tab_id=tab_id,
322
386
  )
323
387
  else:
324
388
  render_ok = await renderer.render(
325
389
  markdown_content=content,
326
390
  output_path=output_path,
391
+ tab_id=tab_id,
327
392
  stats=stats_to_render,
328
393
  references=structured.get("references", []),
329
394
  page_references=structured.get("page_references", []),
330
395
  image_references=structured.get("image_references", []),
331
396
  stages_used=final_resp.get("stages_used", []),
332
- image_timeout=conf.render_image_timeout_ms,
333
397
  theme_color=conf.theme_color,
334
398
  )
335
399
 
336
400
  # Send & Save
337
401
  if not render_ok:
338
- logger.error("Render failed; skipping reply. Check Crawl4AI rendering status.")
402
+ logger.error("Render failed; skipping reply.")
339
403
  if os.path.exists(output_path):
340
404
  try:
341
405
  os.remove(output_path)
@@ -375,6 +439,24 @@ async def process_request(
375
439
  code=display_session_id,
376
440
  )
377
441
 
442
+ if conf.save_conversation and sent_id:
443
+ try:
444
+ # Pass web_results to save fetched pages as markdown, and output image
445
+ history_manager.save_to_disk(
446
+ sent_id,
447
+ web_results=final_resp.get("web_results"),
448
+ image_path=output_path if 'output_path' in locals() else None
449
+ )
450
+ except Exception as e:
451
+ logger.warning(f"Failed to save conversation: {e}")
452
+
453
+ # Cleanup temp image
454
+ if 'output_path' in locals() and output_path and os.path.exists(output_path):
455
+ try:
456
+ os.remove(output_path)
457
+ except Exception:
458
+ pass
459
+
378
460
 
379
461
 
380
462
 
@@ -391,9 +473,29 @@ async def process_request(
391
473
  try:
392
474
  # Use a temporary ID for error cases
393
475
  error_id = f"error_{int(time.time())}_{secrets.token_hex(4)}"
394
- history_manager.remember(error_id, resp.get("conversation_history", []), [], {"model": model_used if 'model_used' in locals() else "unknown", "error": str(e)}, context_id, code=display_session_id if 'display_session_id' in locals() else None)
395
- # history_manager.save_to_disk(error_id)
396
- logger.info(f"Saved error conversation memory to {error_id}")
476
+
477
+ # Try to salvage history
478
+ partial_hist = []
479
+ if 'resp' in locals() and resp:
480
+ partial_hist = resp.get("conversation_history", [])
481
+ elif 'context' in locals() and context and hasattr(context, 'instruct_history'):
482
+ partial_hist = context.instruct_history
483
+
484
+ related_ids = []
485
+ if 'session' in locals():
486
+ msg_id = str(session.event.message.id) if hasattr(session.event, 'message') else str(session.event.id)
487
+ related_ids = [msg_id]
488
+
489
+ history_manager.remember(error_id, partial_hist, related_ids, {"model": "error", "error": str(e)}, context_id, code=display_session_id if 'display_session_id' in locals() else None)
490
+
491
+ # Save debug data on error
492
+ web_res = context.web_results if 'context' in locals() and context else []
493
+
494
+ history_manager.save_to_disk(
495
+ error_id,
496
+ web_results=web_res
497
+ )
498
+
397
499
  except Exception as save_err:
398
500
  logger.error(f"Failed to save error conversation: {save_err}")
399
501
 
@@ -419,9 +521,8 @@ async def handle_question_command(session: Session[MessageCreatedEvent], result:
419
521
  logger.info(f"Question Command Triggered. Message: {session.event.message}")
420
522
 
421
523
  args = result.all_matched_args
422
- logger.info(f"Matched Args: {args}")
423
524
 
424
- await process_request(session, args.get("all_param"), selected_model=None, selected_vision_model=None, conversation_key_override=None, local_mode=False)
525
+ await process_request(session, args.get("all_param"), selected_model=None, selected_vision_model=None, conversation_key_override=None)
425
526
 
426
527
  metadata("hyw", author=[{"name": "kumoSleeping", "email": "zjr2992@outlook.com"}], version=__version__, config=HywConfig)
427
528