entari-plugin-hyw 3.2.113__tar.gz → 3.3.0__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 (59) hide show
  1. {entari_plugin_hyw-3.2.113/src/entari_plugin_hyw.egg-info → entari_plugin_hyw-3.3.0}/PKG-INFO +8 -8
  2. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/README.md +6 -1
  3. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/pyproject.toml +2 -6
  4. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/__init__.py +37 -32
  5. entari_plugin_hyw-3.3.0/src/entari_plugin_hyw/assets/libs/tailwind.css +1 -0
  6. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/tailwind.input.css +1 -1
  7. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/template.j2 +103 -55
  8. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/core/config.py +3 -4
  9. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/core/pipeline.py +507 -282
  10. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/core/render.py +184 -119
  11. entari_plugin_hyw-3.3.0/src/entari_plugin_hyw/utils/__init__.py +2 -0
  12. entari_plugin_hyw-3.3.0/src/entari_plugin_hyw/utils/browser.py +40 -0
  13. entari_plugin_hyw-3.3.0/src/entari_plugin_hyw/utils/playwright_tool.py +36 -0
  14. entari_plugin_hyw-3.3.0/src/entari_plugin_hyw/utils/prompts.py +128 -0
  15. entari_plugin_hyw-3.3.0/src/entari_plugin_hyw/utils/search.py +241 -0
  16. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0/src/entari_plugin_hyw.egg-info}/PKG-INFO +8 -8
  17. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw.egg-info/SOURCES.txt +0 -1
  18. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw.egg-info/requires.txt +1 -7
  19. entari_plugin_hyw-3.2.113/src/entari_plugin_hyw/assets/libs/tailwind.css +0 -1
  20. entari_plugin_hyw-3.2.113/src/entari_plugin_hyw/utils/__init__.py +0 -3
  21. entari_plugin_hyw-3.2.113/src/entari_plugin_hyw/utils/browser.py +0 -61
  22. entari_plugin_hyw-3.2.113/src/entari_plugin_hyw/utils/mcp_playwright.py +0 -128
  23. entari_plugin_hyw-3.2.113/src/entari_plugin_hyw/utils/playwright_tool.py +0 -46
  24. entari_plugin_hyw-3.2.113/src/entari_plugin_hyw/utils/prompts.py +0 -94
  25. entari_plugin_hyw-3.2.113/src/entari_plugin_hyw/utils/search.py +0 -193
  26. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/MANIFEST.in +0 -0
  27. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/setup.cfg +0 -0
  28. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/anthropic.svg +0 -0
  29. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/deepseek.png +0 -0
  30. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/gemini.svg +0 -0
  31. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/google.svg +0 -0
  32. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/grok.png +0 -0
  33. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/microsoft.svg +0 -0
  34. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/minimax.png +0 -0
  35. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/mistral.png +0 -0
  36. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/nvida.png +0 -0
  37. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/openai.svg +0 -0
  38. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/openrouter.png +0 -0
  39. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/perplexity.svg +0 -0
  40. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/qwen.png +0 -0
  41. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/xai.png +0 -0
  42. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/icon/zai.png +0 -0
  43. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/libs/highlight.css +0 -0
  44. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/libs/highlight.js +0 -0
  45. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/libs/katex-auto-render.js +0 -0
  46. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/libs/katex.css +0 -0
  47. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/libs/katex.js +0 -0
  48. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/package-lock.json +0 -0
  49. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/package.json +0 -0
  50. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/tailwind.config.js +0 -0
  51. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/template.html +0 -0
  52. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/assets/template.html.bak +0 -0
  53. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/core/__init__.py +0 -0
  54. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/core/history.py +0 -0
  55. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/core/hyw.py +0 -0
  56. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/core/render.py.bak +0 -0
  57. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw/utils/misc.py +0 -0
  58. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/src/entari_plugin_hyw.egg-info/dependency_links.txt +0 -0
  59. {entari_plugin_hyw-3.2.113 → entari_plugin_hyw-3.3.0}/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: 3.2.113
3
+ Version: 3.3.0
4
4
  Summary: Use large language models to interpret chat messages
5
5
  Author-email: kumoSleeping <zjr2992@outlook.com>
6
6
  License: MIT
@@ -18,15 +18,10 @@ Requires-Python: >=3.10
18
18
  Description-Content-Type: text/markdown
19
19
  Requires-Dist: arclet-entari[full]>=0.16.5
20
20
  Requires-Dist: openai
21
- Requires-Dist: mcp
22
21
  Requires-Dist: httpx
23
22
  Requires-Dist: markdown>=3.10
24
- Requires-Dist: trafilatura>=2.0.0
25
- Requires-Dist: playwright>=1.56.0
23
+ Requires-Dist: crawl4ai>=0.7.8
26
24
  Requires-Dist: jinja2>=3.0
27
- Provides-Extra: playwright
28
- Requires-Dist: playwright>=1.56.0; extra == "playwright"
29
- Requires-Dist: trafilatura>=2.0.0; extra == "playwright"
30
25
  Provides-Extra: dev
31
26
  Requires-Dist: entari-plugin-server>=0.5.0; extra == "dev"
32
27
  Requires-Dist: satori-python-adapter-onebot11>=0.2.5; extra == "dev"
@@ -43,7 +38,9 @@ Requires-Dist: satori-python-adapter-onebot11>=0.2.5; extra == "dev"
43
38
 
44
39
  </div>
45
40
 
