entari-plugin-hyw 3.5.0rc1__py3-none-any.whl → 3.5.0rc2__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.
- entari_plugin_hyw/__init__.py +77 -82
- entari_plugin_hyw/assets/card-dist/index.html +360 -99
- entari_plugin_hyw/card-ui/src/App.vue +246 -52
- entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +122 -67
- entari_plugin_hyw/card-ui/src/components/StageCard.vue +46 -26
- entari_plugin_hyw/card-ui/src/test_regex.js +103 -0
- entari_plugin_hyw/card-ui/src/types.ts +1 -0
- entari_plugin_hyw/{core/history.py → history.py} +25 -1
- entari_plugin_hyw/image_cache.py +283 -0
- entari_plugin_hyw/{core/pipeline.py → pipeline.py} +102 -27
- entari_plugin_hyw/{utils/prompts.py → prompts.py} +7 -24
- entari_plugin_hyw/render_vue.py +314 -0
- entari_plugin_hyw/{utils/search.py → search.py} +227 -10
- {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/METADATA +1 -1
- {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/RECORD +18 -29
- entari_plugin_hyw/core/__init__.py +0 -0
- entari_plugin_hyw/core/config.py +0 -35
- entari_plugin_hyw/core/hyw.py +0 -48
- entari_plugin_hyw/core/render_vue.py +0 -255
- entari_plugin_hyw/test_output/render_0.jpg +0 -0
- entari_plugin_hyw/test_output/render_1.jpg +0 -0
- entari_plugin_hyw/test_output/render_2.jpg +0 -0
- entari_plugin_hyw/test_output/render_3.jpg +0 -0
- entari_plugin_hyw/test_output/render_4.jpg +0 -0
- entari_plugin_hyw/tests/ui_test_output.jpg +0 -0
- entari_plugin_hyw/tests/verify_ui.py +0 -139
- entari_plugin_hyw/utils/__init__.py +0 -2
- entari_plugin_hyw/utils/browser.py +0 -40
- entari_plugin_hyw/utils/playwright_tool.py +0 -36
- /entari_plugin_hyw/{utils/misc.py → misc.py} +0 -0
- {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc2.dist-info}/top_level.txt +0 -0
entari_plugin_hyw/core/config.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional, Dict, Any, List
|
|
3
|
-
|
|
4
|
-
@dataclass
|
|
5
|
-
class HYWConfig:
|
|
6
|
-
api_key: str
|
|
7
|
-
model_name: str
|
|
8
|
-
vision_model_name: Optional[str] = None
|
|
9
|
-
vision_api_key: Optional[str] = None
|
|
10
|
-
vision_base_url: Optional[str] = None
|
|
11
|
-
base_url: str = "https://openrouter.ai/api/v1"
|
|
12
|
-
fusion_mode: bool = False
|
|
13
|
-
save_conversation: bool = False
|
|
14
|
-
headless: bool = True
|
|
15
|
-
instruct_model_name: Optional[str] = None
|
|
16
|
-
instruct_api_key: Optional[str] = None
|
|
17
|
-
instruct_base_url: Optional[str] = None
|
|
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
|
-
search_params: Optional[str] = None # e.g. "&kl=cn-zh" for China region
|
|
21
|
-
search_limit: int = 8
|
|
22
|
-
extra_body: Optional[Dict[str, Any]] = None
|
|
23
|
-
vision_extra_body: Optional[Dict[str, Any]] = None
|
|
24
|
-
instruct_extra_body: Optional[Dict[str, Any]] = None
|
|
25
|
-
temperature: float = 0.4
|
|
26
|
-
max_turns: int = 10
|
|
27
|
-
enable_browser_fallback: bool = False
|
|
28
|
-
language: str = "Simplified Chinese"
|
|
29
|
-
input_price: Optional[float] = None # $ per 1M input tokens
|
|
30
|
-
output_price: Optional[float] = None # $ per 1M output tokens
|
|
31
|
-
vision_input_price: Optional[float] = None
|
|
32
|
-
vision_output_price: Optional[float] = None
|
|
33
|
-
instruct_input_price: Optional[float] = None
|
|
34
|
-
instruct_output_price: Optional[float] = None
|
|
35
|
-
|
entari_plugin_hyw/core/hyw.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional
|
|
2
|
-
from loguru import logger
|
|
3
|
-
from .config import HYWConfig
|
|
4
|
-
from .pipeline import ProcessingPipeline
|
|
5
|
-
|
|
6
|
-
class HYW:
|
|
7
|
-
"""
|
|
8
|
-
V2 Core Wrapper (Facade).
|
|
9
|
-
Delegates all logic to ProcessingPipeline.
|
|
10
|
-
Ensures safe lifecycle management.
|
|
11
|
-
"""
|
|
12
|
-
def __init__(self, config: HYWConfig):
|
|
13
|
-
self.config = config
|
|
14
|
-
# No persistent pipeline - we create one per request to ensure thread safety
|
|
15
|
-
# self.pipeline = ProcessingPipeline(config)
|
|
16
|
-
logger.info(f"HYW V2 (Ironclad) initialized - Model: {config.model_name}")
|
|
17
|
-
|
|
18
|
-
async def agent(self, user_input: str, conversation_history: List[Dict] = None, images: List[str] = None,
|
|
19
|
-
selected_model: str = None, selected_vision_model: str = None, local_mode: bool = False) -> Dict[str, Any]:
|
|
20
|
-
"""
|
|
21
|
-
Main entry point for the plugin (called by __init__.py).
|
|
22
|
-
Creates a fresh pipeline instance for each request to avoid state contamination (race conditions).
|
|
23
|
-
"""
|
|
24
|
-
pipeline = ProcessingPipeline(self.config)
|
|
25
|
-
try:
|
|
26
|
-
# Delegate completely to pipeline
|
|
27
|
-
result = await pipeline.execute(
|
|
28
|
-
user_input,
|
|
29
|
-
conversation_history or [],
|
|
30
|
-
model_name=selected_model,
|
|
31
|
-
images=images,
|
|
32
|
-
selected_vision_model=selected_vision_model,
|
|
33
|
-
)
|
|
34
|
-
return result
|
|
35
|
-
finally:
|
|
36
|
-
await pipeline.close()
|
|
37
|
-
|
|
38
|
-
async def close(self):
|
|
39
|
-
"""Explicit async close method. NO __del__."""
|
|
40
|
-
# Close shared resources
|
|
41
|
-
try:
|
|
42
|
-
from ..utils.search import close_shared_crawler
|
|
43
|
-
await close_shared_crawler()
|
|
44
|
-
except Exception:
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
|
-
# Legacy Compatibility (optional attributes just to prevent blind attribute errors if referenced externally)
|
|
48
|
-
# in V2 we strongly discourage accessing internal tools directly.
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Vue-based Card Renderer (Minimal Python)
|
|
3
|
-
|
|
4
|
-
Python only provides raw data. All frontend logic (markdown, syntax highlighting,
|
|
5
|
-
math rendering, citations) is handled by the Vue frontend.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import gc
|
|
10
|
-
import uuid
|
|
11
|
-
import os
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import List, Dict, Any
|
|
14
|
-
|
|
15
|
-
import asyncio
|
|
16
|
-
from loguru import logger
|
|
17
|
-
from playwright.async_api import async_playwright
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ContentRenderer:
|
|
21
|
-
"""Minimal renderer - only passes raw data to Vue template."""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def __init__(self, template_path: str = None):
|
|
25
|
-
if template_path is None:
|
|
26
|
-
current_dir = Path(__file__).parent
|
|
27
|
-
plugin_root = current_dir.parent
|
|
28
|
-
template_path = plugin_root / "assets" / "card-dist" / "index.html"
|
|
29
|
-
|
|
30
|
-
self.template_path = Path(template_path)
|
|
31
|
-
if not self.template_path.exists():
|
|
32
|
-
raise FileNotFoundError(f"Vue template not found: {self.template_path}")
|
|
33
|
-
|
|
34
|
-
self.template_content = self.template_path.read_text(encoding="utf-8")
|
|
35
|
-
logger.info(f"ContentRenderer: loaded Vue template ({len(self.template_content)} bytes)")
|
|
36
|
-
|
|
37
|
-
# Persistent state
|
|
38
|
-
self.playwright = None
|
|
39
|
-
self.browser = None
|
|
40
|
-
self.context = None
|
|
41
|
-
self.page = None
|
|
42
|
-
self._lock = asyncio.Lock()
|
|
43
|
-
self._render_count = 0
|
|
44
|
-
self._max_renders_before_restart = 50 # Prevent memory leaks
|
|
45
|
-
|
|
46
|
-
async def start(self):
|
|
47
|
-
"""Initialize the browser and page."""
|
|
48
|
-
if self.page:
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
logger.info("ContentRenderer: Starting persistent browser...")
|
|
52
|
-
try:
|
|
53
|
-
self.playwright = await async_playwright().start()
|
|
54
|
-
self.browser = await self.playwright.chromium.launch(headless=True, args=['--no-sandbox', '--disable-setuid-sandbox'])
|
|
55
|
-
self.context = await self.browser.new_context(
|
|
56
|
-
viewport={"width": 520, "height": 1400},
|
|
57
|
-
device_scale_factor=2.4,
|
|
58
|
-
)
|
|
59
|
-
self.page = await self.context.new_page()
|
|
60
|
-
|
|
61
|
-
# Load the template once
|
|
62
|
-
await self.page.goto(self.template_path.as_uri(), wait_until="networkidle")
|
|
63
|
-
logger.info("ContentRenderer: Browser started and template loaded.")
|
|
64
|
-
|
|
65
|
-
except Exception as e:
|
|
66
|
-
logger.error(f"ContentRenderer: Failed to start browser: {e}")
|
|
67
|
-
await self.close()
|
|
68
|
-
raise
|
|
69
|
-
|
|
70
|
-
async def close(self):
|
|
71
|
-
"""Clean up browser resources."""
|
|
72
|
-
if self.page:
|
|
73
|
-
await self.page.close()
|
|
74
|
-
self.page = None
|
|
75
|
-
if self.context:
|
|
76
|
-
await self.context.close()
|
|
77
|
-
self.context = None
|
|
78
|
-
if self.browser:
|
|
79
|
-
await self.browser.close()
|
|
80
|
-
self.browser = None
|
|
81
|
-
if self.playwright:
|
|
82
|
-
await self.playwright.stop()
|
|
83
|
-
self.playwright = None
|
|
84
|
-
logger.info("ContentRenderer: Browser closed.")
|
|
85
|
-
|
|
86
|
-
async def _get_page(self):
|
|
87
|
-
"""Get or recreate the persistent page."""
|
|
88
|
-
if self._render_count >= self._max_renders_before_restart:
|
|
89
|
-
logger.info(f"ContentRenderer: Restarting browser after {self._render_count} renders...")
|
|
90
|
-
await self.close()
|
|
91
|
-
self._render_count = 0
|
|
92
|
-
|
|
93
|
-
if not self.page:
|
|
94
|
-
await self.start()
|
|
95
|
-
|
|
96
|
-
return self.page
|
|
97
|
-
|
|
98
|
-
async def render(
|
|
99
|
-
self,
|
|
100
|
-
markdown_content: str,
|
|
101
|
-
output_path: str,
|
|
102
|
-
stats: Dict[str, Any] = None,
|
|
103
|
-
references: List[Dict[str, Any]] = None,
|
|
104
|
-
page_references: List[Dict[str, Any]] = None,
|
|
105
|
-
image_references: List[Dict[str, Any]] = None,
|
|
106
|
-
stages_used: List[Dict[str, Any]] = None,
|
|
107
|
-
image_timeout: int = 3000,
|
|
108
|
-
**kwargs
|
|
109
|
-
) -> bool:
|
|
110
|
-
"""Render content to image using persistent browser."""
|
|
111
|
-
|
|
112
|
-
resolved_output_path = Path(output_path).resolve()
|
|
113
|
-
resolved_output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
114
|
-
|
|
115
|
-
# Prepare data
|
|
116
|
-
stats_dict = stats[0] if isinstance(stats, list) and stats else (stats or {})
|
|
117
|
-
|
|
118
|
-
render_data = {
|
|
119
|
-
"markdown": markdown_content,
|
|
120
|
-
"total_time": stats_dict.get("total_time", 0) or 0,
|
|
121
|
-
"stages": [
|
|
122
|
-
{
|
|
123
|
-
"name": s.get("name", "Step"),
|
|
124
|
-
"model": s.get("model", ""),
|
|
125
|
-
"provider": s.get("provider", ""),
|
|
126
|
-
"time": s.get("time", 0),
|
|
127
|
-
"cost": s.get("cost", 0),
|
|
128
|
-
"references": s.get("references") or s.get("search_results"),
|
|
129
|
-
"image_references": s.get("image_references"),
|
|
130
|
-
"crawled_pages": s.get("crawled_pages"),
|
|
131
|
-
}
|
|
132
|
-
for s in (stages_used or [])
|
|
133
|
-
],
|
|
134
|
-
"references": references or [],
|
|
135
|
-
"page_references": page_references or [],
|
|
136
|
-
"image_references": image_references or [],
|
|
137
|
-
"stats": stats_dict,
|
|
138
|
-
}
|
|
139
|
-
import time
|
|
140
|
-
start_time = time.time()
|
|
141
|
-
|
|
142
|
-
# Reorder images
|
|
143
|
-
self._reorder_images_in_stages(render_data["markdown"], render_data["stages"])
|
|
144
|
-
|
|
145
|
-
async with self._lock:
|
|
146
|
-
try:
|
|
147
|
-
page = await self._get_page()
|
|
148
|
-
|
|
149
|
-
# Update data via JS
|
|
150
|
-
# Using evaluate to call window.updateRenderData
|
|
151
|
-
await page.evaluate("(data) => window.updateRenderData(data)", render_data)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
# Wait for Vue to update DOM
|
|
155
|
-
# Give Vue a moment to patch the DOM (insert img tags)
|
|
156
|
-
await asyncio.sleep(0.1)
|
|
157
|
-
|
|
158
|
-
# Wait for all images to load
|
|
159
|
-
try:
|
|
160
|
-
await page.wait_for_function(
|
|
161
|
-
"() => Array.from(document.images).every(img => img.complete)",
|
|
162
|
-
timeout=image_timeout
|
|
163
|
-
)
|
|
164
|
-
except Exception:
|
|
165
|
-
logger.warning(f"ContentRenderer: Timeout waiting for images to load ({image_timeout}ms), taking screenshot anyway.")
|
|
166
|
-
|
|
167
|
-
# Resize height if needed?
|
|
168
|
-
# The page height might change. We capture full page or specific element.
|
|
169
|
-
# If capturing element:
|
|
170
|
-
element = await page.query_selector("#main-container")
|
|
171
|
-
if element:
|
|
172
|
-
# Clean previous screenshots? No, overwrite.
|
|
173
|
-
await element.screenshot(path=str(resolved_output_path), type="jpeg", quality=98)
|
|
174
|
-
else:
|
|
175
|
-
await page.screenshot(path=str(resolved_output_path), full_page=True, type="jpeg", quality=98)
|
|
176
|
-
|
|
177
|
-
self._render_count += 1
|
|
178
|
-
|
|
179
|
-
duration = time.time() - start_time
|
|
180
|
-
logger.success(f"ContentRenderer: Rendered in {duration:.3f}s (No.{self._render_count})")
|
|
181
|
-
return True
|
|
182
|
-
|
|
183
|
-
except Exception as exc:
|
|
184
|
-
logger.error(f"ContentRenderer: render failed ({exc})")
|
|
185
|
-
# If render failed, maybe browser is dead. Close it to force restart next time.
|
|
186
|
-
await self.close()
|
|
187
|
-
return False
|
|
188
|
-
finally:
|
|
189
|
-
gc.collect()
|
|
190
|
-
|
|
191
|
-
async def render_models_list(
|
|
192
|
-
self,
|
|
193
|
-
models: List[Dict[str, Any]],
|
|
194
|
-
output_path: str,
|
|
195
|
-
default_base_url: str = "https://openrouter.ai/api/v1",
|
|
196
|
-
**kwargs
|
|
197
|
-
) -> bool:
|
|
198
|
-
"""Render models list."""
|
|
199
|
-
lines = ["# 模型列表"]
|
|
200
|
-
for idx, model in enumerate(models or [], start=1):
|
|
201
|
-
name = model.get("name", "unknown")
|
|
202
|
-
base_url = model.get("base_url") or default_base_url
|
|
203
|
-
provider = model.get("provider", "")
|
|
204
|
-
lines.append(f"{idx}. **{name}** \n - base_url: {base_url} \n - provider: {provider}")
|
|
205
|
-
|
|
206
|
-
markdown_content = "\n\n".join(lines) if len(lines) > 1 else "# 模型列表\n暂无模型"
|
|
207
|
-
|
|
208
|
-
return await self.render(
|
|
209
|
-
markdown_content=markdown_content,
|
|
210
|
-
output_path=output_path,
|
|
211
|
-
stats={},
|
|
212
|
-
references=[],
|
|
213
|
-
stages_used=[],
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
def _reorder_images_in_stages(self, markdown: str, stages: List[Dict[str, Any]]) -> None:
|
|
217
|
-
"""Reorder image references in stages based on appearance in markdown."""
|
|
218
|
-
import re
|
|
219
|
-
|
|
220
|
-
# 1. Extract clean URLs from markdown
|
|
221
|
-
# Matches: 
|
|
222
|
-
img_urls = []
|
|
223
|
-
for match in re.finditer(r'!\[.*?\]\((.*?)\)', markdown):
|
|
224
|
-
# Url might be followed by title: "url" "title"
|
|
225
|
-
url_part = match.group(1).split()[0].strip()
|
|
226
|
-
if url_part and url_part not in img_urls:
|
|
227
|
-
img_urls.append(url_part)
|
|
228
|
-
|
|
229
|
-
if not img_urls:
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
# 2. Reorder each stage's image_references
|
|
233
|
-
for stage in stages:
|
|
234
|
-
refs = stage.get("image_references")
|
|
235
|
-
if not refs:
|
|
236
|
-
continue
|
|
237
|
-
|
|
238
|
-
# Map url -> ref object
|
|
239
|
-
ref_map = {r["url"]: r for r in refs}
|
|
240
|
-
|
|
241
|
-
new_refs = []
|
|
242
|
-
seen_urls = set()
|
|
243
|
-
|
|
244
|
-
# First, add images found in markdown in order
|
|
245
|
-
for url in img_urls:
|
|
246
|
-
if url in ref_map:
|
|
247
|
-
new_refs.append(ref_map[url])
|
|
248
|
-
seen_urls.add(url)
|
|
249
|
-
|
|
250
|
-
# Then add remaining images not found in markdown
|
|
251
|
-
for r in refs:
|
|
252
|
-
if r["url"] not in seen_urls:
|
|
253
|
-
new_refs.append(r)
|
|
254
|
-
|
|
255
|
-
stage["image_references"] = new_refs
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import asyncio
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
# Directly add the core directory to sys.path to avoid importing the parent package (and triggering entari init)
|
|
6
|
-
core_dir = Path(__file__).parent.parent / "core"
|
|
7
|
-
sys.path.append(str(core_dir))
|
|
8
|
-
|
|
9
|
-
# Import directly as a module
|
|
10
|
-
from render_vue import ContentRenderer
|
|
11
|
-
|
|
12
|
-
def verify_ui():
|
|
13
|
-
renderer = ContentRenderer()
|
|
14
|
-
|
|
15
|
-
# Test with real GTNH conversation data
|
|
16
|
-
markdown_content = """# 终极硬核整合包格雷科技新视野
|
|
17
|
-
|
|
18
|
-
<summary>
|
|
19
|
-
《格雷科技:新视野》(GregTech: New Horizons,简称 GTNH)是一款基于 Minecraft 1.7.10 版本的深度硬核科技向整合包。它以 GregTech 5 Unofficial 为核心,通过超过 8 年的持续开发,将 300 多个模组深度集成,构建了极其严苛且逻辑严密的科技树,是公认的生存挑战巅峰之作。
|
|
20
|
-
</summary>
|
|
21
|
-
|
|
22
|
-
## 核心机制与游戏体验
|
|
23
|
-
GTNH 的核心在于"格雷化"改造,几乎所有模组的合成表都经过重新设计,以匹配其严苛的阶级制度 [4][8]。玩家需要从原始的石器时代开始,历经蒸汽时代、电力时代,最终向星际航行迈进。其游戏过程极其漫长,旨在让玩家在每一毫秒的进度中感受工业发展的成就感 [3][7]。
|
|
24
|
-
|
|
25
|
-

|
|
26
|
-
|
|
27
|
-
## 科技阶层与任务系统
|
|
28
|
-
整合包拥有 15 个清晰的科技等级(Tiers),最终目标是建造"星门"(Stargate)[2]。为了引导玩家不迷失在复杂的工业流程中,GTNH 内置了超过 3900 条任务的巨型任务书,涵盖了从基础生存到高阶多方块结构的详细指导 [4][7]。
|
|
29
|
-
|
|
30
|
-
- 15 个科技等级
|
|
31
|
-
- 任务数量:3900+
|
|
32
|
-
- 最终目标:建造"星门"
|
|
33
|
-
|
|
34
|
-
> 机动战士高达系列是日本动画史上最具影响力的动画作品之一,深受全球观众的喜爱。
|
|
35
|
-
|
|
36
|
-
| 特性 | 详细描述 |
|
|
37
|
-
| :--- | :--- |
|
|
38
|
-
| **基础版本** | Minecraft 1.7.10 (高度优化) |
|
|
39
|
-
| **任务数量** | 3900+ 任务引导 [7] |
|
|
40
|
-
| **科技阶层** | 15 个技术等级 [2] |
|
|
41
|
-
| **核心模组** | GregTech 5 Unofficial, Thaumcraft 等 [8] |
|
|
42
|
-
|
|
43
|
-
## 安装与运行建议
|
|
44
|
-
由于其高度集成的特性,官方强烈建议使用 **Prism Launcher** 进行安装和管理 [5]。在运行环境方面,虽然基于旧版 MC,但通过社区努力,目前推荐使用 **Java 17-25** 版本以获得最佳的内存管理和性能优化,确保大型自动化工厂运行流畅 [5]。
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
curl -s https://raw.githubusercontent.com/GTNewHorizons/GT-New-Horizons-Modpack/master/README.md
|
|
48
|
-
java -version
|
|
49
|
-
java -Xmx1024M -Xms1024M -jar prism-launcher.jar
|
|
50
|
-
```
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
stages = [
|
|
54
|
-
{
|
|
55
|
-
"name": "instruct",
|
|
56
|
-
"status": "completed",
|
|
57
|
-
"cost": 0.0002,
|
|
58
|
-
"time": 1.83,
|
|
59
|
-
"model": "qwen/qwen3-235b-a22b-2507",
|
|
60
|
-
"description": "Planning search strategy"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"name": "search",
|
|
64
|
-
"status": "completed",
|
|
65
|
-
"cost": 0.0,
|
|
66
|
-
"time": 0.5,
|
|
67
|
-
"references": [
|
|
68
|
-
{"title": "GTNH 2025 Server Information", "url": "https://stonelegion.com/mc-gtnh-2026/gtnh-2025-server-information-including-client-download/"},
|
|
69
|
-
{"title": "GT New Horizons Wiki", "url": "https://gtnh.miraheze.org/wiki/Main_Page"},
|
|
70
|
-
{"title": "GT New Horizons - GitHub", "url": "https://github.com/GTNewHorizons/GT-New-Horizons-Modpack"},
|
|
71
|
-
{"title": "GT New Horizons - CurseForge", "url": "https://www.curseforge.com/minecraft/modpacks/gt-new-horizons"},
|
|
72
|
-
{"title": "Installing and Migrating - GTNH", "url": "https://gtnh.miraheze.org/wiki/Installing_and_Migrating"},
|
|
73
|
-
{"title": "Modlist - GT New Horizons", "url": "https://wiki.gtnewhorizons.com/wiki/Modlist"},
|
|
74
|
-
{"title": "GregTech: New Horizons - Home", "url": "https://www.gtnewhorizons.com/"},
|
|
75
|
-
{"title": "GT New Horizons - FTB Wiki", "url": "https://ftb.fandom.com/wiki/GT_New_Horizons"}
|
|
76
|
-
],
|
|
77
|
-
"image_references": [
|
|
78
|
-
{
|
|
79
|
-
"title": "GTNH Live Lets Play",
|
|
80
|
-
"url": "https://i.ytimg.com/vi/5T-oSWAgaMM/maxresdefault.jpg",
|
|
81
|
-
"thumbnail": "https://tse4.mm.bing.net/th/id/OIP.b_56VnY4nyrzeqp1JetmFQHaEK?pid=Api"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"title": "GTNH Modpack Cover",
|
|
85
|
-
"url": "https://i.mcmod.cn/modpack/cover/20240113/1705139595_29797_dSkE.jpg",
|
|
86
|
-
"thumbnail": "https://tse1.mm.bing.net/th/id/OIP.KNKaZX1d_4Ueq6vpl1qJNAHaEo?pid=Api"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"title": "GTNH Steam Age",
|
|
90
|
-
"url": "https://i.ytimg.com/vi/8IPwXxqB71w/maxresdefault.jpg",
|
|
91
|
-
"thumbnail": "https://tse4.mm.bing.net/th/id/OIP.P-KrnI4GBH21yPgwpNPSzAHaEK?pid=Api"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"title": "GTNH MCMod Cover",
|
|
95
|
-
"url": "https://i.mcmod.cn/post/cover/20230201/1675241030_2_VqDc.jpg",
|
|
96
|
-
"thumbnail": "https://tse2.mm.bing.net/th/id/OIP.GvYz7YWrg-fnpAHjOiW3OAHaEo?pid=Api"
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
"title": "GTNH Tectech Tutorial",
|
|
100
|
-
"url": "http://i0.hdslb.com/bfs/archive/1ed1e53341fd44018138f2823b2fe6c499fb9c9c.jpg",
|
|
101
|
-
"thumbnail": "https://tse4.mm.bing.net/th/id/OIP.0Wg7xFHTjhxIV9hKuUo4xwHaEo?pid=Api"
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
"name": "agent",
|
|
107
|
-
"status": "completed",
|
|
108
|
-
"cost": 0.0018,
|
|
109
|
-
"time": 13.0,
|
|
110
|
-
"model": "google/gemini-3-flash-preview",
|
|
111
|
-
"description": "Synthesizing information..."
|
|
112
|
-
}
|
|
113
|
-
]
|
|
114
|
-
|
|
115
|
-
output_path = Path(__file__).parent / "ui_test_output.jpg"
|
|
116
|
-
print(f"🎨 Rendering to {output_path}...")
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
# Since render is async, we need to run it in an event loop
|
|
120
|
-
async def run_render():
|
|
121
|
-
await renderer.render(
|
|
122
|
-
markdown_content=markdown_content,
|
|
123
|
-
output_path=str(output_path),
|
|
124
|
-
stats={"total_time": 8.5},
|
|
125
|
-
stages_used=stages,
|
|
126
|
-
references=[{"title": f"Ref {i}", "url": "http://example.com"} for i in range(10)],
|
|
127
|
-
page_references=[{"title": f"Page {i}", "url": "http://example.com"} for i in range(2)],
|
|
128
|
-
image_references=[]
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
asyncio.run(run_render())
|
|
132
|
-
print(f"✨ Success! Image saved to: {output_path}")
|
|
133
|
-
except Exception as e:
|
|
134
|
-
print(f"❌ Error: {e}")
|
|
135
|
-
import traceback
|
|
136
|
-
traceback.print_exc()
|
|
137
|
-
|
|
138
|
-
if __name__ == "__main__":
|
|
139
|
-
verify_ui()
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
from loguru import logger
|
|
3
|
-
from crawl4ai import AsyncWebCrawler
|
|
4
|
-
from crawl4ai.async_configs import CrawlerRunConfig
|
|
5
|
-
from crawl4ai.cache_context import CacheMode
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class BrowserTool:
|
|
9
|
-
"""Crawl4AI-based page fetcher."""
|
|
10
|
-
|
|
11
|
-
def __init__(self, config: Any):
|
|
12
|
-
self.config = config
|
|
13
|
-
|
|
14
|
-
async def navigate(self, url: str) -> str:
|
|
15
|
-
"""Fetch URL content via Crawl4AI and return markdown."""
|
|
16
|
-
if not url:
|
|
17
|
-
return "Error: missing url"
|
|
18
|
-
try:
|
|
19
|
-
async with AsyncWebCrawler() as crawler:
|
|
20
|
-
result = await crawler.arun(
|
|
21
|
-
url=url,
|
|
22
|
-
config=CrawlerRunConfig(
|
|
23
|
-
wait_until="networkidle",
|
|
24
|
-
wait_for_images=True,
|
|
25
|
-
cache_mode=CacheMode.BYPASS,
|
|
26
|
-
word_count_threshold=1,
|
|
27
|
-
screenshot=False,
|
|
28
|
-
),
|
|
29
|
-
)
|
|
30
|
-
if not result.success:
|
|
31
|
-
return f"Error navigating to {url}: {result.error_message or result.status_code}"
|
|
32
|
-
|
|
33
|
-
content = result.markdown or result.extracted_content or result.cleaned_html or result.html or ""
|
|
34
|
-
return content[:8000]
|
|
35
|
-
except Exception as e:
|
|
36
|
-
logger.error(f"HTTP navigation failed: {e}")
|
|
37
|
-
return f"Error navigating to {url}: {e}"
|
|
38
|
-
|
|
39
|
-
async def close(self):
|
|
40
|
-
return None
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
from loguru import logger
|
|
3
|
-
from crawl4ai.async_configs import CrawlerRunConfig
|
|
4
|
-
from crawl4ai.cache_context import CacheMode
|
|
5
|
-
from .search import get_shared_crawler
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class PlaywrightTool:
|
|
9
|
-
"""
|
|
10
|
-
Backwards-compatible wrapper now powered by Crawl4AI.
|
|
11
|
-
"""
|
|
12
|
-
def __init__(self, config: Any):
|
|
13
|
-
self.config = config
|
|
14
|
-
|
|
15
|
-
async def navigate(self, url: str) -> str:
|
|
16
|
-
if not url:
|
|
17
|
-
return "Error: Missing url"
|
|
18
|
-
|
|
19
|
-
try:
|
|
20
|
-
crawler = await get_shared_crawler()
|
|
21
|
-
result = await crawler.arun(
|
|
22
|
-
url=url,
|
|
23
|
-
config=CrawlerRunConfig(
|
|
24
|
-
wait_until="networkidle",
|
|
25
|
-
wait_for_images=True,
|
|
26
|
-
cache_mode=CacheMode.BYPASS,
|
|
27
|
-
word_count_threshold=1,
|
|
28
|
-
screenshot=False,
|
|
29
|
-
),
|
|
30
|
-
)
|
|
31
|
-
if not result.success:
|
|
32
|
-
return f"Error: crawl failed ({result.error_message or result.status_code})"
|
|
33
|
-
return (result.markdown or result.extracted_content or result.cleaned_html or result.html or "")[:8000]
|
|
34
|
-
except Exception as e:
|
|
35
|
-
logger.warning(f"Crawl navigation failed: {e}")
|
|
36
|
-
return f"Error: Crawl navigation failed: {e}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|