feat: 新增表格文件预览,优化知识图谱与 Agent 详情交互
- 新增 spreadsheet-preview 组件及 xlsx 解析工具,支持产物中的表格文件预览 - 优化知识图谱画布、详情面板及样式 - 调整 Agent 详情面板、聊天与消息列表交互 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5daa4b3e20
commit
7d61bc09bc
18 files changed
+1092
-208
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { BotIcon, MessageCircleQuestionIcon, PlusSquare } from "lucide-react";
|
||||
import { BotIcon, PlusSquare } from "lucide-react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useRouteBase } from "@/core/route-base";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
@@ -62,7 +62,6 @@ export default function AgentChatPage() {
|
||||
const agentDisplayName = desensitize(
|
||||
agent?.name ?? (isAgentLoading ? t.common.loading : ""),
|
||||
);
|
||||
// 常用问题 shown as a list beside the input box on the landing screen.
|
||||
const commonQuestions = useMemo(
|
||||
() => normalizeAgentQuestions(agent?.common_questions),
|
||||
[agent?.common_questions],
|
||||
@@ -181,9 +180,9 @@ export default function AgentChatPage() {
|
||||
separate flex sibling, so nothing in the main column can ever bleed
|
||||
over it. */}
|
||||
<div className="flex size-full min-h-0 overflow-hidden">
|
||||
{/* ── Left: main conversation column ───────────────────────────── */}
|
||||
{/* Left: main conversation column */}
|
||||
<main className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
{/* Header — in normal flow (not absolute), fixed height. */}
|
||||
{/* Header in normal flow, fixed height. */}
|
||||
<header className="bg-card/90 border-border/70 flex h-12 shrink-0 items-center gap-2 border-b px-4 backdrop-blur-md">
|
||||
<div className="flex shrink-0 items-center gap-1.5 rounded-md border px-2 py-1">
|
||||
<BotIcon className="text-primary h-3.5 w-3.5" />
|
||||
@@ -223,9 +222,9 @@ export default function AgentChatPage() {
|
||||
</header>
|
||||
|
||||
{isLandingState ? (
|
||||
/* ── home: centered hero + suggested questions + input ──────
|
||||
/* Home: centered hero, suggested questions, and input.
|
||||
Everything lives inside the scrollable main column. No fixed,
|
||||
no absolute, no viewport centering — so the avatar can't touch
|
||||
no absolute, no viewport centering, so the avatar can't touch
|
||||
the header and the input can't cover the sidebar. */
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center overflow-y-auto px-6 pb-10 pt-16 sm:pt-24">
|
||||
<AgentWelcome
|
||||
@@ -233,42 +232,28 @@ export default function AgentChatPage() {
|
||||
agentName={agentDisplayName}
|
||||
className="mb-7"
|
||||
/>
|
||||
{/* 常用问题 sit as a list to the LEFT of the input box (not as
|
||||
floating pills above it). On narrow screens the list stacks
|
||||
above the input instead of squeezing it. */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-col gap-3 sm:flex-row sm:items-end sm:gap-4",
|
||||
commonQuestions.length > 0
|
||||
? "max-w-(--container-width-lg)"
|
||||
: "max-w-(--container-width-md)",
|
||||
)}
|
||||
>
|
||||
<div className="w-full max-w-(--container-width-md)">
|
||||
{commonQuestions.length > 0 && (
|
||||
<div className="border-border/60 bg-card/60 w-full shrink-0 rounded-xl border p-3 sm:w-64">
|
||||
<div className="text-muted-foreground mb-2 flex items-center gap-1.5 text-xs font-medium">
|
||||
<MessageCircleQuestionIcon className="size-3.5" />
|
||||
常用问题
|
||||
</div>
|
||||
<div className="flex max-h-44 flex-col gap-1.5 overflow-y-auto">
|
||||
{commonQuestions.map((question, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
disabled={isDemoMode}
|
||||
onClick={() =>
|
||||
handleSubmit({ text: question.prompt, files: [] })
|
||||
}
|
||||
title={desensitize(question.prompt)}
|
||||
className={cn(
|
||||
"border-border/60 bg-background/60 hover:bg-accent hover:border-border w-full truncate rounded-lg border px-3 py-2 text-left text-sm transition-colors",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
)}
|
||||
>
|
||||
<div className="mb-2 flex max-h-20 flex-wrap items-start justify-start gap-2 overflow-y-auto px-1">
|
||||
{commonQuestions.map((question, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
disabled={isDemoMode}
|
||||
onClick={() =>
|
||||
handleSubmit({ text: question.prompt, files: [] })
|
||||
}
|
||||
title={desensitize(question.prompt)}
|
||||
className={cn(
|
||||
"border-border/70 bg-background/85 hover:bg-accent hover:border-border max-w-full rounded-full border px-3 py-1.5 text-left text-sm leading-5 shadow-sm transition-colors",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
)}
|
||||
>
|
||||
<span className="block max-w-[220px] truncate">
|
||||
{desensitize(question.title)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
@@ -298,7 +283,7 @@ export default function AgentChatPage() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* ── conversation (active / history): messages scroll, input
|
||||
/* Conversation: messages scroll, input
|
||||
docked at the bottom of the main column only. */
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<MessageList
|
||||
@@ -347,8 +332,7 @@ export default function AgentChatPage() {
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* ── Right: agent detail panel (profile + inline editing for
|
||||
admins / owners; read-only for everyone else) ───────────────── */}
|
||||
{/* Right: agent detail panel. */}
|
||||
<AgentDetailPanel
|
||||
agent={agent}
|
||||
agentName={agentDisplayName}
|
||||
@@ -360,3 +344,4 @@ export default function AgentChatPage() {
|
||||
</ThreadContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@ import {
|
||||
getNodeTitle,
|
||||
} from "@/components/knowledge-graph/graphUtils";
|
||||
import "@/components/knowledge-graph/graphStyles.css";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { useKnowledgeGraph, useKnowledgeNote } from "@/core/knowledge/hooks";
|
||||
|
||||
export default function KnowledgeGraphPage() {
|
||||
const navigate = useNavigate();
|
||||
const { theme } = useTheme();
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
const canvasRef = useRef<GraphCanvasHandle>(null);
|
||||
|
||||
@@ -54,7 +56,7 @@ export default function KnowledgeGraphPage() {
|
||||
const [showDetail, setShowDetail] = useState(false);
|
||||
const [zoomLevel, setZoomLevel] = useState(1);
|
||||
const [tooltip, setTooltip] = useState<TooltipState | null>(null);
|
||||
const [graphMode, setGraphMode] = useState<GraphMode>("explore");
|
||||
const [graphMode, setGraphMode] = useState<GraphMode>("full");
|
||||
const [exploreRootId, setExploreRootId] = useState<string | null>(null);
|
||||
const [expandedNodeIds, setExpandedNodeIds] = useState<Set<string>>(new Set());
|
||||
const [visibleNodeIds, setVisibleNodeIds] = useState<Set<string>>(new Set());
|
||||
@@ -269,14 +271,16 @@ export default function KnowledgeGraphPage() {
|
||||
return matches;
|
||||
}, [searchKeyword, filteredNodes]);
|
||||
|
||||
const topNodes = useMemo(
|
||||
() =>
|
||||
[...allNodes]
|
||||
.map((node) => ({ node, degree: degrees.get(node.id) ?? 0 }))
|
||||
.sort((a, b) => b.degree - a.degree)
|
||||
.slice(0, 8),
|
||||
[allNodes, degrees],
|
||||
);
|
||||
const topNodes = useMemo(() => {
|
||||
// 搜索时核心节点榜单只保留命中的节点,与画布高亮保持一致。
|
||||
const pool = searchMatchIds
|
||||
? filteredNodes.filter((node) => searchMatchIds.has(node.id))
|
||||
: allNodes;
|
||||
return pool
|
||||
.map((node) => ({ node, degree: degrees.get(node.id) ?? 0 }))
|
||||
.sort((a, b) => b.degree - a.degree)
|
||||
.slice(0, 8);
|
||||
}, [allNodes, filteredNodes, searchMatchIds, degrees]);
|
||||
|
||||
const selectedNode = selectedNodeId ? (nodeById.get(selectedNodeId) ?? null) : null;
|
||||
const selectedNeighbors = useMemo(() => {
|
||||
@@ -633,6 +637,7 @@ export default function KnowledgeGraphPage() {
|
||||
searchMatchIds={searchMatchIds}
|
||||
layoutType={layoutType}
|
||||
showLabels={showLabels}
|
||||
theme={theme}
|
||||
onSelectNode={handleSelectNode}
|
||||
onTooltip={setTooltip}
|
||||
onZoomChange={setZoomLevel}
|
||||
|
||||
@@ -433,14 +433,22 @@ export default function NewAgentPage() {
|
||||
)}
|
||||
</div>
|
||||
<PromptInput
|
||||
onSubmit={({ text }) => void handleChatSubmit(text)}
|
||||
onSubmit={({ text }) => {
|
||||
if (thread.isLoading) {
|
||||
void thread.stop();
|
||||
return;
|
||||
}
|
||||
void handleChatSubmit(text);
|
||||
}}
|
||||
>
|
||||
<PromptInputTextarea
|
||||
placeholder="直接告诉模型你想要什么样的智能体,或回答模型的问题"
|
||||
disabled={thread.isLoading}
|
||||
/>
|
||||
<PromptInputFooter className="justify-end">
|
||||
<PromptInputSubmit disabled={thread.isLoading} />
|
||||
<PromptInputSubmit
|
||||
status={thread.isLoading ? "streaming" : "ready"}
|
||||
/>
|
||||
</PromptInputFooter>
|
||||
</PromptInput>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user