diff --git a/frontend/src/components/AddTagPopover.tsx b/frontend/src/components/AddTagPopover.tsx index 92d5304..6510f40 100644 --- a/frontend/src/components/AddTagPopover.tsx +++ b/frontend/src/components/AddTagPopover.tsx @@ -1,17 +1,17 @@ import { Button, Input, Popover, theme, Tag, Empty } from "antd"; import { PlusOutlined } from "@ant-design/icons"; -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; interface Tag { - id: number; + id?: string | number; name: string; - color: string; + color?: string; } interface AddTagPopoverProps { tags: Tag[]; onFetchTags?: () => Promise; - onAddTag?: (tag: Tag) => void; + onAddTag?: (tagName: string) => void; onCreateAndTag?: (tagName: string) => void; } @@ -27,20 +27,23 @@ export default function AddTagPopover({ const [newTag, setNewTag] = useState(""); const [allTags, setAllTags] = useState([]); - const tagsSet = useMemo(() => new Set(tags.map((tag) => tag.id)), [tags]); + const tagsSet = useMemo( + () => new Set(tags.map((tag) => (tag.id ?? tag.name))), + [tags] + ); - const fetchTags = async () => { + const fetchTags = useCallback(async () => { if (onFetchTags && showPopover) { const data = await onFetchTags?.(); setAllTags(data || []); } - }; + }, [onFetchTags, showPopover]); useEffect(() => { fetchTags(); - }, [showPopover]); + }, [fetchTags]); const availableTags = useMemo(() => { - return allTags.filter((tag) => !tagsSet.has(tag.id)); + return allTags.filter((tag) => !tagsSet.has(tag.id ?? tag.name)); }, [allTags, tagsSet]); const handleCreateAndAddTag = () => { diff --git a/frontend/src/components/DetailHeader.tsx b/frontend/src/components/DetailHeader.tsx index 5164852..2439398 100644 --- a/frontend/src/components/DetailHeader.tsx +++ b/frontend/src/components/DetailHeader.tsx @@ -22,44 +22,51 @@ interface OperationItem { danger?: boolean; } -interface TagConfig { - showAdd: boolean; - tags: { id: number; name: string; color: string }[]; - onFetchTags?: () => Promise<{ - data: { id: number; name: string; color: string }[]; - }>; - onAddTag?: (tag: { id: number; name: string; color: string }) => void; - onCreateAndTag?: (tagName: string) => void; -} -interface DetailHeaderProps { - data: T; - statistics: StatisticItem[]; - operations: OperationItem[]; - tagConfig?: TagConfig; -} - -function DetailHeader({ - data = {} as T, - statistics, - operations, - tagConfig, -}: DetailHeaderProps): React.ReactNode { +interface TagConfig { + showAdd: boolean; + tags: { id?: string | number; name: string; color?: string }[]; + onFetchTags?: () => Promise<{ id?: string | number; name: string; color?: string }[]>; + onAddTag?: (tagName: string) => void; + onCreateAndTag?: (tagName: string) => void; +} +interface DetailHeaderData { + name?: string; + description?: string; + status?: { color?: string; icon?: React.ReactNode; label?: string }; + tags?: { id?: string | number; name?: string }[]; + icon?: React.ReactNode; + iconColor?: string; +} + +interface DetailHeaderProps { + data: T; + statistics: StatisticItem[]; + operations: OperationItem[]; + tagConfig?: TagConfig; +} + +function DetailHeader({ + data = {} as T, + statistics, + operations, + tagConfig, +}: DetailHeaderProps): React.ReactNode { return (
- {
{(data as any)?.icon}
|| ( - - )} -
+ data?.iconColor + ? "" + : "bg-gradient-to-br from-sky-300 to-blue-500 text-white" + }`} + style={data?.iconColor ? { backgroundColor: data.iconColor } : undefined} + > + {
{data?.icon}
|| ( + + )} +

{data?.name}

diff --git a/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx b/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx index 6d39787..63d473d 100644 --- a/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx +++ b/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx @@ -75,6 +75,30 @@ const OFFICE_PREVIEW_POLL_MAX_TIMES = 60; type OfficePreviewStatus = "UNSET" | "PENDING" | "PROCESSING" | "READY" | "FAILED"; +const parseMetadata = (value?: string | Record) => { + if (!value) { + return null; + } + if (typeof value === "object") { + return value as Record; + } + if (typeof value !== "string") { + return null; + } + try { + const parsed = JSON.parse(value); + return parsed && typeof parsed === "object" ? (parsed as Record) : null; + } catch { + return null; + } +}; + +const isAnnotationItem = (record: KnowledgeItemView) => { + const metadata = parseMetadata(record.metadata); + const source = metadata && typeof metadata === "object" ? (metadata as { source?: { type?: string } }).source : null; + return source?.type === "annotation"; +}; + const isOfficeFileName = (fileName?: string) => { const lowerName = (fileName || "").toLowerCase(); return OFFICE_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext)); @@ -488,7 +512,7 @@ const KnowledgeSetDetail = () => { setReadItemId(record.id); setReadTitle("知识条目"); - if (!record.sourceDatasetId || !record.sourceFileId) { + if (!record.sourceDatasetId || !record.sourceFileId || isAnnotationItem(record)) { const content = record.content || ""; setReadContent(truncatePreviewText(content, PREVIEW_TEXT_MAX_LENGTH)); setReadModalOpen(true); @@ -921,7 +945,7 @@ const KnowledgeSetDetail = () => { ]} tagConfig={{ showAdd: true, - tags: (knowledgeSet?.tags || []) as any, + tags: knowledgeSet?.tags || [], onFetchTags: async () => { const res = await queryDatasetTagsUsingGet({ page: 0, @@ -950,10 +974,10 @@ const KnowledgeSetDetail = () => { fetchKnowledgeSet(); } }, - onAddTag: async (tag: any) => { + onAddTag: async (tagName: string) => { if (knowledgeSet) { const currentTags = knowledgeSet.tags || []; - const newTagName = typeof tag === "string" ? tag : tag?.name; + const newTagName = tagName?.trim(); if (!newTagName) return; await updateKnowledgeSetByIdUsingPut(knowledgeSet.id, { name: knowledgeSet.name,