From 381547ec785bc0843c21f0362126b84455d72b16 Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Thu, 28 May 2026 14:25:44 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=90=86=E8=A7=A3?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/page-sidebar-dark.tsx | 304 ++++++++++++++++++ frontend-web/src/pages/PageRoutes.tsx | 19 +- .../lib/roundtable-constants.ts | 159 +++++++-- 3 files changed, 452 insertions(+), 30 deletions(-) create mode 100644 frontend-web/src/components/page-sidebar-dark.tsx diff --git a/frontend-web/src/components/page-sidebar-dark.tsx b/frontend-web/src/components/page-sidebar-dark.tsx new file mode 100644 index 00000000..12ce893b --- /dev/null +++ b/frontend-web/src/components/page-sidebar-dark.tsx @@ -0,0 +1,304 @@ +import { useEffect, useState } from "react"; +import type { ElementType } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { + Bell, + Bot, + Brain, + CalendarClock, + ChevronLeft, + ChevronRight, + ChevronsUpDown, + CircleHelp, + Database, + FileBox, + FileText, + Folder, + FolderOpen, + Gauge, + Grid3X3, + HelpCircle, + History, + Layout, + Library, + List, + ListChecks, + ListTodo, + MessageSquareCode, + MessageSquarePlus, + Network, + NotebookPen, + Plus, + Settings, + Sparkles, + Target, + User, + Users, + Wrench, +} from "lucide-react"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { NotificationDialog } from "@/components/workspace/notification-dialog"; +import { SettingsDialog } from "@/components/workspace/settings"; +import { useTheme } from "@/contexts/ThemeContext"; +import { getStoredAuth } from "@/core/auth"; +import { useI18n } from "@/core/i18n/hooks"; +import { useUnreadCount } from "@/core/notifications"; + +type MenuItem = { + id: string; + label: string; + icon: ElementType; + path?: string; + adminOnly?: boolean; + children?: MenuItem[]; +}; + +type PageSidebarDarkProps = { + collapsed: boolean; + onToggleCollapsed: () => void; +}; + +type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about"; + +const menuItems: MenuItem[] = [ + { + id: "qa-center", + label: "问答中心", + icon: HelpCircle, + children: [ + { id: "general-qa", label: "通用问答", icon: CircleHelp, path: "/page/workspace/chats/new" }, + { id: "qa-zzjc", label: "zzjc支持", icon: Bot }, + { id: "qa-zzxd", label: "zzXD应用", icon: Layout }, + { id: "qa-research", label: "课题研究", icon: FileText, path: "/page/canvas/ai-writing" }, + ], + }, + { + id: "light-app-center", + label: "轻应用", + icon: Grid3X3, + children: [ + { + id: "light-app-note-management", + label: "笔记管理", + icon: NotebookPen, + children: [ + { + id: "light-app-new-note", + label: "新建笔记", + icon: Plus, + path: "/page/strategy/light-app/new-note", + }, + { + id: "light-app-note-list", + label: "笔记列表", + icon: List, + path: "/page/strategy/light-app/notes", + }, + { + id: "light-app-note-qa", + label: "笔记问答", + icon: MessageSquareCode, + path: "/page/strategy/light-app/note-qa", + }, + ], + }, + { id: "light-app-square", label: "应用广场", icon: Layout }, + { id: "light-app-records", label: "运行记录", icon: History }, + ], + }, + { + id: "task-management", + label: "任务管理", + icon: Target, + children: [ + { + id: "xk-center-task", + label: "XK中任务", + icon: Folder, + children: [ + { id: "xk-task-jc", label: "任务JC", icon: ListTodo, path: "/page/strategy/task-management/xk-task-jc" }, + { id: "xk-task-dispatch", label: "任务调度", icon: ListTodo, path: "/page/strategy/task-management/xk-task-dispatch" }, + ], + }, + { + id: "coh-center-task", + label: "COH中任务", + icon: Folder, + children: [ + { id: "coh-task-3qfx", label: "3QFX", icon: ListTodo }, + { id: "coh-task-fx", label: "任务FX", icon: ListTodo }, + { id: "coh-task-xdfx", label: "XDFX", icon: ListTodo }, + ], + }, + { + id: "pg-center-task", + label: "PG中任务", + icon: Folder, + children: [ + { id: "pg-task-pg", label: "任务PG", icon: ListTodo }, + { id: "pg-task-xdpg", label: "XDPG", icon: ListTodo }, + { id: "pg-task-info-pg", label: "信息PG", icon: ListTodo }, + ], + }, + { + id: "ty-center-task", + label: "TY中任务", + icon: Folder, + children: [ + { id: "ty-task-xdty", label: "XDTY任务", icon: ListTodo }, + { id: "ty-task-plan", label: "方案TY任务", icon: ListTodo }, + { id: "ty-task-xxty", label: "XXTY任务", icon: ListTodo }, + ], + }, + ], + }, + { + id: "comprehensive-management", + label: "综合管理", + icon: Settings, + children: [ + { id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" }, + { id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" }, + { id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" }, + { + id: "mgmt-tasks", + label: "任务管理", + icon: ListChecks, + path: "/page/strategy/scheduled/tasks", + }, + { + id: "mgmt-recommended-questions", + label: "推荐问题配置", + icon: MessageSquarePlus, + path: "/page/strategy/settings/recommended-questions", + adminOnly: true, + }, + { + id: "mgmt-prompt-prefix", + label: "问答前缀配置", + icon: MessageSquareCode, + path: "/page/strategy/settings/prompt-prefix", + adminOnly: true, + }, + { + id: "mgmt-llm-metrics", + label: "大模型调用统计", + icon: Gauge, + path: "/page/strategy/settings/llm-metrics", + adminOnly: true, + }, + { + id: "mgmt-users", + label: "用户管理", + icon: Users, + path: "/page/strategy/admin/users", + adminOnly: true, + }, + { + id: "mgmt-admin-scheduled-tasks", + label: "定时任务管理", + icon: CalendarClock, + path: "/page/strategy/admin/scheduled-tasks", + adminOnly: true, + }, + { + id: "mgmt-user-activity", + label: "用户活动审计", + icon: History, + path: "/page/strategy/admin/user-activity", + adminOnly: true, + }, + ], + }, + { + id: "resource-management", + label: "资源管理", + icon: Database, + children: [ + { id: "resource-network-xwt", label: "网络xwt资源管理", icon: Network }, + { id: "resource-knowledge-base", label: "知识库资源管理", icon: Library }, + { id: "resource-zz", label: "ZZ资源管理", icon: FileBox }, + ], + }, +]; + +const defaultExpandedIds = new Set([ + "qa-center", + "light-app-center", + "light-app-note-management", + "task-management", + "xk-center-task", +]); + +function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] { + return items + .filter((item) => !item.adminOnly || isAdmin) + .map((item) => + item.children + ? { ...item, children: filterMenuItemsByRole(item.children, isAdmin) } + : item, + ); +} + +export function PageSidebarDark({ + collapsed, + onToggleCollapsed, +}: PageSidebarDarkProps) { + const { theme } = useTheme(); + const isDark = theme === "dark"; + const isAdmin = getStoredAuth()?.system_role === "admin"; + const visibleMenuItems = filterMenuItemsByRole(menuItems, !!isAdmin); + const { t } = useI18n(); + const { data: unreadCount = 0 } = useUnreadCount(); + const [expandedIds, setExpandedIds] = useState>(defaultExpandedIds); + const [settingsOpen, setSettingsOpen] = useState(false); + const [settingsDefaultSection, setSettingsDefaultSection] = + useState("appearance"); + const [notificationOpen, setNotificationOpen] = useState(false); + const [mounted, setMounted] = useState(false); + const navigate = useNavigate(); + const { pathname } = useLocation(); + + useEffect(() => { + setMounted(true); + }, []); + + const openSettings = (section: SettingsSection) => { + setSettingsDefaultSection(section); + setSettingsOpen(true); + }; + + const toggleExpanded = (id: string) => { + setExpandedIds((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }; + + return ( + <> + + + {/* TODO: 在这里编写深色主题的侧边栏 HTML 结构 */} +
深色主题侧边栏 - HTML 待补充
+ + ); +} diff --git a/frontend-web/src/pages/PageRoutes.tsx b/frontend-web/src/pages/PageRoutes.tsx index 02579600..c748e18f 100644 --- a/frontend-web/src/pages/PageRoutes.tsx +++ b/frontend-web/src/pages/PageRoutes.tsx @@ -2,7 +2,9 @@ import { useMemo, useState } from "react"; import { Navigate, Route, Routes } from "react-router-dom"; import { PageSidebar } from "@/components/page-sidebar"; +import { PageSidebarDark } from "@/components/page-sidebar-dark"; import { readSidebarEnabled } from "@/core/page-layout/sidebar-enabled"; +import { useTheme } from "@/contexts/ThemeContext"; import WorkspaceRoutes from "./WorkspaceRoutes"; import StrategyRoutes from "./StrategyRoutes"; @@ -13,16 +15,25 @@ import RoundtableRoutes from "./RoundtableRoutes"; function PageLayout() { const [collapsed, setCollapsed] = useState(false); const sidebarEnabled = useMemo(() => readSidebarEnabled(), []); + const { theme } = useTheme(); + const isDark = theme === "dark"; return (
{sidebarEnabled &&
}
{sidebarEnabled && ( - setCollapsed((prev) => !prev)} - /> + isDark ? ( + setCollapsed((prev) => !prev)} + /> + ) : ( + setCollapsed((prev) => !prev)} + /> + ) )}
{ - const parsed = JSON.parse(s) as unknown; - if ( - parsed && - typeof parsed === "object" && - typeof (parsed as { objective?: unknown }).objective === "string" - ) { - return parsed as IntentSummary; +/** + * 从 `fromIndex` 起找第一个 `{`,按花括号配对(跳过字符串内部的括号)找到匹配的 `}`, + * 返回 JSON 子串及其在原文里的起止下标。 + * + * 比 `\{[\s\S]*?\}` 这种正则更抗模型跑偏:容忍 JSON 内嵌套对象、字符串里的裸换行 / + * 花括号,以及模型漏写 ```json 代码围栏(直接 `[INTENT_READY] {...}`)的情况。 + */ +function extractBalancedJson( + text: string, + fromIndex: number, +): { json: string; end: number } | null { + const start = text.indexOf("{", fromIndex); + if (start < 0) return null; + let depth = 0; + let inString = false; + let escaped = false; + for (let i = start; i < text.length; i += 1) { + const ch = text[i]; + if (escaped) { + escaped = false; + continue; } - return null; - }; - try { - return attempt(raw); - } catch { - // 字符串值里有裸换行 / 制表符 → 转义后重试,避免整块摘要丢失导致 Step 2 无法解锁。 - try { - return attempt(sanitizeJsonControlChars(raw)); - } catch { - return null; + if (ch === "\\") { + escaped = true; + continue; + } + if (ch === '"') { + inString = !inString; + continue; + } + if (inString) continue; + if (ch === "{") depth += 1; + else if (ch === "}") { + depth -= 1; + if (depth === 0) return { json: text.slice(start, i + 1), end: i + 1 }; } } + return null; // 括号未闭合(流式中段 / 模型截断) } +/** 去掉对象 / 数组里 `}` `]` 之前的尾随逗号(只在字符串外处理)。 */ +function stripTrailingCommas(raw: string): string { + let out = ""; + let inString = false; + let escaped = false; + for (let i = 0; i < raw.length; i += 1) { + const ch = raw[i]; + if (escaped) { + out += ch; + escaped = false; + continue; + } + if (ch === "\\") { + out += ch; + escaped = true; + continue; + } + if (ch === '"') { + inString = !inString; + out += ch; + continue; + } + if (!inString && ch === ",") { + let j = i + 1; + while (j < raw.length && /\s/.test(raw[j])) j += 1; + if (raw[j] === "}" || raw[j] === "]") continue; // 丢弃尾随逗号 + } + out += ch; + } + return out; +} + +/** 把任意值归一成 string[]:数组逐项转字符串、单个字符串包成单元素、其它视为空。 */ +function toStringArray(value: unknown): string[] { + if (Array.isArray(value)) { + return value + .map((x) => (typeof x === "string" ? x : x == null ? "" : String(x))) + .filter((x) => x.trim().length > 0); + } + if (typeof value === "string" && value.trim().length > 0) return [value]; + return []; +} + +/** + * 把解析出的对象归一成 IntentSummary。只要 `objective` 是非空字符串就认。 + * constraints / assumptions 容忍模型写成字符串 / null / 缺失,统一归到 string[]。 + */ +function normalizeIntentSummary(parsed: unknown): IntentSummary | null { + if (!parsed || typeof parsed !== "object") return null; + const obj = parsed as Record; + const objective = + typeof obj.objective === "string" ? obj.objective.trim() : ""; + if (!objective) return null; + return { + objective, + constraints: toStringArray(obj.constraints), + assumptions: toStringArray(obj.assumptions), + }; +} + +/** + * 解析 INTENT_READY 的 JSON 块。内网模型输出不可控,这里逐级降级重试: + * 1) 原文直接 parse + * 2) 转义字符串内的裸控制字符(换行 / 制表符)后再 parse + * 3) 再去掉尾随逗号后 parse + * 三次都失败才放弃。任一成功都过 `normalizeIntentSummary` 兜字段类型。 + */ +function parseIntentSummary(raw: string): IntentSummary | null { + const sanitized = sanitizeJsonControlChars(raw); + const candidates = [raw, sanitized, stripTrailingCommas(sanitized)]; + for (const candidate of candidates) { + try { + const summary = normalizeIntentSummary(JSON.parse(candidate)); + if (summary) return summary; + } catch { + // 下一个候选 + } + } + return null; +} + +const INTENT_READY_MARKER_RE = /\[INTENT_READY\]/i; + /** * 把含 [INTENT_READY] 标记的气泡正文拆成前 / 摘要 / 后三段。 * 摘要 JSON 格式异常时整体保留在 `before`,用户仍能看到原文。 + * + * 提取不再依赖严格的 ```json 围栏正则:先定位 [INTENT_READY](大小写不敏感), + * 再用括号配对抓出后面的第一个完整 JSON 对象,最后多级降级 parse。这样模型漏写 + * 代码围栏、JSON 里有裸换行 / 尾随逗号、字段类型写偏等情况都能尽量恢复。 */ export function splitIntentReadyBlock(text: string): { before: string; @@ -219,13 +323,16 @@ export function splitIntentReadyBlock(text: string): { after: string; } { if (!text) return { before: text, summary: null, after: "" }; - const m = INTENT_READY_RE.exec(text); - if (!m) return { before: text, summary: null, after: "" }; - const summary = parseIntentSummary(m[1]); + const marker = INTENT_READY_MARKER_RE.exec(text); + if (!marker) return { before: text, summary: null, after: "" }; + const block = extractBalancedJson(text, marker.index + marker[0].length); + if (!block) return { before: text, summary: null, after: "" }; + const summary = parseIntentSummary(block.json); if (!summary) return { before: text, summary: null, after: "" }; return { - before: text.slice(0, m.index).trimEnd(), + before: text.slice(0, marker.index).trimEnd(), summary, - after: text.slice(m.index + m[0].length).trimStart(), + // 丢掉 JSON 块之后残留的代码围栏 ``` 与模型多说的话(SOUL 要求 READY 后即停)。 + after: text.slice(block.end).replace(/^\s*```+/, "").trimStart(), }; } From 2fe058e0ee6862fa1f48c4a17dd0890cd9576d57 Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Thu, 28 May 2026 15:24:00 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/.env.development | 16 +++++++++++++++- frontend-web/.env.development.local.example | 13 +++++++++++++ frontend-web/.env.production | 13 +++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/frontend-web/.env.development b/frontend-web/.env.development index 0d15230d..41decf09 100644 --- a/frontend-web/.env.development +++ b/frontend-web/.env.development @@ -7,6 +7,20 @@ VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api # 轻应用「新建笔记」系列接口 VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000 - +# CMZS后端接口 +VITE_API_BASE_URL=https://ch1.b.uat.4.cn/zncm +# 佳来模型对话接口 +VITE_AIPI_CHAT_URL=https://ch1.b.uat.4.cn/gpt +# 多轮检索接口 +VITE_AIPI_GENERATE_CHAT_URL=https://ch1.b.uat.4.cn/jialai_b1_chat +VITE_AIPI_TASK_CHAT_URL=https://ch1.b.uat.4.cn/qq_b1_chat +VITE_AIPI_KNOWLEDGE_URL=https://chlib.b.uat.4.cn/knowlibrary +VITE_AIPI_B1COH_URL=https://ch1.b.uat.4.cn/web +VITE_AIPI_B2ACTOR_URL=htttps://ty.b.uat.4.cn +VITE_AIPI_MOPECHAT_URL=https://ch1.b.uat.4.cn/likun_b1_mope +VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.uat.4.cn +VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer +VITE_AIPI_PGLINKTO_URL=https://pg.b.uat.4.cn +VITE_AIPI_XKLINKTO_URL=https://kz.b.uat.4.cn # 可选:Canvas 独立 LangGraph 地址(不设则回退到 VITE_LANGGRAPH_BASE_URL) # VITE_CANVAS_LANGGRAPH_BASE_URL= diff --git a/frontend-web/.env.development.local.example b/frontend-web/.env.development.local.example index 3f551f71..471b2649 100644 --- a/frontend-web/.env.development.local.example +++ b/frontend-web/.env.development.local.example @@ -3,5 +3,18 @@ VITE_BACKEND_BASE_URL=http://127.0.0.1:8001 VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api +# 佳来模型对话接口 +VITE_AIPI_CHAT_URL=https://ch1.b.uat.4.cn/gpt +# 多轮检索接口 +VITE_AIPI_GENERATE_CHAT_URL=https://ch1.b.uat.4.cn/jialai_b1_chat +VITE_AIPI_TASK_CHAT_URL=https://ch1.b.uat.4.cn/qq_b1_chat +VITE_AIPI_KNOWLEDGE_URL=https://chlib.b.uat.4.cn/knowlibrary +VITE_AIPI_B1COH_URL=https://ch1.b.uat.4.cn/web +VITE_AIPI_B2ACTOR_URL=htttps://ty.b.uat.4.cn +VITE_AIPI_MOPECHAT_URL=https://ch1.b.uat.4.cn/likun_b1_mope +VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.uat.4.cn +VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer +VITE_AIPI_PGLINKTO_URL=https://pg.b.uat.4.cn +VITE_AIPI_XKLINKTO_URL=https://kz.b.uat.4.cn # VITE_NOTE_API_BASE_URL=http://127.0.0.1:8000 diff --git a/frontend-web/.env.production b/frontend-web/.env.production index 1d13e37e..0e94e082 100644 --- a/frontend-web/.env.production +++ b/frontend-web/.env.production @@ -7,6 +7,19 @@ VITE_LANGGRAPH_BASE_URL=https://ch1.b.uat.4.cn/deerflow/api # 轻应用「新建笔记」系列接口 VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api +# 佳来模型对话接口 +VITE_AIPI_CHAT_URL=https://ch1.b.uat.4.cn/gpt +# 多轮检索接口 +VITE_AIPI_GENERATE_CHAT_URL=https://ch1.b.uat.4.cn/jialai_b1_chat +VITE_AIPI_TASK_CHAT_URL=https://ch1.b.uat.4.cn/qq_b1_chat +VITE_AIPI_KNOWLEDGE_URL=https://chlib.b.uat.4.cn/knowlibrary +VITE_AIPI_B1COH_URL=https://ch1.b.uat.4.cn/web +VITE_AIPI_B2ACTOR_URL=htttps://ty.b.uat.4.cn +VITE_AIPI_MOPECHAT_URL=https://ch1.b.uat.4.cn/likun_b1_mope +VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.uat.4.cn +VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer +VITE_AIPI_PGLINKTO_URL=https://pg.b.uat.4.cn +VITE_AIPI_XKLINKTO_URL=https://kz.b.uat.4.cn # 可选:Canvas 独立 LangGraph 地址 # VITE_CANVAS_LANGGRAPH_BASE_URL=