entari-plugin-hyw 4.0.0rc5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (99) hide show
  1. entari_plugin_hyw/__init__.py +532 -0
  2. entari_plugin_hyw/assets/card-dist/index.html +387 -0
  3. entari_plugin_hyw/assets/card-dist/logos/anthropic.svg +1 -0
  4. entari_plugin_hyw/assets/card-dist/logos/cerebras.svg +9 -0
  5. entari_plugin_hyw/assets/card-dist/logos/deepseek.png +0 -0
  6. entari_plugin_hyw/assets/card-dist/logos/gemini.svg +1 -0
  7. entari_plugin_hyw/assets/card-dist/logos/google.svg +1 -0
  8. entari_plugin_hyw/assets/card-dist/logos/grok.png +0 -0
  9. entari_plugin_hyw/assets/card-dist/logos/huggingface.png +0 -0
  10. entari_plugin_hyw/assets/card-dist/logos/microsoft.svg +15 -0
  11. entari_plugin_hyw/assets/card-dist/logos/minimax.png +0 -0
  12. entari_plugin_hyw/assets/card-dist/logos/mistral.png +0 -0
  13. entari_plugin_hyw/assets/card-dist/logos/nvida.png +0 -0
  14. entari_plugin_hyw/assets/card-dist/logos/openai.svg +1 -0
  15. entari_plugin_hyw/assets/card-dist/logos/openrouter.png +0 -0
  16. entari_plugin_hyw/assets/card-dist/logos/perplexity.svg +24 -0
  17. entari_plugin_hyw/assets/card-dist/logos/qwen.png +0 -0
  18. entari_plugin_hyw/assets/card-dist/logos/xai.png +0 -0
  19. entari_plugin_hyw/assets/card-dist/logos/xiaomi.png +0 -0
  20. entari_plugin_hyw/assets/card-dist/logos/zai.png +0 -0
  21. entari_plugin_hyw/assets/card-dist/vite.svg +1 -0
  22. entari_plugin_hyw/assets/icon/anthropic.svg +1 -0
  23. entari_plugin_hyw/assets/icon/cerebras.svg +9 -0
  24. entari_plugin_hyw/assets/icon/deepseek.png +0 -0
  25. entari_plugin_hyw/assets/icon/gemini.svg +1 -0
  26. entari_plugin_hyw/assets/icon/google.svg +1 -0
  27. entari_plugin_hyw/assets/icon/grok.png +0 -0
  28. entari_plugin_hyw/assets/icon/huggingface.png +0 -0
  29. entari_plugin_hyw/assets/icon/microsoft.svg +15 -0
  30. entari_plugin_hyw/assets/icon/minimax.png +0 -0
  31. entari_plugin_hyw/assets/icon/mistral.png +0 -0
  32. entari_plugin_hyw/assets/icon/nvida.png +0 -0
  33. entari_plugin_hyw/assets/icon/openai.svg +1 -0
  34. entari_plugin_hyw/assets/icon/openrouter.png +0 -0
  35. entari_plugin_hyw/assets/icon/perplexity.svg +24 -0
  36. entari_plugin_hyw/assets/icon/qwen.png +0 -0
  37. entari_plugin_hyw/assets/icon/xai.png +0 -0
  38. entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
  39. entari_plugin_hyw/assets/icon/zai.png +0 -0
  40. entari_plugin_hyw/browser/__init__.py +10 -0
  41. entari_plugin_hyw/browser/engines/base.py +13 -0
  42. entari_plugin_hyw/browser/engines/bing.py +95 -0
  43. entari_plugin_hyw/browser/engines/searxng.py +137 -0
  44. entari_plugin_hyw/browser/landing.html +172 -0
  45. entari_plugin_hyw/browser/manager.py +153 -0
  46. entari_plugin_hyw/browser/service.py +275 -0
  47. entari_plugin_hyw/card-ui/.gitignore +24 -0
  48. entari_plugin_hyw/card-ui/README.md +5 -0
  49. entari_plugin_hyw/card-ui/index.html +16 -0
  50. entari_plugin_hyw/card-ui/package-lock.json +2342 -0
  51. entari_plugin_hyw/card-ui/package.json +31 -0
  52. entari_plugin_hyw/card-ui/public/logos/anthropic.svg +1 -0
  53. entari_plugin_hyw/card-ui/public/logos/cerebras.svg +9 -0
  54. entari_plugin_hyw/card-ui/public/logos/deepseek.png +0 -0
  55. entari_plugin_hyw/card-ui/public/logos/gemini.svg +1 -0
  56. entari_plugin_hyw/card-ui/public/logos/google.svg +1 -0
  57. entari_plugin_hyw/card-ui/public/logos/grok.png +0 -0
  58. entari_plugin_hyw/card-ui/public/logos/huggingface.png +0 -0
  59. entari_plugin_hyw/card-ui/public/logos/microsoft.svg +15 -0
  60. entari_plugin_hyw/card-ui/public/logos/minimax.png +0 -0
  61. entari_plugin_hyw/card-ui/public/logos/mistral.png +0 -0
  62. entari_plugin_hyw/card-ui/public/logos/nvida.png +0 -0
  63. entari_plugin_hyw/card-ui/public/logos/openai.svg +1 -0
  64. entari_plugin_hyw/card-ui/public/logos/openrouter.png +0 -0
  65. entari_plugin_hyw/card-ui/public/logos/perplexity.svg +24 -0
  66. entari_plugin_hyw/card-ui/public/logos/qwen.png +0 -0
  67. entari_plugin_hyw/card-ui/public/logos/xai.png +0 -0
  68. entari_plugin_hyw/card-ui/public/logos/xiaomi.png +0 -0
  69. entari_plugin_hyw/card-ui/public/logos/zai.png +0 -0
  70. entari_plugin_hyw/card-ui/public/vite.svg +1 -0
  71. entari_plugin_hyw/card-ui/src/App.vue +756 -0
  72. entari_plugin_hyw/card-ui/src/assets/vue.svg +1 -0
  73. entari_plugin_hyw/card-ui/src/components/HelloWorld.vue +41 -0
  74. entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +382 -0
  75. entari_plugin_hyw/card-ui/src/components/SectionCard.vue +41 -0
  76. entari_plugin_hyw/card-ui/src/components/StageCard.vue +240 -0
  77. entari_plugin_hyw/card-ui/src/main.ts +5 -0
  78. entari_plugin_hyw/card-ui/src/style.css +29 -0
  79. entari_plugin_hyw/card-ui/src/test_regex.js +103 -0
  80. entari_plugin_hyw/card-ui/src/types.ts +61 -0
  81. entari_plugin_hyw/card-ui/tsconfig.app.json +16 -0
  82. entari_plugin_hyw/card-ui/tsconfig.json +7 -0
  83. entari_plugin_hyw/card-ui/tsconfig.node.json +26 -0
  84. entari_plugin_hyw/card-ui/vite.config.ts +16 -0
  85. entari_plugin_hyw/definitions.py +130 -0
  86. entari_plugin_hyw/history.py +248 -0
  87. entari_plugin_hyw/image_cache.py +274 -0
  88. entari_plugin_hyw/misc.py +135 -0
  89. entari_plugin_hyw/modular_pipeline.py +351 -0
  90. entari_plugin_hyw/render_vue.py +401 -0
  91. entari_plugin_hyw/search.py +116 -0
  92. entari_plugin_hyw/stage_base.py +88 -0
  93. entari_plugin_hyw/stage_instruct.py +328 -0
  94. entari_plugin_hyw/stage_instruct_review.py +92 -0
  95. entari_plugin_hyw/stage_summary.py +164 -0
  96. entari_plugin_hyw-4.0.0rc5.dist-info/METADATA +116 -0
  97. entari_plugin_hyw-4.0.0rc5.dist-info/RECORD +99 -0
  98. entari_plugin_hyw-4.0.0rc5.dist-info/WHEEL +5 -0
  99. entari_plugin_hyw-4.0.0rc5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,137 @@
