diff --git a/CLAUDE.md b/CLAUDE.md index 5d85d784..b0cfcce6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -199,6 +199,30 @@ frontend/ ├── src/ │ ├── api/ # 接口封装 │ ├── pages/ # 页面级组件 +│ │ ├── ai-config/ # AI Config 页面模块目录(types/constants/components/sections) +│ │ ├── ai-steward/ # AI Steward 页面模块目录(types/constants/components/sections) +│ │ ├── charter-detail/ # Charter Detail 页面模块目录 +│ │ ├── contract-sync-board/ # Contract Sync Board 页面模块目录 +│ │ ├── dashboard/ # Dashboard 页面模块目录 +│ │ ├── feishu-config/ # Feishu Config 页面模块目录(helpers/components) +│ │ ├── inbox/ # Inbox 页面模块目录(helpers/components) +│ │ ├── knowledge-graph-context-board/ # KG Context Board 页面模块目录 +│ │ ├── login/ # Login 页面模块目录 +│ │ ├── model-routing-config/ # Model Routing Config 页面模块目录 +│ │ ├── project-board/ # Project Board 页面模块目录(helpers/components) +│ │ ├── projects/ # Projects 页面模块目录(helpers/components) +│ │ ├── recipes/ # Recipes 页面模块目录(helpers/components) +│ │ ├── scheduler-center/ # Scheduler Center 页面模块目录 +│ │ ├── skill-tasks/ # Skill Tasks 页面模块目录 +│ │ ├── skills/ # Skills 页面模块目录 +│ │ ├── task-brief-center/ # Task Brief Center 页面模块目录 +│ │ ├── task-brief-runs/ # Task Brief Runs 页面模块目录 +│ │ ├── task-brief-template/ # Task Brief Template 页面模块目录 +│ │ ├── user-profile/ # User Profile 页面模块目录 +│ │ ├── user-skills/ # User Skills 页面模块目录 +│ │ ├── users/ # Users 页面模块目录(helpers/components) +│ │ ├── workload-detail/ # Workload Detail 页面模块目录 +│ │ └── workloads/ # Workloads 页面模块目录 │ ├── components/ │ │ ├── ui/ # 基础 UI 组件 │ │ ├── inbox/ # 消息台 Feed 列表组件 @@ -222,12 +246,44 @@ frontend/ - 新增:`frontend/src/api/modelRouting.ts`(模型路由 API 封装) - 新增:`frontend/src/api/knowledgeGraph.ts`(KG 能力汇总与双模型消费验证 API) - 更新:`frontend/src/api/aiSteward.ts`(移除 OpenClaw dispatch 封装,新增 LLM 引擎 CRUD / settings 封装) -- 新增:`frontend/src/pages/TaskBriefCenterPage.tsx`(增量任务包中心页面) -- 新增:`frontend/src/pages/TaskBriefTemplatePage.tsx`(任务包模板管理页面) -- 新增:`frontend/src/pages/TaskBriefRunsPage.tsx`(任务包生成记录页面) -- 新增:`frontend/src/pages/ContractSyncBoardPage.tsx`(契约同步看板页面) -- 新增:`frontend/src/pages/ModelRoutingConfigPage.tsx`(模型路由配置页面) -- 新增:`frontend/src/pages/KnowledgeGraphContextBoardPage.tsx`(知识图谱与双模型上下文看板) +- 更新:`frontend/src/pages/CharterDetailPage.tsx`、`ContractSyncBoardPage.tsx`、`DashboardPage.tsx`、`KnowledgeGraphContextBoardPage.tsx`、`LoginPage.tsx`、`ModelRoutingConfigPage.tsx`、`SchedulerCenterPage.tsx`、`SkillTasksPage.tsx`、`SkillsPage.tsx`、`TaskBriefCenterPage.tsx`、`TaskBriefRunsPage.tsx`、`TaskBriefTemplatePage.tsx`、`UserProfilePage.tsx`、`UserSkillsPage.tsx`、`WorkloadDetailPage.tsx`、`WorkloadsPage.tsx`(统一降级为轻量入口,转发到 `pages/*/` 子目录) +- 新增:`frontend/src/pages/charter-detail/CharterDetailPage.tsx`、`contract-sync-board/ContractSyncBoardPage.tsx`、`dashboard/DashboardPage.tsx`、`knowledge-graph-context-board/KnowledgeGraphContextBoardPage.tsx`、`login/LoginPage.tsx`、`model-routing-config/ModelRoutingConfigPage.tsx`(剩余核心页面目录化收敛) +- 新增:`frontend/src/pages/scheduler-center/SchedulerCenterPage.tsx`、`skill-tasks/SkillTasksPage.tsx`、`skills/SkillsPage.tsx`、`task-brief-center/TaskBriefCenterPage.tsx`、`task-brief-runs/TaskBriefRunsPage.tsx`、`task-brief-template/TaskBriefTemplatePage.tsx`(任务/调度/技能页面目录化收敛) +- 新增:`frontend/src/pages/user-profile/UserProfilePage.tsx`、`user-skills/UserSkillsPage.tsx`、`workload-detail/WorkloadDetailPage.tsx`、`workloads/WorkloadsPage.tsx`(用户与负载页面目录化收敛) +- 更新:`frontend/src/pages/InboxPage.tsx`(降级为轻量入口,转发到 `pages/inbox/InboxPage`) +- 新增:`frontend/src/pages/inbox/helpers.ts`(Inbox 页面 tab 过滤、排序、project-scope 判断 helper) +- 新增:`frontend/src/pages/inbox/components/InboxFiltersPanel.tsx`(Inbox 页面顶部筛选/搜索/tab 面板拆分) +- 新增:`frontend/src/pages/inbox/InboxPage.tsx`(Inbox 页面编排层,主文件聚焦状态与动作流转) +- 更新:`frontend/src/pages/RecipesPage.tsx`(降级为轻量入口,转发到 `pages/recipes/RecipesPage`) +- 新增:`frontend/src/pages/recipes/helpers.ts`(Recipes 页面默认表单与分类常量) +- 新增:`frontend/src/pages/recipes/components/CreateRecipeDialog.tsx`、`AnalyzeRecipeDialog.tsx`(Recipes 页面手动创建/AI 识别弹窗拆分) +- 新增:`frontend/src/pages/recipes/RecipesPage.tsx`(Recipes 页面编排层,主文件聚焦列表筛选与状态) +- 更新:`frontend/src/pages/FeishuConfigPage.tsx`(降级为轻量入口,转发到 `pages/feishu-config/FeishuConfigPage`) +- 新增:`frontend/src/pages/feishu-config/helpers.ts`(Feishu Config 表单与状态类型、默认配置常量) +- 新增:`frontend/src/pages/feishu-config/components/SetupGuideCard.tsx`(Feishu Config 配置指南卡片拆分) +- 新增:`frontend/src/pages/feishu-config/FeishuConfigPage.tsx`(Feishu Config 页面编排层,主文件聚焦状态与交互) +- 更新:`frontend/src/pages/AIConfigPage.tsx`(降级为轻量入口,转发到 `pages/ai-config/AIConfigPage`) +- 新增:`frontend/src/pages/ai-config/types.ts`、`frontend/src/pages/ai-config/constants.ts`(AI Config 类型与常量集中管理) +- 新增:`frontend/src/pages/ai-config/components/GovernanceToolsCard.tsx`、`StatusBadge.tsx`、`PriorityBadge.tsx`(AI Config 复用展示组件) +- 新增:`frontend/src/pages/ai-config/sections/OverviewTab.tsx`、`ConfigureTab.tsx`、`KnowledgeTab.tsx`、`AdvancedTab.tsx`(AI Config 分阶段 UI 拆分) +- 新增:`frontend/src/pages/ai-config/AIConfigPage.tsx`(AI Config 页面编排层,主文件仅保留状态与事件编排) +- 更新:`frontend/src/pages/AIStewardPage.tsx`(降级为轻量入口,转发到 `pages/ai-steward/AIStewardPage`) +- 新增:`frontend/src/pages/ai-steward/types.ts`、`frontend/src/pages/ai-steward/constants.ts`(AI Steward 类型与常量集中管理) +- 新增:`frontend/src/pages/ai-steward/components/FeatureOverviewCards.tsx`(AI Steward 首页功能卡片组件) +- 新增:`frontend/src/pages/ai-steward/sections/OverviewTab.tsx`、`CreateProjectTab.tsx`、`SignalsTab.tsx`、`ConflictsTab.tsx`、`BlockersTab.tsx`、`BriefingTab.tsx`、`EngineTab.tsx`(AI Steward 分阶段 UI 拆分) +- 新增:`frontend/src/pages/ai-steward/AIStewardPage.tsx`(AI Steward 页面编排层,主文件聚焦状态与事件) +- 更新:`frontend/src/pages/ProjectBoardPage.tsx`(降级为轻量入口,转发到 `pages/project-board/ProjectBoardPage`) +- 新增:`frontend/src/pages/project-board/helpers.ts`(Project Board 信号筛选状态与 PMO 工作项文本归一 helper) +- 新增:`frontend/src/pages/project-board/components/CreateCharterDialog.tsx`、`ProjectSettingsDialog.tsx`(Project Board 大体量弹窗组件拆分) +- 新增:`frontend/src/pages/project-board/ProjectBoardPage.tsx`(Project Board 页面编排层) +- 更新:`frontend/src/pages/ProjectsPage.tsx`(降级为轻量入口,转发到 `pages/projects/ProjectsPage`) +- 新增:`frontend/src/pages/projects/helpers.ts`(Projects 页面链路阶段、状态样式、权限与错误解析 helper) +- 新增:`frontend/src/pages/projects/components/ProjectQueryDialog.tsx`、`DeleteProjectDialog.tsx`(Projects 页面查询/删除弹窗拆分) +- 新增:`frontend/src/pages/projects/ProjectsPage.tsx`(Projects 页面编排层,主文件聚焦状态与事件) +- 更新:`frontend/src/pages/UsersPage.tsx`(降级为轻量入口,转发到 `pages/users/UsersPage`) +- 新增:`frontend/src/pages/users/helpers.ts`(Users 页面角色/密码/CSV 预览 helper 聚合) +- 新增:`frontend/src/pages/users/components/DeleteUserDialog.tsx`、`ResetPasswordDialog.tsx`、`ImportUsersDialog.tsx`、`BulkAssignDialog.tsx`(Users 页面大体量弹窗组件拆分) +- 新增:`frontend/src/pages/users/UsersPage.tsx`(Users 页面编排层,主文件聚焦状态与事件) - 更新:`frontend/src/pages/AIStewardPage.tsx`(移除 OpenClaw 指挥调度入口,新增本地 LLM 开关与严格失败边界说明) - 移除:`frontend/src/components/ai-steward/OpenClawCommanderPanel.tsx`(OpenClaw 指挥调度面板) diff --git a/frontend/src/pages/AIConfigPage.tsx b/frontend/src/pages/AIConfigPage.tsx index ad596734..194a858b 100644 --- a/frontend/src/pages/AIConfigPage.tsx +++ b/frontend/src/pages/AIConfigPage.tsx @@ -1,1721 +1 @@ -import { useState, useEffect } from "react"; -import { Link, useNavigate } from "react-router-dom"; -import { - Settings as SettingsIcon, Loader2, CheckCircle2, AlertCircle, AlertTriangle, - MessageSquare, Activity, Github, Globe, - ChevronRight, Info, Sparkles, Building2, Upload, Link2, RefreshCw, Trash2, - Files, LayoutTemplate, History, ShieldCheck, Settings2, Boxes -} from "lucide-react"; -import { apiBaseURL, getAuthToken } from "@/lib/api"; -import { - Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Badge } from "@/components/ui/badge"; -import { Switch } from "@/components/ui/switch"; -import PromptRoleSettings from "@/components/ai-steward/PromptRoleSettings"; -import { - FEISHU_EVENT_MODE_OPTIONS, - type FeishuEventMode, - getFeishuEventModeLabel, -} from "@/lib/feishuEventMode"; -import { formatBeijingDateTime } from "@/lib/datetime"; - -// ==================== Types ==================== - -interface IntegrationSource { - id: string; - name: string; - description: string; - icon: any; - status: "available" | "configured" | "needs_setup" | "unavailable"; - priority: "high" | "medium" | "low"; - estimated_benefit: string; - setup_time: string; - auto_detected: boolean; - config_fields?: ConfigField[]; - setup_guide?: string[]; -} - -interface ConfigField { - key: string; - label: string; - type: "text" | "password" | "url" | "toggle"; - placeholder?: string; - required: boolean; - description?: string; -} - -interface AIConfigSuggestion { - recommended_sources: string[]; - reasoning: string; - estimated_impact: string; -} - -interface ConfigStatus { - feishu: { - configured: boolean; - app_id?: string; - event_mode?: FeishuEventMode; - }; - wecom: { - configured: boolean; - corp_id?: string; - agent_id?: string; - token_preview?: string; - encoding_aes_key_preview?: string; - }; - github: { configured: boolean; repos?: string[] }; - browser: { installed: boolean }; - hn_monitor: { active: boolean; last_run?: string }; -} - -interface KnowledgeDoc { - id: number; - title: string; - file_type: string; - source_type: string; - file_url?: string | null; - status: "processing" | "complete" | "failed"; - created_at?: string | null; - last_fetched_at?: string | null; - error_message?: string | null; -} - -export default function AIConfigPage() { - const navigate = useNavigate(); - - const [, setLoading] = useState(true); - const [checking, setChecking] = useState(null); - const [status, setStatus] = useState(null); - const [suggestion, setSuggestion] = useState(null); - const [activeTab, setActiveTab] = useState("overview"); - const [scrollToSection, setScrollToSection] = useState(null); - - // Feishu config form - const [feishuConfig, setFeishuConfig] = useState({ - app_id: "", - app_secret: "", - event_mode: "long_connection" as FeishuEventMode - }); - - // WeChat Work config form - const [wecomConfig, setWecomConfig] = useState({ - corp_id: "", - agent_id: "", - app_secret: "", - token: "", - encoding_aes_key: "" - }); - - // GitHub config form - const [githubConfig, setGithubConfig] = useState({ - repo_url: "", - webhook_url: "", - access_token: "" - }); - - // Monitor settings - const [monitorSettings, setMonitorSettings] = useState({ - hn_enabled: false, - hn_interval: 30, - browser_enabled: false - }); - - const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null); - const [wecomCopyTip, setWecomCopyTip] = useState(""); - const [knowledgeDocs, setKnowledgeDocs] = useState([]); - const [knowledgeFile, setKnowledgeFile] = useState(null); - const [knowledgeURL, setKnowledgeURL] = useState(""); - const [knowledgeURLTitle, setKnowledgeURLTitle] = useState(""); - - // Guide expansion state - const [showFeishuGuide, setShowFeishuGuide] = useState(false); - const [showWecomGuide, setShowWecomGuide] = useState(false); - const [editingFeishu, setEditingFeishu] = useState(false); - const [editingWecom, setEditingWecom] = useState(false); - const [editingGithub, setEditingGithub] = useState(false); - - const governanceTools = [ - { - id: "brief-center", - name: "增量任务包中心", - description: "生成与查看任务包卡片", - to: "/ai-config/task-brief-center", - icon: Files, - }, - { - id: "brief-template", - name: "任务包模板管理", - description: "统一维护模板与规范", - to: "/ai-config/task-brief-templates", - icon: LayoutTemplate, - }, - { - id: "brief-runs", - name: "任务包生成记录", - description: "查看生成历史与状态", - to: "/ai-config/task-brief-runs", - icon: History, - }, - { - id: "contract-board", - name: "契约同步看板", - description: "检查契约文档同步状态", - to: "/ai-config/contract-sync-board", - icon: ShieldCheck, - }, - { - id: "model-routing", - name: "模型路由配置", - description: "动态切换函数模型路由", - to: "/ai-config/model-routing-config", - icon: Settings2, - }, - { - id: "knowledge-graph-board", - name: "知识图谱上下文看板", - description: "查看 KG 汇总与双模型上下文消费验证", - to: "/ai-config/knowledge-graph-board", - icon: Boxes, - }, - ]; - - // ==================== Integration Sources ==================== - - const integrationSources: IntegrationSource[] = [ - { - id: "feishu", - name: "飞书群聊", - description: "自动捕获群聊中的需求讨论,AI 分析价值", - icon: MessageSquare, - status: status?.feishu?.configured ? "configured" : "needs_setup", - priority: "high", - estimated_benefit: "每天自动捕获 5-10 个需求信号", - setup_time: "5 分钟", - auto_detected: true, - config_fields: [ - { key: "app_id", label: "应用编号", type: "text", placeholder: "cli_xxxxxxxxxxxxx", required: true }, - { key: "app_secret", label: "应用密钥", type: "password", placeholder: "应用密钥", required: true }, - ], - setup_guide: [ - "访问 https://open.feishu.cn/ 创建自建应用", - "在「凭证与基础信息」获取应用编号和应用密钥", - "飞书接入已统一为长连接模式,无需再配置回调地址", - "添加权限:im:message, im:message:group_at_msg, im:chat", - "订阅事件:接收消息 v1 (message.receive_v1)", - "保存后后端会自动重启飞书长连接" - ] - }, - { - id: "wecom", - name: "企业微信", - description: "自动捕获企业微信群消息,AI 分析需求价值", - icon: Building2, - status: status?.wecom?.configured ? "configured" : "needs_setup", - priority: "high", - estimated_benefit: "每天自动捕获 5-10 个需求信号", - setup_time: "5 分钟", - auto_detected: true, - config_fields: [ - { key: "corp_id", label: "企业编号", type: "text", placeholder: "wwxxxxxxxxxxxxxx", required: true }, - { key: "agent_id", label: "应用编号", type: "text", placeholder: "1000002", required: true }, - { key: "app_secret", label: "应用密钥", type: "password", placeholder: "应用密钥", required: true }, - { key: "token", label: "验证令牌", type: "text", placeholder: "回调验证令牌", required: true }, - { key: "encoding_aes_key", label: "消息加密密钥", type: "text", placeholder: "消息加密密钥(43位)", required: true } - ], - setup_guide: [ - "访问 https://work.weixin.qq.com/ 创建应用", - "在「应用管理」获取应用编号和应用密钥", - "在「我的企业」获取企业编号", - "在「应用管理」-「接收消息」设置验证令牌和消息加密密钥", - "添加权限:通讯录权限、消息权限", - "点击保存后,复制回调地址到企业微信配置" - ] - }, - { - id: "hn", - name: "技术资讯监控(HN)", - description: "定时抓取技术前沿,分析行业趋势", - icon: Activity, - status: status?.hn_monitor?.active ? "configured" : "available", - priority: "medium", - estimated_benefit: "每周捕获 2-3 个高价值技术趋势", - setup_time: "1 分钟", - auto_detected: true, - config_fields: [ - { key: "hn_interval", label: "抓取间隔(分钟)", type: "text", placeholder: "30", required: false } - ] - }, - { - id: "github", - name: "代码仓监控", - description: "跟踪竞品仓库、上游依赖更新", - icon: Github, - status: status?.github?.configured ? "configured" : "available", - priority: "medium", - estimated_benefit: "及时获知竞品动态和依赖更新", - setup_time: "10 分钟", - auto_detected: true, - config_fields: [ - { key: "repo_url", label: "仓库地址", type: "url", placeholder: "https://github.com/user/repo", required: true }, - { key: "access_token", label: "访问令牌", type: "password", placeholder: "ghp_xxxxxxxxxxxx", required: false } - ] - }, - { - id: "browser", - name: "浏览器扩展", - description: "监控访问的技术文档、竞品页面", - icon: Globe, - status: status?.browser?.installed ? "configured" : "needs_setup", - priority: "low", - estimated_benefit: "自动捕获浏览时的有价值内容", - setup_time: "2 分钟", - auto_detected: false, - config_fields: [], - setup_guide: [ - "从 Chrome 应用商店安装 PMFlow 扩展", - "登录您的 PMFlow 账号", - "扩展会自动监控技术文档和竞品页面" - ] - } - ]; - - // ==================== API Calls ==================== - - const loadStatus = async () => { - setLoading(true); - try { - const token = getAuthToken(); - if (!token) { - navigate("/login"); - return; - } - - // Check Feishu config - const feishuRes = await fetch(`${apiBaseURL}/feishu/config`, { - headers: { Authorization: `Bearer ${token}` } - }); - if (feishuRes.ok) { - const feishuData = await feishuRes.json(); - const mode = "long_connection" as FeishuEventMode; - setStatus(prev => ({ - ...prev!, - feishu: { - configured: feishuData.is_configured, - app_id: feishuData.app_id, - event_mode: mode, - }, - })); - setFeishuConfig({ - app_id: feishuData.app_id || "", - app_secret: feishuData.app_secret || "", - event_mode: mode, - }); - } - - // Check WeChat Work config - const wecomRes = await fetch(`${apiBaseURL}/wecom/config`, { - headers: { Authorization: `Bearer ${token}` } - }); - if (wecomRes.ok) { - const wecomData = await wecomRes.json(); - setStatus(prev => ({ - ...prev!, - wecom: { - configured: wecomData.is_configured, - corp_id: wecomData.corp_id, - agent_id: wecomData.agent_id, - token_preview: wecomData.token_preview || "", - encoding_aes_key_preview: wecomData.encoding_aes_key_preview || "", - }, - })); - setWecomConfig({ - corp_id: wecomData.corp_id || "", - agent_id: wecomData.agent_id || "", - app_secret: wecomData.app_secret || "", - token: wecomData.token || "", - encoding_aes_key: wecomData.encoding_aes_key || "", - }); - } - - // Get AI suggestions - const suggestRes = await fetch(`${apiBaseURL}/ai-steward/config/suggest`, { - headers: { Authorization: `Bearer ${token}` } - }); - if (suggestRes.ok) { - const suggestData = await suggestRes.json(); - setSuggestion(suggestData); - } - - } catch (err) { - console.error("Failed to load status:", err); - } finally { - setLoading(false); - } - }; - - const saveFeishuConfig = async () => { - setChecking("feishu"); - setMessage(null); - if (!feishuConfig.app_id || !feishuConfig.app_secret) { - setMessage({ type: "error", text: "请填写应用编号和应用密钥" }); - setChecking(null); - return; - } - - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/feishu/config`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}` - }, - body: JSON.stringify(feishuConfig) - }); - - if (response.ok) { - setMessage({ type: "success", text: "飞书配置已保存" }); - setEditingFeishu(false); - await loadStatus(); - } else { - const error = await response.json(); - setMessage({ type: "error", text: error.detail || "保存失败" }); - } - } catch (err) { - setMessage({ type: "error", text: "网络错误,请重试" }); - } finally { - setChecking(null); - } - }; - - const testFeishuConnection = async () => { - setChecking("feishu-test"); - setMessage(null); - - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/feishu/test-connection`, { - method: "POST", - headers: { Authorization: `Bearer ${token}` } - }); - - const data = await response.json(); - if (data.status === "success") { - setMessage({ type: "success", text: "飞书连接测试成功!" }); - } else { - setMessage({ type: "error", text: data.message || "连接测试失败" }); - } - } catch (err) { - setMessage({ type: "error", text: "测试失败" }); - } finally { - setChecking(null); - } - }; - - const deleteFeishuConfig = async () => { - if (!confirm("确定要删除飞书配置吗?删除后将无法从飞书群聊捕获信号。")) return; - - setChecking("feishu-delete"); - setMessage(null); - - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/feishu/config`, { - method: "DELETE", - headers: { Authorization: `Bearer ${token}` } - }); - - if (response.ok) { - setMessage({ type: "success", text: "配置已删除" }); - setFeishuConfig({ app_id: "", app_secret: "", event_mode: "long_connection" }); - setEditingFeishu(false); - await loadStatus(); - } else { - setMessage({ type: "error", text: "删除失败" }); - } - } catch (err) { - setMessage({ type: "error", text: "网络错误" }); - } finally { - setChecking(null); - } - }; - - const saveWecomConfig = async () => { - setChecking("wecom"); - setMessage(null); - - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/wecom/config`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}` - }, - body: JSON.stringify(wecomConfig) - }); - - if (response.ok) { - setMessage({ type: "success", text: "企业微信配置已保存" }); - setEditingWecom(false); - await loadStatus(); - } else { - const error = await response.json(); - setMessage({ type: "error", text: error.detail || "保存失败" }); - } - } catch (err) { - setMessage({ type: "error", text: "网络错误,请重试" }); - } finally { - setChecking(null); - } - }; - - const testWecomConnection = async () => { - setChecking("wecom-test"); - setMessage(null); - - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/wecom/test-connection`, { - method: "POST", - headers: { Authorization: `Bearer ${token}` } - }); - - const data = await response.json(); - if (data.status === "success") { - setMessage({ type: "success", text: "企业微信连接测试成功!" }); - } else { - setMessage({ type: "error", text: data.message || "连接测试失败" }); - } - } catch (err) { - setMessage({ type: "error", text: "测试失败" }); - } finally { - setChecking(null); - } - }; - - const deleteWecomConfig = async () => { - if (!confirm("确定要删除企业微信配置吗?删除后将无法从企业微信群聊捕获信号。")) return; - - setChecking("wecom-delete"); - setMessage(null); - - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/wecom/config`, { - method: "DELETE", - headers: { Authorization: `Bearer ${token}` } - }); - - if (response.ok) { - setMessage({ type: "success", text: "配置已删除" }); - setWecomConfig({ corp_id: "", agent_id: "", app_secret: "", token: "", encoding_aes_key: "" }); - setEditingWecom(false); - await loadStatus(); - } else { - setMessage({ type: "error", text: "删除失败" }); - } - } catch (err) { - setMessage({ type: "error", text: "网络错误" }); - } finally { - setChecking(null); - } - }; - - const getPublicBaseUrl = () => { - const normalized = apiBaseURL.replace(/\/api\/?$/, ""); - if (/^https?:\/\//i.test(normalized)) { - return normalized.replace(/\/$/, ""); - } - return window.location.origin.replace(/\/$/, ""); - }; - - const getWecomWebhookUrl = () => `${getPublicBaseUrl()}/wecom/webhook`; - - const startHNMonitor = async () => { - setChecking("hn"); - setMessage(null); - - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/ai-steward/signal/start-hn-monitor?interval_minutes=${monitorSettings.hn_interval}`, { - method: "POST", - headers: { Authorization: `Bearer ${token}` } - }); - - if (response.ok) { - setMessage({ type: "success", text: "技术资讯监控已启动" }); - setMonitorSettings(prev => ({ ...prev!, hn_enabled: true })); - } else { - setMessage({ type: "error", text: "启动失败" }); - } - } catch (err) { - setMessage({ type: "error", text: "网络错误" }); - } finally { - setChecking(null); - } - }; - - const copyText = async (text: string) => { - try { - if (navigator.clipboard && window.isSecureContext) { - await navigator.clipboard.writeText(text); - return true; - } - } catch { - // fallback below - } - - try { - const input = document.createElement("textarea"); - input.value = text; - input.style.position = "fixed"; - input.style.opacity = "0"; - input.style.left = "-9999px"; - document.body.appendChild(input); - input.focus(); - input.select(); - const ok = document.execCommand("copy"); - document.body.removeChild(input); - return ok; - } catch { - return false; - } - }; - - const handleCopyWebhook = async (text: string) => { - const ok = await copyText(text); - if (ok) { - setMessage({ type: "success", text: "已复制到剪切板" }); - setWecomCopyTip("已复制到剪切板"); - setTimeout(() => setWecomCopyTip(""), 1500); - return; - } - setMessage({ type: "error", text: "复制失败,请手动复制地址" }); - setWecomCopyTip("复制失败"); - setTimeout(() => setWecomCopyTip(""), 1500); - }; - - const loadKnowledgeDocs = async () => { - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/rag/knowledge-base/docs`, { - headers: { Authorization: `Bearer ${token}` } - }); - if (!response.ok) return; - const payload = await response.json(); - const docs = (payload?.data ?? payload) || []; - setKnowledgeDocs(Array.isArray(docs) ? docs : []); - } catch (err) { - console.error("Failed to load knowledge docs:", err); - } - }; - - const uploadKnowledgeFile = async () => { - if (!knowledgeFile) { - setMessage({ type: "error", text: "请先选择文件" }); - return; - } - setChecking("knowledge-upload"); - try { - const token = getAuthToken(); - const formData = new FormData(); - formData.append("file", knowledgeFile); - const response = await fetch(`${apiBaseURL}/rag/knowledge-base/upload`, { - method: "POST", - headers: { Authorization: `Bearer ${token}` }, - body: formData, - }); - const payload = await response.json().catch(() => ({})); - if (!response.ok) { - setMessage({ type: "error", text: payload?.detail || "上传失败" }); - return; - } - setKnowledgeFile(null); - setMessage({ type: "success", text: "文件已提交,正在向量化处理" }); - await loadKnowledgeDocs(); - } catch (err) { - setMessage({ type: "error", text: "上传失败,请检查网络" }); - } finally { - setChecking(null); - } - }; - - const addKnowledgeURL = async () => { - if (!knowledgeURL.trim()) { - setMessage({ type: "error", text: "请输入 URL" }); - return; - } - setChecking("knowledge-url"); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/rag/knowledge-base/urls`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ - url: knowledgeURL.trim(), - title: knowledgeURLTitle.trim() || undefined, - }), - }); - const payload = await response.json().catch(() => ({})); - if (!response.ok) { - setMessage({ type: "error", text: payload?.detail || "URL 添加失败" }); - return; - } - setKnowledgeURL(""); - setKnowledgeURLTitle(""); - setMessage({ type: "success", text: "URL 已加入知识库,正在抓取与向量化" }); - await loadKnowledgeDocs(); - } catch (err) { - setMessage({ type: "error", text: "URL 添加失败,请稍后重试" }); - } finally { - setChecking(null); - } - }; - - const refreshKnowledgeDoc = async (docId: number) => { - setChecking(`knowledge-refresh-${docId}`); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/rag/knowledge-base/docs/${docId}/refresh`, { - method: "POST", - headers: { Authorization: `Bearer ${token}` }, - }); - if (!response.ok) { - const payload = await response.json().catch(() => ({})); - setMessage({ type: "error", text: payload?.detail || "刷新失败" }); - return; - } - setMessage({ type: "success", text: "已触发刷新,正在处理" }); - await loadKnowledgeDocs(); - } catch (err) { - setMessage({ type: "error", text: "刷新失败,请稍后重试" }); - } finally { - setChecking(null); - } - }; - - const deleteKnowledgeDoc = async (docId: number) => { - if (!confirm("确认删除该知识库条目及其向量数据?")) return; - setChecking(`knowledge-delete-${docId}`); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/rag/knowledge-base/docs/${docId}`, { - method: "DELETE", - headers: { Authorization: `Bearer ${token}` }, - }); - if (!response.ok) { - const payload = await response.json().catch(() => ({})); - setMessage({ type: "error", text: payload?.detail || "删除失败" }); - return; - } - setMessage({ type: "success", text: "已删除知识库条目" }); - await loadKnowledgeDocs(); - } catch (err) { - setMessage({ type: "error", text: "删除失败,请稍后重试" }); - } finally { - setChecking(null); - } - }; - - // ==================== Initial Load ==================== - - // Scroll to section when tab changes to configure - useEffect(() => { - if (activeTab === "configure" && scrollToSection) { - // Small delay to ensure the tab content is rendered - setTimeout(() => { - const element = document.getElementById(`config-${scrollToSection}`); - if (element) { - element.scrollIntoView({ behavior: "smooth", block: "start" }); - } - setScrollToSection(null); - }, 100); - } - }, [activeTab, scrollToSection]); - - const handleConfigureClick = (sourceId: string) => { - setScrollToSection(sourceId); - setActiveTab("configure"); - }; - - useEffect(() => { - setStatus({ - feishu: { configured: false }, - wecom: { configured: false }, - github: { configured: false }, - browser: { installed: false }, - hn_monitor: { active: false } - }); - loadStatus(); - }, []); - - useEffect(() => { - if (activeTab !== "knowledge") return; - loadKnowledgeDocs(); - }, [activeTab]); - - useEffect(() => { - if (activeTab !== "knowledge") return; - if (!knowledgeDocs.some((item) => item.status === "processing")) return; - const timer = setInterval(() => { - loadKnowledgeDocs(); - }, 3000); - return () => clearInterval(timer); - }, [activeTab, knowledgeDocs]); - - // ==================== Helper Components ==================== - - const StatusBadge = ({ status }: { status: IntegrationSource["status"] }) => { - const config = { - configured: { label: "已配置", variant: "default" as const, icon: CheckCircle2, color: "text-green-600" }, - available: { label: "可用", variant: "secondary" as const, icon: Sparkles, color: "text-blue-600" }, - needs_setup: { label: "待配置", variant: "outline" as const, icon: AlertTriangle, color: "text-orange-600" }, - unavailable: { label: "不可用", variant: "secondary" as const, icon: AlertCircle, color: "text-slate-400" } - }; - const { label, variant, icon: Icon, color } = config[status]; - return ( - - - {label} - - ); - }; - - const PriorityBadge = ({ priority }: { priority: IntegrationSource["priority"] }) => { - const config = { - high: { label: "高优先级", className: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300" }, - medium: { label: "中优先级", className: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300" }, - low: { label: "低优先级", className: "bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300" } - }; - const { label, className } = config[priority]; - return {label}; - }; - - // ==================== Render ==================== - - return ( -
-
- {/* Header */} -
-
- -

