mirror of
https://github.com/TheSmallHanCat/sora2api.git
synced 2026-02-16 11:34:46 +08:00
feat: 新增外部pow获取
This commit is contained in:
@@ -171,6 +171,13 @@ class UpdatePowProxyConfigRequest(BaseModel):
|
|||||||
pow_proxy_enabled: bool
|
pow_proxy_enabled: bool
|
||||||
pow_proxy_url: Optional[str] = None
|
pow_proxy_url: Optional[str] = None
|
||||||
|
|
||||||
|
class UpdatePowServiceConfigRequest(BaseModel):
|
||||||
|
mode: str # "local" or "external"
|
||||||
|
server_url: Optional[str] = None
|
||||||
|
api_key: Optional[str] = None
|
||||||
|
proxy_enabled: Optional[bool] = None
|
||||||
|
proxy_url: Optional[str] = None
|
||||||
|
|
||||||
class BatchDisableRequest(BaseModel):
|
class BatchDisableRequest(BaseModel):
|
||||||
token_ids: List[int]
|
token_ids: List[int]
|
||||||
|
|
||||||
@@ -1373,16 +1380,17 @@ async def update_call_logic_config(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Failed to update call logic configuration: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Failed to update call logic configuration: {str(e)}")
|
||||||
|
|
||||||
# POW proxy config endpoints
|
# POW proxy config endpoints (redirected to pow_service config for unified management)
|
||||||
@router.get("/api/pow-proxy/config")
|
@router.get("/api/pow-proxy/config")
|
||||||
async def get_pow_proxy_config(token: str = Depends(verify_admin_token)) -> dict:
|
async def get_pow_proxy_config(token: str = Depends(verify_admin_token)) -> dict:
|
||||||
"""Get POW proxy configuration"""
|
"""Get POW proxy configuration (unified with pow_service config)"""
|
||||||
config_obj = await db.get_pow_proxy_config()
|
# Read from pow_service config for unified management
|
||||||
|
config_obj = await db.get_pow_service_config()
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"config": {
|
"config": {
|
||||||
"pow_proxy_enabled": config_obj.pow_proxy_enabled,
|
"pow_proxy_enabled": config_obj.proxy_enabled,
|
||||||
"pow_proxy_url": config_obj.pow_proxy_url or ""
|
"pow_proxy_url": config_obj.proxy_url or ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1391,11 +1399,20 @@ async def update_pow_proxy_config(
|
|||||||
request: UpdatePowProxyConfigRequest,
|
request: UpdatePowProxyConfigRequest,
|
||||||
token: str = Depends(verify_admin_token)
|
token: str = Depends(verify_admin_token)
|
||||||
):
|
):
|
||||||
"""Update POW proxy configuration"""
|
"""Update POW proxy configuration (unified with pow_service config)"""
|
||||||
try:
|
try:
|
||||||
await db.update_pow_proxy_config(request.pow_proxy_enabled, request.pow_proxy_url)
|
# Update pow_service config instead for unified management
|
||||||
config.set_pow_proxy_enabled(request.pow_proxy_enabled)
|
config_obj = await db.get_pow_service_config()
|
||||||
config.set_pow_proxy_url(request.pow_proxy_url or "")
|
await db.update_pow_service_config(
|
||||||
|
mode=config_obj.mode,
|
||||||
|
server_url=config_obj.server_url,
|
||||||
|
api_key=config_obj.api_key,
|
||||||
|
proxy_enabled=request.pow_proxy_enabled,
|
||||||
|
proxy_url=request.pow_proxy_url
|
||||||
|
)
|
||||||
|
# Update in-memory config
|
||||||
|
config.set_pow_service_proxy_enabled(request.pow_proxy_enabled)
|
||||||
|
config.set_pow_service_proxy_url(request.pow_proxy_url or "")
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "POW proxy configuration updated"
|
"message": "POW proxy configuration updated"
|
||||||
@@ -1403,6 +1420,50 @@ async def update_pow_proxy_config(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Failed to update POW proxy configuration: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Failed to update POW proxy configuration: {str(e)}")
|
||||||
|
|
||||||
|
# POW service config endpoints
|
||||||
|
@router.get("/api/pow/config")
|
||||||
|
async def get_pow_service_config(token: str = Depends(verify_admin_token)) -> dict:
|
||||||
|
"""Get POW service configuration"""
|
||||||
|
config_obj = await db.get_pow_service_config()
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"config": {
|
||||||
|
"mode": config_obj.mode,
|
||||||
|
"server_url": config_obj.server_url or "",
|
||||||
|
"api_key": config_obj.api_key or "",
|
||||||
|
"proxy_enabled": config_obj.proxy_enabled,
|
||||||
|
"proxy_url": config_obj.proxy_url or ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.post("/api/pow/config")
|
||||||
|
async def update_pow_service_config(
|
||||||
|
request: UpdatePowServiceConfigRequest,
|
||||||
|
token: str = Depends(verify_admin_token)
|
||||||
|
):
|
||||||
|
"""Update POW service configuration"""
|
||||||
|
try:
|
||||||
|
await db.update_pow_service_config(
|
||||||
|
mode=request.mode,
|
||||||
|
server_url=request.server_url,
|
||||||
|
api_key=request.api_key,
|
||||||
|
proxy_enabled=request.proxy_enabled,
|
||||||
|
proxy_url=request.proxy_url
|
||||||
|
)
|
||||||
|
# Update runtime config
|
||||||
|
config.set_pow_service_mode(request.mode)
|
||||||
|
config.set_pow_service_server_url(request.server_url or "")
|
||||||
|
config.set_pow_service_api_key(request.api_key or "")
|
||||||
|
config.set_pow_service_proxy_enabled(request.proxy_enabled or False)
|
||||||
|
config.set_pow_service_proxy_url(request.proxy_url or "")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "POW service configuration updated"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to update POW service configuration: {str(e)}")
|
||||||
|
|
||||||
# Task management endpoints
|
# Task management endpoints
|
||||||
@router.post("/api/tasks/{task_id}/cancel")
|
@router.post("/api/tasks/{task_id}/cancel")
|
||||||
async def cancel_task(task_id: str, token: str = Depends(verify_admin_token)):
|
async def cancel_task(task_id: str, token: str = Depends(verify_admin_token)):
|
||||||
|
|||||||
@@ -238,25 +238,96 @@ class Config:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def pow_proxy_enabled(self) -> bool:
|
def pow_proxy_enabled(self) -> bool:
|
||||||
"""Get POW proxy enabled status"""
|
"""Get POW proxy enabled status
|
||||||
|
|
||||||
|
DEPRECATED: This configuration is deprecated. Use pow_service_proxy_enabled instead.
|
||||||
|
All POW proxy settings are now unified under [pow_service] section.
|
||||||
|
"""
|
||||||
return self._config.get("pow_proxy", {}).get("pow_proxy_enabled", False)
|
return self._config.get("pow_proxy", {}).get("pow_proxy_enabled", False)
|
||||||
|
|
||||||
def set_pow_proxy_enabled(self, enabled: bool):
|
def set_pow_proxy_enabled(self, enabled: bool):
|
||||||
"""Set POW proxy enabled/disabled"""
|
"""Set POW proxy enabled/disabled
|
||||||
|
|
||||||
|
DEPRECATED: This configuration is deprecated. Use set_pow_service_proxy_enabled instead.
|
||||||
|
All POW proxy settings are now unified under [pow_service] section.
|
||||||
|
"""
|
||||||
if "pow_proxy" not in self._config:
|
if "pow_proxy" not in self._config:
|
||||||
self._config["pow_proxy"] = {}
|
self._config["pow_proxy"] = {}
|
||||||
self._config["pow_proxy"]["pow_proxy_enabled"] = enabled
|
self._config["pow_proxy"]["pow_proxy_enabled"] = enabled
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pow_proxy_url(self) -> str:
|
def pow_proxy_url(self) -> str:
|
||||||
"""Get POW proxy URL"""
|
"""Get POW proxy URL
|
||||||
|
|
||||||
|
DEPRECATED: This configuration is deprecated. Use pow_service_proxy_url instead.
|
||||||
|
All POW proxy settings are now unified under [pow_service] section.
|
||||||
|
"""
|
||||||
return self._config.get("pow_proxy", {}).get("pow_proxy_url", "")
|
return self._config.get("pow_proxy", {}).get("pow_proxy_url", "")
|
||||||
|
|
||||||
def set_pow_proxy_url(self, url: str):
|
def set_pow_proxy_url(self, url: str):
|
||||||
"""Set POW proxy URL"""
|
"""Set POW proxy URL
|
||||||
|
|
||||||
|
DEPRECATED: This configuration is deprecated. Use set_pow_service_proxy_url instead.
|
||||||
|
All POW proxy settings are now unified under [pow_service] section.
|
||||||
|
"""
|
||||||
if "pow_proxy" not in self._config:
|
if "pow_proxy" not in self._config:
|
||||||
self._config["pow_proxy"] = {}
|
self._config["pow_proxy"] = {}
|
||||||
self._config["pow_proxy"]["pow_proxy_url"] = url
|
self._config["pow_proxy"]["pow_proxy_url"] = url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pow_service_mode(self) -> str:
|
||||||
|
"""Get POW service mode (local or external)"""
|
||||||
|
return self._config.get("pow_service", {}).get("mode", "local")
|
||||||
|
|
||||||
|
def set_pow_service_mode(self, mode: str):
|
||||||
|
"""Set POW service mode"""
|
||||||
|
if "pow_service" not in self._config:
|
||||||
|
self._config["pow_service"] = {}
|
||||||
|
self._config["pow_service"]["mode"] = mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pow_service_server_url(self) -> str:
|
||||||
|
"""Get POW service server URL"""
|
||||||
|
return self._config.get("pow_service", {}).get("server_url", "")
|
||||||
|
|
||||||
|
def set_pow_service_server_url(self, url: str):
|
||||||
|
"""Set POW service server URL"""
|
||||||
|
if "pow_service" not in self._config:
|
||||||
|
self._config["pow_service"] = {}
|
||||||
|
self._config["pow_service"]["server_url"] = url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pow_service_api_key(self) -> str:
|
||||||
|
"""Get POW service API key"""
|
||||||
|
return self._config.get("pow_service", {}).get("api_key", "")
|
||||||
|
|
||||||
|
def set_pow_service_api_key(self, api_key: str):
|
||||||
|
"""Set POW service API key"""
|
||||||
|
if "pow_service" not in self._config:
|
||||||
|
self._config["pow_service"] = {}
|
||||||
|
self._config["pow_service"]["api_key"] = api_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pow_service_proxy_enabled(self) -> bool:
|
||||||
|
"""Get POW service proxy enabled status"""
|
||||||
|
return self._config.get("pow_service", {}).get("proxy_enabled", False)
|
||||||
|
|
||||||
|
def set_pow_service_proxy_enabled(self, enabled: bool):
|
||||||
|
"""Set POW service proxy enabled status"""
|
||||||
|
if "pow_service" not in self._config:
|
||||||
|
self._config["pow_service"] = {}
|
||||||
|
self._config["pow_service"]["proxy_enabled"] = enabled
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pow_service_proxy_url(self) -> str:
|
||||||
|
"""Get POW service proxy URL"""
|
||||||
|
return self._config.get("pow_service", {}).get("proxy_url", "")
|
||||||
|
|
||||||
|
def set_pow_service_proxy_url(self, url: str):
|
||||||
|
"""Set POW service proxy URL"""
|
||||||
|
if "pow_service" not in self._config:
|
||||||
|
self._config["pow_service"] = {}
|
||||||
|
self._config["pow_service"]["proxy_url"] = url
|
||||||
|
|
||||||
# Global config instance
|
# Global config instance
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|||||||
@@ -224,6 +224,34 @@ class Database:
|
|||||||
VALUES (1, ?, ?)
|
VALUES (1, ?, ?)
|
||||||
""", (pow_proxy_enabled, pow_proxy_url))
|
""", (pow_proxy_enabled, pow_proxy_url))
|
||||||
|
|
||||||
|
# Ensure pow_service_config has a row
|
||||||
|
cursor = await db.execute("SELECT COUNT(*) FROM pow_service_config")
|
||||||
|
count = await cursor.fetchone()
|
||||||
|
if count[0] == 0:
|
||||||
|
# Get POW service config from config_dict if provided, otherwise use defaults
|
||||||
|
mode = "local"
|
||||||
|
server_url = None
|
||||||
|
api_key = None
|
||||||
|
proxy_enabled = False
|
||||||
|
proxy_url = None
|
||||||
|
|
||||||
|
if config_dict:
|
||||||
|
pow_service_config = config_dict.get("pow_service", {})
|
||||||
|
mode = pow_service_config.get("mode", "local")
|
||||||
|
server_url = pow_service_config.get("server_url", "")
|
||||||
|
api_key = pow_service_config.get("api_key", "")
|
||||||
|
proxy_enabled = pow_service_config.get("proxy_enabled", False)
|
||||||
|
proxy_url = pow_service_config.get("proxy_url", "")
|
||||||
|
# Convert empty strings to None
|
||||||
|
server_url = server_url if server_url else None
|
||||||
|
api_key = api_key if api_key else None
|
||||||
|
proxy_url = proxy_url if proxy_url else None
|
||||||
|
|
||||||
|
await db.execute("""
|
||||||
|
INSERT INTO pow_service_config (id, mode, server_url, api_key, proxy_enabled, proxy_url)
|
||||||
|
VALUES (1, ?, ?, ?, ?, ?)
|
||||||
|
""", (mode, server_url, api_key, proxy_enabled, proxy_url))
|
||||||
|
|
||||||
|
|
||||||
async def check_and_migrate_db(self, config_dict: dict = None):
|
async def check_and_migrate_db(self, config_dict: dict = None):
|
||||||
"""Check database integrity and perform migrations if needed
|
"""Check database integrity and perform migrations if needed
|
||||||
@@ -517,6 +545,20 @@ class Database:
|
|||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# Create pow_service_config table
|
||||||
|
await db.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS pow_service_config (
|
||||||
|
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||||
|
mode TEXT DEFAULT 'local',
|
||||||
|
server_url TEXT,
|
||||||
|
api_key TEXT,
|
||||||
|
proxy_enabled BOOLEAN DEFAULT 0,
|
||||||
|
proxy_url TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
# Create indexes
|
# Create indexes
|
||||||
await db.execute("CREATE INDEX IF NOT EXISTS idx_task_id ON tasks(task_id)")
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_task_id ON tasks(task_id)")
|
||||||
await db.execute("CREATE INDEX IF NOT EXISTS idx_task_status ON tasks(status)")
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_task_status ON tasks(status)")
|
||||||
@@ -1276,6 +1318,23 @@ class Database:
|
|||||||
return PowProxyConfig(**dict(row))
|
return PowProxyConfig(**dict(row))
|
||||||
return PowProxyConfig(pow_proxy_enabled=False, pow_proxy_url=None)
|
return PowProxyConfig(pow_proxy_enabled=False, pow_proxy_url=None)
|
||||||
|
|
||||||
|
async def get_pow_service_config(self) -> "PowServiceConfig":
|
||||||
|
"""Get POW service configuration"""
|
||||||
|
from .models import PowServiceConfig
|
||||||
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
cursor = await db.execute("SELECT * FROM pow_service_config WHERE id = 1")
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return PowServiceConfig(**dict(row))
|
||||||
|
return PowServiceConfig(
|
||||||
|
mode="local",
|
||||||
|
server_url=None,
|
||||||
|
api_key=None,
|
||||||
|
proxy_enabled=False,
|
||||||
|
proxy_url=None
|
||||||
|
)
|
||||||
|
|
||||||
async def update_pow_proxy_config(self, pow_proxy_enabled: bool, pow_proxy_url: Optional[str] = None):
|
async def update_pow_proxy_config(self, pow_proxy_enabled: bool, pow_proxy_url: Optional[str] = None):
|
||||||
"""Update POW proxy configuration"""
|
"""Update POW proxy configuration"""
|
||||||
async with aiosqlite.connect(self.db_path) as db:
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
@@ -1286,3 +1345,21 @@ class Database:
|
|||||||
""", (pow_proxy_enabled, pow_proxy_url))
|
""", (pow_proxy_enabled, pow_proxy_url))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
|
async def update_pow_service_config(
|
||||||
|
self,
|
||||||
|
mode: str,
|
||||||
|
server_url: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
proxy_enabled: Optional[bool] = None,
|
||||||
|
proxy_url: Optional[str] = None
|
||||||
|
):
|
||||||
|
"""Update POW service configuration"""
|
||||||
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
# Use INSERT OR REPLACE to ensure the row exists
|
||||||
|
await db.execute("""
|
||||||
|
INSERT OR REPLACE INTO pow_service_config (id, mode, server_url, api_key, proxy_enabled, proxy_url, updated_at)
|
||||||
|
VALUES (1, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
""", (mode, server_url, api_key, proxy_enabled, proxy_url))
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -270,6 +270,18 @@ class DebugLogger:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error logging info: {e}")
|
self.logger.error(f"Error logging info: {e}")
|
||||||
|
|
||||||
|
def log_warning(self, message: str):
|
||||||
|
"""Log warning message to log.txt"""
|
||||||
|
|
||||||
|
# Check if debug mode is enabled
|
||||||
|
if not config.debug_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.logger.warning(f"⚠️ [{self._format_timestamp()}] {message}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error logging warning: {e}")
|
||||||
|
|
||||||
# Global debug logger instance
|
# Global debug logger instance
|
||||||
debug_logger = DebugLogger()
|
debug_logger = DebugLogger()
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,17 @@ class PowProxyConfig(BaseModel):
|
|||||||
created_at: Optional[datetime] = None
|
created_at: Optional[datetime] = None
|
||||||
updated_at: Optional[datetime] = None
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class PowServiceConfig(BaseModel):
|
||||||
|
"""POW service configuration"""
|
||||||
|
id: int = 1
|
||||||
|
mode: str = "local" # "local" or "external"
|
||||||
|
server_url: Optional[str] = None # External POW service URL
|
||||||
|
api_key: Optional[str] = None # External POW service API key
|
||||||
|
proxy_enabled: bool = False # Whether to enable proxy for POW service
|
||||||
|
proxy_url: Optional[str] = None # Proxy URL for POW service
|
||||||
|
created_at: Optional[datetime] = None
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
# API Request/Response models
|
# API Request/Response models
|
||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
role: str
|
role: str
|
||||||
|
|||||||
@@ -144,6 +144,15 @@ async def startup_event():
|
|||||||
config.set_call_logic_mode(call_logic_config.call_mode)
|
config.set_call_logic_mode(call_logic_config.call_mode)
|
||||||
print(f"✓ Call logic mode: {call_logic_config.call_mode}")
|
print(f"✓ Call logic mode: {call_logic_config.call_mode}")
|
||||||
|
|
||||||
|
# Load POW service configuration from database
|
||||||
|
pow_service_config = await db.get_pow_service_config()
|
||||||
|
config.set_pow_service_mode(pow_service_config.mode)
|
||||||
|
config.set_pow_service_server_url(pow_service_config.server_url or "")
|
||||||
|
config.set_pow_service_api_key(pow_service_config.api_key or "")
|
||||||
|
config.set_pow_service_proxy_enabled(pow_service_config.proxy_enabled)
|
||||||
|
config.set_pow_service_proxy_url(pow_service_config.proxy_url or "")
|
||||||
|
print(f"✓ POW service mode: {pow_service_config.mode}")
|
||||||
|
|
||||||
# Initialize concurrency manager with all tokens
|
# Initialize concurrency manager with all tokens
|
||||||
all_tokens = await db.get_all_tokens()
|
all_tokens = await db.get_all_tokens()
|
||||||
await concurrency_manager.initialize(all_tokens)
|
await concurrency_manager.initialize(all_tokens)
|
||||||
|
|||||||
136
src/services/pow_service_client.py
Normal file
136
src/services/pow_service_client.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
"""POW Service Client - External POW service integration"""
|
||||||
|
import json
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
from curl_cffi.requests import AsyncSession
|
||||||
|
|
||||||
|
from ..core.config import config
|
||||||
|
from ..core.logger import debug_logger
|
||||||
|
|
||||||
|
|
||||||
|
class POWServiceClient:
|
||||||
|
"""Client for external POW service API"""
|
||||||
|
|
||||||
|
async def get_sentinel_token(self) -> Optional[Tuple[str, str, str]]:
|
||||||
|
"""Get sentinel token from external POW service
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (sentinel_token, device_id, user_agent) or None on failure
|
||||||
|
"""
|
||||||
|
# Read configuration dynamically on each call
|
||||||
|
server_url = config.pow_service_server_url
|
||||||
|
api_key = config.pow_service_api_key
|
||||||
|
proxy_enabled = config.pow_service_proxy_enabled
|
||||||
|
proxy_url = config.pow_service_proxy_url if proxy_enabled else None
|
||||||
|
|
||||||
|
if not server_url or not api_key:
|
||||||
|
debug_logger.log_error(
|
||||||
|
error_message="POW service not configured: missing server_url or api_key",
|
||||||
|
status_code=0,
|
||||||
|
response_text="Configuration error",
|
||||||
|
source="POWServiceClient"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Construct API endpoint
|
||||||
|
api_url = f"{server_url.rstrip('/')}/api/pow/token"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {api_key}",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
debug_logger.log_info(f"[POW Service] Requesting token from {api_url}")
|
||||||
|
|
||||||
|
async with AsyncSession(impersonate="chrome131") as session:
|
||||||
|
response = await session.get(
|
||||||
|
api_url,
|
||||||
|
headers=headers,
|
||||||
|
proxy=proxy_url,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
error_msg = f"POW service request failed: {response.status_code}"
|
||||||
|
debug_logger.log_error(
|
||||||
|
error_message=error_msg,
|
||||||
|
status_code=response.status_code,
|
||||||
|
response_text=response.text,
|
||||||
|
source="POWServiceClient"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if not data.get("success"):
|
||||||
|
debug_logger.log_error(
|
||||||
|
error_message="POW service returned success=false",
|
||||||
|
status_code=response.status_code,
|
||||||
|
response_text=response.text,
|
||||||
|
source="POWServiceClient"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
token = data.get("token")
|
||||||
|
device_id = data.get("device_id")
|
||||||
|
user_agent = data.get("user_agent")
|
||||||
|
cached = data.get("cached", False)
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
debug_logger.log_error(
|
||||||
|
error_message="POW service returned empty token",
|
||||||
|
status_code=response.status_code,
|
||||||
|
response_text=response.text,
|
||||||
|
source="POWServiceClient"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Parse token to extract device_id if not provided
|
||||||
|
token_data = None
|
||||||
|
if not device_id:
|
||||||
|
try:
|
||||||
|
token_data = json.loads(token)
|
||||||
|
device_id = token_data.get("id")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 记录详细的 token 信息
|
||||||
|
cache_status = "cached" if cached else "fresh"
|
||||||
|
debug_logger.log_info("=" * 100)
|
||||||
|
debug_logger.log_info(f"[POW Service] Token obtained successfully ({cache_status})")
|
||||||
|
debug_logger.log_info(f"[POW Service] Token length: {len(token)}")
|
||||||
|
debug_logger.log_info(f"[POW Service] Device ID: {device_id}")
|
||||||
|
debug_logger.log_info(f"[POW Service] User Agent: {user_agent}")
|
||||||
|
|
||||||
|
# 解析并显示 token 结构
|
||||||
|
if not token_data:
|
||||||
|
try:
|
||||||
|
token_data = json.loads(token)
|
||||||
|
except:
|
||||||
|
debug_logger.log_info(f"[POW Service] Token is not valid JSON")
|
||||||
|
token_data = None
|
||||||
|
|
||||||
|
if token_data:
|
||||||
|
debug_logger.log_info(f"[POW Service] Token structure keys: {list(token_data.keys())}")
|
||||||
|
for key, value in token_data.items():
|
||||||
|
if isinstance(value, str) and len(value) > 100:
|
||||||
|
debug_logger.log_info(f"[POW Service] Token[{key}]: <string, length={len(value)}>")
|
||||||
|
else:
|
||||||
|
debug_logger.log_info(f"[POW Service] Token[{key}]: {value}")
|
||||||
|
|
||||||
|
debug_logger.log_info("=" * 100)
|
||||||
|
|
||||||
|
return token, device_id, user_agent
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_logger.log_error(
|
||||||
|
error_message=f"POW service request exception: {str(e)}",
|
||||||
|
status_code=0,
|
||||||
|
response_text=str(e),
|
||||||
|
source="POWServiceClient"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Global instance
|
||||||
|
pow_service_client = POWServiceClient()
|
||||||
@@ -16,6 +16,7 @@ from urllib.error import HTTPError, URLError
|
|||||||
from curl_cffi.requests import AsyncSession
|
from curl_cffi.requests import AsyncSession
|
||||||
from curl_cffi import CurlMime
|
from curl_cffi import CurlMime
|
||||||
from .proxy_manager import ProxyManager
|
from .proxy_manager import ProxyManager
|
||||||
|
from .pow_service_client import pow_service_client
|
||||||
from ..core.config import config
|
from ..core.config import config
|
||||||
from ..core.logger import debug_logger
|
from ..core.logger import debug_logger
|
||||||
|
|
||||||
@@ -232,32 +233,47 @@ async def _generate_sentinel_token_lightweight(proxy_url: str = None, device_id:
|
|||||||
|
|
||||||
async def _get_cached_sentinel_token(proxy_url: str = None, force_refresh: bool = False) -> str:
|
async def _get_cached_sentinel_token(proxy_url: str = None, force_refresh: bool = False) -> str:
|
||||||
"""Get sentinel token with caching support
|
"""Get sentinel token with caching support
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
proxy_url: Optional proxy URL
|
proxy_url: Optional proxy URL
|
||||||
force_refresh: Force refresh token (e.g., after 400 error)
|
force_refresh: Force refresh token (e.g., after 400 error)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Sentinel token string or None
|
Sentinel token string or None
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
Exception: If 403/429 when fetching oai-did
|
Exception: If 403/429 when fetching oai-did
|
||||||
"""
|
"""
|
||||||
global _cached_sentinel_token
|
global _cached_sentinel_token
|
||||||
|
|
||||||
|
# Check if external POW service is configured
|
||||||
|
if config.pow_service_mode == "external":
|
||||||
|
debug_logger.log_info("[POW] Using external POW service (cached sentinel)")
|
||||||
|
from .pow_service_client import pow_service_client
|
||||||
|
result = await pow_service_client.get_sentinel_token()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
sentinel_token, device_id, service_user_agent = result
|
||||||
|
debug_logger.log_info("[POW] External service returned sentinel token successfully")
|
||||||
|
return sentinel_token
|
||||||
|
else:
|
||||||
|
# Fallback to local mode if external service fails
|
||||||
|
debug_logger.log_info("[POW] External service failed, falling back to local mode")
|
||||||
|
|
||||||
|
# Local mode (original logic)
|
||||||
# Return cached token if available and not forcing refresh
|
# Return cached token if available and not forcing refresh
|
||||||
if _cached_sentinel_token and not force_refresh:
|
if _cached_sentinel_token and not force_refresh:
|
||||||
debug_logger.log_info("[Sentinel] Using cached token")
|
debug_logger.log_info("[Sentinel] Using cached token")
|
||||||
return _cached_sentinel_token
|
return _cached_sentinel_token
|
||||||
|
|
||||||
# Generate new token
|
# Generate new token
|
||||||
debug_logger.log_info("[Sentinel] Generating new token...")
|
debug_logger.log_info("[Sentinel] Generating new token...")
|
||||||
token = await _generate_sentinel_token_lightweight(proxy_url)
|
token = await _generate_sentinel_token_lightweight(proxy_url)
|
||||||
|
|
||||||
if token:
|
if token:
|
||||||
_cached_sentinel_token = token
|
_cached_sentinel_token = token
|
||||||
debug_logger.log_info("[Sentinel] Token cached successfully")
|
debug_logger.log_info("[Sentinel] Token cached successfully")
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
@@ -602,10 +618,10 @@ class SoraClient:
|
|||||||
proxy_url: Optional[str], token_id: Optional[int] = None,
|
proxy_url: Optional[str], token_id: Optional[int] = None,
|
||||||
user_agent: Optional[str] = None) -> Dict[str, Any]:
|
user_agent: Optional[str] = None) -> Dict[str, Any]:
|
||||||
"""Make nf/create request
|
"""Make nf/create request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response dict on success
|
Response dict on success
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
Exception: With error info, including '400' in message for sentinel token errors
|
Exception: With error info, including '400' in message for sentinel token errors
|
||||||
"""
|
"""
|
||||||
@@ -616,20 +632,85 @@ class SoraClient:
|
|||||||
import json as json_mod
|
import json as json_mod
|
||||||
sentinel_data = json_mod.loads(sentinel_token)
|
sentinel_data = json_mod.loads(sentinel_token)
|
||||||
device_id = sentinel_data.get("id", str(uuid4()))
|
device_id = sentinel_data.get("id", str(uuid4()))
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {token}",
|
"Authorization": f"Bearer {token}",
|
||||||
"OpenAI-Sentinel-Token": sentinel_token,
|
"openai-sentinel-token": sentinel_token, # 使用小写,与成功的 curl 请求一致
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"User-Agent": user_agent,
|
"User-Agent": user_agent,
|
||||||
"OAI-Language": "en-US",
|
"oai-language": "en-US", # 使用小写
|
||||||
"OAI-Device-Id": device_id,
|
"oai-device-id": device_id, # 使用小写
|
||||||
|
"Accept": "*/*",
|
||||||
|
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Origin": "https://sora.chatgpt.com",
|
||||||
|
"Referer": "https://sora.chatgpt.com/explore",
|
||||||
|
"Sec-Ch-Ua": '"Not(A:Brand";v="8", "Chromium";v="131", "Google Chrome";v="131"',
|
||||||
|
"Sec-Ch-Ua-Mobile": "?0",
|
||||||
|
"Sec-Ch-Ua-Platform": '"Windows"',
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-origin",
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
"Priority": "u=1, i",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 添加 Cookie 头(关键修复)
|
||||||
|
if token_id:
|
||||||
|
try:
|
||||||
|
from src.core.database import Database
|
||||||
|
db = Database()
|
||||||
|
token_obj = await db.get_token(token_id)
|
||||||
|
if token_obj and token_obj.st:
|
||||||
|
# 添加 session token cookie
|
||||||
|
headers["Cookie"] = f"__Secure-next-auth.session-token={token_obj.st}"
|
||||||
|
debug_logger.log_info(f"[nf/create] Added session token cookie (length: {len(token_obj.st)})")
|
||||||
|
else:
|
||||||
|
debug_logger.log_warning("[nf/create] No session token (st) found for this token")
|
||||||
|
except Exception as e:
|
||||||
|
debug_logger.log_warning(f"[nf/create] Failed to get session token: {e}")
|
||||||
|
|
||||||
|
# 记录详细的 Sentinel Token 信息
|
||||||
|
debug_logger.log_info(f"[nf/create] Preparing request to {url}")
|
||||||
|
debug_logger.log_info(f"[nf/create] Device ID: {device_id}")
|
||||||
|
|
||||||
|
# Sentinel Token 前100字符和后50字符
|
||||||
|
if len(sentinel_token) > 150:
|
||||||
|
debug_logger.log_info(f"[nf/create] Sentinel Token (first 100 chars): {sentinel_token[:100]}...")
|
||||||
|
debug_logger.log_info(f"[nf/create] Sentinel Token (last 50 chars): ...{sentinel_token[-50:]}")
|
||||||
|
else:
|
||||||
|
debug_logger.log_info(f"[nf/create] Sentinel Token: {sentinel_token}")
|
||||||
|
|
||||||
|
debug_logger.log_info(f"[nf/create] Sentinel Token length: {len(sentinel_token)}")
|
||||||
|
|
||||||
|
# Sentinel Token 结构信息
|
||||||
|
debug_logger.log_info(f"[nf/create] Sentinel Token structure:")
|
||||||
|
if "p" in sentinel_data:
|
||||||
|
debug_logger.log_info(f" - p (POW) length: {len(sentinel_data['p'])}")
|
||||||
|
if "t" in sentinel_data:
|
||||||
|
debug_logger.log_info(f" - t (Turnstile) length: {len(sentinel_data['t'])}")
|
||||||
|
if "c" in sentinel_data:
|
||||||
|
debug_logger.log_info(f" - c (Challenge) length: {len(sentinel_data['c'])}")
|
||||||
|
if "id" in sentinel_data:
|
||||||
|
debug_logger.log_info(f" - id: {sentinel_data['id']}")
|
||||||
|
if "flow" in sentinel_data:
|
||||||
|
debug_logger.log_info(f" - flow: {sentinel_data['flow']}")
|
||||||
|
|
||||||
|
# 使用 log_request 方法记录完整的请求详情
|
||||||
|
debug_logger.log_request(
|
||||||
|
method="POST",
|
||||||
|
url=url,
|
||||||
|
headers=headers,
|
||||||
|
body=payload,
|
||||||
|
proxy=proxy_url,
|
||||||
|
source="Server"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await asyncio.to_thread(
|
result = await asyncio.to_thread(
|
||||||
self._post_json_sync, url, headers, payload, 30, proxy_url
|
self._post_json_sync, url, headers, payload, 30, proxy_url
|
||||||
)
|
)
|
||||||
|
debug_logger.log_info(f"[nf/create] Request succeeded, task_id: {result.get('id', 'N/A')}")
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_str = str(e)
|
error_str = str(e)
|
||||||
@@ -664,13 +745,40 @@ class SoraClient:
|
|||||||
raise Exception(f"URL Error: {exc}") from exc
|
raise Exception(f"URL Error: {exc}") from exc
|
||||||
|
|
||||||
async def _generate_sentinel_token(self, token: Optional[str] = None, user_agent: Optional[str] = None) -> Tuple[str, str]:
|
async def _generate_sentinel_token(self, token: Optional[str] = None, user_agent: Optional[str] = None) -> Tuple[str, str]:
|
||||||
"""Generate openai-sentinel-token by calling /backend-api/sentinel/req and solving PoW"""
|
"""Generate openai-sentinel-token by calling /backend-api/sentinel/req and solving PoW
|
||||||
|
|
||||||
|
Supports two modes:
|
||||||
|
- external: Get complete sentinel token from external POW service
|
||||||
|
- local: Generate POW locally and call sentinel/req endpoint
|
||||||
|
"""
|
||||||
|
# Check if external POW service is configured
|
||||||
|
if config.pow_service_mode == "external":
|
||||||
|
debug_logger.log_info("[Sentinel] Using external POW service...")
|
||||||
|
result = await pow_service_client.get_sentinel_token()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
sentinel_token, device_id, service_user_agent = result
|
||||||
|
# Use service user agent if provided, otherwise use default
|
||||||
|
final_user_agent = service_user_agent if service_user_agent else (
|
||||||
|
user_agent if user_agent else
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36"
|
||||||
|
)
|
||||||
|
|
||||||
|
debug_logger.log_info(f"[Sentinel] Got token from external service")
|
||||||
|
debug_logger.log_info(f"[Sentinel] Token cached successfully (external)")
|
||||||
|
return sentinel_token, final_user_agent
|
||||||
|
else:
|
||||||
|
# Fallback to local mode if external service fails
|
||||||
|
debug_logger.log_info("[Sentinel] External service failed, falling back to local mode")
|
||||||
|
|
||||||
|
# Local mode (original logic)
|
||||||
|
debug_logger.log_info("[POW] Using local POW generation")
|
||||||
req_id = str(uuid4())
|
req_id = str(uuid4())
|
||||||
if not user_agent:
|
if not user_agent:
|
||||||
user_agent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36"
|
user_agent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36"
|
||||||
|
|
||||||
pow_token = self._get_pow_token(user_agent)
|
pow_token = self._get_pow_token(user_agent)
|
||||||
|
|
||||||
init_payload = {
|
init_payload = {
|
||||||
"p": pow_token,
|
"p": pow_token,
|
||||||
"id": req_id,
|
"id": req_id,
|
||||||
@@ -688,7 +796,7 @@ class SoraClient:
|
|||||||
"flow": "sora_init"
|
"flow": "sora_init"
|
||||||
}
|
}
|
||||||
request_body = json.dumps(request_payload, separators=(',', ':'))
|
request_body = json.dumps(request_payload, separators=(',', ':'))
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
"Content-Type": "text/plain;charset=UTF-8",
|
"Content-Type": "text/plain;charset=UTF-8",
|
||||||
@@ -712,7 +820,7 @@ class SoraClient:
|
|||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise Exception(f"Sentinel request failed: {response.status_code} {response.text}")
|
raise Exception(f"Sentinel request failed: {response.status_code} {response.text}")
|
||||||
resp = response.json()
|
resp = response.json()
|
||||||
|
|
||||||
debug_logger.log_info(f"Sentinel response: turnstile.dx={bool(resp.get('turnstile', {}).get('dx'))}, token={bool(resp.get('token'))}, pow_required={resp.get('proofofwork', {}).get('required')}")
|
debug_logger.log_info(f"Sentinel response: turnstile.dx={bool(resp.get('turnstile', {}).get('dx'))}, token={bool(resp.get('token'))}, pow_required={resp.get('proofofwork', {}).get('required')}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
@@ -727,11 +835,11 @@ class SoraClient:
|
|||||||
sentinel_token = self._build_sentinel_token(
|
sentinel_token = self._build_sentinel_token(
|
||||||
self.SENTINEL_FLOW, req_id, pow_token, resp, user_agent
|
self.SENTINEL_FLOW, req_id, pow_token, resp, user_agent
|
||||||
)
|
)
|
||||||
|
|
||||||
# Log final token for debugging
|
# Log final token for debugging
|
||||||
parsed = json.loads(sentinel_token)
|
parsed = json.loads(sentinel_token)
|
||||||
debug_logger.log_info(f"Final sentinel: p_prefix={parsed['p'][:10]}, p_suffix={parsed['p'][-5:]}, t_len={len(parsed['t'])}, c_len={len(parsed['c'])}, flow={parsed['flow']}")
|
debug_logger.log_info(f"Final sentinel: p_prefix={parsed['p'][:10]}, p_suffix={parsed['p'][-5:]}, t_len={len(parsed['t'])}, c_len={len(parsed['c'])}, flow={parsed['flow']}")
|
||||||
|
|
||||||
return sentinel_token, user_agent
|
return sentinel_token, user_agent
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -1024,10 +1132,10 @@ class SoraClient:
|
|||||||
|
|
||||||
proxy_url = await self.proxy_manager.get_proxy_url(token_id)
|
proxy_url = await self.proxy_manager.get_proxy_url(token_id)
|
||||||
|
|
||||||
# Get POW proxy from configuration
|
# Get POW proxy from configuration (unified with pow_service config)
|
||||||
pow_proxy_url = None
|
pow_proxy_url = None
|
||||||
if config.pow_proxy_enabled:
|
if config.pow_service_proxy_enabled:
|
||||||
pow_proxy_url = config.pow_proxy_url or None
|
pow_proxy_url = config.pow_service_proxy_url or None
|
||||||
|
|
||||||
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
|||||||
@@ -390,23 +390,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- POW代理配置 -->
|
<!-- POW配置 -->
|
||||||
<div class="rounded-lg border border-border bg-background p-6">
|
<div class="rounded-lg border border-border bg-background p-6">
|
||||||
<h3 class="text-lg font-semibold mb-4">POW代理配置</h3>
|
<h3 class="text-lg font-semibold mb-4">POW配置</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium mb-2 block">计算模式</label>
|
||||||
|
<select id="cfgPowMode" onchange="togglePowFields()" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm">
|
||||||
|
<option value="local">本地计算</option>
|
||||||
|
<option value="external">外部服务</option>
|
||||||
|
</select>
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">选择 POW 计算方式</p>
|
||||||
|
</div>
|
||||||
|
<div id="powExternalFields" style="display: none;">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium mb-2 block">服务器地址</label>
|
||||||
|
<input id="cfgPowServerUrl" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="http://127.0.0.1:8002">
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">外部 POW 服务的地址</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium mb-2 block">API 密钥</label>
|
||||||
|
<input id="cfgPowApiKey" type="password" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="输入 API 密钥">
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">访问外部 POW 服务的密钥</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||||
<input type="checkbox" id="cfgPowProxyEnabled" class="h-4 w-4 rounded border-input">
|
<input type="checkbox" id="cfgPowProxyEnabled" onchange="togglePowProxyFields()" class="h-4 w-4 rounded border-input">
|
||||||
<span class="text-sm font-medium">启用POW代理</span>
|
<span class="text-sm font-medium">启用POW代理</span>
|
||||||
</label>
|
</label>
|
||||||
<p class="text-xs text-muted-foreground mt-1">获取 Sentinel Token 时使用的代理</p>
|
<p class="text-xs text-muted-foreground mt-1">获取 Sentinel Token 时使用的代理</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div id="powProxyUrlField" style="display: none;">
|
||||||
<label class="text-sm font-medium mb-2 block">POW代理地址</label>
|
<label class="text-sm font-medium mb-2 block">POW代理地址</label>
|
||||||
<input id="cfgPowProxyUrl" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="http://127.0.0.1:7890">
|
<input id="cfgPowProxyUrl" class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm" placeholder="http://127.0.0.1:7890">
|
||||||
<p class="text-xs text-muted-foreground mt-1">用于获取 POW Token 的代理地址</p>
|
<p class="text-xs text-muted-foreground mt-1">用于获取 POW Token 的代理地址</p>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="savePowProxyConfig()" class="inline-flex items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 w-full">保存配置</button>
|
<button onclick="savePowConfig()" class="inline-flex items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 w-full">保存配置</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1118,9 +1140,13 @@
|
|||||||
deleteCharacter=async(id)=>{if(!confirm('确定要删除这个角色卡吗?'))return;try{const r=await apiRequest(`/api/characters/${id}`,{method:'DELETE'});if(!r)return;const d=await r.json();if(d.success){showToast('删除成功','success');await loadCharacters()}else{showToast('删除失败','error')}}catch(e){showToast('删除失败: '+e.message,'error')}},
|
deleteCharacter=async(id)=>{if(!confirm('确定要删除这个角色卡吗?'))return;try{const r=await apiRequest(`/api/characters/${id}`,{method:'DELETE'});if(!r)return;const d=await r.json();if(d.success){showToast('删除成功','success');await loadCharacters()}else{showToast('删除失败','error')}}catch(e){showToast('删除失败: '+e.message,'error')}},
|
||||||
loadCallLogicConfig=async()=>{try{const r=await apiRequest('/api/call-logic/config');if(!r)return;const d=await r.json();if(d.success&&d.config){const mode=d.config.call_mode||((d.config.polling_mode_enabled||false)?'polling':'default');$('cfgCallLogicMode').value=mode}else{console.error('调用逻辑配置数据格式错误:',d)}}catch(e){console.error('加载调用逻辑配置失败:',e)}},
|
loadCallLogicConfig=async()=>{try{const r=await apiRequest('/api/call-logic/config');if(!r)return;const d=await r.json();if(d.success&&d.config){const mode=d.config.call_mode||((d.config.polling_mode_enabled||false)?'polling':'default');$('cfgCallLogicMode').value=mode}else{console.error('调用逻辑配置数据格式错误:',d)}}catch(e){console.error('加载调用逻辑配置失败:',e)}},
|
||||||
saveCallLogicConfig=async()=>{try{const mode=$('cfgCallLogicMode').value||'default';const r=await apiRequest('/api/call-logic/config',{method:'POST',body:JSON.stringify({call_mode:mode})});if(!r)return;const d=await r.json();if(d.success){showToast('调用逻辑配置保存成功','success')}else{showToast('保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
|
saveCallLogicConfig=async()=>{try{const mode=$('cfgCallLogicMode').value||'default';const r=await apiRequest('/api/call-logic/config',{method:'POST',body:JSON.stringify({call_mode:mode})});if(!r)return;const d=await r.json();if(d.success){showToast('调用逻辑配置保存成功','success')}else{showToast('保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
|
||||||
loadPowProxyConfig=async()=>{try{const r=await apiRequest('/api/pow-proxy/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('cfgPowProxyEnabled').checked=d.config.pow_proxy_enabled||false;$('cfgPowProxyUrl').value=d.config.pow_proxy_url||''}else{console.error('POW代理配置数据格式错误:',d)}}catch(e){console.error('加载POW代理配置失败:',e)}},
|
loadPowConfig=async()=>{try{const r=await apiRequest('/api/pow/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('cfgPowMode').value=d.config.mode||'local';$('cfgPowServerUrl').value=d.config.server_url||'';$('cfgPowApiKey').value=d.config.api_key||'';$('cfgPowProxyEnabled').checked=d.config.proxy_enabled||false;$('cfgPowProxyUrl').value=d.config.proxy_url||'';togglePowFields();togglePowProxyFields()}else{console.error('POW配置数据格式错误:',d)}}catch(e){console.error('加载POW配置失败:',e)}},
|
||||||
savePowProxyConfig=async()=>{try{const r=await apiRequest('/api/pow-proxy/config',{method:'POST',body:JSON.stringify({pow_proxy_enabled:$('cfgPowProxyEnabled').checked,pow_proxy_url:$('cfgPowProxyUrl').value.trim()})});if(!r)return;const d=await r.json();if(d.success){showToast('POW代理配置保存成功','success')}else{showToast('保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
|
savePowConfig=async()=>{try{const mode=$('cfgPowMode').value;const serverUrl=$('cfgPowServerUrl').value.trim();const apiKey=$('cfgPowApiKey').value.trim();const proxyEnabled=$('cfgPowProxyEnabled').checked;const proxyUrl=$('cfgPowProxyUrl').value.trim();if(mode==='external'){if(!serverUrl)return showToast('请输入服务器地址','error');if(!apiKey)return showToast('请输入API密钥','error')}const r=await apiRequest('/api/pow/config',{method:'POST',body:JSON.stringify({mode:mode,server_url:serverUrl||null,api_key:apiKey||null,proxy_enabled:proxyEnabled,proxy_url:proxyUrl||null})});if(!r)return;const d=await r.json();if(d.success){showToast('POW配置保存成功','success')}else{showToast('保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
|
||||||
switchTab=t=>{const cap=n=>n.charAt(0).toUpperCase()+n.slice(1);['tokens','settings','logs','generate'].forEach(n=>{const active=n===t;$(`panel${cap(n)}`).classList.toggle('hidden',!active);$(`tab${cap(n)}`).classList.toggle('border-primary',active);$(`tab${cap(n)}`).classList.toggle('text-primary',active);$(`tab${cap(n)}`).classList.toggle('border-transparent',!active);$(`tab${cap(n)}`).classList.toggle('text-muted-foreground',!active)});if(t==='settings'){loadAdminConfig();loadProxyConfig();loadPowProxyConfig();loadWatermarkFreeConfig();loadCacheConfig();loadGenerationTimeout();loadATAutoRefreshConfig();loadCallLogicConfig()}else if(t==='logs'){loadLogs()}};
|
loadPowProxyConfig=loadPowConfig,savePowProxyConfig=savePowConfig,loadPowServiceConfig=loadPowConfig,savePowServiceConfig=savePowConfig,
|
||||||
|
togglePowFields=()=>{const mode=$('cfgPowMode').value;const externalFields=$('powExternalFields');if(externalFields){externalFields.style.display=mode==='external'?'block':'none'}},
|
||||||
|
togglePowProxyFields=()=>{const enabled=$('cfgPowProxyEnabled').checked;const proxyUrlField=$('powProxyUrlField');if(proxyUrlField){proxyUrlField.style.display=enabled?'block':'none'}},
|
||||||
|
togglePowServiceFields=()=>{togglePowFields()},
|
||||||
|
switchTab=t=>{const cap=n=>n.charAt(0).toUpperCase()+n.slice(1);['tokens','settings','logs','generate'].forEach(n=>{const active=n===t;$(`panel${cap(n)}`).classList.toggle('hidden',!active);$(`tab${cap(n)}`).classList.toggle('border-primary',active);$(`tab${cap(n)}`).classList.toggle('text-primary',active);$(`tab${cap(n)}`).classList.toggle('border-transparent',!active);$(`tab${cap(n)}`).classList.toggle('text-muted-foreground',!active)});if(t==='settings'){loadAdminConfig();loadProxyConfig();loadPowProxyConfig();loadPowServiceConfig();loadWatermarkFreeConfig();loadCacheConfig();loadGenerationTimeout();loadATAutoRefreshConfig();loadCallLogicConfig()}else if(t==='logs'){loadLogs()}};
|
||||||
// 自适应生成面板 iframe 高度
|
// 自适应生成面板 iframe 高度
|
||||||
window.addEventListener('message', (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
const data = event.data || {};
|
const data = event.data || {};
|
||||||
|
|||||||
Reference in New Issue
Block a user