46
- # v3.2迎来大幅度改动、现在图文不符
41
+ # v3.3 迎来大幅度改动、现在图文不符
42
+
43
+
47
44
 
48
45
  ## 🎑 效果展示
49
46
 
@@ -140,3 +137,6 @@ hyw -t 一大段话。
140
137
  ### 引用回复
141
138
  支持引用消息进行追问,机器人会自动读取被引用的消息作为上下文:
142
139
  - **引用 + 命令**:机器人将理解被引用消息的内容(包括图片)通过 `MessageChain` 操作拼接 `Text`、`Image` 与部分 `Custom`。
140
+
141
+ UncleCode. (2024). Crawl4AI: Open-source LLM Friendly Web Crawler & Scraper [Computer software].
142
+ GitHub. https://github.com/unclecode/crawl4ai
@@ -10,7 +10,9 @@
10
10
 
11
11
  </div>
12
12
 
13
- # v3.2迎来大幅度改动、现在图文不符
13
+ # v3.3 迎来大幅度改动、现在图文不符
14
+
15
+
14
16
 
15
17
  ## 🎑 效果展示
16
18
 
@@ -107,3 +109,6 @@ hyw -t 一大段话。
107
109
  ### 引用回复
108
110
  支持引用消息进行追问,机器人会自动读取被引用的消息作为上下文:
109
111
  - **引用 + 命令**:机器人将理解被引用消息的内容(包括图片)通过 `MessageChain` 操作拼接 `Text`、`Image` 与部分 `Custom`。
112
+
113
+ UncleCode. (2024). Crawl4AI: Open-source LLM Friendly Web Crawler & Scraper [Computer software].
114
+ GitHub. https://github.com/unclecode/crawl4ai
@@ -4,17 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "entari_plugin_hyw"
7
- version = "3.2.113"
7
+ version = "3.3.0"
8
8
  description = "Use large language models to interpret chat messages"
9
9
  authors = [{name = "kumoSleeping", email = "zjr2992@outlook.com"}]
10
10
  dependencies = [
11
11
  "arclet-entari[full]>=0.16.5",
12
12
  "openai",
13
- "mcp",
14
13
  "httpx",
15
14
  "markdown>=3.10",
16
- "trafilatura>=2.0.0",
17
- "playwright>=1.56.0",
15
+ "crawl4ai>=0.7.8",
18
16
  "jinja2>=3.0",
19
17
  ]
20
18
  requires-python = ">=3.10"
@@ -31,7 +29,6 @@ classifiers = [
31
29
  ]
32
30
 
33
31
  [project.optional-dependencies]
