diff --git a/README.md b/README.md index 1d4e156..3287b1f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ - 由于Flow增加了额外的验证码,你可以自行选择使用浏览器打码或第三发打码: 注册[YesCaptcha](https://yescaptcha.com/i/13Xd8K)并获取api key,将其填入系统配置页面```YesCaptcha API密钥```区域 +- 自动更新st浏览器拓展:[Flow2API-Token-Updater](https://github.com/TheSmallHanCat/Flow2API-Token-Updater) + ### 方式一:Docker 部署(推荐) #### 标准模式(不使用代理) diff --git a/src/api/admin.py b/src/api/admin.py index 6e59300..94deac2 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,11 +1,12 @@ """Admin API routes""" -from fastapi import APIRouter, Depends, HTTPException, Header +from fastapi import APIRouter, Depends, HTTPException, Header, Request from fastapi.responses import JSONResponse from pydantic import BaseModel from typing import Optional, List import secrets from ..core.auth import AuthManager from ..core.database import Database +from ..core.config import config from ..services.token_manager import TokenManager from ..services.proxy_manager import ProxyManager @@ -646,15 +647,25 @@ async def get_logs( "operation": log.get("operation"), "status_code": log.get("status_code"), "duration": log.get("duration"), - "created_at": log.get("created_at") + "created_at": log.get("created_at"), + "request_body": log.get("request_body"), + "response_body": log.get("response_body") } for log in logs] +@router.delete("/api/logs") +async def clear_logs(token: str = Depends(verify_admin_token)): + """Clear all logs""" + try: + await db.clear_all_logs() + return {"success": True, "message": "所有日志已清空"} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + @router.get("/api/admin/config") async def get_admin_config(token: str = Depends(verify_admin_token)): """Get admin configuration""" - from ..core.config import config - admin_config = await db.get_admin_config() return { @@ -708,11 +719,9 @@ async def update_debug_config( ): """Update debug configuration""" try: - # Update debug config in database - await db.update_debug_config(enabled=request.enabled) - - # 🔥 Hot reload: sync database config to memory - await db.reload_config_to_memory() + # Update in-memory config only (not database) + # This ensures debug mode is automatically disabled on restart + config.set_debug_enabled(request.enabled) status = "enabled" if request.enabled else "disabled" return {"success": True, "message": f"Debug mode {status}", "enabled": request.enabled} @@ -883,26 +892,35 @@ async def get_captcha_config(token: str = Depends(verify_admin_token)): # ========== Plugin Configuration Endpoints ========== @router.get("/api/plugin/config") -async def get_plugin_config(token: str = Depends(verify_admin_token)): +async def get_plugin_config(request: Request, token: str = Depends(verify_admin_token)): """Get plugin configuration""" plugin_config = await db.get_plugin_config() - # Get server host and port from config - from ..core.config import config - server_host = config.server_host - server_port = config.server_port + # Get the actual domain and port from the request + # This allows the connection URL to reflect the user's actual access path + host_header = request.headers.get("host", "") - # Generate connection URL - if server_host == "0.0.0.0": - connection_url = f"http://127.0.0.1:{server_port}/api/plugin/update-token" + # Generate connection URL based on actual request + if host_header: + # Use the actual domain/IP and port from the request + connection_url = f"http://{host_header}/api/plugin/update-token" else: - connection_url = f"http://{server_host}:{server_port}/api/plugin/update-token" + # Fallback to config-based URL + from ..core.config import config + server_host = config.server_host + server_port = config.server_port + + if server_host == "0.0.0.0": + connection_url = f"http://127.0.0.1:{server_port}/api/plugin/update-token" + else: + connection_url = f"http://{server_host}:{server_port}/api/plugin/update-token" return { "success": True, "config": { "connection_token": plugin_config.connection_token, - "connection_url": connection_url + "connection_url": connection_url, + "auto_enable_on_update": plugin_config.auto_enable_on_update } } @@ -914,17 +932,22 @@ async def update_plugin_config( ): """Update plugin configuration""" connection_token = request.get("connection_token", "") + auto_enable_on_update = request.get("auto_enable_on_update", True) # 默认开启 # Generate random token if empty if not connection_token: connection_token = secrets.token_urlsafe(32) - await db.update_plugin_config(connection_token=connection_token) + await db.update_plugin_config( + connection_token=connection_token, + auto_enable_on_update=auto_enable_on_update + ) return { "success": True, "message": "插件配置更新成功", - "connection_token": connection_token + "connection_token": connection_token, + "auto_enable_on_update": auto_enable_on_update } @@ -989,6 +1012,16 @@ async def plugin_update_token(request: dict, authorization: Optional[str] = Head at_expires=at_expires ) + # Check if auto-enable is enabled and token is disabled + if plugin_config.auto_enable_on_update and not existing_token.is_active: + await token_manager.enable_token(existing_token.id) + return { + "success": True, + "message": f"Token updated and auto-enabled for {email}", + "action": "updated", + "auto_enabled": True + } + return { "success": True, "message": f"Token updated for {email}", diff --git a/src/core/database.py b/src/core/database.py index 8f1cfb1..2d4c808 100644 --- a/src/core/database.py +++ b/src/core/database.py @@ -305,6 +305,20 @@ class Database: except Exception as e: print(f" ✗ Failed to add column '{col_name}': {e}") + # Check and add missing columns to plugin_config table + if await self._table_exists(db, "plugin_config"): + plugin_columns_to_add = [ + ("auto_enable_on_update", "BOOLEAN DEFAULT 1"), # 默认开启 + ] + + for col_name, col_type in plugin_columns_to_add: + if not await self._column_exists(db, "plugin_config", col_name): + try: + await db.execute(f"ALTER TABLE plugin_config ADD COLUMN {col_name} {col_type}") + print(f" ✓ Added column '{col_name}' to plugin_config table") + except Exception as e: + print(f" ✗ Failed to add column '{col_name}': {e}") + # ========== Step 3: Ensure all config tables have default rows ========== # Note: This will NOT overwrite existing config rows # It only ensures missing rows are created with default values from setting.toml @@ -996,6 +1010,12 @@ class Database: rows = await cursor.fetchall() return [dict(row) for row in rows] + async def clear_all_logs(self): + """Clear all request logs""" + async with aiosqlite.connect(self.db_path) as db: + await db.execute("DELETE FROM request_logs") + await db.commit() + async def init_config_from_toml(self, config_dict: dict, is_first_startup: bool = True): """ Initialize database configuration from setting.toml @@ -1227,7 +1247,7 @@ class Database: return PluginConfig(**dict(row)) return PluginConfig() - async def update_plugin_config(self, connection_token: str): + async def update_plugin_config(self, connection_token: str, auto_enable_on_update: bool = True): """Update plugin configuration""" async with aiosqlite.connect(self.db_path) as db: db.row_factory = aiosqlite.Row @@ -1237,13 +1257,13 @@ class Database: if row: await db.execute(""" UPDATE plugin_config - SET connection_token = ?, updated_at = CURRENT_TIMESTAMP + SET connection_token = ?, auto_enable_on_update = ?, updated_at = CURRENT_TIMESTAMP WHERE id = 1 - """, (connection_token,)) + """, (connection_token, auto_enable_on_update)) else: await db.execute(""" - INSERT INTO plugin_config (id, connection_token) - VALUES (1, ?) - """, (connection_token,)) + INSERT INTO plugin_config (id, connection_token, auto_enable_on_update) + VALUES (1, ?, ?) + """, (connection_token, auto_enable_on_update)) await db.commit() diff --git a/src/core/models.py b/src/core/models.py index 27c19a8..75f957f 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -162,6 +162,7 @@ class PluginConfig(BaseModel): """Plugin connection configuration""" id: int = 1 connection_token: str = "" # 插件连接token + auto_enable_on_update: bool = True # 更新token时自动启用(默认开启) created_at: Optional[datetime] = None updated_at: Optional[datetime] = None diff --git a/src/services/generation_handler.py b/src/services/generation_handler.py index b9611fe..a1d8aed 100644 --- a/src/services/generation_handler.py +++ b/src/services/generation_handler.py @@ -102,6 +102,33 @@ MODEL_CONFIG = { "supports_images": False }, + # veo_3_1_t2v_fast_portrait_ultra (竖屏) + "veo_3_1_t2v_fast_portrait_ultra": { + "type": "video", + "video_type": "t2v", + "model_key": "veo_3_1_t2v_fast_portrait_ultra", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": False + }, + + # veo_3_1_t2v_fast_portrait_ultra_relaxed (竖屏) + "veo_3_1_t2v_fast_portrait_ultra_relaxed": { + "type": "video", + "video_type": "t2v", + "model_key": "veo_3_1_t2v_fast_portrait_ultra_relaxed", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": False + }, + + # veo_3_1_t2v_portrait (竖屏) + "veo_3_1_t2v_portrait": { + "type": "video", + "video_type": "t2v", + "model_key": "veo_3_1_t2v_portrait", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": False + }, + # ========== 首尾帧模型 (I2V - Image to Video) ========== # 支持1-2张图片:1张作为首帧,2张作为首尾帧 @@ -165,6 +192,66 @@ MODEL_CONFIG = { "max_images": 2 }, + # veo_3_1_i2v_s_fast_ultra (需要新增横竖屏) + "veo_3_1_i2v_s_fast_ultra_portrait": { + "type": "video", + "video_type": "i2v", + "model_key": "veo_3_1_i2v_s_fast_ultra", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": True, + "min_images": 1, + "max_images": 2 + }, + "veo_3_1_i2v_s_fast_ultra_landscape": { + "type": "video", + "video_type": "i2v", + "model_key": "veo_3_1_i2v_s_fast_ultra", + "aspect_ratio": "VIDEO_ASPECT_RATIO_LANDSCAPE", + "supports_images": True, + "min_images": 1, + "max_images": 2 + }, + + # veo_3_1_i2v_s_fast_ultra_relaxed (需要新增横竖屏) + "veo_3_1_i2v_s_fast_ultra_relaxed_portrait": { + "type": "video", + "video_type": "i2v", + "model_key": "veo_3_1_i2v_s_fast_ultra_relaxed", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": True, + "min_images": 1, + "max_images": 2 + }, + "veo_3_1_i2v_s_fast_ultra_relaxed_landscape": { + "type": "video", + "video_type": "i2v", + "model_key": "veo_3_1_i2v_s_fast_ultra_relaxed", + "aspect_ratio": "VIDEO_ASPECT_RATIO_LANDSCAPE", + "supports_images": True, + "min_images": 1, + "max_images": 2 + }, + + # veo_3_1_i2v_s (需要新增横竖屏) + "veo_3_1_i2v_s_portrait": { + "type": "video", + "video_type": "i2v", + "model_key": "veo_3_1_i2v_s", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": True, + "min_images": 1, + "max_images": 2 + }, + "veo_3_1_i2v_s_landscape": { + "type": "video", + "video_type": "i2v", + "model_key": "veo_3_1_i2v_s", + "aspect_ratio": "VIDEO_ASPECT_RATIO_LANDSCAPE", + "supports_images": True, + "min_images": 1, + "max_images": 2 + }, + # ========== 多图生成 (R2V - Reference Images to Video) ========== # 支持多张图片,不限制数量 @@ -186,6 +273,46 @@ MODEL_CONFIG = { "supports_images": True, "min_images": 0, "max_images": None # 不限制 + }, + + # veo_3_0_r2v_fast_ultra (需要新增横竖屏) + "veo_3_0_r2v_fast_ultra_portrait": { + "type": "video", + "video_type": "r2v", + "model_key": "veo_3_0_r2v_fast_ultra", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": True, + "min_images": 0, + "max_images": None # 不限制 + }, + "veo_3_0_r2v_fast_ultra_landscape": { + "type": "video", + "video_type": "r2v", + "model_key": "veo_3_0_r2v_fast_ultra", + "aspect_ratio": "VIDEO_ASPECT_RATIO_LANDSCAPE", + "supports_images": True, + "min_images": 0, + "max_images": None # 不限制 + }, + + # veo_3_0_r2v_fast_ultra_relaxed (需要新增横竖屏) + "veo_3_0_r2v_fast_ultra_relaxed_portrait": { + "type": "video", + "video_type": "r2v", + "model_key": "veo_3_0_r2v_fast_ultra_relaxed", + "aspect_ratio": "VIDEO_ASPECT_RATIO_PORTRAIT", + "supports_images": True, + "min_images": 0, + "max_images": None # 不限制 + }, + "veo_3_0_r2v_fast_ultra_relaxed_landscape": { + "type": "video", + "video_type": "r2v", + "model_key": "veo_3_0_r2v_fast_ultra_relaxed", + "aspect_ratio": "VIDEO_ASPECT_RATIO_LANDSCAPE", + "supports_images": True, + "min_images": 0, + "max_images": None # 不限制 } } @@ -343,11 +470,25 @@ class GenerationHandler: # 7. 记录成功日志 duration = time.time() - start_time + + # 构建响应数据,包含生成的URL + response_data = { + "status": "success", + "model": model, + "prompt": prompt[:100] + } + + # 添加生成的URL(如果有) + if hasattr(self, '_last_generated_url') and self._last_generated_url: + response_data["url"] = self._last_generated_url + # 清除临时存储 + self._last_generated_url = None + await self._log_request( token.id, f"generate_{generation_type}", {"model": model, "prompt": prompt[:100], "has_images": images is not None and len(images) > 0}, - {"status": "success"}, + response_data, 200, duration ) @@ -358,12 +499,8 @@ class GenerationHandler: if stream: yield self._create_stream_chunk(f"❌ {error_msg}\n") if token: - # 检测429错误,立即禁用token - if "429" in str(e) or "HTTP Error 429" in str(e): - debug_logger.log_warning(f"[429_BAN] Token {token.id} 遇到429错误,立即禁用") - await self.token_manager.ban_token_for_429(token.id) - else: - await self.token_manager.record_error(token.id) + # 记录错误(所有错误统一处理,不再特殊处理429) + await self.token_manager.record_error(token.id) yield self._create_error_response(error_msg) # 记录失败日志 @@ -464,6 +601,9 @@ class GenerationHandler: yield self._create_stream_chunk("缓存已关闭,正在返回源链接...\n") # 返回结果 + # 存储URL用于日志记录 + self._last_generated_url = local_url + if stream: yield self._create_stream_chunk( f"", @@ -732,6 +872,9 @@ class GenerationHandler: completed_at=time.time() ) + # 存储URL用于日志记录 + self._last_generated_url = local_url + # 返回结果 if stream: yield self._create_stream_chunk( diff --git a/static/manage.html b/static/manage.html index c8c0b26..d4d7440 100644 --- a/static/manage.html +++ b/static/manage.html @@ -339,6 +339,13 @@
用于验证Chrome扩展插件的身份,留空将自动生成随机token
+当插件更新token时,如果该token被禁用,则自动启用它
+ℹ️ 使用说明:安装Chrome扩展后,将连接接口和Token配置到插件中,插件会自动提取Google Labs的cookie并更新到系统 @@ -392,11 +399,19 @@
| 状态码 | 耗时(秒) | 时间 | +详情 | @@ -417,6 +433,26 @@ + +
|---|