mirror of
https://github.com/langbot-app/LangBot.git
synced 2025-11-25 19:37:36 +08:00
chore: adjust dir structure
This commit is contained in:
@@ -1,45 +0,0 @@
|
|||||||
from v1 import client # type: ignore
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class TestDifyClient:
|
|
||||||
async def test_chat_messages(self):
|
|
||||||
cln = client.AsyncDifyServiceClient(api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL'))
|
|
||||||
|
|
||||||
async for chunk in cln.chat_messages(inputs={}, query='调用工具查看现在几点?', user='test'):
|
|
||||||
print(json.dumps(chunk, ensure_ascii=False, indent=4))
|
|
||||||
|
|
||||||
async def test_upload_file(self):
|
|
||||||
cln = client.AsyncDifyServiceClient(api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL'))
|
|
||||||
|
|
||||||
file_bytes = open('img.png', 'rb').read()
|
|
||||||
|
|
||||||
print(type(file_bytes))
|
|
||||||
|
|
||||||
file = ('img2.png', file_bytes, 'image/png')
|
|
||||||
|
|
||||||
resp = await cln.upload_file(file=file, user='test')
|
|
||||||
print(json.dumps(resp, ensure_ascii=False, indent=4))
|
|
||||||
|
|
||||||
async def test_workflow_run(self):
|
|
||||||
cln = client.AsyncDifyServiceClient(api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL'))
|
|
||||||
|
|
||||||
# resp = await cln.workflow_run(inputs={}, user="test")
|
|
||||||
# # print(json.dumps(resp, ensure_ascii=False, indent=4))
|
|
||||||
# print(resp)
|
|
||||||
chunks = []
|
|
||||||
|
|
||||||
ignored_events = ['text_chunk']
|
|
||||||
async for chunk in cln.workflow_run(inputs={}, user='test'):
|
|
||||||
if chunk['event'] in ignored_events:
|
|
||||||
continue
|
|
||||||
chunks.append(chunk)
|
|
||||||
print(json.dumps(chunks, ensure_ascii=False, indent=4))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
asyncio.run(TestDifyClient().test_chat_messages())
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
import typing
|
|
||||||
import os
|
|
||||||
import base64
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import pydantic
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from ..core import app
|
|
||||||
|
|
||||||
|
|
||||||
class Announcement(pydantic.BaseModel):
|
|
||||||
"""公告"""
|
|
||||||
|
|
||||||
id: int
|
|
||||||
|
|
||||||
time: str
|
|
||||||
|
|
||||||
timestamp: int
|
|
||||||
|
|
||||||
content: str
|
|
||||||
|
|
||||||
enabled: typing.Optional[bool] = True
|
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
return {
|
|
||||||
'id': self.id,
|
|
||||||
'time': self.time,
|
|
||||||
'timestamp': self.timestamp,
|
|
||||||
'content': self.content,
|
|
||||||
'enabled': self.enabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AnnouncementManager:
|
|
||||||
"""公告管理器"""
|
|
||||||
|
|
||||||
ap: app.Application = None
|
|
||||||
|
|
||||||
def __init__(self, ap: app.Application):
|
|
||||||
self.ap = ap
|
|
||||||
|
|
||||||
async def fetch_all(self) -> list[Announcement]:
|
|
||||||
"""获取所有公告"""
|
|
||||||
try:
|
|
||||||
resp = requests.get(
|
|
||||||
url='https://api.github.com/repos/langbot-app/LangBot/contents/res/announcement.json',
|
|
||||||
proxies=self.ap.proxy_mgr.get_forward_proxies(),
|
|
||||||
timeout=5,
|
|
||||||
)
|
|
||||||
resp.raise_for_status() # 检查请求是否成功
|
|
||||||
obj_json = resp.json()
|
|
||||||
b64_content = obj_json['content']
|
|
||||||
# 解码
|
|
||||||
content = base64.b64decode(b64_content).decode('utf-8')
|
|
||||||
|
|
||||||
return [Announcement(**item) for item in json.loads(content)]
|
|
||||||
except (requests.RequestException, json.JSONDecodeError, KeyError) as e:
|
|
||||||
self.ap.logger.warning(f'获取公告失败: {e}')
|
|
||||||
pass
|
|
||||||
return [] # 请求失败时返回空列表
|
|
||||||
|
|
||||||
async def fetch_saved(self) -> list[Announcement]:
|
|
||||||
if not os.path.exists('data/labels/announcement_saved.json'):
|
|
||||||
with open('data/labels/announcement_saved.json', 'w', encoding='utf-8') as f:
|
|
||||||
f.write('[]')
|
|
||||||
|
|
||||||
with open('data/labels/announcement_saved.json', 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
if not content:
|
|
||||||
content = '[]'
|
|
||||||
|
|
||||||
return [Announcement(**item) for item in json.loads(content)]
|
|
||||||
|
|
||||||
async def write_saved(self, content: list[Announcement]):
|
|
||||||
with open('data/labels/announcement_saved.json', 'w', encoding='utf-8') as f:
|
|
||||||
f.write(json.dumps([item.to_dict() for item in content], indent=4, ensure_ascii=False))
|
|
||||||
|
|
||||||
async def fetch_new(self) -> list[Announcement]:
|
|
||||||
"""获取新公告"""
|
|
||||||
all = await self.fetch_all()
|
|
||||||
saved = await self.fetch_saved()
|
|
||||||
|
|
||||||
to_show: list[Announcement] = []
|
|
||||||
|
|
||||||
for item in all:
|
|
||||||
# 遍历saved检查是否有相同id的公告
|
|
||||||
for saved_item in saved:
|
|
||||||
if saved_item.id == item.id:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if item.enabled:
|
|
||||||
# 没有相同id的公告
|
|
||||||
to_show.append(item)
|
|
||||||
|
|
||||||
await self.write_saved(all)
|
|
||||||
return to_show
|
|
||||||
|
|
||||||
async def show_announcements(self) -> typing.Tuple[str, int]:
|
|
||||||
"""显示公告"""
|
|
||||||
try:
|
|
||||||
announcements = await self.fetch_new()
|
|
||||||
ann_text = ''
|
|
||||||
for ann in announcements:
|
|
||||||
ann_text += f'[公告] {ann.time}: {ann.content}\n'
|
|
||||||
|
|
||||||
# TODO statistics
|
|
||||||
|
|
||||||
return ann_text, logging.INFO
|
|
||||||
except Exception as e:
|
|
||||||
return f'获取公告时出错: {e}', logging.WARNING
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
"""LangBot - Easy-to-use global IM bot platform designed for LLM era"""
|
"""LangBot - Easy-to-use global IM bot platform designed for LLM era"""
|
||||||
|
|
||||||
__version__ = "4.4.1"
|
__version__ = '4.4.1'
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
"""LangBot entry point for package execution"""
|
"""LangBot entry point for package execution"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
@@ -42,7 +43,7 @@ async def main_entry(loop: asyncio.AbstractEventLoop):
|
|||||||
print(asciiart)
|
print(asciiart)
|
||||||
|
|
||||||
# Check dependencies
|
# Check dependencies
|
||||||
from pkg.core.bootutils import deps
|
from langbot.pkg.core.bootutils import deps
|
||||||
|
|
||||||
missing_deps = await deps.check_deps()
|
missing_deps = await deps.check_deps()
|
||||||
|
|
||||||
@@ -7,10 +7,8 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncCozeAPIClient:
|
class AsyncCozeAPIClient:
|
||||||
def __init__(self, api_key: str, api_base: str = "https://api.coze.cn"):
|
def __init__(self, api_key: str, api_base: str = 'https://api.coze.cn'):
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.api_base = api_base
|
self.api_base = api_base
|
||||||
self.session = None
|
self.session = None
|
||||||
@@ -24,13 +22,11 @@ class AsyncCozeAPIClient:
|
|||||||
"""退出时自动关闭会话"""
|
"""退出时自动关闭会话"""
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def coze_session(self):
|
async def coze_session(self):
|
||||||
"""确保HTTP session存在"""
|
"""确保HTTP session存在"""
|
||||||
if self.session is None:
|
if self.session is None:
|
||||||
connector = aiohttp.TCPConnector(
|
connector = aiohttp.TCPConnector(
|
||||||
ssl=False if self.api_base.startswith("http://") else True,
|
ssl=False if self.api_base.startswith('http://') else True,
|
||||||
limit=100,
|
limit=100,
|
||||||
limit_per_host=30,
|
limit_per_host=30,
|
||||||
keepalive_timeout=30,
|
keepalive_timeout=30,
|
||||||
@@ -42,12 +38,10 @@ class AsyncCozeAPIClient:
|
|||||||
sock_read=120,
|
sock_read=120,
|
||||||
)
|
)
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
'Authorization': f'Bearer {self.api_key}',
|
||||||
"Accept": "text/event-stream",
|
'Accept': 'text/event-stream',
|
||||||
}
|
}
|
||||||
self.session = aiohttp.ClientSession(
|
self.session = aiohttp.ClientSession(headers=headers, timeout=timeout, connector=connector)
|
||||||
headers=headers, timeout=timeout, connector=connector
|
|
||||||
)
|
|
||||||
return self.session
|
return self.session
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
@@ -63,15 +57,15 @@ class AsyncCozeAPIClient:
|
|||||||
# 处理 Path 对象
|
# 处理 Path 对象
|
||||||
if isinstance(file, Path):
|
if isinstance(file, Path):
|
||||||
if not file.exists():
|
if not file.exists():
|
||||||
raise ValueError(f"File not found: {file}")
|
raise ValueError(f'File not found: {file}')
|
||||||
with open(file, "rb") as f:
|
with open(file, 'rb') as f:
|
||||||
file = f.read()
|
file = f.read()
|
||||||
|
|
||||||
# 处理文件路径字符串
|
# 处理文件路径字符串
|
||||||
elif isinstance(file, str):
|
elif isinstance(file, str):
|
||||||
if not os.path.isfile(file):
|
if not os.path.isfile(file):
|
||||||
raise ValueError(f"File not found: {file}")
|
raise ValueError(f'File not found: {file}')
|
||||||
with open(file, "rb") as f:
|
with open(file, 'rb') as f:
|
||||||
file = f.read()
|
file = f.read()
|
||||||
|
|
||||||
# 处理文件对象
|
# 处理文件对象
|
||||||
@@ -79,43 +73,39 @@ class AsyncCozeAPIClient:
|
|||||||
file = file.read()
|
file = file.read()
|
||||||
|
|
||||||
session = await self.coze_session()
|
session = await self.coze_session()
|
||||||
url = f"{self.api_base}/v1/files/upload"
|
url = f'{self.api_base}/v1/files/upload'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_io = io.BytesIO(file)
|
file_io = io.BytesIO(file)
|
||||||
async with session.post(
|
async with session.post(
|
||||||
url,
|
url,
|
||||||
data={
|
data={
|
||||||
"file": file_io,
|
'file': file_io,
|
||||||
},
|
},
|
||||||
timeout=aiohttp.ClientTimeout(total=60),
|
timeout=aiohttp.ClientTimeout(total=60),
|
||||||
) as response:
|
) as response:
|
||||||
if response.status == 401:
|
if response.status == 401:
|
||||||
raise Exception("Coze API 认证失败,请检查 API Key 是否正确")
|
raise Exception('Coze API 认证失败,请检查 API Key 是否正确')
|
||||||
|
|
||||||
response_text = await response.text()
|
response_text = await response.text()
|
||||||
|
|
||||||
|
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
raise Exception(
|
raise Exception(f'文件上传失败,状态码: {response.status}, 响应: {response_text}')
|
||||||
f"文件上传失败,状态码: {response.status}, 响应: {response_text}"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
result = await response.json()
|
result = await response.json()
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
raise Exception(f"文件上传响应解析失败: {response_text}")
|
raise Exception(f'文件上传响应解析失败: {response_text}')
|
||||||
|
|
||||||
if result.get("code") != 0:
|
if result.get('code') != 0:
|
||||||
raise Exception(f"文件上传失败: {result.get('msg', '未知错误')}")
|
raise Exception(f'文件上传失败: {result.get("msg", "未知错误")}')
|
||||||
|
|
||||||
file_id = result["data"]["id"]
|
file_id = result['data']['id']
|
||||||
return file_id
|
return file_id
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
raise Exception("文件上传超时")
|
raise Exception('文件上传超时')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"文件上传失败: {str(e)}")
|
raise Exception(f'文件上传失败: {str(e)}')
|
||||||
|
|
||||||
|
|
||||||
async def chat_messages(
|
async def chat_messages(
|
||||||
self,
|
self,
|
||||||
@@ -139,22 +129,21 @@ class AsyncCozeAPIClient:
|
|||||||
timeout: 超时时间
|
timeout: 超时时间
|
||||||
"""
|
"""
|
||||||
session = await self.coze_session()
|
session = await self.coze_session()
|
||||||
url = f"{self.api_base}/v3/chat"
|
url = f'{self.api_base}/v3/chat'
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"bot_id": bot_id,
|
'bot_id': bot_id,
|
||||||
"user_id": user_id,
|
'user_id': user_id,
|
||||||
"stream": stream,
|
'stream': stream,
|
||||||
"auto_save_history": auto_save_history,
|
'auto_save_history': auto_save_history,
|
||||||
}
|
}
|
||||||
|
|
||||||
if additional_messages:
|
if additional_messages:
|
||||||
payload["additional_messages"] = additional_messages
|
payload['additional_messages'] = additional_messages
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
if conversation_id:
|
if conversation_id:
|
||||||
params["conversation_id"] = conversation_id
|
params['conversation_id'] = conversation_id
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with session.post(
|
async with session.post(
|
||||||
@@ -164,29 +153,25 @@ class AsyncCozeAPIClient:
|
|||||||
timeout=aiohttp.ClientTimeout(total=timeout),
|
timeout=aiohttp.ClientTimeout(total=timeout),
|
||||||
) as response:
|
) as response:
|
||||||
if response.status == 401:
|
if response.status == 401:
|
||||||
raise Exception("Coze API 认证失败,请检查 API Key 是否正确")
|
raise Exception('Coze API 认证失败,请检查 API Key 是否正确')
|
||||||
|
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
raise Exception(f"Coze API 流式请求失败,状态码: {response.status}")
|
raise Exception(f'Coze API 流式请求失败,状态码: {response.status}')
|
||||||
|
|
||||||
|
|
||||||
async for chunk in response.content:
|
async for chunk in response.content:
|
||||||
chunk = chunk.decode("utf-8")
|
chunk = chunk.decode('utf-8')
|
||||||
if chunk != '\n':
|
if chunk != '\n':
|
||||||
if chunk.startswith("event:"):
|
if chunk.startswith('event:'):
|
||||||
chunk_type = chunk.replace("event:", "", 1).strip()
|
chunk_type = chunk.replace('event:', '', 1).strip()
|
||||||
elif chunk.startswith("data:"):
|
elif chunk.startswith('data:'):
|
||||||
chunk_data = chunk.replace("data:", "", 1).strip()
|
chunk_data = chunk.replace('data:', '', 1).strip()
|
||||||
else:
|
else:
|
||||||
yield {"event": chunk_type, "data": json.loads(chunk_data) if chunk_data else {}} # 处理本地部署时,接口返回的data为空值
|
yield {
|
||||||
|
'event': chunk_type,
|
||||||
|
'data': json.loads(chunk_data) if chunk_data else {},
|
||||||
|
} # 处理本地部署时,接口返回的data为空值
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
raise Exception(f"Coze API 流式请求超时 ({timeout}秒)")
|
raise Exception(f'Coze API 流式请求超时 ({timeout}秒)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Coze API 流式请求失败: {str(e)}")
|
raise Exception(f'Coze API 流式请求失败: {str(e)}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,6 @@ class DingTalkEvent(dict):
|
|||||||
def name(self):
|
def name(self):
|
||||||
return self.get('Name', '')
|
return self.get('Name', '')
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def conversation(self):
|
def conversation(self):
|
||||||
return self.get('conversation_type', '')
|
return self.get('conversation_type', '')
|
||||||
@@ -219,10 +219,7 @@ class WecomBotClient:
|
|||||||
self.ReceiveId = ''
|
self.ReceiveId = ''
|
||||||
self.app = Quart(__name__)
|
self.app = Quart(__name__)
|
||||||
self.app.add_url_rule(
|
self.app.add_url_rule(
|
||||||
'/callback/command',
|
'/callback/command', 'handle_callback', self.handle_callback_request, methods=['POST', 'GET']
|
||||||
'handle_callback',
|
|
||||||
self.handle_callback_request,
|
|
||||||
methods=['POST', 'GET']
|
|
||||||
)
|
)
|
||||||
self._message_handlers = {
|
self._message_handlers = {
|
||||||
'example': [],
|
'example': [],
|
||||||
@@ -420,7 +417,7 @@ class WecomBotClient:
|
|||||||
await self.logger.error("请求体中缺少 'encrypt' 字段")
|
await self.logger.error("请求体中缺少 'encrypt' 字段")
|
||||||
return Response('Bad Request', status=400)
|
return Response('Bad Request', status=400)
|
||||||
|
|
||||||
xml_post_data = f"<xml><Encrypt><![CDATA[{encrypted_msg}]]></Encrypt></xml>"
|
xml_post_data = f'<xml><Encrypt><![CDATA[{encrypted_msg}]]></Encrypt></xml>'
|
||||||
ret, decrypted_xml = self.wxcpt.DecryptMsg(xml_post_data, msg_signature, timestamp, nonce)
|
ret, decrypted_xml = self.wxcpt.DecryptMsg(xml_post_data, msg_signature, timestamp, nonce)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
await self.logger.error('解密失败')
|
await self.logger.error('解密失败')
|
||||||
@@ -458,7 +455,7 @@ class WecomBotClient:
|
|||||||
picurl = item.get('image', {}).get('url')
|
picurl = item.get('image', {}).get('url')
|
||||||
|
|
||||||
if texts:
|
if texts:
|
||||||
message_data['content'] = "".join(texts) # 拼接所有 text
|
message_data['content'] = ''.join(texts) # 拼接所有 text
|
||||||
if picurl:
|
if picurl:
|
||||||
base64 = await self.download_url_to_base64(picurl, self.EnCodingAESKey)
|
base64 = await self.download_url_to_base64(picurl, self.EnCodingAESKey)
|
||||||
message_data['picurl'] = base64 # 只保留第一个 image
|
message_data['picurl'] = base64 # 只保留第一个 image
|
||||||
@@ -466,7 +463,9 @@ class WecomBotClient:
|
|||||||
# Extract user information
|
# Extract user information
|
||||||
from_info = msg_json.get('from', {})
|
from_info = msg_json.get('from', {})
|
||||||
message_data['userid'] = from_info.get('userid', '')
|
message_data['userid'] = from_info.get('userid', '')
|
||||||
message_data['username'] = from_info.get('alias', '') or from_info.get('name', '') or from_info.get('userid', '')
|
message_data['username'] = (
|
||||||
|
from_info.get('alias', '') or from_info.get('name', '') or from_info.get('userid', '')
|
||||||
|
)
|
||||||
|
|
||||||
# Extract chat/group information
|
# Extract chat/group information
|
||||||
if msg_json.get('chattype', '') == 'group':
|
if msg_json.get('chattype', '') == 'group':
|
||||||
@@ -555,7 +554,7 @@ class WecomBotClient:
|
|||||||
|
|
||||||
encrypted_bytes = response.content
|
encrypted_bytes = response.content
|
||||||
|
|
||||||
aes_key = base64.b64decode(encoding_aes_key + "=") # base64 补齐
|
aes_key = base64.b64decode(encoding_aes_key + '=') # base64 补齐
|
||||||
iv = aes_key[:16]
|
iv = aes_key[:16]
|
||||||
|
|
||||||
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
|
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
|
||||||
@@ -564,22 +563,22 @@ class WecomBotClient:
|
|||||||
pad_len = decrypted[-1]
|
pad_len = decrypted[-1]
|
||||||
decrypted = decrypted[:-pad_len]
|
decrypted = decrypted[:-pad_len]
|
||||||
|
|
||||||
if decrypted.startswith(b"\xff\xd8"): # JPEG
|
if decrypted.startswith(b'\xff\xd8'): # JPEG
|
||||||
mime_type = "image/jpeg"
|
mime_type = 'image/jpeg'
|
||||||
elif decrypted.startswith(b"\x89PNG"): # PNG
|
elif decrypted.startswith(b'\x89PNG'): # PNG
|
||||||
mime_type = "image/png"
|
mime_type = 'image/png'
|
||||||
elif decrypted.startswith((b"GIF87a", b"GIF89a")): # GIF
|
elif decrypted.startswith((b'GIF87a', b'GIF89a')): # GIF
|
||||||
mime_type = "image/gif"
|
mime_type = 'image/gif'
|
||||||
elif decrypted.startswith(b"BM"): # BMP
|
elif decrypted.startswith(b'BM'): # BMP
|
||||||
mime_type = "image/bmp"
|
mime_type = 'image/bmp'
|
||||||
elif decrypted.startswith(b"II*\x00") or decrypted.startswith(b"MM\x00*"): # TIFF
|
elif decrypted.startswith(b'II*\x00') or decrypted.startswith(b'MM\x00*'): # TIFF
|
||||||
mime_type = "image/tiff"
|
mime_type = 'image/tiff'
|
||||||
else:
|
else:
|
||||||
mime_type = "application/octet-stream"
|
mime_type = 'application/octet-stream'
|
||||||
|
|
||||||
# 转 base64
|
# 转 base64
|
||||||
base64_str = base64.b64encode(decrypted).decode("utf-8")
|
base64_str = base64.b64encode(decrypted).decode('utf-8')
|
||||||
return f"data:{mime_type};base64,{base64_str}"
|
return f'data:{mime_type};base64,{base64_str}'
|
||||||
|
|
||||||
async def run_task(self, host: str, port: int, *args, **kwargs):
|
async def run_task(self, host: str, port: int, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -29,7 +29,12 @@ class WecomBotEvent(dict):
|
|||||||
"""
|
"""
|
||||||
用户名称
|
用户名称
|
||||||
"""
|
"""
|
||||||
return self.get('username', '') or self.get('from', {}).get('alias', '') or self.get('from', {}).get('name', '') or self.userid
|
return (
|
||||||
|
self.get('username', '')
|
||||||
|
or self.get('from', {}).get('alias', '')
|
||||||
|
or self.get('from', {}).get('name', '')
|
||||||
|
or self.userid
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def chatname(self) -> str:
|
def chatname(self) -> str:
|
||||||
@@ -340,4 +340,3 @@ class WecomClient:
|
|||||||
async def get_media_id(self, image: platform_message.Image):
|
async def get_media_id(self, image: platform_message.Image):
|
||||||
media_id = await self.upload_to_work(image=image)
|
media_id = await self.upload_to_work(image=image)
|
||||||
return media_id
|
return media_id
|
||||||
|
|
||||||
@@ -124,7 +124,9 @@ class RouterGroup(abc.ABC):
|
|||||||
token = quart.request.headers.get('Authorization', '').replace('Bearer ', '')
|
token = quart.request.headers.get('Authorization', '').replace('Bearer ', '')
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
return self.http_status(401, -1, 'No valid authentication provided (user token or API key required)')
|
return self.http_status(
|
||||||
|
401, -1, 'No valid authentication provided (user token or API key required)'
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user_email = await self.ap.user_service.verify_jwt_token(token)
|
user_email = await self.ap.user_service.verify_jwt_token(token)
|
||||||
@@ -27,7 +27,9 @@ class PipelinesRouterGroup(group.RouterGroup):
|
|||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
return self.success(data={'configs': await self.ap.pipeline_service.get_pipeline_metadata()})
|
return self.success(data={'configs': await self.ap.pipeline_service.get_pipeline_metadata()})
|
||||||
|
|
||||||
@self.route('/<pipeline_uuid>', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
|
@self.route(
|
||||||
|
'/<pipeline_uuid>', methods=['GET', 'PUT', 'DELETE'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY
|
||||||
|
)
|
||||||
async def _(pipeline_uuid: str) -> str:
|
async def _(pipeline_uuid: str) -> str:
|
||||||
if quart.request.method == 'GET':
|
if quart.request.method == 'GET':
|
||||||
pipeline = await self.ap.pipeline_service.get_pipeline(pipeline_uuid)
|
pipeline = await self.ap.pipeline_service.get_pipeline(pipeline_uuid)
|
||||||
@@ -47,7 +49,9 @@ class PipelinesRouterGroup(group.RouterGroup):
|
|||||||
|
|
||||||
return self.success()
|
return self.success()
|
||||||
|
|
||||||
@self.route('/<pipeline_uuid>/extensions', methods=['GET', 'PUT'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY)
|
@self.route(
|
||||||
|
'/<pipeline_uuid>/extensions', methods=['GET', 'PUT'], auth_type=group.AuthType.USER_TOKEN_OR_API_KEY
|
||||||
|
)
|
||||||
async def _(pipeline_uuid: str) -> str:
|
async def _(pipeline_uuid: str) -> str:
|
||||||
if quart.request.method == 'GET':
|
if quart.request.method == 'GET':
|
||||||
# Get current extensions and available plugins
|
# Get current extensions and available plugins
|
||||||
@@ -61,9 +61,7 @@ class ApiKeyService:
|
|||||||
|
|
||||||
async def delete_api_key(self, key_id: int) -> None:
|
async def delete_api_key(self, key_id: int) -> None:
|
||||||
"""Delete an API key"""
|
"""Delete an API key"""
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(sqlalchemy.delete(apikey.ApiKey).where(apikey.ApiKey.id == key_id))
|
||||||
sqlalchemy.delete(apikey.ApiKey).where(apikey.ApiKey.id == key_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_api_key(self, key_id: int, name: str = None, description: str = None) -> None:
|
async def update_api_key(self, key_id: int, name: str = None, description: str = None) -> None:
|
||||||
"""Update an API key's metadata (name, description)"""
|
"""Update an API key's metadata (name, description)"""
|
||||||
@@ -84,8 +84,6 @@ class MCPService:
|
|||||||
new_enable = server_data.get('enable', False)
|
new_enable = server_data.get('enable', False)
|
||||||
|
|
||||||
need_remove = old_server_name and old_server_name in self.ap.tool_mgr.mcp_tool_loader.sessions
|
need_remove = old_server_name and old_server_name in self.ap.tool_mgr.mcp_tool_loader.sessions
|
||||||
need_start = new_enable
|
|
||||||
|
|
||||||
|
|
||||||
if old_enable and not new_enable:
|
if old_enable and not new_enable:
|
||||||
if need_remove:
|
if need_remove:
|
||||||
@@ -113,7 +111,6 @@ class MCPService:
|
|||||||
task = asyncio.create_task(self.ap.tool_mgr.mcp_tool_loader.host_mcp_server(server_config))
|
task = asyncio.create_task(self.ap.tool_mgr.mcp_tool_loader.host_mcp_server(server_config))
|
||||||
self.ap.tool_mgr.mcp_tool_loader._hosted_mcp_tasks.append(task)
|
self.ap.tool_mgr.mcp_tool_loader._hosted_mcp_tasks.append(task)
|
||||||
|
|
||||||
|
|
||||||
async def delete_mcp_server(self, server_uuid: str) -> None:
|
async def delete_mcp_server(self, server_uuid: str) -> None:
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.select(persistence_mcp.MCPServer).where(persistence_mcp.MCPServer.uuid == server_uuid)
|
sqlalchemy.select(persistence_mcp.MCPServer).where(persistence_mcp.MCPServer.uuid == server_uuid)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user