34
- playwright = ["playwright>=1.56.0", "trafilatura>=2.0.0"]
35
32
  dev = [
36
33
  "entari-plugin-server>=0.5.0",
37
34
  "satori-python-adapter-onebot11>=0.2.5",
@@ -48,4 +45,3 @@ include-package-data = true
48
45
  [tool.setuptools.packages.find]
49
46
  where = ["src"]
50
47
  include = ["entari_plugin_hyw*"]
51
-
@@ -65,10 +65,8 @@ class HywConfig(BasicConfModel):
65
65
  intruct_base_url: Optional[str] = None
66
66
  intruct_system_prompt: Optional[str] = None
67
67
  agent_system_prompt: Optional[str] = None
68
- playwright_mcp_command: str = "npx"
69
- playwright_mcp_args: Optional[List[str]] = None
70
- search_base_url: str = "https://duckduckgo.com/?q={query}&format=json&results_per_page={limit}"
71
- image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images&format=json&results_per_page={limit}"
68
+ search_base_url: str = "https://lite.duckduckgo.com/lite/?q={query}"
69
+ image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images"
72
70
  headless: bool = False
73
71
  save_conversation: bool = False
74
72
  icon: str = "openai"
@@ -89,8 +87,8 @@ class HywConfig(BasicConfModel):
89
87
  intruct_output_price: Optional[float] = None
90
88
 
91
89
  # Provider Names
92
- search_name: str = "SearXNG"
93
- search_provider: str = "SearXNG"
90
+ search_name: str = "DuckDuckGo"
91
+ search_provider: str = "Crawl4AI"
94
92
  model_provider: Optional[str] = None
95
93
  vision_model_provider: Optional[str] = None
96
94
  intruct_model_provider: Optional[str] = None
@@ -102,13 +100,6 @@ history_manager = HistoryManager()
102
100
  renderer = ContentRenderer()
103
101
  hyw = HYW(config=conf)
104
102
 
105
- @listen(Ready, once=True)
106
- async def _hyw_warmup_mcp():
107
- try:
108
- await hyw.pipeline.warmup_mcp()
109
- except Exception as e:
110
- logger.warning(f"MCP Playwright warmup error: {e}")
111
-
112
103
  @listen(Ready, once=True)
113
104
  async def _run_ui_test():
114
105
  """Run UI rendering test on startup if configured."""
@@ -145,31 +136,43 @@ async def _run_ui_test():
145
136
 
146
137
  # Mock Data for Full UI Test
147
138
  stats = {
148
- "time": 12.5,
139
+ "total_time": 12.5,
149
140
  "vision_duration": 3.2,
150
141
  "cost": 0.0015
151
142
  }
152
143
 
153
144
  stages = [
154
- {"name": "Vision", "model": "google/gemini-pro-vision", "time": 3.2, "cost": 0.0005, "provider": "Google"},
155
- {"name": "Search", "model": "duckduckgo", "time": 1.5, "cost": 0.0, "provider": "DDG"},
156
- {"name": "Agent", "model": "anthropic/claude-3-5-sonnet", "time": 7.8, "cost": 0.0010, "provider": "Anthropic"}
145
+ {"name": "Vision", "model": "google/gemini-pro-vision", "time": 3.2, "cost": 0.0005, "provider": "Google", "icon_config": "google"},
146
+ {"name": "Search", "model": "duckduckgo", "time": 1.5, "cost": 0.0, "provider": "DDG", "icon_config": "search",
147
+ "children": {"references": [
148
+ {"title": "Crawl4AI, Open-source LLM-Friendly Web Crawler & Scraper", "url": "https://docs.crawl4ai.com/core/llmtxt", "domain": "docs.crawl4ai.com"}
149
+ ]}},
150
+ {"name": "Crawler", "model": "Crawl4AI", "time": 2.5, "cost": 0.0, "provider": "Page Fetcher", "icon_config": "browser",
151
+ "children": {"crawled_pages": [
152
+ {"title": "Quick Start - Crawl4AI Documentation (v0.7.x)", "url": "https://docs.crawl4ai.com/core/quickstart/", "domain": "docs.crawl4ai.com"},
153
+ {"title": "Crawl4AI Explained: The AI-Friendly Web Crawling Framework", "url": "https://scrapfly.io/blog/posts/crawl4AI-explained/", "domain": "scrapfly.io"},
154
+ {"title": "Llmtxt - Crawl4AI Documentation (v0.7.x)", "url": "https://docs.crawl4ai.com/core/llmtxt/", "domain": "docs.crawl4ai.com"},
155
+ {"title": "Multi-URL Crawling - Crawl4AI Documentation (v0.7.x)", "url": "https://docs.crawl4ai.com/advanced/multi-url-crawling/", "domain": "docs.crawl4ai.com"}
156
+ ]}},
157
+ {"name": "Agent", "model": "anthropic/claude-3-5-sonnet", "time": 7.8, "cost": 0.0010, "provider": "Anthropic", "icon_config": "anthropic"}
157
158
  ]
158
159
 
159
- mcp_steps = [
160
- {"name": "search_google", "description": "searching for 'latest entari news'", "icon": "search"},
161
- {"name": "visit_page", "description": "visiting python.org", "icon": "navigate"},
162
- {"name": "click_element", "description": "clicking 'Downloads'", "icon": "click"}
160
+ # References come from search results
161
+ references = [
162
+ {"title": "Crawl4AI, Open-source LLM-Friendly Web Crawler & Scraper", "url": "https://docs.crawl4ai.com/core/llmtxt", "domain": "docs.crawl4ai.com"}
163
163
  ]
164
164
 
165
- references = [
166
- {"title": "Entari Docs", "url": "https://entari.onebot.dev", "domain": "entari.onebot.dev"},
167
- {"title": "Python Language", "url": "https://python.org", "domain": "python.org"}
165
+ # Page references come from crawled pages
166
+ page_references = [
167
+ {"title": "Quick Start - Crawl4AI Documentation (v0.7.x)", "url": "https://docs.crawl4ai.com/core/quickstart/", "domain": "docs.crawl4ai.com"},
168
+ {"title": "Crawl4AI Explained: The AI-Friendly Web Crawling Framework", "url": "https://scrapfly.io/blog/posts/crawl4AI-explained/", "domain": "scrapfly.io"},
169
+ {"title": "Llmtxt - Crawl4AI Documentation (v0.7.x)", "url": "https://docs.crawl4ai.com/core/llmtxt/", "domain": "docs.crawl4ai.com"},
170
+ {"title": "Multi-URL Crawling - Crawl4AI Documentation (v0.7.x)", "url": "https://docs.crawl4ai.com/advanced/multi-url-crawling/", "domain": "docs.crawl4ai.com"}
168
171
  ]
169
172
 
170
173
  output_dir = "data/cache"
171
174
  os.makedirs(output_dir, exist_ok=True)
172
- output_path = f"{output_dir}/ui_test_result.png"
175
+ output_path = f"{output_dir}/ui_test_result.jpg"
173
176
 
174
177
  logger.info(f"UI TEST: Rendering to {output_path}...")
175
178
 
@@ -179,8 +182,9 @@ async def _run_ui_test():
179
182
  output_path=output_path,
180
183
  stats=stats,
181
184
  stages_used=stages,
182
- mcp_steps=mcp_steps,
183
185
  references=references,
186
+ page_references=page_references,
187
+ flow_steps=[],
184
188
  model_name="CLAUDE-3-5-SONNET",
185
189
  provider_name="Anthropic",
186
190
  behavior_summary="Automated Test",
@@ -429,7 +433,7 @@ async def process_request(session: Session[MessageCreatedEvent], all_param: Opti
429
433
 
430
434
  # Render
431
435
  import tempfile
432
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tf:
436
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tf:
433
437
  output_path = tf.name
434
438
  model_used = final_resp.get("model_used")
435
439
  vision_model_used = final_resp.get("vision_model_used")
@@ -496,10 +500,10 @@ async def process_request(session: Session[MessageCreatedEvent], all_param: Opti
496
500
 
497
501
  # 1. Behavior Summary
498
502
  behavior_summary = "Text Generation"
499
- if structured.get("mcp_steps"):
500
- behavior_summary = "Agentic Loop"
501
- elif vision_model_used:
503
+ if vision_model_used:
502
504
  behavior_summary = "Visual Analysis"
505
+ elif any(s.get("name") == "Search" for s in final_resp.get("stages_used", []) or []):
506
+ behavior_summary = "Search-Augmented"
503
507
 
504
508
  # 2. Provider Name
505
509
  # Try to get from m_conf (resolved above)
@@ -523,7 +527,8 @@ async def process_request(session: Session[MessageCreatedEvent], all_param: Opti
523
527
  suggestions=[],
524
528
  stats=stats_to_render,
525
529
  references=structured.get("references", []),
526
- mcp_steps=structured.get("mcp_steps", []),
530
+ page_references=structured.get("page_references", []),
531
+ flow_steps=structured.get("flow_steps", []),
527
532
  stages_used=final_resp.get("stages_used", []),
528
533
  model_name=render_model_name,
529
534
  provider_name=provider_name,
@@ -539,7 +544,7 @@ async def process_request(session: Session[MessageCreatedEvent], all_param: Opti
539
544
 
540
545
  # Send & Save
541
546
  if not render_ok:
542
- logger.error("Render failed; skipping reply. Check browser/playwright status.")
547
+ logger.error("Render failed; skipping reply. Check Crawl4AI rendering status.")
543
548
  if os.path.exists(output_path):
544
549
  try:
545
550
  os.remove(output_path)
@@ -0,0 +1 @@
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.relative{position:relative}.-top-0\.5{top:-.125rem}.z-20{z-index:20}.m-0{margin:0}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-3{margin-bottom:.75rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.box-border{box-sizing:border-box}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-10{height:2.5rem}.h-2{height:.5rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-fit{height:-moz-fit-content;height:fit-content}.w-10{width:2.5rem}.w-2{width:.5rem}.w-3\.5{width:.875rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[14px\]{min-width:14px}.min-w-\[16px\]{min-width:16px}.max-w-\[450px\]{max-width:450px}.max-w-\[80px\]{max-width:80px}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0}.gap-0\.5{gap:.125rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.overflow-hidden,.truncate{overflow:hidden}.truncate{white-space:nowrap}.text-ellipsis,.truncate{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-blue-200{--tw-border-opacity:1;border-color:rgb(191 219 254/var(--tw-border-opacity,1))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity,1))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-orange-200{--tw-border-opacity:1;border-color:rgb(254 215 170/var(--tw-border-opacity,1))}.border-white\/50{border-color:hsla(0,0%,100%,.5)}.bg-\[\#f2f2f2\]{--tw-bg-opacity:1;background-color:rgb(242 242 242/var(--tw-bg-opacity,1))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-green-400{--tw-bg-opacity:1;background-color:rgb(74 222 128/var(--tw-bg-opacity,1))}.bg-orange-50{--tw-bg-opacity:1;background-color:rgb(255 247 237/var(--tw-bg-opacity,1))}.bg-pink-500{--tw-bg-opacity:1;background-color:rgb(236 72 153/var(--tw-bg-opacity,1))}.bg-purple-400{--tw-bg-opacity:1;background-color:rgb(192 132 252/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/60{background-color:hsla(0,0%,100%,.6)}.bg-white\/80{background-color:hsla(0,0%,100%,.8)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-0{padding:0}.p-2\.5{padding:.625rem}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-5{padding-bottom:1.25rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-5{padding-top:1.25rem}.pt-7{padding-top:1.75rem}.align-top{vertical-align:top}.font-mono{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace}.font-sans{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.text-\[10px\]{font-size:10px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.text-\[16px\]{font-size:16px}.text-\[9px\]{font-size:9px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-inherit{color:inherit}.text-orange-600{--tw-text-opacity:1;color:rgb(234 88 12/var(--tw-text-opacity,1))}.text-orange-700{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity,1))}.text-pink-600{--tw-text-opacity:1;color:rgb(219 39 119/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.decoration-gray-300{text-decoration-color:#d1d5db}.decoration-0{text-decoration-thickness:0}.decoration-1{text-decoration-thickness:1px}.underline-offset-2{text-underline-offset:2px}.opacity-80{opacity:.8}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-inset{--tw-ring-inset:inset}.ring-black\/5{--tw-ring-color:rgba(0,0,0,.05)}.backdrop-blur{--tw-backdrop-blur:blur(8px)}.backdrop-blur,.backdrop-blur-sm{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}pre::-webkit-scrollbar{height:8px;background-color:transparent}pre::-webkit-scrollbar-thumb{background-color:hsla(0,0%,100%,.2);border-radius:4px}.markdown-body h1{margin-top:1.5rem;margin-bottom:1rem;border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1));padding-bottom:.5rem;font-size:1.5rem;line-height:2rem;font-weight:700;--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.markdown-body h2{margin-top:1.25rem;margin-bottom:.75rem;font-size:1.25rem}.markdown-body h2,.markdown-body h3{line-height:1.75rem;font-weight:700;--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.markdown-body h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.125rem}.markdown-body h4{margin-top:.75rem;margin-bottom:.5rem;font-size:1rem;line-height:1.5rem;font-weight:700;--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.markdown-body p{margin-bottom:1rem;line-height:1.75rem;--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.markdown-body ul{margin-bottom:1rem;list-style-type:disc}.markdown-body ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.markdown-body ul{padding-left:1.25rem;--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.markdown-body ol{margin-bottom:1rem;list-style-type:decimal}.markdown-body ol>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.markdown-body ol{padding-left:1.25rem;--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.markdown-body li{padding-left:.25rem}.markdown-body li>p{margin-bottom:.25rem}.markdown-body a{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1));text-decoration-line:underline;text-decoration-color:#93c5fd;text-underline-offset:2px;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.markdown-body a:hover{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity,1));text-decoration-color:#1d4ed8}.markdown-body blockquote{margin-top:1rem;margin-bottom:1rem;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1));--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1));padding:.5rem .5rem .5rem 1rem;font-style:italic;--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.hljs-built_in,.hljs-keyword,.hljs-name,.hljs-selector-tag,.hljs-tag{color:#db2777!important}.hljs-addition,.hljs-attribute,.hljs-literal,.hljs-section,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type{color:#d97706!important}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#9ca3af!important}.hljs-bullet,.hljs-number,.hljs-symbol{color:#ea580c!important}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.markdown-body code{border-radius:.25rem;--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1));padding:.125rem .375rem;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:.875rem;line-height:1.25rem;--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.markdown-body pre{margin-bottom:1rem;overflow-x:auto;border-radius:.5rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity,1));--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1));padding:1rem;font-size:.875rem;line-height:1.25rem;line-height:1.5;--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.markdown-body pre code{border-style:none;background-color:transparent;padding:0;--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.markdown-body img{margin-top:1rem;margin-bottom:1rem;height:auto;max-width:60%;border-radius:.5rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity,1));--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.markdown-body table{margin-bottom:1rem;width:100%;border-collapse:collapse;font-size:.875rem;line-height:1.25rem}.markdown-body th{--tw-border-opacity:1;--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1));text-align:left;font-weight:600;--tw-text-opacity:1}.markdown-body td,.markdown-body th{border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity,1));padding:.5rem 1rem;color:rgb(55 65 81/var(--tw-text-opacity,1))}.markdown-body td{--tw-border-opacity:1;--tw-text-opacity:1}.markdown-body hr{margin-top:1.5rem;margin-bottom:1.5rem;--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.markdown-body>:last-child{margin-bottom:0}.katex-display{overflow-x:auto;overflow-y:hidden;padding-top:.5rem;padding-bottom:.5rem}.mermaid{margin-top:1rem;margin-bottom:1rem;display:flex;justify-content:center;border-radius:.5rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity,1));--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1));padding:1rem}.citation-ref{display:inline-flex;align-items:center;justify-content:center;vertical-align:super;font-size:.75em;font-weight:700;color:#4b5563;background-color:#f3f4f6;border:1px solid #e5e7eb;border-radius:9999px;width:1.4em;height:1.4em;margin-left:2px;cursor:pointer;text-decoration:none!important;transition:all .2s;position:relative;line-height:1;box-shadow:none!important}.citation-ref:hover{background-color:#4b5563;color:#fff;text-decoration:none!important}.citation-tooltip{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);margin-bottom:8px;background:#fff;border:1px solid #e5e7eb;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);border-radius:8px;padding:8px 12px;width:-moz-max-content;width:max-content;max-width:300px;z-index:50;opacity:0;visibility:hidden;transition:all .2s;pointer-events:none;font-size:12px;line-height:1.4;color:#374151;text-align:left;font-weight:400}.citation-ref:hover .citation-tooltip{opacity:1;visibility:visible;transform:translateX(-50%) translateY(-4px)}.citation-tooltip-title{font-weight:600;color:#111827;margin-bottom:2px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px}.citation-tooltip-url{color:#6b7280;font-size:10px;display:flex;align-items:center;gap:4px}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:text-black:hover{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.hover\:decoration-gray-500:hover{text-decoration-color:#6b7280}.hover\:shadow:hover{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.\[\&\>\*\:first-child\]\:\!mt-0>:first-child{margin-top:0!important}
@@ -159,7 +159,7 @@ pre::-webkit-scrollbar-thumb {
159
159
  display: inline-flex;
160
160
  align-items: center;
161
161
  justify-content: center;
162
- vertical-align: baseline;
162
+ vertical-align: super;
163
163
  font-size: 0.75em;
164
164
  font-weight: bold;
165
165
  color: #4b5563;
@@ -19,7 +19,7 @@
19
19
  </head>
20
20
 
21
21
  <body class="bg-[#f2f2f2] p-0 box-border m-0 font-sans text-gray-800">
22
- <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-4 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit">
22
+ <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-0 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit pb-8">
23
23
 
24
24
  {# --- MACROS --- #}
25
25
 
@@ -30,7 +30,7 @@
30
30
  {% endmacro %}
31
31
 
32
32
  {% macro card_header(title, icon_html=None, subtitle_html=None, is_plain=False, icon_box_class=None) %}
33
- {% set container_class = "flex items-center gap-3" if is_plain else "flex items-center gap-3 pb-3 mb-3 border-b border-gray-100" %}
33
+ {% set container_class = "flex items-center gap-3" if is_plain else "flex items-center gap-3 pb-3 mb-3 border-gray-100 border-b" %}
34
34
  {% set default_box_class = icon_box_class if icon_box_class else "bg-gray-50 rounded-lg border border-gray-100" %}
35
35
  <div class="{{ container_class }}">
36
36
  {{ icon_container(icon_html, box_class=default_box_class) }}
@@ -73,16 +73,26 @@
73
73
 
74
74
  {# --- MAIN CONTENT --- #}
75
75
 
76
+ <!-- Header: Feature Icons / Badges (Centered) -->
77
+ <!-- Increased top padding from pt-5 to pt-7 for more whitespace -->
78
+ <div class="pt-7 pb-5 px-6 flex justify-between items-center relative z-20">
79
+ <!-- Total Time Badge -->
80
+ <div class="flex items-center gap-1.5 px-3 py-1.5 bg-white/60 backdrop-blur rounded-lg shadow-sm border border-white/50">
81
+ <i class="ri-time-line text-gray-700 font-bold text-[13px]"></i>
82
+ <span class="text-[13px] font-bold text-gray-900 tracking-wide">{{ "%.1f"|format(total_time) }}s</span>
83
+ </div>
84
+ </div>
85
+
76
86
  <!-- Response Card (Content First) -->
77
- <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
78
- <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800">
87
+ <div class="bg-[#f2f2f2] rounded-2xl px-5 pt-0 pb-2 overflow-hidden">
88
+ <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800 [&>*:first-child]:!mt-0">
79
89
  {{ content_html | safe }}
80
90
  </div>
81
91
  </div>
82
92
 
83
93
  <!-- Speculation Card (Optional) -->
84
94
  {% if suggestions %}
85
- <div class="flex flex-col gap-2 bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
95
+ <div class="flex flex-col gap-2 bg-[#f2f2f2] rounded-2xl px-5 py-2 overflow-hidden">
86
96
  {% set sug_icon %}
87
97
  <i class="ri-magic-line text-lg text-pink-600"></i>
88
98
  {% endset %}
@@ -100,7 +110,7 @@
100
110
  {% endif %}
101
111
 
102
112
  <!-- Pipeline & Children (Nested) -->
103
- <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden flex flex-col gap-4">
113
+ <div class="bg-[#f2f2f2] rounded-2xl px-5 pt-2 pb-5 overflow-hidden flex flex-col gap-4">
104
114
 
105
115
  {% if stages %}
106
116
  {% for stage in stages %}
@@ -111,13 +121,13 @@
111
121
  {% set icon_box_class = color_class + " rounded-md border shrink-0" %}
112
122
 
113
123
  {% set title_html %}
114
- <span class="text-[11px] font-bold uppercase text-gray-700 shrink-0">{{ stage.name }}</span>
115
- <span class="text-[11px] font-medium text-gray-700 truncate min-w-0" title="{{ stage.model }}">{{ stage.model_short }}</span>
116
- <span class="ml-auto text-[10px] text-gray-400 shrink-0 truncate max-w-[80px]">{{ stage.provider }}</span>
124
+ <span class="text-[13px] font-bold uppercase text-gray-700 shrink-0">{{ stage.name }}</span>
125
+ <span class="text-[13px] font-medium text-gray-700 truncate min-w-0" title="{{ stage.model }}">{{ stage.model_short }}</span>
126
+ <span class="ml-auto text-[12px] text-gray-400 shrink-0 truncate max-w-[80px]">{{ stage.provider }}</span>
117
127
  {% endset %}
118
128
 
119
129
  {% set stats_html %}
120
- <div class="flex items-center gap-3 text-[11px] text-gray-500 font-mono mt-0.5">
130
+ <div class="flex items-center gap-3 text-[12px] text-gray-500 font-mono mt-0.5">
121
131
  <span>{{ stage.time_str }}</span><span>{{ stage.cost_str }}</span>
122
132
  </div>
123
133
  {% endset %}
@@ -125,12 +135,12 @@
125
135
  {{ list_card(stage.icon_html, title_html, subtitle_html=stats_html, is_compact=True, icon_box_class=icon_box_class) }}
126
136
 
127
137
  {# Nested Children (Indent & Connect) #}
128
- {% if stage.references or stage.mcp_steps %}
138
+ {% if stage.references or stage.flow_steps or stage.crawled_pages %}
129
139
  <div class="ml-4 pl-4 border-l-2 border-gray-200 mt-2 flex flex-col gap-2">
130
140
 
131
141
  {# References #}
132
142
  {% if stage.references %}
133
- <div class="text-[11px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-1">References</div>
143
+ <div class="text-[12px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-1">Search Results</div>
134
144
  {% for ref in stage.references %}
135
145
  {% set favicon_url = "https://www.google.com/s2/favicons?domain=" + ref.domain + "&sz=32" %}
136
146
 
@@ -140,42 +150,66 @@
140
150
 
141
151
  {% set ref_icon_box = "bg-white rounded border border-gray-100 w-6 h-6 shrink-0" %}
142
152
 
143
- {% set title_html = '<div class="text-[11px] font-medium text-gray-900 truncate underline decoration-gray-300 decoration-1 underline-offset-2 hover:text-black hover:decoration-gray-500 transition-colors">' + ref.title + '</div>' %}
144
- {% set subtitle_html = '<div class="text-[10px] text-gray-700 truncate">' + ref.domain + '</div>' %}
145
- {% set right_html = '<div class="flex items-center justify-center min-w-[14px] h-3.5 px-0.5 text-[9px] font-bold text-blue-600 bg-blue-50 border border-blue-200 rounded">' + (loop.index|string) + '</div>' %}
153
+ {% set title_html = '<div class="text-[13px] font-medium text-gray-900 truncate underline decoration-gray-300 decoration-1 underline-offset-2 hover:text-black hover:decoration-gray-500 transition-colors">' + ref.title + '</div>' %}
154
+ {% set subtitle_html = '<div class="text-[12px] text-gray-700 truncate">' + ref.domain + '</div>' %}
155
+ {% set right_html = '<div class="flex items-center justify-center min-w-[16px] h-4 px-0.5 text-[10px] font-bold text-blue-600 bg-blue-50 border border-blue-200 rounded">' + (loop.index|string) + '</div>' %}
146
156
 
147
157
  {{ list_card(ref_icon, title_html, subtitle_html=subtitle_html, link_url=ref.url, right_content_html=right_html, is_compact=True, icon_box_class=ref_icon_box) }}
148
158
  {% endfor %}
149
159
  {% endif %}
150
160
 
151
- {# MCP Steps #}
152
- {% if stage.mcp_steps %}
153
- <div class="text-[11px] uppercase font-bold text-orange-600 tracking-wider mb-1 mt-1">MCP Flow</div>
154
- {% for step in stage.mcp_steps %}
161
+ {# Flow Steps #}
162
+ {% if stage.flow_steps %}
163
+ <div class="text-[12px] uppercase font-bold text-orange-600 tracking-wider mb-1 mt-1">Flow</div>
164
+ {% for step in stage.flow_steps %}
155
165
  {% set icon_box_class = "rounded-md border border-gray-100 bg-white text-gray-500 shrink-0" %}
156
166
 
157
- {% set title_html = '<div class="text-[11px] font-medium text-gray-900 font-mono">' + step.name + '</div>' %}
158
- {% set subtitle_html = '<div class="text-[10px] text-gray-700 leading-tight">' + step.description + '</div>' if step.description else None %}
159
- {% set right_html = '<div class="flex items-center justify-center min-w-[14px] h-3.5 px-0.5 text-[9px] font-bold text-orange-600 bg-orange-50 border border-orange-200 rounded">' + ('abcdefghijklmnopqrstuvwxyz'[loop.index0]) + '</div>' %}
167
+ {% set title_html = '<div class="text-[13px] font-semibold text-gray-900 underline decoration-gray-300 decoration-1 underline-offset-2 truncate">' + step.description + '</div>' %}
168
+ {% set subtitle_html = '<div class="text-[12px] text-gray-700 leading-tight truncate">' + step.description + '</div>' %}
169
+ {% set right_html = '<div class="flex items-center justify-center min-w-[16px] h-4 px-0.5 text-[10px] font-bold text-orange-700 bg-orange-50 border border-orange-200 rounded">' + ('abcdefghijklmnopqrstuvwxyz'[loop.index0]) + '</div>' %}
160
170
 
161
171
  {{ list_card(step.icon_svg, title_html, subtitle_html=subtitle_html, right_content_html=right_html, is_compact=True, icon_box_class=icon_box_class) }}
162
172
  {% endfor %}
163
173
  {% endif %}
164
174
 
175
+ {# Crawled Pages #}
176
+ {% if stage.crawled_pages %}
177
+ <div class="text-[12px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-1">Fetched Pages</div>
178
+ {% for page in stage.crawled_pages %}
179
+ {% set domain = page.url.split('/')[2] if page.url.split('/')|length > 2 else '' %}
180
+ {% set favicon_url = "https://www.google.com/s2/favicons?domain=" + domain + "&sz=32" %}
181
+
182
+ {% set page_icon %}
183
+ <img src="{{ favicon_url }}" class="w-3.5 h-3.5 rounded-sm opacity-80">
184
+ {% endset %}
185
+
186
+ {% set page_icon_box = "bg-white rounded border border-gray-100 w-6 h-6 shrink-0" %}
187
+
188
+ {% set title_html = '<div class="text-[13px] font-medium text-gray-900 truncate underline decoration-gray-300 decoration-1 underline-offset-2 hover:text-black hover:decoration-gray-500 transition-colors">' + page.title + '</div>' %}
189
+ {% set subtitle_html = '<div class="text-[12px] text-gray-500 truncate">' + domain + '</div>' %}
190
+ {% set right_html = '<div class="flex items-center justify-center min-w-[16px] h-4 px-0.5 text-[10px] font-bold text-orange-600 bg-orange-50 border border-orange-200 rounded">' + (loop.index|string) + '</div>' %}
191
+
192
+ {{ list_card(page_icon, title_html, subtitle_html=subtitle_html, link_url=page.url, right_content_html=right_html, is_compact=True, icon_box_class=page_icon_box) }}
193
+ {% endfor %}
194
+ {% endif %}
195
+
165
196
  </div>
166
197
  {% endif %}
167
198
  </div>
168
199
  {% endfor %}
169
200
  {% endif %}
201
+
170
202
  </div>
171
203
 
172
-
173
- </div>
174
-
175
204
  <script>window.REFERENCES = {{ references_json | safe }};</script>
176
205
 
177
206
  <script>
178
207
  document.addEventListener("DOMContentLoaded", () => {
208
+ // Initialize Syntax Highlighting
209
+ if (typeof hljs !== 'undefined') {
210
+ hljs.highlightAll();
211
+ }
212
+
179
213
  // Render Math (KaTeX)
180
214
  const contentDiv = document.getElementById("markdown-content");
181
215
  if(typeof renderMathInElement !== 'undefined') {
@@ -190,8 +224,33 @@
190
224
  });
191
225
  }
192
226
 
193
- // Process Citations
227
+ // Process Citations - handles `search:id` (blue) and `page:id` (orange) formats
194
228
  function processCitations(rootNode) {
229
+ // 1. Handle citations inside <code> tags (generated by markdown backticks)
230
+ const codeElements = rootNode.querySelectorAll('code');
231
+ codeElements.forEach(code => {
232
+ const text = code.textContent.trim();
233
+ const match = /^(search|page):(\d+)$/i.exec(text);
234
+ if (match) {
235
+ const type = match[1].toLowerCase();
236
+ const id = match[2];
237
+ const isPage = type === "page";
238
+ const colorClass = isPage
239
+ ? "text-orange-600 bg-orange-50 border-orange-200"
240
+ : "text-blue-600 bg-blue-50 border-blue-200";
241
+
242
+ const span = document.createElement("span");
243
+ // Using same badge style
244
+ span.innerHTML = `<span class="inline-flex items-center justify-center min-w-[14px] h-4 px-0.5 text-[9px] font-bold ${colorClass} border rounded align-top -top-0.5 relative mx-0.5 cursor-default" title="${type}:${id}">${id}</span>`;
245
+
246
+ // Replace the <code> element with our badge
247
+ if (code.parentNode) {
248
+ code.parentNode.replaceChild(span.firstElementChild, code);
249
+ }
250
+ }
251
+ });
252
+
253
+ // 2. Handle citations in plain text (fallback for non-backticked ones)
195
254
  const walker = document.createTreeWalker(
196
255
  rootNode,
197
256
  NodeFilter.SHOW_TEXT,
@@ -202,8 +261,9 @@
202
261
  const nodesToReplace = [];
203
262
  let node;
204
263
  while (node = walker.nextNode()) {
205
- if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A") continue;
206
- if (/\[\d+\]/.test(node.nodeValue)) {
264
+ if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A" || node.parentElement.tagName === "CODE") continue;
265
+ // Match search:id or page:id
266
+ if (/(search|page):(\d+)/i.test(node.nodeValue)) {
207
267
  nodesToReplace.push(node);
208
268
  }
209
269
  }
@@ -212,43 +272,31 @@
212
272
  const fragment = document.createDocumentFragment();
213
273
  let lastIndex = 0;
214
274
  const text = textNode.nodeValue;
215
- const regex = /\[(\d+)\]/g;
275
+ const regex = /`?(search|page):(\d+)`?/gi;
216
276
  let match;
217
277
 
218
278
  while ((match = regex.exec(text)) !== null) {
219
279
  fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
220
280
 
221
- const id = match[1];
222
- const ref = window.REFERENCES ? window.REFERENCES.find(r => r.id == id) : null;
223
-
224
- if (ref) {
225
- const span = document.createElement("span");
226
- const title = ref.title.replace(/"/g, "&quot;");
227
- const url = ref.url;
228
- let domain = "unknown";
229
- try { domain = new URL(url).hostname; } catch (e) { }
230
- const favicon = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
231
-
232
- span.innerHTML = `<a href="${url}" target="_blank" class="citation-ref" data-id="${id}">
233
- ${id}
234
- <span class="citation-tooltip">
235
- <span class="citation-tooltip-title">${title}</span>
236
- <span class="citation-tooltip-url">
237
- <img src="${favicon}" style="width:12px;height:12px;vertical-align:middle;margin-right:4px;">
238
- ${domain}
239
- </span>
240
- </span>
241
- </a>`;
242
- fragment.appendChild(span.firstElementChild);
243
- } else {
244
- fragment.appendChild(document.createTextNode(match[0]));
245
- }
281
+ const type = match[1].toLowerCase();
282
+ const id = match[2];
283
+
284
+ const span = document.createElement("span");
285
+ const isPage = type === "page";
286
+ const colorClass = isPage
287
+ ? "text-orange-600 bg-orange-50 border-orange-200"
288
+ : "text-blue-600 bg-blue-50 border-blue-200";
289
+
290
+ span.innerHTML = `<span class="inline-flex items-center justify-center min-w-[14px] h-4 px-0.5 text-[9px] font-bold ${colorClass} border rounded align-top -top-0.5 relative mx-0.5 cursor-default" title="${type}:${id}">${id}</span>`;
291
+ fragment.appendChild(span.firstElementChild);
246
292
 
247
293
  lastIndex = regex.lastIndex;
248
294
  }
249
295
 
250
296
  fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
251
- textNode.parentNode.replaceChild(fragment, textNode);
297
+ if (textNode.parentNode) {
298
+ textNode.parentNode.replaceChild(fragment, textNode);
299
+ }
252
300
  });
253
301
  }
254
302
 
@@ -15,19 +15,18 @@ class HYWConfig:
15
15
  intruct_model_name: Optional[str] = None
16
16
  intruct_api_key: Optional[str] = None
17
17
  intruct_base_url: Optional[str] = None
18
- search_base_url: str = "https://duckduckgo.com/?q={query}&format=json&results_per_page={limit}"
19
- image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images&format=json&results_per_page={limit}"
18
+ search_base_url: str = "https://lite.duckduckgo.com/lite/?q={query}"
19
+ image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images"
20
20
  extra_body: Optional[Dict[str, Any]] = None
21
21
  temperature: float = 0.4
22
22
  max_turns: int = 10
23
23
  icon: str = "openai" # logo for primary model
24
24
  vision_icon: Optional[str] = None # logo for vision model (falls back to icon when absent)
25
+ instruct_icon: Optional[str] = None # logo for instruct model
25
26
  enable_browser_fallback: bool = False
26
27
  vision_system_prompt: Optional[str] = None
27
28
  intruct_system_prompt: Optional[str] = None
28
29
  agent_system_prompt: Optional[str] = None
29
- playwright_mcp_command: str = "npx"
30
- playwright_mcp_args: Optional[List[str]] = None
31
30
  input_price: Optional[float] = None # $ per 1M input tokens
32
31
  output_price: Optional[float] = None # $ per 1M output tokens
33
32
  vision_input_price: Optional[float] = None