- AI 智能配置中心 -

-
-

- AI 自动识别可配置的集成源,一键启用主动捕获能力 -

-
- - {/* AI Suggestion Card */} - {suggestion && ( - - -
- -
-

AI 建议配置方案

-

{suggestion.reasoning}

-

预期影响: {suggestion.estimated_impact}

-
-
-
-
- )} - - {/* Message */} - {message && ( -
- {message.type === "success" ? ( - - ) : ( - - )} - {message.text} -
- )} - - - - 配置治理工具 - 新增治理页面统一收敛到智能配置中心入口,避免侧栏信息过载。 - - -
- {governanceTools.map(({ id, name, description, to, icon: Icon }) => ( - -
- -
-

{name}

-

{description}

- - ))} -
-
-
- - {/* Main Content */} - - - 集成源概览 - 快速配置 - 知识库 - 高级设置 - Prompt 设置 - - - {/* Overview Tab */} - -
- {integrationSources.map((source) => { - const Icon = source.icon; - return ( - - -
-
-
- -
-
- {source.name} - {source.description} -
-
- -
-
- -
-
- 优先级 - -
-
- 预计收益 - {source.estimated_benefit} -
-
- 配置耗时 - {source.setup_time} -
- -
-
-
- ); - })} -
-
- - {/* Quick Configure Tab */} - -
- {/* Feishu Config */} - - -
-
- - 飞书群聊集成 - {status?.feishu?.configured && ( - 已配置 - )} -
- -
- - AI 自动从飞书群聊捕获需求信号,无需手动录入 - -
- - {/* Setup Guide */} - {showFeishuGuide && ( -
-

- - 飞书配置指南(6 步完成) -

-
- {integrationSources.find(s => s.id === "feishu")?.setup_guide?.map((step, index) => ( -
-
- {index + 1} -
- {step} -
- ))} -
-
- ⚠️ 配置完成后,请点击下方"测试连接"按钮验证 -
-
- )} - - {status?.feishu?.configured && !editingFeishu ? ( -
-
- - 飞书已配置 - {status.feishu.app_id && ( - - (应用编号: {status.feishu.app_id}) - - )} -
-

- 当前接收模式:{getFeishuEventModeLabel(status.feishu.event_mode || "long_connection")} -

-
- - - -
-
- ) : ( -
- {status?.feishu?.configured && ( -
- 正在修改已有配置:已自动回填当前配置,可直接核对并修改后保存。 -
- )} -
- -

- 飞书接入已统一为 long_connection -

-
- {FEISHU_EVENT_MODE_OPTIONS.map((option) => ( - - ))} -
-
-
-
- - setFeishuConfig({ ...feishuConfig, app_id: e.target.value })} - /> -
-
- - setFeishuConfig({ ...feishuConfig, app_secret: e.target.value })} - /> -
-
-
- 已切换到飞书长连接模式。无需配置 Webhook、Encrypt Key 或 Verification Token。 -
-
- - {status?.feishu?.configured && ( - - )} -
-
- )} - -
- 当前选择的是「长连接」模式,无需配置 Webhook 请求地址。 -
-
-
- - {/* WeChat Work Config */} - - -
-
- - 企业微信集成 - {status?.wecom?.configured && ( - 已配置 - )} -
- -
- - AI 自动从企业微信群聊捕获需求信号,无需手动录入 - -
- - {/* Setup Guide */} - {showWecomGuide && ( -
-

- - 企业微信配置指南(6 步完成) -

-
- {integrationSources.find(s => s.id === "wecom")?.setup_guide?.map((step, index) => ( -
-
- {index + 1} -
- {step} -
- ))} -
-
- ⚠️ 配置完成后,请点击下方"测试连接"按钮验证 -
-
- )} - - {status?.wecom?.configured && !editingWecom ? ( -
-
- - 企业微信已配置 - {status.wecom.corp_id && ( - - (企业编号: {status.wecom.corp_id}) - - )} -
- {status.wecom.agent_id && ( -

- 应用编号:{status.wecom.agent_id} -

- )} -

- Token: {status.wecom.token_preview || "未设置"},EncodingAESKey: {status.wecom.encoding_aes_key_preview || "未设置"} -

-
- - - -
-
- ) : ( -
- {status?.wecom?.configured && ( -
- 正在修改已有配置:已自动回填当前配置,可直接核对并修改后保存。 -
- )} -
-
- - setWecomConfig({ ...wecomConfig, corp_id: e.target.value })} - /> -
-
- - setWecomConfig({ ...wecomConfig, agent_id: e.target.value })} - /> -
-
-
- - setWecomConfig({ ...wecomConfig, app_secret: e.target.value })} - /> -
-
-
- - setWecomConfig({ ...wecomConfig, token: e.target.value })} - /> -
-
- - setWecomConfig({ ...wecomConfig, encoding_aes_key: e.target.value })} - /> -
-
-
- - {status?.wecom?.configured && ( - - )} -
-
- )} - - {/* 回调地址 */} -
-
- -
- {wecomCopyTip && {wecomCopyTip}} - -
-
- {getWecomWebhookUrl()} -

