You've already forked DataMate
Compare commits
3 Commits
9da187d2c6
...
e71116d117
| Author | SHA1 | Date | |
|---|---|---|---|
| e71116d117 | |||
| cac53d7aac | |||
| 43b4a619bc |
@@ -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 = () => {
|
||||||
|
|||||||
@@ -24,21 +24,28 @@ interface OperationItem {
|
|||||||
|
|
||||||
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;
|
||||||
}>;
|
|
||||||
onAddTag?: (tag: { id: number; name: string; color: string }) => void;
|
|
||||||
onCreateAndTag?: (tagName: string) => void;
|
onCreateAndTag?: (tagName: string) => void;
|
||||||
}
|
}
|
||||||
interface DetailHeaderProps<T> {
|
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<T extends DetailHeaderData> {
|
||||||
data: T;
|
data: T;
|
||||||
statistics: StatisticItem[];
|
statistics: StatisticItem[];
|
||||||
operations: OperationItem[];
|
operations: OperationItem[];
|
||||||
tagConfig?: TagConfig;
|
tagConfig?: TagConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DetailHeader<T>({
|
function DetailHeader<T extends DetailHeaderData>({
|
||||||
data = {} as T,
|
data = {} as T,
|
||||||
statistics,
|
statistics,
|
||||||
operations,
|
operations,
|
||||||
@@ -50,13 +57,13 @@ function DetailHeader<T>({
|
|||||||
<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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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")}>
|
||||||
全库搜索
|
全库搜索
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user