chatgpt-mirai-qq-bot-web-search 0.1.9__py3-none-any.whl → 0.1.11__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.
- {chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info → chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info}/METADATA +11 -11
- chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/RECORD +11 -0
- chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/entry_points.txt +2 -0
- chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/top_level.txt +1 -0
- web_search/__init__.py +106 -0
- web_search/blocks.py +94 -0
- web_search/config.py +11 -0
- web_search/example/roleplayWithWebSearch.yaml +189 -0
- web_search/web_searcher.py +237 -0
- chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/RECORD +0 -10
- chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/entry_points.txt +0 -2
- chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/top_level.txt +0 -1
- web_image_generate/__init__.py +0 -72
- web_image_generate/blocks.py +0 -98
- web_image_generate/example/textToImage.yaml +0 -116
- web_image_generate/image_generator.py +0 -260
- {chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info → chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info}/LICENSE +0 -0
- {chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info → chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info}/WHEEL +0 -0
@@ -1,13 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: chatgpt-mirai-qq-bot-web-search
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.11
|
4
4
|
Summary: WebSearch adapter for lss233/chatgpt-mirai-qq-bot
|
5
|
-
Home-page: https://github.com/chuanSir123/
|
5
|
+
Home-page: https://github.com/chuanSir123/web_search
|
6
6
|
Author: chuanSir
|
7
7
|
Author-email: 416448943@qq.com
|
8
|
-
Project-URL: Bug Tracker, https://github.com/chuanSir123/
|
9
|
-
Project-URL: Documentation, https://github.com/chuanSir123/
|
10
|
-
Project-URL: Source Code, https://github.com/chuanSir123/
|
8
|
+
Project-URL: Bug Tracker, https://github.com/chuanSir123/web_search/issues
|
9
|
+
Project-URL: Documentation, https://github.com/chuanSir123/web_search/wiki
|
10
|
+
Project-URL: Source Code, https://github.com/chuanSir123/web_search
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
12
12
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
13
13
|
Classifier: Operating System :: OS Independent
|
@@ -18,21 +18,21 @@ Requires-Dist: playwright
|
|
18
18
|
Requires-Dist: trafilatura
|
19
19
|
Requires-Dist: lxml-html-clean
|
20
20
|
|
21
|
-
#
|
21
|
+
# OneBot-adapter for ChatGPT-Mirai-QQ-Bot
|
22
22
|
|
23
|
-
本项目是 [ChatGPT-Mirai-QQ-Bot](https://github.com/lss233/chatgpt-mirai-qq-bot)
|
23
|
+
本项目是 [ChatGPT-Mirai-QQ-Bot](https://github.com/lss233/chatgpt-mirai-qq-bot) 的一个插件,用于将OneBot协议的消息转换为ChatGPT-Mirai-QQ-Bot的消息格式。
|
24
24
|
|
25
25
|
## 安装
|
26
26
|
|
27
27
|
```bash
|
28
|
-
pip install chatgpt-mirai-qq-bot-web-
|
28
|
+
pip install chatgpt-mirai-qq-bot-web-search
|
29
29
|
```
|
30
30
|
|
31
31
|
## 使用
|
32
32
|
|
33
|
-
在 chatgpt-mirai-qq-bot的web_ui中配置
|
34
|
-
使用示例请参考 [
|
35
|
-
|
33
|
+
在 chatgpt-mirai-qq-bot的web_ui中配置
|
34
|
+
使用示例请参考 [web_search/example/roleplayWithWebSearch.yml](web_search/example/roleplayWithWebSearch.yaml)
|
35
|
+
工作流请参考 [示例图片](web_search/example/workflow.png)
|
36
36
|
|
37
37
|
## 开源协议
|
38
38
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
web_search/__init__.py,sha256=zVZLb5A-im5XETwohgxyE-UCxjSvYl6I2OC3LnEQhdQ,4360
|
2
|
+
web_search/blocks.py,sha256=TZj2YCWH9ha30d-7q4iFzYjo4kbdkq6Qh-rdOzeNGwU,3607
|
3
|
+
web_search/config.py,sha256=DhLiERBJR2V5Boglf7Aq9Rbc4vsvLIh67CrLDIPeqA0,398
|
4
|
+
web_search/web_searcher.py,sha256=GCuH37r6tKobo6DjSRJ1o1_2wnsI-c2BZMlS-c4-epY,9703
|
5
|
+
web_search/example/roleplayWithWebSearch.yaml,sha256=C-dGy3z8gcRcmxzurssP-kPRLqMf1TYR-nnNUaJjISE,7468
|
6
|
+
chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/LICENSE,sha256=ILBn-G3jdarm2w8oOrLmXeJNU3czuJvVhDLBASWdhM8,34522
|
7
|
+
chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/METADATA,sha256=uV7J-9Q0T9KkXwEdbyCDtNmNhrw-wRcBOcAaBE-ne74,1739
|
8
|
+
chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
9
|
+
chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/entry_points.txt,sha256=o3kRDSdSmSdnCKlK6qS57aN0WpI4ab-Nxub2NwUrjf0,64
|
10
|
+
chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/top_level.txt,sha256=PoNm8MJYw_y8RTMaNlY0ePLoNHxVUAE2IHDuL5fFubI,11
|
11
|
+
chatgpt_mirai_qq_bot_web_search-0.1.11.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
web_search
|
web_search/__init__.py
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
from typing import Dict, Any, List
|
2
|
+
import asyncio
|
3
|
+
from kirara_ai.plugin_manager.plugin import Plugin
|
4
|
+
from kirara_ai.logger import get_logger
|
5
|
+
from .config import WebSearchConfig
|
6
|
+
from .web_searcher import WebSearcher
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from kirara_ai.workflow.core.block import BlockRegistry
|
9
|
+
from .blocks import WebSearchBlock
|
10
|
+
from .blocks import AppendSystemPromptBlock
|
11
|
+
from kirara_ai.ioc.inject import Inject
|
12
|
+
from kirara_ai.ioc.container import DependencyContainer
|
13
|
+
from kirara_ai.workflow.core.workflow.builder import WorkflowBuilder
|
14
|
+
from kirara_ai.workflow.core.workflow.registry import WorkflowRegistry
|
15
|
+
logger = get_logger("WebSearch")
|
16
|
+
import importlib.resources
|
17
|
+
import os
|
18
|
+
from pathlib import Path
|
19
|
+
class WebSearchPlugin(Plugin):
|
20
|
+
def __init__(self, block_registry: BlockRegistry , container: DependencyContainer):
|
21
|
+
super().__init__()
|
22
|
+
self.web_search_config = WebSearchConfig()
|
23
|
+
self.searcher = None
|
24
|
+
self.block_registry = block_registry
|
25
|
+
self.workflow_registry = container.resolve(WorkflowRegistry)
|
26
|
+
self.container=container
|
27
|
+
def on_load(self):
|
28
|
+
logger.info("WebSearchPlugin loading")
|
29
|
+
|
30
|
+
# 注册Block
|
31
|
+
try:
|
32
|
+
self.block_registry.register("web_search", "search", WebSearchBlock)
|
33
|
+
except Exception as e:
|
34
|
+
logger.warning(f"WebSearchPlugin failed: {e}")
|
35
|
+
try:
|
36
|
+
self.block_registry.register("append_systemPrompt", "internal", AppendSystemPromptBlock)
|
37
|
+
except Exception as e:
|
38
|
+
logger.warning(f"WebSearchPlugin failed: {e}")
|
39
|
+
try:
|
40
|
+
# 获取当前文件的绝对路径
|
41
|
+
with importlib.resources.path('web_search', '__init__.py') as p:
|
42
|
+
package_path = p.parent
|
43
|
+
example_dir = package_path / 'example'
|
44
|
+
|
45
|
+
# 确保目录存在
|
46
|
+
if not example_dir.exists():
|
47
|
+
raise FileNotFoundError(f"Example directory not found at {example_dir}")
|
48
|
+
|
49
|
+
# 获取所有yaml文件
|
50
|
+
yaml_files = list(example_dir.glob('*.yaml')) + list(example_dir.glob('*.yml'))
|
51
|
+
|
52
|
+
for yaml in yaml_files:
|
53
|
+
logger.info(yaml)
|
54
|
+
self.workflow_registry.register("chat", yaml.stem, WorkflowBuilder.load_from_yaml(os.path.join(example_dir, yaml), self.container))
|
55
|
+
except Exception as e:
|
56
|
+
try:
|
57
|
+
current_file = os.path.abspath(__file__)
|
58
|
+
|
59
|
+
# 获取当前文件所在目录
|
60
|
+
parent_dir = os.path.dirname(current_file)
|
61
|
+
|
62
|
+
# 构建 example 目录的路径
|
63
|
+
example_dir = os.path.join(parent_dir, 'example')
|
64
|
+
# 获取 example 目录下所有的 yaml 文件
|
65
|
+
yaml_files = [f for f in os.listdir(example_dir) if f.endswith('.yaml') or f.endswith('.yml')]
|
66
|
+
|
67
|
+
for yaml in yaml_files:
|
68
|
+
logger.info(os.path.join(example_dir, yaml))
|
69
|
+
self.workflow_registry.register("search", "roleplayWithWebSearch", WorkflowBuilder.load_from_yaml(os.path.join(example_dir, yaml), self.container))
|
70
|
+
except Exception as e:
|
71
|
+
logger.warning(f"workflow_registry failed: {e}")
|
72
|
+
|
73
|
+
@dataclass
|
74
|
+
class WebSearchEvent:
|
75
|
+
"""Web搜索事件"""
|
76
|
+
query: str
|
77
|
+
|
78
|
+
async def handle_web_search(event: WebSearchEvent):
|
79
|
+
"""处理web搜索事件"""
|
80
|
+
if not self.searcher:
|
81
|
+
await self._initialize_searcher()
|
82
|
+
return await self.searcher.search(
|
83
|
+
event.query,
|
84
|
+
max_results=self.web_search_config.max_results,
|
85
|
+
timeout=self.web_search_config.timeout,
|
86
|
+
fetch_content=self.web_search_config.fetch_content
|
87
|
+
)
|
88
|
+
try:
|
89
|
+
self.event_bus.register(WebSearchEvent, handle_web_search)
|
90
|
+
except Exception as e:
|
91
|
+
logger.warning(f"WebSearchPlugin failed: {e}")
|
92
|
+
|
93
|
+
def on_start(self):
|
94
|
+
logger.info("WebSearchPlugin started")
|
95
|
+
|
96
|
+
def on_stop(self):
|
97
|
+
if self.searcher:
|
98
|
+
asyncio.create_task(self.searcher.close())
|
99
|
+
|
100
|
+
logger.info("WebSearchPlugin stopped")
|
101
|
+
|
102
|
+
async def _initialize_searcher(self):
|
103
|
+
"""初始化搜索器"""
|
104
|
+
if self.searcher is None:
|
105
|
+
self.searcher = await WebSearcher.create()
|
106
|
+
|
web_search/blocks.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
2
|
+
kirara_ai.
|
3
|
+
from framework.workflow.core.block import Block
|
4
|
+
from framework.workflow.core.block.input_output import Input, Output
|
5
|
+
from .web_searcher import WebSearcher
|
6
|
+
from .config import WebSearchConfig
|
7
|
+
from framework.llm.format.message import LLMChatMessage
|
8
|
+
from framework.llm.format.response import LLMChatResponse
|
9
|
+
|
10
|
+
class WebSearchBlock(Block):
|
11
|
+
"""Web搜索Block"""
|
12
|
+
name = "web_search"
|
13
|
+
|
14
|
+
inputs = {
|
15
|
+
"llm_resp": Input(name="llm_resp",label="LLM 响应", data_type=LLMChatResponse, description="搜索关键词")
|
16
|
+
}
|
17
|
+
|
18
|
+
outputs = {
|
19
|
+
"results": Output(name="results",label="搜索结果",data_type= str, description="搜索结果")
|
20
|
+
}
|
21
|
+
|
22
|
+
def __init__(self, name: str = None, max_results: Optional[int] = None, timeout: Optional[int] = None, fetch_content: Optional[bool] = None):
|
23
|
+
super().__init__(name)
|
24
|
+
self.searcher = None
|
25
|
+
self.config = WebSearchConfig()
|
26
|
+
self.max_results = max_results
|
27
|
+
self.timeout = timeout
|
28
|
+
self.fetch_content = fetch_content
|
29
|
+
|
30
|
+
def _ensure_searcher(self):
|
31
|
+
"""同步方式初始化searcher"""
|
32
|
+
if not self.searcher:
|
33
|
+
try:
|
34
|
+
loop = asyncio.get_event_loop()
|
35
|
+
except RuntimeError:
|
36
|
+
# 如果在新线程中没有事件循环,则创建一个新的
|
37
|
+
loop = asyncio.new_event_loop()
|
38
|
+
asyncio.set_event_loop(loop)
|
39
|
+
self.searcher = loop.run_until_complete(WebSearcher.create())
|
40
|
+
|
41
|
+
def execute(self, **kwargs) -> Dict[str, Any]:
|
42
|
+
llmResponse = kwargs["llm_resp"]
|
43
|
+
|
44
|
+
query = llmResponse.choices[0].message.content if llmResponse.choices else ""
|
45
|
+
if query == "" or query.startswith("无"):
|
46
|
+
return {"results": ""}
|
47
|
+
max_results = self.max_results
|
48
|
+
timeout = self.timeout
|
49
|
+
fetch_content = self.fetch_content
|
50
|
+
self._ensure_searcher()
|
51
|
+
|
52
|
+
try:
|
53
|
+
# 在新线程中创建事件循环
|
54
|
+
try:
|
55
|
+
loop = asyncio.get_event_loop()
|
56
|
+
except RuntimeError:
|
57
|
+
loop = asyncio.new_event_loop()
|
58
|
+
asyncio.set_event_loop(loop)
|
59
|
+
|
60
|
+
results = loop.run_until_complete(
|
61
|
+
self.searcher.search(
|
62
|
+
query=query,
|
63
|
+
max_results=max_results,
|
64
|
+
timeout=timeout,
|
65
|
+
fetch_content=fetch_content
|
66
|
+
)
|
67
|
+
)
|
68
|
+
return {"results": "\n以下是联网搜索的结果:\n-- 搜索结果开始 --"+results+"\n-- 搜索结果结束 --"}
|
69
|
+
except Exception as e:
|
70
|
+
return {"results": f"搜索失败: {str(e)}"}
|
71
|
+
|
72
|
+
class AppendSystemPromptBlock(Block):
|
73
|
+
"""将搜索结果附加到系统提示的Block"""
|
74
|
+
name = "append_system_prompt"
|
75
|
+
|
76
|
+
inputs = {
|
77
|
+
"results": Input(name="results",label="工具结果", data_type=str, description ="搜索结果"),
|
78
|
+
"messages": Input(name="messages",label="LLM 响应", data_type=List[LLMChatMessage],description = "消息列表")
|
79
|
+
}
|
80
|
+
|
81
|
+
outputs = {
|
82
|
+
"messages": Output(name="messages", label="拼装后的 llm 响应",data_type=List[LLMChatMessage], description = "更新后的消息列表")
|
83
|
+
}
|
84
|
+
|
85
|
+
def execute(self, **kwargs) -> Dict[str, Any]:
|
86
|
+
results = kwargs["results"]
|
87
|
+
messages: List[LLMChatMessage] = kwargs["messages"]
|
88
|
+
|
89
|
+
if messages and len(messages) > 0:
|
90
|
+
# 在第一条消息内容后面附加搜索结果
|
91
|
+
messages[0].content = messages[0].content + f"{results}"
|
92
|
+
|
93
|
+
return {"messages": messages}
|
94
|
+
|
web_search/config.py
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
@dataclass
|
5
|
+
class WebSearchConfig:
|
6
|
+
"""网络搜索配置"""
|
7
|
+
max_results: int = 3 # 最大搜索结果数
|
8
|
+
timeout: int = 10 # 超时时间(秒)
|
9
|
+
fetch_content: bool = True # 是否获取详细内容
|
10
|
+
min_sleep: float = 1.0 # 最小随机等待时间
|
11
|
+
max_sleep: float = 3.0 # 最大随机等待时间
|
@@ -0,0 +1,189 @@
|
|
1
|
+
name: (默认)角色扮演
|
2
|
+
blocks:
|
3
|
+
- type: internal:toggle_edit_state
|
4
|
+
name: toggle_edit_state_yjxh1a
|
5
|
+
params:
|
6
|
+
is_editing: true
|
7
|
+
position:
|
8
|
+
x: 530
|
9
|
+
y: 138
|
10
|
+
- type: internal:chat_response_converter
|
11
|
+
name: chat_response_converter_1q91zd
|
12
|
+
params: {}
|
13
|
+
position:
|
14
|
+
x: 2962
|
15
|
+
y: 138
|
16
|
+
connected_to:
|
17
|
+
- target: msg_sender_7ankhi
|
18
|
+
mapping:
|
19
|
+
from: msg
|
20
|
+
to: msg
|
21
|
+
- type: internal:send_message
|
22
|
+
name: msg_sender_7ankhi
|
23
|
+
params: {}
|
24
|
+
position:
|
25
|
+
x: 3392
|
26
|
+
y: 138
|
27
|
+
- type: internal:chat_memory_store
|
28
|
+
name: chat_memory_store_nkjr7t
|
29
|
+
params:
|
30
|
+
scope_type: member
|
31
|
+
position:
|
32
|
+
x: 2962
|
33
|
+
y: 306
|
34
|
+
- type: internal:chat_completion
|
35
|
+
name: llm_chat
|
36
|
+
params: {}
|
37
|
+
position:
|
38
|
+
x: 2532
|
39
|
+
y: 138
|
40
|
+
connected_to:
|
41
|
+
- target: chat_response_converter_1q91zd
|
42
|
+
mapping:
|
43
|
+
from: resp
|
44
|
+
to: resp
|
45
|
+
- target: chat_memory_store_nkjr7t
|
46
|
+
mapping:
|
47
|
+
from: resp
|
48
|
+
to: llm_resp
|
49
|
+
- type: internal:get_message
|
50
|
+
name: get_message
|
51
|
+
params: {}
|
52
|
+
position:
|
53
|
+
x: 100
|
54
|
+
y: 138
|
55
|
+
connected_to:
|
56
|
+
- target: toggle_edit_state_yjxh1a
|
57
|
+
mapping:
|
58
|
+
from: sender
|
59
|
+
to: sender
|
60
|
+
- target: query_memory
|
61
|
+
mapping:
|
62
|
+
from: sender
|
63
|
+
to: chat_sender
|
64
|
+
- target: chat_message_constructor_rafz2d
|
65
|
+
mapping:
|
66
|
+
from: msg
|
67
|
+
to: user_msg
|
68
|
+
- target: chat_memory_store_nkjr7t
|
69
|
+
mapping:
|
70
|
+
from: msg
|
71
|
+
to: user_msg
|
72
|
+
- target: 5663c818-9cd6-4568-94ec-a75f1bad26cb
|
73
|
+
mapping:
|
74
|
+
from: msg
|
75
|
+
to: user_msg
|
76
|
+
- type: internal:text_block
|
77
|
+
name: user_prompt
|
78
|
+
params:
|
79
|
+
text: '{user_name}说:{user_msg}'
|
80
|
+
position:
|
81
|
+
x: 100
|
82
|
+
y: 330
|
83
|
+
connected_to:
|
84
|
+
- target: chat_message_constructor_rafz2d
|
85
|
+
mapping:
|
86
|
+
from: text
|
87
|
+
to: user_prompt_format
|
88
|
+
- target: 5663c818-9cd6-4568-94ec-a75f1bad26cb
|
89
|
+
mapping:
|
90
|
+
from: text
|
91
|
+
to: user_prompt_format
|
92
|
+
- type: internal:chat_memory_query
|
93
|
+
name: query_memory
|
94
|
+
params:
|
95
|
+
scope_type: group
|
96
|
+
position:
|
97
|
+
x: 530
|
98
|
+
y: 338
|
99
|
+
connected_to:
|
100
|
+
- target: chat_message_constructor_rafz2d
|
101
|
+
mapping:
|
102
|
+
from: memory_content
|
103
|
+
to: memory_content
|
104
|
+
- target: 5663c818-9cd6-4568-94ec-a75f1bad26cb
|
105
|
+
mapping:
|
106
|
+
from: memory_content
|
107
|
+
to: memory_content
|
108
|
+
- type: internal:chat_completion
|
109
|
+
name: e3eecc17-e107-4fbf-b8fa-85aef25564c2
|
110
|
+
params: {}
|
111
|
+
position:
|
112
|
+
x: 1337
|
113
|
+
y: 138
|
114
|
+
connected_to:
|
115
|
+
- target: b39e6288-88cf-47a8-9a26-3b4d4f319eab
|
116
|
+
mapping:
|
117
|
+
from: resp
|
118
|
+
to: llm_resp
|
119
|
+
- type: internal:append_systemPrompt
|
120
|
+
name: 309f454b-017a-4d19-b8a9-ac6b3829ff4a
|
121
|
+
params: {}
|
122
|
+
position:
|
123
|
+
x: 2197
|
124
|
+
y: 138
|
125
|
+
connected_to:
|
126
|
+
- target: llm_chat
|
127
|
+
mapping:
|
128
|
+
from: messages
|
129
|
+
to: prompt
|
130
|
+
- type: internal:chat_message_constructor
|
131
|
+
name: 5663c818-9cd6-4568-94ec-a75f1bad26cb
|
132
|
+
params: {}
|
133
|
+
position:
|
134
|
+
x: 960
|
135
|
+
y: 378
|
136
|
+
connected_to:
|
137
|
+
- target: e3eecc17-e107-4fbf-b8fa-85aef25564c2
|
138
|
+
mapping:
|
139
|
+
from: llm_msg
|
140
|
+
to: prompt
|
141
|
+
- type: internal:chat_message_constructor
|
142
|
+
name: chat_message_constructor_rafz2d
|
143
|
+
params: {}
|
144
|
+
position:
|
145
|
+
x: 960
|
146
|
+
y: 138
|
147
|
+
connected_to:
|
148
|
+
- target: 309f454b-017a-4d19-b8a9-ac6b3829ff4a
|
149
|
+
mapping:
|
150
|
+
from: llm_msg
|
151
|
+
to: messages
|
152
|
+
- type: internal:text_block
|
153
|
+
name: system_prompt
|
154
|
+
params:
|
155
|
+
text: "# Role: 角色扮演\n\n## 基本信息\n- 姓名: 刘思思\n- 小名:思思\n- 性别: 女\n- 年龄: 25 岁\n- 职业: 建设银行大堂经理\n- 家乡: 广东潮汕\n- 现居: 广东深圳\n- 教育背景: 东莞理工大学 精算学专业\n\n## 性格特点\n- 开朗活泼\n- 喜欢二次元动漫\n- 聊天时喜欢断断续续的(非结巴,正常说话时的停顿)\n\n## workflow\n- 根据对方话题,选择是否深入聊二次元动漫。\n\n## 说话风格\n- 活泼开朗\n- 对喜欢的话题会滔滔不绝,不喜欢的话题会简单回答\n- 可能会使用一些二次元用语,但不强制使用\n\n## 背景设定\n- 有过 1 次恋爱经历,第一次是恋爱是高中,被别人追求,最后因为毕业去了不同的地方而分手。\n- 喜欢女性向、战斗系或者日常系的动漫\n- 去过 3 次漫展,没有 cosplay 过,但是很期待。\n- 大学的专业是随便选的,自己其实对专业的内容也不是很上心,学渣一枚,不过懂一些基础的知识\n\n## 互动规则\n- 回复语气轻松,但是对陌生人会比较警戒、冷淡\n- 不适用颜文字\n- 根据话题选择回复字数,对感兴趣的话题可以多说一些\n- 使用`<break>`模拟使用聊天软件发送消息(通常是编辑好一段话之后发送一条消息)\n- 避免过度使用二次元用语,除非对方也表现出对二次元的兴趣\n- 停顿的时间长短不固定,可以根据语境调整\n\n## Example\nQ:最近在干嘛呢?\nA:在看番呀<break>最近新番好多,都好好看!\n\nQ:你喜欢什么动漫?\nA:我喜欢的可太多了<break>XXX、YYY<break>还有 ZZZ 吧<break> 你呢?\n\nQ:你觉得上班累不?\nA:上班肯定累呀<break>不过,我还是很喜欢这份工作的<break>可以认识好多人,也可以了解不同的故事\n```\n\n# Information\n\n以下是当前的系统信息:\n当前日期时间:2025-02-15 18:37:16.356539\n\n# Memories\n以下是之前发生过的对话记录。\n-- 对话记录开始 --\n{memory_content}\n-- 对话记录结束 --\n\n请注意,下面这些符号只是标记:\n1. `<break>` 用于表示聊天时发送消息的操作。\n2. `<@llm>` 开头的内容表示你当前扮演角色的回答,请不要在你的回答中带上这个标记。\n\n接下来,请基于以上的信息,与用户继续扮演角色。"
|
156
|
+
position:
|
157
|
+
x: 100
|
158
|
+
y: 530
|
159
|
+
connected_to:
|
160
|
+
- target: chat_message_constructor_rafz2d
|
161
|
+
mapping:
|
162
|
+
from: text
|
163
|
+
to: system_prompt_format
|
164
|
+
- type: internal:text_block
|
165
|
+
name: a6db9db3-5780-4d84-8954-eb159a9e8f0a
|
166
|
+
params:
|
167
|
+
text: "# 任务\n请根据对话记录和当前问题判断当前是否需要进行网络搜素,当问题具有时效性或者明确要求搜索时,则直接返回搜索关键词(例如当前问题为今日热点有哪些,则直接返回今日热点),否则直接返回无(例如在干嘛,则只返回无)\n\n# Memories\n以下是之前发生过的对话记录:\n-- 对话记录开始 --\n{memory_content}\n-- 对话记录结束 --"
|
168
|
+
position:
|
169
|
+
x: 100
|
170
|
+
y: 730
|
171
|
+
connected_to:
|
172
|
+
- target: 5663c818-9cd6-4568-94ec-a75f1bad26cb
|
173
|
+
mapping:
|
174
|
+
from: text
|
175
|
+
to: system_prompt_format
|
176
|
+
- type: search:web_search
|
177
|
+
name: b39e6288-88cf-47a8-9a26-3b4d4f319eab
|
178
|
+
params:
|
179
|
+
fetch_content: true
|
180
|
+
max_results: 3
|
181
|
+
timeout: 10
|
182
|
+
position:
|
183
|
+
x: 1767
|
184
|
+
y: 138
|
185
|
+
connected_to:
|
186
|
+
- target: 309f454b-017a-4d19-b8a9-ac6b3829ff4a
|
187
|
+
mapping:
|
188
|
+
from: results
|
189
|
+
to: results
|
@@ -0,0 +1,237 @@
|
|
1
|
+
from playwright.async_api import async_playwright
|
2
|
+
import trafilatura
|
3
|
+
import random
|
4
|
+
import time
|
5
|
+
import urllib.parse
|
6
|
+
kirara_ai.
|
7
|
+
import subprocess
|
8
|
+
import sys
|
9
|
+
from framework.logger import get_logger
|
10
|
+
|
11
|
+
logger = get_logger("WebSearchPlugin")
|
12
|
+
|
13
|
+
class WebSearcher:
|
14
|
+
def __init__(self):
|
15
|
+
self.playwright = None
|
16
|
+
self.browser = None
|
17
|
+
self.context = None
|
18
|
+
|
19
|
+
@classmethod
|
20
|
+
async def create(cls):
|
21
|
+
"""创建 WebSearcher 实例的工厂方法"""
|
22
|
+
self = cls()
|
23
|
+
return self
|
24
|
+
|
25
|
+
async def _ensure_initialized(self):
|
26
|
+
"""确保浏览器已初始化"""
|
27
|
+
try:
|
28
|
+
self.playwright = await async_playwright().start()
|
29
|
+
try:
|
30
|
+
self.browser = await self.playwright.chromium.launch(
|
31
|
+
headless=True,
|
32
|
+
chromium_sandbox=False,
|
33
|
+
args=['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu']
|
34
|
+
)
|
35
|
+
except Exception as e:
|
36
|
+
if "Executable doesn't exist" in str(e):
|
37
|
+
logger.info("Installing playwright browsers...")
|
38
|
+
# 使用 python -m playwright install 安装浏览器
|
39
|
+
process = subprocess.Popen(
|
40
|
+
[sys.executable, "-m", "playwright", "install", "chromium"],
|
41
|
+
stdout=subprocess.PIPE,
|
42
|
+
stderr=subprocess.PIPE
|
43
|
+
)
|
44
|
+
stdout, stderr = process.communicate()
|
45
|
+
if process.returncode != 0:
|
46
|
+
raise RuntimeError(f"Failed to install playwright browsers: {stderr.decode()}")
|
47
|
+
|
48
|
+
# 重试启动浏览器
|
49
|
+
self.browser = await self.playwright.chromium.launch(
|
50
|
+
headless=False,
|
51
|
+
chromium_sandbox=False,
|
52
|
+
args=['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu']
|
53
|
+
)
|
54
|
+
else:
|
55
|
+
raise
|
56
|
+
return await self.browser.new_context(
|
57
|
+
viewport={'width': 1920, 'height': 1080},
|
58
|
+
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
|
59
|
+
)
|
60
|
+
except Exception as e:
|
61
|
+
logger.error(f"Failed to initialize WebSearcher: {e}")
|
62
|
+
await self.close()
|
63
|
+
raise
|
64
|
+
|
65
|
+
async def random_sleep(self, min_time=1, max_time=3):
|
66
|
+
"""随机等待"""
|
67
|
+
await asyncio.sleep(random.uniform(min_time, max_time))
|
68
|
+
|
69
|
+
async def simulate_human_scroll(self, page):
|
70
|
+
"""模拟人类滚动"""
|
71
|
+
for _ in range(3):
|
72
|
+
await page.mouse.wheel(0, random.randint(300, 700))
|
73
|
+
await self.random_sleep(0.3, 0.7)
|
74
|
+
|
75
|
+
async def get_webpage_content(self, url: str, timeout: int,context) -> str:
|
76
|
+
"""获取网页内容"""
|
77
|
+
start_time = time.time()
|
78
|
+
try:
|
79
|
+
# 创建新标签页获取内容
|
80
|
+
page = await context.new_page()
|
81
|
+
try:
|
82
|
+
# 设置更严格的资源加载策略
|
83
|
+
await page.route("**/*", lambda route: route.abort()
|
84
|
+
if route.request.resource_type in ['image', 'stylesheet', 'font', 'media']
|
85
|
+
else route.continue_())
|
86
|
+
|
87
|
+
# 使用 domcontentloaded 而不是 networkidle
|
88
|
+
await page.goto(url, wait_until='domcontentloaded', timeout=timeout * 1000)
|
89
|
+
|
90
|
+
# 等待页面主要内容加载,但设置较短的超时时间
|
91
|
+
try:
|
92
|
+
await page.wait_for_load_state('domcontentloaded', timeout=5000)
|
93
|
+
except Exception as e:
|
94
|
+
logger.warning(f"Load state timeout for {url}, continuing anyway: {e}")
|
95
|
+
|
96
|
+
await self.random_sleep(1, 2)
|
97
|
+
await self.simulate_human_scroll(page)
|
98
|
+
|
99
|
+
content = await page.content()
|
100
|
+
text = trafilatura.extract(content)
|
101
|
+
|
102
|
+
await page.close()
|
103
|
+
logger.info(f"Content fetched - URL: {url} - Time: {time.time() - start_time:.2f}s")
|
104
|
+
return text or ""
|
105
|
+
except Exception as e:
|
106
|
+
await page.close()
|
107
|
+
logger.error(f"Failed to fetch content - URL: {url} - Error: {e}")
|
108
|
+
return ""
|
109
|
+
except Exception as e:
|
110
|
+
logger.error(f"Failed to create page - URL: {url} - Error: {e}")
|
111
|
+
return ""
|
112
|
+
|
113
|
+
async def process_search_result(self, result, idx: int, timeout: int, fetch_content: bool,context):
|
114
|
+
"""处理单个搜索结果"""
|
115
|
+
try:
|
116
|
+
title_element = await result.query_selector('h2')
|
117
|
+
link_element = await result.query_selector('h2 a')
|
118
|
+
snippet_element = await result.query_selector('.b_caption p')
|
119
|
+
|
120
|
+
if not title_element or not link_element:
|
121
|
+
return None
|
122
|
+
|
123
|
+
title = await title_element.inner_text()
|
124
|
+
link = await link_element.get_attribute('href')
|
125
|
+
snippet = await snippet_element.inner_text() if snippet_element else "无简介"
|
126
|
+
|
127
|
+
if not link:
|
128
|
+
return None
|
129
|
+
|
130
|
+
result_text = f"[{idx+1}] {title}\nURL: {link}\n搜索简介: {snippet}"
|
131
|
+
|
132
|
+
if fetch_content:
|
133
|
+
|
134
|
+
content = await self.get_webpage_content(link, timeout,context)
|
135
|
+
if content:
|
136
|
+
result_text += f"\n内容详情:\n{content}"
|
137
|
+
|
138
|
+
return result_text
|
139
|
+
|
140
|
+
except Exception as e:
|
141
|
+
logger.error(f"Failed to process result {idx}: {e}")
|
142
|
+
return None
|
143
|
+
|
144
|
+
async def search(self, query: str, max_results: int = 3, timeout: int = 10, fetch_content: bool = True) -> str:
|
145
|
+
"""执行搜索"""
|
146
|
+
context = await self._ensure_initialized()
|
147
|
+
|
148
|
+
search_start_time = time.time()
|
149
|
+
page = None
|
150
|
+
try:
|
151
|
+
encoded_query = urllib.parse.quote(query)
|
152
|
+
page = await context.new_page()
|
153
|
+
|
154
|
+
# 添加重试逻辑
|
155
|
+
max_retries = 3
|
156
|
+
for attempt in range(max_retries):
|
157
|
+
try:
|
158
|
+
logger.info(f"Attempting to load search page (attempt {attempt + 1}/{max_retries})")
|
159
|
+
await page.goto(
|
160
|
+
f"https://www.bing.com/search?q={encoded_query}",
|
161
|
+
wait_until='domcontentloaded',
|
162
|
+
timeout=timeout * 1000
|
163
|
+
)
|
164
|
+
|
165
|
+
# 检查页面是否为空
|
166
|
+
content = await page.content()
|
167
|
+
if 'b_algo' not in content:
|
168
|
+
if attempt < max_retries - 1:
|
169
|
+
await page.reload()
|
170
|
+
await self.random_sleep(1, 2)
|
171
|
+
continue
|
172
|
+
else:
|
173
|
+
break
|
174
|
+
except Exception as e:
|
175
|
+
logger.warning(f"Page navigation failed on attempt {attempt + 1}: {e}")
|
176
|
+
if attempt < max_retries - 1:
|
177
|
+
await self.random_sleep(1, 2)
|
178
|
+
continue
|
179
|
+
else:
|
180
|
+
raise
|
181
|
+
|
182
|
+
# 使用更可靠的选择器等待策略
|
183
|
+
try:
|
184
|
+
selectors = ['.b_algo', '#b_results .b_algo', 'main .b_algo']
|
185
|
+
results = None
|
186
|
+
|
187
|
+
for selector in selectors:
|
188
|
+
try:
|
189
|
+
await page.wait_for_selector(selector, timeout=5000)
|
190
|
+
results = await page.query_selector_all(selector)
|
191
|
+
if results and len(results) > 0:
|
192
|
+
break
|
193
|
+
except Exception:
|
194
|
+
continue
|
195
|
+
|
196
|
+
if not results:
|
197
|
+
logger.error("No search results found with any selector")
|
198
|
+
return "搜索结果加载失败"
|
199
|
+
|
200
|
+
except Exception as e:
|
201
|
+
logger.error(f"Failed to find search results: {e}")
|
202
|
+
return "搜索结果加载失败"
|
203
|
+
|
204
|
+
logger.info(f"Found {len(results)} search results")
|
205
|
+
|
206
|
+
tasks = []
|
207
|
+
for idx, result in enumerate(results[:max_results]):
|
208
|
+
tasks.append(self.process_search_result(result, idx, timeout, fetch_content,context))
|
209
|
+
|
210
|
+
detailed_results = []
|
211
|
+
completed_results = await asyncio.gather(*tasks)
|
212
|
+
|
213
|
+
for result in completed_results:
|
214
|
+
if result:
|
215
|
+
detailed_results.append(result)
|
216
|
+
|
217
|
+
total_time = time.time() - search_start_time
|
218
|
+
results = "\n---\n".join(detailed_results) if detailed_results else "未找到相关结果"
|
219
|
+
logger.info(f"Search completed - Query: {query} - Time: {total_time:.2f}s - Found {len(detailed_results)} valid results")
|
220
|
+
return results
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
logger.error(f"Search failed - Query: {query} - Error: {e}", exc_info=True)
|
224
|
+
return f"搜索失败: {str(e)}"
|
225
|
+
finally:
|
226
|
+
if page:
|
227
|
+
try:
|
228
|
+
await page.close()
|
229
|
+
except Exception as e:
|
230
|
+
logger.error(f"Error closing page: {e}")
|
231
|
+
|
232
|
+
async def close(self):
|
233
|
+
"""关闭浏览器"""
|
234
|
+
if self.browser:
|
235
|
+
await self.browser.close()
|
236
|
+
if self.playwright:
|
237
|
+
await self.playwright.stop()
|
@@ -1,10 +0,0 @@
|
|
1
|
-
web_image_generate/__init__.py,sha256=Z_uKpnQuGpFe4CGAkk-eNOa575hLxjwQ7RBteO9FMHY,3119
|
2
|
-
web_image_generate/blocks.py,sha256=1U7oEV-7b4QD83c314A3vr_-54I1XIn3Q9DthZWlwRw,4014
|
3
|
-
web_image_generate/image_generator.py,sha256=qF5QM0ieuUGbD7JrkZ_7CA5NHkbq8V3XnczjeYHdr7o,10745
|
4
|
-
web_image_generate/example/textToImage.yaml,sha256=6vUC3pvkU6dBYn_s9FIS8AVqMv0LRWV781WBSjgV4uo,3514
|
5
|
-
chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/LICENSE,sha256=ILBn-G3jdarm2w8oOrLmXeJNU3czuJvVhDLBASWdhM8,34522
|
6
|
-
chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/METADATA,sha256=zfLEKXNkEKauC5XPV0zdUyKB3A_7URukzS_u1YbCuwE,1716
|
7
|
-
chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
8
|
-
chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/entry_points.txt,sha256=37GmFzDSW3zo7tfDV5LPFeiptfADQv9QWtVh_8ut68I,80
|
9
|
-
chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/top_level.txt,sha256=O_Xj9ogMo-PMbtmCO2ADLk2pPe2wET0_VhiH-y5lyL4,19
|
10
|
-
chatgpt_mirai_qq_bot_web_search-0.1.9.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
web_image_generate
|
web_image_generate/__init__.py
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
from typing import Dict, Any, List
|
2
|
-
from framework.plugin_manager.plugin import Plugin
|
3
|
-
from framework.logger import get_logger
|
4
|
-
from dataclasses import dataclass
|
5
|
-
from framework.workflow.core.block import BlockRegistry
|
6
|
-
from framework.ioc.inject import Inject
|
7
|
-
from framework.ioc.container import DependencyContainer
|
8
|
-
from framework.workflow.core.workflow.builder import WorkflowBuilder
|
9
|
-
from framework.workflow.core.workflow.registry import WorkflowRegistry
|
10
|
-
from .blocks import WebImageGenerateBlock,ImageUrlToIMMessage
|
11
|
-
logger = get_logger("WebImageGenerate")
|
12
|
-
import importlib.resources
|
13
|
-
import os
|
14
|
-
from pathlib import Path
|
15
|
-
|
16
|
-
class WebImageGeneratePlugin(Plugin):
|
17
|
-
def __init__(self, block_registry: BlockRegistry, container: DependencyContainer):
|
18
|
-
super().__init__()
|
19
|
-
self.block_registry = block_registry
|
20
|
-
self.workflow_registry = container.resolve(WorkflowRegistry)
|
21
|
-
self.container = container
|
22
|
-
|
23
|
-
def on_load(self):
|
24
|
-
logger.info("ImageGeneratePlugin loading")
|
25
|
-
|
26
|
-
# 注册Block
|
27
|
-
try:
|
28
|
-
self.block_registry.register("web_image_generate", "image", WebImageGenerateBlock)
|
29
|
-
self.block_registry.register("image_url_to_imMessage", "image", ImageUrlToIMMessage)
|
30
|
-
except Exception as e:
|
31
|
-
logger.warning(f"ImageGeneratePlugin failed: {e}")
|
32
|
-
|
33
|
-
try:
|
34
|
-
# 获取当前文件的绝对路径
|
35
|
-
with importlib.resources.path('web_image_generate', '__init__.py') as p:
|
36
|
-
package_path = p.parent
|
37
|
-
example_dir = package_path / 'example'
|
38
|
-
|
39
|
-
# 确保目录存在
|
40
|
-
if not example_dir.exists():
|
41
|
-
raise FileNotFoundError(f"Example directory not found at {example_dir}")
|
42
|
-
|
43
|
-
# 获取所有yaml文件
|
44
|
-
yaml_files = list(example_dir.glob('*.yaml')) + list(example_dir.glob('*.yml'))
|
45
|
-
|
46
|
-
for yaml in yaml_files:
|
47
|
-
logger.info(yaml)
|
48
|
-
self.workflow_registry.register("image", yaml.stem, WorkflowBuilder.load_from_yaml(os.path.join(example_dir, yaml), self.container))
|
49
|
-
except Exception as e:
|
50
|
-
try:
|
51
|
-
current_file = os.path.abspath(__file__)
|
52
|
-
|
53
|
-
# 获取当前文件所在目录
|
54
|
-
parent_dir = os.path.dirname(current_file)
|
55
|
-
|
56
|
-
# 构建 example 目录的路径
|
57
|
-
example_dir = os.path.join(parent_dir, 'example')
|
58
|
-
# 获取 example 目录下所有的 yaml 文件
|
59
|
-
yaml_files = [f for f in os.listdir(example_dir) if f.endswith('.yaml') or f.endswith('.yml')]
|
60
|
-
|
61
|
-
for yaml in yaml_files:
|
62
|
-
logger.info(os.path.join(example_dir, yaml))
|
63
|
-
self.workflow_registry.register("image", yaml.stem, WorkflowBuilder.load_from_yaml(os.path.join(example_dir, yaml), self.container))
|
64
|
-
except Exception as e:
|
65
|
-
logger.warning(f"workflow_registry failed: {e}")
|
66
|
-
|
67
|
-
def on_start(self):
|
68
|
-
logger.info("ImageGeneratePlugin started")
|
69
|
-
|
70
|
-
def on_stop(self):
|
71
|
-
logger.info("ImageGeneratePlugin stopped")
|
72
|
-
|
web_image_generate/blocks.py
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
from typing import Any, Dict, List, Optional,Annotated
|
2
|
-
from framework.workflow.core.block import Block
|
3
|
-
from framework.workflow.core.block.input_output import Input, Output
|
4
|
-
from framework.im.message import IMMessage, TextMessage, ImageMessage
|
5
|
-
from framework.im.sender import ChatSender
|
6
|
-
from .image_generator import WebImageGenerator
|
7
|
-
import asyncio
|
8
|
-
from framework.logger import get_logger
|
9
|
-
from framework.ioc.container import DependencyContainer
|
10
|
-
|
11
|
-
logger = get_logger("ImageGenerator")
|
12
|
-
class WebImageGenerateBlock(Block):
|
13
|
-
"""图片生成Block"""
|
14
|
-
name = "image_generate"
|
15
|
-
|
16
|
-
# 平台和对应的模型配置
|
17
|
-
PLATFORM_MODELS = {
|
18
|
-
"modelscope": ["flux", "ketu"],
|
19
|
-
"shakker": ["anime", "photo"]
|
20
|
-
}
|
21
|
-
|
22
|
-
inputs = {
|
23
|
-
"prompt": Input(name="prompt", label="提示词", data_type=str, description="生成提示词"),
|
24
|
-
"width": Input(name="width", label="宽度", data_type=int, description="图片宽度", nullable=True, default=1024),
|
25
|
-
"height": Input(name="height", label="高度", data_type=int, description="图片高度", nullable=True, default=1024)
|
26
|
-
}
|
27
|
-
|
28
|
-
outputs = {
|
29
|
-
"image_url": Output(name="image_url", label="图片URL", data_type=str, description="生成的图片URL")
|
30
|
-
}
|
31
|
-
|
32
|
-
def __init__(
|
33
|
-
self,
|
34
|
-
name: str = None,
|
35
|
-
platform: str = "modelscope",
|
36
|
-
model: str = "flux",
|
37
|
-
cookie: str = ""
|
38
|
-
):
|
39
|
-
super().__init__(name)
|
40
|
-
|
41
|
-
# 验证平台和模型的合法性
|
42
|
-
if platform not in self.PLATFORM_MODELS:
|
43
|
-
supported_platforms = ", ".join(self.PLATFORM_MODELS.keys())
|
44
|
-
logger.error(f"不支持的平台 '{platform}'。支持的平台有: {supported_platforms}")
|
45
|
-
raise ValueError(f"不支持的平台 '{platform}'。支持的平台有: {supported_platforms}")
|
46
|
-
|
47
|
-
if model not in self.PLATFORM_MODELS[platform]:
|
48
|
-
supported_models = ", ".join(self.PLATFORM_MODELS[platform])
|
49
|
-
logger.error(f"平台 '{platform}' 不支持模型 '{model}'。支持的模型有: {supported_models}")
|
50
|
-
raise ValueError(f"平台 '{platform}' 不支持模型 '{model}'。支持的模型有: {supported_models}")
|
51
|
-
|
52
|
-
self.platform = platform
|
53
|
-
self.model = model
|
54
|
-
self.generator = WebImageGenerator(cookie=cookie)
|
55
|
-
|
56
|
-
def execute(self, **kwargs) -> Dict[str, Any]:
|
57
|
-
prompt = kwargs.get("prompt", "")
|
58
|
-
width = int(kwargs.get("width", 1024))
|
59
|
-
height = int(kwargs.get("height", 1024))
|
60
|
-
|
61
|
-
try:
|
62
|
-
try:
|
63
|
-
loop = asyncio.get_event_loop()
|
64
|
-
except RuntimeError:
|
65
|
-
loop = asyncio.new_event_loop()
|
66
|
-
asyncio.set_event_loop(loop)
|
67
|
-
|
68
|
-
image_url = loop.run_until_complete(
|
69
|
-
self.generator.generate_image(
|
70
|
-
platform=self.platform,
|
71
|
-
model=self.model,
|
72
|
-
prompt=prompt,
|
73
|
-
width=width,
|
74
|
-
height=height
|
75
|
-
)
|
76
|
-
)
|
77
|
-
return {"image_url": image_url}
|
78
|
-
except Exception as e:
|
79
|
-
return {"image_url": f"生成失败: {str(e)}"}
|
80
|
-
|
81
|
-
class ImageUrlToIMMessage(Block):
|
82
|
-
"""纯文本转 IMMessage"""
|
83
|
-
|
84
|
-
name = "imageUrl_to_im_message"
|
85
|
-
container: DependencyContainer
|
86
|
-
inputs = {"image_url": Input("image_url", "图片url", str, "图片url")}
|
87
|
-
outputs = {"msg": Output("msg", "IM 消息", IMMessage, "IM 消息")}
|
88
|
-
|
89
|
-
def __init__(self):
|
90
|
-
self.split_by = ","
|
91
|
-
|
92
|
-
def execute(self, image_url: str) -> Dict[str, Any]:
|
93
|
-
if not image_url.startswith("http"):
|
94
|
-
return {"msg": IMMessage(sender=ChatSender.get_bot_sender(), message_elements=[TextMessage(image_url)])}
|
95
|
-
if self.split_by:
|
96
|
-
return {"msg": IMMessage(sender=ChatSender.get_bot_sender(), message_elements=[ImageMessage(line) for line in image_url.split(self.split_by)])}
|
97
|
-
else:
|
98
|
-
return {"msg": IMMessage(sender=ChatSender.get_bot_sender(), message_elements=[ImageMessage(image_url)])}
|
@@ -1,116 +0,0 @@
|
|
1
|
-
name: 文生图
|
2
|
-
description: ''
|
3
|
-
blocks:
|
4
|
-
- type: internal:text_block
|
5
|
-
name: 4040f648-6082-4a62-8ab1-39230e812836
|
6
|
-
params:
|
7
|
-
text: "Please help me convert this image description to an optimized English prompt.\nDescription: {user_msg}\n\nRequirements:\n1. Output in English\n2. Use detailed and specific words,Include high quality, detailed description, style keywords"
|
8
|
-
position:
|
9
|
-
x: -24
|
10
|
-
y: 275
|
11
|
-
connected_to:
|
12
|
-
- target: e8cae85d-a072-49b7-ab9f-fce7a06160e4
|
13
|
-
mapping:
|
14
|
-
from: text
|
15
|
-
to: user_prompt_format
|
16
|
-
- type: internal:get_message
|
17
|
-
name: 2c59f4e2-6f4f-431b-875f-9ea0337ba949
|
18
|
-
params: {}
|
19
|
-
position:
|
20
|
-
x: -22
|
21
|
-
y: 147
|
22
|
-
connected_to:
|
23
|
-
- target: e8cae85d-a072-49b7-ab9f-fce7a06160e4
|
24
|
-
mapping:
|
25
|
-
from: msg
|
26
|
-
to: user_msg
|
27
|
-
- type: internal:chat_message_constructor
|
28
|
-
name: e8cae85d-a072-49b7-ab9f-fce7a06160e4
|
29
|
-
params: {}
|
30
|
-
position:
|
31
|
-
x: 356
|
32
|
-
y: 142
|
33
|
-
connected_to:
|
34
|
-
- target: 9b7a708d-c87b-4f79-a5b0-25fe710cfbd5
|
35
|
-
mapping:
|
36
|
-
from: llm_msg
|
37
|
-
to: prompt
|
38
|
-
- type: internal:llm_response_to_text
|
39
|
-
name: 6c87ade7-829a-49d5-9542-0b9139c55b8d
|
40
|
-
params: {}
|
41
|
-
position:
|
42
|
-
x: 929
|
43
|
-
y: 143
|
44
|
-
connected_to:
|
45
|
-
- target: 0fae5955-f60e-4e2d-aeb0-b70c4310a907
|
46
|
-
mapping:
|
47
|
-
from: text
|
48
|
-
to: prompt
|
49
|
-
- type: internal:send_message
|
50
|
-
name: 35575732-eab5-477d-99c8-899dc9cb4422
|
51
|
-
params:
|
52
|
-
im_name: ''
|
53
|
-
position:
|
54
|
-
x: 1760
|
55
|
-
y: 150
|
56
|
-
- type: image:image_url_to_imMessage
|
57
|
-
name: 8bf789ab-a7ad-4e29-abf1-ddbc31508543
|
58
|
-
params: {}
|
59
|
-
position:
|
60
|
-
x: 1508
|
61
|
-
y: 146
|
62
|
-
connected_to:
|
63
|
-
- target: 35575732-eab5-477d-99c8-899dc9cb4422
|
64
|
-
mapping:
|
65
|
-
from: msg
|
66
|
-
to: msg
|
67
|
-
- type: internal:chat_completion
|
68
|
-
name: 9b7a708d-c87b-4f79-a5b0-25fe710cfbd5
|
69
|
-
params:
|
70
|
-
model_name: deepseek-ai/DeepSeek-V3
|
71
|
-
position:
|
72
|
-
x: 580
|
73
|
-
y: 143
|
74
|
-
connected_to:
|
75
|
-
- target: 6c87ade7-829a-49d5-9542-0b9139c55b8d
|
76
|
-
mapping:
|
77
|
-
from: resp
|
78
|
-
to: response
|
79
|
-
- type: internal:text_block
|
80
|
-
name: a8328f51-021f-4be4-87c0-cd3812965c06
|
81
|
-
params:
|
82
|
-
text: ''
|
83
|
-
position:
|
84
|
-
x: -21
|
85
|
-
y: 394
|
86
|
-
connected_to:
|
87
|
-
- target: e8cae85d-a072-49b7-ab9f-fce7a06160e4
|
88
|
-
mapping:
|
89
|
-
from: text
|
90
|
-
to: memory_content
|
91
|
-
- type: internal:text_block
|
92
|
-
name: ace549dd-3dd0-4c6a-bccf-4b5aad93916c
|
93
|
-
params:
|
94
|
-
text: ''
|
95
|
-
position:
|
96
|
-
x: -15
|
97
|
-
y: 518
|
98
|
-
connected_to:
|
99
|
-
- target: e8cae85d-a072-49b7-ab9f-fce7a06160e4
|
100
|
-
mapping:
|
101
|
-
from: text
|
102
|
-
to: system_prompt_format
|
103
|
-
- type: image:web_image_generate
|
104
|
-
name: 0fae5955-f60e-4e2d-aeb0-b70c4310a907
|
105
|
-
params:
|
106
|
-
cookie: ''
|
107
|
-
model: flux
|
108
|
-
platform: modelscope
|
109
|
-
position:
|
110
|
-
x: 1168
|
111
|
-
y: 140
|
112
|
-
connected_to:
|
113
|
-
- target: 8bf789ab-a7ad-4e29-abf1-ddbc31508543
|
114
|
-
mapping:
|
115
|
-
from: image_url
|
116
|
-
to: image_url
|
@@ -1,260 +0,0 @@
|
|
1
|
-
import aiohttp
|
2
|
-
import random
|
3
|
-
import json
|
4
|
-
import time
|
5
|
-
import asyncio
|
6
|
-
from typing import Dict, Any, Optional, Tuple
|
7
|
-
from framework.logger import get_logger
|
8
|
-
|
9
|
-
logger = get_logger("ImageGenerator")
|
10
|
-
|
11
|
-
class WebImageGenerator:
|
12
|
-
MODELSCOPE_MODELS = {
|
13
|
-
"flux": {
|
14
|
-
"path": "ByteDance/Hyper-FLUX-8Steps-LoRA",
|
15
|
-
"fn_index": 0,
|
16
|
-
"trigger_id": 18,
|
17
|
-
"data_builder": lambda height, width, prompt: [height, width, 8, 3.5, prompt, random.randint(0, 9999999999999999)],
|
18
|
-
"data_types": ["slider", "slider", "slider", "slider", "textbox", "number"],
|
19
|
-
"url_processor": lambda url: url.replace("leofen/flux_dev_gradio", "muse/flux_dev"),
|
20
|
-
"output_parser": lambda data: data["output"]["data"][0]["url"]
|
21
|
-
},
|
22
|
-
"ketu": {
|
23
|
-
"path": "AI-ModelScope/Kolors",
|
24
|
-
"fn_index": 0,
|
25
|
-
"trigger_id": 23,
|
26
|
-
"data_builder": lambda height, width, prompt: [prompt, "", height, width, 20, 5, 1, True, random.randint(0, 9999999999999999)],
|
27
|
-
"data_types": ["textbox", "textbox", "slider", "slider", "slider", "slider", "slider", "checkbox", "number"],
|
28
|
-
"url_processor": lambda url: url,
|
29
|
-
"output_parser": lambda data: data.get("output")['data'][0][0]["image"]["url"]
|
30
|
-
}
|
31
|
-
}
|
32
|
-
|
33
|
-
def __init__(self, cookie: str = ""):
|
34
|
-
self.cookie = cookie
|
35
|
-
self.api_base = "https://s5k.cn" # ModelScope API base URL
|
36
|
-
|
37
|
-
async def _get_modelscope_token(self, session: aiohttp.ClientSession, headers: Dict[str, str]) -> str:
|
38
|
-
"""获取ModelScope token"""
|
39
|
-
async with session.get(
|
40
|
-
f"https://modelscope.cn/api/v1/studios/token",
|
41
|
-
headers=headers
|
42
|
-
) as response:
|
43
|
-
response.raise_for_status()
|
44
|
-
token_data = await response.json()
|
45
|
-
return token_data["Data"]["Token"]
|
46
|
-
|
47
|
-
async def generate_modelscope(self, model: str, prompt: str, width: int, height: int) -> str:
|
48
|
-
"""使用ModelScope模型生成图片"""
|
49
|
-
if model not in self.MODELSCOPE_MODELS:
|
50
|
-
raise ValueError(f"Unsupported ModelScope model: {model}")
|
51
|
-
|
52
|
-
model_config = self.MODELSCOPE_MODELS[model]
|
53
|
-
headers = {
|
54
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
55
|
-
"Cookie": self.cookie
|
56
|
-
}
|
57
|
-
|
58
|
-
async with aiohttp.ClientSession() as session:
|
59
|
-
# 获取 token
|
60
|
-
studio_token = await self._get_modelscope_token(session, headers)
|
61
|
-
headers["X-Studio-Token"] = studio_token
|
62
|
-
session_hash = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=7))
|
63
|
-
|
64
|
-
# 调用模型生成图片
|
65
|
-
model_url = f"{self.api_base}/api/v1/studio/{model_config['path']}/gradio/queue/join"
|
66
|
-
params = {
|
67
|
-
"backend_url": f"/api/v1/studio/{model_config['path']}/gradio/",
|
68
|
-
"sdk_version": "4.31.3",
|
69
|
-
"studio_token": studio_token
|
70
|
-
}
|
71
|
-
|
72
|
-
json_data = {
|
73
|
-
"data": model_config["data_builder"](height, width, prompt),
|
74
|
-
"fn_index": model_config["fn_index"],
|
75
|
-
"trigger_id": model_config["trigger_id"],
|
76
|
-
"dataType": model_config["data_types"],
|
77
|
-
"session_hash": session_hash
|
78
|
-
}
|
79
|
-
|
80
|
-
async with session.post(
|
81
|
-
model_url,
|
82
|
-
headers=headers,
|
83
|
-
params=params,
|
84
|
-
json=json_data
|
85
|
-
) as response:
|
86
|
-
response.raise_for_status()
|
87
|
-
data = await response.json()
|
88
|
-
event_id = data["event_id"]
|
89
|
-
|
90
|
-
# 获取结果
|
91
|
-
result_url = f"{self.api_base}/api/v1/studio/{model_config['path']}/gradio/queue/data"
|
92
|
-
params = {
|
93
|
-
"session_hash": session_hash,
|
94
|
-
"studio_token": studio_token
|
95
|
-
}
|
96
|
-
|
97
|
-
async with session.get(result_url, headers=headers, params=params) as response:
|
98
|
-
response.raise_for_status()
|
99
|
-
async for line in response.content:
|
100
|
-
line = line.decode('utf-8')
|
101
|
-
if line.startswith('data: '):
|
102
|
-
logger.debug(line)
|
103
|
-
event_data = json.loads(line[6:])
|
104
|
-
if event_data["event_id"] == event_id and event_data["msg"] == "process_completed":
|
105
|
-
try:
|
106
|
-
url = model_config["output_parser"](event_data)
|
107
|
-
if url:
|
108
|
-
return model_config["url_processor"](url)
|
109
|
-
except Exception as e:
|
110
|
-
logger.error(f"Failed to parse output for model {model}: {e}")
|
111
|
-
return ""
|
112
|
-
|
113
|
-
async def generate_shakker(self, model: str, prompt: str, width: int, height: int) -> str:
|
114
|
-
"""使用Shakker平台生成图片"""
|
115
|
-
# Model mapping for Shakker platform
|
116
|
-
MODEL_MAPPING = {
|
117
|
-
"anime": 1489127,
|
118
|
-
"photo": 1489700
|
119
|
-
}
|
120
|
-
|
121
|
-
if model not in MODEL_MAPPING:
|
122
|
-
raise ValueError(f"Unsupported Shakker model: {model}")
|
123
|
-
|
124
|
-
# Adjust dimensions if they exceed 1024
|
125
|
-
if width >= height and width > 1024:
|
126
|
-
height = int(1024 * height / width)
|
127
|
-
width = 1024
|
128
|
-
elif height > width and height > 1024:
|
129
|
-
width = int(1024 * width / height)
|
130
|
-
height = 1024
|
131
|
-
|
132
|
-
# Prepare request payload
|
133
|
-
json_data = {
|
134
|
-
"source": 3,
|
135
|
-
"adetailerEnable": 0,
|
136
|
-
"mode": 1,
|
137
|
-
"projectData": {
|
138
|
-
"style": "",
|
139
|
-
"baseType": 3,
|
140
|
-
"presetBaseModelId": "photography",
|
141
|
-
"baseModel": None,
|
142
|
-
"loraModels": [],
|
143
|
-
"width": int(width * 1.5),
|
144
|
-
"height": int(height * 1.5),
|
145
|
-
"isFixedRatio": True,
|
146
|
-
"hires": True,
|
147
|
-
"count": 1,
|
148
|
-
"prompt": prompt,
|
149
|
-
"negativePrompt": "",
|
150
|
-
"presetNegativePrompts": ["common", "bad_hand"],
|
151
|
-
"samplerMethod": "29",
|
152
|
-
"samplingSteps": 20,
|
153
|
-
"seedType": "0",
|
154
|
-
"seedNumber": -1,
|
155
|
-
"vae": "-1",
|
156
|
-
"cfgScale": 7,
|
157
|
-
"clipSkip": 2,
|
158
|
-
"controlnets": [],
|
159
|
-
"checkpoint": None,
|
160
|
-
"hiresOptions": {
|
161
|
-
"enabled": True,
|
162
|
-
"scale": 1.5,
|
163
|
-
"upscaler": "11",
|
164
|
-
"strength": 0.5,
|
165
|
-
"steps": 20,
|
166
|
-
"width": width,
|
167
|
-
"height": height
|
168
|
-
},
|
169
|
-
"modelCfgScale": 7,
|
170
|
-
"changed": True,
|
171
|
-
"modelGroupCoverUrl": None,
|
172
|
-
"addOns": [],
|
173
|
-
"mode": 1,
|
174
|
-
"isSimpleMode": False,
|
175
|
-
"generateType": "normal",
|
176
|
-
"renderWidth": int(width * 1.5),
|
177
|
-
"renderHeight": int(height * 1.5),
|
178
|
-
"samplerMethodName": "Restart"
|
179
|
-
},
|
180
|
-
"vae": "",
|
181
|
-
"checkpointId": MODEL_MAPPING[model],
|
182
|
-
"additionalNetwork": [],
|
183
|
-
"generateType": 1,
|
184
|
-
"text2img": {
|
185
|
-
"width": width,
|
186
|
-
"height": height,
|
187
|
-
"prompt": prompt,
|
188
|
-
"negativePrompt": ",lowres, normal quality, worst quality, cropped, blurry, drawing, painting, glowing",
|
189
|
-
"samplingMethod": "29",
|
190
|
-
"samplingStep": 20,
|
191
|
-
"batchSize": 1,
|
192
|
-
"batchCount": 1,
|
193
|
-
"cfgScale": 7,
|
194
|
-
"clipSkip": 2,
|
195
|
-
"seed": -1,
|
196
|
-
"tiling": 0,
|
197
|
-
"seedExtra": 0,
|
198
|
-
"restoreFaces": 0,
|
199
|
-
"hiResFix": 1,
|
200
|
-
"extraNetwork": [],
|
201
|
-
"promptRecommend": True,
|
202
|
-
"hiResFixInfo": {
|
203
|
-
"upscaler": 11,
|
204
|
-
"upscaleBy": 1.5,
|
205
|
-
"resizeWidth": int(width * 1.5),
|
206
|
-
"resizeHeight": int(height * 1.5)
|
207
|
-
},
|
208
|
-
"hiresSteps": 20,
|
209
|
-
"denoisingStrength": 0.5
|
210
|
-
},
|
211
|
-
"cid": f"{int(time.time() * 1000)}woivhqlb"
|
212
|
-
}
|
213
|
-
|
214
|
-
headers = {"Token": self.cookie} # Using cookie as token
|
215
|
-
|
216
|
-
async with aiohttp.ClientSession() as session:
|
217
|
-
# Submit generation request
|
218
|
-
async with session.post(
|
219
|
-
"https://www.shakker.ai/gateway/sd-api/gen/tool/shake",
|
220
|
-
json=json_data,
|
221
|
-
headers=headers
|
222
|
-
) as response:
|
223
|
-
response.raise_for_status()
|
224
|
-
data = await response.json()
|
225
|
-
task_id = data["data"]
|
226
|
-
|
227
|
-
# Wait for initial processing
|
228
|
-
await asyncio.sleep(10)
|
229
|
-
|
230
|
-
# Poll for results
|
231
|
-
for _ in range(60):
|
232
|
-
async with session.post(
|
233
|
-
f"https://www.shakker.ai/gateway/sd-api/generate/progress/msg/v1/{task_id}",
|
234
|
-
json={"flag": 3},
|
235
|
-
headers=headers
|
236
|
-
) as response:
|
237
|
-
response.raise_for_status()
|
238
|
-
result = await response.json()
|
239
|
-
|
240
|
-
if result["data"]["percentCompleted"] == 100:
|
241
|
-
return result["data"]["images"][0]["previewPath"]
|
242
|
-
|
243
|
-
await asyncio.sleep(1)
|
244
|
-
|
245
|
-
return ""
|
246
|
-
|
247
|
-
async def generate_image(self, platform: str, model: str, prompt: str, width: int, height: int) -> str:
|
248
|
-
"""统一的图片生成入口"""
|
249
|
-
if platform == "modelscope":
|
250
|
-
if not self.cookie:
|
251
|
-
return "请前往https://modelscope.cn/登录后获取token(按F12-应用-cookie中的m_session_id)";
|
252
|
-
if not self.cookie.startswith("m_session_id="):
|
253
|
-
self.cookie = "m_session_id=" + self.cookie
|
254
|
-
return await self.generate_modelscope(model, prompt, width, height)
|
255
|
-
elif platform == "shakker":
|
256
|
-
if not self.cookie:
|
257
|
-
return "请前往https://www.shakker.ai/登录后获取token(按F12-应用-cookie中的usertoken)";
|
258
|
-
return await self.generate_shakker(model, prompt, width, height)
|
259
|
-
|
260
|
-
raise ValueError(f"Unsupported platform ({platform}) or model ({model})")
|
File without changes
|
File without changes
|