- 复制此回调地址到企业微信管理后台 - 应用管理 - 接收消息 - 请求地址 -

-
-
-
- - {/* HN Monitor */} - - -
- - 技术资讯监控(HN) - {status?.hn_monitor?.active && ( - 运行中 - )} -
- - 定时抓取 HN 首页,AI 分析技术趋势 - -
- -
-
- -

每 {monitorSettings.hn_interval} 分钟抓取一次

-
- { - if (checked) startHNMonitor(); - setMonitorSettings({ ...monitorSettings, hn_enabled: checked }); - }} - /> -
- {!monitorSettings.hn_enabled && ( - - )} -
-
- - {/* GitHub */} - - -
- - 代码仓监控 - {status?.github?.configured && ( - 已配置 - )} -
- - 跟踪竞品仓库更新、上游依赖变更 - -
- - {status?.github?.configured && !editingGithub ? ( -
-
- - 代码仓监控已配置 - {status.github.repos && ( - - ({status.github.repos.length} 个仓库) - - )} -
-
- - -
-
- ) : ( -
-
- - setGithubConfig({ ...githubConfig, repo_url: e.target.value })} - /> -

支持输入多个仓库,用逗号分隔

-
-
- - setGithubConfig({ ...githubConfig, webhook_url: e.target.value })} - /> -