1
+
2
+ import urllib.parse
3
+ import re
4
+ from typing import List, Dict, Any
5
+ from loguru import logger
6
+ from .base import SearchEngine
7
+
8
+ class SearXNGEngine(SearchEngine):
9
+ """
10
+ Parser for DuckDuckGo and SearXNG results.
11
+ Handles both Markdown (from Crawl4AI) and HTML (fallback).
12
+ """
13
+
14
+ def build_url(self, query: str, limit: int = 10) -> str:
15
+ encoded_query = urllib.parse.quote(query)
16
+ # Default fallback if not configurable per instance, but usually this is what we support as "searxng"
17
+ base = "https://lite.duckduckgo.com/lite/"
18
+ return f"{base}?q={encoded_query}"
19
+
20
+ def parse(self, content: str) -> List[Dict[str, Any]]:
21
+ # Prioritize HTML parsing if content looks like HTML
22
+ if "<html" in content.lower() or "<!doctype" in content.lower() or "<div" in content.lower():
23
+ results = self._parse_html(content)
24
+ if results:
25
+ return results
26
+
27
+ # Fallback to Markdown
28
+ return self._parse_markdown(content)
29
+
30
+ def _parse_html(self, content: str) -> List[Dict[str, Any]]:
31
+ results = []
32
+ seen_urls = set()
33
+
34
+ # Simple regex for DDG Lite / SearXNG HTML structure
35
+ link_regex = re.compile(r'<a[^>]+href=["\'](http[^"\']+)["\'][^>]*>(.*?)</a>', re.IGNORECASE | re.DOTALL)
36
+
37
+ pos = 0
38
+ while True:
39
+ match = link_regex.search(content, pos)
40
+ if not match:
41
+ break
42
+
43
+ href = match.group(1)
44
+ title_html = match.group(2)
45
+
46
+ # Clean title
47
+ title = re.sub(r'<[^>]+>', '', title_html).strip()
48
+
49
+ pos = match.end()
50
+
51
+ # Filter junk
52
+ if "search" in href and "q=" in href: continue
53
+ if "google.com" in href or "bing.com" in href: continue
54
+ if href in seen_urls: continue
55
+
56
+ # Look ahead for snippet
57
+ snippet_chunk = content[pos:pos+1000]
58
+ snippet_match = re.search(r'(.*?)<a', snippet_chunk, re.DOTALL | re.IGNORECASE)
59
+ raw_snippet = snippet_match.group(1) if snippet_match else snippet_chunk
60
+
61
+ # Clean HTML tags from snippet
62
+ snippet = re.sub(r'<[^>]+>', ' ', raw_snippet)
63
+ snippet = re.sub(r'\s+', ' ', snippet).strip()
64
+
65
+ # No truncation as per user request (or very generous limit)
66
+ snippet = snippet[:5000]
67
+
68
+ # Valid result check
69
+ if title and len(title) > 2 and snippet:
70
+ # Extract images from the result block (rough heuristic)
71
+ images = []
72
+ img_matches = re.findall(r'<img[^>]+src=["\'](http[^"\']+)["\']', snippet_match.group(0) if snippet_match else snippet_chunk)
73
+ for img_url in img_matches:
74
+ if not any(x in img_url for x in ['favicon', 'icon', 'tracking', 'pixel']):
75
+ images.append(img_url)
76
+
77
+ results.append({
78
+ "title": title,
79
+ "url": href,
80
+ "domain": urllib.parse.urlparse(href).hostname or "",
81
+ "content": snippet,
82
+ "images": images[:3] # Limit per result
83
+ })
84
+ seen_urls.add(href)
85
+
86
+ logger.info(f"SearXNG Parser(HTML) found {len(results)} results.")
87
+ return results
88
+
89
+ def _parse_markdown(self, content: str) -> List[Dict[str, Any]]:
90
+ results = []
91
+ seen_urls = set()
92
+
93
+ # Link regex: [Title](URL)
94
+ link_regex = re.compile(r'\[(.*?)\]\((https?://.*?)\)')
95
+
96
+ lines = content.split('\n')
97
+ current_result = None
98
+
99
+ for line in lines:
100
+ line = line.strip()
101
+ if not line: continue
102
+
103
+ # Check for link
104
+ match = link_regex.search(line)
105
+ if match:
106
+ # Save previous result
107
+ if current_result:
108
+ results.append(current_result)
109
+
110
+ title, href = match.groups()
111
+
112
+ # Filter junk
113
+ if "search" in href and "q=" in href: continue
114
+ if "google.com" in href or "bing.com" in href: continue
115
+ if href in seen_urls:
116
+ current_result = None
117
+ continue
118
+
119
+ seen_urls.add(href)
120
+
121
+ current_result = {
122
+ "title": title,
123
+ "url": href,
124
+ "domain": urllib.parse.urlparse(href).hostname or "",
125
+ "content": ""
126
+ }
127
+ elif current_result:
128
+ # Append snippet
129
+ if len(current_result["content"]) < 5000:
130
+ current_result["content"] += " " + line
131
+
132
+ # Append last
133
+ if current_result:
134
+ results.append(current_result)
135
+
136
+ logger.info(f"SearXNG Parser(Markdown) found {len(results)} results.")
137
+ return results
@@ -0,0 +1,172 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>entari-plugin-hyw Browser</title>
7
+ <style>
8
+ :root {
9
+ --theme-color: #ef4444;
10
+ --text-primary: #2c2c2e;
11
+ --text-body: #3a3a3c;
12
+ --text-muted: #86868b;
13
+ --bg-border: #f2f2f2;
14
+ }
15
+
16
+ body {
17
+ background-color: var(--bg-border);
18
+ color: var(--text-primary);
19
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
20
+ margin: 0;
21
+ padding: 0;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ height: 100vh;
26
+ overflow: hidden;
27
+ }
28
+
29
+ #main-container {
30
+ width: 560px;
31
+ background: white;
32
+ padding: 40px;
33
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
34
+ position: relative;
35
+ transform: scale(1.1);
36
+ /* Slightly larger for visibility */
37
+ }
38
+
39
+ .corner-badge {
40
+ position: absolute;
41
+ top: -10px;
42
+ left: -10px;
43
+ height: 28px;
44
+ padding: 0 12px;
45
+ background-color: var(--theme-color);
46
+ color: white;
47
+ display: flex;
48
+ align-items: center;
49
+ font-size: 12px;
50
+ font-weight: 800;
51
+ text-transform: uppercase;
52
+ letter-spacing: 0.5px;
53
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.15);
54
+ z-index: 10;
55
+ }
56
+
57
+ h1 {
58
+ font-size: 32px;
59
+ font-weight: 900;
60
+ text-transform: uppercase;
61
+ letter-spacing: -1px;
62
+ margin: 0 0 24px 0;
63
+ line-height: 1.1;
64
+ }
65
+
66
+ .brand-text {
67
+ color: var(--theme-color);
68
+ }
69
+
70
+ .section-title {
71
+ font-size: 17px;
72
+ font-bold: 700;
73
+ text-transform: uppercase;
74
+ letter-spacing: -0.5px;
75
+ margin-bottom: 12px;
76
+ color: var(--text-primary);
77
+ }
78
+
79
+ .content-box {
80
+ padding-top: 24px;
81
+ border-top: 1px solid #eee;
82
+ margin-top: 32px;
83
+ }
84
+
85
+ .summary {
86
+ font-size: 15px;
87
+ line-height: 1.6;
88
+ color: var(--text-body);
89
+ text-align: justify;
90
+ }
91
+
92
+ .summary-en {
93
+ margin-top: 16px;
94
+ color: var(--text-muted);
95
+ font-size: 13px;
96
+ font-style: italic;
97
+ line-height: 1.5;
98
+ }
99
+
100
+ b {
101
+ color: var(--theme-color);
102
+ font-weight: 700;
103
+ }
104
+
105
+ /* Subtle loading animation to show it's "live" */
106
+ .progress-bar {
107
+ height: 3px;
108
+ background: #f3f3f3;
109
+ width: 100%;
110
+ margin-top: 32px;
111
+ position: relative;
112
+ overflow: hidden;
113
+ }
114
+
115
+ .progress-fill {
116
+ position: absolute;
117
+ height: 100%;
118
+ background: var(--theme-color);
119
+ width: 30%;
120
+ left: -30%;
121
+ animation: moveProgress 2s infinite ease-in-out;
122
+ }
123
+
124
+ @keyframes moveProgress {
125
+ 0% {
126
+ left: -30%;
127
+ width: 20%;
128
+ }
129
+
130
+ 50% {
131
+ width: 50%;
132
+ }
133
+
134
+ 100% {
135
+ left: 100%;
136
+ width: 20%;
137
+ }
138
+ }
139
+ </style>
140
+ </head>
141
+
142
+ <body>
143
+ <div id="main-container">
144
+ <!-- Corner Badge Style -->
145
+ <div class="corner-badge">
146
+ Status: Ready
147
+ </div>
148
+
149
+ <h1>entari-plugin-<span class="brand-text">hyw</span></h1>
150
+
151
+ <div class="content-box">
152
+ <div class="section-title">Browser Service</div>
153
+ <div class="summary">
154
+ 这是一个受 <b>entari-plugin-hyw</b> 插件控制的自动化浏览器实例。<br>
155
+ 它负责网络搜索、内容爬取以及卡片 UI 的实时渲染。<br>
156
+ 请勿关闭此窗口,以确保插件功能正常运行。
157
+ </div>
158
+
159
+ <div class="summary-en">
160
+ This is an automated browser instance controlled by the <b>entari-plugin-hyw</b> plugin.
161
+ It handles web searches, content crawling, and real-time Card UI rendering.
162
+ Please do not close this window to ensure the plugin functions correctly.
163
+ </div>
164
+ </div>
165
+
166
+ <div class="progress-bar">
167
+ <div class="progress-fill"></div>
168
+ </div>
169
+ </div>
170
+ </body>
171
+
172
+ </html>
@@ -0,0 +1,153 @@
1
+ """
2
+ Shared Browser Manager (DrissionPage)
3
+
4
+ Manages a single ChromiumPage browser instance.
5
+ Supports multi-tab concurrency via DrissionPage's built-in tab management.
6
+ """
7
+
8
+ import threading
9
+ from typing import Optional, Any
10
+ from loguru import logger
11
+ from DrissionPage import ChromiumPage, ChromiumOptions
12
+ from DrissionPage.errors import PageDisconnectedError
13
+
14
+ class SharedBrowserManager:
15
+ """
16
+ Manages a shared DrissionPage Chromium browser.
17
+ """
18
+
19
+ _instance: Optional["SharedBrowserManager"] = None
20
+ _lock = threading.Lock()
21
+
22
+ def __init__(self, headless: bool = True):
23
+ self.headless = headless
24
+ self._page: Optional[ChromiumPage] = None
25
+ self._starting = False
26
+ self._tab_lock = threading.Lock()
27
+
28
+ @classmethod
29
+ def get_instance(cls, headless: bool = True) -> "SharedBrowserManager":
30
+ """Get or create the singleton SharedBrowserManager."""
31
+ with cls._lock:
32
+ if cls._instance is None:
33
+ cls._instance = cls(headless=headless)
34
+ return cls._instance
35
+
36
+ def start(self) -> bool:
37
+ """Start the Chromium browser."""
38
+ if self._page is not None:
39
+ # Check if alive
40
+ try:
41
+ if self._page.run_cdp('Browser.getVersion'):
42
+ return True
43
+ except (PageDisconnectedError, Exception):
44
+ self._page = None
45
+
46
+ with self._lock:
47
+ if self._starting:
48
+ return False
49
+ self._starting = True
50
+
51
+ try:
52
+ logger.info("SharedBrowserManager: Starting DrissionPage browser...")
53
+
54
+ # Configure options
55
+ co = ChromiumOptions()
56
+ co.headless(self.headless)
57
+ co.auto_port() # Auto find available port
58
+
59
+ # Anti-detection settings
60
+ co.set_argument('--no-sandbox')
61
+ co.set_argument('--disable-gpu')
62
+ # Essential for loading local files and avoiding CORS issues
63
+ co.set_argument('--allow-file-access-from-files')
64
+ co.set_argument('--disable-web-security')
65
+ # Hide scrollbars globally
66
+ co.set_argument('--hide-scrollbars')
67
+ # 十万的原因是滚动条屏蔽(大概吧)
68
+ co.set_argument('--window-size=1280,20000')
69
+ self._page = ChromiumPage(addr_or_opts=co)
70
+
71
+ # Show Landing Page
72
+ try:
73
+ import os
74
+ landing_path = os.path.join(os.path.dirname(__file__), 'landing.html')
75
+ if os.path.exists(landing_path):
76
+ self._page.get(f"file://{landing_path}")
77
+ except Exception as e:
78
+ logger.warning(f"SharedBrowserManager: Failed to show landing page: {e}")
79
+
80
+ logger.success(f"SharedBrowserManager: Browser ready (port={self._page.address})")
81
+ return True
82
+
83
+ except Exception as e:
84
+ logger.error(f"SharedBrowserManager: Failed to start browser: {e}")
85
+ self._page = None
86
+ raise
87
+ finally:
88
+ self._starting = False
89
+
90
+ @property
91
+ def page(self) -> Optional[ChromiumPage]:
92
+ """Get the main ChromiumPage instance."""
93
+ if self._page is None:
94
+ self.start()
95
+ return self._page
96
+
97
+ def new_tab(self, url: str) -> Any:
98
+ """Thread-safe tab creation."""
99
+ with self._tab_lock:
100
+ page = self.page
101
+ if not page:
102
+ raise RuntimeError("Browser not available")
103
+ return page.new_tab(url)
104
+
105
+ def close(self):
106
+ """Shutdown the browser."""
107
+ with self._lock:
108
+ if self._page:
109
+ try:
110
+ self._page.quit()
111
+ logger.info("SharedBrowserManager: Browser closed.")
112
+ except Exception as e:
113
+ logger.warning(f"SharedBrowserManager: Error closing browser: {e}")
114
+ finally:
115
+ self._page = None
116
+
117
+ @staticmethod
118
+ def hide_scrollbars(page: ChromiumPage):
119
+ """
120
+ Robustly hide scrollbars using CDP commands.
121
+ This eliminates the reserved space/gutter that standard CSS might miss.
122
+ """
123
+ try:
124
+ # Emulation.setScrollbarsHidden is a CDP command
125
+ page.run_cdp('Emulation.setScrollbarsHidden', hidden=True)
126
+ logger.debug("SharedBrowserManager: CDP scrollbars hidden.")
127
+ except Exception as e:
128
+ logger.warning(f"SharedBrowserManager: Failed to hide scrollbars via CDP: {e}")
129
+
130
+
131
+ # Module-level singleton accessor
132
+ _shared_manager: Optional[SharedBrowserManager] = None
133
+
134
+
135
+ def get_shared_browser_manager(headless: bool = True) -> SharedBrowserManager:
136
+ """
137
+ Get or create the shared browser manager.
138
+ """
139
+ global _shared_manager
140
+
141
+ if _shared_manager is None:
142
+ _shared_manager = SharedBrowserManager.get_instance(headless=headless)
143
+ _shared_manager.start()
144
+
145
+ return _shared_manager
146
+
147
+
148
+ def close_shared_browser():
149
+ """Close the shared browser manager."""
150
+ global _shared_manager
151
+ if _shared_manager:
152
+ _shared_manager.close()
153
+ _shared_manager = None