Compare commits

..

3 Commits

Author SHA1 Message Date
e71116d117 refactor(components): 更新标签组件类型定义和数据处理逻辑
- 修改 Tag 接口定义,将 id 和 color 字段改为可选类型
- 更新 onAddTag 回调函数参数类型,从对象改为字符串
- 在 AddTagPopover 组件中添加 useCallback 优化数据获取逻辑
- 调整标签去重逻辑,支持 id 或 name 任一字段匹配
- 更新 DetailHeader 组件的数据类型定义和泛型约束
- 添加 parseMetadata 工具函数用于解析元数据
- 实现 isAnnotationItem 函数判断注释类型数据
- 优化知识库详情页的标签处理和数据类型转换
2026-02-02 22:15:16 +08:00
cac53d7aac fix(knowledge): 更新知识管理页面标题为知识集
- 将页面标题从"知识管理"修改为"知识集"
2026-02-02 21:49:39 +08:00
43b4a619bc refactor(knowledge): 移除知识库创建中的扩展元数据字段
- 删除了表单中的扩展元数据输入区域
- 移除了对应的 Form.Item 包装器
- 简化了创建知识库表单结构
2026-02-02 21:48:21 +08:00
5 changed files with 80 additions and 49 deletions

View File

@@ -1,17 +1,17 @@
import { Button, Input, Popover, theme, Tag, Empty } from "antd"; import { Button, Input, Popover, theme, Tag, Empty } from "antd";
import { PlusOutlined } from "@ant-design/icons"; import { PlusOutlined } from "@ant-design/icons";
import { useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
interface Tag { interface Tag {
id: number; id?: string | number;
name: string; name: string;
color: string; color?: string;
} }
interface AddTagPopoverProps { interface AddTagPopoverProps {
tags: Tag[]; tags: Tag[];
onFetchTags?: () => Promise<Tag[]>; onFetchTags?: () => Promise<Tag[]>;
onAddTag?: (tag: Tag) => void; onAddTag?: (tagName: string) => void;
onCreateAndTag?: (tagName: string) => void; onCreateAndTag?: (tagName: string) => void;
} }
@@ -27,20 +27,23 @@ export default function AddTagPopover({
const [newTag, setNewTag] = useState(""); const [newTag, setNewTag] = useState("");
const [allTags, setAllTags] = useState<Tag[]>([]); const [allTags, setAllTags] = useState<Tag[]>([]);
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) { if (onFetchTags && showPopover) {
const data = await onFetchTags?.(); const data = await onFetchTags?.();
setAllTags(data || []); setAllTags(data || []);
} }
}; }, [onFetchTags, showPopover]);
useEffect(() => { useEffect(() => {
fetchTags(); fetchTags();
}, [showPopover]); }, [fetchTags]);
const availableTags = useMemo(() => { const availableTags = useMemo(() => {
return allTags.filter((tag) => !tagsSet.has(tag.id)); return allTags.filter((tag) => !tagsSet.has(tag.id ?? tag.name));
}, [allTags, tagsSet]); }, [allTags, tagsSet]);
const handleCreateAndAddTag = () => { const handleCreateAndAddTag = () => {

View File

@@ -22,44 +22,51 @@ interface OperationItem {
danger?: boolean; danger?: boolean;
} }
interface TagConfig { interface TagConfig {
showAdd: boolean; showAdd: boolean;
tags: { id: number; name: string; color: string }[]; tags: { id?: string | number; name: string; color?: string }[];
onFetchTags?: () => Promise<{ onFetchTags?: () => Promise<{ id?: string | number; name: string; color?: string }[]>;
data: { id: number; name: string; color: string }[]; onAddTag?: (tagName: string) => void;
}>; onCreateAndTag?: (tagName: string) => void;
onAddTag?: (tag: { id: number; name: string; color: string }) => void; }
onCreateAndTag?: (tagName: string) => void; interface DetailHeaderData {
} name?: string;
interface DetailHeaderProps<T> { description?: string;
data: T; status?: { color?: string; icon?: React.ReactNode; label?: string };
statistics: StatisticItem[]; tags?: { id?: string | number; name?: string }[];
operations: OperationItem[]; icon?: React.ReactNode;
tagConfig?: TagConfig; iconColor?: string;
} }
function DetailHeader<T>({ interface DetailHeaderProps<T extends DetailHeaderData> {
data = {} as T, data: T;
statistics, statistics: StatisticItem[];
operations, operations: OperationItem[];
tagConfig, tagConfig?: TagConfig;
}: DetailHeaderProps<T>): React.ReactNode { }
function DetailHeader<T extends DetailHeaderData>({
data = {} as T,
statistics,
operations,
tagConfig,
}: DetailHeaderProps<T>): React.ReactNode {
return ( return (
<Card> <Card>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex items-start gap-4 flex-1"> <div className="flex items-start gap-4 flex-1">
<div <div
className={`w-16 h-16 text-white rounded-lg flex-center shadow-lg ${ className={`w-16 h-16 text-white rounded-lg flex-center shadow-lg ${
(data as any)?.iconColor data?.iconColor
? "" ? ""
: "bg-gradient-to-br from-sky-300 to-blue-500 text-white" : "bg-gradient-to-br from-sky-300 to-blue-500 text-white"
}`} }`}
style={(data as any)?.iconColor ? { backgroundColor: (data as any).iconColor } : undefined} style={data?.iconColor ? { backgroundColor: data.iconColor } : undefined}
> >
{<div className="w-[2.8rem] h-[2.8rem] text-gray-50">{(data as any)?.icon}</div> || ( {<div className="w-[2.8rem] h-[2.8rem] text-gray-50">{data?.icon}</div> || (
<Database className="w-8 h-8 text-white" /> <Database className="w-8 h-8 text-white" />
)} )}
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<h1 className="text-lg font-bold text-gray-900">{data?.name}</h1> <h1 className="text-lg font-bold text-gray-900">{data?.name}</h1>

View File

@@ -75,6 +75,30 @@ const OFFICE_PREVIEW_POLL_MAX_TIMES = 60;
type OfficePreviewStatus = "UNSET" | "PENDING" | "PROCESSING" | "READY" | "FAILED"; type OfficePreviewStatus = "UNSET" | "PENDING" | "PROCESSING" | "READY" | "FAILED";
const parseMetadata = (value?: string | Record<string, unknown>) => {
if (!value) {
return null;
}
if (typeof value === "object") {
return value as Record<string, unknown>;
}
if (typeof value !== "string") {
return null;
}
try {
const parsed = JSON.parse(value);
return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : 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 isOfficeFileName = (fileName?: string) => {
const lowerName = (fileName || "").toLowerCase(); const lowerName = (fileName || "").toLowerCase();
return OFFICE_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext)); return OFFICE_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext));
@@ -488,7 +512,7 @@ const KnowledgeSetDetail = () => {
setReadItemId(record.id); setReadItemId(record.id);
setReadTitle("知识条目"); setReadTitle("知识条目");
if (!record.sourceDatasetId || !record.sourceFileId) { if (!record.sourceDatasetId || !record.sourceFileId || isAnnotationItem(record)) {
const content = record.content || ""; const content = record.content || "";
setReadContent(truncatePreviewText(content, PREVIEW_TEXT_MAX_LENGTH)); setReadContent(truncatePreviewText(content, PREVIEW_TEXT_MAX_LENGTH));
setReadModalOpen(true); setReadModalOpen(true);
@@ -921,7 +945,7 @@ const KnowledgeSetDetail = () => {
]} ]}
tagConfig={{ tagConfig={{
showAdd: true, showAdd: true,
tags: (knowledgeSet?.tags || []) as any, tags: knowledgeSet?.tags || [],
onFetchTags: async () => { onFetchTags: async () => {
const res = await queryDatasetTagsUsingGet({ const res = await queryDatasetTagsUsingGet({
page: 0, page: 0,
@@ -950,10 +974,10 @@ const KnowledgeSetDetail = () => {
fetchKnowledgeSet(); fetchKnowledgeSet();
} }
}, },
onAddTag: async (tag: any) => { onAddTag: async (tagName: string) => {
if (knowledgeSet) { if (knowledgeSet) {
const currentTags = knowledgeSet.tags || []; const currentTags = knowledgeSet.tags || [];
const newTagName = typeof tag === "string" ? tag : tag?.name; const newTagName = tagName?.trim();
if (!newTagName) return; if (!newTagName) return;
await updateKnowledgeSetByIdUsingPut(knowledgeSet.id, { await updateKnowledgeSetByIdUsingPut(knowledgeSet.id, {
name: knowledgeSet.name, name: knowledgeSet.name,

View File

@@ -257,7 +257,7 @@ export default function KnowledgeManagementPage() {
return ( return (
<div className="h-full flex flex-col gap-4"> <div className="h-full flex flex-col gap-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h1 className="text-xl font-bold"></h1> <h1 className="text-xl font-bold"></h1>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Button onClick={() => navigate("/data/knowledge-management/search")}> <Button onClick={() => navigate("/data/knowledge-management/search")}>

View File

@@ -192,9 +192,6 @@ export default function CreateKnowledgeSet({
placeholder="请选择或输入标签" placeholder="请选择或输入标签"
/> />
</Form.Item> </Form.Item>
<Form.Item label="扩展元数据" name="metadata">
<Input.TextArea placeholder="请输入元数据(JSON)" rows={3} />
</Form.Item>
</Form> </Form>
</Modal> </Modal>
</> </>