仓库侧配置的回调地址

-
-
- - setGithubConfig({ ...githubConfig, access_token: e.target.value })} - /> -

用于获取仓库信息的个人访问令牌

-
- - {status?.github?.configured && ( - - )} -
- )} -
-
- - {/* Browser Extension */} - - -
- - 浏览器扩展 - 敬请期待 -
- - 自动监控访问的技术文档、竞品页面 - -
- -
- -

- 浏览器扩展正在开发中 -

-

- 支持功能:自动捕获页面内容、智能分析价值 -

-
-
-
-
-
- - {/* Knowledge Base Tab */} - -
- - - - - 文件上传 - - 支持 PDF / Word / Markdown / TXT,上传后自动分块与向量化。 - - - setKnowledgeFile(e.target.files?.[0] || null)} - /> -
-
- {knowledgeFile ? `已选择: ${knowledgeFile.name}` : "未选择文件"} -
- -
-
-
- - - - - - URL 知识源 - - 添加公开文档 URL,系统自动抓取并向量化,可手动刷新。 - - - setKnowledgeURLTitle(e.target.value)} - /> - setKnowledgeURL(e.target.value)} - /> -
- -
-
-
- - - - 知识库列表 - 处理中状态会每 3 秒自动刷新。 - - - {knowledgeDocs.length === 0 ? ( -
暂无知识库条目
- ) : ( -
- {knowledgeDocs.map((doc) => ( -
-
-
-
{doc.title}
-
- 类型: {doc.file_type} · 来源: {doc.source_type} -
-
- 上传时间: {formatBeijingDateTime(doc.created_at)} - {doc.last_fetched_at ? ` · 最近抓取: ${formatBeijingDateTime(doc.last_fetched_at)}` : ""} -
- {doc.file_url && ( -
{doc.file_url}
- )} - {doc.error_message && ( -
{doc.error_message}
- )} -
-
- - {doc.status === "processing" ? "processing" : doc.status === "complete" ? "complete" : "failed"} - - {doc.source_type === "url" && ( - - )} - -
-
-
- ))} -
- )} -
-
-
-
- - {/* Advanced Tab */} - -
- - - 自动检测规则 - - AI 自动判断信号价值的阈值设置 - - - -
-
- - 影响分 ≥ 0.7 -
-

