Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1383690574 | ||
|
|
e29eced691 | ||
|
|
31581e24a9 | ||
|
|
6d3e07ce35 | ||
|
|
2b41112631 | ||
|
|
008dc02f25 | ||
|
|
a5c80c7c87 | ||
|
|
1161fa9227 | ||
|
|
99bd869204 | ||
|
|
830b96fec5 | ||
|
|
9cdbed4602 |
15
.drone.yml
15
.drone.yml
@ -1,18 +1,17 @@
|
||||
# Drone CI/CD 配置文件
|
||||
kind: pipeline
|
||||
type: docker # 使用 Docker 作为执行环境
|
||||
name: imgsearcher-pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
# 步骤1: 构建 Python 应用
|
||||
- name: build_imgsearcher
|
||||
image: ccr-29eug8s3-vpc.cnc.bj.baidubce.com/service/python:3.9-slim
|
||||
image: python:3.9-slim
|
||||
commands:
|
||||
- pip install poetry
|
||||
- poetry config virtualenvs.create false
|
||||
- poetry install --no-dev
|
||||
- pip install -r requirements.txt
|
||||
- mkdir -p build/imgsearcher
|
||||
- cp -r app.py app pyproject.toml poetry.lock Dockerfile build/imgsearcher/
|
||||
- cp -r app.py app requirements.txt Dockerfile build/imgsearcher/
|
||||
- pip install -r requirements.txt
|
||||
when:
|
||||
branch:
|
||||
- dev
|
||||
@ -51,8 +50,8 @@ steps:
|
||||
from_secret: kubernetes_cert_dev
|
||||
namespace:
|
||||
from_secret: kubernetes_namespace_dev
|
||||
deployment: imgsearcher
|
||||
container: imgsearcher
|
||||
deployment: imgsearcher-api
|
||||
container: imgsearcher-api
|
||||
repo: ccr-29eug8s3-vpc.cnc.bj.baidubce.com/service/imgsearcher
|
||||
tag: ${DRONE_COMMIT_SHA}
|
||||
when:
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@ -2,21 +2,12 @@ FROM python:3.9-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装Poetry
|
||||
RUN pip install --no-cache-dir poetry
|
||||
|
||||
# 配置Poetry不创建虚拟环境
|
||||
RUN poetry config virtualenvs.create false
|
||||
|
||||
# 复制Poetry配置文件
|
||||
COPY pyproject.toml poetry.lock* ./
|
||||
|
||||
# 安装依赖
|
||||
RUN poetry install --no-dev --no-interaction --no-ansi
|
||||
|
||||
# 复制应用程序代码
|
||||
COPY . .
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 创建上传目录
|
||||
RUN mkdir -p uploads
|
||||
|
||||
|
||||
306
app.py
306
app.py
@ -4,13 +4,15 @@
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, session
|
||||
import requests
|
||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, session, Response, stream_with_context
|
||||
from flask_cors import CORS
|
||||
from werkzeug.utils import secure_filename
|
||||
from app.api.baidu_image_search import BaiduImageSearch
|
||||
from app.api.image_utils import ImageUtils
|
||||
from app.api.azure_openai import AzureOpenAI
|
||||
from app.api.type_manager_mongo import TypeManagerMongo
|
||||
from app.api.robot_manager import RobotManager
|
||||
|
||||
app = Flask(__name__, template_folder='app/templates', static_folder='app/static')
|
||||
CORS(app) # 启用CORS支持跨域请求
|
||||
@ -40,16 +42,19 @@ except ValueError as e:
|
||||
# 初始化类型管理器
|
||||
type_manager = TypeManagerMongo()
|
||||
|
||||
# 初始化机器人角色管理器
|
||||
robot_manager = RobotManager()
|
||||
|
||||
def allowed_file(filename):
|
||||
"""检查文件扩展名是否允许"""
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/imgsearcherApi/')
|
||||
def index():
|
||||
"""首页"""
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/upload', methods=['POST'])
|
||||
@app.route('/imgsearcherApi/upload', methods=['POST'])
|
||||
def upload_image():
|
||||
"""上传图片到图库"""
|
||||
if 'file' not in request.files:
|
||||
@ -97,7 +102,7 @@ def upload_image():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/search', methods=['POST'])
|
||||
@app.route('/imgsearcherApi/search', methods=['POST'])
|
||||
def search_image():
|
||||
"""搜索相似图片"""
|
||||
if 'file' not in request.files:
|
||||
@ -152,7 +157,7 @@ def search_image():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/delete', methods=['POST'])
|
||||
@app.route('/imgsearcherApi/delete', methods=['POST'])
|
||||
def delete_image():
|
||||
"""删除图库中的图片"""
|
||||
data = request.get_json()
|
||||
@ -169,7 +174,7 @@ def delete_image():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/update', methods=['POST'])
|
||||
@app.route('/imgsearcherApi/update', methods=['POST'])
|
||||
def update_image():
|
||||
"""更新图库中的图片信息"""
|
||||
data = request.get_json()
|
||||
@ -204,7 +209,7 @@ def update_image():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/token')
|
||||
@app.route('/imgsearcherApi/api/token')
|
||||
def get_token():
|
||||
"""获取API访问令牌"""
|
||||
try:
|
||||
@ -213,12 +218,12 @@ def get_token():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/chat-with-image', methods=['GET'])
|
||||
@app.route('/imgsearcherApi/chat-with-image', methods=['GET'])
|
||||
def chat_with_image_page():
|
||||
"""图片对话页面"""
|
||||
return render_template('chat.html')
|
||||
|
||||
@app.route('/api/upload-chat-image', methods=['POST'])
|
||||
@app.route('/imgsearcherApi/api/upload-chat-image', methods=['POST'])
|
||||
def upload_chat_image():
|
||||
"""上传图片用于对话"""
|
||||
if 'file' not in request.files:
|
||||
@ -231,6 +236,9 @@ def upload_chat_image():
|
||||
if not allowed_file(file.filename):
|
||||
return jsonify({'error': '不支持的文件类型'}), 400
|
||||
|
||||
# 获取选择的机器人角色ID
|
||||
robot_id = request.form.get('robot_id', '')
|
||||
|
||||
# 保存文件
|
||||
filename = secure_filename(file.filename)
|
||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||
@ -239,6 +247,9 @@ def upload_chat_image():
|
||||
# 存储图片路径到会话
|
||||
session['chat_image_path'] = file_path
|
||||
|
||||
# 存储机器人角色ID到会话
|
||||
session['robot_id'] = robot_id
|
||||
|
||||
# 清空对话历史
|
||||
session['conversation_history'] = []
|
||||
|
||||
@ -269,16 +280,27 @@ def upload_chat_image():
|
||||
except:
|
||||
continue
|
||||
|
||||
# 获取机器人角色信息
|
||||
robot_info = None
|
||||
if robot_id:
|
||||
robot = robot_manager.get_robot(robot_id)
|
||||
if robot:
|
||||
robot_info = {
|
||||
'name': robot.get('name', ''),
|
||||
'background': robot.get('background', '')
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'image_path': file_path,
|
||||
'image_type': image_type,
|
||||
'description': description
|
||||
'description': description,
|
||||
'robot': robot_info
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/chat', methods=['POST'])
|
||||
@app.route('/imgsearcherApi/api/chat', methods=['POST'])
|
||||
def chat():
|
||||
"""与图片进行对话"""
|
||||
if not azure_openai_api:
|
||||
@ -290,6 +312,7 @@ def chat():
|
||||
|
||||
message = data['message']
|
||||
image_path = session.get('chat_image_path')
|
||||
robot_id = session.get('robot_id', '')
|
||||
|
||||
if not image_path or not os.path.exists(image_path):
|
||||
return jsonify({'error': '没有上传图片或图片已失效'}), 400
|
||||
@ -297,12 +320,23 @@ def chat():
|
||||
# 获取对话历史
|
||||
conversation_history = session.get('conversation_history', [])
|
||||
|
||||
# 获取机器人角色信息
|
||||
robot_info = None
|
||||
if robot_id:
|
||||
robot = robot_manager.get_robot(robot_id)
|
||||
if robot:
|
||||
robot_info = {
|
||||
'name': robot.get('name', ''),
|
||||
'background': robot.get('background', '')
|
||||
}
|
||||
|
||||
try:
|
||||
# 调用Azure OpenAI API进行对话
|
||||
response = azure_openai_api.chat_with_image(
|
||||
image_path=image_path,
|
||||
message=message,
|
||||
conversation_history=conversation_history
|
||||
conversation_history=conversation_history,
|
||||
robot_info=robot_info
|
||||
)
|
||||
|
||||
# 提取回复内容
|
||||
@ -320,5 +354,253 @@ def chat():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/imgsearcherApi/robots', methods=['GET'])
|
||||
def robot_page():
|
||||
"""机器人角色管理页面"""
|
||||
return render_template('robots.html')
|
||||
|
||||
@app.route('/imgsearcherApi/api/robots', methods=['GET'])
|
||||
def get_robots():
|
||||
"""获取所有机器人角色"""
|
||||
try:
|
||||
robots = robot_manager.get_all_robots()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'robots': robots
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/imgsearcherApi/api/robots', methods=['POST'])
|
||||
def add_robot():
|
||||
"""添加新的机器人角色"""
|
||||
try:
|
||||
name = request.form.get('name')
|
||||
background = request.form.get('background')
|
||||
|
||||
if not name or not background:
|
||||
return jsonify({'error': '机器人名称和背景故事不能为空'}), 400
|
||||
|
||||
avatar_file = None
|
||||
if 'avatar' in request.files:
|
||||
avatar_file = request.files['avatar']
|
||||
if avatar_file.filename == '':
|
||||
avatar_file = None
|
||||
elif not allowed_file(avatar_file.filename):
|
||||
return jsonify({'error': '不支持的文件类型'}), 400
|
||||
|
||||
result = robot_manager.add_robot(name, background, avatar_file)
|
||||
|
||||
if 'error' in result:
|
||||
return jsonify(result), 400
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'robot': result
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/imgsearcherApi/api/robots/<robot_id>', methods=['GET'])
|
||||
def get_robot(robot_id):
|
||||
"""获取指定机器人角色"""
|
||||
try:
|
||||
robot = robot_manager.get_robot(robot_id)
|
||||
if not robot:
|
||||
return jsonify({'error': '找不到该机器人'}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'robot': robot
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/imgsearcherApi/api/robots/<robot_id>', methods=['PUT'])
|
||||
def update_robot(robot_id):
|
||||
"""更新机器人角色"""
|
||||
try:
|
||||
name = request.form.get('name')
|
||||
background = request.form.get('background')
|
||||
|
||||
avatar_file = None
|
||||
if 'avatar' in request.files:
|
||||
avatar_file = request.files['avatar']
|
||||
if avatar_file.filename == '':
|
||||
avatar_file = None
|
||||
elif not allowed_file(avatar_file.filename):
|
||||
return jsonify({'error': '不支持的文件类型'}), 400
|
||||
|
||||
result = robot_manager.update_robot(robot_id, name, background, avatar_file)
|
||||
|
||||
if 'error' in result:
|
||||
return jsonify(result), 400
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'robot': result
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/imgsearcherApi/api/robots/<robot_id>', methods=['DELETE'])
|
||||
def delete_robot(robot_id):
|
||||
"""删除机器人角色"""
|
||||
try:
|
||||
success = robot_manager.delete_robot(robot_id)
|
||||
if not success:
|
||||
return jsonify({'error': '找不到该机器人或删除失败'}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# ==================== Chat Stream ====================
|
||||
@app.route('/imgsearcherApi/api/chat-stream', methods=['POST'])
|
||||
def chat_stream():
|
||||
"""流式返回文本增量 + 最终语音 (SSE)"""
|
||||
if not azure_openai_api:
|
||||
return jsonify({'error': 'Azure OpenAI API未正确配置'}), 500
|
||||
|
||||
data = request.get_json()
|
||||
if not data or 'message' not in data:
|
||||
return jsonify({'error': '缺少消息内容'}), 400
|
||||
|
||||
message = data['message']
|
||||
image_path = session.get('chat_image_path')
|
||||
robot_id = session.get('robot_id', '')
|
||||
if not image_path or not os.path.exists(image_path):
|
||||
return jsonify({'error': '没有上传图片或图片已失效'}), 400
|
||||
|
||||
conversation_history = session.get('conversation_history', [])
|
||||
robot_info = None
|
||||
if robot_id:
|
||||
robot = robot_manager.get_robot(robot_id)
|
||||
if robot:
|
||||
robot_info = {'name': robot.get('name', ''), 'background': robot.get('background', '')}
|
||||
|
||||
# 语音音色选择
|
||||
voice_wav = data.get('voice') or 'zh-CN-XiaoXiao-Assistant-Audio.wav'
|
||||
# 与Azure流式对话
|
||||
openai_resp = azure_openai_api.chat_with_image_stream(
|
||||
image_path=image_path,
|
||||
message=message,
|
||||
conversation_history=conversation_history,
|
||||
robot_info=robot_info
|
||||
)
|
||||
|
||||
def event_stream():
|
||||
accumulated_text = ""
|
||||
pending = ""
|
||||
punctuation = "。!?.!?"
|
||||
def send_tts(sentence):
|
||||
if not sentence.strip():
|
||||
return
|
||||
tts_params = {
|
||||
'tts_text': sentence,
|
||||
'prompt_wav': voice_wav,
|
||||
'text_split_method': 'cut5'
|
||||
}
|
||||
try:
|
||||
tts_resp = requests.get(
|
||||
"https://cloud.infini-ai.com/AIStudio/v1/inference/api/te-c7zfd4hrdoj2vqmw/tts/CosyVoice/v1/zero_shot",
|
||||
params=tts_params,
|
||||
headers={'Authorization': f'Bearer {os.getenv("INFINI_API_KEY", "sk-daooolufaf7ienn6")}'},
|
||||
stream=True,
|
||||
timeout=30
|
||||
)
|
||||
if tts_resp.status_code == 200:
|
||||
audio_bytes = bytearray()
|
||||
for chunk in tts_resp.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
audio_bytes.extend(chunk)
|
||||
if audio_bytes:
|
||||
b64_audio = base64.b64encode(audio_bytes).decode()
|
||||
yield f"data: {{\"type\": \"audio\", \"content\": \"{b64_audio}\" }}\n\n"
|
||||
except Exception as e:
|
||||
print("TTS 调用失败", e)
|
||||
# 读取 GPT 流
|
||||
for line in openai_resp.iter_lines():
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
if line.strip() == b'data: [DONE]':
|
||||
break
|
||||
if line.startswith(b'data:'):
|
||||
content_json = json.loads(line[5:].strip())
|
||||
choices = content_json.get('choices', [])
|
||||
if not choices:
|
||||
continue
|
||||
delta = choices[0].get('delta', {}).get('content', '')
|
||||
if not delta:
|
||||
continue
|
||||
accumulated_text += delta
|
||||
pending += delta
|
||||
# 立即发送文本增量
|
||||
yield f"data: {{\"type\": \"text\", \"content\": {json.dumps(delta, ensure_ascii=False)} }}\n\n"
|
||||
# 检测标点
|
||||
while True:
|
||||
idx = -1
|
||||
for i, ch in enumerate(pending):
|
||||
if ch in punctuation:
|
||||
idx = i
|
||||
break
|
||||
if idx == -1:
|
||||
break
|
||||
sentence = pending[:idx+1]
|
||||
pending = pending[idx+1:]
|
||||
# 调用 TTS 并流式发送
|
||||
for audio_evt in send_tts(sentence):
|
||||
yield audio_evt
|
||||
except Exception as e:
|
||||
print("解析OpenAI流错误", e)
|
||||
continue
|
||||
# 处理剩余 pending
|
||||
if pending.strip():
|
||||
for audio_evt in send_tts(pending):
|
||||
yield audio_evt
|
||||
pending = ""
|
||||
# 更新会话历史
|
||||
conversation_history.append({"role": "user", "content": message})
|
||||
conversation_history.append({"role": "assistant", "content": accumulated_text})
|
||||
session['conversation_history'] = conversation_history
|
||||
yield "event: end\ndata: {}\n\n"
|
||||
|
||||
# 保持请求上下文,避免在生成器中操作 session 报错
|
||||
return Response(stream_with_context(event_stream()), content_type='text/event-stream')
|
||||
|
||||
# ==================== TTS ====================
|
||||
@app.route('/imgsearcherApi/api/tts', methods=['POST'])
|
||||
def tts():
|
||||
"""文本转语音接口,返回音频数据流"""
|
||||
data = request.get_json()
|
||||
if not data or 'text' not in data:
|
||||
return jsonify({'error': '缺少text参数'}), 400
|
||||
|
||||
text = data['text']
|
||||
prompt_text = data.get('prompt_text') or "我是威震天,我只代表月亮消灭你"
|
||||
prompt_wav = data.get('voice') or data.get('prompt_wav') or "zh-CN-XiaoXiao-Assistant-Audio.wav"
|
||||
|
||||
tts_base_url = "https://cloud.infini-ai.com/AIStudio/v1/inference/api/te-c7zfd4hrdoj2vqmw/tts/CosyVoice/v1/zero_shot"
|
||||
api_key = os.getenv('INFINI_API_KEY', 'sk-daooolufaf7ienn6')
|
||||
headers = {'Authorization': f'Bearer {api_key}'}
|
||||
params = {
|
||||
'tts_text': text,
|
||||
'prompt_wav': prompt_wav,
|
||||
'text_split_method': 'cut5'
|
||||
}
|
||||
|
||||
try:
|
||||
r = requests.get(tts_base_url, params=params, headers=headers, timeout=20)
|
||||
if r.status_code == 200:
|
||||
return Response(r.content, content_type=r.headers.get('Content-Type', 'audio/wav'))
|
||||
else:
|
||||
print(f"TTS remote error {r.status_code}: {r.text}")
|
||||
return jsonify({'error': f'TTS服务错误: {r.text}'}), 502
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'TTS请求失败: {str(e)}'}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5001)
|
||||
|
||||
@ -85,7 +85,7 @@ class AzureOpenAI:
|
||||
print(f"获取图片类型和描述失败: {str(e)}")
|
||||
return "", ""
|
||||
|
||||
def chat_with_image(self, image_path, message, conversation_history=None):
|
||||
def chat_with_image(self, image_path, message, conversation_history=None, robot_info=None):
|
||||
"""
|
||||
使用图片和消息与GPT-4o多模态模型进行对话
|
||||
|
||||
@ -93,6 +93,7 @@ class AzureOpenAI:
|
||||
image_path: 图片路径
|
||||
message: 用户消息
|
||||
conversation_history: 对话历史记录
|
||||
robot_info: 机器人角色信息,包含name和background
|
||||
|
||||
Returns:
|
||||
dict: 模型响应
|
||||
@ -107,7 +108,14 @@ class AzureOpenAI:
|
||||
base64_image = self._encode_image(image_path)
|
||||
|
||||
# 构建系统提示
|
||||
system_message = "你是一个智能助手,能够分析图片并回答问题。"
|
||||
if robot_info and robot_info.get('name') and robot_info.get('background'):
|
||||
# 如果有机器人角色信息,使用机器人角色
|
||||
system_message = f"你是{robot_info['name']},一个能够分析图片并回答问题的角色。\n\n你的背景故事:{robot_info['background']}\n\n在对话中,你应该始终保持这个角色的身份和特点,用第一人称回答问题。"
|
||||
else:
|
||||
# 默认智能助手
|
||||
system_message = "你是一个智能助手,能够分析图片并回答问题。"
|
||||
|
||||
# 添加图片类型和描述信息
|
||||
if image_type and description:
|
||||
system_message += f"\n\n这是一张{image_type}的图片。\n描述信息:{description}\n\n请基于这些信息和图片内容回答用户的问题。"
|
||||
|
||||
@ -164,3 +172,39 @@ class AzureOpenAI:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"Azure OpenAI API请求失败: {response.text}")
|
||||
|
||||
def chat_with_image_stream(self, image_path, message, conversation_history=None, robot_info=None):
|
||||
"""流式输出 GPT-4o 回复,返回 requests.Response 对象 (stream=True)"""
|
||||
if conversation_history is None:
|
||||
conversation_history = []
|
||||
image_type, description = self._get_image_type_description(image_path)
|
||||
base64_image = self._encode_image(image_path)
|
||||
|
||||
if robot_info and robot_info.get('name') and robot_info.get('background'):
|
||||
system_message = f"你是{robot_info['name']},一个能够分析图片并回答问题的角色。\n\n你的背景故事:{robot_info['background']}\n\n在对话中,你应该始终保持这个角色的身份和特点,用第一人称回答问题。"
|
||||
else:
|
||||
system_message = "你是一个智能助手,能够分析图片并回答问题。"
|
||||
if image_type and description:
|
||||
system_message += f"\n\n这是一张{image_type}的图片。\n描述信息:{description}\n\n请基于这些信息和图片内容回答用户的问题。"
|
||||
|
||||
messages = [{"role": "system", "content": system_message}]
|
||||
for msg in conversation_history:
|
||||
messages.append(msg)
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": message},
|
||||
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
|
||||
]
|
||||
})
|
||||
|
||||
headers = {"Content-Type": "application/json", "api-key": self.api_key}
|
||||
payload = {
|
||||
"messages": messages,
|
||||
"max_tokens": 2000,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.95,
|
||||
"stream": True
|
||||
}
|
||||
url = f"{self.endpoint}/openai/deployments/{self.deployment_name}/chat/completions?api-version={self.api_version}"
|
||||
return requests.post(url, headers=headers, json=payload, stream=True)
|
||||
|
||||
217
app/api/robot_manager.py
Normal file
217
app/api/robot_manager.py
Normal file
@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import datetime
|
||||
from pymongo import MongoClient
|
||||
from dotenv import load_dotenv
|
||||
import base64
|
||||
import uuid
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
class RobotManager:
|
||||
"""管理机器人角色的类"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化MongoDB连接"""
|
||||
# 从环境变量获取MongoDB连接信息
|
||||
mongo_uri = os.getenv('MONGO_URI', 'mongodb://localhost:27017/')
|
||||
db_name = os.getenv('MONGO_DB_NAME', 'imgsearcher')
|
||||
|
||||
# 连接MongoDB
|
||||
self.client = MongoClient(mongo_uri)
|
||||
self.db = self.client[db_name]
|
||||
self.collection = self.db['robot_characters']
|
||||
|
||||
# 确保有索引以提高查询性能
|
||||
self.collection.create_index('robot_id')
|
||||
self.collection.create_index('name')
|
||||
|
||||
# 上传文件夹路径
|
||||
self.upload_folder = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'uploads')
|
||||
if not os.path.exists(self.upload_folder):
|
||||
os.makedirs(self.upload_folder)
|
||||
|
||||
print(f"MongoDB RobotManager 初始化完成,连接到 {db_name}")
|
||||
|
||||
def add_robot(self, name, background, avatar_file=None):
|
||||
"""
|
||||
添加新的机器人角色
|
||||
|
||||
Args:
|
||||
name: 机器人名称
|
||||
background: 机器人背景故事
|
||||
avatar_file: 机器人头像文件(可选)
|
||||
|
||||
Returns:
|
||||
dict: 包含新添加的机器人信息
|
||||
"""
|
||||
if not name or not background:
|
||||
return {"error": "机器人名称和背景故事不能为空"}
|
||||
|
||||
# 检查是否已存在同名机器人
|
||||
if self.collection.find_one({"name": name}):
|
||||
return {"error": f"已存在名为 '{name}' 的机器人"}
|
||||
|
||||
# 生成唯一ID
|
||||
robot_id = str(uuid.uuid4())
|
||||
|
||||
# 处理头像文件
|
||||
avatar_path = None
|
||||
if avatar_file:
|
||||
# 保存头像文件
|
||||
filename = f"robot_avatar_{robot_id}.{avatar_file.filename.split('.')[-1]}"
|
||||
file_path = os.path.join(self.upload_folder, filename)
|
||||
avatar_file.save(file_path)
|
||||
avatar_path = file_path
|
||||
|
||||
# 创建机器人记录
|
||||
robot = {
|
||||
"robot_id": robot_id,
|
||||
"name": name,
|
||||
"background": background,
|
||||
"avatar_path": avatar_path,
|
||||
"created_at": datetime.datetime.now()
|
||||
}
|
||||
|
||||
# 保存到数据库
|
||||
self.collection.insert_one(robot)
|
||||
|
||||
# 返回新添加的机器人信息(不包含MongoDB的_id字段)
|
||||
robot.pop("_id", None)
|
||||
return robot
|
||||
|
||||
def get_robot(self, robot_id):
|
||||
"""
|
||||
获取指定ID的机器人信息
|
||||
|
||||
Args:
|
||||
robot_id: 机器人ID
|
||||
|
||||
Returns:
|
||||
dict: 机器人信息,如果不存在则返回None
|
||||
"""
|
||||
robot = self.collection.find_one({"robot_id": robot_id})
|
||||
if robot:
|
||||
# 转换MongoDB的_id为字符串
|
||||
robot["_id"] = str(robot["_id"])
|
||||
|
||||
# 如果有头像,添加base64编码的头像数据
|
||||
if robot.get("avatar_path") and os.path.exists(robot["avatar_path"]):
|
||||
with open(robot["avatar_path"], "rb") as f:
|
||||
avatar_data = base64.b64encode(f.read()).decode('utf-8')
|
||||
robot["avatar_data"] = avatar_data
|
||||
|
||||
return robot
|
||||
return None
|
||||
|
||||
def update_robot(self, robot_id, name=None, background=None, avatar_file=None):
|
||||
"""
|
||||
更新机器人信息
|
||||
|
||||
Args:
|
||||
robot_id: 机器人ID
|
||||
name: 新的机器人名称(可选)
|
||||
background: 新的背景故事(可选)
|
||||
avatar_file: 新的头像文件(可选)
|
||||
|
||||
Returns:
|
||||
dict: 更新后的机器人信息
|
||||
"""
|
||||
# 获取现有机器人
|
||||
robot = self.collection.find_one({"robot_id": robot_id})
|
||||
if not robot:
|
||||
return {"error": f"找不到ID为 '{robot_id}' 的机器人"}
|
||||
|
||||
# 准备更新数据
|
||||
update_data = {}
|
||||
|
||||
if name:
|
||||
# 检查新名称是否与其他机器人冲突
|
||||
existing = self.collection.find_one({"name": name, "robot_id": {"$ne": robot_id}})
|
||||
if existing:
|
||||
return {"error": f"已存在名为 '{name}' 的机器人"}
|
||||
update_data["name"] = name
|
||||
|
||||
if background:
|
||||
update_data["background"] = background
|
||||
|
||||
# 处理新头像
|
||||
if avatar_file:
|
||||
# 删除旧头像
|
||||
old_avatar_path = robot.get("avatar_path")
|
||||
if old_avatar_path and os.path.exists(old_avatar_path):
|
||||
try:
|
||||
os.remove(old_avatar_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 保存新头像
|
||||
filename = f"robot_avatar_{robot_id}.{avatar_file.filename.split('.')[-1]}"
|
||||
file_path = os.path.join(self.upload_folder, filename)
|
||||
avatar_file.save(file_path)
|
||||
update_data["avatar_path"] = file_path
|
||||
|
||||
# 更新数据库
|
||||
if update_data:
|
||||
update_data["updated_at"] = datetime.datetime.now()
|
||||
self.collection.update_one({"robot_id": robot_id}, {"$set": update_data})
|
||||
|
||||
# 返回更新后的机器人信息
|
||||
return self.get_robot(robot_id)
|
||||
|
||||
def delete_robot(self, robot_id):
|
||||
"""
|
||||
删除机器人
|
||||
|
||||
Args:
|
||||
robot_id: 机器人ID
|
||||
|
||||
Returns:
|
||||
bool: 是否成功删除
|
||||
"""
|
||||
# 获取机器人信息
|
||||
robot = self.collection.find_one({"robot_id": robot_id})
|
||||
if not robot:
|
||||
return False
|
||||
|
||||
# 删除头像文件
|
||||
avatar_path = robot.get("avatar_path")
|
||||
if avatar_path and os.path.exists(avatar_path):
|
||||
try:
|
||||
os.remove(avatar_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 从数据库中删除
|
||||
result = self.collection.delete_one({"robot_id": robot_id})
|
||||
return result.deleted_count > 0
|
||||
|
||||
def get_all_robots(self):
|
||||
"""
|
||||
获取所有机器人列表
|
||||
|
||||
Returns:
|
||||
list: 机器人信息列表
|
||||
"""
|
||||
robots = []
|
||||
for robot in self.collection.find():
|
||||
# 转换MongoDB的_id为字符串
|
||||
robot["_id"] = str(robot["_id"])
|
||||
|
||||
# 如果有头像,添加base64编码的头像数据
|
||||
if robot.get("avatar_path") and os.path.exists(robot["avatar_path"]):
|
||||
with open(robot["avatar_path"], "rb") as f:
|
||||
avatar_data = base64.b64encode(f.read()).decode('utf-8')
|
||||
robot["avatar_data"] = avatar_data
|
||||
|
||||
robots.append(robot)
|
||||
|
||||
return robots
|
||||
|
||||
def close(self):
|
||||
"""关闭MongoDB连接"""
|
||||
if self.client:
|
||||
self.client.close()
|
||||
@ -1,5 +1,35 @@
|
||||
// 页面加载完成后执行
|
||||
|
||||
// ====== 音频队列处理 ======
|
||||
const audioQueue = [];
|
||||
let audioPlaying = false;
|
||||
|
||||
function enqueueAudio(blob) {
|
||||
audioQueue.push(blob);
|
||||
playNextAudio();
|
||||
}
|
||||
|
||||
function playNextAudio() {
|
||||
if (audioPlaying || audioQueue.length === 0) return;
|
||||
const blob = audioQueue.shift();
|
||||
const audio = new Audio(URL.createObjectURL(blob));
|
||||
audioPlaying = true;
|
||||
audio.addEventListener('ended', () => {
|
||||
audioPlaying = false;
|
||||
playNextAudio();
|
||||
});
|
||||
audio.play();
|
||||
}
|
||||
|
||||
// 顺序播放一组音频片段(用于重放)
|
||||
function playAudioSegments(segments) {
|
||||
segments.forEach(seg => enqueueAudio(seg));
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 加载机器人角色列表
|
||||
loadRobotCharacters();
|
||||
|
||||
// 初始化图片上传表单
|
||||
initUploadImageForm();
|
||||
|
||||
@ -7,6 +37,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
initChatForm();
|
||||
});
|
||||
|
||||
// 加载机器人角色列表
|
||||
async function loadRobotCharacters() {
|
||||
const robotSelect = document.getElementById('robotSelect');
|
||||
|
||||
try {
|
||||
const response = await fetch('/imgsearcherApi/api/robots');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.robots && data.robots.length > 0) {
|
||||
// 添加机器人选项
|
||||
data.robots.forEach(robot => {
|
||||
const option = document.createElement('option');
|
||||
option.value = robot.robot_id;
|
||||
option.textContent = robot.name;
|
||||
robotSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载机器人角色失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图片上传表单
|
||||
function initUploadImageForm() {
|
||||
const form = document.getElementById('uploadImageForm');
|
||||
@ -44,7 +96,7 @@ function initUploadImageForm() {
|
||||
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 上传中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/upload-chat-image', {
|
||||
const response = await fetch('/imgsearcherApi/api/upload-chat-image', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
@ -74,14 +126,31 @@ function initUploadImageForm() {
|
||||
// 显示聊天界面
|
||||
chatContainer.classList.remove('d-none');
|
||||
|
||||
// 添加欢迎消息
|
||||
// 显示欢迎消息
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
chatMessages.innerHTML = '';
|
||||
|
||||
const welcomeMessage = document.createElement('div');
|
||||
welcomeMessage.className = 'message message-assistant';
|
||||
|
||||
let welcomeText = '你好!我是AI助手,可以帮你分析这张图片并回答问题。';
|
||||
let welcomeText = '';
|
||||
|
||||
// 如果选择了机器人角色,使用机器人的自我介绍
|
||||
if (data.robot) {
|
||||
welcomeText = `你好!我是${data.robot.name},`;
|
||||
// 存储机器人信息到页面数据中
|
||||
document.getElementById('chatContainer').dataset.robotName = data.robot.name;
|
||||
|
||||
// 显示机器人信息在界面上
|
||||
const robotInfoElement = document.createElement('div');
|
||||
robotInfoElement.className = 'alert alert-info mt-2 mb-3';
|
||||
robotInfoElement.innerHTML = `<strong>当前角色:${data.robot.name}</strong><br><small>${data.robot.background}</small>`;
|
||||
document.getElementById('chatContainer').prepend(robotInfoElement);
|
||||
} else {
|
||||
welcomeText = '你好!我是AI助手,';
|
||||
}
|
||||
|
||||
welcomeText += '可以帮你分析这张图片并回答问题。';
|
||||
if (data.image_type && data.description) {
|
||||
welcomeText += `我看到这是一张${data.image_type}的图片,${data.description}。你有什么想问的吗?`;
|
||||
} else {
|
||||
@ -136,28 +205,87 @@ function initChatForm() {
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/chat', {
|
||||
// 使用流式接口
|
||||
const response = await fetch('/imgsearcherApi/api/chat-stream', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: message
|
||||
message: message,
|
||||
voice: document.getElementById('voiceSelect') ? document.getElementById('voiceSelect').value : ''
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// 移除正在输入指示器
|
||||
chatMessages.removeChild(typingIndicator);
|
||||
|
||||
if (data.error) {
|
||||
alert(`对话失败: ${data.error}`);
|
||||
if (!response.ok) {
|
||||
chatMessages.removeChild(typingIndicator);
|
||||
alert('对话失败');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加AI回复到聊天界面
|
||||
addMessage('assistant', data.reply);
|
||||
// 创建助手消息占位
|
||||
const assistantElement = document.createElement('div');
|
||||
assistantElement.className = 'message message-assistant';
|
||||
const contentDiv = document.createElement('div');
|
||||
contentDiv.className = 'message-content';
|
||||
assistantElement.appendChild(contentDiv);
|
||||
// 存储该消息对应的音频片段集合
|
||||
const audioSegments = [];
|
||||
const timeDiv = document.createElement('div');
|
||||
timeDiv.className = 'message-time';
|
||||
timeDiv.textContent = getCurrentTime();
|
||||
assistantElement.appendChild(timeDiv);
|
||||
chatMessages.appendChild(assistantElement);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
let buf = '';
|
||||
let streamEnded = false;
|
||||
while (!streamEnded) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
buf += decoder.decode(value, { stream: true });
|
||||
|
||||
let pos;
|
||||
while ((pos = buf.indexOf('\n\n')) !== -1) {
|
||||
const raw = buf.slice(0, pos).trim();
|
||||
buf = buf.slice(pos + 2);
|
||||
if (!raw) continue;
|
||||
|
||||
if (raw.startsWith('event: end')) {
|
||||
chatMessages.removeChild(typingIndicator);
|
||||
streamEnded = true;
|
||||
// 为该消息添加重放按钮
|
||||
if (audioSegments.length > 0) {
|
||||
const replayBtn = document.createElement('button');
|
||||
replayBtn.className = 'btn btn-sm btn-outline-secondary ms-2 replay-btn';
|
||||
replayBtn.innerHTML = '<i class="bi bi-play-circle"></i> 重放音频';
|
||||
replayBtn.addEventListener('click', () => { playAudioSegments(audioSegments); });
|
||||
assistantElement.appendChild(replayBtn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!raw.startsWith('data:')) continue;
|
||||
const payload = raw.slice(5).trim();
|
||||
if (!payload) continue;
|
||||
try {
|
||||
const obj = JSON.parse(payload);
|
||||
if (obj.type === 'text') {
|
||||
contentDiv.innerHTML += formatMessage(obj.content);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
} else if (obj.type === 'audio') {
|
||||
const audioBytes = Uint8Array.from(atob(obj.content), c => c.charCodeAt(0));
|
||||
const audioBlob = new Blob([audioBytes], { type: 'audio/wav' });
|
||||
audioSegments.push(audioBlob);
|
||||
enqueueAudio(audioBlob);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析事件失败', e, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// 移除正在输入指示器
|
||||
@ -171,7 +299,7 @@ function initChatForm() {
|
||||
}
|
||||
|
||||
// 添加消息到聊天界面
|
||||
function addMessage(role, content) {
|
||||
async function addMessage(role, content) {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
|
||||
const messageElement = document.createElement('div');
|
||||
@ -184,6 +312,9 @@ function addMessage(role, content) {
|
||||
|
||||
chatMessages.appendChild(messageElement);
|
||||
|
||||
|
||||
|
||||
|
||||
// 滚动到底部
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ function initUploadForm() {
|
||||
formData.append('tags', tags);
|
||||
|
||||
try {
|
||||
const response = await fetch('/upload', {
|
||||
const response = await fetch('/imgsearcherApi/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
@ -182,7 +182,7 @@ function initSearchForm() {
|
||||
batchActions.classList.add('d-none');
|
||||
reliableTypesInfo.classList.add('d-none');
|
||||
|
||||
fetch('/search', {
|
||||
fetch('/imgsearcherApi/search', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
@ -321,7 +321,7 @@ function initUpdateForm() {
|
||||
};
|
||||
|
||||
// 发送更新请求
|
||||
fetch('/update', {
|
||||
fetch('/imgsearcherApi/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@ -364,7 +364,7 @@ function initDeleteForm() {
|
||||
const contSign = document.getElementById('deleteContSign').value;
|
||||
|
||||
// 发送删除请求
|
||||
fetch('/delete', {
|
||||
fetch('/imgsearcherApi/delete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@ -490,7 +490,7 @@ async function batchDelete(selectedItems) {
|
||||
const contSign = selectedItems[i].getAttribute('data-cont-sign');
|
||||
|
||||
try {
|
||||
const response = await fetch('/delete', {
|
||||
const response = await fetch('/imgsearcherApi/delete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
297
app/static/js/robots.js
Normal file
297
app/static/js/robots.js
Normal file
@ -0,0 +1,297 @@
|
||||
// 页面加载完成后执行
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化机器人表单
|
||||
initRobotForm();
|
||||
|
||||
// 加载机器人列表
|
||||
loadRobots();
|
||||
|
||||
// 初始化编辑和删除功能
|
||||
initEditRobotModal();
|
||||
initDeleteRobotModal();
|
||||
});
|
||||
|
||||
// 初始化机器人表单
|
||||
function initRobotForm() {
|
||||
const form = document.getElementById('robotForm');
|
||||
const avatarInput = document.getElementById('robotAvatar');
|
||||
const avatarPreview = document.getElementById('avatarPreview');
|
||||
const avatarPreviewContainer = document.querySelector('#robotForm .avatar-preview');
|
||||
|
||||
// 头像预览
|
||||
avatarInput.addEventListener('change', function() {
|
||||
if (this.files && this.files[0]) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function(e) {
|
||||
avatarPreview.src = e.target.result;
|
||||
avatarPreviewContainer.classList.remove('d-none');
|
||||
};
|
||||
|
||||
reader.readAsDataURL(this.files[0]);
|
||||
} else {
|
||||
avatarPreviewContainer.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
// 表单提交
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
// 显示提交中状态
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
const originalButtonText = submitButton.innerHTML;
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 添加中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/imgsearcherApi/api/robots', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
alert(`添加失败: ${data.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
form.reset();
|
||||
avatarPreviewContainer.classList.add('d-none');
|
||||
|
||||
// 刷新机器人列表
|
||||
loadRobots();
|
||||
|
||||
// 显示成功消息
|
||||
alert('机器人角色添加成功!');
|
||||
|
||||
} catch (error) {
|
||||
alert(`添加失败: ${error.message}`);
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = originalButtonText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载机器人列表
|
||||
async function loadRobots() {
|
||||
const robotsList = document.getElementById('robotsList');
|
||||
const loadingIndicator = document.getElementById('loadingIndicator');
|
||||
const noRobotsMessage = document.getElementById('noRobotsMessage');
|
||||
|
||||
// 显示加载指示器
|
||||
loadingIndicator.classList.remove('d-none');
|
||||
noRobotsMessage.classList.add('d-none');
|
||||
|
||||
// 清除现有的机器人卡片(保留标题和加载指示器)
|
||||
const existingCards = robotsList.querySelectorAll('.robot-card-container');
|
||||
existingCards.forEach(card => card.remove());
|
||||
|
||||
try {
|
||||
const response = await fetch('/imgsearcherApi/api/robots');
|
||||
const data = await response.json();
|
||||
|
||||
// 隐藏加载指示器
|
||||
loadingIndicator.classList.add('d-none');
|
||||
|
||||
if (!data.robots || data.robots.length === 0) {
|
||||
noRobotsMessage.classList.remove('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
// 渲染机器人卡片
|
||||
data.robots.forEach(robot => {
|
||||
const robotCard = createRobotCard(robot);
|
||||
robotsList.appendChild(robotCard);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载机器人列表失败:', error);
|
||||
loadingIndicator.classList.add('d-none');
|
||||
alert(`加载机器人列表失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建机器人卡片
|
||||
function createRobotCard(robot) {
|
||||
const cardContainer = document.createElement('div');
|
||||
cardContainer.className = 'col-md-4 mb-4 robot-card-container';
|
||||
|
||||
const avatarHtml = robot.avatar_data
|
||||
? `<img src="data:image/jpeg;base64,${robot.avatar_data}" alt="${robot.name}" class="robot-avatar">`
|
||||
: `<div class="robot-avatar-placeholder"><i class="bi bi-person"></i></div>`;
|
||||
|
||||
cardContainer.innerHTML = `
|
||||
<div class="card robot-card">
|
||||
<div class="card-body text-center">
|
||||
${avatarHtml}
|
||||
<h5 class="card-title">${robot.name}</h5>
|
||||
<div class="robot-background text-start mb-3">
|
||||
<small class="text-muted">${robot.background}</small>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary edit-robot" data-robot-id="${robot.robot_id}">
|
||||
<i class="bi bi-pencil"></i> 编辑
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger delete-robot" data-robot-id="${robot.robot_id}">
|
||||
<i class="bi bi-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 添加编辑按钮事件
|
||||
cardContainer.querySelector('.edit-robot').addEventListener('click', function() {
|
||||
openEditRobotModal(robot);
|
||||
});
|
||||
|
||||
// 添加删除按钮事件
|
||||
cardContainer.querySelector('.delete-robot').addEventListener('click', function() {
|
||||
openDeleteRobotModal(robot.robot_id, robot.name);
|
||||
});
|
||||
|
||||
return cardContainer;
|
||||
}
|
||||
|
||||
// 初始化编辑机器人模态框
|
||||
function initEditRobotModal() {
|
||||
const editRobotForm = document.getElementById('editRobotForm');
|
||||
const saveChangesButton = document.getElementById('saveRobotChanges');
|
||||
const editRobotAvatar = document.getElementById('editRobotAvatar');
|
||||
const editAvatarPreview = document.getElementById('editAvatarPreview');
|
||||
|
||||
// 头像预览
|
||||
editRobotAvatar.addEventListener('change', function() {
|
||||
if (this.files && this.files[0]) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function(e) {
|
||||
editAvatarPreview.src = e.target.result;
|
||||
};
|
||||
|
||||
reader.readAsDataURL(this.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// 保存更改按钮点击事件
|
||||
saveChangesButton.addEventListener('click', async function() {
|
||||
const robotId = document.getElementById('editRobotId').value;
|
||||
const formData = new FormData(editRobotForm);
|
||||
|
||||
// 显示保存中状态
|
||||
const originalButtonText = saveChangesButton.innerHTML;
|
||||
saveChangesButton.disabled = true;
|
||||
saveChangesButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 保存中...';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/imgsearcherApi/api/robots/${robotId}`, {
|
||||
method: 'PUT',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
alert(`更新失败: ${data.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
const editModal = bootstrap.Modal.getInstance(document.getElementById('editRobotModal'));
|
||||
editModal.hide();
|
||||
|
||||
// 刷新机器人列表
|
||||
loadRobots();
|
||||
|
||||
// 显示成功消息
|
||||
alert('机器人角色更新成功!');
|
||||
|
||||
} catch (error) {
|
||||
alert(`更新失败: ${error.message}`);
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
saveChangesButton.disabled = false;
|
||||
saveChangesButton.innerHTML = originalButtonText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 打开编辑机器人模态框
|
||||
function openEditRobotModal(robot) {
|
||||
document.getElementById('editRobotId').value = robot.robot_id;
|
||||
document.getElementById('editRobotName').value = robot.name;
|
||||
document.getElementById('editRobotBackground').value = robot.background;
|
||||
|
||||
// 设置头像预览
|
||||
if (robot.avatar_data) {
|
||||
document.getElementById('editAvatarPreview').src = `data:image/jpeg;base64,${robot.avatar_data}`;
|
||||
} else {
|
||||
document.getElementById('editAvatarPreview').src = '';
|
||||
}
|
||||
|
||||
// 打开模态框
|
||||
const editModal = new bootstrap.Modal(document.getElementById('editRobotModal'));
|
||||
editModal.show();
|
||||
}
|
||||
|
||||
// 初始化删除机器人模态框
|
||||
function initDeleteRobotModal() {
|
||||
const confirmDeleteButton = document.getElementById('confirmDeleteRobot');
|
||||
|
||||
confirmDeleteButton.addEventListener('click', async function() {
|
||||
const robotId = document.getElementById('deleteRobotId').value;
|
||||
|
||||
// 显示删除中状态
|
||||
const originalButtonText = confirmDeleteButton.innerHTML;
|
||||
confirmDeleteButton.disabled = true;
|
||||
confirmDeleteButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 删除中...';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/imgsearcherApi/api/robots/${robotId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
alert(`删除失败: ${data.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
const deleteModal = bootstrap.Modal.getInstance(document.getElementById('deleteRobotModal'));
|
||||
deleteModal.hide();
|
||||
|
||||
// 刷新机器人列表
|
||||
loadRobots();
|
||||
|
||||
// 显示成功消息
|
||||
alert('机器人角色删除成功!');
|
||||
|
||||
} catch (error) {
|
||||
alert(`删除失败: ${error.message}`);
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
confirmDeleteButton.disabled = false;
|
||||
confirmDeleteButton.innerHTML = originalButtonText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 打开删除机器人模态框
|
||||
function openDeleteRobotModal(robotId, robotName) {
|
||||
document.getElementById('deleteRobotId').value = robotId;
|
||||
document.getElementById('deleteRobotModalLabel').textContent = `确认删除 "${robotName}"`;
|
||||
document.querySelector('#deleteRobotModal .modal-body p').textContent = `确定要删除机器人角色 "${robotName}" 吗?此操作不可撤销。`;
|
||||
|
||||
// 打开模态框
|
||||
const deleteModal = new bootstrap.Modal(document.getElementById('deleteRobotModal'));
|
||||
deleteModal.show();
|
||||
}
|
||||
@ -15,7 +15,7 @@
|
||||
<h1>图片智能对话系统</h1>
|
||||
<p class="lead">上传图片,与AI进行多轮对话</p>
|
||||
<nav class="mt-3">
|
||||
<a href="/" class="btn btn-outline-secondary">返回首页</a>
|
||||
<a href="/imgsearcherApi" class="btn btn-outline-secondary">返回首页</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@ -25,12 +25,44 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">上传图片</h5>
|
||||
<form id="uploadImageForm" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="chatImageFile" class="form-label">选择图片</label>
|
||||
<input class="form-control" type="file" id="chatImageFile" name="file" accept="image/*" required>
|
||||
<div class="form-text">支持JPG、PNG、JPEG、GIF格式</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="chatImageFile" class="form-label">选择图片</label>
|
||||
<input class="form-control" type="file" id="chatImageFile" name="file" accept="image/*" required>
|
||||
<div class="form-text">支持JPG、PNG、JPEG、GIF格式</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="robotSelect" class="form-label">选择机器人角色(可选)</label>
|
||||
<select class="form-select" id="robotSelect" name="robot_id">
|
||||
<option value="">默认AI助手</option>
|
||||
<!-- 机器人选项将在这里动态添加 -->
|
||||
</select>
|
||||
<div class="form-text">选择一个机器人角色进行对话</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 语音音色选择 -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="voiceSelect" class="form-label">选择音色</label>
|
||||
<select class="form-select" id="voiceSelect" name="voice">
|
||||
<option value="zh-CN-XiaoXiao-Assistant-Audio.wav" selected>小小助手</option>
|
||||
<option value="zh-CN-XiaoXiao-Chat-Audio.wav">小小聊天</option>
|
||||
<option value="zh-CN-YunYang-CustomerService-Audio.wav">云扬客服</option>
|
||||
<option value="zh-CN-Yunxi-Boy-Default-Audio.wav">云希男孩</option>
|
||||
<option value="zh-CN-Yunxi-Narrator-Narrative-Audio.wav">云希旁白</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<button type="submit" class="btn btn-primary">上传并开始对话</button>
|
||||
<a href="/imgsearcherApi/robots" class="btn btn-outline-secondary">管理机器人角色</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">上传并开始对话</button>
|
||||
</form>
|
||||
<div id="uploadPreview" class="mt-3 text-center d-none">
|
||||
<div class="preview-container">
|
||||
|
||||
@ -3,22 +3,22 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>地瓜机器人相似图片搜索及分类识别系统</title>
|
||||
<title>地瓜&乐森变形金刚demo</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="text-center my-5">
|
||||
<h1>地瓜机器人相似图片搜索及分类识别demo</h1>
|
||||
<p class="lead">基于图片相似度进行分类的demo演示</p>
|
||||
<h1>地瓜&乐森变形金刚demo</h1>
|
||||
<p class="lead">基于图片相似度的demo演示</p>
|
||||
</header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload" type="button" role="tab" aria-controls="upload" aria-selected="true">角色录入</button>
|
||||
<button class="nav-link active" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload" type="button" role="tab" aria-controls="upload" aria-selected="true">待识别角色录入</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="search-tab" data-bs-toggle="tab" data-bs-target="#search" type="button" role="tab" aria-controls="search" aria-selected="false">图片搜索</button>
|
||||
@ -27,15 +27,18 @@
|
||||
<!-- <button class="nav-link" id="manage-tab" data-bs-toggle="tab" data-bs-target="#manage" type="button" role="tab" aria-controls="manage" aria-selected="false">图库管理</button> -->
|
||||
<!-- </li> -->
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="/chat-with-image" class="nav-link" style="color: #0d6efd; font-weight: bold;">图片智能对话</a>
|
||||
<a href="/imgsearcherApi/chat-with-image" class="nav-link" style="color: #0d6efd; font-weight: bold;">图片智能对话</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="/imgsearcherApi/robots" class="nav-link" style="color: #198754; font-weight: bold;">机器人角色管理</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<!-- 角色录入 -->
|
||||
<!-- 待识别角色录入 -->
|
||||
<div class="tab-pane fade show active" id="upload" role="tabpanel" aria-labelledby="upload-tab">
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">角色录入</h5>
|
||||
<h5 class="card-title">待识别角色录入</h5>
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="uploadFile" class="form-label">选择图片(这里可以多选的,也就是批量入库)</label>
|
||||
@ -43,15 +46,17 @@
|
||||
<div class="form-text">支持JPG、PNG、BMP格式,最短边至少50px,最长边最大4096px</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="imageType" class="form-label">类型(这个实际就是宠物名,类似小狗pet、小猫pussy之类的)</label>
|
||||
<label for="imageType" class="form-label">角色名称</label>
|
||||
<input type="text" class="form-control" id="imageType" name="type" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="imageDescription" class="form-label">描述(目前demo只支持250个字符,后面正式版可以扩充,这个实际是角色卡信息,也就是宠物的描述,类似小狗是金毛之类的)</label>
|
||||
<label for="imageDescription" class="form-label">描述(角色卡信息)</label>
|
||||
<textarea class="form-control" id="imageDescription" name="description" rows="2" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="imageName" class="form-label">名称(这个实际是图片名称,直接和宠物名重名即可,我会给加个名字)</label>
|
||||
<label for="imageName" class="form-label">图片名称
|
||||
|
||||
</label>
|
||||
<input type="text" class="form-control" id="imageName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
|
||||
186
app/templates/robots.html
Normal file
186
app/templates/robots.html
Normal file
@ -0,0 +1,186 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>机器人角色管理 - 地瓜机器人</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<style>
|
||||
.robot-card {
|
||||
height: 100%;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.robot-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
.robot-avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.robot-avatar-placeholder {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
background-color: #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 15px;
|
||||
font-size: 2rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.robot-background {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#robotForm .avatar-preview {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
}
|
||||
#robotForm .avatar-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="text-center my-4">
|
||||
<h1>机器人角色管理</h1>
|
||||
<p class="lead">创建和管理您的机器人角色</p>
|
||||
<nav class="mt-3">
|
||||
<a href="/imgsearcherApi" class="btn btn-outline-secondary me-2">返回首页</a>
|
||||
<a href="/imgsearcherApi/chat-with-image" class="btn btn-outline-primary">图片智能对话</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">添加新机器人角色</h5>
|
||||
<form id="robotForm" enctype="multipart/form-data">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="robotName" class="form-label">机器人名称</label>
|
||||
<input type="text" class="form-control" id="robotName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="robotAvatar" class="form-label">头像(可选)</label>
|
||||
<input class="form-control" type="file" id="robotAvatar" name="avatar" accept="image/*">
|
||||
<div class="avatar-preview d-none mt-2">
|
||||
<img id="avatarPreview" src="" alt="头像预览">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="mb-3">
|
||||
<label for="robotBackground" class="form-label">背景故事</label>
|
||||
<textarea class="form-control" id="robotBackground" name="background" rows="5" required></textarea>
|
||||
<div class="form-text">描述机器人的性格、背景和特点,这些信息将用于图片对话中</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> 添加机器人
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="robotsList">
|
||||
<div class="col-12 mb-4">
|
||||
<h3>现有机器人角色</h3>
|
||||
<div class="text-center py-5 d-none" id="loadingIndicator">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">加载中...</span>
|
||||
</div>
|
||||
<p class="mt-2">加载机器人角色...</p>
|
||||
</div>
|
||||
<div class="alert alert-info d-none" id="noRobotsMessage">
|
||||
暂无机器人角色,请添加一个新的机器人角色。
|
||||
</div>
|
||||
</div>
|
||||
<!-- 机器人卡片将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑机器人模态框 -->
|
||||
<div class="modal fade" id="editRobotModal" tabindex="-1" aria-labelledby="editRobotModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editRobotModalLabel">编辑机器人角色</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editRobotForm" enctype="multipart/form-data">
|
||||
<input type="hidden" id="editRobotId">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="editRobotName" class="form-label">机器人名称</label>
|
||||
<input type="text" class="form-control" id="editRobotName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editRobotAvatar" class="form-label">头像(可选)</label>
|
||||
<input class="form-control" type="file" id="editRobotAvatar" name="avatar" accept="image/*">
|
||||
<div class="avatar-preview mt-2">
|
||||
<img id="editAvatarPreview" src="" alt="头像预览">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="mb-3">
|
||||
<label for="editRobotBackground" class="form-label">背景故事</label>
|
||||
<textarea class="form-control" id="editRobotBackground" name="background" rows="5" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="saveRobotChanges">保存更改</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确认删除模态框 -->
|
||||
<div class="modal fade" id="deleteRobotModal" tabindex="-1" aria-labelledby="deleteRobotModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteRobotModalLabel">确认删除</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>确定要删除这个机器人角色吗?此操作不可撤销。</p>
|
||||
<input type="hidden" id="deleteRobotId">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-danger" id="confirmDeleteRobot">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/robots.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
43
deploy/k8s/bj2/deployment.yaml
Normal file
43
deploy/k8s/bj2/deployment.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: imgsearcher-api
|
||||
namespace: bj2-dcloud
|
||||
labels:
|
||||
app: imgsearcher-api
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: imgsearcher-api
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: imgsearcher-api
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ccr
|
||||
containers:
|
||||
- name: imgsearcher-api
|
||||
image: ccr-29eug8s3-vpc.cnc.bj.baidubce.com/service/imgsearcher:${DRONE_COMMIT_SHA}
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
env:
|
||||
- name: BAIDU_API_KEY
|
||||
value: "cqn5SYRZAKM9WBXgYg8niUdM"
|
||||
- name: BAIDU_SECRET_KEY
|
||||
value: "vG1VNzQPsz7G8sogjMm9vCDU7LUawRnD"
|
||||
- name: APP_ID
|
||||
value: "6691505"
|
||||
- name: AZURE_OPENAI_API_KEY
|
||||
value: "AMbAvhIHJIyKdNUeSMYBkM9wf4ISBoYrNUW7g0L5lwCSiETBGCpsJQQJ99AKACHYHv6XJ3w3AAAAACOGS84A"
|
||||
- name: AZURE_OPENAI_ENDPOINT
|
||||
value: "https://admin-m3wql277-eastus2.cognitiveservices.azure.com"
|
||||
- name: AZURE_OPENAI_API_VERSION
|
||||
value: "2023-12-01-preview"
|
||||
- name: AZURE_OPENAI_DEPLOYMENT_NAME
|
||||
value: "gpt-4o-drobotics"
|
||||
- name: MONGO_URI
|
||||
value: "mongodb://root:12345678a!@wIBH1rV4a.mongodb.bj.baidubce.com/imgsearcher?authSource=admin"
|
||||
- name: MONGO_DB_NAME
|
||||
value: "imgsearcher"
|
||||
29
deploy/k8s/bj2/ingress.yaml
Normal file
29
deploy/k8s/bj2/ingress.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
# Ingress的名称
|
||||
name: imgsearcher-ingress
|
||||
# 部署的命名空间
|
||||
namespace: bj2-dcloud
|
||||
# Ingress的具体配置
|
||||
spec:
|
||||
# 指定使用nginx作为Ingress控制器
|
||||
ingressClassName: nginx
|
||||
# 定义路由规则
|
||||
rules:
|
||||
# 指定域名规则
|
||||
- host: imagechat.d-robotircs.cc # 访问域名,根据项目
|
||||
# HTTP规则配置
|
||||
http:
|
||||
# 路径配置列表
|
||||
paths:
|
||||
# 单个路径规则配置
|
||||
- path: /imgsearcherApi/ # URL路径前缀
|
||||
pathType: Prefix # 路径匹配类型:Prefix表示前缀匹配
|
||||
# 后端服务配置
|
||||
backend:
|
||||
# 服务配置
|
||||
service:
|
||||
name: imgsearcher-api-service # 后端Service名称
|
||||
port:
|
||||
number: 80 # 后端Service端口
|
||||
13
deploy/k8s/bj2/service.yaml
Normal file
13
deploy/k8s/bj2/service.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: imgsearcher-api-service
|
||||
namespace: bj2-dcloud
|
||||
spec:
|
||||
selector:
|
||||
app: imgsearcher-api
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 5001
|
||||
type: ClusterIP
|
||||
@ -1,113 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: imgsearcher
|
||||
namespace: imgsearcher
|
||||
labels:
|
||||
app: imgsearcher
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: imgsearcher
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: imgsearcher
|
||||
spec:
|
||||
containers:
|
||||
- name: imgsearcher
|
||||
image: ${DRONE_REPO_OWNER}/imgsearcher:${DRONE_COMMIT_SHA:0:8}
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
env:
|
||||
- name: MONGO_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: imgsearcher-secrets
|
||||
key: mongo-uri
|
||||
- name: BAIDU_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: imgsearcher-secrets
|
||||
key: baidu-api-key
|
||||
- name: BAIDU_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: imgsearcher-secrets
|
||||
key: baidu-secret-key
|
||||
- name: AZURE_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: imgsearcher-secrets
|
||||
key: azure-api-key
|
||||
- name: AZURE_API_ENDPOINT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: imgsearcher-secrets
|
||||
key: azure-api-endpoint
|
||||
resources:
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "256Mi"
|
||||
volumeMounts:
|
||||
- name: uploads
|
||||
mountPath: /app/uploads
|
||||
volumes:
|
||||
- name: uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: imgsearcher-uploads-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: imgsearcher
|
||||
namespace: imgsearcher
|
||||
spec:
|
||||
selector:
|
||||
app: imgsearcher
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 5001
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: imgsearcher
|
||||
namespace: imgsearcher
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
rules:
|
||||
- host: imgsearcher.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: imgsearcher
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- imgsearcher.example.com
|
||||
secretName: imgsearcher-tls
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: imgsearcher-uploads-pvc
|
||||
namespace: imgsearcher
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
storageClassName: standard
|
||||
468
poetry.lock
generated
468
poetry.lock
generated
@ -1,42 +1,141 @@
|
||||
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
description = "Fast, simple object-to-object and broadcast signaling"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"},
|
||||
{file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2023.5.7"
|
||||
version = "2025.1.31"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
|
||||
{file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
|
||||
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
|
||||
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.0.12"
|
||||
version = "3.4.1"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
optional = false
|
||||
python-versions = ">=3.5.0"
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
|
||||
{file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
|
||||
{file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
|
||||
{file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
|
||||
{file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
|
||||
{file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
|
||||
{file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
|
||||
{file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
|
||||
{file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
unicode-backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
version = "8.1.8"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
|
||||
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -57,21 +156,23 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "2.0.1"
|
||||
version = "2.3.3"
|
||||
description = "A simple framework for building complex web applications."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "Flask-2.0.1-py3-none-any.whl", hash = "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"},
|
||||
{file = "Flask-2.0.1.tar.gz", hash = "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55"},
|
||||
{file = "flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"},
|
||||
{file = "flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.1.2"
|
||||
itsdangerous = ">=2.0"
|
||||
Jinja2 = ">=3.0"
|
||||
Werkzeug = ">=2.0"
|
||||
blinker = ">=1.6.2"
|
||||
click = ">=8.1.3"
|
||||
importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
|
||||
itsdangerous = ">=2.1.2"
|
||||
Jinja2 = ">=3.1.2"
|
||||
Werkzeug = ">=2.3.7"
|
||||
|
||||
[package.extras]
|
||||
async = ["asgiref (>=3.2)"]
|
||||
@ -95,38 +196,66 @@ Six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
version = "3.10"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
||||
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "8.6.1"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
markers = "python_version == \"3.9\""
|
||||
files = [
|
||||
{file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
|
||||
{file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=3.20"
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
perf = ["ipython"]
|
||||
test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
|
||||
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
|
||||
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
|
||||
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.2"
|
||||
version = "3.1.6"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -137,123 +266,136 @@ i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.2"
|
||||
version = "3.0.2"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
|
||||
{file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
|
||||
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "8.3.1"
|
||||
version = "8.4.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "Pillow-8.3.1-1-cp36-cp36m-win_amd64.whl", hash = "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24"},
|
||||
{file = "Pillow-8.3.1-1-cp37-cp37m-win_amd64.whl", hash = "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a"},
|
||||
{file = "Pillow-8.3.1-1-cp38-cp38-win_amd64.whl", hash = "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093"},
|
||||
{file = "Pillow-8.3.1-1-cp39-cp39-win_amd64.whl", hash = "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77"},
|
||||
{file = "Pillow-8.3.1-1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-win32.whl", hash = "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-win32.whl", hash = "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-win32.whl", hash = "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-win32.whl", hash = "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8"},
|
||||
{file = "Pillow-8.3.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf"},
|
||||
{file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63"},
|
||||
{file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4"},
|
||||
{file = "Pillow-8.3.1.tar.gz", hash = "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-win32.whl", hash = "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-win32.whl", hash = "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-win32.whl", hash = "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-win32.whl", hash = "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-win32.whl", hash = "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b"},
|
||||
{file = "Pillow-8.4.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2"},
|
||||
{file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad"},
|
||||
{file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"},
|
||||
{file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "0.19.0"
|
||||
version = "0.19.2"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
|
||||
{file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
|
||||
{file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
|
||||
{file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -261,72 +403,94 @@ cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.26.0"
|
||||
version = "2.32.3"
|
||||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
|
||||
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
|
||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
|
||||
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
charset-normalizer = ">=2,<4"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<3"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton ; sys_platform == \"win32\" and python_version == \"2.7\""]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
version = "1.17.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.16"
|
||||
version = "2.4.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"},
|
||||
{file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"},
|
||||
{file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"},
|
||||
{file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""]
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.2.3"
|
||||
version = "3.1.3"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"},
|
||||
{file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"},
|
||||
{file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"},
|
||||
{file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.1.1"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog"]
|
||||
watchdog = ["watchdog (>=2.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.21.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
markers = "python_version == \"3.9\""
|
||||
files = [
|
||||
{file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
|
||||
{file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
|
||||
@ -1,6 +1,36 @@
|
||||
flask==2.0.1
|
||||
requests==2.26.0
|
||||
python-dotenv==0.19.0
|
||||
Pillow==8.3.1
|
||||
flask-cors==3.0.10
|
||||
# 核心Web框架
|
||||
Flask==2.3.3
|
||||
Flask-Cors==3.0.10
|
||||
Werkzeug==3.1.3
|
||||
|
||||
# 图像处理
|
||||
Pillow==8.4.0
|
||||
|
||||
# 数据库
|
||||
pymongo==4.5.0
|
||||
dnspython==2.7.0
|
||||
|
||||
# HTTP请求
|
||||
requests==2.32.3
|
||||
|
||||
# 环境变量管理
|
||||
python-dotenv==0.19.2
|
||||
|
||||
# 其他工具
|
||||
click==8.1.8
|
||||
blinker==1.9.0
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.2
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
tomli==2.2.1
|
||||
tomlkit==0.13.2
|
||||
trove-classifiers==2025.3.19.19
|
||||
typing_extensions==4.13.1
|
||||
urllib3==2.4.0
|
||||
virtualenv==20.30.0
|
||||
Werkzeug==3.1.3
|
||||
xattr==1.1.4
|
||||
zipp==3.21.0
|
||||
zstandard==0.23.0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user