refactor: 移除通用频道管理(LangBot)模块,IM Bot 改为直接对接;迁移 _call_real_llm 至 im_llm
This commit is contained in:
1 parent
945eeb8ba0
commit
ab54d7344c
15 files changed
+3208
-8784
No files matched your search
@@ -1,6 +1,5 @@
|
||||
from . import auth
|
||||
from . import chat
|
||||
from . import channels
|
||||
from . import knowledge
|
||||
from . import market
|
||||
from . import token_usage
|
||||
|
||||
@@ -608,7 +608,7 @@ def _qihua_reply(text: str) -> str:
|
||||
自行创建 DB 会话,避免上层线程无可用 session 时崩溃。"""
|
||||
db = None
|
||||
try:
|
||||
from app.api.channels import _call_real_llm
|
||||
from app.api.im_llm import _call_real_llm
|
||||
db = SessionLocal()
|
||||
return _call_real_llm(db, None, text, "个人微信")
|
||||
except Exception as e:
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
IM Bot 真实 LLM 调用。
|
||||
|
||||
原实现位于 channels.py(通用频道管理模块),该模块已移除。
|
||||
为保持 IM Bot(微信机器人等)正常生成回复,将 _call_real_llm 迁移至此独立模块。
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..capability_registry import resolve_auto_model
|
||||
from ..models import AIModel, ModelProvider
|
||||
|
||||
|
||||
def _call_real_llm(db: Session, user=None, prompt: str = "", platform: str = "im") -> str:
|
||||
"""调用七虎AI 默认文本模型生成回复,供 IM Bot 复用。"""
|
||||
try:
|
||||
ai_model = resolve_auto_model("text", db)
|
||||
if not ai_model:
|
||||
return "(未配置默认文本模型,无法生成回复)"
|
||||
provider = db.query(ModelProvider).filter(ModelProvider.id == ai_model.provider_id).first()
|
||||
api_key = ai_model.api_key or (provider.api_key if provider else None)
|
||||
api_url = ai_model.endpoint
|
||||
if not api_url and provider and provider.api_base_url:
|
||||
api_url = f"{provider.api_base_url.rstrip('/')}/v1/chat/completions"
|
||||
if not (api_key and api_url):
|
||||
return "(模型未配置 API Key 或地址,无法生成回复)"
|
||||
messages = [
|
||||
{"role": "system", "content": "你是七虎AI 助手,负责在即时通讯中回复用户消息。"},
|
||||
{"role": "user", "content": prompt},
|
||||
]
|
||||
request_body = {"model": ai_model.name, "messages": messages, "max_tokens": 2048}
|
||||
with httpx.Client(timeout=ai_model.timeout or 120) as client:
|
||||
resp = client.post(
|
||||
api_url,
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
json=request_body,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
return f"(AI 模型调用失败 HTTP {resp.status_code})"
|
||||
data = resp.json()
|
||||
return data.get("choices", [{}])[0].get("message", {}).get("content", "")
|
||||
except Exception as e:
|
||||
return f"(AI 回复生成失败:{e})"
|
||||
+244
-245
@@ -1,245 +1,244 @@
|
||||
"""
|
||||
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, channels, 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
|
||||
from app.api.im_bot_user import router as im_bot_user_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.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())
|
||||
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
|
||||
|
||||
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(channels.router, prefix="/app/api/channels", tags=["频道"])
|
||||
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(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
|
||||
from app.api.im_bot_user import router as im_bot_user_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.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())
|
||||
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
|
||||
|
||||
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(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"}
|
||||
+171
-173
@@ -1,173 +1,171 @@
|
||||
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 ChannelPage = React.lazy(() => import('@/pages/front/ChannelPage'));
|
||||
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 pageFallback = <div className="loading-screen">加载中...</div>;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [user, setUser] = useState<User | null>(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 <div className="loading-screen">加载中...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, login, logout, isAdmin }}>
|
||||
<Suspense fallback={pageFallback}>
|
||||
<Routes>
|
||||
{/* Login */}
|
||||
<Route path="/login" element={
|
||||
user ? (isAdmin ? <Navigate to="/admin" /> : <Navigate to="/chat" />) : <LoginPage />
|
||||
} />
|
||||
|
||||
{/* Root - Landing for guests, redirect for authenticated users */}
|
||||
<Route path="/" element={
|
||||
user ? (isAdmin ? <Navigate to="/admin" /> : <Navigate to="/chat" />) : <LandingPage />
|
||||
} />
|
||||
|
||||
{/* Front Routes (authenticated only) */}
|
||||
<Route path="/" element={<FrontLayout />}>
|
||||
<Route path="chat" element={<ChatPage />} />
|
||||
<Route path="chat/:id" element={<ChatPage />} />
|
||||
<Route path="channels" element={<ChannelPage />} />
|
||||
<Route path="knowledge" element={<KnowledgePage />} />
|
||||
<Route path="market" element={<MarketPage />} />
|
||||
<Route path="token" element={<TokenDashboardPage />} />
|
||||
<Route path="pricing" element={<PricingPage />} />
|
||||
<Route path="profile" element={<ProfilePage />} />
|
||||
<Route path="notifications" element={<NotificationPage />} />
|
||||
<Route path="assets" element={<AssetsPage />} />
|
||||
<Route path="assets/novel/:id" element={<NovelPreviewPage />} />
|
||||
<Route path="evolution" element={<EvolutionDashboard />} />
|
||||
<Route path="wechat-bot" element={<WechatBot />} />
|
||||
</Route>
|
||||
|
||||
{/* Admin Routes */}
|
||||
<Route path="/admin" element={<AdminLayout />}>
|
||||
<Route index element={<AdminDashboard />} />
|
||||
<Route path="users" element={<AdminUsers />} />
|
||||
<Route path="models" element={<AdminModels />} />
|
||||
<Route path="settings" element={<AdminSiteSettings />} />
|
||||
<Route path="sms" element={<AdminSmsConfig />} />
|
||||
<Route path="payment" element={<AdminPayment />} />
|
||||
<Route path="services" element={<AdminServices />} />
|
||||
<Route path="membership" element={<AdminMembership />} />
|
||||
<Route path="ads" element={<AdminAds />} />
|
||||
<Route path="market" element={<AdminMarket />} />
|
||||
<Route path="logs" element={<AdminLogs />} />
|
||||
<Route path="skills" element={<AdminSkills />} />
|
||||
<Route path="agents" element={<AdminAgents />} />
|
||||
<Route path="code-exec" element={<AdminCodeExec />} />
|
||||
<Route path="distributions" element={<DistributionBuilder />} />
|
||||
<Route path="im-bots" element={<ImBots />} />
|
||||
<Route path="privacy" element={<PrivacySettings />} />
|
||||
<Route path="promotion" element={<Promotion />} />
|
||||
<Route path="achievement" element={<Achievement />} />
|
||||
<Route path="tenant" element={<Tenant />} />
|
||||
<Route path="billing" element={<Billing />} />
|
||||
<Route path="analytics" element={<Analytics />} />
|
||||
<Route path="knowledge" element={<AdminKnowledge />} />
|
||||
<Route path="workflow" element={<AdminWorkflow />} />
|
||||
<Route path="storage" element={<AdminStorage />} />
|
||||
<Route path="developer" element={<AdminDeveloper />} />
|
||||
<Route path="developer-tools" element={<AdminDeveloperTools />} />
|
||||
<Route path="automation" element={<AdminAutomation />} />
|
||||
<Route path="mcp" element={<AdminMcp />} />
|
||||
<Route path="apps" element={<AdminApps />} />
|
||||
<Route path="assets" element={<AssetCenter />} />
|
||||
<Route path="portal" element={<UserPortal />} />
|
||||
</Route>
|
||||
|
||||
{/* 公开前台首页:不受登录态拦截,供后台「访问前台」直接打开 */}
|
||||
<Route path="/home" element={<LandingPage />} />
|
||||
<Route path="*" element={<Navigate to="/" />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
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 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 pageFallback = <div className="loading-screen">加载中...</div>;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [user, setUser] = useState<User | null>(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 <div className="loading-screen">加载中...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, login, logout, isAdmin }}>
|
||||
<Suspense fallback={pageFallback}>
|
||||
<Routes>
|
||||
{/* Login */}
|
||||
<Route path="/login" element={
|
||||
user ? (isAdmin ? <Navigate to="/admin" /> : <Navigate to="/chat" />) : <LoginPage />
|
||||
} />
|
||||
|
||||
{/* Root - Landing for guests, redirect for authenticated users */}
|
||||
<Route path="/" element={
|
||||
user ? (isAdmin ? <Navigate to="/admin" /> : <Navigate to="/chat" />) : <LandingPage />
|
||||
} />
|
||||
|
||||
{/* Front Routes (authenticated only) */}
|
||||
<Route path="/" element={<FrontLayout />}>
|
||||
<Route path="chat" element={<ChatPage />} />
|
||||
<Route path="chat/:id" element={<ChatPage />} />
|
||||
<Route path="knowledge" element={<KnowledgePage />} />
|
||||
<Route path="market" element={<MarketPage />} />
|
||||
<Route path="token" element={<TokenDashboardPage />} />
|
||||
<Route path="pricing" element={<PricingPage />} />
|
||||
<Route path="profile" element={<ProfilePage />} />
|
||||
<Route path="notifications" element={<NotificationPage />} />
|
||||
<Route path="assets" element={<AssetsPage />} />
|
||||
<Route path="assets/novel/:id" element={<NovelPreviewPage />} />
|
||||
<Route path="evolution" element={<EvolutionDashboard />} />
|
||||
<Route path="wechat-bot" element={<WechatBot />} />
|
||||
</Route>
|
||||
|
||||
{/* Admin Routes */}
|
||||
<Route path="/admin" element={<AdminLayout />}>
|
||||
<Route index element={<AdminDashboard />} />
|
||||
<Route path="users" element={<AdminUsers />} />
|
||||
<Route path="models" element={<AdminModels />} />
|
||||
<Route path="settings" element={<AdminSiteSettings />} />
|
||||
<Route path="sms" element={<AdminSmsConfig />} />
|
||||
<Route path="payment" element={<AdminPayment />} />
|
||||
<Route path="services" element={<AdminServices />} />
|
||||
<Route path="membership" element={<AdminMembership />} />
|
||||
<Route path="ads" element={<AdminAds />} />
|
||||
<Route path="market" element={<AdminMarket />} />
|
||||
<Route path="logs" element={<AdminLogs />} />
|
||||
<Route path="skills" element={<AdminSkills />} />
|
||||
<Route path="agents" element={<AdminAgents />} />
|
||||
<Route path="code-exec" element={<AdminCodeExec />} />
|
||||
<Route path="distributions" element={<DistributionBuilder />} />
|
||||
<Route path="im-bots" element={<ImBots />} />
|
||||
<Route path="privacy" element={<PrivacySettings />} />
|
||||
<Route path="promotion" element={<Promotion />} />
|
||||
<Route path="achievement" element={<Achievement />} />
|
||||
<Route path="tenant" element={<Tenant />} />
|
||||
<Route path="billing" element={<Billing />} />
|
||||
<Route path="analytics" element={<Analytics />} />
|
||||
<Route path="knowledge" element={<AdminKnowledge />} />
|
||||
<Route path="workflow" element={<AdminWorkflow />} />
|
||||
<Route path="storage" element={<AdminStorage />} />
|
||||
<Route path="developer" element={<AdminDeveloper />} />
|
||||
<Route path="developer-tools" element={<AdminDeveloperTools />} />
|
||||
<Route path="automation" element={<AdminAutomation />} />
|
||||
<Route path="mcp" element={<AdminMcp />} />
|
||||
<Route path="apps" element={<AdminApps />} />
|
||||
<Route path="assets" element={<AssetCenter />} />
|
||||
<Route path="portal" element={<UserPortal />} />
|
||||
</Route>
|
||||
|
||||
{/* 公开前台首页:不受登录态拦截,供后台「访问前台」直接打开 */}
|
||||
<Route path="/home" element={<LandingPage />} />
|
||||
<Route path="*" element={<Navigate to="/" />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
+1959
-2016
File diff suppressed because it is too large.
Load diff
@@ -1,183 +1,182 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import './FrontLayout.css';
|
||||
|
||||
const navGroups = [
|
||||
{
|
||||
title: '智能体',
|
||||
items: [
|
||||
{ path: '/chat', icon: 'chat', label: 'AI聊天' },
|
||||
{ path: '/knowledge', icon: 'knowledge', label: '知识库' },
|
||||
{ path: '/market', icon: 'market', label: '技能市场' },
|
||||
{ path: '/evolution', icon: 'evolution', label: 'AI自进化' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'IM Bot',
|
||||
items: [
|
||||
{ path: '/channels', icon: 'channel', label: '频道管理' },
|
||||
{ path: '/wechat-bot', icon: 'wechat', label: '微信机器人' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '资源中心',
|
||||
items: [
|
||||
{ path: '/assets', icon: 'assets', label: '资产文件' },
|
||||
{ path: '/token', icon: 'token', label: 'Token驾驶舱' },
|
||||
{ path: '/pricing', icon: 'pricing', label: '付费中心' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const bottomItems = [
|
||||
{ path: '/profile', icon: 'profile', label: '个人中心' },
|
||||
{ path: '/notifications', icon: 'notify', label: '消息通知' },
|
||||
];
|
||||
|
||||
const iconPaths: Record<string, JSX.Element> = {
|
||||
chat: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>,
|
||||
channel: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>,
|
||||
knowledge: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>,
|
||||
market: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.2"/><rect x="14" y="3" width="7" height="7" rx="1.2"/><rect x="3" y="14" width="7" height="7" rx="1.2"/><rect x="14" y="14" width="7" height="7" rx="1.2"/></svg>,
|
||||
apps: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="9" height="9" rx="2"/><rect x="13" y="2" width="9" height="9" rx="2"/><rect x="2" y="13" width="9" height="9" rx="2"/><rect x="13" y="13" width="5" height="5" rx="1.5"/><path d="M18 18v3"/><path d="M19.5 16.5v6"/></svg>,
|
||||
token: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>,
|
||||
pricing: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 18V6"/></svg>,
|
||||
profile: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>,
|
||||
notify: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>,
|
||||
assets: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>,
|
||||
evolution: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>,
|
||||
wechat: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/><circle cx="9" cy="11" r="1"/><circle cx="15" cy="11" r="1"/></svg>,
|
||||
};
|
||||
|
||||
const FrontLayout: React.FC = () => {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
if (!user) {
|
||||
navigate('/login');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="front-layout">
|
||||
{/* Sidebar */}
|
||||
<aside className={`front-sidebar ${collapsed ? 'collapsed' : ''}`}>
|
||||
{/* Header */}
|
||||
<div className="sidebar-header">
|
||||
<div className="sidebar-logo">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="flGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#5B5FED"/>
|
||||
<stop offset="100%" stopColor="#4A4ECC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="8" fill="url(#flGrad)"/>
|
||||
<path d="M8 20V12l4 4 4-4v8" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
|
||||
<path d="M6 22h20" stroke="white" strokeWidth="1.5" strokeLinecap="round" opacity="0.5"/>
|
||||
</svg>
|
||||
{!collapsed && <span className="sidebar-brand">七虎AI</span>}
|
||||
</div>
|
||||
<button className="collapse-btn" onClick={() => setCollapsed(!collapsed)}>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
{collapsed ? <><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></> : <><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></>}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="sidebar-nav">
|
||||
<NavLink to="/" className={({ isActive }) => `nav-item nav-home-link`} end>
|
||||
<span className="nav-item-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
</span>
|
||||
{!collapsed && <span className="nav-item-label">返回首页</span>}
|
||||
</NavLink>
|
||||
{navGroups.map(group => (
|
||||
<div className="nav-section" key={group.title}>
|
||||
{!collapsed && <div className="nav-group-title">{group.title}</div>}
|
||||
{group.items.map(item => (
|
||||
<NavLink
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
title={collapsed ? item.label : undefined}
|
||||
>
|
||||
<span className="nav-icon">{iconPaths[item.icon]}</span>
|
||||
{!collapsed && <span className="nav-label">{item.label}</span>}
|
||||
{!collapsed && <span className="nav-active-dot" />}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div className="nav-section nav-bottom">
|
||||
{bottomItems.map(item => (
|
||||
<NavLink
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
title={collapsed ? item.label : undefined}
|
||||
>
|
||||
<span className="nav-icon">{iconPaths[item.icon]}</span>
|
||||
{!collapsed && <span className="nav-label">{item.label}</span>}
|
||||
{!collapsed && <span className="nav-active-dot" />}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="sidebar-footer">
|
||||
<div className="user-info-mini">
|
||||
<div className="user-avatar-sm">
|
||||
{user.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="user-details">
|
||||
<span className="user-name">{user.username}</span>
|
||||
<span className="user-plan">{user.membership?.planName || '免费版'}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{user.role === 'admin' && (
|
||||
<NavLink
|
||||
to="/admin"
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
title="管理后台"
|
||||
>
|
||||
<span className="nav-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
</svg>
|
||||
</span>
|
||||
{!collapsed && <span className="nav-label">管理后台</span>}
|
||||
</NavLink>
|
||||
)}
|
||||
<button
|
||||
className="nav-item logout-btn"
|
||||
onClick={() => { logout(); navigate('/login'); }}
|
||||
title="退出登录"
|
||||
>
|
||||
<span className="nav-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
||||
<polyline points="16 17 21 12 16 7"/>
|
||||
<line x1="21" y1="12" x2="9" y2="12"/>
|
||||
</svg>
|
||||
</span>
|
||||
{!collapsed && <span className="nav-label">退出登录</span>}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="front-main">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FrontLayout;
|
||||
import React, { useState } from 'react';
|
||||
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import './FrontLayout.css';
|
||||
|
||||
const navGroups = [
|
||||
{
|
||||
title: '智能体',
|
||||
items: [
|
||||
{ path: '/chat', icon: 'chat', label: 'AI聊天' },
|
||||
{ path: '/knowledge', icon: 'knowledge', label: '知识库' },
|
||||
{ path: '/market', icon: 'market', label: '技能市场' },
|
||||
{ path: '/evolution', icon: 'evolution', label: 'AI自进化' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'IM Bot',
|
||||
items: [
|
||||
{ path: '/wechat-bot', icon: 'wechat', label: '微信机器人' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '资源中心',
|
||||
items: [
|
||||
{ path: '/assets', icon: 'assets', label: '资产文件' },
|
||||
{ path: '/token', icon: 'token', label: 'Token驾驶舱' },
|
||||
{ path: '/pricing', icon: 'pricing', label: '付费中心' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const bottomItems = [
|
||||
{ path: '/profile', icon: 'profile', label: '个人中心' },
|
||||
{ path: '/notifications', icon: 'notify', label: '消息通知' },
|
||||
];
|
||||
|
||||
const iconPaths: Record<string, JSX.Element> = {
|
||||
chat: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>,
|
||||
channel: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>,
|
||||
knowledge: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>,
|
||||
market: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.2"/><rect x="14" y="3" width="7" height="7" rx="1.2"/><rect x="3" y="14" width="7" height="7" rx="1.2"/><rect x="14" y="14" width="7" height="7" rx="1.2"/></svg>,
|
||||
apps: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="9" height="9" rx="2"/><rect x="13" y="2" width="9" height="9" rx="2"/><rect x="2" y="13" width="9" height="9" rx="2"/><rect x="13" y="13" width="5" height="5" rx="1.5"/><path d="M18 18v3"/><path d="M19.5 16.5v6"/></svg>,
|
||||
token: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>,
|
||||
pricing: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 18V6"/></svg>,
|
||||
profile: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>,
|
||||
notify: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>,
|
||||
assets: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>,
|
||||
evolution: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>,
|
||||
wechat: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/><circle cx="9" cy="11" r="1"/><circle cx="15" cy="11" r="1"/></svg>,
|
||||
};
|
||||
|
||||
const FrontLayout: React.FC = () => {
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
if (!user) {
|
||||
navigate('/login');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="front-layout">
|
||||
{/* Sidebar */}
|
||||
<aside className={`front-sidebar ${collapsed ? 'collapsed' : ''}`}>
|
||||
{/* Header */}
|
||||
<div className="sidebar-header">
|
||||
<div className="sidebar-logo">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="flGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#5B5FED"/>
|
||||
<stop offset="100%" stopColor="#4A4ECC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="8" fill="url(#flGrad)"/>
|
||||
<path d="M8 20V12l4 4 4-4v8" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
|
||||
<path d="M6 22h20" stroke="white" strokeWidth="1.5" strokeLinecap="round" opacity="0.5"/>
|
||||
</svg>
|
||||
{!collapsed && <span className="sidebar-brand">七虎AI</span>}
|
||||
</div>
|
||||
<button className="collapse-btn" onClick={() => setCollapsed(!collapsed)}>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
{collapsed ? <><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></> : <><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></>}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="sidebar-nav">
|
||||
<NavLink to="/" className={({ isActive }) => `nav-item nav-home-link`} end>
|
||||
<span className="nav-item-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
</span>
|
||||
{!collapsed && <span className="nav-item-label">返回首页</span>}
|
||||
</NavLink>
|
||||
{navGroups.map(group => (
|
||||
<div className="nav-section" key={group.title}>
|
||||
{!collapsed && <div className="nav-group-title">{group.title}</div>}
|
||||
{group.items.map(item => (
|
||||
<NavLink
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
title={collapsed ? item.label : undefined}
|
||||
>
|
||||
<span className="nav-icon">{iconPaths[item.icon]}</span>
|
||||
{!collapsed && <span className="nav-label">{item.label}</span>}
|
||||
{!collapsed && <span className="nav-active-dot" />}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div className="nav-section nav-bottom">
|
||||
{bottomItems.map(item => (
|
||||
<NavLink
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
title={collapsed ? item.label : undefined}
|
||||
>
|
||||
<span className="nav-icon">{iconPaths[item.icon]}</span>
|
||||
{!collapsed && <span className="nav-label">{item.label}</span>}
|
||||
{!collapsed && <span className="nav-active-dot" />}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="sidebar-footer">
|
||||
<div className="user-info-mini">
|
||||
<div className="user-avatar-sm">
|
||||
{user.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="user-details">
|
||||
<span className="user-name">{user.username}</span>
|
||||
<span className="user-plan">{user.membership?.planName || '免费版'}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{user.role === 'admin' && (
|
||||
<NavLink
|
||||
to="/admin"
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
title="管理后台"
|
||||
>
|
||||
<span className="nav-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
</svg>
|
||||
</span>
|
||||
{!collapsed && <span className="nav-label">管理后台</span>}
|
||||
</NavLink>
|
||||
)}
|
||||
<button
|
||||
className="nav-item logout-btn"
|
||||
onClick={() => { logout(); navigate('/login'); }}
|
||||
title="退出登录"
|
||||
>
|
||||
<span className="nav-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
||||
<polyline points="16 17 21 12 16 7"/>
|
||||
<line x1="21" y1="12" x2="9" y2="12"/>
|
||||
</svg>
|
||||
</span>
|
||||
{!collapsed && <span className="nav-label">退出登录</span>}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="front-main">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FrontLayout;
|
||||
@@ -2,7 +2,7 @@
|
||||
// 分离的Mock数据层,便于后续接入真实后端时替换
|
||||
|
||||
import type {
|
||||
User, Conversation, Message, Channel, KnowledgeBase,
|
||||
User, Conversation, Message, KnowledgeBase,
|
||||
KnowledgeDocument, MarketApp, AppReview, TokenUsageLog,
|
||||
TokenBalance, Order, MembershipPlan, Coupon, UserCoupon,
|
||||
Notification, ModelProvider, AIModel, SiteSettings,
|
||||
@@ -97,13 +97,7 @@ export const mockMessages: Record<string, Message[]> = {
|
||||
],
|
||||
};
|
||||
|
||||
// ==================== 频道数据 (IM账户绑定) ====================
|
||||
export const mockChannels: Channel[] = [
|
||||
{ id: 'ch1', userId: 'u1', platform: 'feishu', name: '飞书AI助手', accountName: 'zangsan@company.feishu.cn', agentId: 'a1', knowledgeBaseId: 'kb1', status: 'connected', lastActive: '2026-05-26T09:30:00Z', boundModel: 'DeepSeek-V3', features: ['AI对话', '文档问答', '任务提醒', '会议纪要'], createdAt: '2026-04-01T08:00:00Z' },
|
||||
{ id: 'ch2', userId: 'u1', platform: 'dingtalk', name: '钉钉AI助理', accountName: '张经理', agentId: 'a2', knowledgeBaseId: 'kb2', status: 'connected', lastActive: '2026-05-26T08:15:00Z', boundModel: 'GPT-4o', features: ['AI对话', '审批辅助', '日程管理'], createdAt: '2026-04-15T10:00:00Z' },
|
||||
{ id: 'ch3', userId: 'u1', platform: 'wechat', name: '微信AI伙伴', accountName: 'wxid_abc123', agentId: undefined, knowledgeBaseId: undefined, status: 'pending', lastActive: undefined, boundModel: 'DeepSeek-V3', features: ['AI对话', '内容创作'], createdAt: '2026-05-01T09:00:00Z' },
|
||||
{ id: 'ch4', userId: 'u1', platform: 'telegram', name: 'Telegram Bot', accountName: '@aiflow_bot', agentId: undefined, knowledgeBaseId: undefined, status: 'disconnected', lastActive: '2026-05-10T14:00:00Z', boundModel: 'Qwen-Max', features: ['AI对话'], createdAt: '2026-05-10T14:00:00Z' },
|
||||
];
|
||||
|
||||
|
||||
// ==================== 知识库数据 ====================
|
||||
export const mockKnowledgeBases: KnowledgeBase[] = [
|
||||
|
||||
@@ -33,7 +33,7 @@ const statusColors: Record<string, string> = {
|
||||
disconnected: '#6b7280',
|
||||
};
|
||||
|
||||
type Tab = 'dashboard' | 'channels' | 'routes' | 'langbot' | 'messages' | 'setup';
|
||||
type Tab = 'dashboard' | 'channels' | 'routes' | 'messages' | 'setup';
|
||||
|
||||
const ImBots: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('dashboard');
|
||||
@@ -46,7 +46,6 @@ const ImBots: React.FC = () => {
|
||||
const [filterPlatform, setFilterPlatform] = useState('');
|
||||
const [filterStatus, setFilterStatus] = useState('');
|
||||
const [routes, setRoutes] = useState<any[]>([]);
|
||||
const [langbotConfig, setLangbotConfig] = useState<any[]>([]);
|
||||
const [messages, setMessages] = useState<any[]>([]);
|
||||
const [messagesTotal, setMessagesTotal] = useState(0);
|
||||
const [messagesPage, setMessagesPage] = useState(1);
|
||||
@@ -77,18 +76,6 @@ const ImBots: React.FC = () => {
|
||||
credentials: '',
|
||||
});
|
||||
|
||||
// LangBot form state
|
||||
const [langbotForm, setLangbotForm] = useState({
|
||||
id: 1,
|
||||
gateway_url: '',
|
||||
api_key: '',
|
||||
webhook_secret: '',
|
||||
bot_uuid: '',
|
||||
callback_url: '',
|
||||
sync_channels: 0,
|
||||
status: 'inactive',
|
||||
});
|
||||
|
||||
const showToast = (msg: string, type: 'success' | 'error' = 'success') => {
|
||||
setToast({ msg, type });
|
||||
setTimeout(() => setToast(null), 3000);
|
||||
@@ -155,22 +142,6 @@ const ImBots: React.FC = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load LangBot config
|
||||
const loadLangbot = useCallback(async () => {
|
||||
try {
|
||||
const res = await adminAPI.getLangbotConfig();
|
||||
if ((res as any)?.success) {
|
||||
const configs = res.data || [];
|
||||
setLangbotConfig(configs);
|
||||
if (configs.length > 0) {
|
||||
setLangbotForm({ ...langbotForm, ...configs[0] });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Failed to load LangBot config', 'error');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load messages
|
||||
const loadMessages = useCallback(async () => {
|
||||
try {
|
||||
@@ -211,8 +182,6 @@ const ImBots: React.FC = () => {
|
||||
} else if (activeTab === 'routes') {
|
||||
loadRoutes();
|
||||
loadChannels();
|
||||
} else if (activeTab === 'langbot') {
|
||||
loadLangbot();
|
||||
} else if (activeTab === 'messages') {
|
||||
loadMessages();
|
||||
}
|
||||
@@ -348,51 +317,7 @@ const ImBots: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle LangBot config save
|
||||
const handleSaveLangbot = async () => {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
config_id: String(langbotForm.id || 1),
|
||||
gateway_url: langbotForm.gateway_url,
|
||||
status: langbotForm.status,
|
||||
sync_channels: String(langbotForm.sync_channels),
|
||||
});
|
||||
if (langbotForm.api_key) params.append('api_key', langbotForm.api_key);
|
||||
if (langbotForm.webhook_secret) params.append('webhook_secret', langbotForm.webhook_secret);
|
||||
if (langbotForm.bot_uuid) params.append('bot_uuid', langbotForm.bot_uuid);
|
||||
if (langbotForm.callback_url) params.append('callback_url', langbotForm.callback_url);
|
||||
await adminAPI.updateLangbotConfig('?' + params.toString());
|
||||
showToast('LangBot config saved');
|
||||
loadLangbot();
|
||||
} catch (e) {
|
||||
showToast('Failed to save LangBot config', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle LangBot test
|
||||
const handleTestLangbot = async () => {
|
||||
if (!langbotForm.gateway_url) {
|
||||
showToast('Please enter gateway URL', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
gateway_url: langbotForm.gateway_url,
|
||||
});
|
||||
if (langbotForm.api_key) params.append('api_key', langbotForm.api_key);
|
||||
const res = await adminAPI.testLangbot('?' + params.toString());
|
||||
if ((res as any)?.success) {
|
||||
const data = res.data;
|
||||
if (data.reachable) {
|
||||
showToast(`LangBot reachable! Status: ${data.status_code}`);
|
||||
} else {
|
||||
showToast(`LangBot unreachable: ${data.error}`, 'error');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Test failed', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Copy to clipboard
|
||||
const copyToClipboard = (text: string) => {
|
||||
@@ -411,7 +336,6 @@ const ImBots: React.FC = () => {
|
||||
{ key: 'dashboard', label: '仪表盘', icon: 'M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z' },
|
||||
{ key: 'channels', label: '频道管理', icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z' },
|
||||
{ key: 'routes', label: '消息路由', icon: 'M3.5 18.5l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 17l1.5 1.5z' },
|
||||
{ key: 'langbot', label: 'LangBot 网关', icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z' },
|
||||
{ key: 'messages', label: '消息日志', icon: 'M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z' },
|
||||
{ key: 'setup', label: '快速接入', icon: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z' },
|
||||
];
|
||||
@@ -422,7 +346,7 @@ const ImBots: React.FC = () => {
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||
<div>
|
||||
<h1 style={{ fontSize: '24px', fontWeight: 700, margin: 0 }}>IM 机器人管理</h1>
|
||||
<p style={{ color: '#888', fontSize: '14px', margin: '4px 0 0 0' }}>管理即时通讯平台接入、消息路由和 LangBot 网关</p>
|
||||
<p style={{ color: '#888', fontSize: '14px', margin: '4px 0 0 0' }}>管理即时通讯平台接入与消息路由</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
{stats && (
|
||||
@@ -430,7 +354,6 @@ const ImBots: React.FC = () => {
|
||||
<StatBadge label="频道" value={stats.total_channels} color="#3370FF" />
|
||||
<StatBadge label="在线" value={stats.active_channels} color="#10b981" />
|
||||
<StatBadge label="今日消息" value={stats.today_messages_in + stats.today_messages_out} color="#f59e0b" />
|
||||
<StatBadge label="LangBot" value={stats.langbot_status === 'active' ? 'ON' : 'OFF'} color={stats.langbot_status === 'active' ? '#10b981' : '#6b7280'} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -744,105 +667,6 @@ const ImBots: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* LangBot Tab */}
|
||||
{activeTab === 'langbot' && (
|
||||
<div style={{ maxWidth: '800px' }}>
|
||||
<div style={{ background: '#1a1d27', borderRadius: '12px', padding: '24px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '16px' }}>LangBot 网关配置</h3>
|
||||
<span style={{
|
||||
padding: '4px 12px', borderRadius: '4px', fontSize: '12px',
|
||||
background: langbotForm.status === 'active' ? '#10b98120' : '#6b728020',
|
||||
color: langbotForm.status === 'active' ? '#10b981' : '#6b7280',
|
||||
}}>
|
||||
{langbotForm.status === 'active' ? '已启用' : '未启用'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
|
||||
<FormField label="网关 URL" >
|
||||
<input
|
||||
value={langbotForm.gateway_url}
|
||||
onChange={(e) => setLangbotForm({ ...langbotForm, gateway_url: e.target.value })}
|
||||
placeholder="http://localhost:5300"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Bot UUID">
|
||||
<input
|
||||
value={langbotForm.bot_uuid || ''}
|
||||
onChange={(e) => setLangbotForm({ ...langbotForm, bot_uuid: e.target.value })}
|
||||
placeholder="LangBot HTTP Bot UUID"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="API Key">
|
||||
<input
|
||||
type="password"
|
||||
value={langbotForm.api_key || ''}
|
||||
onChange={(e) => setLangbotForm({ ...langbotForm, api_key: e.target.value })}
|
||||
placeholder="LangBot REST API Key"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Webhook Secret">
|
||||
<input
|
||||
type="password"
|
||||
value={langbotForm.webhook_secret || ''}
|
||||
onChange={(e) => setLangbotForm({ ...langbotForm, webhook_secret: e.target.value })}
|
||||
placeholder="HMAC-SHA256 签名密钥"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="回调 URL">
|
||||
<input
|
||||
value={langbotForm.callback_url || ''}
|
||||
onChange={(e) => setLangbotForm({ ...langbotForm, callback_url: e.target.value })}
|
||||
placeholder="http://210.16.161.218:12888/app/api/channels/webhook/langbot"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="自动同步频道">
|
||||
<select
|
||||
value={langbotForm.sync_channels}
|
||||
onChange={(e) => setLangbotForm({ ...langbotForm, sync_channels: Number(e.target.value) })}
|
||||
style={selectStyle}
|
||||
>
|
||||
<option value={0}>否</option>
|
||||
<option value={1}>是</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label="状态">
|
||||
<select
|
||||
value={langbotForm.status}
|
||||
onChange={(e) => setLangbotForm({ ...langbotForm, status: e.target.value })}
|
||||
style={selectStyle}
|
||||
>
|
||||
<option value="inactive">未启用</option>
|
||||
<option value="active">启用</option>
|
||||
</select>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: '12px', marginTop: '24px' }}>
|
||||
<button onClick={handleSaveLangbot} style={primaryButtonStyle}>保存配置</button>
|
||||
<button onClick={handleTestLangbot} style={secondaryButtonStyle}>测试连接</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* LangBot Info */}
|
||||
<div style={{ background: '#1a1d27', borderRadius: '12px', padding: '24px', marginTop: '16px' }}>
|
||||
<h4 style={{ marginTop: 0, fontSize: '14px', color: '#888' }}>LangBot 集成说明</h4>
|
||||
<ul style={{ fontSize: '13px', color: '#aaa', lineHeight: '1.8', paddingLeft: '20px' }}>
|
||||
<li>LangBot 是开源的 LLM 原生 IM 机器人平台,支持飞书、微信、钉钉等</li>
|
||||
<li>配置 LangBot 作为外部网关后,IM 消息将通过 LangBot 转发到本平台</li>
|
||||
<li>在 LangBot 中创建 HTTP Bot 适配器,将 Webhook URL 指向本平台</li>
|
||||
<li>API Key 在 LangBot 的 config.yaml 中通过 api.global_api_key 配置</li>
|
||||
<li>Webhook Secret 用于 HMAC-SHA256 双向签名验证</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Messages Tab */}
|
||||
{activeTab === 'messages' && (
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -68,7 +68,7 @@ const features = [
|
||||
{ d: <><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></>,
|
||||
title: 'Token 驾驶舱', desc: '实时监控 Token 消耗,费用透明可控,每一笔都有据可查。' },
|
||||
{ d: <><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></>,
|
||||
title: 'IM 频道绑定', desc: '微信 / 企微 / 飞书 / QQ 多平台绑定,AI 助手融入日常工作流。' },
|
||||
title: 'AI 微信机器人', desc: '个人微信 AI 助手直接对接,消息自动回复,AI 融入你的日常沟通。' },
|
||||
{ d: <><polyline points="16 3 21 3 21 8" /><line x1="4" y1="20" x2="21" y2="3" /><polyline points="21 16 21 21 16 21" /><line x1="15" y1="15" x2="21" y2="21" /><line x1="4" y1="4" x2="9" y2="9" /></>,
|
||||
title: '自动化任务编排', desc: '关键词触发、定时执行等自动化任务,让 AI 按你的节奏工作。' },
|
||||
];
|
||||
|
||||
@@ -63,24 +63,6 @@ export interface Message {
|
||||
used_skills?: string[];
|
||||
}
|
||||
|
||||
// 频道
|
||||
// IM账户绑定频道
|
||||
export type IMPlatform = 'feishu' | 'wechat' | 'dingtalk' | 'whatsapp' | 'telegram' | 'slack';
|
||||
|
||||
export interface Channel {
|
||||
id: string;
|
||||
userId: string;
|
||||
platform: IMPlatform;
|
||||
name: string;
|
||||
accountName: string;
|
||||
agentId?: string;
|
||||
knowledgeBaseId?: string;
|
||||
status: 'connected' | 'disconnected' | 'pending';
|
||||
lastActive?: string;
|
||||
boundModel: string;
|
||||
features: string[];
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// 知识库
|
||||
export interface KnowledgeBase {
|
||||
|
||||
Reference in new issue
Block a user