entari-plugin-hyw 3.5.0rc1__py3-none-any.whl → 3.5.0rc3__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.0rc3.dist-info}/METADATA +1 -1
- {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc3.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.0rc3.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-3.5.0rc1.dist-info → entari_plugin_hyw-3.5.0rc3.dist-info}/top_level.txt +0 -0
entari_plugin_hyw/__init__.py
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
|
+
from importlib.metadata import version as get_version
|
|
2
3
|
from typing import List, Dict, Any, Optional, Union
|
|
3
4
|
import time
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
# 从 pyproject.toml 读取版本号,避免重复维护
|
|
8
|
+
try:
|
|
9
|
+
__version__ = get_version("entari_plugin_hyw")
|
|
10
|
+
except Exception:
|
|
11
|
+
__version__ = "0.0.0"
|
|
4
12
|
|
|
5
13
|
from arclet.alconna import Alconna, Args, AllParam, CommandMeta, Option, Arparma, MultiVar, store_true
|
|
6
14
|
from arclet.entari import metadata, listen, Session, plugin_config, BasicConfModel, plugin, command
|
|
15
|
+
from arclet.letoderea import on
|
|
7
16
|
from arclet.entari import MessageChain, Text, Image, MessageCreatedEvent, Quote, At
|
|
8
17
|
from satori.element import Custom
|
|
9
18
|
from loguru import logger
|
|
10
19
|
import arclet.letoderea as leto
|
|
11
20
|
from arclet.entari.event.command import CommandReceive
|
|
12
21
|
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
15
|
-
from .
|
|
16
|
-
from .
|
|
17
|
-
from arclet.entari.event.lifespan import Cleanup
|
|
22
|
+
from .pipeline import ProcessingPipeline
|
|
23
|
+
from .history import HistoryManager
|
|
24
|
+
from .render_vue import ContentRenderer
|
|
25
|
+
from .misc import process_onebot_json, process_images, resolve_model_name
|
|
26
|
+
from arclet.entari.event.lifespan import Cleanup
|
|
18
27
|
|
|
19
28
|
import os
|
|
20
29
|
import secrets
|
|
@@ -22,6 +31,32 @@ import base64
|
|
|
22
31
|
|
|
23
32
|
import re
|
|
24
33
|
|
|
34
|
+
|
|
35
|
+
def parse_color(color: str) -> str:
|
|
36
|
+
"""
|
|
37
|
+
Parse color from hex or RGB tuple to hex format.
|
|
38
|
+
Supports: #ff0000, ff0000, (255, 0, 0), 255,0,0
|
|
39
|
+
"""
|
|
40
|
+
if not color:
|
|
41
|
+
return "#ef4444"
|
|
42
|
+
|
|
43
|
+
color = str(color).strip()
|
|
44
|
+
|
|
45
|
+
# Hex format: #fff or #ffffff or ffffff
|
|
46
|
+
if color.startswith('#') and len(color) in [4, 7]:
|
|
47
|
+
return color
|
|
48
|
+
if re.match(r'^[0-9a-fA-F]{6}$', color):
|
|
49
|
+
return f'#{color}'
|
|
50
|
+
|
|
51
|
+
# RGB tuple: (r, g, b) or r,g,b
|
|
52
|
+
rgb_match = re.match(r'^\(?(\d+)[,\s]+(\d+)[,\s]+(\d+)\)?$', color)
|
|
53
|
+
if rgb_match:
|
|
54
|
+
r, g, b = (max(0, min(255, int(x))) for x in rgb_match.groups())
|
|
55
|
+
return f'#{r:02x}{g:02x}{b:02x}'
|
|
56
|
+
|
|
57
|
+
logger.warning(f"Invalid color '{color}', using default #ef4444")
|
|
58
|
+
return "#ef4444"
|
|
59
|
+
|
|
25
60
|
class _RecentEventDeduper:
|
|
26
61
|
def __init__(self, ttl_seconds: float = 30.0, max_size: int = 2048):
|
|
27
62
|
self.ttl_seconds = ttl_seconds
|
|
@@ -89,89 +124,45 @@ class HywConfig(BasicConfModel):
|
|
|
89
124
|
# Provider Names
|
|
90
125
|
search_name: str = "DuckDuckGo"
|
|
91
126
|
search_provider: str = "crawl4ai" # crawl4ai | httpx | ddgs
|
|
127
|
+
fetch_provider: str = "crawl4ai" # crawl4ai | jinaai
|
|
128
|
+
jina_api_key: Optional[str] = None # Optional API key for Jina AI
|
|
92
129
|
model_provider: Optional[str] = None
|
|
93
130
|
vision_model_provider: Optional[str] = None
|
|
94
131
|
instruct_model_provider: Optional[str] = None
|
|
132
|
+
# UI Theme
|
|
133
|
+
theme_color: str = "#ef4444" # Tailwind red-500, supports hex/RGB/color names
|
|
134
|
+
|
|
135
|
+
def __post_init__(self):
|
|
136
|
+
"""Parse and normalize theme color after initialization."""
|
|
137
|
+
self.theme_color = parse_color(self.theme_color)
|
|
95
138
|
|
|
96
139
|
|
|
97
140
|
|
|
98
141
|
conf = plugin_config(HywConfig)
|
|
99
142
|
history_manager = HistoryManager()
|
|
100
143
|
renderer = ContentRenderer()
|
|
101
|
-
hyw = HYW(config=conf)
|
|
102
144
|
|
|
103
145
|
|
|
104
|
-
@listen(Startup)
|
|
105
|
-
async def _hyw_startup():
|
|
106
|
-
try:
|
|
107
|
-
# Pre-launch browser
|
|
108
|
-
logger.info("HYW: Pre-launching renderer browser...")
|
|
109
|
-
await renderer.start()
|
|
110
|
-
except Exception as e:
|
|
111
|
-
logger.warning(f"HYW: Renderer warm-up failed: {e}")
|
|
112
|
-
|
|
113
|
-
@listen(Cleanup, once=True)
|
|
114
|
-
async def _hyw_cleanup():
|
|
115
|
-
try:
|
|
116
|
-
await hyw.close()
|
|
117
|
-
await renderer.close()
|
|
118
|
-
except Exception as e:
|
|
119
|
-
logger.warning(f"HYW cleanup error: {e}")
|
|
120
|
-
|
|
121
146
|
class GlobalCache:
|
|
122
147
|
models_image_path: Optional[str] = None
|
|
123
148
|
|
|
124
149
|
global_cache = GlobalCache()
|
|
125
150
|
|
|
126
|
-
from satori.exception import ActionFailed
|
|
127
|
-
from satori.adapters.onebot11.reverse import _Connection
|
|
128
|
-
|
|
129
|
-
# Monkeypatch to suppress ActionFailed for get_msg
|
|
130
|
-
original_call_api = _Connection.call_api
|
|
131
|
-
|
|
132
|
-
async def patched_call_api(self, action: str, params: dict = None):
|
|
133
|
-
try:
|
|
134
|
-
return await original_call_api(self, action, params)
|
|
135
|
-
except ActionFailed as e:
|
|
136
|
-
if action == "get_msg":
|
|
137
|
-
logger.warning(f"Suppressed ActionFailed for get_msg: {e}")
|
|
138
|
-
return None
|
|
139
|
-
raise e
|
|
140
|
-
|
|
141
|
-
_Connection.call_api = patched_call_api
|
|
142
|
-
|
|
143
|
-
EMOJI_TO_CODE = {
|
|
144
|
-
"✨": "10024",
|
|
145
|
-
"✅": "10004",
|
|
146
|
-
"❌": "10060"
|
|
147
|
-
}
|
|
148
|
-
|
|
149
151
|
async def react(session: Session, emoji: str):
|
|
150
152
|
if not conf.reaction: return
|
|
151
153
|
try:
|
|
152
|
-
|
|
153
|
-
code = EMOJI_TO_CODE.get(emoji, "10024")
|
|
154
|
-
# OneBot specific reaction
|
|
155
|
-
await session.account.protocol.call_api(
|
|
156
|
-
"internal/set_group_reaction",
|
|
157
|
-
{
|
|
158
|
-
"group_id": str(session.guild.id),
|
|
159
|
-
"message_id": str(session.event.message.id),
|
|
160
|
-
"code": code,
|
|
161
|
-
"is_add": True
|
|
162
|
-
}
|
|
163
|
-
)
|
|
164
|
-
else:
|
|
165
|
-
# Standard Satori reaction
|
|
166
|
-
await session.reaction_create(emoji=emoji)
|
|
167
|
-
except ActionFailed:
|
|
168
|
-
pass
|
|
154
|
+
await session.reaction_create(emoji=emoji)
|
|
169
155
|
except Exception as e:
|
|
170
156
|
logger.warning(f"Reaction failed: {e}")
|
|
171
157
|
|
|
172
|
-
async def process_request(
|
|
173
|
-
|
|
174
|
-
|
|
158
|
+
async def process_request(
|
|
159
|
+
session: Session[MessageCreatedEvent],
|
|
160
|
+
all_param: Optional[MessageChain] = None,
|
|
161
|
+
selected_model: Optional[str] = None,
|
|
162
|
+
selected_vision_model: Optional[str] = None,
|
|
163
|
+
conversation_key_override: Optional[str] = None,
|
|
164
|
+
local_mode: bool = False,
|
|
165
|
+
) -> None:
|
|
175
166
|
logger.info(f"Processing request: {all_param}")
|
|
176
167
|
mc = MessageChain(all_param)
|
|
177
168
|
logger.info(f"reply: {session.reply}")
|
|
@@ -251,12 +242,19 @@ async def process_request(session: Session[MessageCreatedEvent], all_param: Opti
|
|
|
251
242
|
|
|
252
243
|
images, err = await process_images(mc, vision_model)
|
|
253
244
|
|
|
254
|
-
# Call
|
|
255
|
-
# Sanitize user_input: use extracted text only
|
|
245
|
+
# Call Pipeline directly
|
|
256
246
|
safe_input = msg_text
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
247
|
+
pipeline = ProcessingPipeline(conf)
|
|
248
|
+
try:
|
|
249
|
+
resp = await pipeline.execute(
|
|
250
|
+
safe_input,
|
|
251
|
+
hist_payload,
|
|
252
|
+
model_name=model,
|
|
253
|
+
images=images,
|
|
254
|
+
selected_vision_model=vision_model,
|
|
255
|
+
)
|
|
256
|
+
finally:
|
|
257
|
+
await pipeline.close()
|
|
260
258
|
|
|
261
259
|
# Step 1 Results
|
|
262
260
|
step1_vision_model = resp.get("vision_model_used")
|
|
@@ -300,6 +298,7 @@ async def process_request(session: Session[MessageCreatedEvent], all_param: Opti
|
|
|
300
298
|
image_references=structured.get("image_references", []),
|
|
301
299
|
stages_used=final_resp.get("stages_used", []),
|
|
302
300
|
image_timeout=conf.render_image_timeout_ms,
|
|
301
|
+
theme_color=conf.theme_color,
|
|
303
302
|
)
|
|
304
303
|
|
|
305
304
|
# Send & Save
|
|
@@ -368,18 +367,16 @@ async def process_request(session: Session[MessageCreatedEvent], all_param: Opti
|
|
|
368
367
|
logger.error(f"Failed to save error conversation: {save_err}")
|
|
369
368
|
|
|
370
369
|
|
|
371
|
-
|
|
372
|
-
# Main Command (Question)
|
|
373
370
|
alc = Alconna(
|
|
374
371
|
conf.question_command,
|
|
375
372
|
Args["all_param;?", AllParam],
|
|
376
373
|
)
|
|
377
374
|
|
|
378
|
-
@command.on(alc)
|
|
375
|
+
@command.on(alc)
|
|
379
376
|
async def handle_question_command(session: Session[MessageCreatedEvent], result: Arparma):
|
|
380
377
|
"""Handle main Question command"""
|
|
381
378
|
try:
|
|
382
|
-
|
|
379
|
+
logger.info(f"Question Command Triggered. Message: {result}")
|
|
383
380
|
mid = str(session.event.message.id) if getattr(session.event, "message", None) else str(session.event.id)
|
|
384
381
|
dedupe_key = f"{getattr(session.account, 'id', 'account')}:{mid}"
|
|
385
382
|
if _event_deduper.seen_recently(dedupe_key):
|
|
@@ -393,14 +390,12 @@ async def handle_question_command(session: Session[MessageCreatedEvent], result:
|
|
|
393
390
|
args = result.all_matched_args
|
|
394
391
|
logger.info(f"Matched Args: {args}")
|
|
395
392
|
|
|
396
|
-
# Only all_param is supported now
|
|
397
|
-
# Context ID for history lookup is automatically handled in process_request
|
|
398
|
-
|
|
399
393
|
await process_request(session, args.get("all_param"), selected_model=None, selected_vision_model=None, conversation_key_override=None, local_mode=False)
|
|
400
394
|
|
|
401
|
-
metadata("hyw", author=[{"name": "kumoSleeping", "email": "zjr2992@outlook.com"}], version=
|
|
395
|
+
metadata("hyw", author=[{"name": "kumoSleeping", "email": "zjr2992@outlook.com"}], version=__version__, config=HywConfig)
|
|
396
|
+
|
|
402
397
|
|
|
403
|
-
@
|
|
398
|
+
@listen(CommandReceive)
|
|
404
399
|
async def remove_at(content: MessageChain):
|
|
405
|
-
|
|
406
|
-
return content
|
|
400
|
+
logger.info(f"remove_at: {content}")
|
|
401
|
+
return content.lstrip(At)
|