diff --git a/VERSION b/VERSION index dd9c3ca..f2d2606 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2026.07.19.4 +v2026.07.19.5 diff --git a/backend/python/app/api/admin/observability_admin.py b/backend/python/app/api/admin/observability_admin.py new file mode 100644 index 0000000..94990e2 --- /dev/null +++ b/backend/python/app/api/admin/observability_admin.py @@ -0,0 +1,103 @@ +"""调用可观测面板后端 API(媒体/LLM 调用归因)。 +路由前缀: /app/api/admin/observability +数据来源: token_usage_logs(Phase 2.2 已加 trace_id 列,可经 X-Trace-Id 关联请求链路)。 +""" +from datetime import datetime, timedelta + +from fastapi import APIRouter, Query, Depends +from ...database import get_db +from ..auth import get_current_user +from sqlalchemy import text +from sqlalchemy.orm import Session + +router = APIRouter() + + +@router.get("/summary") +async def observability_summary( + days: int = Query(30, description="统计天数"), + db: Session = Depends(get_db), + user=Depends(get_current_user), +): + """媒体/LLM 调用归因:总览 + 按模型 + 按用户 + 每日趋势 + 最近调用(trace_id 下钻)。""" + since = datetime.now() - timedelta(days=days) + + totals = db.execute(text(""" + SELECT COUNT(*), + COALESCE(SUM(total_tokens), 0), + COALESCE(SUM(cost), 0), + COUNT(DISTINCT user_id) + FROM token_usage_logs WHERE created_at >= :since + """), {"since": since}).fetchone() + + by_model = db.execute(text(""" + SELECT model_name, COUNT(*), COALESCE(SUM(total_tokens), 0), COALESCE(SUM(cost), 0) + FROM token_usage_logs WHERE created_at >= :since + GROUP BY model_name ORDER BY COALESCE(SUM(total_tokens), 0) DESC + """), {"since": since}).fetchall() + + by_user = db.execute(text(""" + SELECT t.user_id, u.username, COUNT(*), COALESCE(SUM(t.total_tokens), 0), COALESCE(SUM(t.cost), 0) + FROM token_usage_logs t LEFT JOIN users u ON u.id = t.user_id + WHERE t.created_at >= :since + GROUP BY t.user_id, u.username ORDER BY COALESCE(SUM(t.total_tokens), 0) DESC LIMIT 20 + """), {"since": since}).fetchall() + + daily = db.execute(text(""" + SELECT DATE(created_at) AS d, COALESCE(SUM(total_tokens), 0), COALESCE(SUM(cost), 0), COUNT(*) + FROM token_usage_logs WHERE created_at >= :since + GROUP BY DATE(created_at) ORDER BY d + """), {"since": since}).fetchall() + + recent = db.execute(text(""" + SELECT trace_id, model_name, total_tokens, cost, created_at + FROM token_usage_logs WHERE created_at >= :since + ORDER BY created_at DESC LIMIT 50 + """), {"since": since}).fetchall() + + return { + "totals": { + "calls": int(totals[0] or 0), + "tokens": int(totals[1] or 0), + "cost": float(totals[2] or 0), + "users": int(totals[3] or 0), + }, + "by_model": [ + { + "model_name": m[0] or "unknown", + "calls": int(m[1] or 0), + "tokens": int(m[2] or 0), + "cost": float(m[3] or 0), + } + for m in by_model + ], + "by_user": [ + { + "user_id": u[0], + "username": u[1] or u[0], + "calls": int(u[2] or 0), + "tokens": int(u[3] or 0), + "cost": float(u[4] or 0), + } + for u in by_user + ], + "daily": [ + { + "date": str(d[0]), + "tokens": int(d[1] or 0), + "cost": float(d[2] or 0), + "calls": int(d[3] or 0), + } + for d in daily + ], + "recent": [ + { + "trace_id": r[0], + "model_name": r[1] or "unknown", + "tokens": int(r[2] or 0), + "cost": float(r[3] or 0), + "created_at": r[4].strftime("%Y-%m-%d %H:%M:%S") if r[4] else None, + } + for r in recent + ], + } diff --git a/backend/python/app/api/chat_service.py b/backend/python/app/api/chat_service.py index 94955df..ceefa86 100644 --- a/backend/python/app/api/chat_service.py +++ b/backend/python/app/api/chat_service.py @@ -16,7 +16,7 @@ import httpx import os import logging from ..database import get_db, SessionLocal -from app.core.observability import span +from app.core.observability import span, get_trace_id from ..models import ( @@ -1743,6 +1743,8 @@ async def _send_message_core( conversation_id=conv_id, + trace_id=get_trace_id(), + diff --git a/backend/python/app/api/im_llm.py b/backend/python/app/api/im_llm.py index 0b1d392..5a0d1cd 100644 --- a/backend/python/app/api/im_llm.py +++ b/backend/python/app/api/im_llm.py @@ -1,232 +1,234 @@ -""" -IM Bot 真实 LLM 调用。 - -原实现位于 channels.py(通用频道管理模块),该模块已移除。 -为保持 IM Bot(微信机器人等)正常生成回复,将 _call_real_llm 迁移至此独立模块。 - -行为(对齐 AI 聊天): -- Issue 7:图像/视频/语音意图优先走平台媒体模型(与 AI 聊天同一套 Auto 路由), - 不再对"制作图片"这类请求只回文本。 -- Issue 8:默认 Auto 模型;每轮先自动发现本账号已安装且平台已部署的技能, - 多个适用时由模型自行判断选 1 个最优,通过 function calling 执行。 -- 2026-07-15:修复 trust_env=False + proxy 处理,避免容器坏代理导致调用失败; - 媒体生成前先经 _optimize_media_prompt 精炼提示词,提升 AGNES 生成质量。 -""" -import asyncio -import json -import logging -import re as _re -from datetime import datetime, timezone, timedelta -from typing import Optional -from urllib.parse import urlparse as _urlparse - -import httpx -from sqlalchemy.orm import Session - -from ..capability_registry import resolve_auto_model -from ..models import AIModel, ModelProvider, TokenUsageLog, gen_uuid - -logger = logging.getLogger(__name__) - - -# 香港出网代理(实测对 AGNES 延迟更低,2026-07-15 用户实测),与 chat.py 同值。 -_HK_PROXY = "http://qyyfw:XHR521yhf@103.242.14.208:11495" - - -def _proxy_for_url(url: str) -> str | None: - """按目标域名选择是否走代理(与 chat.py 同逻辑)。 - - 2026-07-15 调整:AGNES 改走香港代理(实测延迟更低),不再直连。 - 国内域名(尊重 NO_PROXY)直连,更稳定。 - """ - import os - host = (_urlparse(url).hostname or "").lower() - if "agnes-ai.com" in host or "agnes-ai.space" in host: - return _HK_PROXY - if "qihuyun.com" in host: - return None - _no_proxy = [h.strip().lower() for h in os.environ.get("NO_PROXY", "").split(",") if h.strip()] - for _np in _no_proxy: - if host == _np or host.endswith("." + _np) or _np in host: - return None - return os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY") or "http://172.18.0.1:7890" - - -def _run_async(coro, timeout=None): - """在同步上下文中运行协程,兼容「无事件循环」与「已有事件循环」两种场景。 - - IM 个人微信在独立线程中调用(无事件循环)→ asyncio.run; - IM 多平台 Webhook 可能在 FastAPI 协程内调用(已有事件循环)→ 丢到线程池执行。 - - timeout: 可选,秒数;超时抛出 asyncio.TimeoutError(由调用方捕获)。 - """ - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = None - - if timeout is not None: - async def _timed(): - return await asyncio.wait_for(coro, timeout=timeout) - target = _timed() - else: - target = coro - - if loop is not None and loop.is_running(): - import concurrent.futures - with concurrent.futures.ThreadPoolExecutor(1) as ex: - return ex.submit(lambda: asyncio.run(target)).result(timeout=(timeout + 10) if timeout else None) - return asyncio.run(target) - - -def _log_media_usage(db: Session, user_id: str, ai_model, media_type: str, prompt: str): - """IM Bot 媒体生成记账:把图片/视频消耗写入 TokenUsageLog,供驾驶舱统计。 - - 此前 IM Bot 路径完全不写 TokenUsageLog,导致微信生成的图片/视频在驾驶舱里“查无此项”。 - 媒体 API 不返回 token 用量,这里用与 chat.py _generate_media_task 一致的估算。 - """ - if not user_id or not ai_model: - return - try: - base = 1200 if media_type == "video" else 400 - est = base + (len(prompt or "") // 4) - db.add(TokenUsageLog( - id=gen_uuid(), - user_id=user_id, - model_id=ai_model.id, - model_name=ai_model.name, - prompt_tokens=est, - completion_tokens=0, - total_tokens=est, - created_at=datetime.now(timezone(timedelta(hours=8))).replace(tzinfo=None), - )) - db.commit() - except Exception as e: - try: - db.rollback() - except Exception: - pass - logger.warning(f"[im_llm] 媒体 Token 记账失败(忽略): {e}") - - -def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im", user_id: str = None) -> "str | dict": - """调用七虎AI 生成回复,供 IM Bot 复用。支持媒体意图路由与已安装技能自动发现。""" - try: - from app.api.chat import ( - _generate_media, - detect_media_intent, - _discover_installed_skills, - _build_system_prompt, - _execute_skill_tool, - ) - - # ===== 1) 媒体意图路由(Issue 7)===== - intent = detect_media_intent(prompt) - if intent: - ai_model = resolve_auto_model(intent, db) - if ai_model: - try: - result = _run_async(_generate_media(db, ai_model, prompt, user_id), timeout=300) - except Exception: - # 任何未预期异常(如 AGNES 连接重置)统一兜底为友好提示, - # 绝不直接把原始异常(如 "Server disconnected")甩给用户 - result = "⚠️ 媒体生成暂时失败(连接或服务波动),请稍后重试。" - if result: - # 媒体生成成功:返回结构化对象,交由 im_bot_user 在微信内联发送图片/视频 - s = result.strip() - if s.startswith(" str | None: + """按目标域名选择是否走代理(与 chat.py 同逻辑)。 + + 2026-07-15 调整:AGNES 改走香港代理(实测延迟更低),不再直连。 + 国内域名(尊重 NO_PROXY)直连,更稳定。 + """ + import os + host = (_urlparse(url).hostname or "").lower() + if "agnes-ai.com" in host or "agnes-ai.space" in host: + return _HK_PROXY + if "qihuyun.com" in host: + return None + _no_proxy = [h.strip().lower() for h in os.environ.get("NO_PROXY", "").split(",") if h.strip()] + for _np in _no_proxy: + if host == _np or host.endswith("." + _np) or _np in host: + return None + return os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY") or "http://172.18.0.1:7890" + + +def _run_async(coro, timeout=None): + """在同步上下文中运行协程,兼容「无事件循环」与「已有事件循环」两种场景。 + + IM 个人微信在独立线程中调用(无事件循环)→ asyncio.run; + IM 多平台 Webhook 可能在 FastAPI 协程内调用(已有事件循环)→ 丢到线程池执行。 + + timeout: 可选,秒数;超时抛出 asyncio.TimeoutError(由调用方捕获)。 + """ + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + + if timeout is not None: + async def _timed(): + return await asyncio.wait_for(coro, timeout=timeout) + target = _timed() + else: + target = coro + + if loop is not None and loop.is_running(): + import concurrent.futures + with concurrent.futures.ThreadPoolExecutor(1) as ex: + return ex.submit(lambda: asyncio.run(target)).result(timeout=(timeout + 10) if timeout else None) + return asyncio.run(target) + + +def _log_media_usage(db: Session, user_id: str, ai_model, media_type: str, prompt: str): + """IM Bot 媒体生成记账:把图片/视频消耗写入 TokenUsageLog,供驾驶舱统计。 + + 此前 IM Bot 路径完全不写 TokenUsageLog,导致微信生成的图片/视频在驾驶舱里“查无此项”。 + 媒体 API 不返回 token 用量,这里用与 chat.py _generate_media_task 一致的估算。 + """ + if not user_id or not ai_model: + return + try: + base = 1200 if media_type == "video" else 400 + est = base + (len(prompt or "") // 4) + db.add(TokenUsageLog( + id=gen_uuid(), + user_id=user_id, + model_id=ai_model.id, + model_name=ai_model.name, + trace_id=get_trace_id(), + prompt_tokens=est, + completion_tokens=0, + total_tokens=est, + created_at=datetime.now(timezone(timedelta(hours=8))).replace(tzinfo=None), + )) + db.commit() + except Exception as e: + try: + db.rollback() + except Exception: + pass + logger.warning(f"[im_llm] 媒体 Token 记账失败(忽略): {e}") + + +def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im", user_id: str = None) -> "str | dict": + """调用七虎AI 生成回复,供 IM Bot 复用。支持媒体意图路由与已安装技能自动发现。""" + try: + from app.api.chat import ( + _generate_media, + detect_media_intent, + _discover_installed_skills, + _build_system_prompt, + _execute_skill_tool, + ) + + # ===== 1) 媒体意图路由(Issue 7)===== + intent = detect_media_intent(prompt) + if intent: + ai_model = resolve_auto_model(intent, db) + if ai_model: + try: + result = _run_async(_generate_media(db, ai_model, prompt, user_id), timeout=300) + except Exception: + # 任何未预期异常(如 AGNES 连接重置)统一兜底为友好提示, + # 绝不直接把原始异常(如 "Server disconnected")甩给用户 + result = "⚠️ 媒体生成暂时失败(连接或服务波动),请稍后重试。" + if result: + # 媒体生成成功:返回结构化对象,交由 im_bot_user 在微信内联发送图片/视频 + s = result.strip() + if s.startswith(" {status_code} ({elapsed:.3f}s)" - ) - - # 异步记录API调用统计 - try: - db = SessionLocal() - try: - _record_api_call(db, method, path, status_code, elapsed) - finally: - db.close() - except Exception as e: - logger.warning(f"API统计记录失败: {e}") - - return response - except Exception as e: - elapsed = time.time() - start_time - if not should_skip: - access_logger.info( - f"[{request_id}] {method} {path} -> ERROR ({elapsed:.3f}s): {str(e)}" - ) - raise - - async def __call__(self, scope, receive, send): - if scope["type"] != "http": - await self.app(scope, receive, send) - return - await super().__call__(scope, receive, send) - - -def _record_api_call(db, method: str, path: str, status_code: int, elapsed: float): - """记录API调用到数据库统计表""" - # 简化路径以减少基数(去掉动态ID) - parts = path.split("/") - simplified_parts = [] - for part in parts: - # 将UUID样式的路径段替换为{id} - if len(part) == 36 and part.count("-") == 4: - simplified_parts.append("{id}") - # 将纯数字路径段替换为{id} - elif part.isdigit(): - simplified_parts.append("{id}") - else: - simplified_parts.append(part) - simplified_path = "/".join(simplified_parts) - - # 统计记录通过数据库实现,简化版记录到日志 - is_error = status_code >= 400 - is_slow = elapsed >= SLOW_REQUEST_THRESHOLD - - if is_slow: - logger.warning( - f"慢请求: {method} {simplified_path} -> {status_code} ({elapsed:.3f}s, 阈值: {SLOW_REQUEST_THRESHOLD}s)" - ) - - -@asynccontextmanager -async def lifespan(app: FastAPI): - logger.info("Qihu-AIFlow v2.0.0 启动中...") - - # 启动后台 Evolution Pipeline(知识库图谱已存在的用户配置自动恢复运行) - try: - from app.services.evolution_service import EvolutionWatcherManager - from app.models import UserEvolutionConfig - db = SessionLocal() - try: - configs = db.query(UserEvolutionConfig).filter( - UserEvolutionConfig.watcher_status == "running" - ).all() - if configs: - watcher = EvolutionWatcherManager() - for cfg in configs: - try: - watcher.start_user_evolution(cfg.user_id, lambda: SessionLocal()) - logger.info(f"[Evolution] 恢复用户 {cfg.user_id} 的进化Pipeline") - except Exception as e: - logger.warning(f"[Evolution] 恢复用户 {cfg.user_id} 失败: {e}") - logger.info(f"[Evolution] 已恢复 {len(configs)} 个用户的进化Pipeline") - else: - logger.info("[Evolution] 无待恢复的进化配置") - finally: - db.close() - except Exception as e: - logger.warning(f"[Evolution] Pipeline自动启动异常(不影响正常服务): {e}") - - # 启动Redis事件总线 - try: - from app.event_bus import get_event_bus - event_bus = get_event_bus() - event_bus.start(num_consumers=2) - logger.info("[EventBus] Redis事件总线已启动") - except Exception as e: - logger.warning(f"[EventBus] 启动失败(不影响正常服务): {e}") - - logger.info("Qihu-AIFlow v2.0.0 启动完成") - - yield - - # 停止Redis事件总线 - try: - from app.event_bus import get_event_bus - get_event_bus().stop() - logger.info("[EventBus] Redis事件总线已停止") - except Exception: - pass - - # 优雅关闭:停止所有 Evolution Pipeline - try: - from app.services.evolution_service import EvolutionWatcherManager - EvolutionWatcherManager().shutdown_all() - logger.info("[Evolution] 所有Pipeline已停止") - except Exception: - pass - - logger.info("Qihu-AIFlow 正在关闭...") - - -app = FastAPI( - title="Qihu-AIFlow - 智能自动化PaaS平台", - version="1.0.0", - lifespan=lifespan, -) - -# API调用追踪中间件 -app.add_middleware(APITrackingMiddleware) - -# CORS配置 -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# 前台 API -app.include_router(auth.router, prefix="/app/api/auth", tags=["认证"]) -app.include_router(chat.router, prefix="/app/api/chat", tags=["AI聊天"]) -app.include_router(knowledge.router, prefix="/app/api/knowledge", tags=["知识库"]) -app.include_router(market.router, prefix="/app/api/market", tags=["应用市场"]) -app.include_router(token_usage.router, prefix="/app/api/token", tags=["Token驾驶舱"]) -app.include_router(payment.router, prefix="/app/api/payment", tags=["付费中心"]) -app.include_router(profile.router, prefix="/app/api/profile", tags=["个人中心"]) -app.include_router(notifications.router, prefix="/app/api/notifications", tags=["消息通知"]) -app.include_router(assets.router, prefix="/app/api/assets", tags=["资产管理"]) -app.include_router(evolution.router, prefix="/app/api/evolution", tags=["AI自进化"]) - -# 后台 API -app.include_router(dashboard.router, prefix="/app/api/admin/dashboard", tags=["后台-仪表盘"]) -app.include_router(users.router, prefix="/app/api/admin/users", tags=["后台-用户管理"]) -app.include_router(models.router, prefix="/app/api/admin/models", tags=["后台-模型管理"]) -app.include_router(settings.router, prefix="/app/api/admin/settings", tags=["后台-网站设置"]) -app.include_router(payment_admin.router, prefix="/app/api/admin/payment", tags=["后台-支付配置"]) -app.include_router(services.router, prefix="/app/api/admin/services", tags=["后台-增值服务"]) -app.include_router(membership_admin.router, prefix="/app/api/admin/membership", tags=["后台-会员管理"]) -app.include_router(ads.router, prefix="/app/api/admin/ads", tags=["后台-广告管理"]) -app.include_router(market_admin.router, prefix="/app/api/admin/market", tags=["后台-应用审核"]) -app.include_router(logs.router, prefix="/app/api/admin/logs", tags=["后台-日志风控"]) - -app.include_router(skills_admin.router, prefix="/app/api/admin/skills", tags=["后台-技能管理"]) -app.include_router(agents_admin.router, prefix="/app/api/admin/agents", tags=["后台-Agent管理"]) -app.include_router(knowledge_admin.router, prefix="/app/api/admin/knowledge", tags=["后台-知识库管理"]) -app.include_router(workflow_admin.router, prefix="/app/api/admin/workflows", tags=["后台-工作流编排"]) -app.include_router(asset_admin.router, prefix="/app/api/admin/assets", tags=["后台-资产管理"]) -app.include_router(storage_admin.router, prefix="/app/api/admin/storage", tags=["后台-存储配置"]) -app.include_router(automation_admin.router, prefix="/app/api/admin/automation", tags=["后台-自动化任务"]) -app.include_router(mcp_admin.router, prefix="/app/api/admin/mcp", tags=["后台-MCP服务"]) -app.include_router(developer_admin.router, prefix="/app/api/admin/developer", tags=["后台-开发者API"]) -app.include_router(developer_tools_router, prefix="/app/api/admin/developer-tools", tags=["dev-tools"]) -app.include_router(apps_admin.router, prefix="/app/api/admin/apps", tags=["admin-ai-apps"]) -app.include_router(mcp_market_router, prefix="/app/api/admin/mcp-market", tags=["mcp-market"]) -app.include_router(code_exec_admin.router, prefix="/app/api/admin/code-exec", tags=["code-exec"]) -app.include_router(distro_admin.router, prefix="/app/api/admin/distributions", tags=["distributions"]) -app.include_router(privacy_admin.router, prefix="/app/api/admin/privacy", tags=["privacy"]) -app.include_router(im_bot_admin.router, prefix="/app/api/admin/im-bot", tags=["im-bot"]) -app.include_router(im_bot_user_router, prefix="/app/api/im/wechat", tags=["im-wechat-user"]) -app.include_router(im_bot_platform_router, prefix="/app/api/im/platform", tags=["im-platform-user"]) -app.include_router(promotion_admin.router, prefix="/app/api/admin/promotion", tags=["promotion"]) -app.include_router(tenant_admin.router, prefix="/app/api/admin/tenant", tags=["tenant"]) -app.include_router(billing_admin.router, prefix="/app/api/admin/billing", tags=["billing"]) -app.include_router(analytics_admin.router, prefix="/app/api/admin/analytics", tags=["analytics"]) -app.include_router(user_portal.router, prefix="/app/api/portal", tags=["portal"]) -app.include_router(achievement_admin.router, prefix="/app/api/admin/achievement", tags=["achievement"]) -app.include_router(casdoor_router, prefix="/app/api/auth", tags=["Casdoor SSO"]) - - -@app.get("/app/api/health") -async def health_check(): - return {"status": "ok", "service": "Qihu-AIFlow"} +""" +Qihu-AIFlow FastAPI 主应用 +完整后端API,监听18080端口,所有接口以 /app 为前缀 +""" +import sys +import os +import time +import uuid +import logging +from contextlib import asynccontextmanager +from fastapi import FastAPI, Request +from fastapi.middleware.cors import CORSMiddleware +from starlette.middleware.base import BaseHTTPMiddleware +from datetime import datetime + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.api import auth, chat, knowledge, market, token_usage, payment, profile, notifications, assets, evolution +from app.casdoor_auth import router as casdoor_router +from app.api.admin import dashboard, users, models, settings, payment_admin, services, membership_admin, ads, market_admin, logs, skills_admin, agents_admin, knowledge_admin, workflow_admin, storage_admin, automation_admin, mcp_admin, developer_admin, apps_admin, distro_admin, code_exec_admin, privacy_admin, im_bot_admin, promotion_admin, achievement_admin, tenant_admin, billing_admin, analytics_admin, observability_admin +from app.api.im_bot_user import router as im_bot_user_router +from app.api.im_bot_platform import router as im_bot_platform_router +from app.api.portal import user_portal +from app.api.admin.developer_tools_admin import router as developer_tools_router +from app.api.admin.mcp_market_admin import router as mcp_market_router +from app.api.admin import asset_admin, achievement_admin +from app.config import setup_file_logging, SLOW_REQUEST_THRESHOLD +from app.core.observability import set_trace_id +from app.database import SessionLocal +from app.models import OperationLog as OperationLogModel, TokenUsageLog as TokenUsageLogModel + +# 导入平台适配器触发注册(AstrBot借鉴优化方案 P1) +import app.platform_adapters # noqa: F401 - 触发所有平台适配器装饰器注册 + +# 初始化文件日志(按天轮转) +access_logger = setup_file_logging() +logger = logging.getLogger("aiflow") + + +class APITrackingMiddleware(BaseHTTPMiddleware): + """API调用统计中间件 - 记录请求耗时,标记慢请求""" + + async def dispatch(self, request: Request, call_next): + request_id = str(uuid.uuid4()) + set_trace_id(request_id) + start_time = time.time() + path = request.url.path + method = request.method + + # 跳过健康检查和静态资源 + skip_paths = ["/app/api/health", "/favicon.ico"] + should_skip = any(path.startswith(p) for p in skip_paths) + + try: + response = await call_next(request) + elapsed = time.time() - start_time + status_code = response.status_code + response.headers["X-Trace-Id"] = request_id + + if not should_skip: + access_logger.info( + f"[{request_id}] {method} {path} -> {status_code} ({elapsed:.3f}s)" + ) + + # 异步记录API调用统计 + try: + db = SessionLocal() + try: + _record_api_call(db, method, path, status_code, elapsed) + finally: + db.close() + except Exception as e: + logger.warning(f"API统计记录失败: {e}") + + return response + except Exception as e: + elapsed = time.time() - start_time + if not should_skip: + access_logger.info( + f"[{request_id}] {method} {path} -> ERROR ({elapsed:.3f}s): {str(e)}" + ) + raise + + async def __call__(self, scope, receive, send): + if scope["type"] != "http": + await self.app(scope, receive, send) + return + await super().__call__(scope, receive, send) + + +def _record_api_call(db, method: str, path: str, status_code: int, elapsed: float): + """记录API调用到数据库统计表""" + # 简化路径以减少基数(去掉动态ID) + parts = path.split("/") + simplified_parts = [] + for part in parts: + # 将UUID样式的路径段替换为{id} + if len(part) == 36 and part.count("-") == 4: + simplified_parts.append("{id}") + # 将纯数字路径段替换为{id} + elif part.isdigit(): + simplified_parts.append("{id}") + else: + simplified_parts.append(part) + simplified_path = "/".join(simplified_parts) + + # 统计记录通过数据库实现,简化版记录到日志 + is_error = status_code >= 400 + is_slow = elapsed >= SLOW_REQUEST_THRESHOLD + + if is_slow: + logger.warning( + f"慢请求: {method} {simplified_path} -> {status_code} ({elapsed:.3f}s, 阈值: {SLOW_REQUEST_THRESHOLD}s)" + ) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info("Qihu-AIFlow v2.0.0 启动中...") + + # 启动后台 Evolution Pipeline(知识库图谱已存在的用户配置自动恢复运行) + try: + from app.services.evolution_service import EvolutionWatcherManager + from app.models import UserEvolutionConfig + db = SessionLocal() + try: + configs = db.query(UserEvolutionConfig).filter( + UserEvolutionConfig.watcher_status == "running" + ).all() + if configs: + watcher = EvolutionWatcherManager() + for cfg in configs: + try: + watcher.start_user_evolution(cfg.user_id, lambda: SessionLocal()) + logger.info(f"[Evolution] 恢复用户 {cfg.user_id} 的进化Pipeline") + except Exception as e: + logger.warning(f"[Evolution] 恢复用户 {cfg.user_id} 失败: {e}") + logger.info(f"[Evolution] 已恢复 {len(configs)} 个用户的进化Pipeline") + else: + logger.info("[Evolution] 无待恢复的进化配置") + finally: + db.close() + except Exception as e: + logger.warning(f"[Evolution] Pipeline自动启动异常(不影响正常服务): {e}") + + # 启动Redis事件总线 + try: + from app.event_bus import get_event_bus + event_bus = get_event_bus() + event_bus.start(num_consumers=2) + logger.info("[EventBus] Redis事件总线已启动") + except Exception as e: + logger.warning(f"[EventBus] 启动失败(不影响正常服务): {e}") + + logger.info("Qihu-AIFlow v2.0.0 启动完成") + + yield + + # 停止Redis事件总线 + try: + from app.event_bus import get_event_bus + get_event_bus().stop() + logger.info("[EventBus] Redis事件总线已停止") + except Exception: + pass + + # 优雅关闭:停止所有 Evolution Pipeline + try: + from app.services.evolution_service import EvolutionWatcherManager + EvolutionWatcherManager().shutdown_all() + logger.info("[Evolution] 所有Pipeline已停止") + except Exception: + pass + + logger.info("Qihu-AIFlow 正在关闭...") + + +app = FastAPI( + title="Qihu-AIFlow - 智能自动化PaaS平台", + version="1.0.0", + lifespan=lifespan, +) + +# API调用追踪中间件 +app.add_middleware(APITrackingMiddleware) + +# CORS配置 +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 前台 API +app.include_router(auth.router, prefix="/app/api/auth", tags=["认证"]) +app.include_router(chat.router, prefix="/app/api/chat", tags=["AI聊天"]) +app.include_router(knowledge.router, prefix="/app/api/knowledge", tags=["知识库"]) +app.include_router(market.router, prefix="/app/api/market", tags=["应用市场"]) +app.include_router(token_usage.router, prefix="/app/api/token", tags=["Token驾驶舱"]) +app.include_router(payment.router, prefix="/app/api/payment", tags=["付费中心"]) +app.include_router(profile.router, prefix="/app/api/profile", tags=["个人中心"]) +app.include_router(notifications.router, prefix="/app/api/notifications", tags=["消息通知"]) +app.include_router(assets.router, prefix="/app/api/assets", tags=["资产管理"]) +app.include_router(evolution.router, prefix="/app/api/evolution", tags=["AI自进化"]) + +# 后台 API +app.include_router(dashboard.router, prefix="/app/api/admin/dashboard", tags=["后台-仪表盘"]) +app.include_router(users.router, prefix="/app/api/admin/users", tags=["后台-用户管理"]) +app.include_router(models.router, prefix="/app/api/admin/models", tags=["后台-模型管理"]) +app.include_router(settings.router, prefix="/app/api/admin/settings", tags=["后台-网站设置"]) +app.include_router(payment_admin.router, prefix="/app/api/admin/payment", tags=["后台-支付配置"]) +app.include_router(services.router, prefix="/app/api/admin/services", tags=["后台-增值服务"]) +app.include_router(membership_admin.router, prefix="/app/api/admin/membership", tags=["后台-会员管理"]) +app.include_router(ads.router, prefix="/app/api/admin/ads", tags=["后台-广告管理"]) +app.include_router(market_admin.router, prefix="/app/api/admin/market", tags=["后台-应用审核"]) +app.include_router(logs.router, prefix="/app/api/admin/logs", tags=["后台-日志风控"]) + +app.include_router(skills_admin.router, prefix="/app/api/admin/skills", tags=["后台-技能管理"]) +app.include_router(agents_admin.router, prefix="/app/api/admin/agents", tags=["后台-Agent管理"]) +app.include_router(knowledge_admin.router, prefix="/app/api/admin/knowledge", tags=["后台-知识库管理"]) +app.include_router(workflow_admin.router, prefix="/app/api/admin/workflows", tags=["后台-工作流编排"]) +app.include_router(asset_admin.router, prefix="/app/api/admin/assets", tags=["后台-资产管理"]) +app.include_router(storage_admin.router, prefix="/app/api/admin/storage", tags=["后台-存储配置"]) +app.include_router(automation_admin.router, prefix="/app/api/admin/automation", tags=["后台-自动化任务"]) +app.include_router(mcp_admin.router, prefix="/app/api/admin/mcp", tags=["后台-MCP服务"]) +app.include_router(developer_admin.router, prefix="/app/api/admin/developer", tags=["后台-开发者API"]) +app.include_router(developer_tools_router, prefix="/app/api/admin/developer-tools", tags=["dev-tools"]) +app.include_router(apps_admin.router, prefix="/app/api/admin/apps", tags=["admin-ai-apps"]) +app.include_router(mcp_market_router, prefix="/app/api/admin/mcp-market", tags=["mcp-market"]) +app.include_router(code_exec_admin.router, prefix="/app/api/admin/code-exec", tags=["code-exec"]) +app.include_router(distro_admin.router, prefix="/app/api/admin/distributions", tags=["distributions"]) +app.include_router(privacy_admin.router, prefix="/app/api/admin/privacy", tags=["privacy"]) +app.include_router(im_bot_admin.router, prefix="/app/api/admin/im-bot", tags=["im-bot"]) +app.include_router(im_bot_user_router, prefix="/app/api/im/wechat", tags=["im-wechat-user"]) +app.include_router(im_bot_platform_router, prefix="/app/api/im/platform", tags=["im-platform-user"]) +app.include_router(promotion_admin.router, prefix="/app/api/admin/promotion", tags=["promotion"]) +app.include_router(tenant_admin.router, prefix="/app/api/admin/tenant", tags=["tenant"]) +app.include_router(billing_admin.router, prefix="/app/api/admin/billing", tags=["billing"]) +app.include_router(analytics_admin.router, prefix="/app/api/admin/analytics", tags=["analytics"]) +app.include_router(observability_admin.router, prefix="/app/api/admin/observability", tags=["后台-调用可观测"]) +app.include_router(user_portal.router, prefix="/app/api/portal", tags=["portal"]) +app.include_router(achievement_admin.router, prefix="/app/api/admin/achievement", tags=["achievement"]) +app.include_router(casdoor_router, prefix="/app/api/auth", tags=["Casdoor SSO"]) + + +@app.get("/app/api/health") +async def health_check(): + return {"status": "ok", "service": "Qihu-AIFlow"} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4e3d2aa..9de57b7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,175 +1,177 @@ -import React, { useState, useEffect, Suspense } from 'react'; -import { Routes, Route, Navigate } from 'react-router-dom'; -import type { User } from '@/types'; -import { AuthContext, AuthContextType } from '@/contexts/AuthContext'; -import { authAPI } from '@/api'; - -// Layouts(保留为即时加载,构成应用外壳) -import FrontLayout from '@/components/layout/FrontLayout'; -import AdminLayout from '@/components/layout/AdminLayout'; - -// 路由级代码分割:每个页面独立打包成 chunk,首屏只加载外壳与公共依赖, -// 访问具体页面时才按需加载对应 chunk,显著加快首屏渲染与路由切换速度。 -const LandingPage = React.lazy(() => import('@/pages/front/LandingPage')); -const LoginPage = React.lazy(() => import('@/pages/front/LoginPage')); -const ChatPage = React.lazy(() => import('@/pages/front/ChatPage')); -const KnowledgePage = React.lazy(() => import('@/pages/front/KnowledgePage')); -const MarketPage = React.lazy(() => import('@/pages/front/MarketPage')); -const TokenDashboardPage = React.lazy(() => import('@/pages/front/TokenDashboardPage')); -const PricingPage = React.lazy(() => import('@/pages/front/PricingPage')); -const ProfilePage = React.lazy(() => import('@/pages/front/ProfilePage')); -const NotificationPage = React.lazy(() => import('@/pages/front/NotificationPage')); -const AssetsPage = React.lazy(() => import('@/pages/front/AssetsPage')); -const NovelPreviewPage = React.lazy(() => import('@/pages/front/NovelPreviewPage')); -const EvolutionDashboard = React.lazy(() => import('@/pages/front/EvolutionDashboard')); - -const AdminDashboard = React.lazy(() => import('@/pages/admin/Dashboard')); -const AdminUsers = React.lazy(() => import('@/pages/admin/Users')); -const AdminModels = React.lazy(() => import('@/pages/admin/Models')); -const AdminSiteSettings = React.lazy(() => import('@/pages/admin/SiteSettings')); -const AdminSmsConfig = React.lazy(() => import('@/pages/admin/SmsConfig')); -const AdminPayment = React.lazy(() => import('@/pages/admin/Payment')); -const AdminServices = React.lazy(() => import('@/pages/admin/Services')); -const AdminMembership = React.lazy(() => import('@/pages/admin/Membership')); -const AdminAds = React.lazy(() => import('@/pages/admin/Ads')); -const AdminMarket = React.lazy(() => import('@/pages/admin/Market')); -const AdminLogs = React.lazy(() => import('@/pages/admin/Logs')); -const AdminSkills = React.lazy(() => import('@/pages/admin/Skills')); -const AdminAgents = React.lazy(() => import('@/pages/admin/Agents')); -const AdminCodeExec = React.lazy(() => import('@/pages/admin/CodeExec')); -const DistributionBuilder = React.lazy(() => import('@/pages/admin/DistributionBuilder')); -const ImBots = React.lazy(() => import('@/pages/admin/ImBots')); -const PrivacySettings = React.lazy(() => import('@/pages/admin/PrivacySettings')); -const Promotion = React.lazy(() => import('@/pages/admin/Promotion')); -const Achievement = React.lazy(() => import('@/pages/admin/Achievement')); -const Tenant = React.lazy(() => import('@/pages/admin/Tenant')); -const Billing = React.lazy(() => import('@/pages/admin/Billing')); -const Analytics = React.lazy(() => import('@/pages/admin/Analytics')); -const AdminKnowledge = React.lazy(() => import('@/pages/admin/Knowledge')); -const AdminWorkflow = React.lazy(() => import('@/pages/admin/Workflow')); -const AdminStorage = React.lazy(() => import('@/pages/admin/Storage')); -const AdminDeveloper = React.lazy(() => import('@/pages/admin/Developer')); -const AdminDeveloperTools = React.lazy(() => import('@/pages/admin/DeveloperTools')); -const AdminAutomation = React.lazy(() => import('@/pages/admin/Automation')); -const AdminMcp = React.lazy(() => import('@/pages/admin/Mcp')); -const AdminApps = React.lazy(() => import('@/pages/admin/Apps')); -const AssetCenter = React.lazy(() => import('@/pages/admin/AssetCenter')); -const UserPortal = React.lazy(() => import('@/pages/UserPortal')); -const WechatBot = React.lazy(() => import('@/pages/front/WechatBot')); -const ImBotHub = React.lazy(() => import('@/pages/front/ImBotHub')); -const AppServices = React.lazy(() => import('@/pages/front/AppServices')); - -const pageFallback =
加载中...
; - -const App: React.FC = () => { - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - - // 初始化:从localStorage恢复登录状态 - useEffect(() => { - const token = localStorage.getItem('aiflow_token'); - const cachedUser = localStorage.getItem('aiflow_user'); - if (token && cachedUser) { - try { - setUser(JSON.parse(cachedUser)); - } catch { - localStorage.removeItem('aiflow_token'); - localStorage.removeItem('aiflow_user'); - } - } - setLoading(false); - }, []); - - const login = (u: User) => { - setUser(u); - localStorage.setItem('aiflow_user', JSON.stringify(u)); - }; - const logout = () => { - setUser(null); - localStorage.removeItem('aiflow_token'); - localStorage.removeItem('aiflow_user'); - }; - const isAdmin = user?.role === 'admin'; - - if (loading) { - return
加载中...
; - } - - return ( - - - - {/* Login */} - : ) : - } /> - - {/* Root - Landing for guests, redirect for authenticated users */} - : ) : - } /> - - {/* Front Routes (authenticated only) */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - {/* Admin Routes */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - {/* 公开前台首页:不受登录态拦截,供后台「访问前台」直接打开 */} - } /> - } /> - - - - ); -}; - -export default App; +import React, { useState, useEffect, Suspense } from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; +import type { User } from '@/types'; +import { AuthContext, AuthContextType } from '@/contexts/AuthContext'; +import { authAPI } from '@/api'; + +// Layouts(保留为即时加载,构成应用外壳) +import FrontLayout from '@/components/layout/FrontLayout'; +import AdminLayout from '@/components/layout/AdminLayout'; + +// 路由级代码分割:每个页面独立打包成 chunk,首屏只加载外壳与公共依赖, +// 访问具体页面时才按需加载对应 chunk,显著加快首屏渲染与路由切换速度。 +const LandingPage = React.lazy(() => import('@/pages/front/LandingPage')); +const LoginPage = React.lazy(() => import('@/pages/front/LoginPage')); +const ChatPage = React.lazy(() => import('@/pages/front/ChatPage')); +const KnowledgePage = React.lazy(() => import('@/pages/front/KnowledgePage')); +const MarketPage = React.lazy(() => import('@/pages/front/MarketPage')); +const TokenDashboardPage = React.lazy(() => import('@/pages/front/TokenDashboardPage')); +const PricingPage = React.lazy(() => import('@/pages/front/PricingPage')); +const ProfilePage = React.lazy(() => import('@/pages/front/ProfilePage')); +const NotificationPage = React.lazy(() => import('@/pages/front/NotificationPage')); +const AssetsPage = React.lazy(() => import('@/pages/front/AssetsPage')); +const NovelPreviewPage = React.lazy(() => import('@/pages/front/NovelPreviewPage')); +const EvolutionDashboard = React.lazy(() => import('@/pages/front/EvolutionDashboard')); + +const AdminDashboard = React.lazy(() => import('@/pages/admin/Dashboard')); +const AdminUsers = React.lazy(() => import('@/pages/admin/Users')); +const AdminModels = React.lazy(() => import('@/pages/admin/Models')); +const AdminSiteSettings = React.lazy(() => import('@/pages/admin/SiteSettings')); +const AdminSmsConfig = React.lazy(() => import('@/pages/admin/SmsConfig')); +const AdminPayment = React.lazy(() => import('@/pages/admin/Payment')); +const AdminServices = React.lazy(() => import('@/pages/admin/Services')); +const AdminMembership = React.lazy(() => import('@/pages/admin/Membership')); +const AdminAds = React.lazy(() => import('@/pages/admin/Ads')); +const AdminMarket = React.lazy(() => import('@/pages/admin/Market')); +const AdminLogs = React.lazy(() => import('@/pages/admin/Logs')); +const AdminSkills = React.lazy(() => import('@/pages/admin/Skills')); +const AdminAgents = React.lazy(() => import('@/pages/admin/Agents')); +const AdminCodeExec = React.lazy(() => import('@/pages/admin/CodeExec')); +const DistributionBuilder = React.lazy(() => import('@/pages/admin/DistributionBuilder')); +const ImBots = React.lazy(() => import('@/pages/admin/ImBots')); +const PrivacySettings = React.lazy(() => import('@/pages/admin/PrivacySettings')); +const Promotion = React.lazy(() => import('@/pages/admin/Promotion')); +const Achievement = React.lazy(() => import('@/pages/admin/Achievement')); +const Tenant = React.lazy(() => import('@/pages/admin/Tenant')); +const Billing = React.lazy(() => import('@/pages/admin/Billing')); +const Analytics = React.lazy(() => import('@/pages/admin/Analytics')); +const Observability = React.lazy(() => import('@/pages/admin/Observability')); +const AdminKnowledge = React.lazy(() => import('@/pages/admin/Knowledge')); +const AdminWorkflow = React.lazy(() => import('@/pages/admin/Workflow')); +const AdminStorage = React.lazy(() => import('@/pages/admin/Storage')); +const AdminDeveloper = React.lazy(() => import('@/pages/admin/Developer')); +const AdminDeveloperTools = React.lazy(() => import('@/pages/admin/DeveloperTools')); +const AdminAutomation = React.lazy(() => import('@/pages/admin/Automation')); +const AdminMcp = React.lazy(() => import('@/pages/admin/Mcp')); +const AdminApps = React.lazy(() => import('@/pages/admin/Apps')); +const AssetCenter = React.lazy(() => import('@/pages/admin/AssetCenter')); +const UserPortal = React.lazy(() => import('@/pages/UserPortal')); +const WechatBot = React.lazy(() => import('@/pages/front/WechatBot')); +const ImBotHub = React.lazy(() => import('@/pages/front/ImBotHub')); +const AppServices = React.lazy(() => import('@/pages/front/AppServices')); + +const pageFallback =
加载中...
; + +const App: React.FC = () => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + // 初始化:从localStorage恢复登录状态 + useEffect(() => { + const token = localStorage.getItem('aiflow_token'); + const cachedUser = localStorage.getItem('aiflow_user'); + if (token && cachedUser) { + try { + setUser(JSON.parse(cachedUser)); + } catch { + localStorage.removeItem('aiflow_token'); + localStorage.removeItem('aiflow_user'); + } + } + setLoading(false); + }, []); + + const login = (u: User) => { + setUser(u); + localStorage.setItem('aiflow_user', JSON.stringify(u)); + }; + const logout = () => { + setUser(null); + localStorage.removeItem('aiflow_token'); + localStorage.removeItem('aiflow_user'); + }; + const isAdmin = user?.role === 'admin'; + + if (loading) { + return
加载中...
; + } + + return ( + + + + {/* Login */} + : ) : + } /> + + {/* Root - Landing for guests, redirect for authenticated users */} + : ) : + } /> + + {/* Front Routes (authenticated only) */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* Admin Routes */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* 公开前台首页:不受登录态拦截,供后台「访问前台」直接打开 */} + } /> + } /> + + + + ); +}; + +export default App; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 6e67928..1b3fd6f 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -1,1986 +1,1987 @@ -/** - - * AIFlow API 客户端 - - * 统一管理所有前后端API请求 - - */ - -const API_BASE = '/app/api'; - - - -// 从localStorage获取token - -function getToken(): string | null { - - return localStorage.getItem('aiflow_token'); - -} - - - -// 通用请求方法 - -export async function request( - - endpoint: string, - - options: RequestInit = {} - -): Promise<{ code: number; data: T; message: string }> { - - const token = getToken(); - - const headers: Record = { - - 'Content-Type': 'application/json', - - ...(options.headers as Record || {}), - - }; - - if (token) { - - headers['Authorization'] = `Bearer ${token}`; - - } - - const res = await fetch(`${API_BASE}${endpoint}`, { - - ...options, - - headers, - - }); - - if (res.status === 401) { - - // 登录接口的401需要精确提示(用户名不存在 / 密码错误) - - if (endpoint === '/auth/login') { - - const err = await res.json().catch(() => ({ detail: '登录失败' })); - - const error: any = new Error(err.detail || '登录失败'); - - error.status = 401; - - error.detail = err.detail; - - throw error; - - } - - // 其他接口的401 = token过期/未登录,清除状态并跳转 - - localStorage.removeItem('aiflow_token'); - - localStorage.removeItem('aiflow_user'); - - window.location.href = '/login'; - - throw new Error('未登录'); - - } - - if (res.status === 403) { - const err = await res.json().catch(() => ({ detail: 'Forbidden' })); - if (err.detail === 'Not authenticated' || err.detail === '需要管理员权限') { - localStorage.removeItem('aiflow_token'); - localStorage.removeItem('aiflow_user'); - window.location.href = '/login'; - throw new Error('未登录或权限不足'); - } - } - - if (!res.ok) { - - const err = await res.json().catch(() => ({ detail: '请求失败' })); - - const error: any = new Error(err.detail || err.message || '请求失败'); - - error.status = res.status; - - error.response = { status: res.status, data: err }; - - error.detail = err.detail; - - throw error; - - } - - return res.json(); - -} - - - -// GET请求 - -function get(endpoint: string): Promise<{ code: number; data: T; message: string }> { - - return request(endpoint); - -} - - - -// POST请求 - -function post(endpoint: string, body?: any): Promise<{ code: number; data: T; message: string }> { - - return request(endpoint, { - - method: 'POST', - - body: body ? JSON.stringify(body) : undefined, - - }); - -} - - - -// PUT请求 - -function put(endpoint: string, body?: any): Promise<{ code: number; data: T; message: string }> { - - return request(endpoint, { - - method: 'PUT', - - body: body ? JSON.stringify(body) : undefined, - - }); - -} - - - -// DELETE请求 - -function del(endpoint: string): Promise<{ code: number; data: T; message: string }> { - - return request(endpoint, { method: 'DELETE' }); - -} - - - -// ============================================================ - -// 认证相关API - -// ============================================================ - -export const authAPI = { - - login: (username: string, password: string) => - - post('/auth/login', { username, password }), - - register: (data: { username: string; password: string; email?: string; phone?: string }) => - - post('/auth/register', data), - - getMe: () => get('/auth/me'), - - logout: () => post('/auth/logout'), - - getSettings: () => get('/auth/settings'), - - sendCode: (phone: string, purpose: string) => - post('/auth/send-code', { phone, purpose }), - - resetPassword: (data: { phone: string; code: string; new_password: string }) => - post('/auth/reset-password', data), - -}; - - - -// ============================================================ - -// 聊天相关API - -// ============================================================ - -export const chatAPI = { - - getConversations: (status?: string) => - - get(`/chat/conversations${status ? `?status=${status}` : ''}`), - - createConversation: (data: { title: string; model_id?: string }) => - - post('/chat/conversations', data), - - updateConversation: (id: string, data: any) => - - put(`/chat/conversations/${id}`, data), - - deleteConversation: (id: string) => - - del(`/chat/conversations/${id}`), - - getMessages: (convId: string) => - - get(`/chat/conversations/${convId}/messages`), - - sendMessage: (convId: string, data: { content: string; model_id?: string; agent_ids?: string[]; skill_ids?: string[]; knowledge_base_ids?: string[]; use_tools?: boolean }) => - - post(`/chat/conversations/${convId}/messages`, data), - - clearContext: (convId: string) => - - post(`/chat/conversations/${convId}/clear`), - - convertToKnowledgeGraph: (convId: string) => - - post(`/chat/conversations/${convId}/to-knowledge-graph/`), - - getModels: () => get('/chat/models'), - -}; - - - - - - -// ============================================================ - -// 知识库相关API - -// ============================================================ - -export const knowledgeAPI = { - - getBases: () => get('/knowledge/bases'), - - createBase: (data: any) => post('/knowledge/bases', data), - - getBase: (id: string) => get(`/knowledge/bases/${id}`), - - updateBase: (id: string, data: any) => put(`/knowledge/bases/${id}`, data), - - deleteBase: (id: string) => del(`/knowledge/bases/${id}`), - - getDocuments: (baseId: string) => get(`/knowledge/bases/${baseId}/documents`), - - getDocument: (baseId: string, docId: string) => get(`/knowledge/bases/${baseId}/documents/${docId}`), - - uploadDocument: (baseId: string, data: any) => post(`/knowledge/bases/${baseId}/documents`, data), - - deleteDocument: (baseId: string, docId: string) => del(`/knowledge/bases/${baseId}/documents/${docId}`), - - getDirectories: () => get('/knowledge/directories'), - - createDirectory: (data: any) => post('/knowledge/directories', data), - - buildGraph: (baseId: string) => post(`/knowledge/bases/${baseId}/build-graph`), - - getGraph: (baseId: string) => get(`/knowledge/bases/${baseId}/graph`), - - searchQA: (baseId: string, question: string) => post('/knowledge/qa', { base_id: baseId, question }), - - searchDocuments: (baseId: string, keyword: string) => get(`/knowledge/bases/${baseId}/search?keyword=${encodeURIComponent(keyword)}`), - -}; - - - -// ============================================================ - -// 应用市场相关API - -// ============================================================ - -export const marketAPI = { - - getApps: (params?: { type?: string; source?: string; capability?: string; search?: string; page?: number; page_size?: number }) => { - - const searchParams = new URLSearchParams(); - - if (params?.type) searchParams.set('type', params.type); - - if (params?.source) searchParams.set('source', params.source); - - if (params?.capability) searchParams.set('capability', params.capability); - - if (params?.search) searchParams.set('search', params.search); - - if (params?.page) searchParams.set('page', String(params.page)); - - if (params?.page_size) searchParams.set('page_size', String(params.page_size)); - - const qs = searchParams.toString(); - - return get(`/market/apps${qs ? `?${qs}` : ''}`); - - }, - - getApp: (id: string) => get(`/market/apps/${id}`), - - createApp: (data: any) => post('/market/apps', data), - - verifyAgent: (data: any) => post('/market/apps/verify-agent', data), - - detectCapability: (data: any) => post('/market/apps/detect-capability', data), - - purchaseApp: (id: string) => post(`/market/apps/${id}/purchase`), - - installApp: (id: string) => post(`/market/apps/${id}/install`), - - uninstallApp: (id: string) => del(`/market/apps/${id}/uninstall`), - - reviewApp: (id: string, data: { rating: number; content?: string }) => - - post(`/market/apps/${id}/review`, data), - - getMyApps: () => get('/market/my-apps'), - - getReviews: (appId: string) => get(`/market/reviews/${appId}`), - -}; - - - -// ============================================================ - -// Token相关API - -// ============================================================ - -export const tokenAPI = { - - getOverview: () => get('/token/overview'), - - getRankings: () => get('/token/rankings'), - - getLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - const qs = sp.toString(); - - return get(`/token/logs${qs ? `?${qs}` : ''}`); - - }, - - getBalance: () => get('/token/balance'), - - recharge: (amount: number) => post('/token/recharge', { amount }), - -}; - - - -// ============================================================ - -// 付费相关API - -// ============================================================ - -export const paymentAPI = { - - getPlans: () => get('/payment/plans'), - - createOrder: (data: any) => post('/payment/orders', data), - - getOrders: (status?: string) => get(`/payment/orders${status ? `?status=${status}` : ''}`), - - getOrder: (id: string) => get(`/payment/orders/${id}`), - - payOrder: (id: string, method?: string) => post(`/payment/orders/${id}/pay`, { payment_method: method }), - - getCoupons: () => get('/payment/coupons'), - - useCoupon: (code: string) => post('/payment/coupons/use', { code }), - - getServices: () => get('/payment/services'), - - createCustomOrder: (data: any) => post('/payment/custom-services', data), - - createServiceRequest: (data: any) => post('/payment/service-requests', data), - -}; - - - -// ============================================================ - -// 个人中心相关API - -// ============================================================ - -export const profileAPI = { - - getProfile: () => get('/profile/'), - - updateProfile: (data: any) => put('/profile/', data), - - updatePassword: (data: { old_password: string; new_password: string }) => - - put('/profile/password', data), - - getLoginLogs: () => get('/profile/login-logs'), - - getApiKeys: () => get('/profile/api-keys'), - - createApiKey: (name?: string) => post('/profile/api-keys', { name }), - - updateApiKey: (id: string, status: string) => put(`/profile/api-keys/${id}`, { status }), - - getMembership: () => get('/profile/membership'), - - getPurchasedApps: () => get('/profile/purchased-apps'), - -}; - - - -// ============================================================ - -// 通知相关API - -// ============================================================ - -export const notificationAPI = { - - getNotifications: (params?: { type?: string; page?: number }) => { - - const sp = new URLSearchParams(); - - if (params?.type) sp.set('type', params.type); - - if (params?.page) sp.set('page', String(params.page)); - - const qs = sp.toString(); - - return get(`/notifications/${qs ? `?${qs}` : ''}`); - - }, - - markRead: (id: string) => put(`/notifications/${id}/read`), - - markAllRead: () => put('/notifications/read-all'), - - getUnreadCount: () => get('/notifications/unread-count'), - -}; - - - -// ============================================================ - -// 资产管理相关API - -// ============================================================ - -export const assetsAPI = { - - getAssets: (params?: { category?: string; page?: number; page_size?: number }) => { - const qs: string[] = []; - if (params?.category) qs.push(`category=${encodeURIComponent(params.category)}`); - if (params?.page) qs.push(`page=${params.page}`); - if (params?.page_size) qs.push(`page_size=${params.page_size}`); - const q = qs.length ? `?${qs.join('&')}` : ''; - return get(`/assets/${q}`); - }, - - createAsset: (data: any) => post('/assets/', data), - - getAsset: (id: string) => get(`/assets/${id}`), - - updateAsset: (id: string, data: any) => put(`/assets/${id}`, data), - - deleteAsset: (id: string) => del(`/assets/${id}`), - - getCategories: () => get('/assets/categories'), - - getNovelChapters: (novelId: string) => get(`/assets/novels/${novelId}/chapters`), - - addChapter: (novelId: string, data: any) => post(`/assets/novels/${novelId}/chapters`, data), - -}; - - - -// ============================================================ - -// 后台管理API - -// ============================================================ - -export const adminAPI = { - - // 仪表盘 - - getStats: () => get('/admin/dashboard/stats'), - - getRecentLogs: () => get('/admin/dashboard/recent-logs'), - - getRecentAudit: () => get('/admin/dashboard/recent-audit'), - - getDailyStats: (days?: number) => get<{ days: number; data: Array<{ date: string; tokens: number }> }>(`/admin/dashboard/daily-stats${days ? `?days=${days}` : ''}`), - - // 用户管理 - - getUsers: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/users/${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - getUser: (id: string) => get(`/admin/users/${id}`), - - banUser: (id: string) => put(`/admin/users/${id}/ban`), - - unbanUser: (id: string) => put(`/admin/users/${id}/unban`), - - getRegisterSettings: () => get('/admin/users/register-settings'), - - updateRegisterSettings: (data: any) => put('/admin/users/register-settings', data), - - // 模型管理 - - getProviders: () => get('/admin/models/providers'), - - addProvider: (data: any) => post('/admin/models/providers', data), - - updateProvider: (id: string, data: any) => put(`/admin/models/providers/${id}`, data), - - deleteProvider: (id: string) => del(`/admin/models/providers/${id}`), - - fetchModels: (id: string) => post(`/admin/models/providers/${id}/fetch-models`), - - addModels: (id: string, modelIds: string[]) => post(`/admin/models/providers/${id}/add-models`, { model_ids: modelIds }), - - getProtocols: () => get('/admin/models/protocols'), - - getAdminModels: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/models/${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - addModel: (data: any) => post('/admin/models/', data), - - updateModel: (id: string, data: any) => put(`/admin/models/${id}`, data), - - deleteModel: (id: string) => del(`/admin/models/${id}`), - - toggleModel: (id: string, isEnabled: boolean) => put(`/admin/models/${id}/toggle?is_enabled=${isEnabled}`), - - setDefaultModel: (id: string) => put(`/admin/models/${id}/set-default`), - - // 按能力默认模型(Auto 自动模式使用) - getCapabilities: () => get('/admin/models/capabilities'), - getCapabilityDefaults: () => get('/admin/models/capability-defaults'), - setCapabilityDefault: (capability: string, modelId: string | null) => - put('/admin/models/capability-defaults', { capability, model_id: modelId }), - - // Phase 2: Enhanced model management - - testModelConnection: (id: string) => post(`/admin/models/${id}/test-connection`), - - testAllModels: () => post('/admin/models/test-all'), - - fetchProviderModels: (id: string) => post(`/admin/models/providers/${id}/fetch-models`), - - createAdminModel: (data: any) => post('/admin/models/', data), - - updateAdminModel: (id: string, data: any) => put(`/admin/models/${id}`, data), - - deleteAdminModel: (id: string) => del(`/admin/models/${id}`), - - getModelStats: () => get('/admin/models/stats'), - - // 网站设置 - - getSiteSettings: () => get('/admin/settings/site'), - - updateSiteSettings: (data: any) => put('/admin/settings/site', data), - - getEmailConfig: () => get('/admin/settings/email'), - - updateEmailConfig: (data: any) => put('/admin/settings/email', data), - - testEmail: (params?: { test_to?: string; test_subject?: string; test_body?: string }) => { - - const qs = new URLSearchParams(); - - if (params?.test_to) qs.set('test_to', params.test_to); - - if (params?.test_subject) qs.set('test_subject', params.test_subject); - - if (params?.test_body) qs.set('test_body', params.test_body); - - const qstr = qs.toString(); - - return post(`/admin/settings/email/test${qstr ? `?${qstr}` : ''}`); - - }, - - generateEmailContent: (topic: string) => post(`/admin/settings/email/generate-content?topic=${encodeURIComponent(topic)}`), - - getSmsConfig: () => get('/admin/settings/sms'), - - updateSmsConfig: (data: any) => put('/admin/settings/sms', data), - - testSms: () => post('/admin/settings/sms/test'), - - getSmsBalance: () => post('/admin/settings/sms/balance'), - - // 支付配置 - - getPaymentConfigs: () => get('/admin/payment/configs'), - - updatePaymentConfig: (id: string, data: any) => put(`/admin/payment/configs/${id}`, data), - - getAdminOrders: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/payment/orders${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - refundOrder: (id: string) => put(`/admin/payment/orders/${id}/refund`), - - // 增值服务 - - getServices: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/services/${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - addService: (data: any) => post('/admin/services/', data), - - updateService: (id: string, data: any) => put(`/admin/services/${id}`, data), - - deleteService: (id: string) => del(`/admin/services/${id}`), - - getCustomOrders: (status?: string) => get(`/admin/services/custom-orders${status ? `?status=${status}` : ''}`), - - updateCustomOrder: (id: string, data: any) => put(`/admin/services/custom-orders/${id}`, data), - - getCustomRequests: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/services/custom-requests${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - updateCustomRequest: (id: string, data: any) => put(`/admin/services/custom-requests/${id}`, data), - - getAvailableAgents: () => get('/admin/services/agents'), - - assignAgentToService: (id: string, agentId: string) => put(`/admin/services/${id}/agent?agent_id=${agentId}`), - - // 会员管理(增强版) - - getPlans: () => get('/admin/membership/plans'), - - addPlan: (params: string) => post(`/admin/membership/plans${params}`), - - updatePlan: (id: string, params: string) => put(`/admin/membership/plans/${id}${params}`), - - deletePlan: (id: string) => del(`/admin/membership/plans/${id}`), - - getSubscriptions: (page?: number) => get(`/admin/membership/subscriptions${page ? `?page=${page}` : ''}`), - - getAutomationRules: () => get('/admin/membership/automation-rules'), - - getBenefitsComparison: () => get('/admin/membership/benefits-comparison'), - - getMembershipConfigs: () => get('/admin/membership/global-configs'), - - updateMembershipConfig: (key: string, params: string) => put(`/admin/membership/global-configs/${key}${params}`), - - getUserMemberships: (page?: number, planId?: string, status?: string) => { - - const p = new URLSearchParams(); - - if (page) p.append('page', String(page)); - - if (planId) p.append('plan_id', planId); - - if (status) p.append('status', status); - - const qs = p.toString(); - - return get(`/admin/membership/user-memberships${qs ? `?${qs}` : ''}`); - - }, - - assignPlan: (userId: string, planId: string, days: number) => put(`/admin/membership/users/${userId}/assign-plan?plan_id=${planId}&duration_days=${days}`), - - getPaymentLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/payment-logs${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - getAuditLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/audit-logs${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - // 新增:日志统计 - - getLogStats: () => get('/admin/logs/stats'), - - // 新增:API调用统计 - - getApiStats: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/api-stats${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - // 新增:慢请求记录 - - getSlowRequests: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/slow-requests${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - // 新增:操作类型枚举 - - getActionTypes: () => get('/admin/logs/action-types'), - - // 新增:审计日志导出 - - exportAuditLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/export/audit-logs${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - exportOperationLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/export/operations${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - exportTokenLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/export/token-logs${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - getRiskRules: () => get('/admin/logs/risk-rules'), - - addRiskRule: (data: any) => post('/admin/logs/risk-rules', data), - - updateRiskRule: (id: string, data: any) => put(`/admin/logs/risk-rules/${id}`, data), - - deleteRiskRule: (id: string) => del(`/admin/logs/risk-rules/${id}`), - - // 技能管理 (Phase 2.2) - - getAdminSkills: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); - - return get(`/admin/skills/${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - getAdminSkill: (id: string) => get(`/admin/skills/${id}`), - - createAdminSkill: (data: any) => post('/admin/skills/', data), - - updateAdminSkill: (id: string, data: any) => put(`/admin/skills/${id}`, data), - - deleteAdminSkill: (id: string) => del(`/admin/skills/${id}`), - - changeAdminSkillState: (id: string, data: any) => put(`/admin/skills/${id}/state`, data), - - getAdminSkillVersions: (id: string) => get(`/admin/skills/${id}/versions`), - - rollbackAdminSkill: (id: string, data: any) => post(`/admin/skills/${id}/rollback`, data), - - publishAdminSkill: (id: string, data: any) => post(`/admin/skills/${id}/publish`, data), - - unpublishAdminSkill: (id: string) => post(`/admin/skills/${id}/unpublish`), - - getAdminSkillsStats: () => get('/admin/skills/stats/overview'), - -// 智能体管理 (Phase 2.3) - - getAdminAgents: (params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/agents/${query}`); - - }, - - getAdminAgent: (id: string) => get(`/admin/agents/${id}`), - - createAdminAgent: (data: any) => post('/admin/agents/', data), - - updateAdminAgent: (id: string, data: any) => put(`/admin/agents/${id}`, data), - - deleteAdminAgent: (id: string) => del(`/admin/agents/${id}`), - - toggleAdminAgent: (id: string, isActive: boolean) => put(`/admin/agents/${id}/toggle`, { is_active: isActive }), - - getAdminAgentsStats: () => get('/admin/agents/stats/overview'), - - getAdminAgentReviews: (id: string) => get(`/admin/agents/${id}/reviews`), - - getAdminAvailableSkills: (params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/agents/available/skills${query}`); - - }, - - getAdminAvailableModels: (params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/agents/available/models${query}`); - - }, - - // 知识库管理 (Phase 2.4) - - getAdminKnowledgeStats: () => get('/admin/knowledge/stats/overview'), - - getAdminKnowledge: (params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/knowledge/${query}`); - - }, - - getAdminKnowledgeDetail: (id: string) => get(`/admin/knowledge/${id}`), - - createAdminKnowledge: (data: any) => post('/admin/knowledge/', data), - - updateAdminKnowledge: (id: string, data: any) => put(`/admin/knowledge/${id}`, data), - - deleteAdminKnowledge: (id: string) => del(`/admin/knowledge/${id}`), - - toggleAdminKnowledge: (id: string, isActive: boolean) => put(`/admin/knowledge/${id}/toggle`, { is_active: isActive }), - - getAdminKnowledgeDocs: (kbId: string, params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/knowledge/${kbId}/documents${query}`); - - }, - - deleteAdminKnowledgeDoc: (kbId: string, docId: string) => del(`/admin/knowledge/${kbId}/documents/${docId}`), - - getAdminKnowledgeExtServices: () => get('/admin/knowledge/external/services'), - - createAdminKnowledgeExtService: (data: any) => post('/admin/knowledge/external/services', data), - - updateAdminKnowledgeExtService: (id: string, data: any) => put(`/admin/knowledge/external/services/${id}`, data), - - deleteAdminKnowledgeExtService: (id: string) => del(`/admin/knowledge/external/services/${id}`), - - checkAdminKnowledgeExtStatus: (id: string) => get(`/admin/knowledge/external/services/${id}/status`), - - syncAdminKnowledge: (kbId: string, data: any) => post(`/admin/knowledge/external/sync/${kbId}`, data), - - batchDeleteAdminKnowledge: (ids: string[]) => post('/admin/knowledge/batch/delete', ids), - - // OSS存储配置 (Phase 3.1) - - getAssetStats: () => get('/admin/assets/stats'), - - getAdminAssets: (query: string) => get(`/admin/assets/?${query}`), - - deleteAdminAsset: (id: string) => del(`/admin/assets/${id}`), - - getAssetActivity: () => get('/admin/assets/activity'), - - getAdminStorageStats: () => get('/admin/storage/stats/overview'), - - getAdminStorageConfigs: (params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/storage/${query}`); - - }, - - getAdminStorageConfig: (id: string) => get(`/admin/storage/${id}`), - - createAdminStorageConfig: (data: any) => post('/admin/storage/', data), - - updateAdminStorageConfig: (id: string, data: any) => put(`/admin/storage/${id}`, data), - - deleteAdminStorageConfig: (id: string) => del(`/admin/storage/${id}`), - - toggleAdminStorageConfig: (id: string) => put(`/admin/storage/${id}/toggle`), - - setDefaultAdminStorageConfig: (id: string) => put(`/admin/storage/${id}/set-default`), - - testAdminStorageConfig: (data: any) => post('/admin/storage/test', data), - - getAdminStorageUsage: (id: string) => get(`/admin/storage/${id}/usage`), - - // Storage Quota APIs (Phase C5) - - getStorageQuotaOverview: () => get('/admin/storage/quota/overview'), - - getStorageQuotaUsers: (params?: any) => { - - if (params) { - - const query = Object.entries(params).map(([k, v]) => v !== undefined && v !== '' && v !== null ? `${k}=${encodeURIComponent(String(v))}` : '').filter(Boolean).join('&'); - - return get(`/admin/storage/quota/users${query ? '?' + query : ''}`); - - } - - return get('/admin/storage/quota/users'); - - }, - - getStorageQuotaUser: (userId: string) => get(`/admin/storage/quota/users/${userId}`), - - adjustStorageQuota: (userId: string, data: any) => put(`/admin/storage/quota/users/${userId}`, data), - - recalcStorageQuota: (userId: string) => post(`/admin/storage/quota/recalculate/${userId}`, {}), - - initAllStorageQuotas: () => post('/admin/storage/quota/init-all', {}), - - - - // 自动化任务调度 (Phase 3.2) - - getAdminAutomationStats: () => get('/admin/automation/stats/overview'), - - getAdminAutomationTasks: (params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/automation/${query}`); - - }, - - getAdminAutomationTask: (id: string) => get(`/admin/automation/${id}`), - - createAdminAutomationTask: (data: any) => post('/admin/automation/', data), - - updateAdminAutomationTask: (id: string, data: any) => put(`/admin/automation/${id}`, data), - - deleteAdminAutomationTask: (id: string) => del(`/admin/automation/${id}`), - - toggleAdminAutomationTask: (id: string) => put(`/admin/automation/${id}/toggle`), - - executeAdminAutomationTask: (id: string) => post(`/admin/automation/${id}/execute`), - - getAdminAutomationTaskHistory: (id: string, params?: any) => { - - const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; - - return get(`/admin/automation/${id}/history${query}`); - - }, - - // MCP Server Management - - getAdminMcpStats: () => get('/admin/mcp/stats/overview'), - - getAdminMcpServers: (params?: any) => { - - const query = new URLSearchParams(params).toString(); - - return get(`/admin/mcp/${query ? '?' + query : ''}`); - - }, - - getAdminMcpServer: (id: string) => get(`/admin/mcp/${id}`), - - createAdminMcpServer: (data: any) => post('/admin/mcp/', data), - - updateAdminMcpServer: (id: string, data: any) => put(`/admin/mcp/${id}`, data), - - deleteAdminMcpServer: (id: string) => del(`/admin/mcp/${id}`), - - toggleAdminMcpServer: (id: string) => put(`/admin/mcp/${id}/toggle`), - - testAdminMcpServer: (id: string, data?: any) => post(`/admin/mcp/${id}/test`, data || {}), - - getAdminMcpServerTools: (id: string) => get(`/admin/mcp/${id}/tools`), - - getAdminMcpServerResources: (id: string) => get(`/admin/mcp/${id}/resources`), - - // Developer API Management - - getAdminDeveloperStats: () => get('/admin/developer/stats/overview'), - - getAdminApiKeys: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/developer/api-keys${sp.toString() ? '?' + sp.toString() : ''}`); - - }, - - createAdminApiKey: (data: any) => post('/admin/developer/api-keys/', data), - - updateAdminApiKey: (id: string, data: any) => put(`/admin/developer/api-keys/${id}`, data), - - revokeAdminApiKey: (id: string) => post(`/admin/developer/api-keys/${id}/revoke`), - - deleteAdminApiKey: (id: string) => del(`/admin/developer/api-keys/${id}`), - - getAdminOpenApiClients: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/developer/openapi-clients${sp.toString() ? '?' + sp.toString() : ''}`); - - }, - - createAdminOpenApiClient: (data: any) => post('/admin/developer/openapi-clients/', data), - - updateAdminOpenApiClient: (id: string, data: any) => put(`/admin/developer/openapi-clients/${id}`, data), - - resetAdminOpenApiClientSecret: (id: string) => post(`/admin/developer/openapi-clients/${id}/reset-secret`), - - deleteAdminOpenApiClient: (id: string) => del(`/admin/developer/openapi-clients/${id}`), - - getAdminCallStats: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/developer/call-stats${sp.toString() ? '?' + sp.toString() : ''}`); - - }, - - getAdminCallStatsOverview: () => get('/admin/developer/call-stats/overview'), - - getAdminSlowLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/developer/slow-logs${sp.toString() ? '?' + sp.toString() : ''}`); - - }, - - clearAdminSlowLogs: () => del('/admin/developer/slow-logs'), - - getAdminSlowLogsStats: () => get('/admin/developer/slow-logs/stats'), - - // AI App Management - - getAIAppStats: () => get('/admin/apps/stats/overview'), - - getAIApps: (params?: string) => get('/admin/apps/apps' + (params ? '?' + params : '')), - - getAIApp: (id: string) => get('/admin/apps/apps/' + id), - - createAIApp: (data: any) => post('/admin/apps/apps', data), - - updateAIApp: (id: string, data: any) => put('/admin/apps/apps/' + id, data), - - deleteAIApp: (id: string) => del('/admin/apps/apps/' + id), - - aiAppLifecycle: (id: string, action: string) => post('/admin/apps/apps/' + id + '/' + action, {}), - - getAIAppVersions: (id: string) => get('/admin/apps/apps/' + id + '/versions'), - - getAIAppAnalytics: (id: string) => get('/admin/apps/apps/' + id + '/analytics'), - - getAIAppTemplates: () => get('/admin/apps/templates'), - - createAIAppTemplate: (data: any) => post('/admin/apps/templates', data), - - createAIAppFromTemplate: (templateId: string, data: any) => post('/admin/apps/apps/from-template/' + templateId, data), - - getAIAppCategories: () => get('/admin/apps/categories'), - - // Logo 上传 - - uploadLogo: (formData: FormData) => { - - const token = localStorage.getItem('access_token'); - - return fetch(`/app/api/admin/settings/logo/upload`, { - - method: 'POST', - - headers: { 'Authorization': `Bearer ${token}` }, - - body: formData, - - }).then(res => res.json()); - - }, - - - - // MCP Market (Phase G1) - - getMcpMarketStats: () => - - get('/admin/mcp-market/stats'), - - getMcpTools: (params?: Record) => - - get('/admin/mcp-market/?' + new URLSearchParams(params || {}).toString()), - - getMcpToolDetail: (id: string) => - - get('/admin/mcp-market/' + id), - - createMcpTool: (data: any) => - - post('/admin/mcp-market/', data), - - updateMcpTool: (id: string, data: any) => - - put('/admin/mcp-market/' + id, data), - - deleteMcpTool: (id: string) => - - del('/admin/mcp-market/' + id), - - scanMcpTool: (id: string) => - - post('/admin/mcp-market/' + id + '/security-scan'), - - updateMcpToolSecurity: (id: string, data: any) => - - put('/admin/mcp-market/' + id + '/security-status', data), - - regenMcpDeepLink: (id: string) => - - post('/admin/mcp-market/' + id + '/deep-link'), - - getMcpManagedServers: () => - - get('/admin/mcp-market/managed/servers'), - - seedMcpTools: () => - - post('/admin/mcp-market/seed'), - - - - // Code Execution Sandbox (Phase G2) - - getCodeExecStats: () => get('/admin/code-exec/stats'), - - getCodeExecRuntimes: () => get('/admin/code-exec/runtimes'), - - executeCode: (data: { language: string; code: string; stdin?: string; timeout?: number }) => - - post('/admin/code-exec/execute', data), - - getCodeExecTasks: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/code-exec/tasks${sp.toString() ? '?' + sp.toString() : ''}`); - - }, - - getCodeExecTask: (id: string) => get(`/admin/code-exec/tasks/${id}`), - - deleteCodeExecTask: (id: string) => del(`/admin/code-exec/tasks/${id}`), - - - - - - // Distribution Builder (Phase G4) - - getDistributionStats: () => get('/admin/distributions/stats'), - - getDistributions: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]: [string, any]) => { if (v) sp.set(k, String(v)); }); - - return get('/admin/distributions' + (sp.toString() ? '?' + sp.toString() : '')); - - }, - - getDistributionTemplates: () => get('/admin/distributions/templates'), - - createDistribution: (data: any) => post('/admin/distributions', data), - - getDistribution: (id: string) => get('/admin/distributions/' + id), - - updateDistribution: (id: string, data: any) => put('/admin/distributions/' + id, data), - - deleteDistribution: (id: string) => del('/admin/distributions/' + id), - - getDistributionLicenses: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]: [string, any]) => { if (v) sp.set(k, String(v)); }); - - return get('/admin/distributions/licenses' + (sp.toString() ? '?' + sp.toString() : '')); - - }, - - createDistributionLicense: (distroId: string, data: any) => post('/admin/distributions/' + distroId + '/licenses', data), - - getDistributionLicense: (id: string) => get('/admin/distributions/licenses/' + id), - - revokeDistributionLicense: (id: string) => post('/admin/distributions/licenses/' + id + '/revoke'), - - // ===== Ads Management ===== - - getAds: () => get('/admin/ads/'), - - addAd: (data: any) => post('/admin/ads/', data), - - updateAd: (id: string, data: any) => put(`/admin/ads/${id}`, data), - - deleteAd: (id: string) => del(`/admin/ads/${id}`), - - - - // ===== Market Admin (extended) ===== - - getMarketOverview: () => get('/admin/market/overview'), - - getTopApps: (metric?: string, limit?: number) => get(`/admin/market/top-apps${metric ? '?metric=' + metric : ''}${limit ? (metric ? '&' : '?') + 'limit=' + limit : ''}`), - - getAdminMarketApps: (params?: string) => get(`/admin/market/apps${params ? '?' + params : ''}`), - - getPendingApps: () => get('/admin/market/pending'), - - getCoupons: () => get('/admin/market/coupons'), - - getRecentReviews: () => get('/admin/market/reviews'), - - approveApp: (id: string) => post(`/admin/market/apps/${id}/approve`), - - rejectApp: (id: string) => post(`/admin/market/apps/${id}/reject`), - - setFeaturedApp: (id: string, featured: boolean, sortOrder?: number) => put(`/admin/market/apps/${id}/featured`, { featured, sort_order: sortOrder }), - - deleteMarketApp: (id: string) => del(`/admin/market/apps/${id}`), - - deleteCoupon: (id: string) => del(`/admin/market/coupons/${id}`), - - updateMarketApp: (id: string, data: any) => put(`/admin/market/apps/${id}`, data), - - updateCoupon: (id: string, data: any) => put(`/admin/market/coupons/${id}`, data), - - addCoupon: (data: any) => post('/admin/market/coupons', data), - - - - // ===== Membership (extended) ===== - - getMembershipStats: () => get('/admin/membership/stats'), - - revokeMembership: (userId: string) => post(`/admin/membership/users/${userId}/revoke`), - - - - // ===== Logs (extended) ===== - - getOperationLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/operation-logs${sp.toString() ? '?' + sp.toString() : ''}`); - - }, - - getTokenLogs: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/logs/token-logs${sp.toString() ? '?' + sp.toString() : ''}`); - - }, - - - - // ===== Analytics ===== - - getAnalyticsOverview: (days?: number) => get(`/admin/analytics/overview${days ? '?days=' + days : ''}`), - - getAnalyticsApiTrends: (days?: number) => get(`/admin/analytics/trends/api-calls${days ? '?days=' + days : ''}`), - - getAnalyticsTokenTrends: (days?: number) => get(`/admin/analytics/trends/tokens${days ? '?days=' + days : ''}`), - - getAnalyticsUserTrends: (days?: number) => get(`/admin/analytics/trends/users${days ? '?days=' + days : ''}`), - - getAnalyticsRevenueTrends: (days?: number) => get(`/admin/analytics/trends/revenue${days ? '?days=' + days : ''}`), - - getAnalyticsModelUsage: (days?: number) => get(`/admin/analytics/models/usage${days ? '?days=' + days : ''}`), - - - - // ===== Billing ===== - - getBillingRules: () => get('/admin/billing/rules'), - - getBillingPackages: () => get('/admin/billing/packages'), - - getBillingTransactions: (page?: number) => get(`/admin/billing/transactions${page ? '?page=' + page : ''}`), - - getBillingStats: () => get('/admin/billing/stats/overview'), - - - - // ===== Tenant ===== - - getTenants: (page?: number) => get(`/admin/tenant/list${page ? '?page=' + page : ''}`), - - getTenantStats: () => get('/admin/tenant/stats/overview'), - - getTenantMembers: (tenantId: string) => get(`/admin/tenant/${tenantId}/members`), - - createTenant: (params: string) => post(`/admin/tenant/create${params}`), - - - -}; - -// ===== AI自进化API ===== - -export const evolutionAPI = { - - // 驾驶舱面板 - - getSkillTree: () => get('/evolution/dashboard/skill-tree'), - - getTimeline: (eventType?: string) => - - get('/evolution/dashboard/timeline' + (eventType ? `?event_type=${eventType}` : '')), - - getStatistics: () => get('/evolution/dashboard/statistics'), - - getProfile: () => get('/evolution/dashboard/profile'), - - - - // 技能管理 - - getSkills: (params?: { state?: string; keyword?: string; sort_by?: string; order?: string }) => { - - const query = new URLSearchParams(); - - if (params?.state) query.append('state', params.state); - - if (params?.keyword) query.append('keyword', params.keyword); - - if (params?.sort_by) query.append('sort_by', params.sort_by); - - if (params?.order) query.append('order', params.order); - - const qs = query.toString(); - - return get('/evolution/skills' + (qs ? `?${qs}` : '')); - - }, - - getSkillDetail: (name: string) => get(`/evolution/skills/${encodeURIComponent(name)}`), - - updateSkill: (name: string, data: { description?: string; tags?: string[]; category?: string }) => - - put(`/evolution/skills/${encodeURIComponent(name)}`, data), - - deleteSkill: (name: string) => del(`/evolution/skills/${encodeURIComponent(name)}`), - - freezeSkill: (name: string) => post(`/evolution/skills/${encodeURIComponent(name)}/freeze`), - - unfreezeSkill: (name: string) => post(`/evolution/skills/${encodeURIComponent(name)}/unfreeze`), - - getSkillHistory: (name: string) => get(`/evolution/skills/${encodeURIComponent(name)}/history`), - - getSkillTrajectories: (name: string) => get(`/evolution/skills/${encodeURIComponent(name)}/trajectories`), - - - - // 事件 - - getEvents: (params?: { event_type?: string; skill_name?: string; limit?: number }) => { - - const query = new URLSearchParams(); - - if (params?.event_type) query.append('event_type', params.event_type); - - if (params?.skill_name) query.append('skill_name', params.skill_name); - - if (params?.limit) query.append('limit', String(params.limit)); - - const qs = query.toString(); - - return get('/evolution/events' + (qs ? `?${qs}` : '')); - - }, - - - - // 轨迹提交 - - submitTrajectory: (data: { conversation_id: string; messages: any[] }) => - - post('/evolution/trajectory/submit', data), - - - - // v2: 技能回滚 - - rollbackSkill: (skillName: string, version: number) => - - post(`/evolution/skills/${encodeURIComponent(skillName)}/rollback/${version}`), - - - - // v2: 发布到市场 - - publishSkill: (skillName: string, data: { price: number; market_description: string }) => - - post(`/evolution/skills/${encodeURIComponent(skillName)}/publish`, data), - - - - // v2: 团队共享 - - shareToTeam: (skillName: string, teamId: string) => - - post(`/evolution/skills/${encodeURIComponent(skillName)}/share-to-team`, { team_id: teamId }), - - - - // v2: 流水线状态 - - getPipelineStatus: () => get('/evolution/pipeline/status'), - - - - // v2: 手动触发流水线 - - triggerPipeline: () => post('/evolution/pipeline/trigger'), - - - - // v2: Watcher状态 - - getWatcherStatus: () => get('/evolution/watcher/status'), - - - - // v2: 进化报告 - - getReports: (reportType?: string) => { - - const qs = reportType ? `?report_type=${reportType}` : ''; - - return get(`/evolution/reports${qs}`); - - }, - - getReportDetail: (reportId: string) => get(`/evolution/reports/${reportId}`), - - - - - - // Workflow (Phase 3.6) - - getWorkflows: (params?: Record) => - - get('/admin/workflows/?' + new URLSearchParams(params || {}).toString()), - - getWorkflow: (id: string) => - - get(`/admin/workflows/${id}`), - - getWorkflowStats: () => - - get('/admin/workflows/stats'), - - getWorkflowTemplates: () => - - get('/admin/workflows/templates'), - - createWorkflow: (data: any) => - - post('/admin/workflows/', data), - - updateWorkflow: (id: string, data: any) => - - put(`/admin/workflows/${id}`, data), - - deleteWorkflow: (id: string) => - - del(`/admin/workflows/${id}`), - - toggleWorkflow: (id: string, isActive: boolean) => - - post(`/admin/workflows/${id}/toggle?is_active=${isActive}`), - - executeWorkflow: (id: string, inputText: string) => - - post(`/admin/workflows/${id}/execute`, { input_text: inputText }), - - - - - - // Privacy & Local Agent (Phase G5) - - getPrivacyStats: () => get('/admin/privacy/stats'), - - getPrivacyConfig: () => get('/admin/privacy/config'), - - updatePrivacyConfig: (params: any) => { - - const sp = new URLSearchParams(); - - Object.entries(params).forEach(([k, v]) => { if (v != null) sp.append(k, String(v)); }); - - return put('/admin/privacy/config?' + sp.toString()); - - }, - - switchPrivacyMode: (mode: string) => post('/admin/privacy/switch-mode?mode=' + encodeURIComponent(mode)), - - getPrivacyLicenses: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params?.status) sp.append('status', params.status); - - return get('/admin/privacy/licenses' + (sp.toString() ? '?' + sp.toString() : '')); - - }, - - generateOfflineLicense: (data: any) => { - - const sp = new URLSearchParams(); - - if (data.agent_id) sp.append('agent_id', data.agent_id); - - sp.append('max_offline_days', String(data.max_offline_days || 7)); - - return post('/admin/privacy/licenses/generate?' + sp.toString()); - - }, - - revokePrivacyLicense: (id: number) => post('/admin/privacy/licenses/' + id + '/revoke'), - - validatePrivacyLicense: (licenseKey: string) => { - - const sp = new URLSearchParams(); - - sp.append('license_key', licenseKey); - - return post('/admin/privacy/licenses/validate?' + sp.toString()); - - }, - - getKbSyncStates: () => get('/admin/privacy/kb-sync'), - - triggerKbSync: (kbId: number, direction: string) => { - - const sp = new URLSearchParams(); - - sp.append('knowledge_base_id', String(kbId)); - - sp.append('sync_direction', direction); - - return post('/admin/privacy/kb-sync/trigger?' + sp.toString()); - - }, - - getLocalAgents: () => get('/admin/privacy/local-agents'), - - getPrivacyUsageStats: () => get('/admin/privacy/usage/stats'), - - getBridgeConfig: () => get('/admin/privacy/bridge-config'), - - - - // IM Bot Management (Phase 4) - - getImBotStats: () => get('/admin/im-bot/stats'), - - getImBotDashboard: () => get('/admin/im-bot/dashboard'), - - getImBotChannels: (params?: string) => get(`/admin/im-bot/channels${params || ''}`), - - getImBotChannelDetail: (id: string) => get(`/admin/im-bot/channels/${id}`), - - testImBotChannel: (id: string) => post(`/admin/im-bot/channels/${id}/test`), - - activateImBotChannel: (id: string) => post(`/admin/im-bot/channels/${id}/activate`), - - deactivateImBotChannel: (id: string) => post(`/admin/im-bot/channels/${id}/deactivate`), - - getImBotRoutes: (params?: string) => get(`/admin/im-bot/routes${params || ''}`), - - createImBotRoute: (params: string) => post(`/admin/im-bot/routes${params}`), - - deleteImBotRoute: (id: string) => del(`/admin/im-bot/routes/${id}`), - - // 个人微信(iLink 协议)原生适配器 - - getWechatConfig: () => get('/admin/im-bot/wechat'), - - wechatQrcode: () => post('/admin/im-bot/wechat/qrcode'), - - wechatQrcodeStatus: (params: string) => post(`/admin/im-bot/wechat/qrcode/status${params}`), - - getWechatStatus: () => get('/admin/im-bot/wechat/status'), - - disconnectWechat: () => post('/admin/im-bot/wechat/disconnect'), - - getImBotPlatforms: () => get('/admin/im-bot/platforms'), - - getImBotWebhookUrl: (platform: string) => get(`/admin/im-bot/webhook-url/${platform}`), - - getImBotMessages: (params?: string) => get(`/admin/im-bot/messages${params || ''}`), - - imBotQuickSetup: (params: string) => post(`/admin/im-bot/quick-setup${params}`), - - - - // 推广系统 - - getPromotionStats: () => get('/admin/promotion/stats'), - - getPromotions: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); - - return get(`/admin/promotion/promotions${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - createPromotion: (params: any) => { - - const sp = new URLSearchParams(); - - Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); - - return post(`/admin/promotion/promotions${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - updatePromotion: (id: string, params: any) => { - - const sp = new URLSearchParams(); - - Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); - - return put(`/admin/promotion/promotions/${id}${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - deletePromotion: (id: string) => del(`/admin/promotion/promotions/${id}`), - - togglePromotion: (id: string) => post(`/admin/promotion/promotions/${id}/toggle`), - - getReferralLinks: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/promotion/referral-links${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - generateReferralLink: (params: any) => { - - const sp = new URLSearchParams(); - - Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return post(`/admin/promotion/referral-links/generate${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - getReferralRewards: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/promotion/rewards${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - approveReward: (id: string) => post(`/admin/promotion/rewards/${id}/approve`), - - markRewardPaid: (id: string, params: any) => { - - const sp = new URLSearchParams(); - - Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return post(`/admin/promotion/rewards/${id}/pay${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - getPromotionLeaderboard: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/promotion/leaderboard${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - regenerateReferralCode: (id: string) => post(`/admin/promotion/referral-links/${id}/regenerate`), - - getMyReferrals: () => get('/admin/promotion/my-referrals'), - - getMyRewards: () => get('/admin/promotion/my-rewards'), - - - - // 成就系统 - - getAchievements: (params?: string) => get(`/admin/achievement/achievements${params || ''}`), - - getAchievementDetail: (id: string) => get(`/admin/achievement/achievements/${id}`), - - createAchievement: (params: string) => post(`/admin/achievement/achievements${params}`), - - updateAchievement: (id: string, params: string) => put(`/admin/achievement/achievements/${id}${params}`), - - deleteAchievement: (id: string) => del(`/admin/achievement/achievements/${id}`), - - getUserAchievements: () => get('/admin/achievement/user/achievements'), - - claimAchievementReward: (id: string) => post(`/admin/achievement/user/achievements/${id}/claim`), - - checkAchievementProgress: () => post('/admin/achievement/check-progress'), - - getAchievementLeaderboard: (params?: string) => get(`/admin/achievement/leaderboard${params || ''}`), - - - - // 多租户管理 - - getTenants: (page?: number, keyword?: string, status?: string) => { - - const sp = new URLSearchParams(); - - if (page) sp.set('page', String(page)); - - if (keyword) sp.set('keyword', keyword); - - if (status) sp.set('status', status); - - sp.set('page_size', '20'); - - return get(`/admin/tenant/list${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - createTenant: (params: string) => post(`/admin/tenant/create${params}`), - - getTenantDetail: (id: string) => get(`/admin/tenant/${id}`), - - updateTenant: (id: string, params: string) => put(`/admin/tenant/${id}${params}`), - - deleteTenant: (id: string) => del(`/admin/tenant/${id}`), - - getTenantMembers: (tenantId: string, page?: number) => { - - const sp = new URLSearchParams(); - - sp.set('page', String(page || 1)); - - sp.set('page_size', '100'); - - return get(`/admin/tenant/${tenantId}/members?${sp.toString()}`); - - }, - - addTenantMember: (tenantId: string, params: string) => post(`/admin/tenant/${tenantId}/members/add${params}`), - - updateTenantMember: (tenantId: string, memberId: string, params: string) => put(`/admin/tenant/${tenantId}/members/${memberId}${params}`), - - removeTenantMember: (tenantId: string, memberId: string) => del(`/admin/tenant/${tenantId}/members/${memberId}`), - - getTenantQuotas: (tenantId: string) => get(`/admin/tenant/${tenantId}/quotas`), - - updateTenantQuota: (tenantId: string, params: string) => put(`/admin/tenant/${tenantId}/quotas${params}`), - - getTenantInvitations: (tenantId: string) => get(`/admin/tenant/${tenantId}/invitations`), - - createTenantInvitation: (tenantId: string, params: string) => post(`/admin/tenant/${tenantId}/invitations${params}`), - - getTenantStats: () => get('/admin/tenant/stats/overview'), - - - - // 计费与支付 - - getBillingRules: (page?: number) => get(`/admin/billing/rules?page=${page || 1}&page_size=100`), - - createBillingRule: (params: string) => post(`/admin/billing/rules/create${params}`), - - updateBillingRule: (id: string, params: string) => put(`/admin/billing/rules/${id}${params}`), - - deleteBillingRule: (id: string) => del(`/admin/billing/rules/${id}`), - - getBillingPackages: () => get('/admin/billing/packages'), - - createBillingPackage: (params: string) => post(`/admin/billing/packages/create${params}`), - - updateBillingPackage: (id: string, params: string) => put(`/admin/billing/packages/${id}${params}`), - - deleteBillingPackage: (id: string) => del(`/admin/billing/packages/${id}`), - - getBillingTransactions: (page?: number) => get(`/admin/billing/transactions?page=${page || 1}&page_size=20`), - - getInvoices: (page?: number) => get(`/admin/billing/invoices?page=${page || 1}&page_size=20`), - - generateInvoice: (params: string) => post(`/admin/billing/invoices/generate${params}`), - - getPaymentConfigs: () => get('/admin/billing/payment-configs'), - - updatePaymentConfig: (id: string, params: string) => put(`/admin/billing/payment-configs/${id}${params}`), - - getBillingStats: () => get('/admin/billing/stats/overview'), - - - - // 数据分析 - - getAnalyticsOverview: (days?: number) => get(`/admin/analytics/overview?days=${days || 30}`), - - getAnalyticsApiTrends: (days?: number) => get(`/admin/analytics/trends/api-calls?days=${days || 7}`), - - getAnalyticsTokenTrends: (days?: number) => get(`/admin/analytics/trends/tokens?days=${days || 7}`), - - getAnalyticsUserTrends: (days?: number) => get(`/admin/analytics/trends/users?days=${days || 30}`), - - getAnalyticsRevenueTrends: (days?: number) => get(`/admin/analytics/trends/revenue?days=${days || 30}`), - - getAnalyticsModelUsage: (days?: number) => get(`/admin/analytics/models/usage?days=${days || 30}`), - - getAnalyticsDashboards: () => get('/admin/analytics/dashboards'), - - createAnalyticsDashboard: (params: string) => post(`/admin/analytics/dashboards/create${params}`), - - deleteAnalyticsDashboard: (id: string) => del(`/admin/analytics/dashboards/${id}`), - - getAnalyticsEvents: (params?: any) => { - - const sp = new URLSearchParams(); - - if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); - - return get(`/admin/analytics/events${sp.toString() ? `?${sp.toString()}` : ''}`); - - }, - - - -}; - - - - - -// ===== 前台用户个人微信 (iLink 协议 / 微信 ClawBot 插件) ===== - -export const imUserAPI = { - - getStatus: () => get('/im/wechat/status'), - - qrcode: () => post('/im/wechat/qrcode'), - - qrcodeStatus: (qrcode: string) => post(`/im/wechat/qrcode/status?qrcode=${encodeURIComponent(qrcode)}`), - - disconnect: () => post('/im/wechat/disconnect'), - - history: () => get('/im/wechat/history'), - -}; - +/** + + * AIFlow API 客户端 + + * 统一管理所有前后端API请求 + + */ + +const API_BASE = '/app/api'; + + + +// 从localStorage获取token + +function getToken(): string | null { + + return localStorage.getItem('aiflow_token'); + +} + + + +// 通用请求方法 + +export async function request( + + endpoint: string, + + options: RequestInit = {} + +): Promise<{ code: number; data: T; message: string }> { + + const token = getToken(); + + const headers: Record = { + + 'Content-Type': 'application/json', + + ...(options.headers as Record || {}), + + }; + + if (token) { + + headers['Authorization'] = `Bearer ${token}`; + + } + + const res = await fetch(`${API_BASE}${endpoint}`, { + + ...options, + + headers, + + }); + + if (res.status === 401) { + + // 登录接口的401需要精确提示(用户名不存在 / 密码错误) + + if (endpoint === '/auth/login') { + + const err = await res.json().catch(() => ({ detail: '登录失败' })); + + const error: any = new Error(err.detail || '登录失败'); + + error.status = 401; + + error.detail = err.detail; + + throw error; + + } + + // 其他接口的401 = token过期/未登录,清除状态并跳转 + + localStorage.removeItem('aiflow_token'); + + localStorage.removeItem('aiflow_user'); + + window.location.href = '/login'; + + throw new Error('未登录'); + + } + + if (res.status === 403) { + const err = await res.json().catch(() => ({ detail: 'Forbidden' })); + if (err.detail === 'Not authenticated' || err.detail === '需要管理员权限') { + localStorage.removeItem('aiflow_token'); + localStorage.removeItem('aiflow_user'); + window.location.href = '/login'; + throw new Error('未登录或权限不足'); + } + } + + if (!res.ok) { + + const err = await res.json().catch(() => ({ detail: '请求失败' })); + + const error: any = new Error(err.detail || err.message || '请求失败'); + + error.status = res.status; + + error.response = { status: res.status, data: err }; + + error.detail = err.detail; + + throw error; + + } + + return res.json(); + +} + + + +// GET请求 + +function get(endpoint: string): Promise<{ code: number; data: T; message: string }> { + + return request(endpoint); + +} + + + +// POST请求 + +function post(endpoint: string, body?: any): Promise<{ code: number; data: T; message: string }> { + + return request(endpoint, { + + method: 'POST', + + body: body ? JSON.stringify(body) : undefined, + + }); + +} + + + +// PUT请求 + +function put(endpoint: string, body?: any): Promise<{ code: number; data: T; message: string }> { + + return request(endpoint, { + + method: 'PUT', + + body: body ? JSON.stringify(body) : undefined, + + }); + +} + + + +// DELETE请求 + +function del(endpoint: string): Promise<{ code: number; data: T; message: string }> { + + return request(endpoint, { method: 'DELETE' }); + +} + + + +// ============================================================ + +// 认证相关API + +// ============================================================ + +export const authAPI = { + + login: (username: string, password: string) => + + post('/auth/login', { username, password }), + + register: (data: { username: string; password: string; email?: string; phone?: string }) => + + post('/auth/register', data), + + getMe: () => get('/auth/me'), + + logout: () => post('/auth/logout'), + + getSettings: () => get('/auth/settings'), + + sendCode: (phone: string, purpose: string) => + post('/auth/send-code', { phone, purpose }), + + resetPassword: (data: { phone: string; code: string; new_password: string }) => + post('/auth/reset-password', data), + +}; + + + +// ============================================================ + +// 聊天相关API + +// ============================================================ + +export const chatAPI = { + + getConversations: (status?: string) => + + get(`/chat/conversations${status ? `?status=${status}` : ''}`), + + createConversation: (data: { title: string; model_id?: string }) => + + post('/chat/conversations', data), + + updateConversation: (id: string, data: any) => + + put(`/chat/conversations/${id}`, data), + + deleteConversation: (id: string) => + + del(`/chat/conversations/${id}`), + + getMessages: (convId: string) => + + get(`/chat/conversations/${convId}/messages`), + + sendMessage: (convId: string, data: { content: string; model_id?: string; agent_ids?: string[]; skill_ids?: string[]; knowledge_base_ids?: string[]; use_tools?: boolean }) => + + post(`/chat/conversations/${convId}/messages`, data), + + clearContext: (convId: string) => + + post(`/chat/conversations/${convId}/clear`), + + convertToKnowledgeGraph: (convId: string) => + + post(`/chat/conversations/${convId}/to-knowledge-graph/`), + + getModels: () => get('/chat/models'), + +}; + + + + + + +// ============================================================ + +// 知识库相关API + +// ============================================================ + +export const knowledgeAPI = { + + getBases: () => get('/knowledge/bases'), + + createBase: (data: any) => post('/knowledge/bases', data), + + getBase: (id: string) => get(`/knowledge/bases/${id}`), + + updateBase: (id: string, data: any) => put(`/knowledge/bases/${id}`, data), + + deleteBase: (id: string) => del(`/knowledge/bases/${id}`), + + getDocuments: (baseId: string) => get(`/knowledge/bases/${baseId}/documents`), + + getDocument: (baseId: string, docId: string) => get(`/knowledge/bases/${baseId}/documents/${docId}`), + + uploadDocument: (baseId: string, data: any) => post(`/knowledge/bases/${baseId}/documents`, data), + + deleteDocument: (baseId: string, docId: string) => del(`/knowledge/bases/${baseId}/documents/${docId}`), + + getDirectories: () => get('/knowledge/directories'), + + createDirectory: (data: any) => post('/knowledge/directories', data), + + buildGraph: (baseId: string) => post(`/knowledge/bases/${baseId}/build-graph`), + + getGraph: (baseId: string) => get(`/knowledge/bases/${baseId}/graph`), + + searchQA: (baseId: string, question: string) => post('/knowledge/qa', { base_id: baseId, question }), + + searchDocuments: (baseId: string, keyword: string) => get(`/knowledge/bases/${baseId}/search?keyword=${encodeURIComponent(keyword)}`), + +}; + + + +// ============================================================ + +// 应用市场相关API + +// ============================================================ + +export const marketAPI = { + + getApps: (params?: { type?: string; source?: string; capability?: string; search?: string; page?: number; page_size?: number }) => { + + const searchParams = new URLSearchParams(); + + if (params?.type) searchParams.set('type', params.type); + + if (params?.source) searchParams.set('source', params.source); + + if (params?.capability) searchParams.set('capability', params.capability); + + if (params?.search) searchParams.set('search', params.search); + + if (params?.page) searchParams.set('page', String(params.page)); + + if (params?.page_size) searchParams.set('page_size', String(params.page_size)); + + const qs = searchParams.toString(); + + return get(`/market/apps${qs ? `?${qs}` : ''}`); + + }, + + getApp: (id: string) => get(`/market/apps/${id}`), + + createApp: (data: any) => post('/market/apps', data), + + verifyAgent: (data: any) => post('/market/apps/verify-agent', data), + + detectCapability: (data: any) => post('/market/apps/detect-capability', data), + + purchaseApp: (id: string) => post(`/market/apps/${id}/purchase`), + + installApp: (id: string) => post(`/market/apps/${id}/install`), + + uninstallApp: (id: string) => del(`/market/apps/${id}/uninstall`), + + reviewApp: (id: string, data: { rating: number; content?: string }) => + + post(`/market/apps/${id}/review`, data), + + getMyApps: () => get('/market/my-apps'), + + getReviews: (appId: string) => get(`/market/reviews/${appId}`), + +}; + + + +// ============================================================ + +// Token相关API + +// ============================================================ + +export const tokenAPI = { + + getOverview: () => get('/token/overview'), + + getRankings: () => get('/token/rankings'), + + getLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + const qs = sp.toString(); + + return get(`/token/logs${qs ? `?${qs}` : ''}`); + + }, + + getBalance: () => get('/token/balance'), + + recharge: (amount: number) => post('/token/recharge', { amount }), + +}; + + + +// ============================================================ + +// 付费相关API + +// ============================================================ + +export const paymentAPI = { + + getPlans: () => get('/payment/plans'), + + createOrder: (data: any) => post('/payment/orders', data), + + getOrders: (status?: string) => get(`/payment/orders${status ? `?status=${status}` : ''}`), + + getOrder: (id: string) => get(`/payment/orders/${id}`), + + payOrder: (id: string, method?: string) => post(`/payment/orders/${id}/pay`, { payment_method: method }), + + getCoupons: () => get('/payment/coupons'), + + useCoupon: (code: string) => post('/payment/coupons/use', { code }), + + getServices: () => get('/payment/services'), + + createCustomOrder: (data: any) => post('/payment/custom-services', data), + + createServiceRequest: (data: any) => post('/payment/service-requests', data), + +}; + + + +// ============================================================ + +// 个人中心相关API + +// ============================================================ + +export const profileAPI = { + + getProfile: () => get('/profile/'), + + updateProfile: (data: any) => put('/profile/', data), + + updatePassword: (data: { old_password: string; new_password: string }) => + + put('/profile/password', data), + + getLoginLogs: () => get('/profile/login-logs'), + + getApiKeys: () => get('/profile/api-keys'), + + createApiKey: (name?: string) => post('/profile/api-keys', { name }), + + updateApiKey: (id: string, status: string) => put(`/profile/api-keys/${id}`, { status }), + + getMembership: () => get('/profile/membership'), + + getPurchasedApps: () => get('/profile/purchased-apps'), + +}; + + + +// ============================================================ + +// 通知相关API + +// ============================================================ + +export const notificationAPI = { + + getNotifications: (params?: { type?: string; page?: number }) => { + + const sp = new URLSearchParams(); + + if (params?.type) sp.set('type', params.type); + + if (params?.page) sp.set('page', String(params.page)); + + const qs = sp.toString(); + + return get(`/notifications/${qs ? `?${qs}` : ''}`); + + }, + + markRead: (id: string) => put(`/notifications/${id}/read`), + + markAllRead: () => put('/notifications/read-all'), + + getUnreadCount: () => get('/notifications/unread-count'), + +}; + + + +// ============================================================ + +// 资产管理相关API + +// ============================================================ + +export const assetsAPI = { + + getAssets: (params?: { category?: string; page?: number; page_size?: number }) => { + const qs: string[] = []; + if (params?.category) qs.push(`category=${encodeURIComponent(params.category)}`); + if (params?.page) qs.push(`page=${params.page}`); + if (params?.page_size) qs.push(`page_size=${params.page_size}`); + const q = qs.length ? `?${qs.join('&')}` : ''; + return get(`/assets/${q}`); + }, + + createAsset: (data: any) => post('/assets/', data), + + getAsset: (id: string) => get(`/assets/${id}`), + + updateAsset: (id: string, data: any) => put(`/assets/${id}`, data), + + deleteAsset: (id: string) => del(`/assets/${id}`), + + getCategories: () => get('/assets/categories'), + + getNovelChapters: (novelId: string) => get(`/assets/novels/${novelId}/chapters`), + + addChapter: (novelId: string, data: any) => post(`/assets/novels/${novelId}/chapters`, data), + +}; + + + +// ============================================================ + +// 后台管理API + +// ============================================================ + +export const adminAPI = { + + // 仪表盘 + + getStats: () => get('/admin/dashboard/stats'), + + getRecentLogs: () => get('/admin/dashboard/recent-logs'), + + getRecentAudit: () => get('/admin/dashboard/recent-audit'), + + getDailyStats: (days?: number) => get<{ days: number; data: Array<{ date: string; tokens: number }> }>(`/admin/dashboard/daily-stats${days ? `?days=${days}` : ''}`), + getObservabilitySummary: (days?: number) => get(`/admin/observability/summary${days ? `?days=${days}` : ''}`), + + // 用户管理 + + getUsers: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/users/${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + getUser: (id: string) => get(`/admin/users/${id}`), + + banUser: (id: string) => put(`/admin/users/${id}/ban`), + + unbanUser: (id: string) => put(`/admin/users/${id}/unban`), + + getRegisterSettings: () => get('/admin/users/register-settings'), + + updateRegisterSettings: (data: any) => put('/admin/users/register-settings', data), + + // 模型管理 + + getProviders: () => get('/admin/models/providers'), + + addProvider: (data: any) => post('/admin/models/providers', data), + + updateProvider: (id: string, data: any) => put(`/admin/models/providers/${id}`, data), + + deleteProvider: (id: string) => del(`/admin/models/providers/${id}`), + + fetchModels: (id: string) => post(`/admin/models/providers/${id}/fetch-models`), + + addModels: (id: string, modelIds: string[]) => post(`/admin/models/providers/${id}/add-models`, { model_ids: modelIds }), + + getProtocols: () => get('/admin/models/protocols'), + + getAdminModels: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/models/${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + addModel: (data: any) => post('/admin/models/', data), + + updateModel: (id: string, data: any) => put(`/admin/models/${id}`, data), + + deleteModel: (id: string) => del(`/admin/models/${id}`), + + toggleModel: (id: string, isEnabled: boolean) => put(`/admin/models/${id}/toggle?is_enabled=${isEnabled}`), + + setDefaultModel: (id: string) => put(`/admin/models/${id}/set-default`), + + // 按能力默认模型(Auto 自动模式使用) + getCapabilities: () => get('/admin/models/capabilities'), + getCapabilityDefaults: () => get('/admin/models/capability-defaults'), + setCapabilityDefault: (capability: string, modelId: string | null) => + put('/admin/models/capability-defaults', { capability, model_id: modelId }), + + // Phase 2: Enhanced model management + + testModelConnection: (id: string) => post(`/admin/models/${id}/test-connection`), + + testAllModels: () => post('/admin/models/test-all'), + + fetchProviderModels: (id: string) => post(`/admin/models/providers/${id}/fetch-models`), + + createAdminModel: (data: any) => post('/admin/models/', data), + + updateAdminModel: (id: string, data: any) => put(`/admin/models/${id}`, data), + + deleteAdminModel: (id: string) => del(`/admin/models/${id}`), + + getModelStats: () => get('/admin/models/stats'), + + // 网站设置 + + getSiteSettings: () => get('/admin/settings/site'), + + updateSiteSettings: (data: any) => put('/admin/settings/site', data), + + getEmailConfig: () => get('/admin/settings/email'), + + updateEmailConfig: (data: any) => put('/admin/settings/email', data), + + testEmail: (params?: { test_to?: string; test_subject?: string; test_body?: string }) => { + + const qs = new URLSearchParams(); + + if (params?.test_to) qs.set('test_to', params.test_to); + + if (params?.test_subject) qs.set('test_subject', params.test_subject); + + if (params?.test_body) qs.set('test_body', params.test_body); + + const qstr = qs.toString(); + + return post(`/admin/settings/email/test${qstr ? `?${qstr}` : ''}`); + + }, + + generateEmailContent: (topic: string) => post(`/admin/settings/email/generate-content?topic=${encodeURIComponent(topic)}`), + + getSmsConfig: () => get('/admin/settings/sms'), + + updateSmsConfig: (data: any) => put('/admin/settings/sms', data), + + testSms: () => post('/admin/settings/sms/test'), + + getSmsBalance: () => post('/admin/settings/sms/balance'), + + // 支付配置 + + getPaymentConfigs: () => get('/admin/payment/configs'), + + updatePaymentConfig: (id: string, data: any) => put(`/admin/payment/configs/${id}`, data), + + getAdminOrders: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/payment/orders${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + refundOrder: (id: string) => put(`/admin/payment/orders/${id}/refund`), + + // 增值服务 + + getServices: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/services/${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + addService: (data: any) => post('/admin/services/', data), + + updateService: (id: string, data: any) => put(`/admin/services/${id}`, data), + + deleteService: (id: string) => del(`/admin/services/${id}`), + + getCustomOrders: (status?: string) => get(`/admin/services/custom-orders${status ? `?status=${status}` : ''}`), + + updateCustomOrder: (id: string, data: any) => put(`/admin/services/custom-orders/${id}`, data), + + getCustomRequests: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/services/custom-requests${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + updateCustomRequest: (id: string, data: any) => put(`/admin/services/custom-requests/${id}`, data), + + getAvailableAgents: () => get('/admin/services/agents'), + + assignAgentToService: (id: string, agentId: string) => put(`/admin/services/${id}/agent?agent_id=${agentId}`), + + // 会员管理(增强版) + + getPlans: () => get('/admin/membership/plans'), + + addPlan: (params: string) => post(`/admin/membership/plans${params}`), + + updatePlan: (id: string, params: string) => put(`/admin/membership/plans/${id}${params}`), + + deletePlan: (id: string) => del(`/admin/membership/plans/${id}`), + + getSubscriptions: (page?: number) => get(`/admin/membership/subscriptions${page ? `?page=${page}` : ''}`), + + getAutomationRules: () => get('/admin/membership/automation-rules'), + + getBenefitsComparison: () => get('/admin/membership/benefits-comparison'), + + getMembershipConfigs: () => get('/admin/membership/global-configs'), + + updateMembershipConfig: (key: string, params: string) => put(`/admin/membership/global-configs/${key}${params}`), + + getUserMemberships: (page?: number, planId?: string, status?: string) => { + + const p = new URLSearchParams(); + + if (page) p.append('page', String(page)); + + if (planId) p.append('plan_id', planId); + + if (status) p.append('status', status); + + const qs = p.toString(); + + return get(`/admin/membership/user-memberships${qs ? `?${qs}` : ''}`); + + }, + + assignPlan: (userId: string, planId: string, days: number) => put(`/admin/membership/users/${userId}/assign-plan?plan_id=${planId}&duration_days=${days}`), + + getPaymentLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/payment-logs${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + getAuditLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/audit-logs${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + // 新增:日志统计 + + getLogStats: () => get('/admin/logs/stats'), + + // 新增:API调用统计 + + getApiStats: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/api-stats${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + // 新增:慢请求记录 + + getSlowRequests: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/slow-requests${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + // 新增:操作类型枚举 + + getActionTypes: () => get('/admin/logs/action-types'), + + // 新增:审计日志导出 + + exportAuditLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/export/audit-logs${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + exportOperationLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/export/operations${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + exportTokenLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/export/token-logs${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + getRiskRules: () => get('/admin/logs/risk-rules'), + + addRiskRule: (data: any) => post('/admin/logs/risk-rules', data), + + updateRiskRule: (id: string, data: any) => put(`/admin/logs/risk-rules/${id}`, data), + + deleteRiskRule: (id: string) => del(`/admin/logs/risk-rules/${id}`), + + // 技能管理 (Phase 2.2) + + getAdminSkills: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); + + return get(`/admin/skills/${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + getAdminSkill: (id: string) => get(`/admin/skills/${id}`), + + createAdminSkill: (data: any) => post('/admin/skills/', data), + + updateAdminSkill: (id: string, data: any) => put(`/admin/skills/${id}`, data), + + deleteAdminSkill: (id: string) => del(`/admin/skills/${id}`), + + changeAdminSkillState: (id: string, data: any) => put(`/admin/skills/${id}/state`, data), + + getAdminSkillVersions: (id: string) => get(`/admin/skills/${id}/versions`), + + rollbackAdminSkill: (id: string, data: any) => post(`/admin/skills/${id}/rollback`, data), + + publishAdminSkill: (id: string, data: any) => post(`/admin/skills/${id}/publish`, data), + + unpublishAdminSkill: (id: string) => post(`/admin/skills/${id}/unpublish`), + + getAdminSkillsStats: () => get('/admin/skills/stats/overview'), + +// 智能体管理 (Phase 2.3) + + getAdminAgents: (params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/agents/${query}`); + + }, + + getAdminAgent: (id: string) => get(`/admin/agents/${id}`), + + createAdminAgent: (data: any) => post('/admin/agents/', data), + + updateAdminAgent: (id: string, data: any) => put(`/admin/agents/${id}`, data), + + deleteAdminAgent: (id: string) => del(`/admin/agents/${id}`), + + toggleAdminAgent: (id: string, isActive: boolean) => put(`/admin/agents/${id}/toggle`, { is_active: isActive }), + + getAdminAgentsStats: () => get('/admin/agents/stats/overview'), + + getAdminAgentReviews: (id: string) => get(`/admin/agents/${id}/reviews`), + + getAdminAvailableSkills: (params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/agents/available/skills${query}`); + + }, + + getAdminAvailableModels: (params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/agents/available/models${query}`); + + }, + + // 知识库管理 (Phase 2.4) + + getAdminKnowledgeStats: () => get('/admin/knowledge/stats/overview'), + + getAdminKnowledge: (params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/knowledge/${query}`); + + }, + + getAdminKnowledgeDetail: (id: string) => get(`/admin/knowledge/${id}`), + + createAdminKnowledge: (data: any) => post('/admin/knowledge/', data), + + updateAdminKnowledge: (id: string, data: any) => put(`/admin/knowledge/${id}`, data), + + deleteAdminKnowledge: (id: string) => del(`/admin/knowledge/${id}`), + + toggleAdminKnowledge: (id: string, isActive: boolean) => put(`/admin/knowledge/${id}/toggle`, { is_active: isActive }), + + getAdminKnowledgeDocs: (kbId: string, params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/knowledge/${kbId}/documents${query}`); + + }, + + deleteAdminKnowledgeDoc: (kbId: string, docId: string) => del(`/admin/knowledge/${kbId}/documents/${docId}`), + + getAdminKnowledgeExtServices: () => get('/admin/knowledge/external/services'), + + createAdminKnowledgeExtService: (data: any) => post('/admin/knowledge/external/services', data), + + updateAdminKnowledgeExtService: (id: string, data: any) => put(`/admin/knowledge/external/services/${id}`, data), + + deleteAdminKnowledgeExtService: (id: string) => del(`/admin/knowledge/external/services/${id}`), + + checkAdminKnowledgeExtStatus: (id: string) => get(`/admin/knowledge/external/services/${id}/status`), + + syncAdminKnowledge: (kbId: string, data: any) => post(`/admin/knowledge/external/sync/${kbId}`, data), + + batchDeleteAdminKnowledge: (ids: string[]) => post('/admin/knowledge/batch/delete', ids), + + // OSS存储配置 (Phase 3.1) + + getAssetStats: () => get('/admin/assets/stats'), + + getAdminAssets: (query: string) => get(`/admin/assets/?${query}`), + + deleteAdminAsset: (id: string) => del(`/admin/assets/${id}`), + + getAssetActivity: () => get('/admin/assets/activity'), + + getAdminStorageStats: () => get('/admin/storage/stats/overview'), + + getAdminStorageConfigs: (params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/storage/${query}`); + + }, + + getAdminStorageConfig: (id: string) => get(`/admin/storage/${id}`), + + createAdminStorageConfig: (data: any) => post('/admin/storage/', data), + + updateAdminStorageConfig: (id: string, data: any) => put(`/admin/storage/${id}`, data), + + deleteAdminStorageConfig: (id: string) => del(`/admin/storage/${id}`), + + toggleAdminStorageConfig: (id: string) => put(`/admin/storage/${id}/toggle`), + + setDefaultAdminStorageConfig: (id: string) => put(`/admin/storage/${id}/set-default`), + + testAdminStorageConfig: (data: any) => post('/admin/storage/test', data), + + getAdminStorageUsage: (id: string) => get(`/admin/storage/${id}/usage`), + + // Storage Quota APIs (Phase C5) + + getStorageQuotaOverview: () => get('/admin/storage/quota/overview'), + + getStorageQuotaUsers: (params?: any) => { + + if (params) { + + const query = Object.entries(params).map(([k, v]) => v !== undefined && v !== '' && v !== null ? `${k}=${encodeURIComponent(String(v))}` : '').filter(Boolean).join('&'); + + return get(`/admin/storage/quota/users${query ? '?' + query : ''}`); + + } + + return get('/admin/storage/quota/users'); + + }, + + getStorageQuotaUser: (userId: string) => get(`/admin/storage/quota/users/${userId}`), + + adjustStorageQuota: (userId: string, data: any) => put(`/admin/storage/quota/users/${userId}`, data), + + recalcStorageQuota: (userId: string) => post(`/admin/storage/quota/recalculate/${userId}`, {}), + + initAllStorageQuotas: () => post('/admin/storage/quota/init-all', {}), + + + + // 自动化任务调度 (Phase 3.2) + + getAdminAutomationStats: () => get('/admin/automation/stats/overview'), + + getAdminAutomationTasks: (params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/automation/${query}`); + + }, + + getAdminAutomationTask: (id: string) => get(`/admin/automation/${id}`), + + createAdminAutomationTask: (data: any) => post('/admin/automation/', data), + + updateAdminAutomationTask: (id: string, data: any) => put(`/admin/automation/${id}`, data), + + deleteAdminAutomationTask: (id: string) => del(`/admin/automation/${id}`), + + toggleAdminAutomationTask: (id: string) => put(`/admin/automation/${id}/toggle`), + + executeAdminAutomationTask: (id: string) => post(`/admin/automation/${id}/execute`), + + getAdminAutomationTaskHistory: (id: string, params?: any) => { + + const query = params ? '?' + Object.entries(params).filter(([_, v]) => v !== undefined && v !== '').map(([k, v]) => `${k}=${v}`).join('&') : ''; + + return get(`/admin/automation/${id}/history${query}`); + + }, + + // MCP Server Management + + getAdminMcpStats: () => get('/admin/mcp/stats/overview'), + + getAdminMcpServers: (params?: any) => { + + const query = new URLSearchParams(params).toString(); + + return get(`/admin/mcp/${query ? '?' + query : ''}`); + + }, + + getAdminMcpServer: (id: string) => get(`/admin/mcp/${id}`), + + createAdminMcpServer: (data: any) => post('/admin/mcp/', data), + + updateAdminMcpServer: (id: string, data: any) => put(`/admin/mcp/${id}`, data), + + deleteAdminMcpServer: (id: string) => del(`/admin/mcp/${id}`), + + toggleAdminMcpServer: (id: string) => put(`/admin/mcp/${id}/toggle`), + + testAdminMcpServer: (id: string, data?: any) => post(`/admin/mcp/${id}/test`, data || {}), + + getAdminMcpServerTools: (id: string) => get(`/admin/mcp/${id}/tools`), + + getAdminMcpServerResources: (id: string) => get(`/admin/mcp/${id}/resources`), + + // Developer API Management + + getAdminDeveloperStats: () => get('/admin/developer/stats/overview'), + + getAdminApiKeys: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/developer/api-keys${sp.toString() ? '?' + sp.toString() : ''}`); + + }, + + createAdminApiKey: (data: any) => post('/admin/developer/api-keys/', data), + + updateAdminApiKey: (id: string, data: any) => put(`/admin/developer/api-keys/${id}`, data), + + revokeAdminApiKey: (id: string) => post(`/admin/developer/api-keys/${id}/revoke`), + + deleteAdminApiKey: (id: string) => del(`/admin/developer/api-keys/${id}`), + + getAdminOpenApiClients: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/developer/openapi-clients${sp.toString() ? '?' + sp.toString() : ''}`); + + }, + + createAdminOpenApiClient: (data: any) => post('/admin/developer/openapi-clients/', data), + + updateAdminOpenApiClient: (id: string, data: any) => put(`/admin/developer/openapi-clients/${id}`, data), + + resetAdminOpenApiClientSecret: (id: string) => post(`/admin/developer/openapi-clients/${id}/reset-secret`), + + deleteAdminOpenApiClient: (id: string) => del(`/admin/developer/openapi-clients/${id}`), + + getAdminCallStats: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/developer/call-stats${sp.toString() ? '?' + sp.toString() : ''}`); + + }, + + getAdminCallStatsOverview: () => get('/admin/developer/call-stats/overview'), + + getAdminSlowLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/developer/slow-logs${sp.toString() ? '?' + sp.toString() : ''}`); + + }, + + clearAdminSlowLogs: () => del('/admin/developer/slow-logs'), + + getAdminSlowLogsStats: () => get('/admin/developer/slow-logs/stats'), + + // AI App Management + + getAIAppStats: () => get('/admin/apps/stats/overview'), + + getAIApps: (params?: string) => get('/admin/apps/apps' + (params ? '?' + params : '')), + + getAIApp: (id: string) => get('/admin/apps/apps/' + id), + + createAIApp: (data: any) => post('/admin/apps/apps', data), + + updateAIApp: (id: string, data: any) => put('/admin/apps/apps/' + id, data), + + deleteAIApp: (id: string) => del('/admin/apps/apps/' + id), + + aiAppLifecycle: (id: string, action: string) => post('/admin/apps/apps/' + id + '/' + action, {}), + + getAIAppVersions: (id: string) => get('/admin/apps/apps/' + id + '/versions'), + + getAIAppAnalytics: (id: string) => get('/admin/apps/apps/' + id + '/analytics'), + + getAIAppTemplates: () => get('/admin/apps/templates'), + + createAIAppTemplate: (data: any) => post('/admin/apps/templates', data), + + createAIAppFromTemplate: (templateId: string, data: any) => post('/admin/apps/apps/from-template/' + templateId, data), + + getAIAppCategories: () => get('/admin/apps/categories'), + + // Logo 上传 + + uploadLogo: (formData: FormData) => { + + const token = localStorage.getItem('access_token'); + + return fetch(`/app/api/admin/settings/logo/upload`, { + + method: 'POST', + + headers: { 'Authorization': `Bearer ${token}` }, + + body: formData, + + }).then(res => res.json()); + + }, + + + + // MCP Market (Phase G1) + + getMcpMarketStats: () => + + get('/admin/mcp-market/stats'), + + getMcpTools: (params?: Record) => + + get('/admin/mcp-market/?' + new URLSearchParams(params || {}).toString()), + + getMcpToolDetail: (id: string) => + + get('/admin/mcp-market/' + id), + + createMcpTool: (data: any) => + + post('/admin/mcp-market/', data), + + updateMcpTool: (id: string, data: any) => + + put('/admin/mcp-market/' + id, data), + + deleteMcpTool: (id: string) => + + del('/admin/mcp-market/' + id), + + scanMcpTool: (id: string) => + + post('/admin/mcp-market/' + id + '/security-scan'), + + updateMcpToolSecurity: (id: string, data: any) => + + put('/admin/mcp-market/' + id + '/security-status', data), + + regenMcpDeepLink: (id: string) => + + post('/admin/mcp-market/' + id + '/deep-link'), + + getMcpManagedServers: () => + + get('/admin/mcp-market/managed/servers'), + + seedMcpTools: () => + + post('/admin/mcp-market/seed'), + + + + // Code Execution Sandbox (Phase G2) + + getCodeExecStats: () => get('/admin/code-exec/stats'), + + getCodeExecRuntimes: () => get('/admin/code-exec/runtimes'), + + executeCode: (data: { language: string; code: string; stdin?: string; timeout?: number }) => + + post('/admin/code-exec/execute', data), + + getCodeExecTasks: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/code-exec/tasks${sp.toString() ? '?' + sp.toString() : ''}`); + + }, + + getCodeExecTask: (id: string) => get(`/admin/code-exec/tasks/${id}`), + + deleteCodeExecTask: (id: string) => del(`/admin/code-exec/tasks/${id}`), + + + + + + // Distribution Builder (Phase G4) + + getDistributionStats: () => get('/admin/distributions/stats'), + + getDistributions: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]: [string, any]) => { if (v) sp.set(k, String(v)); }); + + return get('/admin/distributions' + (sp.toString() ? '?' + sp.toString() : '')); + + }, + + getDistributionTemplates: () => get('/admin/distributions/templates'), + + createDistribution: (data: any) => post('/admin/distributions', data), + + getDistribution: (id: string) => get('/admin/distributions/' + id), + + updateDistribution: (id: string, data: any) => put('/admin/distributions/' + id, data), + + deleteDistribution: (id: string) => del('/admin/distributions/' + id), + + getDistributionLicenses: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]: [string, any]) => { if (v) sp.set(k, String(v)); }); + + return get('/admin/distributions/licenses' + (sp.toString() ? '?' + sp.toString() : '')); + + }, + + createDistributionLicense: (distroId: string, data: any) => post('/admin/distributions/' + distroId + '/licenses', data), + + getDistributionLicense: (id: string) => get('/admin/distributions/licenses/' + id), + + revokeDistributionLicense: (id: string) => post('/admin/distributions/licenses/' + id + '/revoke'), + + // ===== Ads Management ===== + + getAds: () => get('/admin/ads/'), + + addAd: (data: any) => post('/admin/ads/', data), + + updateAd: (id: string, data: any) => put(`/admin/ads/${id}`, data), + + deleteAd: (id: string) => del(`/admin/ads/${id}`), + + + + // ===== Market Admin (extended) ===== + + getMarketOverview: () => get('/admin/market/overview'), + + getTopApps: (metric?: string, limit?: number) => get(`/admin/market/top-apps${metric ? '?metric=' + metric : ''}${limit ? (metric ? '&' : '?') + 'limit=' + limit : ''}`), + + getAdminMarketApps: (params?: string) => get(`/admin/market/apps${params ? '?' + params : ''}`), + + getPendingApps: () => get('/admin/market/pending'), + + getCoupons: () => get('/admin/market/coupons'), + + getRecentReviews: () => get('/admin/market/reviews'), + + approveApp: (id: string) => post(`/admin/market/apps/${id}/approve`), + + rejectApp: (id: string) => post(`/admin/market/apps/${id}/reject`), + + setFeaturedApp: (id: string, featured: boolean, sortOrder?: number) => put(`/admin/market/apps/${id}/featured`, { featured, sort_order: sortOrder }), + + deleteMarketApp: (id: string) => del(`/admin/market/apps/${id}`), + + deleteCoupon: (id: string) => del(`/admin/market/coupons/${id}`), + + updateMarketApp: (id: string, data: any) => put(`/admin/market/apps/${id}`, data), + + updateCoupon: (id: string, data: any) => put(`/admin/market/coupons/${id}`, data), + + addCoupon: (data: any) => post('/admin/market/coupons', data), + + + + // ===== Membership (extended) ===== + + getMembershipStats: () => get('/admin/membership/stats'), + + revokeMembership: (userId: string) => post(`/admin/membership/users/${userId}/revoke`), + + + + // ===== Logs (extended) ===== + + getOperationLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/operation-logs${sp.toString() ? '?' + sp.toString() : ''}`); + + }, + + getTokenLogs: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/logs/token-logs${sp.toString() ? '?' + sp.toString() : ''}`); + + }, + + + + // ===== Analytics ===== + + getAnalyticsOverview: (days?: number) => get(`/admin/analytics/overview${days ? '?days=' + days : ''}`), + + getAnalyticsApiTrends: (days?: number) => get(`/admin/analytics/trends/api-calls${days ? '?days=' + days : ''}`), + + getAnalyticsTokenTrends: (days?: number) => get(`/admin/analytics/trends/tokens${days ? '?days=' + days : ''}`), + + getAnalyticsUserTrends: (days?: number) => get(`/admin/analytics/trends/users${days ? '?days=' + days : ''}`), + + getAnalyticsRevenueTrends: (days?: number) => get(`/admin/analytics/trends/revenue${days ? '?days=' + days : ''}`), + + getAnalyticsModelUsage: (days?: number) => get(`/admin/analytics/models/usage${days ? '?days=' + days : ''}`), + + + + // ===== Billing ===== + + getBillingRules: () => get('/admin/billing/rules'), + + getBillingPackages: () => get('/admin/billing/packages'), + + getBillingTransactions: (page?: number) => get(`/admin/billing/transactions${page ? '?page=' + page : ''}`), + + getBillingStats: () => get('/admin/billing/stats/overview'), + + + + // ===== Tenant ===== + + getTenants: (page?: number) => get(`/admin/tenant/list${page ? '?page=' + page : ''}`), + + getTenantStats: () => get('/admin/tenant/stats/overview'), + + getTenantMembers: (tenantId: string) => get(`/admin/tenant/${tenantId}/members`), + + createTenant: (params: string) => post(`/admin/tenant/create${params}`), + + + +}; + +// ===== AI自进化API ===== + +export const evolutionAPI = { + + // 驾驶舱面板 + + getSkillTree: () => get('/evolution/dashboard/skill-tree'), + + getTimeline: (eventType?: string) => + + get('/evolution/dashboard/timeline' + (eventType ? `?event_type=${eventType}` : '')), + + getStatistics: () => get('/evolution/dashboard/statistics'), + + getProfile: () => get('/evolution/dashboard/profile'), + + + + // 技能管理 + + getSkills: (params?: { state?: string; keyword?: string; sort_by?: string; order?: string }) => { + + const query = new URLSearchParams(); + + if (params?.state) query.append('state', params.state); + + if (params?.keyword) query.append('keyword', params.keyword); + + if (params?.sort_by) query.append('sort_by', params.sort_by); + + if (params?.order) query.append('order', params.order); + + const qs = query.toString(); + + return get('/evolution/skills' + (qs ? `?${qs}` : '')); + + }, + + getSkillDetail: (name: string) => get(`/evolution/skills/${encodeURIComponent(name)}`), + + updateSkill: (name: string, data: { description?: string; tags?: string[]; category?: string }) => + + put(`/evolution/skills/${encodeURIComponent(name)}`, data), + + deleteSkill: (name: string) => del(`/evolution/skills/${encodeURIComponent(name)}`), + + freezeSkill: (name: string) => post(`/evolution/skills/${encodeURIComponent(name)}/freeze`), + + unfreezeSkill: (name: string) => post(`/evolution/skills/${encodeURIComponent(name)}/unfreeze`), + + getSkillHistory: (name: string) => get(`/evolution/skills/${encodeURIComponent(name)}/history`), + + getSkillTrajectories: (name: string) => get(`/evolution/skills/${encodeURIComponent(name)}/trajectories`), + + + + // 事件 + + getEvents: (params?: { event_type?: string; skill_name?: string; limit?: number }) => { + + const query = new URLSearchParams(); + + if (params?.event_type) query.append('event_type', params.event_type); + + if (params?.skill_name) query.append('skill_name', params.skill_name); + + if (params?.limit) query.append('limit', String(params.limit)); + + const qs = query.toString(); + + return get('/evolution/events' + (qs ? `?${qs}` : '')); + + }, + + + + // 轨迹提交 + + submitTrajectory: (data: { conversation_id: string; messages: any[] }) => + + post('/evolution/trajectory/submit', data), + + + + // v2: 技能回滚 + + rollbackSkill: (skillName: string, version: number) => + + post(`/evolution/skills/${encodeURIComponent(skillName)}/rollback/${version}`), + + + + // v2: 发布到市场 + + publishSkill: (skillName: string, data: { price: number; market_description: string }) => + + post(`/evolution/skills/${encodeURIComponent(skillName)}/publish`, data), + + + + // v2: 团队共享 + + shareToTeam: (skillName: string, teamId: string) => + + post(`/evolution/skills/${encodeURIComponent(skillName)}/share-to-team`, { team_id: teamId }), + + + + // v2: 流水线状态 + + getPipelineStatus: () => get('/evolution/pipeline/status'), + + + + // v2: 手动触发流水线 + + triggerPipeline: () => post('/evolution/pipeline/trigger'), + + + + // v2: Watcher状态 + + getWatcherStatus: () => get('/evolution/watcher/status'), + + + + // v2: 进化报告 + + getReports: (reportType?: string) => { + + const qs = reportType ? `?report_type=${reportType}` : ''; + + return get(`/evolution/reports${qs}`); + + }, + + getReportDetail: (reportId: string) => get(`/evolution/reports/${reportId}`), + + + + + + // Workflow (Phase 3.6) + + getWorkflows: (params?: Record) => + + get('/admin/workflows/?' + new URLSearchParams(params || {}).toString()), + + getWorkflow: (id: string) => + + get(`/admin/workflows/${id}`), + + getWorkflowStats: () => + + get('/admin/workflows/stats'), + + getWorkflowTemplates: () => + + get('/admin/workflows/templates'), + + createWorkflow: (data: any) => + + post('/admin/workflows/', data), + + updateWorkflow: (id: string, data: any) => + + put(`/admin/workflows/${id}`, data), + + deleteWorkflow: (id: string) => + + del(`/admin/workflows/${id}`), + + toggleWorkflow: (id: string, isActive: boolean) => + + post(`/admin/workflows/${id}/toggle?is_active=${isActive}`), + + executeWorkflow: (id: string, inputText: string) => + + post(`/admin/workflows/${id}/execute`, { input_text: inputText }), + + + + + + // Privacy & Local Agent (Phase G5) + + getPrivacyStats: () => get('/admin/privacy/stats'), + + getPrivacyConfig: () => get('/admin/privacy/config'), + + updatePrivacyConfig: (params: any) => { + + const sp = new URLSearchParams(); + + Object.entries(params).forEach(([k, v]) => { if (v != null) sp.append(k, String(v)); }); + + return put('/admin/privacy/config?' + sp.toString()); + + }, + + switchPrivacyMode: (mode: string) => post('/admin/privacy/switch-mode?mode=' + encodeURIComponent(mode)), + + getPrivacyLicenses: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params?.status) sp.append('status', params.status); + + return get('/admin/privacy/licenses' + (sp.toString() ? '?' + sp.toString() : '')); + + }, + + generateOfflineLicense: (data: any) => { + + const sp = new URLSearchParams(); + + if (data.agent_id) sp.append('agent_id', data.agent_id); + + sp.append('max_offline_days', String(data.max_offline_days || 7)); + + return post('/admin/privacy/licenses/generate?' + sp.toString()); + + }, + + revokePrivacyLicense: (id: number) => post('/admin/privacy/licenses/' + id + '/revoke'), + + validatePrivacyLicense: (licenseKey: string) => { + + const sp = new URLSearchParams(); + + sp.append('license_key', licenseKey); + + return post('/admin/privacy/licenses/validate?' + sp.toString()); + + }, + + getKbSyncStates: () => get('/admin/privacy/kb-sync'), + + triggerKbSync: (kbId: number, direction: string) => { + + const sp = new URLSearchParams(); + + sp.append('knowledge_base_id', String(kbId)); + + sp.append('sync_direction', direction); + + return post('/admin/privacy/kb-sync/trigger?' + sp.toString()); + + }, + + getLocalAgents: () => get('/admin/privacy/local-agents'), + + getPrivacyUsageStats: () => get('/admin/privacy/usage/stats'), + + getBridgeConfig: () => get('/admin/privacy/bridge-config'), + + + + // IM Bot Management (Phase 4) + + getImBotStats: () => get('/admin/im-bot/stats'), + + getImBotDashboard: () => get('/admin/im-bot/dashboard'), + + getImBotChannels: (params?: string) => get(`/admin/im-bot/channels${params || ''}`), + + getImBotChannelDetail: (id: string) => get(`/admin/im-bot/channels/${id}`), + + testImBotChannel: (id: string) => post(`/admin/im-bot/channels/${id}/test`), + + activateImBotChannel: (id: string) => post(`/admin/im-bot/channels/${id}/activate`), + + deactivateImBotChannel: (id: string) => post(`/admin/im-bot/channels/${id}/deactivate`), + + getImBotRoutes: (params?: string) => get(`/admin/im-bot/routes${params || ''}`), + + createImBotRoute: (params: string) => post(`/admin/im-bot/routes${params}`), + + deleteImBotRoute: (id: string) => del(`/admin/im-bot/routes/${id}`), + + // 个人微信(iLink 协议)原生适配器 + + getWechatConfig: () => get('/admin/im-bot/wechat'), + + wechatQrcode: () => post('/admin/im-bot/wechat/qrcode'), + + wechatQrcodeStatus: (params: string) => post(`/admin/im-bot/wechat/qrcode/status${params}`), + + getWechatStatus: () => get('/admin/im-bot/wechat/status'), + + disconnectWechat: () => post('/admin/im-bot/wechat/disconnect'), + + getImBotPlatforms: () => get('/admin/im-bot/platforms'), + + getImBotWebhookUrl: (platform: string) => get(`/admin/im-bot/webhook-url/${platform}`), + + getImBotMessages: (params?: string) => get(`/admin/im-bot/messages${params || ''}`), + + imBotQuickSetup: (params: string) => post(`/admin/im-bot/quick-setup${params}`), + + + + // 推广系统 + + getPromotionStats: () => get('/admin/promotion/stats'), + + getPromotions: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); + + return get(`/admin/promotion/promotions${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + createPromotion: (params: any) => { + + const sp = new URLSearchParams(); + + Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); + + return post(`/admin/promotion/promotions${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + updatePromotion: (id: string, params: any) => { + + const sp = new URLSearchParams(); + + Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); }); + + return put(`/admin/promotion/promotions/${id}${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + deletePromotion: (id: string) => del(`/admin/promotion/promotions/${id}`), + + togglePromotion: (id: string) => post(`/admin/promotion/promotions/${id}/toggle`), + + getReferralLinks: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/promotion/referral-links${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + generateReferralLink: (params: any) => { + + const sp = new URLSearchParams(); + + Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return post(`/admin/promotion/referral-links/generate${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + getReferralRewards: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/promotion/rewards${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + approveReward: (id: string) => post(`/admin/promotion/rewards/${id}/approve`), + + markRewardPaid: (id: string, params: any) => { + + const sp = new URLSearchParams(); + + Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return post(`/admin/promotion/rewards/${id}/pay${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + getPromotionLeaderboard: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/promotion/leaderboard${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + regenerateReferralCode: (id: string) => post(`/admin/promotion/referral-links/${id}/regenerate`), + + getMyReferrals: () => get('/admin/promotion/my-referrals'), + + getMyRewards: () => get('/admin/promotion/my-rewards'), + + + + // 成就系统 + + getAchievements: (params?: string) => get(`/admin/achievement/achievements${params || ''}`), + + getAchievementDetail: (id: string) => get(`/admin/achievement/achievements/${id}`), + + createAchievement: (params: string) => post(`/admin/achievement/achievements${params}`), + + updateAchievement: (id: string, params: string) => put(`/admin/achievement/achievements/${id}${params}`), + + deleteAchievement: (id: string) => del(`/admin/achievement/achievements/${id}`), + + getUserAchievements: () => get('/admin/achievement/user/achievements'), + + claimAchievementReward: (id: string) => post(`/admin/achievement/user/achievements/${id}/claim`), + + checkAchievementProgress: () => post('/admin/achievement/check-progress'), + + getAchievementLeaderboard: (params?: string) => get(`/admin/achievement/leaderboard${params || ''}`), + + + + // 多租户管理 + + getTenants: (page?: number, keyword?: string, status?: string) => { + + const sp = new URLSearchParams(); + + if (page) sp.set('page', String(page)); + + if (keyword) sp.set('keyword', keyword); + + if (status) sp.set('status', status); + + sp.set('page_size', '20'); + + return get(`/admin/tenant/list${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + createTenant: (params: string) => post(`/admin/tenant/create${params}`), + + getTenantDetail: (id: string) => get(`/admin/tenant/${id}`), + + updateTenant: (id: string, params: string) => put(`/admin/tenant/${id}${params}`), + + deleteTenant: (id: string) => del(`/admin/tenant/${id}`), + + getTenantMembers: (tenantId: string, page?: number) => { + + const sp = new URLSearchParams(); + + sp.set('page', String(page || 1)); + + sp.set('page_size', '100'); + + return get(`/admin/tenant/${tenantId}/members?${sp.toString()}`); + + }, + + addTenantMember: (tenantId: string, params: string) => post(`/admin/tenant/${tenantId}/members/add${params}`), + + updateTenantMember: (tenantId: string, memberId: string, params: string) => put(`/admin/tenant/${tenantId}/members/${memberId}${params}`), + + removeTenantMember: (tenantId: string, memberId: string) => del(`/admin/tenant/${tenantId}/members/${memberId}`), + + getTenantQuotas: (tenantId: string) => get(`/admin/tenant/${tenantId}/quotas`), + + updateTenantQuota: (tenantId: string, params: string) => put(`/admin/tenant/${tenantId}/quotas${params}`), + + getTenantInvitations: (tenantId: string) => get(`/admin/tenant/${tenantId}/invitations`), + + createTenantInvitation: (tenantId: string, params: string) => post(`/admin/tenant/${tenantId}/invitations${params}`), + + getTenantStats: () => get('/admin/tenant/stats/overview'), + + + + // 计费与支付 + + getBillingRules: (page?: number) => get(`/admin/billing/rules?page=${page || 1}&page_size=100`), + + createBillingRule: (params: string) => post(`/admin/billing/rules/create${params}`), + + updateBillingRule: (id: string, params: string) => put(`/admin/billing/rules/${id}${params}`), + + deleteBillingRule: (id: string) => del(`/admin/billing/rules/${id}`), + + getBillingPackages: () => get('/admin/billing/packages'), + + createBillingPackage: (params: string) => post(`/admin/billing/packages/create${params}`), + + updateBillingPackage: (id: string, params: string) => put(`/admin/billing/packages/${id}${params}`), + + deleteBillingPackage: (id: string) => del(`/admin/billing/packages/${id}`), + + getBillingTransactions: (page?: number) => get(`/admin/billing/transactions?page=${page || 1}&page_size=20`), + + getInvoices: (page?: number) => get(`/admin/billing/invoices?page=${page || 1}&page_size=20`), + + generateInvoice: (params: string) => post(`/admin/billing/invoices/generate${params}`), + + getPaymentConfigs: () => get('/admin/billing/payment-configs'), + + updatePaymentConfig: (id: string, params: string) => put(`/admin/billing/payment-configs/${id}${params}`), + + getBillingStats: () => get('/admin/billing/stats/overview'), + + + + // 数据分析 + + getAnalyticsOverview: (days?: number) => get(`/admin/analytics/overview?days=${days || 30}`), + + getAnalyticsApiTrends: (days?: number) => get(`/admin/analytics/trends/api-calls?days=${days || 7}`), + + getAnalyticsTokenTrends: (days?: number) => get(`/admin/analytics/trends/tokens?days=${days || 7}`), + + getAnalyticsUserTrends: (days?: number) => get(`/admin/analytics/trends/users?days=${days || 30}`), + + getAnalyticsRevenueTrends: (days?: number) => get(`/admin/analytics/trends/revenue?days=${days || 30}`), + + getAnalyticsModelUsage: (days?: number) => get(`/admin/analytics/models/usage?days=${days || 30}`), + + getAnalyticsDashboards: () => get('/admin/analytics/dashboards'), + + createAnalyticsDashboard: (params: string) => post(`/admin/analytics/dashboards/create${params}`), + + deleteAnalyticsDashboard: (id: string) => del(`/admin/analytics/dashboards/${id}`), + + getAnalyticsEvents: (params?: any) => { + + const sp = new URLSearchParams(); + + if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); }); + + return get(`/admin/analytics/events${sp.toString() ? `?${sp.toString()}` : ''}`); + + }, + + + +}; + + + + + +// ===== 前台用户个人微信 (iLink 协议 / 微信 ClawBot 插件) ===== + +export const imUserAPI = { + + getStatus: () => get('/im/wechat/status'), + + qrcode: () => post('/im/wechat/qrcode'), + + qrcodeStatus: (qrcode: string) => post(`/im/wechat/qrcode/status?qrcode=${encodeURIComponent(qrcode)}`), + + disconnect: () => post('/im/wechat/disconnect'), + + history: () => get('/im/wechat/history'), + +}; + diff --git a/frontend/src/components/layout/AdminLayout.tsx b/frontend/src/components/layout/AdminLayout.tsx index fd74038..7bbda48 100644 --- a/frontend/src/components/layout/AdminLayout.tsx +++ b/frontend/src/components/layout/AdminLayout.tsx @@ -1,435 +1,437 @@ -import React, { useState, useEffect } from 'react'; - -import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'; - -import { useAuth } from '@/contexts/AuthContext'; - -import './AdminLayout.css'; - - - -interface NavItem { - - path: string; - - icon: string; - - label: string; - - exact?: boolean; - -} - - - -interface NavGroup { - - title: string; - - icon: string; - - items: NavItem[]; - -} - - - -const adminNavGroups: NavGroup[] = [ - - { - title: '数据驾驶舱', - icon: 'dashboard', - items: [ - { path: '/admin', icon: 'dashboard', label: '平台驾驶舱', exact: true }, - ], - }, - - { - title: 'AI技能中心', - icon: 'agents', - items: [ - { path: '/admin/models', icon: 'models', label: '大模型库' }, - { path: '/admin/agents', icon: 'agents', label: 'Agent 管理' }, - { path: '/admin/skills', icon: 'skills', label: 'Skills技能管理' }, - { path: '/admin/mcp', icon: 'mcp', label: 'MCP Client 管理' }, - { path: '/admin/market', icon: 'market', label: 'AI 技能审核' }, - ], - }, - - { - title: '平台运营', - icon: 'users', - items: [ - { path: '/admin/users', icon: 'users', label: '用户管理' }, - { path: '/admin/membership', icon: 'membership', label: '会员管理' }, - { path: '/admin/promotion', icon: 'promotion', label: '推广系统' }, - { path: '/admin/achievement', icon: 'achievement', label: '成就系统' }, - ], - }, - - { - title: '商业变现', - icon: 'billing', - items: [ - { path: '/admin/billing', icon: 'billing', label: '计费与支付' }, - { path: '/admin/payment', icon: 'payment', label: '支付渠道' }, - { path: '/admin/services', icon: 'services', label: '增值服务' }, - { path: '/admin/ads', icon: 'ads', label: '广告管理' }, - { path: '/admin/assets', icon: 'assets', label: '资产管理' }, - ], - }, - - { - title: '系统管理', - icon: 'settings', - items: [ - { path: '/admin/settings', icon: 'settings', label: '网站设置' }, - { path: '/admin/logs', icon: 'logs', label: '日志与风控' }, - { path: '/admin/knowledge', icon: 'knowledge', label: '知识库管理' }, - { path: '/admin/sms', icon: 'sms', label: '短信配置' }, - { path: '/admin/storage', icon: 'storage', label: '存储配置' }, - { path: '/admin/automation', icon: 'automation', label: '自动化任务' }, - ], - }, - - { - title: '待处理', - icon: 'developer', - items: [ - { path: '/admin/analytics', icon: 'analytics', label: '数据分析' }, - { path: '/admin/workflow', icon: 'workflow', label: '工作流编排' }, - { path: '/admin/apps', icon: 'apps', label: 'AI 应用管理' }, - { path: '/admin/code-exec', icon: 'code', label: '代码执行沙箱' }, - { path: '/admin/tenant', icon: 'tenant', label: '多租户管理' }, - { path: '/admin/im-bots', icon: 'imbot', label: 'IM 机器人' }, - { path: '/admin/privacy', icon: 'privacy', label: '隐私与本地代理' }, - { path: '/admin/distributions', icon: 'distro', label: '发行版构建' }, - { path: '/admin/developer', icon: 'developer', label: '开发者 API' }, - { path: '/admin/developer-tools', icon: 'devtools', label: '开发者工具' }, - ], - }, - -]; - - - -const adminIconPaths: Record = { - - dashboard: , - - users: , - - models: , - - settings: , - - payment: , - - services: , - - membership: , - - ads: , - - market: , - - logs: , - - agents: , - - skills: , - - code: , - - distro: , - - privacy: , - - promotion: , - - achievement: , - - tenant: , - - billing: , - - analytics: , - - imbot: , - - storage: , - - automation: , - - developer: , - - devtools: , - - knowledge: , - - workflow: , - - mcp: , - - apps: , - - assets: , - - chevron: , - - sms: , - -}; - - - -const AdminLayout: React.FC = () => { - - const { user, logout } = useAuth(); - - const navigate = useNavigate(); - - const location = useLocation(); - - // 后台必须登录且为管理员 - useEffect(() => { - const token = localStorage.getItem('aiflow_token'); - if (!token || !user || user.role !== 'admin') { - logout(); - navigate('/login', { replace: true }); - } - }, [user, logout, navigate]); - - const navCrumb = (() => { - - for (const g of adminNavGroups) { - - for (const it of g.items) { - - const match = it.exact - - ? location.pathname === it.path - - : location.pathname === it.path || location.pathname.startsWith(it.path + '/'); - - if (match) return { group: g.title, label: it.label }; - - } - - } - - return { group: '', label: '' }; - - })(); - - const navGroup = navCrumb.group; - - const navLabel = navCrumb.label; - - - - // 默认展开全部功能板块,确保后台所有模块在 UI 中可见(DESIGN.md: 侧栏分类清晰) - const [expandedGroups, setExpandedGroups] = useState>(() => { - return new Set(adminNavGroups.map((g) => g.title)); - }); - - - - const toggleGroup = (title: string) => { - - setExpandedGroups(prev => { - - const next = new Set(prev); - - if (next.has(title)) next.delete(title); - - else next.add(title); - - return next; - - }); - - }; - - - - const handleLogout = () => { - - logout(); - - navigate('/'); - - }; - - - - const handleVisitFrontend = () => { - - const url = window.location.origin + '/chat'; - - window.open(url, '_blank'); - - }; - - - - return ( - -
- - - -
- -
- - - -
- -
{user?.username?.[0]?.toUpperCase() || 'A'}
- - {user?.username || 'admin'} - -
- -
- - - -
- -
- - ); - -}; - - - -export default AdminLayout; - +import React, { useState, useEffect } from 'react'; + +import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'; + +import { useAuth } from '@/contexts/AuthContext'; + +import './AdminLayout.css'; + + + +interface NavItem { + + path: string; + + icon: string; + + label: string; + + exact?: boolean; + +} + + + +interface NavGroup { + + title: string; + + icon: string; + + items: NavItem[]; + +} + + + +const adminNavGroups: NavGroup[] = [ + + { + title: '数据驾驶舱', + icon: 'dashboard', + items: [ + { path: '/admin', icon: 'dashboard', label: '平台驾驶舱', exact: true }, + ], + }, + + { + title: 'AI技能中心', + icon: 'agents', + items: [ + { path: '/admin/models', icon: 'models', label: '大模型库' }, + { path: '/admin/agents', icon: 'agents', label: 'Agent 管理' }, + { path: '/admin/skills', icon: 'skills', label: 'Skills技能管理' }, + { path: '/admin/mcp', icon: 'mcp', label: 'MCP Client 管理' }, + { path: '/admin/market', icon: 'market', label: 'AI 技能审核' }, + ], + }, + + { + title: '平台运营', + icon: 'users', + items: [ + { path: '/admin/users', icon: 'users', label: '用户管理' }, + { path: '/admin/membership', icon: 'membership', label: '会员管理' }, + { path: '/admin/promotion', icon: 'promotion', label: '推广系统' }, + { path: '/admin/achievement', icon: 'achievement', label: '成就系统' }, + ], + }, + + { + title: '商业变现', + icon: 'billing', + items: [ + { path: '/admin/billing', icon: 'billing', label: '计费与支付' }, + { path: '/admin/payment', icon: 'payment', label: '支付渠道' }, + { path: '/admin/services', icon: 'services', label: '增值服务' }, + { path: '/admin/ads', icon: 'ads', label: '广告管理' }, + { path: '/admin/assets', icon: 'assets', label: '资产管理' }, + ], + }, + + { + title: '系统管理', + icon: 'settings', + items: [ + { path: '/admin/settings', icon: 'settings', label: '网站设置' }, + { path: '/admin/logs', icon: 'logs', label: '日志与风控' }, + { path: '/admin/knowledge', icon: 'knowledge', label: '知识库管理' }, + { path: '/admin/sms', icon: 'sms', label: '短信配置' }, + { path: '/admin/storage', icon: 'storage', label: '存储配置' }, + { path: '/admin/automation', icon: 'automation', label: '自动化任务' }, + ], + }, + + { + title: '待处理', + icon: 'developer', + items: [ + { path: '/admin/analytics', icon: 'analytics', label: '数据分析' }, + { path: '/admin/observability', icon: 'observability', label: '调用可观测' }, + { path: '/admin/workflow', icon: 'workflow', label: '工作流编排' }, + { path: '/admin/apps', icon: 'apps', label: 'AI 应用管理' }, + { path: '/admin/code-exec', icon: 'code', label: '代码执行沙箱' }, + { path: '/admin/tenant', icon: 'tenant', label: '多租户管理' }, + { path: '/admin/im-bots', icon: 'imbot', label: 'IM 机器人' }, + { path: '/admin/privacy', icon: 'privacy', label: '隐私与本地代理' }, + { path: '/admin/distributions', icon: 'distro', label: '发行版构建' }, + { path: '/admin/developer', icon: 'developer', label: '开发者 API' }, + { path: '/admin/developer-tools', icon: 'devtools', label: '开发者工具' }, + ], + }, + +]; + + + +const adminIconPaths: Record = { + + dashboard: , + + users: , + + models: , + + settings: , + + payment: , + + services: , + + membership: , + + ads: , + + market: , + + logs: , + + agents: , + + skills: , + + code: , + + distro: , + + privacy: , + + promotion: , + + achievement: , + + tenant: , + + billing: , + + analytics: , + observability: , + + imbot: , + + storage: , + + automation: , + + developer: , + + devtools: , + + knowledge: , + + workflow: , + + mcp: , + + apps: , + + assets: , + + chevron: , + + sms: , + +}; + + + +const AdminLayout: React.FC = () => { + + const { user, logout } = useAuth(); + + const navigate = useNavigate(); + + const location = useLocation(); + + // 后台必须登录且为管理员 + useEffect(() => { + const token = localStorage.getItem('aiflow_token'); + if (!token || !user || user.role !== 'admin') { + logout(); + navigate('/login', { replace: true }); + } + }, [user, logout, navigate]); + + const navCrumb = (() => { + + for (const g of adminNavGroups) { + + for (const it of g.items) { + + const match = it.exact + + ? location.pathname === it.path + + : location.pathname === it.path || location.pathname.startsWith(it.path + '/'); + + if (match) return { group: g.title, label: it.label }; + + } + + } + + return { group: '', label: '' }; + + })(); + + const navGroup = navCrumb.group; + + const navLabel = navCrumb.label; + + + + // 默认展开全部功能板块,确保后台所有模块在 UI 中可见(DESIGN.md: 侧栏分类清晰) + const [expandedGroups, setExpandedGroups] = useState>(() => { + return new Set(adminNavGroups.map((g) => g.title)); + }); + + + + const toggleGroup = (title: string) => { + + setExpandedGroups(prev => { + + const next = new Set(prev); + + if (next.has(title)) next.delete(title); + + else next.add(title); + + return next; + + }); + + }; + + + + const handleLogout = () => { + + logout(); + + navigate('/'); + + }; + + + + const handleVisitFrontend = () => { + + const url = window.location.origin + '/chat'; + + window.open(url, '_blank'); + + }; + + + + return ( + +
+ + + +
+ +
+ + + +
+ +
{user?.username?.[0]?.toUpperCase() || 'A'}
+ + {user?.username || 'admin'} + +
+ +
+ + + +
+ +
+ + ); + +}; + + + +export default AdminLayout; + diff --git a/frontend/src/pages/admin/Observability.tsx b/frontend/src/pages/admin/Observability.tsx new file mode 100644 index 0000000..668d4b1 --- /dev/null +++ b/frontend/src/pages/admin/Observability.tsx @@ -0,0 +1,165 @@ +import { useEffect, useState } from 'react'; +import { adminAPI } from '@/api'; + +interface Summary { + totals: { calls: number; tokens: number; cost: number; users: number }; + by_model: { model_name: string; calls: number; tokens: number; cost: number }[]; + by_user: { user_id: string; username: string; calls: number; tokens: number; cost: number }[]; + daily: { date: string; tokens: number; cost: number; calls: number }[]; + recent: { trace_id: string | null; model_name: string; tokens: number; cost: number; created_at: string | null }[]; +} + +const ACCENT = '#5B5FED'; +const ACCENT_HOVER = '#4A4ECC'; + +export default function Observability() { + const [days, setDays] = useState(30); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [err, setErr] = useState(''); + + useEffect(() => { + let alive = true; + setLoading(true); + setErr(''); + adminAPI.getObservabilitySummary(days) + .then((d: Summary) => { if (alive) setData(d); }) + .catch((e: any) => { if (alive) setErr(e?.message || '加载失败'); }) + .finally(() => { if (alive) setLoading(false); }); + return () => { alive = false; }; + }, [days]); + + const maxTokens = data ? Math.max(1, ...data.daily.map(d => d.tokens)) : 1; + const maxModel = data ? Math.max(1, ...data.by_model.map(m => m.tokens)) : 1; + const fmt = (n: number) => n.toLocaleString('zh-CN'); + const fmtCost = (n: number) => `¥${n.toFixed(4)}`; + + const card: React.CSSProperties = { + background: '#fff', border: '1px solid #eceef5', borderRadius: 16, + padding: 20, boxShadow: '0 1px 3px rgba(20,30,60,0.04)', marginBottom: 18, + }; + const stat: React.CSSProperties = { + flex: 1, minWidth: 150, background: '#fff', border: '1px solid #eceef5', + borderRadius: 16, padding: 18, boxShadow: '0 1px 3px rgba(20,30,60,0.04)', + }; + + return ( +
+
+
+

调用可观测

+
+ 媒体 / LLM 调用归因 · 基于 trace_id 关联请求链路(Phase 2.2) +
+
+
+ {[7, 30, 90].map(d => ( + + ))} +
+
+ + {err &&
{err}
} + {loading && !data &&
加载中…
} + + {data && ( + <> +
+
调用次数
{fmt(data.totals.calls)}
+
Token 消耗
{fmt(data.totals.tokens)}
+
成本
{fmtCost(data.totals.cost)}
+
活跃用户
{fmt(data.totals.users)}
+
+ +
+

按模型

+
+ {data.by_model.length === 0 &&
暂无数据
} + {data.by_model.map(m => ( +
+
{m.model_name}
+
+
+
+
{fmt(m.tokens)}
+
{fmtCost(m.cost)}
+
+ ))} +
+
+ +
+

每日趋势(Token)

+
+ {data.daily.length === 0 &&
暂无数据
} + {data.daily.map(d => ( +
+
+
{d.date.slice(5)}
+
+ ))} +
+
+ +
+
+

按用户(Top 20)

+ + + + + + {data.by_user.map(u => ( + + + + + + + ))} + {data.by_user.length === 0 && } + +
用户调用Token成本
{u.username}{fmt(u.calls)}{fmt(u.tokens)}{fmtCost(u.cost)}
暂无数据
+
+
+ +
+

最近调用(trace_id 可下钻)

+ + + + + + {data.recent.map((r, i) => ( + + + + + + + + ))} + {data.recent.length === 0 && } + +
Trace ID模型Token成本时间
{r.trace_id || '—'}{r.model_name}{fmt(r.tokens)}{fmtCost(r.cost)}{r.created_at || '—'}
暂无数据
+
+ + )} +
+ ); +} + +const th: React.CSSProperties = { padding: '8px 6px', fontWeight: 600, fontSize: 12 }; +const thR: React.CSSProperties = { ...th, textAlign: 'right' }; +const td: React.CSSProperties = { padding: '8px 6px' }; +const tdR: React.CSSProperties = { ...td, textAlign: 'right', color: '#5b6172' };