gomyck-tools 1.4.1__py3-none-any.whl → 1.4.2__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.
- ctools/__init__.py +21 -0
- ctools/ai/env_config.py +18 -1
- ctools/ai/llm_chat.py +8 -8
- ctools/ai/llm_client.py +26 -24
- ctools/ai/mcp/mcp_client.py +33 -17
- ctools/ai/tools/json_extract.py +3 -2
- ctools/ai/tools/quick_tools.py +68 -21
- ctools/ai/tools/tool_use_xml_parse.py +2 -1
- ctools/ai/tools/xml_extract.py +3 -0
- ctools/application.py +19 -17
- ctools/auto/browser_element.py +11 -3
- ctools/auto/plan_area.py +2 -2
- ctools/auto/pty_process.py +0 -1
- ctools/auto/screenshot.py +3 -4
- ctools/auto/win_canvas.py +10 -4
- ctools/auto/win_control.py +8 -4
- ctools/call.py +18 -22
- ctools/cdate.py +43 -2
- ctools/cid.py +3 -4
- ctools/cipher/aes_util.py +2 -2
- ctools/cipher/b64.py +2 -0
- ctools/cipher/czip.py +3 -1
- ctools/cipher/rsa.py +6 -1
- ctools/cipher/sign.py +1 -0
- ctools/cipher/sm_util.py +3 -0
- ctools/cjson.py +5 -0
- ctools/cron_lite.py +10 -4
- ctools/database/database.py +37 -17
- ctools/dict_wrapper.py +1 -0
- ctools/ex.py +1 -0
- ctools/geo/coord_trans.py +94 -94
- ctools/geo/douglas_rarefy.py +13 -9
- ctools/metrics.py +6 -0
- ctools/office/cword.py +7 -7
- ctools/office/word_fill.py +1 -4
- ctools/path_info.py +29 -0
- ctools/pools/process_pool.py +6 -1
- ctools/pools/thread_pool.py +6 -2
- ctools/similar.py +3 -0
- ctools/stream/ckafka.py +11 -5
- ctools/stream/credis.py +29 -21
- ctools/stream/mqtt_utils.py +2 -2
- ctools/sys_info.py +8 -0
- ctools/sys_log.py +3 -0
- ctools/util/cftp.py +4 -2
- ctools/util/http_util.py +1 -0
- ctools/util/image_process.py +1 -1
- ctools/util/snow_id.py +3 -2
- ctools/web/__init__.py +2 -2
- ctools/web/aio_web_server.py +19 -9
- ctools/web/api_result.py +3 -2
- ctools/web/bottle_web_base.py +15 -2
- ctools/web/bottle_webserver.py +14 -8
- ctools/web/bottle_websocket.py +4 -0
- ctools/web/ctoken.py +5 -1
- ctools/web/download_util.py +1 -1
- ctools/web/params_util.py +4 -0
- ctools/web/upload_util.py +1 -1
- {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/METADATA +2 -1
- gomyck_tools-1.4.2.dist-info/RECORD +82 -0
- gomyck_tools-1.4.1.dist-info/RECORD +0 -82
- {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/WHEEL +0 -0
- {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {gomyck_tools-1.4.1.dist-info → gomyck_tools-1.4.2.dist-info}/top_level.txt +0 -0
ctools/__init__.py
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
import asyncio
|
2
|
+
import importlib.util
|
3
|
+
|
4
|
+
banner = """
|
5
|
+
|
6
|
+
██████╗████████╗ ██████╗ ██████╗ ██╗ ███████╗
|
7
|
+
██╔════╝╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██╔════╝
|
8
|
+
██║ ██║ ██║ ██║██║ ██║██║ ███████╗
|
9
|
+
██║ ██║ ██║ ██║██║ ██║██║ ╚════██║
|
10
|
+
╚██████╗ ██║ ╚██████╔╝╚██████╔╝███████╗███████║
|
11
|
+
╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ --by gomyck 2025
|
12
|
+
"""
|
13
|
+
|
14
|
+
print(banner)
|
15
|
+
|
16
|
+
if importlib.util.find_spec("uvloop"):
|
17
|
+
import uvloop
|
18
|
+
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
19
|
+
print("uvloop version:", uvloop.__version__)
|
20
|
+
else:
|
21
|
+
print("uvloop not installed, using default asyncio loop")
|
ctools/ai/env_config.py
CHANGED
@@ -13,7 +13,7 @@ from dotenv.main import DotEnv
|
|
13
13
|
class Configuration:
|
14
14
|
"""Manages configuration and environment variables for the MCP client."""
|
15
15
|
|
16
|
-
def __init__(self, dotenv_path: str=".env") -> None:
|
16
|
+
def __init__(self, dotenv_path: str = ".env") -> None:
|
17
17
|
"""Initialize configuration with environment variables."""
|
18
18
|
if not os.path.exists(dotenv_path): raise FileNotFoundError(f"Could not find .env file at {dotenv_path}")
|
19
19
|
self.env = DotEnv(dotenv_path=dotenv_path)
|
@@ -44,3 +44,20 @@ class Configuration:
|
|
44
44
|
with open(self.get_env("MCP_CONFIG_PATH"), "r") as f:
|
45
45
|
return json.load(f)
|
46
46
|
|
47
|
+
def bool_env(key, default):
|
48
|
+
value = os.getenv(key)
|
49
|
+
if value:
|
50
|
+
val = value.strip().lower()
|
51
|
+
if val == "true": return True
|
52
|
+
if val == "false": return False
|
53
|
+
return default
|
54
|
+
|
55
|
+
def float_env(key, default):
|
56
|
+
value = os.getenv(key)
|
57
|
+
if value: return float(value)
|
58
|
+
return default
|
59
|
+
|
60
|
+
def int_env(key, default):
|
61
|
+
value = os.getenv(key)
|
62
|
+
if value: return int(value)
|
63
|
+
return default
|
ctools/ai/llm_chat.py
CHANGED
@@ -14,10 +14,11 @@ continue_prompt_default = """
|
|
14
14
|
3.如果你认为所有数据都处理完毕, 请输出标记:{end_flag}.
|
15
15
|
"""
|
16
16
|
|
17
|
+
|
17
18
|
class ChatSession:
|
18
19
|
|
19
20
|
def __init__(self, prompts: str, llm_client: LLMClient, max_tools_call: int = 10, mcp_clients: list[MCPClient] = None,
|
20
|
-
|
21
|
+
auto_complete: bool = False, end_flag: str = "EOF", continue_prompt: str = continue_prompt_default) -> None:
|
21
22
|
"""
|
22
23
|
初始化聊天
|
23
24
|
:param prompts: 提示词
|
@@ -49,7 +50,7 @@ class ChatSession:
|
|
49
50
|
else:
|
50
51
|
mcp_tools_prompt = await get_tools_prompt(self.mcp_clients, "")
|
51
52
|
self.full_messages.append(build_message(ROLE.SYSTEM, mcp_tools_prompt))
|
52
|
-
#log.info(mcp_tools_prompt)
|
53
|
+
# log.info(mcp_tools_prompt)
|
53
54
|
else:
|
54
55
|
if user_system_prompt:
|
55
56
|
self.full_messages.append(build_message(ROLE.SYSTEM, user_system_prompt))
|
@@ -86,9 +87,9 @@ class ChatSession:
|
|
86
87
|
await self.process_chunk_message(chunk, get_call_id, get_event_msg_func)
|
87
88
|
llm_response = "".join(res)
|
88
89
|
log.info("\n收到回答: %s", llm_response)
|
89
|
-
no_think_llm_response = remove_think_blocks(llm_response)
|
90
|
+
no_think_llm_response = remove_think_blocks(llm_response) # 处理掉 think, 然后再判断 EOF, 避免 think 里出现 EOF
|
90
91
|
if self.end_flag in no_think_llm_response:
|
91
|
-
self.full_messages.append(build_message(ROLE.ASSISTANT, llm_response.replace(self.end_flag, "")))
|
92
|
+
self.full_messages.append(build_message(ROLE.ASSISTANT, llm_response.replace(self.end_flag, ""))) # 去掉 EOF
|
92
93
|
current_process_index = 999
|
93
94
|
final_resp = True
|
94
95
|
await self.process_full_message(final_resp, get_call_id, get_full_msg_func)
|
@@ -103,9 +104,9 @@ class ChatSession:
|
|
103
104
|
self.full_messages.append(build_message(ROLE.USER, "工具调用出现错误, 请重试."))
|
104
105
|
elif current_process_index == max_tools_call - 1:
|
105
106
|
await self.add_tool_call_res_2_message(last_user_input, tool_call_result)
|
106
|
-
self.full_messages.append(build_message(ROLE.USER, "调用次数已达上限, 请直接回答."))
|
107
|
+
self.full_messages.append(build_message(ROLE.USER, "调用次数已达上限, 请直接回答.")) # 不能调换顺序
|
107
108
|
else:
|
108
|
-
self.full_messages.append(build_message(ROLE.ASSISTANT, llm_response))
|
109
|
+
self.full_messages.append(build_message(ROLE.ASSISTANT, llm_response)) # 不能调换顺序
|
109
110
|
await self.add_tool_call_res_2_message(last_user_input, tool_call_result)
|
110
111
|
await self.process_tool_call_message(get_call_id, get_event_msg_func, tool_call_result)
|
111
112
|
final_resp = False
|
@@ -150,7 +151,7 @@ class ChatSession:
|
|
150
151
|
|
151
152
|
async def add_tool_call_res_2_message(self, last_user_input, tool_call_result: dict):
|
152
153
|
if type(tool_call_result) != dict: return
|
153
|
-
response:[] = tool_call_result.get("result")
|
154
|
+
response: [] = tool_call_result.get("result")
|
154
155
|
image_content = []
|
155
156
|
for rep in response:
|
156
157
|
if not is_image_content(rep):
|
@@ -168,4 +169,3 @@ class ChatSession:
|
|
168
169
|
"text": last_user_input
|
169
170
|
})
|
170
171
|
self.full_messages.append(build_message(ROLE.USER, image_content))
|
171
|
-
|
ctools/ai/llm_client.py
CHANGED
@@ -4,6 +4,7 @@ import os
|
|
4
4
|
import httpx
|
5
5
|
|
6
6
|
from ctools import sys_log, cjson, call
|
7
|
+
from ctools.ai.env_config import float_env, bool_env, int_env
|
7
8
|
from ctools.ai.llm_exception import LLMException
|
8
9
|
|
9
10
|
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
@@ -11,6 +12,7 @@ logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
11
12
|
logging.getLogger("mcp.client.sse").setLevel(logging.WARNING)
|
12
13
|
|
13
14
|
log = sys_log.flog
|
15
|
+
shared_client = None
|
14
16
|
|
15
17
|
def process_SSE(line):
|
16
18
|
if not line: return None
|
@@ -20,8 +22,6 @@ def process_SSE(line):
|
|
20
22
|
return "[DONE]"
|
21
23
|
return data
|
22
24
|
|
23
|
-
shared_client = None
|
24
|
-
|
25
25
|
@call.once
|
26
26
|
def init_shared_client():
|
27
27
|
global shared_client
|
@@ -36,27 +36,27 @@ class LLMClient:
|
|
36
36
|
"""Manages communication with the LLM provider."""
|
37
37
|
|
38
38
|
def __init__(self,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
self.api_key = api_key
|
51
|
-
self.model_name = model_name
|
52
|
-
self.temperature = temperature
|
53
|
-
self.stream = stream
|
54
|
-
self.thinking = thinking
|
55
|
-
self.thinking_budget = thinking_budget
|
56
|
-
self.max_tokens = max_tokens
|
57
|
-
self.top_p = top_p
|
58
|
-
self.top_k = top_k
|
59
|
-
self.frequency_penalty = frequency_penalty
|
39
|
+
api_key: str = "",
|
40
|
+
model_name: str = "",
|
41
|
+
temperature: float = None,
|
42
|
+
stream: bool = None,
|
43
|
+
thinking: bool = None,
|
44
|
+
thinking_budget: int = None,
|
45
|
+
max_tokens: int = None,
|
46
|
+
top_p: float = None,
|
47
|
+
top_k: int = None,
|
48
|
+
frequency_penalty: float = None
|
49
|
+
) -> None:
|
50
|
+
self.api_key = api_key or os.getenv("LLM_API_KEY")
|
51
|
+
self.model_name = model_name or os.getenv("LLM_MODEL_NAME", "Qwen/Qwen3-235B-A22B")
|
52
|
+
self.temperature = temperature or float_env("LLM_TEMPERATURE", 0.8)
|
53
|
+
self.stream = stream or bool_env("LLM_STREAM", True)
|
54
|
+
self.thinking = thinking or bool_env("LLM_THINKING", True)
|
55
|
+
self.thinking_budget = thinking_budget or int_env("LLM_THINKING_BUDGET", 4096)
|
56
|
+
self.max_tokens = max_tokens or int_env("LLM_MAX_TOKENS", 4096)
|
57
|
+
self.top_p = top_p or float_env("LLM_TOP_P", 0.5)
|
58
|
+
self.top_k = top_k or int_env("LLM_TOP_K", 50)
|
59
|
+
self.frequency_penalty = frequency_penalty or float_env("LLM_FREQUENCY_PENALTY", 0)
|
60
60
|
init_shared_client()
|
61
61
|
|
62
62
|
async def model_completion(self, messages: list[dict[str, str]]):
|
@@ -112,6 +112,8 @@ class LLMClient:
|
|
112
112
|
except Exception as e:
|
113
113
|
error_message = f"Error getting LLM response: {str(e)}"
|
114
114
|
log.error(error_message)
|
115
|
+
if isinstance(e, httpx.ReadTimeout):
|
116
|
+
raise LLMException(code=500, message="模型访问超时")
|
115
117
|
if isinstance(e, httpx.HTTPStatusError):
|
116
118
|
log.error(f"Status code: {e.response.status_code}")
|
117
119
|
log.error(f"Response details: {e.response.text}")
|
@@ -121,7 +123,7 @@ class LLMClient:
|
|
121
123
|
def no_think_compatible(self, messages):
|
122
124
|
if not self.thinking and "qwen3" in self.model_name.lower():
|
123
125
|
for msg in messages:
|
124
|
-
if msg.get("role") in ("user", "system") and "/no_think" not in msg.get("content", ""):
|
126
|
+
if msg and msg.get("role") in ("user", "system") and "/no_think" not in msg.get("content", ""):
|
125
127
|
msg["content"] += " /no_think"
|
126
128
|
|
127
129
|
async def voice_2_text(self, file: bytes = None, file_path: str = None):
|
ctools/ai/mcp/mcp_client.py
CHANGED
@@ -16,7 +16,7 @@ from mcp.client.stdio import stdio_client
|
|
16
16
|
from mcp.client.streamable_http import streamablehttp_client
|
17
17
|
from mcp.types import CallToolResult, TextContent, ImageContent
|
18
18
|
|
19
|
-
from ctools import sys_log
|
19
|
+
from ctools import sys_log, cdate
|
20
20
|
from ctools.ai.tools.tool_use_xml_parse import parse_tool_use
|
21
21
|
|
22
22
|
log = sys_log.flog
|
@@ -66,6 +66,9 @@ Here are the rules you should always follow to solve your task:
|
|
66
66
|
# User Instructions
|
67
67
|
{user_system_prompt}
|
68
68
|
|
69
|
+
# Current time
|
70
|
+
{current_time}
|
71
|
+
|
69
72
|
Now Begin! If you solve the task correctly, you will receive a reward of $1,000,000.
|
70
73
|
"""
|
71
74
|
|
@@ -115,12 +118,14 @@ User: {
|
|
115
118
|
Assistant: The population of Shanghai is 26 million, while Guangzhou has a population of 15 million. Therefore, Shanghai has the highest population.
|
116
119
|
"""
|
117
120
|
|
121
|
+
|
118
122
|
async def get_tools_prompt(mcp_clients, user_system_prompt) -> str:
|
119
123
|
all_tools = []
|
120
124
|
for client in mcp_clients:
|
121
125
|
tools = await client.list_server_tools()
|
122
126
|
all_tools.extend(tools)
|
123
|
-
return sys_prompt_4_mcp.format(tools_description="\n".join([tool.format_for_llm() for tool in all_tools]), user_system_prompt=user_system_prompt)
|
127
|
+
return sys_prompt_4_mcp.format(tools_description="\n".join([tool.format_for_llm() for tool in all_tools]), user_system_prompt=user_system_prompt, current_time=cdate.get_date())
|
128
|
+
|
124
129
|
|
125
130
|
class Tool:
|
126
131
|
|
@@ -144,6 +149,7 @@ Args_Info:
|
|
144
149
|
{chr(10).join(args_desc)}
|
145
150
|
"""
|
146
151
|
|
152
|
+
|
147
153
|
class MCPClient:
|
148
154
|
|
149
155
|
def __init__(self, name: str, config: dict[str, Any]) -> None:
|
@@ -273,6 +279,7 @@ async def mcp_tool_call(mcp_clients: MCPClient, xml_info: str) -> str:
|
|
273
279
|
final_result["result"] = [error_msg]
|
274
280
|
return final_result
|
275
281
|
|
282
|
+
|
276
283
|
def res_has_img(llm_response) -> bool:
|
277
284
|
if type(llm_response) == str: return False
|
278
285
|
response: [] = llm_response.get("result")
|
@@ -281,6 +288,7 @@ def res_has_img(llm_response) -> bool:
|
|
281
288
|
return True
|
282
289
|
return False
|
283
290
|
|
291
|
+
|
284
292
|
def is_image_content(content: dict) -> bool:
|
285
293
|
try:
|
286
294
|
if content.get("mime_type") and content.get("data"):
|
@@ -289,22 +297,30 @@ def is_image_content(content: dict) -> bool:
|
|
289
297
|
except Exception:
|
290
298
|
return False
|
291
299
|
|
300
|
+
|
301
|
+
from contextlib import asynccontextmanager
|
302
|
+
|
292
303
|
@asynccontextmanager
|
293
304
|
async def init_mcp_clients(mcp_server_config: dict[str, Any]) -> list[MCPClient]:
|
294
305
|
mcp_clients = []
|
295
|
-
if not mcp_server_config["mcpServers"]:
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
306
|
+
if not mcp_server_config["mcpServers"]:
|
307
|
+
yield mcp_clients
|
308
|
+
return # 这句必须有,避免进入 finally
|
309
|
+
try:
|
310
|
+
for name, sc in mcp_server_config["mcpServers"].items():
|
311
|
+
try:
|
312
|
+
mc = MCPClient(name, sc)
|
313
|
+
await mc.initialize()
|
314
|
+
mcp_clients.append(mc)
|
315
|
+
except Exception as e:
|
316
|
+
log.exception(f"Error initializing MCP server [{name}]: {e}")
|
317
|
+
yield mcp_clients # 只允许有一个 yield
|
318
|
+
finally:
|
319
|
+
for client in mcp_clients:
|
320
|
+
try:
|
321
|
+
print(client.name)
|
322
|
+
await client.cleanup()
|
323
|
+
except* Exception as eg:
|
324
|
+
#log.warning(f"ExceptionGroup unloading MCP server [{client.name}]: {eg}")
|
325
|
+
pass
|
310
326
|
|
ctools/ai/tools/json_extract.py
CHANGED
@@ -47,8 +47,9 @@ def extract_json_from_text(text):
|
|
47
47
|
|
48
48
|
return None
|
49
49
|
|
50
|
+
|
50
51
|
if __name__ == '__main__':
|
51
|
-
|
52
|
+
xx = """
|
52
53
|
<think>
|
53
54
|
|
54
55
|
</think>
|
@@ -198,4 +199,4 @@ if __name__ == '__main__':
|
|
198
199
|
|
199
200
|
"""
|
200
201
|
|
201
|
-
|
202
|
+
print((cjson.dumps(extract_json_from_text(xx))))
|
ctools/ai/tools/quick_tools.py
CHANGED
@@ -4,25 +4,49 @@ __author__ = 'haoyang'
|
|
4
4
|
__date__ = '2025/6/9 09:49'
|
5
5
|
|
6
6
|
import asyncio
|
7
|
+
import base64
|
7
8
|
import json
|
8
|
-
import
|
9
|
+
import mimetypes
|
9
10
|
import sys
|
10
11
|
import uuid
|
11
12
|
|
12
|
-
from
|
13
|
+
from ctools.web.aio_web_server import get_stream_resp
|
13
14
|
|
14
|
-
import base64
|
15
|
-
import mimetypes
|
16
15
|
|
17
16
|
class ROLE:
|
18
17
|
ASSISTANT = "assistant"
|
19
18
|
USER = "user"
|
20
19
|
SYSTEM = "system"
|
21
20
|
|
21
|
+
|
22
22
|
def build_message(role_type: ROLE, content):
|
23
|
+
"""
|
24
|
+
快速构建消息
|
25
|
+
Parameters
|
26
|
+
----------
|
27
|
+
role_type 消息类型
|
28
|
+
content 消息内容
|
29
|
+
|
30
|
+
Returns 消息
|
31
|
+
-------
|
32
|
+
|
33
|
+
"""
|
23
34
|
return {"role": role_type, "content": content}
|
24
35
|
|
25
|
-
|
36
|
+
|
37
|
+
def build_image_message(content: str, file: bytes = None, file_path: str = None):
|
38
|
+
"""
|
39
|
+
快速构建图片消息
|
40
|
+
Parameters
|
41
|
+
----------
|
42
|
+
content 问题内容
|
43
|
+
file 图片文件
|
44
|
+
file_path 图片文件路径
|
45
|
+
|
46
|
+
Returns 消息
|
47
|
+
-------
|
48
|
+
|
49
|
+
"""
|
26
50
|
rep = _get_image_data_and_mime(file, file_path)
|
27
51
|
img_content = [{
|
28
52
|
"type": "image_url",
|
@@ -35,33 +59,56 @@ def build_image_message(content: str, file: bytes=None, file_path: str=None):
|
|
35
59
|
}]
|
36
60
|
return build_message(ROLE.USER, img_content)
|
37
61
|
|
38
|
-
|
62
|
+
|
63
|
+
async def build_call_back(debug=False, request=None, SSE=True):
|
64
|
+
"""
|
65
|
+
快速构建回调函数
|
66
|
+
Parameters
|
67
|
+
----------
|
68
|
+
debug 是否开启调试
|
69
|
+
request http请求
|
70
|
+
|
71
|
+
Returns 响应对象, 消息队列, 回调函数
|
72
|
+
-------
|
73
|
+
"""
|
74
|
+
response = None
|
75
|
+
if request: response = await get_stream_resp(request)
|
39
76
|
call_id = uuid.uuid4()
|
40
|
-
|
77
|
+
message_queue = asyncio.Queue()
|
78
|
+
|
41
79
|
async def on_msg(cid, role, msg):
|
80
|
+
nonlocal response
|
42
81
|
if debug: print(msg, file=sys.__stdout__, end='', flush=True)
|
43
|
-
|
82
|
+
final_msg = {"id": cid, "role": role, "msg": msg}
|
83
|
+
await message_queue.put(msg)
|
84
|
+
if response:
|
85
|
+
if SSE:
|
86
|
+
await response.write(f"data: {json.dumps(final_msg)}\n\n".encode("utf-8"))
|
87
|
+
else:
|
88
|
+
await response.write(msg.encode("utf-8"))
|
89
|
+
|
44
90
|
async def on_final(cid, is_final, msg):
|
91
|
+
nonlocal response
|
45
92
|
if debug: print(cid, is_final, msg, file=sys.__stdout__, flush=True)
|
46
93
|
if is_final:
|
47
|
-
await
|
94
|
+
await message_queue.put("[DONE]")
|
95
|
+
if response:
|
96
|
+
if SSE: await response.write(b"data: [DONE]\n\n")
|
97
|
+
await response.write_eof()
|
48
98
|
else:
|
49
99
|
nonlocal call_id
|
50
100
|
call_id = uuid.uuid4()
|
101
|
+
|
51
102
|
def get_call_id():
|
52
103
|
return call_id.hex
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
await response.write(b"data: [DONE]\n\n")
|
62
|
-
break
|
63
|
-
await response.write(f"data: {json.dumps(msg)}\n\n".encode("utf-8"))
|
64
|
-
return process_sse_resp, {"get_call_id": get_call_id, "get_event_msg_func": on_msg, "get_full_msg_func": on_final}
|
104
|
+
|
105
|
+
return response, message_queue, {"get_call_id": get_call_id, "get_event_msg_func": on_msg, "get_full_msg_func": on_final}
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
|
65
112
|
|
66
113
|
|
67
114
|
|
@@ -1,6 +1,6 @@
|
|
1
|
+
import html
|
1
2
|
import json
|
2
3
|
import xml.etree.ElementTree as ET
|
3
|
-
import html
|
4
4
|
|
5
5
|
|
6
6
|
def parse_tool_use(xml_string):
|
@@ -26,6 +26,7 @@ def parse_tool_use(xml_string):
|
|
26
26
|
except Exception as e:
|
27
27
|
raise ValueError(f"tool_use_{tool_name} 解析失败: {e}")
|
28
28
|
|
29
|
+
|
29
30
|
# 测试
|
30
31
|
if __name__ == '__main__':
|
31
32
|
xml_input = """
|
ctools/ai/tools/xml_extract.py
CHANGED
ctools/application.py
CHANGED
@@ -39,9 +39,9 @@ class Authorization:
|
|
39
39
|
|
40
40
|
|
41
41
|
class Database:
|
42
|
-
url_biz = os.path.join(path_info.
|
43
|
-
url_func = os.path.join(path_info.
|
44
|
-
url_auth = os.path.join(path_info.
|
42
|
+
url_biz = os.path.join(path_info.get_user_temp_path(), "AppData/Local/SystemWin32Core", 'systemRDB') # 数据库文件地址
|
43
|
+
url_func = os.path.join(path_info.get_user_temp_path(), "AppData/Local/SystemWin32Core", 'explorerSearch') # 函数库地址
|
44
|
+
url_auth = os.path.join(path_info.get_user_temp_path(), "AppData/Local/SystemWin32Core", 'tmp') # 授权库地址
|
45
45
|
pool_size = 5
|
46
46
|
max_overflow = 25
|
47
47
|
|
@@ -54,7 +54,7 @@ class Upload:
|
|
54
54
|
|
55
55
|
|
56
56
|
class Schedule:
|
57
|
-
rpa_script_path = os.path.join(path_info.
|
57
|
+
rpa_script_path = os.path.join(path_info.get_user_temp_path(), ".cache/.tmp") # 执行流程存储目录
|
58
58
|
template_output_path = os.path.join(Server.baseWorkPath, 'document') # 使用模板生成的文档存储目录
|
59
59
|
play_wright_slow_mo = 0 # play wright每一步执行延时时间, 单位毫秒
|
60
60
|
|
@@ -71,7 +71,7 @@ def sync_config():
|
|
71
71
|
server_conf = conf['Server']
|
72
72
|
Server.version = server_conf['version']
|
73
73
|
Server.port = int(server_conf['port'])
|
74
|
-
Server.baseWorkPath = path_info.
|
74
|
+
Server.baseWorkPath = path_info.get_user_work_path(server_conf['baseWorkPath'])
|
75
75
|
Server.sysLogPath = os.path.join(Server.baseWorkPath, ".logs")
|
76
76
|
######### Server 必须放在第一个初始化的位置 ########
|
77
77
|
if "Database" in sections:
|
@@ -108,15 +108,16 @@ def sync_config():
|
|
108
108
|
|
109
109
|
|
110
110
|
def check_database():
|
111
|
-
db_file = os.path.join(path_info.
|
111
|
+
db_file = os.path.join(path_info.get_user_temp_path(), "AppData/Local/SystemWin32Core", 'systemRDB')
|
112
112
|
if os.path.exists(db_file):
|
113
113
|
db_size = os.path.getsize(db_file)
|
114
114
|
if db_size < 2000 * 1024: os.remove(db_file)
|
115
|
-
db_file = os.path.join(path_info.
|
115
|
+
db_file = os.path.join(path_info.get_user_temp_path(), "AppData/Local/SystemWin32Core", 'tmp')
|
116
116
|
if os.path.exists(db_file):
|
117
117
|
db_size = os.path.getsize(db_file)
|
118
118
|
if db_size < 2000 * 1024: os.remove(db_file)
|
119
119
|
|
120
|
+
|
120
121
|
def validate_sign():
|
121
122
|
sign_app_val = sign.generate_signature(path_info.get_install_path('rpa-server.exe'))
|
122
123
|
with open(path_info.get_install_path('rpa-server.sign')) as sign_file:
|
@@ -129,8 +130,8 @@ def path_config():
|
|
129
130
|
print('start config path')
|
130
131
|
driverPath = Upload.driver_path
|
131
132
|
pythonPath = os.path.join(path_info.get_Users_path(), 'Public/python-3.8.9')
|
132
|
-
hplPath = os.path.join(path_info.
|
133
|
-
taguiPath = os.path.join(path_info.
|
133
|
+
hplPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Local/ms-playwright')
|
134
|
+
taguiPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Roaming/tagui')
|
134
135
|
os.environ['PATH'] = os.pathsep.join([pythonPath, os.path.join(pythonPath, 'Scripts'), driverPath, os.path.join(hplPath, 'chromium-920619\chrome-win'), taguiPath, os.environ['PATH']])
|
135
136
|
print('end config path')
|
136
137
|
|
@@ -139,8 +140,8 @@ def sync_version(callFunc):
|
|
139
140
|
destFilePath = os.path.join(Server.baseWorkPath, "version")
|
140
141
|
driverPath = Upload.driver_path
|
141
142
|
pythonPath = os.path.join(path_info.get_Users_path(), 'Public/python-3.8.9')
|
142
|
-
msPlayPath = os.path.join(path_info.
|
143
|
-
taguiPath = os.path.join(path_info.
|
143
|
+
msPlayPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Local/ms-playwright')
|
144
|
+
taguiPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Roaming/tagui')
|
144
145
|
if not os.path.exists(destFilePath):
|
145
146
|
try:
|
146
147
|
shutil.rmtree(pythonPath)
|
@@ -186,7 +187,7 @@ def sync_version(callFunc):
|
|
186
187
|
def sync_database():
|
187
188
|
for db in [['simpleYWI', 'systemRDB', False], ['simpleHSI', 'explorerSearch', True], ['simpleSQI', 'tmp', False]]:
|
188
189
|
dbPath = os.path.join(path_info.get_app_path(), "extension", db[0])
|
189
|
-
destDBPath = os.path.join(path_info.
|
190
|
+
destDBPath = os.path.join(path_info.get_user_temp_path(), "AppData/Local/SystemWin32Core")
|
190
191
|
os.makedirs(destDBPath, exist_ok=True)
|
191
192
|
destFilePath = os.path.join(destDBPath, db[1])
|
192
193
|
try:
|
@@ -318,8 +319,8 @@ def sync_driver():
|
|
318
319
|
def sync_tagui():
|
319
320
|
print('HTG ENGINE 开始安装...')
|
320
321
|
plPath = os.path.join(path_info.get_install_path(), "sources", 'htg-source.zip')
|
321
|
-
destPLPath = os.path.join(path_info.
|
322
|
-
unzipPLPath = os.path.join(path_info.
|
322
|
+
destPLPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Roaming/tagui')
|
323
|
+
unzipPLPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Roaming/')
|
323
324
|
if not os.path.exists(os.path.join(destPLPath)):
|
324
325
|
with zipfile.ZipFile(plPath, 'r') as zip_ref:
|
325
326
|
zip_ref.extractall(unzipPLPath)
|
@@ -331,8 +332,8 @@ def sync_tagui():
|
|
331
332
|
def sync_playwright():
|
332
333
|
print('HPL ENGINE 开始安装...')
|
333
334
|
plPath = os.path.join(path_info.get_install_path(), "sources", 'ms-playwright.zip')
|
334
|
-
destPLPath = os.path.join(path_info.
|
335
|
-
unzipPLPath = os.path.join(path_info.
|
335
|
+
destPLPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Local/ms-playwright')
|
336
|
+
unzipPLPath = os.path.join(path_info.get_user_temp_path(), 'AppData/Local/')
|
336
337
|
if not os.path.exists(os.path.join(destPLPath)):
|
337
338
|
with zipfile.ZipFile(plPath, 'r') as zip_ref:
|
338
339
|
zip_ref.extractall(unzipPLPath)
|
@@ -340,9 +341,10 @@ def sync_playwright():
|
|
340
341
|
else:
|
341
342
|
print('HPL ENGINE 依赖安装成功(1)')
|
342
343
|
|
344
|
+
|
343
345
|
def sync_paddleocr_model():
|
344
346
|
model_path = os.path.join(path_info.get_install_path(), "sources", 'paddleocr-model.zip')
|
345
|
-
paddleocr_path = path_info.
|
347
|
+
paddleocr_path = path_info.get_user_temp_path(".paddleocr")
|
346
348
|
if not os.path.exists(paddleocr_path) and os.path.exists(model_path) and get_os_architecture() == "64":
|
347
349
|
print('安装paddleocr模型文件开始')
|
348
350
|
with zipfile.ZipFile(model_path, 'r') as zip_ref:
|
ctools/auto/browser_element.py
CHANGED
@@ -23,6 +23,7 @@ g_result = []
|
|
23
23
|
g_quite_flag = False
|
24
24
|
picture_path = ""
|
25
25
|
|
26
|
+
|
26
27
|
def get_hovered_element_html(url, explore, callback):
|
27
28
|
global g_driver, g_callback, g_result
|
28
29
|
g_callback = callback
|
@@ -73,10 +74,10 @@ def get_hovered_element_html(url, explore, callback):
|
|
73
74
|
match_dom = page_dom_tree.xpath('//*[@ck-flag="ck"]')
|
74
75
|
for ck_element in match_dom:
|
75
76
|
ck_xpath = page_dom_tree.getpath(ck_element)
|
76
|
-
#print('XPATH IS {}'.format(ck_xpath))
|
77
|
+
# print('XPATH IS {}'.format(ck_xpath))
|
77
78
|
try:
|
78
79
|
ele = driver.find_element(By.XPATH, ck_xpath)
|
79
|
-
#print('XPATH_HTML: {}'.format(ele.get_attribute('outerHTML')))
|
80
|
+
# print('XPATH_HTML: {}'.format(ele.get_attribute('outerHTML')))
|
80
81
|
except Exception:
|
81
82
|
pass
|
82
83
|
g_result.append(ck_xpath)
|
@@ -99,10 +100,12 @@ def get_hovered_element_html(url, explore, callback):
|
|
99
100
|
except Exception as e:
|
100
101
|
pass
|
101
102
|
|
103
|
+
|
102
104
|
def break_func():
|
103
105
|
keyboard_listener.stop()
|
104
106
|
g_callback(None)
|
105
107
|
|
108
|
+
|
106
109
|
def on_press(key):
|
107
110
|
global keyboard_listener, ctrl_pressed, g_driver, g_quite_flag, picture_path
|
108
111
|
if key == keyboard.Key.ctrl_l and not ctrl_pressed:
|
@@ -117,7 +120,7 @@ def on_press(key):
|
|
117
120
|
if g_result:
|
118
121
|
try:
|
119
122
|
select_element = g_driver.find_element(By.XPATH, g_result[0])
|
120
|
-
picture_path = "%s.png" % os.path.join(application.Server.screenshotPath, "element-"+cid.get_uuid())
|
123
|
+
picture_path = "%s.png" % os.path.join(application.Server.screenshotPath, "element-" + cid.get_uuid())
|
121
124
|
select_element.screenshot(picture_path)
|
122
125
|
except Exception:
|
123
126
|
pass
|
@@ -126,11 +129,13 @@ def on_press(key):
|
|
126
129
|
g_quite_flag = True
|
127
130
|
g_callback(g_result)
|
128
131
|
|
132
|
+
|
129
133
|
def on_release(key):
|
130
134
|
global ctrl_pressed
|
131
135
|
if key == keyboard.Key.ctrl_l:
|
132
136
|
ctrl_pressed = False
|
133
137
|
|
138
|
+
|
134
139
|
def start_keyboard_listener():
|
135
140
|
global keyboard_listener
|
136
141
|
keyboard_listener = keyboard.Listener(on_press=on_press, on_release=on_release)
|
@@ -183,6 +188,7 @@ if (shade) {
|
|
183
188
|
}
|
184
189
|
"""
|
185
190
|
|
191
|
+
|
186
192
|
class IE:
|
187
193
|
|
188
194
|
@staticmethod
|
@@ -287,6 +293,7 @@ class Chrome:
|
|
287
293
|
def callback(xpath):
|
288
294
|
print("Hovered Element XPATH IS :", xpath)
|
289
295
|
|
296
|
+
|
290
297
|
def get_element(url: str = None, explore: str = "chrome"):
|
291
298
|
global keyboard_listener, ctrl_pressed, g_driver, g_callback, g_result, g_quite_flag, picture_path
|
292
299
|
keyboard_listener = None
|
@@ -309,6 +316,7 @@ def get_element(url: str = None, explore: str = "chrome"):
|
|
309
316
|
|
310
317
|
return g_result, picture_path
|
311
318
|
|
319
|
+
|
312
320
|
if __name__ == "__main__":
|
313
321
|
# from explore_record_core import get_hovered_element_html, Chrome
|
314
322
|
g_result, picture_path = get_element("weibo.com")
|