diff --git a/app/services/grok/services/media.py b/app/services/grok/services/media.py index 37e186f..79d9b67 100644 --- a/app/services/grok/services/media.py +++ b/app/services/grok/services/media.py @@ -158,6 +158,14 @@ class VideoService: "message": f"{prompt} {mode_flag}", "toolOverrides": {"videoGen": True}, "enableSideBySide": True, + "deviceEnvInfo": { + "darkModeEnabled": False, + "devicePixelRatio": 2, + "screenWidth": 1920, + "screenHeight": 1080, + "viewportWidth": 1920, + "viewportHeight": 1080, + }, "responseMetadata": { "experiments": [], "modelConfigOverride": { @@ -166,7 +174,7 @@ class VideoService: "aspectRatio": aspect_ratio, "parentPostId": post_id, "resolutionName": resolution_name, - "videoLength": video_length + "videoLength": video_length, } } }, @@ -293,17 +301,19 @@ class VideoService: preset: str = "normal", ): """视频生成入口""" - # 获取 token + # 获取 token(使用智能路由) token_mgr = await get_token_manager() await token_mgr.reload_if_stale() - token = None - for pool_name in ModelService.pool_candidates_for_model(model): - token = token_mgr.get_token(pool_name) - if token: - break + # 使用智能路由选择 token(根据视频需求与候选池) + pool_candidates = ModelService.pool_candidates_for_model(model) + token_info = token_mgr.get_token_for_video( + resolution=resolution, + video_length=video_length, + pool_candidates=pool_candidates, + ) - if not token: + if not token_info: raise AppException( message="No available tokens. Please try again later.", error_type=ErrorType.RATE_LIMIT.value, @@ -311,6 +321,11 @@ class VideoService: status_code=429, ) + # 从 TokenInfo 对象中提取 token 字符串 + token = token_info.token + if token.startswith("sso="): + token = token[4:] + think = {"enabled": True, "disabled": False}.get(thinking) is_stream = stream if stream is not None else get_config("chat.stream") diff --git a/app/services/token/manager.py b/app/services/token/manager.py index 8ff5e74..b5e28f1 100644 --- a/app/services/token/manager.py +++ b/app/services/token/manager.py @@ -210,6 +210,94 @@ class TokenManager: return token[4:] return token + def get_token_info(self, pool_name: str = "ssoBasic") -> Optional["TokenInfo"]: + """ + 获取可用 Token 的完整信息 + + Args: + pool_name: Token 池名称 + + Returns: + TokenInfo 对象或 None + """ + pool = self.pools.get(pool_name) + if not pool: + logger.warning(f"Pool '{pool_name}' not found") + return None + + token_info = pool.select() + if not token_info: + logger.warning(f"No available token in pool '{pool_name}'") + return None + + return token_info + + def get_token_for_video( + self, + resolution: str = "480p", + video_length: int = 6, + pool_candidates: Optional[List[str]] = None, + ) -> Optional["TokenInfo"]: + """ + 根据视频需求智能选择 Token 池 + + 路由策略: + - 如果 resolution 是 "720p" 或 video_length > 6: 优先使用 "ssoSuper" 池 + - 否则优先使用 "ssoBasic" 池 + - 当提供 pool_candidates 时,按候选池顺序回退 + + Args: + resolution: 视频分辨率 ("480p" 或 "720p") + video_length: 视频时长(秒) + pool_candidates: 候选 Token 池(按优先级) + + Returns: + TokenInfo 对象或 None(无可用 token) + """ + # 确定首选池 + requires_super = resolution == "720p" or video_length > 6 + primary_pool = SUPER_POOL_NAME if requires_super else BASIC_POOL_NAME + + if pool_candidates: + ordered_pools = list(pool_candidates) + if primary_pool in ordered_pools: + ordered_pools.remove(primary_pool) + ordered_pools.insert(0, primary_pool) + else: + fallback_pool = BASIC_POOL_NAME if requires_super else SUPER_POOL_NAME + ordered_pools = [primary_pool, fallback_pool] + + for idx, pool_name in enumerate(ordered_pools): + token_info = self.get_token_info(pool_name) + if token_info: + if idx == 0: + logger.info( + f"Video token routing: resolution={resolution}, length={video_length}s -> " + f"pool={pool_name} (token={token_info.token[:10]}...)" + ) + else: + logger.info( + f"Video token routing: fallback from {ordered_pools[0]} -> {pool_name} " + f"(token={token_info.token[:10]}...)" + ) + return token_info + + if idx == 0 and requires_super and pool_name == primary_pool: + next_pool = ordered_pools[1] if len(ordered_pools) > 1 else None + if next_pool: + logger.warning( + f"Video token routing: {primary_pool} pool has no available token for " + f"resolution={resolution}, length={video_length}s. " + f"Falling back to {next_pool} pool." + ) + + # 两个池都没有可用 token + logger.warning( + f"Video token routing: no available token in any pool " + f"(resolution={resolution}, length={video_length}s)" + ) + return None + async def consume( self, token_str: str, effort: EffortType = EffortType.LOW ) -> bool: diff --git a/config.defaults.toml b/config.defaults.toml index cf95fdf..8694424 100644 --- a/config.defaults.toml +++ b/config.defaults.toml @@ -137,3 +137,4 @@ nsfw_max_concurrent = 10 nsfw_batch_size = 50 # NSFW 单次最大数量 nsfw_max_tokens = 1000 +