- 低于此阈值的信号将被忽略,减少噪音 -

-
-
-
- - 中等 -
-

- 控制认知冲突检测的严格程度 -

-
-
-
- - 每天 9:00 -
-

- 个性化每日简报的推送时间 -

-
-
-
- - - - 数据同步设置 - - 控制数据同步频率和范围 - - - -
-
- -

重要事件立即推送

-
- -
-
-
- -

导入过去 30 天的聊天记录

-
- -
-
-
- -

新信号自动关联到活跃项目

-
- -
-
-
-
-
- - {/* Prompt Profiles Tab */} - - - -
-
-
- ); -} +export { default } from "./ai-config/AIConfigPage"; diff --git a/frontend/src/pages/AIStewardPage.tsx b/frontend/src/pages/AIStewardPage.tsx index 523f6c35..2a60e173 100644 --- a/frontend/src/pages/AIStewardPage.tsx +++ b/frontend/src/pages/AIStewardPage.tsx @@ -1,1674 +1 @@ -import { useState, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; -import { - Sparkles, Loader2, CheckCircle2, AlertCircle, FileText, ListTodo, AlertTriangle, - Radar, MessageSquare, Eye, Shield, Bell, TrendingUp, Clock, Users, - Lightbulb, Zap, Target, Activity, CheckSquare -} from "lucide-react"; -import { apiBaseURL, getAuthToken } from "@/lib/api"; -import { - Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Badge } from "@/components/ui/badge"; -import { useAuthStore } from "@/stores/auth"; -import { formatProjectNameWithId, toZhPriority, toZhSeverity, toZhStatus } from "@/lib/display"; -import { formatBeijingDate } from "@/lib/datetime"; -import { QAHistoryPanel } from "@/components/ai-steward/QAHistoryPanel"; -import { - aiStewardApi, - type LLMEngine, - type LLMEngineListResponse, - type LLMEngineUpsertRequest, -} from "@/api/aiSteward"; - -// ==================== Types ==================== - -interface ProjectFromTextResponse { - project: { id: string; name: string; title: string; description: string; status: string }; - signal: { id: string; content: string; impact_score: number }; - charter: { id: string; title: string; content: string }; - tasks: Array<{ id: string; title: string; type: string; estimated_hours: number; priority: string }>; - risks: Array<{ type: string; description: string; severity: string }>; - analysis: { project_name: string; tech_stack: string[]; timeline: string; primary_role: string }; -} - -interface CognitiveConflict { - id: string; - type: string; - description: string; - severity: string; - related_users: string[]; -} - -interface Blocker { - id: string; - type: string; - description: string; - severity: string; - detected_at: string; -} - -interface DailyBriefing { - date: string; - summary: string; - highlights: Array<{ type: string; priority: string; content: string }>; - your_tasks: Array<{ id: number; title: string; status: string; priority: string }>; - risks: Array<{ type: string; task: string; severity: string }>; - signals: Array<{ id: number; source: string; content: string; impact_score: number }>; - conflicts: Array<{ id: string; type: string; description: string }>; - action_items: Array<{ type: string; content: string; priority: string }>; - team_updates: Array<{ assignee_id: number; task: string; status: string }>; -} - -const PROVIDER_ZH: Record = { - deepseek: "DeepSeek", - glm: "GLM", - openai: "OpenAI", - ollama: "Ollama(本地)", -}; - -const DEFAULT_LLM_ENGINE_SETTINGS = { - enable_local_kb_qa: false, - local_kb_qa_scope: "kb_qa_only" as const, -}; - -const ENGINE_PROVIDER_FALLBACK_PRESETS: Record = { - deepseek: { - model: "deepseek-chat", - base_url: "https://api.deepseek.com/v1", - }, - glm: { - model: "GLM-4.7", - base_url: "https://api.edgefn.net/v1", - }, - openai: { - model: "MiniMax-M2.5", - base_url: "https://api.edgefn.net/v1", - }, - ollama: { - model: "llama3:latest", - base_url: "http://host.docker.internal:11434", - }, -}; - -export default function AIStewardPage() { - const navigate = useNavigate(); - const user = useAuthStore((s) => s.user); - - // Project creation state - const [input, setInput] = useState(""); - const [loading, setLoading] = useState(false); - const [projectResult, setProjectResult] = useState(null); - const [error, setError] = useState(null); - - // AI Steward features state - const [activeTab, setActiveTab] = useState("overview"); - const [conflicts, setConflicts] = useState([]); - const [blockers, setBlockers] = useState([]); - const [briefing, setBriefing] = useState(null); - const [featureLoading, setFeatureLoading] = useState(null); - const [engineData, setEngineData] = useState({ - active_engine_id: null, - engines: [], - settings: DEFAULT_LLM_ENGINE_SETTINGS, - }); - const [engineProviderPresets, setEngineProviderPresets] = useState>( - ENGINE_PROVIDER_FALLBACK_PRESETS - ); - const [engineForm, setEngineForm] = useState({ - name: "", - provider: "glm" as "deepseek" | "glm" | "openai" | "ollama", - api_key: "", - model: ENGINE_PROVIDER_FALLBACK_PRESETS.glm.model, - base_url: ENGINE_PROVIDER_FALLBACK_PRESETS.glm.base_url, - }); - const [engineEditingId, setEngineEditingId] = useState(null); - const [engineMessage, setEngineMessage] = useState<{ type: "success" | "error"; text: string } | null>(null); - - const exampleInputs = [ - "我们要做一个智能客服工具,基于大语言模型,对标同类竞品,3 周上线", - "开发一个智能推荐系统,使用协同过滤算法,针对电商用户,2个月内完成", - "构建实时数据大屏,采用实时推送与可视化图表,支持百万级数据点,1个月交付", - ]; - - // ==================== API Calls ==================== - - const loadConflicts = async () => { - setFeatureLoading("conflicts"); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/ai-steward/cognitive-conflicts`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (response.ok) { - const data = await response.json(); - setConflicts(data); - } - } catch (err) { - console.error("Failed to load conflicts:", err); - } finally { - setFeatureLoading(null); - } - }; - - const loadBlockers = async () => { - setFeatureLoading("blockers"); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/ai-steward/blockers/active`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (response.ok) { - const data = await response.json(); - setBlockers(data.blockers || []); - } - } catch (err) { - console.error("Failed to load blockers:", err); - } finally { - setFeatureLoading(null); - } - }; - - const loadBriefing = async () => { - setFeatureLoading("briefing"); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/ai-steward/daily-briefing`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (response.ok) { - const data = await response.json(); - setBriefing(data); - } - } catch (err) { - console.error("Failed to load briefing:", err); - } finally { - setFeatureLoading(null); - } - }; - - - const detectConflicts = async (projectId: number = 1) => { - setFeatureLoading("detect-conflicts"); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/ai-steward/conflicts/detect`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ project_id: projectId, detect_type: "all" }), - }); - if (response.ok) { - await loadConflicts(); - return true; - } - } catch (err) { - console.error("Failed to detect conflicts:", err); - } finally { - setFeatureLoading(null); - } - return false; - }; - - const detectTaskBlockers = async () => { - setFeatureLoading("detect-blockers"); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/ai-steward/blockers/detect-tasks`, { - method: "POST", - headers: { Authorization: `Bearer ${token}` }, - }); - if (response.ok) { - await loadBlockers(); - return true; - } - } catch (err) { - console.error("Failed to detect blockers:", err); - } finally { - setFeatureLoading(null); - } - return false; - }; - - const startHNMonitor = async () => { - setFeatureLoading("hn-monitor"); - try { - const token = getAuthToken(); - const response = await fetch(`${apiBaseURL}/ai-steward/signal/start-hn-monitor?interval_minutes=30`, { - method: "POST", - headers: { Authorization: `Bearer ${token}` }, - }); - if (response.ok) { - return true; - } - } catch (err) { - console.error("Failed to start HN monitor:", err); - } finally { - setFeatureLoading(null); - } - return false; - }; - - const loadEngineConfigs = async () => { - setFeatureLoading("engine-list"); - try { - const data = await aiStewardApi.listLlmEngines(); - setEngineData({ - active_engine_id: data?.active_engine_id ?? null, - engines: data?.engines ?? [], - settings: data?.settings ?? DEFAULT_LLM_ENGINE_SETTINGS, - }); - } catch (err) { - console.error("Failed to load engine configs:", err); - } finally { - setFeatureLoading(null); - } - }; - - const loadEngineProviderPresets = async () => { - try { - const data = await aiStewardApi.getLlmProviderPresets(); - const providers = data?.providers ?? {}; - const nextPresets: Record = { - deepseek: { - model: providers.deepseek?.model || ENGINE_PROVIDER_FALLBACK_PRESETS.deepseek.model, - base_url: providers.deepseek?.base_url || ENGINE_PROVIDER_FALLBACK_PRESETS.deepseek.base_url, - }, - glm: { - model: providers.glm?.model || data?.low || ENGINE_PROVIDER_FALLBACK_PRESETS.glm.model, - base_url: providers.glm?.base_url || data?.base_url || ENGINE_PROVIDER_FALLBACK_PRESETS.glm.base_url, - }, - openai: { - model: providers.openai?.model || data?.high || ENGINE_PROVIDER_FALLBACK_PRESETS.openai.model, - base_url: providers.openai?.base_url || data?.base_url || ENGINE_PROVIDER_FALLBACK_PRESETS.openai.base_url, - }, - ollama: { - model: providers.ollama?.model || ENGINE_PROVIDER_FALLBACK_PRESETS.ollama.model, - base_url: providers.ollama?.base_url || ENGINE_PROVIDER_FALLBACK_PRESETS.ollama.base_url, - }, - }; - setEngineProviderPresets(nextPresets); - setEngineForm((prev) => { - if (engineEditingId) return prev; - const preset = nextPresets[prev.provider]; - return { - ...prev, - model: preset.model, - base_url: preset.base_url, - }; - }); - } catch (err) { - console.error("Failed to load provider presets:", err); - } - }; - - const resetEngineForm = () => { - setEngineEditingId(null); - setEngineForm({ - name: "", - provider: "glm", - api_key: "", - model: engineProviderPresets.glm.model, - base_url: engineProviderPresets.glm.base_url, - }); - }; - - const saveEngineConfig = async () => { - setFeatureLoading("engine-save"); - setEngineMessage(null); - try { - const isEdit = Boolean(engineEditingId); - const body: LLMEngineUpsertRequest = { - name: engineForm.name || `${engineForm.provider.toUpperCase()} 引擎`, - provider: engineForm.provider, - model: engineForm.model || undefined, - base_url: engineForm.base_url || undefined, - }; - if (engineForm.api_key.trim()) { - body.api_key = engineForm.api_key.trim(); - } - if (!isEdit) { - body.set_active = true; - } - const data = isEdit - ? await aiStewardApi.updateLlmEngine(String(engineEditingId), body) - : await aiStewardApi.createLlmEngine(body); - setEngineData({ - active_engine_id: data?.active_engine_id ?? null, - engines: data?.engines ?? [], - settings: data?.settings ?? DEFAULT_LLM_ENGINE_SETTINGS, - }); - setEngineMessage({ type: "success", text: isEdit ? "引擎配置已更新" : "引擎配置已新增" }); - resetEngineForm(); - } catch (err) { - setEngineMessage({ type: "error", text: err instanceof Error ? err.message : "网络错误,保存失败" }); - } finally { - setFeatureLoading(null); - } - }; - - const activateEngine = async (engineId: string) => { - setFeatureLoading(`engine-activate-${engineId}`); - setEngineMessage(null); - try { - const data = await aiStewardApi.activateLlmEngine(engineId); - setEngineData({ - active_engine_id: data?.active_engine_id ?? null, - engines: data?.engines ?? [], - settings: data?.settings ?? DEFAULT_LLM_ENGINE_SETTINGS, - }); - setEngineMessage({ type: "success", text: "已切换激活引擎" }); - } catch (err) { - setEngineMessage({ type: "error", text: err instanceof Error ? err.message : "网络错误,切换失败" }); - } finally { - setFeatureLoading(null); - } - }; - - const deleteEngine = async (engineId: string) => { - if (!confirm("确定删除该引擎配置吗?")) return; - setFeatureLoading(`engine-delete-${engineId}`); - setEngineMessage(null); - try { - const data = await aiStewardApi.deleteLlmEngine(engineId); - setEngineData({ - active_engine_id: data?.active_engine_id ?? null, - engines: data?.engines ?? [], - settings: data?.settings ?? DEFAULT_LLM_ENGINE_SETTINGS, - }); - if (engineEditingId === engineId) { - resetEngineForm(); - } - setEngineMessage({ type: "success", text: "引擎配置已删除" }); - } catch (err) { - setEngineMessage({ type: "error", text: err instanceof Error ? err.message : "网络错误,删除失败" }); - } finally { - setFeatureLoading(null); - } - }; - - const testEngineConnection = async (engineId?: string) => { - const key = engineId ? `engine-test-${engineId}` : "engine-test-active"; - setFeatureLoading(key); - setEngineMessage(null); - try { - const data = engineId ? await aiStewardApi.testLlmEngine(engineId) : await aiStewardApi.testActiveLlmEngine(); - const name = data?.name || "未知引擎"; - const latency = typeof data?.latency_ms === "number" ? `${data.latency_ms}ms` : "-"; - const preview = String(data?.response_preview || "").slice(0, 60); - setEngineMessage({ - type: "success", - text: `连通成功:${name}(延迟 ${latency})${preview ? `,响应:${preview}` : ""}`, - }); - } catch (err) { - setEngineMessage({ type: "error", text: err instanceof Error ? err.message : "网络错误,连通测试失败" }); - } finally { - setFeatureLoading(null); - } - }; - - const updateLocalKbQaSetting = async (checked: boolean) => { - setFeatureLoading("engine-settings"); - setEngineMessage(null); - try { - const data = await aiStewardApi.updateLlmEngineSettings({ enable_local_kb_qa: checked }); - setEngineData({ - active_engine_id: data?.active_engine_id ?? null, - engines: data?.engines ?? [], - settings: data?.settings ?? DEFAULT_LLM_ENGINE_SETTINGS, - }); - setEngineMessage({ - type: "success", - text: checked ? "已启用本地 LLM,仅知识库问答可使用" : "已禁用本地 LLM", - }); - } catch (err) { - setEngineMessage({ type: "error", text: err instanceof Error ? err.message : "更新开关失败" }); - } finally { - setFeatureLoading(null); - } - }; - - const startEditEngine = (engine: LLMEngine) => { - setEngineEditingId(engine.id); - setEngineForm({ - name: engine.name || "", - provider: engine.provider, - api_key: "", - model: engine.model || "", - base_url: engine.base_url || "", - }); - setEngineMessage(null); - }; - - // ==================== Project Creation ==================== - - const handleProjectSubmit = async () => { - if (!input.trim()) { - setError("请输入项目描述"); - return; - } - setLoading(true); - setError(null); - setProjectResult(null); - - try { - const token = getAuthToken(); - if (!token) { - navigate("/login"); - return; - } - - const response = await fetch(`${apiBaseURL}/ai-steward/project-from-text`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ input_text: input }), - }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.detail || "项目创建失败"); - } - - const data: ProjectFromTextResponse = await response.json(); - setProjectResult(data); - } catch (err) { - setError(err instanceof Error ? err.message : "未知错误"); - } finally { - setLoading(false); - } - }; - - // ==================== Initial Load ==================== - - useEffect(() => { - if (activeTab === "conflicts") loadConflicts(); - if (activeTab === "blockers") loadBlockers(); - if (activeTab === "briefing") loadBriefing(); - if (activeTab === "engine") { - loadEngineProviderPresets(); - loadEngineConfigs(); - } - }, [activeTab]); - - // ==================== Render ==================== - - return ( -
-
- {/* Header */} -
-
- -

- AI 管家 -

-
-

- 主动式 AI 管家 - 7x24 小时自动守护项目 -

-
- - {/* Feature Overview Cards */} -
- setActiveTab("signals")}> - -
- - 主动抓取 -
- 信号捕获 -
- -

