diff --git a/src/api/admin.py b/src/api/admin.py index c659729..5fd6809 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1391,7 +1391,26 @@ async def cancel_task(task_id: str, token: str = Depends(verify_admin_token)): for log in logs: if log.get("task_id") == task_id and log.get("status_code") == -1: import time - duration = time.time() - (log.get("created_at").timestamp() if log.get("created_at") else time.time()) + from datetime import datetime + + # Calculate duration + created_at = log.get("created_at") + if created_at: + # If created_at is a string, parse it + if isinstance(created_at, str): + try: + created_at = datetime.fromisoformat(created_at.replace('Z', '+00:00')) + duration = time.time() - created_at.timestamp() + except: + duration = 0 + # If it's already a datetime object + elif isinstance(created_at, datetime): + duration = time.time() - created_at.timestamp() + else: + duration = 0 + else: + duration = 0 + await db.update_request_log( log.get("id"), response_body='{"error": "用户手动取消任务"}', diff --git a/src/core/database.py b/src/core/database.py index 1a3f298..7564e17 100644 --- a/src/core/database.py +++ b/src/core/database.py @@ -180,6 +180,30 @@ class Database: VALUES (1, ?) """, (at_auto_refresh_enabled,)) + # Ensure call_logic_config has a row + cursor = await db.execute("SELECT COUNT(*) FROM call_logic_config") + count = await cursor.fetchone() + if count[0] == 0: + # Get call logic config from config_dict if provided, otherwise use defaults + call_mode = "default" + polling_mode_enabled = False + + if config_dict: + call_logic_config = config_dict.get("call_logic", {}) + call_mode = call_logic_config.get("call_mode", "default") + # Normalize call_mode + if call_mode not in ("default", "polling"): + # Check legacy polling_mode_enabled field + polling_mode_enabled = call_logic_config.get("polling_mode_enabled", False) + call_mode = "polling" if polling_mode_enabled else "default" + else: + polling_mode_enabled = call_mode == "polling" + + await db.execute(""" + INSERT INTO call_logic_config (id, call_mode, polling_mode_enabled) + VALUES (1, ?, ?) + """, (call_mode, polling_mode_enabled)) + async def check_and_migrate_db(self, config_dict: dict = None): """Check database integrity and perform migrations if needed @@ -386,6 +410,9 @@ class Database: admin_password TEXT DEFAULT 'admin', api_key TEXT DEFAULT 'han1234', error_ban_threshold INTEGER DEFAULT 3, + task_retry_enabled BOOLEAN DEFAULT 1, + task_max_retries INTEGER DEFAULT 3, + auto_disable_on_401 BOOLEAN DEFAULT 1, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) @@ -1199,10 +1226,10 @@ class Database: normalized = "polling" if call_mode == "polling" else "default" polling_mode_enabled = normalized == "polling" async with aiosqlite.connect(self.db_path) as db: + # Use INSERT OR REPLACE to ensure the row exists await db.execute(""" - UPDATE call_logic_config - SET polling_mode_enabled = ?, call_mode = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = 1 - """, (polling_mode_enabled, normalized)) + INSERT OR REPLACE INTO call_logic_config (id, call_mode, polling_mode_enabled, updated_at) + VALUES (1, ?, ?, CURRENT_TIMESTAMP) + """, (normalized, polling_mode_enabled)) await db.commit() diff --git a/static/manage.html b/static/manage.html index cb9b7f5..462f93e 100644 --- a/static/manage.html +++ b/static/manage.html @@ -1086,9 +1086,9 @@ saveGenerationTimeout=async()=>{const imageTimeout=parseInt($('cfgImageTimeout').value)||300,videoTimeout=parseInt($('cfgVideoTimeout').value)||1500;console.log('保存生成超时配置:',{imageTimeout,videoTimeout});if(imageTimeout<60||imageTimeout>3600)return showToast('图片超时时间必须在 60-3600 秒之间','error');if(videoTimeout<60||videoTimeout>7200)return showToast('视频超时时间必须在 60-7200 秒之间','error');try{const r=await apiRequest('/api/generation/timeout',{method:'POST',body:JSON.stringify({image_timeout:imageTimeout,video_timeout:videoTimeout})});if(!r){console.error('保存请求失败');return}const d=await r.json();console.log('保存结果:',d);if(d.success){showToast('生成超时配置保存成功','success');await new Promise(r=>setTimeout(r,200));await loadGenerationTimeout()}else{console.error('保存失败:',d);showToast('保存失败','error')}}catch(e){console.error('保存失败:',e);showToast('保存失败: '+e.message,'error')}}, toggleATAutoRefresh=async()=>{try{const enabled=$('atAutoRefreshToggle').checked;const r=await apiRequest('/api/token-refresh/enabled',{method:'POST',body:JSON.stringify({enabled:enabled})});if(!r){$('atAutoRefreshToggle').checked=!enabled;return}const d=await r.json();if(d.success){showToast(enabled?'AT自动刷新已启用':'AT自动刷新已禁用','success')}else{showToast('操作失败: '+(d.detail||'未知错误'),'error');$('atAutoRefreshToggle').checked=!enabled}}catch(e){showToast('操作失败: '+e.message,'error');$('atAutoRefreshToggle').checked=!enabled}}, loadATAutoRefreshConfig=async()=>{try{const r=await apiRequest('/api/token-refresh/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('atAutoRefreshToggle').checked=d.config.at_auto_refresh_enabled||false}else{console.error('AT自动刷新配置数据格式错误:',d)}}catch(e){console.error('加载AT自动刷新配置失败:',e)}}, - loadLogs=async()=>{try{const r=await apiRequest('/api/logs?limit=100');if(!r)return;const logs=await r.json();window.allLogs=logs;const tb=$('logsTableBody');tb.innerHTML=logs.map(l=>{const isProcessing=l.status_code===-1&&l.task_status==='processing';const isFailed=l.task_status==='failed';const isCompleted=l.task_status==='completed';const statusText=isProcessing?'处理中':l.status_code;const statusClass=isProcessing?'bg-blue-50 text-blue-700':l.status_code===200?'bg-green-50 text-green-700':'bg-red-50 text-red-700';let progressHtml='-';if(isProcessing&&l.task_status){const taskStatusMap={processing:'生成中',completed:'已完成',failed:'失败'};const taskStatusText=taskStatusMap[l.task_status]||l.task_status;const progress=l.progress||0;progressHtml=`
任务正在生成中...
${log.task_status?`状态: ${log.task_status}
`:''}文件URL:
${item.url}${JSON.stringify(responseBody,null,2)}${JSON.stringify(responseBody,null,2)}无响应数据
${log.response_body||'无'}${responseBody.error.message||responseBody.error||'未知错误'}
${log.response_body}${log.response_body}任务正在生成中...
${log.task_status?`状态: ${log.task_status}
`:''}文件URL:
${item.url}${JSON.stringify(responseBody,null,2)}${JSON.stringify(responseBody,null,2)}无响应数据
${log.response_body||'无'}${responseBody.error.message||responseBody.error||'未知错误'}
${log.response_body}${log.response_body}