1927 lines
76 KiB
Python
1927 lines
76 KiB
Python
from aiohttp import web
|
||
from server import PromptServer
|
||
from .config_manager import config_manager
|
||
from .services.baidu import BaiduTranslateService
|
||
from .services.llm import LLMService
|
||
from .services.vlm import VisionService
|
||
from .services.model_list import get_models_from_service
|
||
import base64
|
||
import json
|
||
import traceback
|
||
import asyncio
|
||
import folder_paths
|
||
import imageio
|
||
import os
|
||
from .utils.common import (
|
||
# 统一日志前缀(从 common.py 导入)
|
||
PREFIX, ERROR_PREFIX, PROCESS_PREFIX,
|
||
REQUEST_PREFIX, WARN_PREFIX,
|
||
_ANSI_CLEAR_EOL,
|
||
# 统一日志函数和常量
|
||
log_prepare, TASK_TRANSLATE, TASK_EXPAND, TASK_IMAGE_CAPTION, SOURCE_FRONTEND
|
||
)
|
||
from .utils.video import extract_frame_by_index, get_video_frame_info
|
||
|
||
# 动态获取插件目录名作为路由前缀的基础
|
||
# 这样即使文件夹被重命名(例如加上 comfyui- 前缀),路由也会自动适配
|
||
NODE_DIR_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
|
||
API_PREFIX = f'/{NODE_DIR_NAME}/api'
|
||
|
||
# print(f"{PREFIX} API 路由已挂载至: {API_PREFIX}")
|
||
|
||
# 在服务器初始化时验证激活提示词
|
||
config_manager.validate_and_fix_active_prompts()
|
||
# 新增:在启动时补全模型提供商及参数
|
||
config_manager.validate_and_fix_model_params()
|
||
|
||
|
||
|
||
# 用于跟踪正在进行的异步任务
|
||
ACTIVE_TASKS = {}
|
||
|
||
# ---流式进度设置(运行时状态,实时生效无需重启)---
|
||
_streaming_progress_enabled = True
|
||
|
||
def is_streaming_progress_enabled():
|
||
"""供其他模块查询当前流式进度设置"""
|
||
return _streaming_progress_enabled
|
||
|
||
# 不再使用RouteTableDef
|
||
# routes = web.RouteTableDef()
|
||
|
||
def get_result_text(result):
|
||
"""
|
||
智能提取接口返回中的文本内容,用于字符统计。
|
||
优先级:data.translated > data.expanded > data.description > data.result > data.text > result > data(str)
|
||
"""
|
||
if not isinstance(result, dict):
|
||
return str(result) if result is not None else ''
|
||
data = result.get('data')
|
||
if isinstance(data, dict):
|
||
for key in ['translated', 'expanded', 'description', 'result', 'text']:
|
||
if key in data and isinstance(data[key], str):
|
||
return data[key]
|
||
if isinstance(data, str):
|
||
return data
|
||
if 'result' in result and isinstance(result['result'], str):
|
||
return result['result']
|
||
if 'text' in result and isinstance(result['text'], str):
|
||
return result['text']
|
||
return ''
|
||
|
||
# ---流式进度设置API---
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/settings/streaming_progress')
|
||
async def get_streaming_progress_setting(request):
|
||
"""获取流式进度设置"""
|
||
return web.json_response({"enabled": _streaming_progress_enabled})
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/settings/streaming_progress')
|
||
async def set_streaming_progress_setting(request):
|
||
"""设置流式进度(实时生效,不需重启)"""
|
||
global _streaming_progress_enabled
|
||
try:
|
||
data = await request.json()
|
||
_streaming_progress_enabled = data.get("enabled", True)
|
||
return web.json_response({"success": True})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 更新流式进度设置失败: {str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/baidu_translate')
|
||
async def get_baidu_translate_config(request):
|
||
"""获取百度翻译配置"""
|
||
config = config_manager.get_baidu_translate_config()
|
||
return web.json_response(config)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/llm')
|
||
async def get_llm_config(request):
|
||
"""获取LLM配置"""
|
||
config = config_manager.get_llm_config()
|
||
return web.json_response(config)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/vision')
|
||
async def get_vision_config(request):
|
||
"""获取视觉模型配置"""
|
||
config = config_manager.get_vision_config()
|
||
return web.json_response(config)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/translate')
|
||
async def get_translate_config(request):
|
||
"""获取翻译服务配置"""
|
||
config = config_manager.get_translate_config()
|
||
return web.json_response(config)
|
||
|
||
# --- 方案A:API Key掩码接口(安全版本)---
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/llm/masked')
|
||
async def get_llm_config_masked(request):
|
||
"""
|
||
获取LLM配置(API Key掩码版本)
|
||
用于前端显示,不暴露完整API Key
|
||
"""
|
||
config = config_manager.get_llm_config_masked()
|
||
return web.json_response(config)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/vision/masked')
|
||
async def get_vision_config_masked(request):
|
||
"""
|
||
获取视觉模型配置(API Key掩码版本)
|
||
用于前端显示,不暴露完整API Key
|
||
"""
|
||
config = config_manager.get_vision_config_masked()
|
||
return web.json_response(config)
|
||
|
||
# --- 服务商管理API接口(v2.0)---
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/services')
|
||
async def get_services_list(request):
|
||
"""
|
||
获取所有服务商列表
|
||
返回所有已配置的服务商(不包含敏感信息)
|
||
"""
|
||
try:
|
||
services = config_manager.get_all_services()
|
||
|
||
# 移除敏感信息
|
||
safe_services = []
|
||
for service in services:
|
||
safe_service = service.copy()
|
||
# 移除加密的API Key
|
||
if 'api_key_encrypted' in safe_service:
|
||
del safe_service['api_key_encrypted']
|
||
# 添加掩码信息
|
||
safe_service['has_api_key'] = bool(service.get('api_key_encrypted'))
|
||
safe_services.append(safe_service)
|
||
|
||
return web.json_response({
|
||
"success": True,
|
||
"services": safe_services
|
||
})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 获取服务商列表失败 | 错误:{str(e)}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/services/{{service_id}}/masked')
|
||
async def get_service_masked(request):
|
||
"""
|
||
获取指定服务商配置(掩码版本)
|
||
API Key为掩码,不暴露完整值
|
||
"""
|
||
try:
|
||
service_id = request.match_info['service_id']
|
||
service = config_manager.get_service(service_id)
|
||
|
||
if not service:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": f"服务商不存在: {service_id}"
|
||
}, status=404)
|
||
|
||
# 掩码处理
|
||
safe_service = service.copy()
|
||
if 'api_key_encrypted' in safe_service:
|
||
# 解密后掩码
|
||
from .utils.common import SimpleEncryption
|
||
api_key = SimpleEncryption.decrypt(safe_service['api_key_encrypted'])
|
||
safe_service['api_key_masked'] = config_manager.mask_api_key(api_key)
|
||
safe_service['api_key_exists'] = bool(api_key)
|
||
del safe_service['api_key_encrypted']
|
||
|
||
return web.json_response({
|
||
"success": True,
|
||
"service": safe_service
|
||
})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 获取服务商配置失败 | 错误:{str(e)}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/services')
|
||
async def create_service(request):
|
||
"""
|
||
创建新的服务商
|
||
请求体: {type, name, base_url, api_key, description}
|
||
"""
|
||
try:
|
||
data = await request.json()
|
||
|
||
service_type = data.get('type')
|
||
name = data.get('name')
|
||
base_url = data.get('base_url', '')
|
||
api_key = data.get('api_key', '')
|
||
description = data.get('description', '')
|
||
|
||
if not service_type or not name:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少必需参数: type 和 name"
|
||
}, status=400)
|
||
|
||
service_id = config_manager.create_service(
|
||
service_type=service_type,
|
||
name=name,
|
||
base_url=base_url,
|
||
api_key=api_key,
|
||
description=description
|
||
)
|
||
|
||
if service_id:
|
||
print(f"{PREFIX} 成功创建服务商 | 名称:{name} | ID:{service_id}")
|
||
return web.json_response({
|
||
"success": True,
|
||
"service_id": service_id
|
||
})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "创建服务商失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 创建服务商异常 | 错误:{str(e)}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
@PromptServer.instance.routes.put(f'{API_PREFIX}/services/{{service_id}}')
|
||
async def update_service_config(request):
|
||
"""
|
||
更新服务商配置
|
||
请求体: {name, description, base_url, api_key, auto_unload}
|
||
注意:仅在payload包含api_key时才更新API Key
|
||
"""
|
||
try:
|
||
service_id = request.match_info['service_id']
|
||
data = await request.json()
|
||
|
||
# 更新服务商
|
||
success = config_manager.update_service(service_id, **data)
|
||
|
||
if success:
|
||
print(f"{PREFIX} 成功更新服务商 | ID:{service_id}")
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "更新服务商失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 更新服务商异常 | 错误:{str(e)}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
@PromptServer.instance.routes.delete(f'{API_PREFIX}/services/{{service_id}}')
|
||
async def delete_service(request):
|
||
"""
|
||
删除服务商
|
||
"""
|
||
try:
|
||
service_id = request.match_info['service_id']
|
||
|
||
success = config_manager.delete_service(service_id)
|
||
|
||
if success:
|
||
print(f"{PREFIX} 成功删除服务商 | ID:{service_id}")
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "删除服务商失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 删除服务商异常 | 错误:{str(e)}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/services/{{service_id}}')
|
||
async def get_service(request):
|
||
"""
|
||
获取指定服务商的配置
|
||
"""
|
||
try:
|
||
service_id = request.match_info.get('service_id')
|
||
service = config_manager.get_service(service_id)
|
||
|
||
if service:
|
||
return web.json_response({
|
||
"success": True,
|
||
"service": service
|
||
})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": f"服务商不存在: {service_id}"
|
||
}, status=404)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 获取服务商失败: {str(e)}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/services/current')
|
||
async def set_current_service_api(request):
|
||
"""
|
||
设置当前使用的服务商
|
||
请求体: {service_type: 'llm'|'vlm'|'translate', service_id: string, model_name?: string}
|
||
"""
|
||
try:
|
||
data = await request.json()
|
||
service_type = data.get('service_type')
|
||
service_id = data.get('service_id')
|
||
model_name = data.get('model_name') # 可选参数
|
||
|
||
# 验证参数
|
||
if not service_type or not service_id:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少必要参数: service_type 和 service_id"
|
||
}, status=400)
|
||
|
||
if service_type not in ['llm', 'vlm', 'translate']:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "service_type必须为'llm'、'vlm'或'translate'"
|
||
}, status=400)
|
||
|
||
# 调用配置管理器设置服务(现在支持model_name参数)
|
||
success = config_manager.set_current_service(service_type, service_id, model_name)
|
||
|
||
if success:
|
||
return web.json_response({
|
||
"success": True,
|
||
"message": f"成功设置当前{service_type}服务"
|
||
})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "设置服务失败"
|
||
}, status=500)
|
||
except Exception as e:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
# ====================== 模型列表API ======================
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/services/{{service_id}}/models')
|
||
async def get_service_models(request):
|
||
"""
|
||
获取服务商的模型列表
|
||
"""
|
||
try:
|
||
service_id = request.match_info.get('service_id')
|
||
|
||
# 获取服务商信息
|
||
service = config_manager.get_service(service_id)
|
||
|
||
if not service:
|
||
print(f"{ERROR_PREFIX} 服务商不存在 | ID:{service_id}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": f"服务商不存在: {service_id}"
|
||
}, status=404)
|
||
|
||
base_url = service.get('base_url', '')
|
||
api_key = service.get('api_key', '')
|
||
service_type = service.get('type', 'openai_compatible')
|
||
|
||
# 特殊处理:智谱服务强制使用 'zhipu' 类型(使用预定义列表)
|
||
if service_id == 'zhipu':
|
||
service_type = 'zhipu'
|
||
|
||
# 获取模型列表(新格式包含success和error)
|
||
result = get_models_from_service(base_url, api_key, service_type)
|
||
|
||
# 检查是否成功
|
||
if not result.get('success'):
|
||
error_msg = result.get('error', '未知错误')
|
||
print(f"{ERROR_PREFIX} 获取模型列表失败: {error_msg}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": error_msg
|
||
}, status=400)
|
||
|
||
models = result.get('models', {'llm': [], 'vlm': []})
|
||
|
||
# 统计模型总数(LLM和VLM列表相同,只统计一个)
|
||
total_count = len(models.get('llm', []))
|
||
print(f"{PREFIX} 模型列表获取成功 | 共 {total_count} 个模型")
|
||
|
||
return web.json_response({
|
||
"success": True,
|
||
"models": models
|
||
})
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 获取模型列表失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/services/{{service_id}}/models')
|
||
async def add_model_to_service_api(request):
|
||
"""
|
||
添加模型到服务商
|
||
|
||
Request body: {model_type, model_name, temperature, top_p, max_tokens}
|
||
"""
|
||
try:
|
||
service_id = request.match_info.get('service_id')
|
||
data = await request.json()
|
||
|
||
model_type = data.get('model_type')
|
||
model_name = data.get('model_name')
|
||
temperature = data.get('temperature', 0.7)
|
||
top_p = data.get('top_p', 0.9)
|
||
max_tokens = data.get('max_tokens', 1024)
|
||
|
||
if not model_type or not model_name:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少必需参数: model_type 和 model_name"
|
||
}, status=400)
|
||
|
||
success = config_manager.add_model_to_service(
|
||
service_id, model_type, model_name, temperature, top_p, max_tokens
|
||
)
|
||
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "添加模型失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 添加模型失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
|
||
@PromptServer.instance.routes.delete(f'{API_PREFIX}/services/{{service_id}}/models/{{model_type}}/{{model_name}}')
|
||
async def delete_model_from_service_api(request):
|
||
"""
|
||
从服务商删除模型
|
||
"""
|
||
try:
|
||
service_id = request.match_info.get('service_id')
|
||
model_type = request.match_info.get('model_type')
|
||
model_name = request.match_info.get('model_name')
|
||
|
||
success = config_manager.delete_model_from_service(service_id, model_type, model_name)
|
||
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "删除模型失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 删除模型失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
|
||
@PromptServer.instance.routes.put(f'{API_PREFIX}/services/{{service_id}}/models/default')
|
||
async def set_default_model_api(request):
|
||
"""
|
||
设置默认模型
|
||
|
||
Request body: {model_type, model_name}
|
||
"""
|
||
try:
|
||
service_id = request.match_info.get('service_id')
|
||
data = await request.json()
|
||
|
||
model_type = data.get('model_type')
|
||
model_name = data.get('model_name')
|
||
|
||
if not model_type or not model_name:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少必需参数: model_type 和 model_name"
|
||
}, status=400)
|
||
|
||
success = config_manager.set_default_model(service_id, model_type, model_name)
|
||
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "设置默认模型失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 设置默认模型失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
|
||
@PromptServer.instance.routes.put(f'{API_PREFIX}/services/{{service_id}}/models/order')
|
||
async def update_model_order_api(request):
|
||
"""
|
||
更新模型顺序
|
||
|
||
Request body: {model_type, model_names}
|
||
"""
|
||
try:
|
||
service_id = request.match_info.get('service_id')
|
||
data = await request.json()
|
||
|
||
model_type = data.get('model_type')
|
||
model_names = data.get('model_names', [])
|
||
|
||
if not model_type:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少必需参数: model_type"
|
||
}, status=400)
|
||
|
||
success = config_manager.update_model_order(service_id, model_type, model_names)
|
||
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "更新模型顺序失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 更新模型顺序失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
|
||
@PromptServer.instance.routes.put(f'{API_PREFIX}/services/order')
|
||
async def update_services_order_api(request):
|
||
"""
|
||
更新服务商顺序
|
||
|
||
Request body: {service_ids: ['id1', 'id2', ...]}
|
||
"""
|
||
try:
|
||
data = await request.json()
|
||
service_ids = data.get('service_ids', [])
|
||
|
||
if not service_ids:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少必需参数: service_ids"
|
||
}, status=400)
|
||
|
||
success = config_manager.update_services_order(service_ids)
|
||
|
||
if success:
|
||
print(f"{PREFIX} 成功更新服务商顺序 | 顺序:{', '.join(service_ids)}")
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "更新服务商顺序失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 更新服务商顺序失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
|
||
@PromptServer.instance.routes.put(f'{API_PREFIX}/services/{{service_id}}/models/parameter')
|
||
async def update_model_parameter_api(request):
|
||
"""
|
||
更新模型参数
|
||
|
||
Request body: {model_type, model_name, parameter_name, parameter_value}
|
||
"""
|
||
try:
|
||
service_id = request.match_info.get('service_id')
|
||
data = await request.json()
|
||
|
||
model_type = data.get('model_type')
|
||
model_name = data.get('model_name')
|
||
parameter_name = data.get('parameter_name')
|
||
parameter_value = data.get('parameter_value')
|
||
|
||
if not all([model_type, model_name, parameter_name, parameter_value is not None]):
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少必需参数"
|
||
}, status=400)
|
||
|
||
success = config_manager.update_model_parameter(
|
||
service_id, model_type, model_name, parameter_name, parameter_value
|
||
)
|
||
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "更新模型参数失败"
|
||
}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 更新模型参数失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": str(e)
|
||
}, status=500)
|
||
|
||
|
||
# ====================== 配置API ======================
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/system_prompts')
|
||
async def get_system_prompts_config(request):
|
||
"""获取系统提示词配置"""
|
||
config = config_manager.get_system_prompts()
|
||
return web.json_response(config)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/default_system_prompts')
|
||
async def get_default_system_prompts_config(request):
|
||
"""获取默认系统提示词配置"""
|
||
default_prompts = config_manager.default_system_prompts
|
||
return web.json_response(default_prompts)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/tags')
|
||
async def get_tags_config(request):
|
||
"""获取标签配置(已废弃,tags.json 已被 CSV 系统替代)"""
|
||
try:
|
||
# tags.json 已废弃,返回空对象
|
||
return web.json_response({})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 标签配置加载失败 | 错误:{str(e)}")
|
||
return web.json_response({"error": str(e)}, status=500)
|
||
|
||
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/tags_user')
|
||
async def get_user_tags_config(request):
|
||
"""获取用户自定义标签配置"""
|
||
try:
|
||
# 使用配置管理器加载用户标签
|
||
user_tags = config_manager.load_user_tags()
|
||
|
||
return web.json_response(user_tags)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 用户标签配置加载失败 | 错误:{str(e)}")
|
||
return web.json_response({"error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/tags_user')
|
||
async def update_user_tags_config(request):
|
||
"""更新用户自定义标签配置"""
|
||
try:
|
||
data = await request.json()
|
||
|
||
# 检查数据结构是否正确
|
||
if not isinstance(data, dict):
|
||
print(f"{ERROR_PREFIX} 用户标签配置更新失败 | 错误:参数格式错误")
|
||
return web.json_response({"error": "参数格式错误"}, status=400)
|
||
|
||
# 使用配置管理器保存用户标签
|
||
success = config_manager.save_user_tags(data)
|
||
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
print(f"{ERROR_PREFIX} 用户标签配置文件更新失败")
|
||
return web.json_response({"error": "保存失败"}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 用户标签配置更新异常 | 错误:{str(e)}")
|
||
return web.json_response({"error": str(e)}, status=500)
|
||
|
||
# ---CSV标签系统API---
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/tags_files')
|
||
async def get_tags_files(request):
|
||
"""获取tags目录下所有CSV文件列表"""
|
||
try:
|
||
files = config_manager.list_tags_files()
|
||
return web.json_response({"success": True, "files": files})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 获取标签文件列表失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/tags_csv/{{filename}}')
|
||
async def get_tags_csv(request):
|
||
"""加载指定CSV标签文件"""
|
||
try:
|
||
filename = request.match_info.get('filename')
|
||
if not filename or not filename.endswith('.csv'):
|
||
return web.json_response({"success": False, "error": "无效的文件名"}, status=400)
|
||
|
||
tags_data = config_manager.load_tags_csv(filename)
|
||
return web.json_response({"success": True, "data": tags_data})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 加载CSV标签文件失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/tags_csv/{{filename}}')
|
||
async def save_tags_csv(request):
|
||
"""保存标签数据到指定CSV文件"""
|
||
try:
|
||
filename = request.match_info.get('filename')
|
||
if not filename or not filename.endswith('.csv'):
|
||
return web.json_response({"success": False, "error": "无效的文件名"}, status=400)
|
||
|
||
data = await request.json()
|
||
tags_data = data.get('data', {})
|
||
|
||
success = config_manager.save_tags_csv(filename, tags_data)
|
||
if success:
|
||
print(f"{PREFIX} CSV标签文件保存成功 | 文件:{filename}")
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({"success": False, "error": "保存失败"}, status=500)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 保存CSV标签文件失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/tags_selection')
|
||
async def get_tags_selection(request):
|
||
"""获取用户选择的标签文件"""
|
||
try:
|
||
selection = config_manager.get_tags_selection()
|
||
return web.json_response({"success": True, "selection": selection})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 获取标签选择失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/tags_selection')
|
||
async def save_tags_selection(request):
|
||
"""保存用户选择的标签文件"""
|
||
try:
|
||
data = await request.json()
|
||
success = config_manager.save_tags_selection(data)
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({"success": False, "error": "保存失败"}, status=500)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 保存标签选择失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.get(f'{API_PREFIX}/config/favorites')
|
||
async def get_favorites(request):
|
||
"""获取收藏列表"""
|
||
try:
|
||
favorites = config_manager.get_favorites()
|
||
return web.json_response({"success": True, "favorites": favorites})
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 获取收藏列表失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/favorites/add')
|
||
async def add_favorite(request):
|
||
"""添加单个收藏"""
|
||
try:
|
||
data = await request.json()
|
||
tag_value = data.get('tag_value')
|
||
tag_name = data.get('tag_name') # 获取可选的标签名称
|
||
category = data.get('category') # 获取可选的分类(来源文件)
|
||
if not tag_value:
|
||
return web.json_response({"success": False, "error": "缺少tag_value参数"}, status=400)
|
||
|
||
success = config_manager.add_favorite(tag_value, tag_name, category)
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({"success": False, "error": "添加失败"}, status=500)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 添加收藏失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/favorites/remove')
|
||
async def remove_favorite(request):
|
||
"""移除单个收藏"""
|
||
try:
|
||
data = await request.json()
|
||
tag_value = data.get('tag_value')
|
||
category = data.get('category')
|
||
if not tag_value:
|
||
return web.json_response({"success": False, "error": "缺少tag_value参数"}, status=400)
|
||
|
||
success = config_manager.remove_favorite(tag_value, category)
|
||
if success:
|
||
return web.json_response({"success": True})
|
||
else:
|
||
return web.json_response({"success": False, "error": "移除失败"}, status=500)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 移除收藏失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/baidu_translate')
|
||
async def update_baidu_translate_config(request):
|
||
"""更新百度翻译配置"""
|
||
try:
|
||
data = await request.json()
|
||
app_id = data.get('app_id')
|
||
secret_key = data.get('secret_key')
|
||
|
||
# 获取当前配置
|
||
current_config = config_manager.get_baidu_translate_config()
|
||
|
||
# 如果提供了 app_id,则更新 app_id
|
||
if app_id is not None:
|
||
current_config['app_id'] = app_id
|
||
|
||
# 如果提供了 secret_key,则更新 secret_key
|
||
if secret_key is not None:
|
||
current_config['secret_key'] = secret_key
|
||
|
||
# 更新配置
|
||
success = config_manager.update_baidu_translate_config(
|
||
current_config['app_id'],
|
||
current_config['secret_key']
|
||
)
|
||
|
||
if success:
|
||
return web.json_response({'message': '配置已更新'})
|
||
else:
|
||
print(f"{ERROR_PREFIX} 百度翻译配置更新失败")
|
||
return web.json_response({'error': '配置更新失败'}, status=500)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 百度翻译配置更新异常 | 错误:{str(e)}")
|
||
return web.json_response({'error': str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/system_prompts')
|
||
async def update_system_prompts_config(request):
|
||
"""更新系统提示词配置"""
|
||
try:
|
||
data = await request.json()
|
||
|
||
# 检查数据结构是否正确
|
||
if not isinstance(data, dict):
|
||
print(f"{ERROR_PREFIX} 系统提示词配置更新失败 | 错误:参数格式错误")
|
||
return web.json_response({'error': '参数格式错误'}, status=400)
|
||
|
||
# 前端会直接发送包含 active_prompts 的完整配置,后端只需验证并保存
|
||
if 'active_prompts' not in data:
|
||
print(f"{WARN_PREFIX} 系统提示词配置更新警告 | 错误:缺少 active_prompts,尝试从现有配置恢复")
|
||
# 即使缺少,也尝试从当前配置中恢复,以增强健壮性
|
||
current_config = config_manager.get_system_prompts()
|
||
data['active_prompts'] = current_config.get('active_prompts', {
|
||
"expand": None, "vision_zh": None, "vision_en": None
|
||
})
|
||
|
||
# 将 active_prompts 分离出来单独处理
|
||
active_prompts_to_update = data.pop('active_prompts', None)
|
||
|
||
# 清理提示词数据中可能存在的isActive标志(以防万一)
|
||
for prompt_type in ['expand_prompts', 'vision_prompts']:
|
||
if prompt_type in data:
|
||
for prompt_id, prompt_data in data[prompt_type].items():
|
||
if isinstance(prompt_data, dict) and 'isActive' in prompt_data:
|
||
del prompt_data['isActive']
|
||
|
||
# 更新提示词定义
|
||
success = config_manager.update_system_prompts(data)
|
||
|
||
# 如果有 active_prompts,则更新它们
|
||
if active_prompts_to_update:
|
||
success = success and config_manager.update_active_prompts(active_prompts_to_update)
|
||
|
||
if success:
|
||
active_prompts = active_prompts_to_update or {}
|
||
expand_id = active_prompts.get('expand', '无')
|
||
vision_zh_id = active_prompts.get('vision_zh', '无')
|
||
vision_en_id = active_prompts.get('vision_en', '无')
|
||
print(f"{PREFIX} 系统提示词配置更新成功 | 激活提示词: 提示词优化={expand_id}, 中文反推={vision_zh_id}, 英文反推={vision_en_id}")
|
||
|
||
# 在更新配置后验证激活提示词
|
||
print(f"{PREFIX} 正在验证更新后的激活提示词配置...")
|
||
config_manager.validate_and_fix_active_prompts()
|
||
|
||
return web.json_response({'success': True})
|
||
else:
|
||
print(f"{ERROR_PREFIX} 系统提示词配置更新失败")
|
||
return web.json_response({'error': '配置更新失败'}, status=500)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 系统提示词配置更新异常 | 错误:{str(e)}")
|
||
return web.json_response({'error': str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/active_prompt')
|
||
async def update_active_prompt_config(request):
|
||
"""更新单个激活的提示词配置"""
|
||
try:
|
||
data = await request.json()
|
||
prompt_type = data.get('type')
|
||
prompt_id = data.get('prompt_id')
|
||
|
||
if not prompt_type or not prompt_id:
|
||
return web.json_response({'error': '缺少参数 type 或 prompt_id'}, status=400)
|
||
|
||
success = config_manager.update_active_prompt(prompt_type, prompt_id)
|
||
|
||
if success:
|
||
print(f"{PREFIX} 激活的提示词已更新 | 类型:{prompt_type} | ID:{prompt_id}")
|
||
|
||
# 在更新单个激活提示词后验证配置
|
||
print(f"{PREFIX} 正在验证更新后的激活提示词配置...")
|
||
config_manager.validate_and_fix_active_prompts()
|
||
|
||
return web.json_response({'success': True})
|
||
else:
|
||
print(f"{ERROR_PREFIX} 激活的提示词更新失败")
|
||
return web.json_response({'error': '配置更新失败'}, status=500)
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 激活的提示词更新异常 | 错误:{str(e)}")
|
||
return web.json_response({'error': str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/llm')
|
||
async def update_llm_config(request):
|
||
"""更新LLM配置"""
|
||
try:
|
||
data = await request.json()
|
||
current_provider = data.get('current_provider')
|
||
providers = data.get('providers', {})
|
||
|
||
# 确保至少有一个参数提供
|
||
if not current_provider and not providers:
|
||
print(f"{ERROR_PREFIX} LLM配置更新失败 | 错误:参数不能全为空")
|
||
return web.json_response({'error': '参数不能全为空'}, status=400)
|
||
|
||
success = True
|
||
|
||
# 先更新各提供商的配置
|
||
if providers:
|
||
# 逐个更新各提供商的配置
|
||
for provider, provider_config in providers.items():
|
||
if provider not in ['zhipu', 'siliconflow', '302ai', 'ollama', 'custom']:
|
||
continue
|
||
|
||
model = provider_config.get('model')
|
||
api_key = provider_config.get('api_key')
|
||
base_url = provider_config.get('base_url')
|
||
temperature = provider_config.get('temperature')
|
||
max_tokens = provider_config.get('max_tokens')
|
||
top_p = provider_config.get('top_p')
|
||
auto_unload = provider_config.get('auto_unload')
|
||
|
||
# 更新配置,但不更新current_provider
|
||
success = success and config_manager.update_llm_config(
|
||
provider=provider,
|
||
model=model,
|
||
base_url=base_url,
|
||
api_key=api_key,
|
||
temperature=temperature,
|
||
max_tokens=max_tokens,
|
||
top_p=top_p,
|
||
auto_unload=auto_unload,
|
||
update_current=False
|
||
)
|
||
|
||
# 最后更新当前提供商
|
||
if current_provider:
|
||
success = success and config_manager.update_llm_config(
|
||
provider=current_provider,
|
||
update_current=True
|
||
)
|
||
|
||
if success:
|
||
print(f"{PREFIX} LLM配置更新成功")
|
||
return web.json_response({'success': True})
|
||
else:
|
||
print(f"{ERROR_PREFIX} LLM配置更新失败")
|
||
return web.json_response({'error': '配置更新失败'}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} LLM配置更新异常 | 错误:{str(e)}")
|
||
return web.json_response({'error': str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/config/vision')
|
||
async def update_vision_config(request):
|
||
"""更新视觉模型配置"""
|
||
try:
|
||
data = await request.json()
|
||
current_provider = data.get('current_provider')
|
||
providers = data.get('providers', {})
|
||
|
||
# 确保至少有一个参数提供
|
||
if not current_provider and not providers:
|
||
print(f"{ERROR_PREFIX} 视觉模型配置更新失败 | 错误:参数不能全为空")
|
||
return web.json_response({'error': '参数不能全为空'}, status=400)
|
||
|
||
success = True
|
||
|
||
# 先更新各提供商的配置
|
||
if providers:
|
||
# 逐个更新各提供商的配置
|
||
for provider, provider_config in providers.items():
|
||
if provider not in ['zhipu', 'siliconflow', '302ai', 'ollama', 'custom']:
|
||
continue
|
||
|
||
model = provider_config.get('model')
|
||
api_key = provider_config.get('api_key')
|
||
base_url = provider_config.get('base_url')
|
||
temperature = provider_config.get('temperature')
|
||
max_tokens = provider_config.get('max_tokens')
|
||
top_p = provider_config.get('top_p')
|
||
auto_unload = provider_config.get('auto_unload')
|
||
|
||
# 更新配置,但不更新current_provider
|
||
success = success and config_manager.update_vision_config(
|
||
provider=provider,
|
||
model=model,
|
||
base_url=base_url,
|
||
api_key=api_key,
|
||
temperature=temperature,
|
||
max_tokens=max_tokens,
|
||
top_p=top_p,
|
||
auto_unload=auto_unload,
|
||
update_current=False
|
||
)
|
||
|
||
# 最后更新当前提供商
|
||
if current_provider:
|
||
success = success and config_manager.update_vision_config(
|
||
provider=current_provider,
|
||
update_current=True
|
||
)
|
||
|
||
if success:
|
||
print(f"{PREFIX} 视觉模型配置更新成功")
|
||
return web.json_response({'success': True})
|
||
else:
|
||
print(f"{ERROR_PREFIX} 视觉模型配置更新失败")
|
||
return web.json_response({'error': '配置更新失败'}, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 视觉模型配置更新异常 | 错误:{str(e)}")
|
||
return web.json_response({'error': str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/request/cancel')
|
||
async def cancel_request(request):
|
||
"""取消一个正在进行的异步请求"""
|
||
try:
|
||
data = await request.json()
|
||
request_id = data.get("request_id")
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
task = ACTIVE_TASKS.get(request_id)
|
||
if task and not task.done():
|
||
task.cancel()
|
||
# 从字典中移除已取消的任务
|
||
del ACTIVE_TASKS[request_id]
|
||
print(f"{WARN_PREFIX} 请求已取消 | ID:{request_id}")
|
||
return web.json_response({"success": True, "message": "请求已取消"})
|
||
else:
|
||
return web.json_response({"success": False, "error": "未找到活动任务或任务已完成"}, status=404)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} 取消请求时发生异常 | 错误:{error_msg}")
|
||
return web.json_response({"success": False, "error": error_msg}, status=500)
|
||
|
||
# 新增API路由
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/baidu/translate')
|
||
async def baidu_translate(request):
|
||
"""
|
||
百度翻译API
|
||
注意:所有的格式处理(包括序号"1."格式保护等)均由前端的promptFormatter.js负责,
|
||
后端只负责原始文本的翻译,不进行格式预处理或后处理。
|
||
"""
|
||
request_id = None
|
||
try:
|
||
data = await request.json()
|
||
text = data.get("text")
|
||
from_lang = data.get("from", "auto")
|
||
to_lang = data.get("to", "zh")
|
||
request_id = data.get("request_id")
|
||
is_auto = data.get("is_auto", False)
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
# 准备阶段日志
|
||
from_lang_name = {"auto": "自动", "zh": "中文", "en": "英文"}.get(from_lang, from_lang)
|
||
to_lang_name = {"zh": "中文", "en": "英文"}.get(to_lang, to_lang)
|
||
log_prepare(TASK_TRANSLATE, request_id, SOURCE_FRONTEND, "百度翻译", None, None, {"方向": f"{from_lang_name}→{to_lang_name}", "长度": len(text)})
|
||
|
||
# 创建并注册任务
|
||
task = asyncio.create_task(BaiduTranslateService.translate(text, from_lang, to_lang, request_id, is_auto, task_type=TASK_TRANSLATE, source=SOURCE_FRONTEND))
|
||
ACTIVE_TASKS[request_id] = task
|
||
|
||
result = await task
|
||
|
||
# 服务层已输出错误日志,此处不再重复输出
|
||
return web.json_response(result)
|
||
except asyncio.CancelledError:
|
||
print(f"\r{_ANSI_CLEAR_EOL}{WARN_PREFIX} 百度翻译任务被取消 | ID:{request_id}", flush=True)
|
||
return web.json_response({"success": False, "error": "请求已取消", "cancelled": True}, status=400)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} 百度翻译请求异常 | 错误:{error_msg}")
|
||
return web.json_response({"success": False, "error": error_msg})
|
||
finally:
|
||
if request_id and request_id in ACTIVE_TASKS:
|
||
del ACTIVE_TASKS[request_id]
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/llm/expand')
|
||
async def llm_expand(request):
|
||
"""LLM扩写API"""
|
||
request_id = None
|
||
try:
|
||
data = await request.json()
|
||
prompt = data.get("prompt")
|
||
request_id = data.get("request_id")
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
# 准备阶段日志
|
||
from .services.thinking_control import build_thinking_suppression
|
||
from .utils.common import format_model_with_thinking
|
||
from .services.openai_base import OpenAICompatibleService
|
||
|
||
llm_config = config_manager.get_llm_config()
|
||
if llm_config:
|
||
provider = llm_config.get('provider', 'ollama')
|
||
model = llm_config.get('model', '')
|
||
provider_display = OpenAICompatibleService.get_provider_display_name(provider)
|
||
|
||
# 检查disable_thinking配置
|
||
service = config_manager.get_service(provider)
|
||
disable_thinking_enabled = service.get('disable_thinking', True) if service else True
|
||
|
||
thinking_extra = build_thinking_suppression(provider, model) if disable_thinking_enabled else None
|
||
model_display = format_model_with_thinking(model, bool(thinking_extra))
|
||
|
||
# 获取规则名称
|
||
system_prompts = config_manager.get_system_prompts()
|
||
rule_name = "未知规则"
|
||
if system_prompts and 'expand_prompts' in system_prompts:
|
||
active_prompt_id = system_prompts.get('active_prompts', {}).get('expand', 'expand_default')
|
||
if active_prompt_id in system_prompts['expand_prompts']:
|
||
rule_name = system_prompts['expand_prompts'][active_prompt_id].get('name', active_prompt_id)
|
||
elif len(system_prompts['expand_prompts']) > 0:
|
||
# 如果激活的ID不存在,使用第一个
|
||
first_id = list(system_prompts['expand_prompts'].keys())[0]
|
||
rule_name = system_prompts['expand_prompts'][first_id].get('name', first_id)
|
||
|
||
log_prepare(TASK_EXPAND, request_id, SOURCE_FRONTEND, provider_display, model_display, rule_name, {"长度": len(prompt)})
|
||
|
||
# 创建并注册任务
|
||
task = asyncio.create_task(LLMService.expand_prompt(prompt, request_id, task_type=TASK_EXPAND, source=SOURCE_FRONTEND))
|
||
ACTIVE_TASKS[request_id] = task
|
||
|
||
result = await task
|
||
|
||
# 服务层已输出错误日志,此处不再重复输出
|
||
return web.json_response(result)
|
||
except asyncio.CancelledError:
|
||
print(f"\r{_ANSI_CLEAR_EOL}{WARN_PREFIX} LLM提示词优化任务被取消 | ID:{request_id}", flush=True)
|
||
return web.json_response({"success": False, "error": "请求已取消", "cancelled": True}, status=400)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} LLM提示词优化请求异常 | 请求ID:{request_id if request_id else '未知'} | 错误:{error_msg}")
|
||
return web.json_response({"success": False, "error": error_msg})
|
||
finally:
|
||
if request_id and request_id in ACTIVE_TASKS:
|
||
del ACTIVE_TASKS[request_id]
|
||
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/llm/translate')
|
||
async def llm_translate(request):
|
||
"""LLM翻译API"""
|
||
request_id = None
|
||
try:
|
||
data = await request.json()
|
||
text = data.get("text")
|
||
from_lang = data.get("from", "auto")
|
||
to_lang = data.get("to", "zh")
|
||
request_id = data.get("request_id")
|
||
is_auto = data.get("is_auto", False)
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
# 准备阶段日志
|
||
from .services.thinking_control import build_thinking_suppression
|
||
from .utils.common import format_model_with_thinking
|
||
from .services.openai_base import OpenAICompatibleService
|
||
|
||
prefix = AUTO_TRANSLATE_REQUEST_PREFIX if is_auto else REQUEST_PREFIX
|
||
from_lang_name = {"auto": "自动检测", "zh": "中文", "en": "英文"}.get(from_lang, from_lang)
|
||
to_lang_name = {"zh": "中文", "en": "英文"}.get(to_lang, to_lang)
|
||
|
||
# 使用独立的翻译配置
|
||
translate_config = config_manager.get_translate_config()
|
||
if translate_config:
|
||
provider = translate_config.get('provider', 'baidu')
|
||
model = translate_config.get('model', '')
|
||
provider_display = OpenAICompatibleService.get_provider_display_name(provider)
|
||
|
||
# 检查disable_thinking配置
|
||
service = config_manager.get_service(provider)
|
||
disable_thinking_enabled = service.get('disable_thinking', True) if service else True
|
||
|
||
thinking_extra = build_thinking_suppression(provider, model) if disable_thinking_enabled else None
|
||
model_display = format_model_with_thinking(model, bool(thinking_extra))
|
||
|
||
log_prepare(TASK_TRANSLATE, request_id, SOURCE_FRONTEND, provider_display, model_display, None, {"方向": f"{from_lang_name}→{to_lang_name}", "长度": len(text)})
|
||
|
||
# 创建并注册任务
|
||
task = asyncio.create_task(LLMService.translate(
|
||
text=text,
|
||
from_lang=from_lang,
|
||
to_lang=to_lang,
|
||
request_id=request_id,
|
||
custom_provider=provider if translate_config else None,
|
||
custom_provider_config=translate_config,
|
||
cancel_event=None,
|
||
task_type=TASK_TRANSLATE,
|
||
source=SOURCE_FRONTEND
|
||
))
|
||
ACTIVE_TASKS[request_id] = task
|
||
|
||
result = await task
|
||
|
||
# 服务层已输出错误日志,此处不再重复输出
|
||
return web.json_response(result)
|
||
except asyncio.CancelledError:
|
||
print(f"\r{_ANSI_CLEAR_EOL}{WARN_PREFIX} LLM翻译任务被取消 | ID:{request_id}", flush=True)
|
||
return web.json_response({"success": False, "error": "请求已取消", "cancelled": True}, status=400)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} LLM翻译请求异常 | 请求ID:{request_id if request_id else '未知'} | 错误:{error_msg}")
|
||
return web.json_response({"success": False, "error": error_msg})
|
||
finally:
|
||
if request_id and request_id in ACTIVE_TASKS:
|
||
del ACTIVE_TASKS[request_id]
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/vlm/analyze')
|
||
async def vlm_analyze(request):
|
||
"""视觉分析API"""
|
||
request_id = None
|
||
try:
|
||
data = await request.json()
|
||
image_data = data.get("image")
|
||
request_id = data.get("request_id")
|
||
# 从请求中获取提示词内容,这是关键的修复
|
||
prompt_content = data.get("prompt")
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
# 准备阶段日志
|
||
from .services.thinking_control import build_thinking_suppression
|
||
from .utils.common import format_model_with_thinking
|
||
from .services.openai_base import OpenAICompatibleService
|
||
|
||
vision_config = config_manager.get_vision_config()
|
||
if vision_config:
|
||
provider = vision_config.get('provider', 'ollama')
|
||
model = vision_config.get('model', '')
|
||
provider_display = OpenAICompatibleService.get_provider_display_name(provider)
|
||
|
||
# 检查disable_thinking配置
|
||
service = config_manager.get_service(provider)
|
||
disable_thinking_enabled = service.get('disable_thinking', True) if service else True
|
||
|
||
thinking_extra = build_thinking_suppression(provider, model) if disable_thinking_enabled else None
|
||
model_display = format_model_with_thinking(model, bool(thinking_extra))
|
||
|
||
# 获取规则名称
|
||
rule_name = "默认规则"
|
||
if prompt_content:
|
||
# 如果前端传了自定义提示词,尝试匹配规则名称
|
||
system_prompts = config_manager.get_system_prompts()
|
||
if system_prompts and 'vision_prompts' in system_prompts:
|
||
# 尝试从vision_prompts中匹配
|
||
for prompt_id, prompt_data in system_prompts['vision_prompts'].items():
|
||
if prompt_data.get('content') == prompt_content:
|
||
rule_name = prompt_data.get('name', prompt_id)
|
||
break
|
||
else:
|
||
# 如果没有匹配到,说明是自定义的
|
||
rule_name = "自定义规则"
|
||
else:
|
||
# 使用激活的系统提示词(需要判断语言)
|
||
# 这里简化为获取激活的中文规则,实际应该根据提示词判断语言
|
||
system_prompts = config_manager.get_system_prompts()
|
||
if system_prompts:
|
||
active_prompts = system_prompts.get('active_prompts', {})
|
||
# 优先使用中文规则
|
||
active_prompt_id = active_prompts.get('vision_zh')
|
||
if active_prompt_id and 'vision_prompts' in system_prompts:
|
||
if active_prompt_id in system_prompts['vision_prompts']:
|
||
rule_name = system_prompts['vision_prompts'][active_prompt_id].get('name', active_prompt_id)
|
||
|
||
log_prepare(TASK_IMAGE_CAPTION, request_id, SOURCE_FRONTEND, provider_display, model_display, rule_name)
|
||
|
||
# 创建并注册任务,使用新的接口签名
|
||
task = asyncio.create_task(VisionService.analyze_image(
|
||
image_data=image_data,
|
||
request_id=request_id,
|
||
prompt_content=prompt_content,
|
||
task_type=TASK_IMAGE_CAPTION,
|
||
source=SOURCE_FRONTEND
|
||
))
|
||
ACTIVE_TASKS[request_id] = task
|
||
|
||
result = await task
|
||
|
||
# 服务层已输出错误日志,此处不再重复输出
|
||
return web.json_response(result)
|
||
except asyncio.CancelledError:
|
||
print(f"\r{_ANSI_CLEAR_EOL}{WARN_PREFIX} 视觉分析任务被取消 | ID:{request_id}", flush=True)
|
||
return web.json_response({"success": False, "error": "请求已取消", "cancelled": True}, status=400)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} 视觉分析请求异常 | 请求ID:{request_id if request_id else '未知'} | 错误:{error_msg}")
|
||
return web.json_response({"success": False, "error": error_msg})
|
||
finally:
|
||
if request_id and request_id in ACTIVE_TASKS:
|
||
del ACTIVE_TASKS[request_id]
|
||
|
||
# ---流式输出API(SSE)---
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/vlm/analyze/stream')
|
||
async def vlm_analyze_stream(request):
|
||
"""
|
||
视觉分析API(流式版本)
|
||
使用 Server-Sent Events (SSE) 逐 token 推送分析结果
|
||
"""
|
||
request_id = None
|
||
response = None
|
||
try:
|
||
data = await request.json()
|
||
image_data = data.get("image")
|
||
request_id = data.get("request_id")
|
||
prompt_content = data.get("prompt")
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
# 创建 SSE 响应
|
||
response = web.StreamResponse(
|
||
status=200,
|
||
headers={
|
||
'Content-Type': 'text/event-stream',
|
||
'Cache-Control': 'no-cache',
|
||
'Connection': 'keep-alive',
|
||
'X-Accel-Buffering': 'no', # 禁用 nginx 缓冲
|
||
}
|
||
)
|
||
await response.prepare(request)
|
||
|
||
# 准备阶段日志
|
||
from .services.thinking_control import build_thinking_suppression
|
||
from .utils.common import format_model_with_thinking
|
||
from .services.openai_base import OpenAICompatibleService
|
||
|
||
vision_config = config_manager.get_vision_config()
|
||
if vision_config:
|
||
provider = vision_config.get('provider', 'ollama')
|
||
model = vision_config.get('model', '')
|
||
provider_display = OpenAICompatibleService.get_provider_display_name(provider)
|
||
|
||
service = config_manager.get_service(provider)
|
||
disable_thinking_enabled = service.get('disable_thinking', True) if service else True
|
||
|
||
thinking_extra = build_thinking_suppression(provider, model) if disable_thinking_enabled else None
|
||
model_display = format_model_with_thinking(model, bool(thinking_extra))
|
||
|
||
# 获取规则名称
|
||
rule_name = "默认规则"
|
||
if prompt_content:
|
||
system_prompts = config_manager.get_system_prompts()
|
||
if system_prompts and 'vision_prompts' in system_prompts:
|
||
for prompt_id, prompt_data in system_prompts['vision_prompts'].items():
|
||
if prompt_data.get('content') == prompt_content:
|
||
rule_name = prompt_data.get('name', prompt_id)
|
||
break
|
||
else:
|
||
rule_name = "自定义规则"
|
||
|
||
log_prepare(TASK_IMAGE_CAPTION, request_id, SOURCE_FRONTEND, provider_display, model_display, rule_name)
|
||
|
||
# 用于收集完整内容
|
||
full_content = []
|
||
|
||
# 定义流式回调
|
||
def stream_callback(chunk):
|
||
full_content.append(chunk)
|
||
# 将 chunk 作为 SSE 事件发送(同步版本,由调用者处理)
|
||
return chunk
|
||
|
||
# 创建任务
|
||
async def run_analysis():
|
||
return await VisionService.analyze_image(
|
||
image_data=image_data,
|
||
request_id=request_id,
|
||
prompt_content=prompt_content,
|
||
stream_callback=stream_callback,
|
||
task_type=TASK_IMAGE_CAPTION,
|
||
source=SOURCE_FRONTEND
|
||
)
|
||
|
||
task = asyncio.create_task(run_analysis())
|
||
ACTIVE_TASKS[request_id] = task
|
||
|
||
# 轮询并发送流式内容
|
||
last_sent_index = 0
|
||
while not task.done():
|
||
# 发送新的 chunks
|
||
while last_sent_index < len(full_content):
|
||
chunk = full_content[last_sent_index]
|
||
sse_data = json.dumps({"chunk": chunk}, ensure_ascii=False)
|
||
await response.write(f"data: {sse_data}\n\n".encode('utf-8'))
|
||
last_sent_index += 1
|
||
await asyncio.sleep(0.05) # 50ms 轮询间隔
|
||
|
||
# 发送剩余的 chunks
|
||
while last_sent_index < len(full_content):
|
||
chunk = full_content[last_sent_index]
|
||
sse_data = json.dumps({"chunk": chunk}, ensure_ascii=False)
|
||
await response.write(f"data: {sse_data}\n\n".encode('utf-8'))
|
||
last_sent_index += 1
|
||
|
||
# 获取结果
|
||
result = await task
|
||
|
||
# 发送完成信号
|
||
done_data = json.dumps({"done": True, "result": result}, ensure_ascii=False)
|
||
await response.write(f"data: {done_data}\n\n".encode('utf-8'))
|
||
|
||
await response.write_eof()
|
||
return response
|
||
|
||
except asyncio.CancelledError:
|
||
print(f"\r{_ANSI_CLEAR_EOL}{WARN_PREFIX} 流式视觉分析任务被取消 | ID:{request_id}", flush=True)
|
||
if response:
|
||
error_data = json.dumps({"error": "请求已取消", "cancelled": True}, ensure_ascii=False)
|
||
await response.write(f"data: {error_data}\n\n".encode('utf-8'))
|
||
await response.write_eof()
|
||
return response if response else web.json_response({"success": False, "error": "请求已取消", "cancelled": True}, status=400)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} 流式视觉分析请求异常 | 请求ID:{request_id if request_id else '未知'} | 错误:{error_msg}")
|
||
if response:
|
||
error_data = json.dumps({"error": error_msg}, ensure_ascii=False)
|
||
await response.write(f"data: {error_data}\n\n".encode('utf-8'))
|
||
await response.write_eof()
|
||
return response
|
||
return web.json_response({"success": False, "error": error_msg})
|
||
finally:
|
||
if request_id and request_id in ACTIVE_TASKS:
|
||
del ACTIVE_TASKS[request_id]
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/llm/expand/stream')
|
||
async def llm_expand_stream(request):
|
||
"""
|
||
LLM扩写API(流式版本)
|
||
使用 Server-Sent Events (SSE) 逐 token 推送扩写结果
|
||
"""
|
||
request_id = None
|
||
response = None
|
||
try:
|
||
data = await request.json()
|
||
prompt = data.get("prompt")
|
||
request_id = data.get("request_id")
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
# 创建 SSE 响应
|
||
response = web.StreamResponse(
|
||
status=200,
|
||
headers={
|
||
'Content-Type': 'text/event-stream',
|
||
'Cache-Control': 'no-cache',
|
||
'Connection': 'keep-alive',
|
||
'X-Accel-Buffering': 'no',
|
||
}
|
||
)
|
||
await response.prepare(request)
|
||
|
||
# 准备阶段日志
|
||
from .services.thinking_control import build_thinking_suppression
|
||
from .utils.common import format_model_with_thinking
|
||
from .services.openai_base import OpenAICompatibleService
|
||
|
||
llm_config = config_manager.get_llm_config()
|
||
if llm_config:
|
||
provider = llm_config.get('provider', 'ollama')
|
||
model = llm_config.get('model', '')
|
||
provider_display = OpenAICompatibleService.get_provider_display_name(provider)
|
||
|
||
service = config_manager.get_service(provider)
|
||
disable_thinking_enabled = service.get('disable_thinking', True) if service else True
|
||
|
||
thinking_extra = build_thinking_suppression(provider, model) if disable_thinking_enabled else None
|
||
model_display = format_model_with_thinking(model, bool(thinking_extra))
|
||
|
||
# 获取规则名称
|
||
system_prompts = config_manager.get_system_prompts()
|
||
rule_name = "未知规则"
|
||
if system_prompts and 'expand_prompts' in system_prompts:
|
||
active_prompt_id = system_prompts.get('active_prompts', {}).get('expand', 'expand_default')
|
||
if active_prompt_id in system_prompts['expand_prompts']:
|
||
rule_name = system_prompts['expand_prompts'][active_prompt_id].get('name', active_prompt_id)
|
||
elif len(system_prompts['expand_prompts']) > 0:
|
||
first_id = list(system_prompts['expand_prompts'].keys())[0]
|
||
rule_name = system_prompts['expand_prompts'][first_id].get('name', first_id)
|
||
|
||
log_prepare(TASK_EXPAND, request_id, SOURCE_FRONTEND, provider_display, model_display, rule_name, {"长度": len(prompt)})
|
||
|
||
# 用于收集完整内容
|
||
full_content = []
|
||
|
||
# 定义流式回调
|
||
def stream_callback(chunk):
|
||
full_content.append(chunk)
|
||
return chunk
|
||
|
||
# 创建任务
|
||
async def run_expand():
|
||
return await LLMService.expand_prompt(
|
||
prompt=prompt,
|
||
request_id=request_id,
|
||
stream_callback=stream_callback,
|
||
task_type=TASK_EXPAND,
|
||
source=SOURCE_FRONTEND
|
||
)
|
||
|
||
task = asyncio.create_task(run_expand())
|
||
ACTIVE_TASKS[request_id] = task
|
||
|
||
# 轮询并发送流式内容
|
||
last_sent_index = 0
|
||
while not task.done():
|
||
while last_sent_index < len(full_content):
|
||
chunk = full_content[last_sent_index]
|
||
sse_data = json.dumps({"chunk": chunk}, ensure_ascii=False)
|
||
await response.write(f"data: {sse_data}\n\n".encode('utf-8'))
|
||
last_sent_index += 1
|
||
await asyncio.sleep(0.05)
|
||
|
||
# 发送剩余的 chunks
|
||
while last_sent_index < len(full_content):
|
||
chunk = full_content[last_sent_index]
|
||
sse_data = json.dumps({"chunk": chunk}, ensure_ascii=False)
|
||
await response.write(f"data: {sse_data}\n\n".encode('utf-8'))
|
||
last_sent_index += 1
|
||
|
||
# 获取结果
|
||
result = await task
|
||
|
||
# 发送完成信号
|
||
done_data = json.dumps({"done": True, "result": result}, ensure_ascii=False)
|
||
await response.write(f"data: {done_data}\n\n".encode('utf-8'))
|
||
|
||
await response.write_eof()
|
||
return response
|
||
|
||
except asyncio.CancelledError:
|
||
print(f"\r{_ANSI_CLEAR_EOL}{WARN_PREFIX} 流式LLM扩写任务被取消 | ID:{request_id}", flush=True)
|
||
if response:
|
||
error_data = json.dumps({"error": "请求已取消", "cancelled": True}, ensure_ascii=False)
|
||
await response.write(f"data: {error_data}\n\n".encode('utf-8'))
|
||
await response.write_eof()
|
||
return response if response else web.json_response({"success": False, "error": "请求已取消", "cancelled": True}, status=400)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} 流式LLM扩写请求异常 | 请求ID:{request_id if request_id else '未知'} | 错误:{error_msg}")
|
||
if response:
|
||
error_data = json.dumps({"error": error_msg}, ensure_ascii=False)
|
||
await response.write(f"data: {error_data}\n\n".encode('utf-8'))
|
||
await response.write_eof()
|
||
return response
|
||
return web.json_response({"success": False, "error": error_msg})
|
||
finally:
|
||
if request_id and request_id in ACTIVE_TASKS:
|
||
del ACTIVE_TASKS[request_id]
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/llm/translate/stream')
|
||
async def llm_translate_stream(request):
|
||
"""
|
||
LLM翻译API(流式版本)
|
||
使用 Server-Sent Events (SSE) 逐 token 推送翻译结果
|
||
注意:仅支持LLM翻译,百度翻译不支持流式,请使用原有接口
|
||
"""
|
||
request_id = None
|
||
response = None
|
||
try:
|
||
data = await request.json()
|
||
text = data.get("text")
|
||
from_lang = data.get("from", "auto")
|
||
to_lang = data.get("to", "zh")
|
||
request_id = data.get("request_id")
|
||
|
||
if not request_id:
|
||
return web.json_response({"success": False, "error": "缺少request_id"}, status=400)
|
||
|
||
if not text or text.strip() == '':
|
||
return web.json_response({"success": False, "error": "缺少翻译文本"}, status=400)
|
||
|
||
# 创建 SSE 响应
|
||
response = web.StreamResponse(
|
||
status=200,
|
||
headers={
|
||
'Content-Type': 'text/event-stream',
|
||
'Cache-Control': 'no-cache',
|
||
'Connection': 'keep-alive',
|
||
'X-Accel-Buffering': 'no',
|
||
}
|
||
)
|
||
await response.prepare(request)
|
||
|
||
# 准备阶段日志
|
||
from .services.thinking_control import build_thinking_suppression
|
||
from .utils.common import format_model_with_thinking
|
||
from .services.openai_base import OpenAICompatibleService
|
||
|
||
# 使用翻译服务配置(而非LLM配置)
|
||
translate_config = config_manager.get_translate_config()
|
||
if translate_config:
|
||
provider = translate_config.get('provider', 'ollama')
|
||
model = translate_config.get('model', '')
|
||
provider_display = OpenAICompatibleService.get_provider_display_name(provider)
|
||
|
||
service = config_manager.get_service(provider)
|
||
disable_thinking_enabled = service.get('disable_thinking', True) if service else True
|
||
|
||
thinking_extra = build_thinking_suppression(provider, model) if disable_thinking_enabled else None
|
||
model_display = format_model_with_thinking(model, bool(thinking_extra))
|
||
|
||
log_prepare(TASK_TRANSLATE, request_id, SOURCE_FRONTEND, provider_display, model_display, f"{from_lang}→{to_lang}", {"长度": len(text)})
|
||
|
||
# 用于收集完整内容
|
||
full_content = []
|
||
|
||
# 定义流式回调
|
||
def stream_callback(chunk):
|
||
full_content.append(chunk)
|
||
return chunk
|
||
|
||
# 创建任务
|
||
async def run_translate():
|
||
return await LLMService.translate(
|
||
text=text,
|
||
from_lang=from_lang,
|
||
to_lang=to_lang,
|
||
request_id=request_id,
|
||
stream_callback=stream_callback,
|
||
task_type=TASK_TRANSLATE,
|
||
source=SOURCE_FRONTEND
|
||
)
|
||
|
||
task = asyncio.create_task(run_translate())
|
||
ACTIVE_TASKS[request_id] = task
|
||
|
||
# 轮询并发送流式内容
|
||
last_sent_index = 0
|
||
while not task.done():
|
||
while last_sent_index < len(full_content):
|
||
chunk = full_content[last_sent_index]
|
||
sse_data = json.dumps({"chunk": chunk}, ensure_ascii=False)
|
||
await response.write(f"data: {sse_data}\n\n".encode('utf-8'))
|
||
last_sent_index += 1
|
||
await asyncio.sleep(0.05)
|
||
|
||
# 发送剩余的 chunks
|
||
while last_sent_index < len(full_content):
|
||
chunk = full_content[last_sent_index]
|
||
sse_data = json.dumps({"chunk": chunk}, ensure_ascii=False)
|
||
await response.write(f"data: {sse_data}\n\n".encode('utf-8'))
|
||
last_sent_index += 1
|
||
|
||
# 获取结果
|
||
result = await task
|
||
|
||
# 发送完成信号
|
||
done_data = json.dumps({"done": True, "result": result}, ensure_ascii=False)
|
||
await response.write(f"data: {done_data}\n\n".encode('utf-8'))
|
||
|
||
await response.write_eof()
|
||
return response
|
||
|
||
except asyncio.CancelledError:
|
||
print(f"\r{_ANSI_CLEAR_EOL}{WARN_PREFIX} 流式LLM翻译任务被取消 | ID:{request_id}", flush=True)
|
||
if response:
|
||
error_data = json.dumps({"error": "请求已取消", "cancelled": True}, ensure_ascii=False)
|
||
await response.write(f"data: {error_data}\n\n".encode('utf-8'))
|
||
await response.write_eof()
|
||
return response if response else web.json_response({"success": False, "error": "请求已取消", "cancelled": True}, status=400)
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} 流式LLM翻译请求异常 | 请求ID:{request_id if request_id else '未知'} | 错误:{error_msg}")
|
||
if response:
|
||
error_data = json.dumps({"error": error_msg}, ensure_ascii=False)
|
||
await response.write(f"data: {error_data}\n\n".encode('utf-8'))
|
||
await response.write_eof()
|
||
return response
|
||
return web.json_response({"success": False, "error": error_msg})
|
||
finally:
|
||
if request_id and request_id in ACTIVE_TASKS:
|
||
del ACTIVE_TASKS[request_id]
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/video/info')
|
||
async def get_video_info(request):
|
||
"""获取视频文件信息(FPS, 时长等)"""
|
||
try:
|
||
data = await request.json()
|
||
filename = data.get("filename")
|
||
subfolder = data.get("subfolder", "")
|
||
type_ = data.get("type", "input")
|
||
|
||
if not filename:
|
||
return web.json_response({"success": False, "error": "缺少文件名参数"}, status=400)
|
||
|
||
# 获取文件完整路径
|
||
file_path = None
|
||
|
||
# 1. 尝试直接作为绝对路径
|
||
if os.path.exists(filename):
|
||
file_path = filename
|
||
else:
|
||
# 2. 使用ComfyUI的路径解析
|
||
file_path = folder_paths.get_annotated_filepath(filename)
|
||
|
||
if not file_path or not os.path.exists(file_path):
|
||
# 3. 尝试在input目录查找
|
||
input_dir = folder_paths.get_input_directory()
|
||
possible_path = os.path.join(input_dir, filename)
|
||
if os.path.exists(possible_path):
|
||
file_path = possible_path
|
||
|
||
if not file_path or not os.path.exists(file_path):
|
||
return web.json_response({"success": False, "error": "找不到视频文件"}, status=404)
|
||
|
||
# 读取元数据
|
||
info_result = get_video_frame_info(file_path)
|
||
if info_result["success"]:
|
||
return web.json_response({
|
||
"success": True,
|
||
"fps": info_result["original_fps"],
|
||
"duration": info_result["duration"],
|
||
"total_frames": info_result["original_total_frames"],
|
||
"path": file_path
|
||
})
|
||
else:
|
||
return web.json_response({"success": False, "error": f"读取视频元数据失败: {info_result.get('error')}"}, status=500)
|
||
|
||
except Exception as e:
|
||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/models/list')
|
||
async def get_models_list(request):
|
||
"""
|
||
获取模型列表API
|
||
请求参数:
|
||
- provider: 服务提供商 (zhipu, siliconflow, 302ai, ollama, custom)
|
||
- model_type: 模型类型 ('llm' 或 'vision')
|
||
- recommended: 是否获取推荐列表 (可选,默认False)
|
||
"""
|
||
try:
|
||
data = await request.json()
|
||
provider = data.get("provider")
|
||
model_type = data.get("model_type", "llm")
|
||
recommended = data.get("recommended", False)
|
||
|
||
if not provider:
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": "缺少provider参数"
|
||
}, status=400)
|
||
|
||
# 如果请求推荐模型列表,直接返回静态列表
|
||
if recommended:
|
||
result = ModelListService.get_recommended_models(provider, model_type)
|
||
return web.json_response(result)
|
||
|
||
# 从配置中获取对应provider的API Key
|
||
if model_type == 'llm':
|
||
config = config_manager.get_llm_config()
|
||
else:
|
||
config = config_manager.get_vision_config()
|
||
|
||
# 获取对应provider的配置
|
||
providers_config = config.get('providers', {})
|
||
provider_config = providers_config.get(provider, {})
|
||
api_key = provider_config.get('api_key', '')
|
||
base_url = provider_config.get('base_url', '')
|
||
|
||
# 调用模型列表服务
|
||
result = ModelListService.get_models(provider, api_key, model_type, base_url)
|
||
|
||
return web.json_response(result)
|
||
|
||
except Exception as e:
|
||
error_msg = str(e)
|
||
print(f"{ERROR_PREFIX} 获取模型列表异常 | 错误:{error_msg}")
|
||
return web.json_response({
|
||
"success": False,
|
||
"error": error_msg
|
||
}, status=500)
|
||
|
||
# ---视频帧提取 API---
|
||
|
||
@PromptServer.instance.routes.post(f'{API_PREFIX}/video/frame')
|
||
async def get_video_frame(request):
|
||
"""
|
||
获取视频指定帧的图片
|
||
|
||
请求参数:
|
||
filename: 视频文件名
|
||
frame_index: 帧索引(基于 force_rate 后的帧序列)
|
||
force_rate: 强制帧率(可选,0 表示原始帧率)
|
||
type: 文件类型(input/output,默认 input)
|
||
|
||
返回:
|
||
success: 是否成功
|
||
data: base64 编码的 JPEG 图片
|
||
width/height: 图片尺寸
|
||
"""
|
||
try:
|
||
data = await request.json()
|
||
filename = data.get("filename")
|
||
frame_index = data.get("frame_index", 0)
|
||
force_rate = data.get("force_rate", 0)
|
||
type_ = data.get("type", "input")
|
||
|
||
if not filename:
|
||
return web.json_response({"success": False, "error": "缺少文件名参数"}, status=400)
|
||
|
||
# 获取文件完整路径
|
||
file_path = None
|
||
|
||
# 1. 尝试直接作为绝对路径
|
||
if os.path.exists(filename):
|
||
file_path = filename
|
||
else:
|
||
# 2. 使用 ComfyUI 的路径解析
|
||
file_path = folder_paths.get_annotated_filepath(filename)
|
||
|
||
if not file_path or not os.path.exists(file_path):
|
||
# 3. 尝试在 input 目录查找
|
||
input_dir = folder_paths.get_input_directory()
|
||
possible_path = os.path.join(input_dir, filename)
|
||
if os.path.exists(possible_path):
|
||
file_path = possible_path
|
||
|
||
if not file_path or not os.path.exists(file_path):
|
||
return web.json_response({"success": False, "error": "找不到视频文件"}, status=404)
|
||
|
||
# 调用帧提取函数
|
||
result = extract_frame_by_index(file_path, frame_index, force_rate)
|
||
|
||
if result["success"]:
|
||
return web.json_response(result)
|
||
else:
|
||
return web.json_response(result, status=500)
|
||
|
||
except Exception as e:
|
||
print(f"{ERROR_PREFIX} 帧提取失败 | 错误:{str(e)}")
|
||
return web.json_response({"success": False, "error": str(e)}, status=500) |