- 飞书群聊、技术资讯、代码仓、浏览器 - 自动捕获需求信号 -

-
-
- - setActiveTab("conflicts")}> - -
- - 认知对齐 -
- 冲突消解 -
- -

- 自动检测隐性认知差异,推送 1v1 异步对齐 -

-
-
- - setActiveTab("blockers")}> - -
- - 自动解决 -
- 阻塞检测 -
- -

- 代码注释扫描,自动生成解决方案,创建追踪任务 -

-
-
- - setActiveTab("briefing")}> - -
- - 智能简报 -
- 进度同步 -
- -

- 个性化每日简报,只显示相关变更,替代日报 -

-
-
-
- - {/* Main Content */} - - - - - 总览 - 零配置创建 - 信号捕获 - 认知冲突 - 阻塞检测 - 每日简报 - 问答记录 - 引擎配置 - - - {/* Overview Tab */} - -
-
-

AI 管家正在运行

-

- 4 大主动服务守护你的项目,无需手动触发 -

-
- -
-
-
-
- -
-
-

1. 主动抓取信号

-

- 无需手动录入,AI 自动捕获所有需求来源 -

-
-
-
    -
  • - - - 飞书群聊监控 - 配置完成,正在运行 -
  • -
  • - - - 技术资讯定时抓取 - 可启用 -
  • -
  • - - 浏览器标签监控 - 需安装扩展 -
  • -
-
- -
-
-
- -
-
-

2. 主动消解认知差

-

- 提前发现隐性分歧,避免返工 -

-
-
-
    -
  • - - 自动检测定义不一致(如"留存" vs "DAU") -
  • -
  • - - 任务与宪章一致性检查 -
  • -
  • - - AI 生成对齐方案 -
  • -
-
- -
-
-
- -
-
-

3. 主动解决阻塞

-

- 代码注释扫描,自动推荐替代方案 -

-
-
-
    -
  • - - 识别阻塞模式("等待X接口"、"达不到效果") -
  • -
  • - - AI 分析严重程度并生成解决方案 -
  • -
  • - - 自动创建追踪任务 -
  • -
-
- -
-
-
- -
-
-

4. 主动同步进度

-

- 个性化简报,彻底取消日报周报 -

-
-
-
    -
  • - - 按角色智能过滤信息 -
  • -
  • - - 只显示相关任务变更和风险 -
  • -
  • - - 推送行动项和对齐提醒 -
  • -
-
-
-
-
- - {/* Zero-Config Project Creation */} - - {!projectResult ? ( -
-
- -