From 7d61bc09bc33bff5eccb2c193d07bc8ac272dfe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Thu, 11 Jun 2026 17:42:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=A2=84=E8=A7=88=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=9B=BE=E8=B0=B1=E4=B8=8E=20Agent=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 spreadsheet-preview 组件及 xlsx 解析工具,支持产物中的表格文件预览 - 优化知识图谱画布、详情面板及样式 - 调整 Agent 详情面板、聊天与消息列表交互 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../knowledge-graph/GraphCanvas.tsx | 71 +++- .../knowledge-graph/GraphDetailPanel.tsx | 19 +- .../knowledge-graph/graphStyles.css | 311 +++++++++++----- .../src/components/wiki/WikiHeader.tsx | 4 +- frontend-web/src/components/wiki/wikiUtils.ts | 10 +- .../workspace/agents/agent-detail-panel.tsx | 162 ++++++++- .../artifacts/artifact-file-detail.tsx | 34 +- .../artifacts/spreadsheet-preview.tsx | 151 ++++++++ .../components/workspace/chats/chat-box.tsx | 10 +- .../components/workspace/messages/context.ts | 16 + .../workspace/messages/message-list.tsx | 23 +- .../workspace/workspace-nav-chat-list.tsx | 2 +- .../src/core/page-layout/sidebar-menu.ts | 2 +- .../src/core/skill-display/runtime.ts | 41 ++- frontend-web/src/core/utils/xlsx.ts | 338 ++++++++++++++++++ frontend-web/src/pages/AgentChatPage.tsx | 71 ++-- frontend-web/src/pages/KnowledgeGraphPage.tsx | 23 +- frontend-web/src/pages/NewAgentPage.tsx | 12 +- 18 files changed, 1092 insertions(+), 208 deletions(-) create mode 100644 frontend-web/src/components/workspace/artifacts/spreadsheet-preview.tsx create mode 100644 frontend-web/src/core/utils/xlsx.ts diff --git a/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx b/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx index a548e2e3..16ad2250 100644 --- a/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx +++ b/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx @@ -35,6 +35,42 @@ import { const MIN_ZOOM = 0.15; const MAX_ZOOM = 4; +/** Canvas-drawn colors per app theme (CSS variables don't reach the 2D context). */ +const CANVAS_PALETTE = { + dark: { + exportBg: "#0b1020", + labelHalo: "rgba(2, 6, 23, 0.9)", + nodeRim: "rgba(2, 6, 23, 0.55)", + rootRim: "#e0f2fe", + expandedRim: "#7dd3fc", + rootRing: "rgba(125, 211, 252, 0.9)", + expandedDot: "#7dd3fc", + expandedDotRim: "rgba(2, 6, 23, 0.8)", + selectionRing: "#ffffff", + hoverRing: "rgba(255, 255, 255, 0.85)", + pin: "rgba(255, 255, 255, 0.8)", + labelActive: "#ffffff", + label: "#e2e8f0", + minimapViewport: "rgba(56, 189, 248, 0.8)", + }, + light: { + exportBg: "#f8fafc", + labelHalo: "rgba(255, 255, 255, 0.92)", + nodeRim: "rgba(255, 255, 255, 0.9)", + rootRim: "#075985", + expandedRim: "#0284c7", + rootRing: "rgba(2, 132, 199, 0.85)", + expandedDot: "#0284c7", + expandedDotRim: "rgba(255, 255, 255, 0.9)", + selectionRing: "#0f172a", + hoverRing: "rgba(15, 23, 42, 0.7)", + pin: "rgba(15, 23, 42, 0.6)", + labelActive: "#0f172a", + label: "#334155", + minimapViewport: "rgba(2, 132, 199, 0.8)", + }, +} as const; + export interface GraphCanvasHandle { zoomIn: () => void; zoomOut: () => void; @@ -54,6 +90,8 @@ interface GraphCanvasProps { searchMatchIds: Set | null; layoutType: LayoutType; showLabels: boolean; + /** App theme; drives the canvas-drawn colors (labels, rims, rings). */ + theme: "light" | "dark"; onSelectNode: (id: string | null) => void; onTooltip: (tooltip: TooltipState | null) => void; onZoomChange: (k: number) => void; @@ -231,6 +269,7 @@ export const GraphCanvas = forwardRef( props.searchMatchIds, props.showLabels, props.explore, + props.theme, requestDraw, ]); @@ -330,7 +369,7 @@ export const GraphCanvas = forwardRef( out.height = main.height; const ctx = out.getContext("2d"); if (!ctx) return; - ctx.fillStyle = "#0b1020"; + ctx.fillStyle = CANVAS_PALETTE[propsRef.current.theme].exportBg; ctx.fillRect(0, 0, out.width, out.height); ctx.drawImage(main, 0, 0); out.toBlob((blob) => { @@ -381,6 +420,7 @@ export const GraphCanvas = forwardRef( const search = p.searchMatchIds; const k = t.k || 1; const explore = p.explore; + const palette = CANVAS_PALETTE[p.theme]; // ---- edges for (const e of sim.edges) { @@ -418,7 +458,7 @@ export const GraphCanvas = forwardRef( ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.lineWidth = 3 / k; - ctx.strokeStyle = "rgba(2, 6, 23, 0.9)"; + ctx.strokeStyle = palette.labelHalo; ctx.strokeText(e.info.zh, mx, my); ctx.fillStyle = e.info.color; ctx.fillText(e.info.zh, mx, my); @@ -443,7 +483,7 @@ export const GraphCanvas = forwardRef( let baseRadius = n.radius; let labelByExplore = false; let strokeWidth = 1.4 / k; - let strokeColor = "rgba(2, 6, 23, 0.55)"; + let strokeColor: string = palette.nodeRim; let fillColor = n.color; if (explore?.preview) { alpha *= 0.42; @@ -454,7 +494,12 @@ export const GraphCanvas = forwardRef( alpha *= Math.max(0.45, 1 - depth * 0.12); labelByExplore = depth <= 2 || isActive || isSelected; strokeWidth = (depth === 0 ? 4 : isExpanded ? 3 : 1.5) / k; - strokeColor = depth === 0 ? "#e0f2fe" : isExpanded ? "#7dd3fc" : "rgba(2, 6, 23, 0.55)"; + strokeColor = + depth === 0 + ? palette.rootRim + : isExpanded + ? palette.expandedRim + : palette.nodeRim; if (isExploreRoot) fillColor = "#38bdf8"; } @@ -487,15 +532,15 @@ export const GraphCanvas = forwardRef( ctx.beginPath(); ctx.arc(n.x, n.y, r + 8 / k, 0, Math.PI * 2); ctx.lineWidth = 1.4 / k; - ctx.strokeStyle = "rgba(125, 211, 252, 0.9)"; + ctx.strokeStyle = palette.rootRing; ctx.stroke(); } else if (isExpanded) { ctx.beginPath(); ctx.arc(n.x + r * 0.58, n.y - r * 0.58, Math.max(4 / k, 3), 0, Math.PI * 2); - ctx.fillStyle = "#7dd3fc"; + ctx.fillStyle = palette.expandedDot; ctx.fill(); ctx.lineWidth = 1 / k; - ctx.strokeStyle = "rgba(2, 6, 23, 0.8)"; + ctx.strokeStyle = palette.expandedDotRim; ctx.stroke(); } @@ -504,7 +549,7 @@ export const GraphCanvas = forwardRef( ctx.beginPath(); ctx.arc(n.x, n.y, r + 4 / k, 0, Math.PI * 2); ctx.lineWidth = 2 / k; - ctx.strokeStyle = "#ffffff"; + ctx.strokeStyle = palette.selectionRing; ctx.stroke(); ctx.beginPath(); ctx.arc(n.x, n.y, r + 8 / k, 0, Math.PI * 2); @@ -515,7 +560,7 @@ export const GraphCanvas = forwardRef( ctx.beginPath(); ctx.arc(n.x, n.y, r + 3.5 / k, 0, Math.PI * 2); ctx.lineWidth = 1.6 / k; - ctx.strokeStyle = "rgba(255, 255, 255, 0.85)"; + ctx.strokeStyle = palette.hoverRing; ctx.stroke(); } @@ -523,7 +568,7 @@ export const GraphCanvas = forwardRef( if (n.fx != null && !isSelected) { ctx.beginPath(); ctx.arc(n.x, n.y, Math.max(r * 0.28, 2 / k), 0, Math.PI * 2); - ctx.fillStyle = "rgba(255, 255, 255, 0.8)"; + ctx.fillStyle = palette.pin; ctx.fill(); } @@ -542,9 +587,9 @@ export const GraphCanvas = forwardRef( ctx.textAlign = "center"; ctx.textBaseline = "top"; ctx.lineWidth = 3.5 / k; - ctx.strokeStyle = "rgba(2, 6, 23, 0.9)"; + ctx.strokeStyle = palette.labelHalo; ctx.strokeText(text, n.x, n.y + r + 5 / k); - ctx.fillStyle = isActive || isSelected ? "#ffffff" : "#e2e8f0"; + ctx.fillStyle = isActive || isSelected ? palette.labelActive : palette.label; ctx.fillText(text, n.x, n.y + r + 5 / k); } } @@ -599,7 +644,7 @@ export const GraphCanvas = forwardRef( const y0 = ((0 - t.y) / t.k) * scale + oy; const x1 = ((w - t.x) / t.k) * scale + ox; const y1 = ((h - t.y) / t.k) * scale + oy; - ctx.strokeStyle = "rgba(56, 189, 248, 0.8)"; + ctx.strokeStyle = CANVAS_PALETTE[propsRef.current.theme].minimapViewport; ctx.lineWidth = 1; ctx.strokeRect(x0, y0, x1 - x0, y1 - y0); } diff --git a/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx b/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx index 9c533d90..5dec2078 100644 --- a/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx +++ b/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx @@ -125,29 +125,22 @@ function DetailBody(props: GraphDetailPanelProps & { node: GraphNode }) { ? ` · ${node.category}` : ""} - + 度数 {props.degree} {props.explore && props.explore.depth !== null ? ( <> - + {props.explore.isRoot ? "探索起点" : `第 ${props.explore.depth} 层`} {props.explore.expanded ? "已展开" : "可展开"} diff --git a/frontend-web/src/components/knowledge-graph/graphStyles.css b/frontend-web/src/components/knowledge-graph/graphStyles.css index 53896817..e6595f66 100644 --- a/frontend-web/src/components/knowledge-graph/graphStyles.css +++ b/frontend-web/src/components/knowledge-graph/graphStyles.css @@ -1,34 +1,134 @@ -/* Knowledge-graph workbench — dark, professional analysis-tool styling. */ +/* Knowledge-graph workbench — professional analysis-tool styling. + Light theme is the default; dark values are applied under `.dark` + (the app toggles the `dark` class on ). */ .kg-page { - --kg-bg: #0b1020; - --kg-panel: rgba(15, 23, 42, 0.78); - --kg-panel-solid: #0f172a; - --kg-border: rgba(148, 163, 184, 0.18); - --kg-text-main: #e5e7eb; - --kg-text-secondary: #94a3b8; - --kg-text-muted: #64748b; - --kg-primary: #38bdf8; - --kg-primary-soft: rgba(56, 189, 248, 0.16); + --kg-bg: #f4f6fb; + --kg-bg-image: + radial-gradient(circle at 20% 10%, rgba(2, 132, 199, 0.07), transparent 32%), + radial-gradient(circle at 80% 20%, rgba(124, 58, 237, 0.06), transparent 28%); + --kg-panel: rgba(255, 255, 255, 0.82); + --kg-panel-solid: #ffffff; + --kg-header-bg: rgba(255, 255, 255, 0.88); + --kg-float-bg: rgba(255, 255, 255, 0.88); + --kg-tooltip-bg: rgba(255, 255, 255, 0.97); + --kg-tooltip-shadow: 0 18px 40px rgba(15, 23, 42, 0.18); + --kg-button-bg: rgba(255, 255, 255, 0.75); + --kg-input-bg: rgba(255, 255, 255, 0.9); + --kg-card-bg: rgba(248, 250, 252, 0.9); + --kg-canvas-bg: rgba(255, 255, 255, 0.55); + --kg-canvas-glow: rgba(2, 132, 199, 0.06); + --kg-grid-line: rgba(15, 23, 42, 0.05); + + --kg-border: rgba(15, 23, 42, 0.12); + --kg-border-soft: rgba(15, 23, 42, 0.09); + --kg-border-faint: rgba(15, 23, 42, 0.07); + --kg-divider: rgba(15, 23, 42, 0.06); + --kg-input-border: rgba(15, 23, 42, 0.16); + + --kg-text-strong: #0f172a; + --kg-text-main: #1e293b; + --kg-text-soft: #334155; + --kg-text-secondary: #64748b; + --kg-text-muted: #94a3b8; + + --kg-primary: #0284c7; + --kg-primary-text: #0369a1; + --kg-primary-accent: #0369a1; + --kg-primary-soft: rgba(2, 132, 199, 0.1); + --kg-primary-soft-strong: rgba(2, 132, 199, 0.16); + --kg-primary-soft-faint: rgba(2, 132, 199, 0.07); + --kg-primary-border: rgba(2, 132, 199, 0.3); + --kg-primary-border-strong: rgba(2, 132, 199, 0.45); + --kg-focus-border: rgba(2, 132, 199, 0.55); + --kg-focus-ring: rgba(2, 132, 199, 0.12); + + --kg-pill-bg: rgba(2, 132, 199, 0.08); + --kg-pill-border: rgba(2, 132, 199, 0.24); + --kg-violet-bg: rgba(124, 58, 237, 0.08); + --kg-violet-border: rgba(124, 58, 237, 0.24); + --kg-violet-text: #6d28d9; + + --kg-hover-bg: rgba(15, 23, 42, 0.07); + --kg-hover-bg-faint: rgba(15, 23, 42, 0.045); + --kg-warn: #b45309; + --kg-success-bg: rgba(5, 150, 105, 0.12); + --kg-success-text: #047857; + --kg-warn-bg: rgba(217, 119, 6, 0.12); + --kg-warn-text: #b45309; height: 100%; min-height: 0; - background: - radial-gradient(circle at 20% 10%, rgba(56, 189, 248, 0.1), transparent 32%), - radial-gradient(circle at 80% 20%, rgba(167, 139, 250, 0.1), transparent 28%), - #0b1020; + background-color: var(--kg-bg); + background-image: var(--kg-bg-image); color: var(--kg-text-main); display: flex; flex-direction: column; overflow: hidden; } +.dark .kg-page { + --kg-bg: #0b1020; + --kg-bg-image: + radial-gradient(circle at 20% 10%, rgba(56, 189, 248, 0.1), transparent 32%), + radial-gradient(circle at 80% 20%, rgba(167, 139, 250, 0.1), transparent 28%); + --kg-panel: rgba(15, 23, 42, 0.78); + --kg-panel-solid: #0f172a; + --kg-header-bg: rgba(15, 23, 42, 0.86); + --kg-float-bg: rgba(15, 23, 42, 0.82); + --kg-tooltip-bg: rgba(15, 23, 42, 0.94); + --kg-tooltip-shadow: 0 18px 40px rgba(0, 0, 0, 0.35); + --kg-button-bg: rgba(15, 23, 42, 0.65); + --kg-input-bg: rgba(2, 6, 23, 0.45); + --kg-card-bg: rgba(2, 6, 23, 0.35); + --kg-canvas-bg: rgba(2, 6, 23, 0.5); + --kg-canvas-glow: rgba(56, 189, 248, 0.08); + --kg-grid-line: rgba(148, 163, 184, 0.04); + + --kg-border: rgba(148, 163, 184, 0.18); + --kg-border-soft: rgba(148, 163, 184, 0.14); + --kg-border-faint: rgba(148, 163, 184, 0.1); + --kg-divider: rgba(148, 163, 184, 0.08); + --kg-input-border: rgba(148, 163, 184, 0.2); + + --kg-text-strong: #ffffff; + --kg-text-main: #e5e7eb; + --kg-text-soft: #cbd5e1; + --kg-text-secondary: #94a3b8; + --kg-text-muted: #64748b; + + --kg-primary: #38bdf8; + --kg-primary-text: #bae6fd; + --kg-primary-accent: #7dd3fc; + --kg-primary-soft: rgba(56, 189, 248, 0.14); + --kg-primary-soft-strong: rgba(56, 189, 248, 0.18); + --kg-primary-soft-faint: rgba(56, 189, 248, 0.08); + --kg-primary-border: rgba(56, 189, 248, 0.35); + --kg-primary-border-strong: rgba(56, 189, 248, 0.45); + --kg-focus-border: rgba(56, 189, 248, 0.55); + --kg-focus-ring: rgba(56, 189, 248, 0.1); + + --kg-pill-bg: rgba(56, 189, 248, 0.12); + --kg-pill-border: rgba(56, 189, 248, 0.28); + --kg-violet-bg: rgba(167, 139, 250, 0.12); + --kg-violet-border: rgba(167, 139, 250, 0.28); + --kg-violet-text: #ddd6fe; + + --kg-hover-bg: rgba(148, 163, 184, 0.12); + --kg-hover-bg-faint: rgba(148, 163, 184, 0.08); + --kg-warn: #fbbf24; + --kg-success-bg: rgba(52, 211, 153, 0.14); + --kg-success-text: #6ee7b7; + --kg-warn-bg: rgba(251, 191, 36, 0.14); + --kg-warn-text: #fcd34d; +} + /* ------------------------------------------------------------------ header */ .kg-header { height: 56px; border-bottom: 1px solid var(--kg-border); - background: rgba(15, 23, 42, 0.86); + background: var(--kg-header-bg); backdrop-filter: blur(14px); display: flex; align-items: center; @@ -61,7 +161,7 @@ } .kg-back:hover { - background: rgba(148, 163, 184, 0.12); + background: var(--kg-hover-bg); } .kg-title-block { @@ -74,7 +174,7 @@ .kg-title { font-size: 18px; font-weight: 800; - color: #ffffff; + color: var(--kg-text-strong); white-space: nowrap; } @@ -94,9 +194,9 @@ height: 28px; padding: 0 10px; border-radius: 999px; - background: rgba(56, 189, 248, 0.12); - border: 1px solid rgba(56, 189, 248, 0.28); - color: #bae6fd; + background: var(--kg-pill-bg); + border: 1px solid var(--kg-pill-border); + color: var(--kg-primary-text); font-size: 12px; display: inline-flex; align-items: center; @@ -105,9 +205,9 @@ } .kg-stat-pill.kg-stat-pill-violet { - background: rgba(167, 139, 250, 0.12); - border-color: rgba(167, 139, 250, 0.28); - color: #ddd6fe; + background: var(--kg-violet-bg); + border-color: var(--kg-violet-border); + color: var(--kg-violet-text); } .kg-header-actions { @@ -123,8 +223,8 @@ padding: 0 8px; border-radius: 9px; border: 1px solid var(--kg-border); - background: rgba(15, 23, 42, 0.65); - color: #cbd5e1; + background: var(--kg-button-bg); + color: var(--kg-text-soft); font-size: 12px; display: inline-flex; align-items: center; @@ -138,9 +238,9 @@ } .kg-icon-button:hover { - color: #ffffff; - background: rgba(56, 189, 248, 0.14); - border-color: rgba(56, 189, 248, 0.35); + color: var(--kg-text-strong); + background: var(--kg-primary-soft); + border-color: var(--kg-primary-border); } .kg-icon-button:disabled { @@ -149,9 +249,9 @@ } .kg-icon-button.kg-icon-button-active { - color: #ffffff; - background: rgba(56, 189, 248, 0.18); - border-color: rgba(56, 189, 248, 0.45); + color: var(--kg-text-strong); + background: var(--kg-primary-soft-strong); + border-color: var(--kg-primary-border-strong); } .kg-header-divider { @@ -198,7 +298,7 @@ .kg-panel-header { height: 46px; padding: 0 16px; - border-bottom: 1px solid rgba(148, 163, 184, 0.14); + border-bottom: 1px solid var(--kg-border-soft); display: flex; align-items: center; justify-content: space-between; @@ -222,9 +322,9 @@ width: 28px; height: 28px; border-radius: 8px; - border: 1px solid rgba(148, 163, 184, 0.18); - background: rgba(15, 23, 42, 0.65); - color: #94a3b8; + border: 1px solid var(--kg-border); + background: var(--kg-button-bg); + color: var(--kg-text-secondary); display: inline-flex; align-items: center; justify-content: center; @@ -233,9 +333,9 @@ } .kg-panel-close-btn:hover { - color: #ffffff; - background: rgba(56, 189, 248, 0.14); - border-color: rgba(56, 189, 248, 0.35); + color: var(--kg-text-strong); + background: var(--kg-primary-soft); + border-color: var(--kg-primary-border); } .kg-panel-content { @@ -266,8 +366,8 @@ height: 36px; width: 100%; border-radius: 10px; - border: 1px solid rgba(148, 163, 184, 0.2); - background: rgba(2, 6, 23, 0.45); + border: 1px solid var(--kg-input-border); + background: var(--kg-input-bg); color: var(--kg-text-main); padding: 0 12px 0 32px; font-size: 13px; @@ -279,8 +379,8 @@ } .kg-search:focus { - border-color: rgba(56, 189, 248, 0.55); - box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.1); + border-color: var(--kg-focus-border); + box-shadow: 0 0 0 3px var(--kg-focus-ring); } .kg-search-hint { @@ -290,11 +390,11 @@ } .kg-search-hint.kg-search-hint-warn { - color: #fbbf24; + color: var(--kg-warn); } .kg-filter-section { - border-top: 1px solid rgba(148, 163, 184, 0.1); + border-top: 1px solid var(--kg-border-faint); padding: 10px 0; } @@ -307,7 +407,7 @@ height: 34px; border: none; background: transparent; - color: #cbd5e1; + color: var(--kg-text-soft); display: flex; align-items: center; justify-content: space-between; @@ -318,11 +418,11 @@ } .kg-filter-section-header:hover { - color: #ffffff; + color: var(--kg-text-strong); } .kg-filter-section-extra { - color: #64748b; + color: var(--kg-text-muted); font-size: 12px; font-weight: 400; display: inline-flex; @@ -340,7 +440,7 @@ align-items: center; justify-content: space-between; gap: 8px; - color: #cbd5e1; + color: var(--kg-text-soft); font-size: 13px; cursor: pointer; border-radius: 8px; @@ -349,7 +449,7 @@ } .kg-check-row:hover { - background: rgba(148, 163, 184, 0.08); + background: var(--kg-hover-bg-faint); } .kg-check-row-main { @@ -360,7 +460,7 @@ } .kg-check-row input[type="checkbox"] { - accent-color: #38bdf8; + accent-color: var(--kg-primary); width: 14px; height: 14px; flex-shrink: 0; @@ -390,15 +490,15 @@ .kg-stat-card { border-radius: 10px; - border: 1px solid rgba(148, 163, 184, 0.12); - background: rgba(2, 6, 23, 0.35); + border: 1px solid var(--kg-border-faint); + background: var(--kg-card-bg); padding: 8px 10px; } .kg-stat-card-value { font-size: 16px; font-weight: 800; - color: #ffffff; + color: var(--kg-text-strong); } .kg-stat-card-label { @@ -411,13 +511,13 @@ display: flex; gap: 8px; padding: 8px 6px; - border-bottom: 1px solid rgba(148, 163, 184, 0.08); + border-bottom: 1px solid var(--kg-divider); cursor: pointer; border-radius: 8px; } .kg-rank-item:hover { - background: rgba(56, 189, 248, 0.08); + background: var(--kg-primary-soft-faint); } .kg-rank-index { @@ -430,7 +530,7 @@ .kg-rank-title { flex: 1; - color: #cbd5e1; + color: var(--kg-text-soft); font-size: 12px; line-height: 1.5; overflow: hidden; @@ -462,15 +562,14 @@ .kg-canvas-wrap { position: relative; - background: - linear-gradient(rgba(148, 163, 184, 0.04) 1px, transparent 1px), - linear-gradient(90deg, rgba(148, 163, 184, 0.04) 1px, transparent 1px), - radial-gradient(circle at 50% 40%, rgba(56, 189, 248, 0.08), transparent 42%), - rgba(2, 6, 23, 0.5); + background-color: var(--kg-canvas-bg); + background-image: + linear-gradient(var(--kg-grid-line) 1px, transparent 1px), + linear-gradient(90deg, var(--kg-grid-line) 1px, transparent 1px), + radial-gradient(circle at 50% 40%, var(--kg-canvas-glow), transparent 42%); background-size: 28px 28px, 28px 28px, - auto, auto; border: 1px solid var(--kg-border); border-radius: 18px; @@ -511,9 +610,9 @@ height: 32px; padding: 0 6px; border-radius: 999px; - background: rgba(15, 23, 42, 0.82); + background: var(--kg-float-bg); border: 1px solid var(--kg-border); - color: #cbd5e1; + color: var(--kg-text-soft); font-size: 12px; display: inline-flex; align-items: center; @@ -532,7 +631,7 @@ border-radius: 999px; border: none; background: transparent; - color: #cbd5e1; + color: var(--kg-text-soft); font-size: 12px; cursor: pointer; display: inline-flex; @@ -541,21 +640,21 @@ } .kg-pill-button:hover { - color: #ffffff; + color: var(--kg-text-strong); } .kg-pill-button.kg-pill-button-active { - background: rgba(56, 189, 248, 0.18); - color: #ffffff; + background: var(--kg-primary-soft-strong); + color: var(--kg-text-strong); } .kg-explore-button { height: 32px; padding: 0 10px; border-radius: 999px; - border: 1px solid rgba(148, 163, 184, 0.18); - background: rgba(15, 23, 42, 0.72); - color: #cbd5e1; + border: 1px solid var(--kg-border); + background: var(--kg-float-bg); + color: var(--kg-text-soft); font-size: 12px; cursor: pointer; display: inline-flex; @@ -565,8 +664,8 @@ } .kg-explore-button:hover { - background: rgba(56, 189, 248, 0.14); - color: #ffffff; + background: var(--kg-primary-soft); + color: var(--kg-text-strong); } .kg-explore-button:disabled { @@ -583,9 +682,9 @@ max-width: min(560px, calc(100% - 48px)); padding: 8px 14px; border-radius: 999px; - border: 1px solid rgba(56, 189, 248, 0.22); - background: rgba(15, 23, 42, 0.78); - color: #bae6fd; + border: 1px solid var(--kg-pill-border); + background: var(--kg-panel); + color: var(--kg-primary-text); font-size: 12px; text-align: center; backdrop-filter: blur(12px); @@ -599,9 +698,9 @@ width: 32px; min-height: 88px; border-radius: 12px; - border: 1px solid rgba(56, 189, 248, 0.28); - background: rgba(15, 23, 42, 0.86); - color: #bae6fd; + border: 1px solid var(--kg-pill-border); + background: var(--kg-header-bg); + color: var(--kg-primary-text); display: flex; align-items: center; justify-content: center; @@ -621,8 +720,8 @@ } .kg-side-rail-toggle:hover { - background: rgba(56, 189, 248, 0.16); - color: #ffffff; + background: var(--kg-primary-soft-strong); + color: var(--kg-text-strong); } .kg-minimap { @@ -632,7 +731,7 @@ width: 180px; height: 120px; border-radius: 12px; - background: rgba(15, 23, 42, 0.78); + background: var(--kg-panel); border: 1px solid var(--kg-border); overflow: hidden; z-index: 10; @@ -674,9 +773,9 @@ max-width: 320px; padding: 12px; border-radius: 12px; - background: rgba(15, 23, 42, 0.94); - border: 1px solid rgba(56, 189, 248, 0.25); - box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35); + background: var(--kg-tooltip-bg); + border: 1px solid var(--kg-pill-border); + box-shadow: var(--kg-tooltip-shadow); color: var(--kg-text-main); pointer-events: none; backdrop-filter: blur(14px); @@ -731,7 +830,7 @@ font-size: 17px; line-height: 1.5; font-weight: 800; - color: #ffffff; + color: var(--kg-text-strong); margin-bottom: 10px; word-break: break-word; } @@ -748,8 +847,28 @@ margin-bottom: 12px; } +.kg-detail-type-neutral { + background: var(--kg-hover-bg); + color: var(--kg-text-soft); +} + +.kg-detail-type-primary { + background: var(--kg-primary-soft); + color: var(--kg-primary-accent); +} + +.kg-detail-type-success { + background: var(--kg-success-bg); + color: var(--kg-success-text); +} + +.kg-detail-type-warn { + background: var(--kg-warn-bg); + color: var(--kg-warn-text); +} + .kg-detail-summary { - color: #cbd5e1; + color: var(--kg-text-soft); font-size: 13px; line-height: 1.8; margin-bottom: 16px; @@ -770,7 +889,7 @@ } .kg-detail-meta-value { - color: #cbd5e1; + color: var(--kg-text-soft); word-break: break-word; } @@ -785,8 +904,8 @@ height: 24px; padding: 0 8px; border-radius: 999px; - background: rgba(148, 163, 184, 0.12); - color: #cbd5e1; + background: var(--kg-hover-bg); + color: var(--kg-text-soft); font-size: 12px; display: inline-flex; align-items: center; @@ -815,8 +934,8 @@ .kg-neighbor-item { padding: 8px 10px; border-radius: 10px; - background: rgba(2, 6, 23, 0.35); - border: 1px solid rgba(148, 163, 184, 0.1); + background: var(--kg-card-bg); + border: 1px solid var(--kg-border-faint); cursor: pointer; display: flex; align-items: center; @@ -824,15 +943,15 @@ } .kg-neighbor-item:hover { - background: rgba(56, 189, 248, 0.1); - border-color: rgba(56, 189, 248, 0.24); + background: var(--kg-primary-soft-faint); + border-color: var(--kg-primary-border); } .kg-neighbor-title { flex: 1; min-width: 0; font-size: 12px; - color: #cbd5e1; + color: var(--kg-text-soft); line-height: 1.5; overflow: hidden; text-overflow: ellipsis; @@ -903,7 +1022,7 @@ max-width: 240px; overflow: hidden; text-overflow: ellipsis; - color: #bae6fd; + color: var(--kg-primary-text); } /* -------------------------------------------------------------- responsive */ diff --git a/frontend-web/src/components/wiki/WikiHeader.tsx b/frontend-web/src/components/wiki/WikiHeader.tsx index 2ffa1080..a82c391d 100644 --- a/frontend-web/src/components/wiki/WikiHeader.tsx +++ b/frontend-web/src/components/wiki/WikiHeader.tsx @@ -73,9 +73,9 @@ export function WikiHeader({
- 知识库 + 问答梳理
-
LLM Wiki · 知识库中心
+
LLM Wiki · 问答梳理中心
diff --git a/frontend-web/src/components/wiki/wikiUtils.ts b/frontend-web/src/components/wiki/wikiUtils.ts index 6450fffd..9500fa93 100644 --- a/frontend-web/src/components/wiki/wikiUtils.ts +++ b/frontend-web/src/components/wiki/wikiUtils.ts @@ -123,14 +123,20 @@ export function splitRelatedSection(markdown: string): { export function noteFolderSegments(note: KnowledgeNote): string[] { const rawPath = note.vault_path?.replace(/\\/g, "/").replace(/^\/+/, ""); if (rawPath && rawPath.includes("/")) { - return rawPath.split("/").slice(0, -1).filter(Boolean); + let segments = rawPath.split("/").slice(0, -1).filter(Boolean); + // Vault notes live under projects/// — the first two + // levels are constant scaffolding, so hide them from the directory tree. + if (segments[0] === "projects" || segments[0] === "project") { + segments = segments.slice(2); + } + if (segments.length > 0) return segments; } return [SOURCE_TYPE_LABELS[note.source_type] ?? FALLBACK_FOLDER]; } /** Breadcrumb segments for a note (folders only; the title is appended by the caller). */ export function noteBreadcrumbSegments(note: KnowledgeNote): string[] { - return ["知识库", ...noteFolderSegments(note)]; + return ["问答梳理", ...noteFolderSegments(note)]; } function sortTree(nodes: TreeNode[]): void { diff --git a/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx b/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx index 63b086bb..42f79179 100644 --- a/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx +++ b/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx @@ -2,6 +2,7 @@ import { BotIcon, + CheckIcon, GlobeIcon, InfoIcon, Loader2Icon, @@ -15,6 +16,7 @@ import { SquarePenIcon, Trash2Icon, UserRoundIcon, + XIcon, } from "lucide-react"; import { useRouter } from "next/navigation"; import { useMemo, useState, type ReactNode } from "react"; @@ -156,6 +158,13 @@ export function AgentDetailPanel({ const [mobileOpen, setMobileOpen] = useState(false); const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(null); + const [editingQuestionIndex, setEditingQuestionIndex] = useState< + number | null + >(null); + const [questionDraft, setQuestionDraft] = useState({ + title: "", + prompt: "", + }); const [pickerOpen, setPickerOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); const [publishPickerOpen, setPublishPickerOpen] = useState(false); @@ -200,6 +209,7 @@ export function AgentDetailPanel({ function beginEdit() { if (!agent || !canEdit) return; + cancelQuestionEdit(); setDraft(draftFromAgent(agent)); setEditing(true); } @@ -210,6 +220,42 @@ export function AgentDetailPanel({ setPickerOpen(false); } + function beginQuestionEdit(index: number, question: AgentQuestion) { + if (!canEdit) return; + setEditingQuestionIndex(index); + setQuestionDraft(question); + } + + function cancelQuestionEdit() { + setEditingQuestionIndex(null); + setQuestionDraft({ title: "", prompt: "" }); + } + + async function saveQuestionEdit(index: number) { + if (!agent) return; + const nextQuestions = questions.map((question, questionIndex) => + questionIndex === index + ? { + title: questionDraft.title.trim() || questionDraft.prompt.trim(), + prompt: questionDraft.prompt.trim() || questionDraft.title.trim(), + } + : question, + ); + const cleanedQuestions = nextQuestions.filter( + (question) => question.prompt.trim().length > 0, + ); + try { + await updateAgent.mutateAsync({ + id: agent.id, + request: { common_questions: cleanedQuestions }, + }); + toast.success("Updated"); + cancelQuestionEdit(); + } catch (err) { + toast.error(err instanceof Error ? err.message : String(err)); + } + } + async function saveEdit() { if (!agent || !draft) return; const trimmedName = draft.name.trim(); @@ -367,7 +413,14 @@ export function AgentDetailPanel({ ) : (
- @@ -544,21 +597,98 @@ export function AgentDetailPanel({ /> ) : (
- {questions.map((question, index) => ( - - ))} + {questions.map((question, index) => { + const isEditingThis = editingQuestionIndex === index; + return ( +
+ {isEditingThis ? ( +
+ + setQuestionDraft({ + ...questionDraft, + title: e.currentTarget.value, + }) + } + className="h-8 text-sm font-medium" + /> +