You've already forked DataMate
* feat: Update site name to DataMate and refine text for AI data processing * feat: Refactor settings page and implement model access functionality - Created a new ModelAccess component for managing model configurations. - Removed the old Settings component and replaced it with a new SettingsPage component that integrates ModelAccess, SystemConfig, and WebhookConfig. - Added SystemConfig component for managing system settings. - Implemented WebhookConfig component for managing webhook configurations. - Updated API functions for model management in settings.apis.ts. - Adjusted routing to point to the new SettingsPage component. * feat: Implement Data Collection Page with Task Management and Execution Log - Created DataCollectionPage component to manage data collection tasks. - Added TaskManagement and ExecutionLog components for task handling and logging. - Integrated task operations including start, stop, edit, and delete functionalities. - Implemented filtering and searching capabilities in task management. - Introduced SimpleCronScheduler for scheduling tasks with cron expressions. - Updated CreateTask component to utilize new scheduling and template features. - Enhanced BasicInformation component to conditionally render fields based on visibility settings. - Refactored ImportConfiguration component to remove NAS import section. * feat: Update task creation API endpoint and enhance task creation form with new fields and validation * Refactor file upload and operator management components - Removed unnecessary console logs from file download and export functions. - Added size property to TaskItem interface for better task management. - Simplified TaskUpload component by utilizing useFileSliceUpload hook for file upload logic. - Enhanced OperatorPluginCreate component to handle file uploads and parsing more efficiently. - Updated ConfigureStep component to use Ant Design Form for better data handling and validation. - Improved PreviewStep component to navigate back to the operator market. - Added support for additional file types in UploadStep component. - Implemented delete operator functionality in OperatorMarketPage with confirmation prompts. - Cleaned up unused API functions in operator.api.ts to streamline the codebase. - Fixed number formatting utility to handle zero values correctly. * Refactor Knowledge Generation to Knowledge Base - Created new API service for Knowledge Base operations including querying, creating, updating, and deleting knowledge bases and files. - Added constants for Knowledge Base status and type mappings. - Defined models for Knowledge Base and related files. - Removed obsolete Knowledge Base creation and home components, replacing them with new implementations under the Knowledge Base structure. - Updated routing to reflect the new Knowledge Base paths. - Adjusted menu items to align with the new Knowledge Base terminology. - Modified ModelAccess interface to include modelName and type properties. * feat: Implement Knowledge Base Page with CRUD operations and data management - Added KnowledgeBasePage component for displaying and managing knowledge bases. - Integrated search and filter functionalities with SearchControls component. - Implemented CreateKnowledgeBase component for creating and editing knowledge bases. - Enhanced AddDataDialog for file uploads and dataset selections. - Introduced TableTransfer component for managing data transfers between tables. - Updated API functions for knowledge base operations, including file management. - Refactored knowledge base model to include file status and metadata. - Adjusted routing to point to the new KnowledgeBasePage. * feat: enhance OperatorPluginCreate and ConfigureStep for better upload handling and UI updates * refactor: remove unused components and clean up API logging in KnowledgeBase * feat: update icons in various components and improve styling for better UI consistency * fix: adjust upload step handling and improve error display in configuration step * feat: Add RatioTransfer component for dataset selection and configuration - Implemented RatioTransfer component to manage dataset selection and ratio configuration. - Integrated dataset fetching with search and filter capabilities. - Added RatioConfig component for displaying and updating selected datasets' configurations. - Enhanced SelectDataset component with improved UI and functionality for dataset selection. - Updated RatioTasksPage to utilize new ratio task status mapping and improved error handling for task deletion. - Refactored ratio model and constants for better type safety and clarity. - Changed Vite configuration to use local backend service for development. * feat: Add .editorconfig and enhance SystemConfig with table for settings display * feat: Enhance parameter configuration for range inputs and update default values * feat: Update site name to DataMate and refine text for AI data processing * Refactor file upload and operator management components - Removed unnecessary console logs from file download and export functions. - Added size property to TaskItem interface for better task management. - Simplified TaskUpload component by utilizing useFileSliceUpload hook for file upload logic. - Enhanced OperatorPluginCreate component to handle file uploads and parsing more efficiently. - Updated ConfigureStep component to use Ant Design Form for better data handling and validation. - Improved PreviewStep component to navigate back to the operator market. - Added support for additional file types in UploadStep component. - Implemented delete operator functionality in OperatorMarketPage with confirmation prompts. - Cleaned up unused API functions in operator.api.ts to streamline the codebase. - Fixed number formatting utility to handle zero values correctly. * Refactor Knowledge Generation to Knowledge Base - Created new API service for Knowledge Base operations including querying, creating, updating, and deleting knowledge bases and files. - Added constants for Knowledge Base status and type mappings. - Defined models for Knowledge Base and related files. - Removed obsolete Knowledge Base creation and home components, replacing them with new implementations under the Knowledge Base structure. - Updated routing to reflect the new Knowledge Base paths. - Adjusted menu items to align with the new Knowledge Base terminology. - Modified ModelAccess interface to include modelName and type properties. * feat: Implement Knowledge Base Page with CRUD operations and data management - Added KnowledgeBasePage component for displaying and managing knowledge bases. - Integrated search and filter functionalities with SearchControls component. - Implemented CreateKnowledgeBase component for creating and editing knowledge bases. - Enhanced AddDataDialog for file uploads and dataset selections. - Introduced TableTransfer component for managing data transfers between tables. - Updated API functions for knowledge base operations, including file management. - Refactored knowledge base model to include file status and metadata. - Adjusted routing to point to the new KnowledgeBasePage. * feat: enhance OperatorPluginCreate and ConfigureStep for better upload handling and UI updates * feat: update icons in various components and improve styling for better UI consistency * fix: adjust upload step handling and improve error display in configuration step * feat: Update site name to DataMate and refine text for AI data processing * Refactor file upload and operator management components - Removed unnecessary console logs from file download and export functions. - Added size property to TaskItem interface for better task management. - Simplified TaskUpload component by utilizing useFileSliceUpload hook for file upload logic. - Enhanced OperatorPluginCreate component to handle file uploads and parsing more efficiently. - Updated ConfigureStep component to use Ant Design Form for better data handling and validation. - Improved PreviewStep component to navigate back to the operator market. - Added support for additional file types in UploadStep component. - Implemented delete operator functionality in OperatorMarketPage with confirmation prompts. - Cleaned up unused API functions in operator.api.ts to streamline the codebase. - Fixed number formatting utility to handle zero values correctly. * Refactor Knowledge Generation to Knowledge Base - Created new API service for Knowledge Base operations including querying, creating, updating, and deleting knowledge bases and files. - Added constants for Knowledge Base status and type mappings. - Defined models for Knowledge Base and related files. - Removed obsolete Knowledge Base creation and home components, replacing them with new implementations under the Knowledge Base structure. - Updated routing to reflect the new Knowledge Base paths. - Adjusted menu items to align with the new Knowledge Base terminology. - Modified ModelAccess interface to include modelName and type properties. * feat: Implement Knowledge Base Page with CRUD operations and data management - Added KnowledgeBasePage component for displaying and managing knowledge bases. - Integrated search and filter functionalities with SearchControls component. - Implemented CreateKnowledgeBase component for creating and editing knowledge bases. - Enhanced AddDataDialog for file uploads and dataset selections. - Introduced TableTransfer component for managing data transfers between tables. - Updated API functions for knowledge base operations, including file management. - Refactored knowledge base model to include file status and metadata. - Adjusted routing to point to the new KnowledgeBasePage. * feat: enhance OperatorPluginCreate and ConfigureStep for better upload handling and UI updates * feat: update icons in various components and improve styling for better UI consistency * fix: adjust upload step handling and improve error display in configuration step * feat: add settings drawer and integrate SettingsPage component * feat: add ratio task management features including detail view and API integration
293 lines
9.6 KiB
TypeScript
293 lines
9.6 KiB
TypeScript
import React, { useState, useEffect, useRef } from "react";
|
|
import { Tag, Pagination, Tooltip, Empty, Popover, Spin } from "antd";
|
|
import { ClockCircleOutlined, StarFilled } from "@ant-design/icons";
|
|
import type { ItemType } from "antd/es/menu/interface";
|
|
import { formatDateTime } from "@/utils/unit";
|
|
import ActionDropdown from "./ActionDropdown";
|
|
import { Database } from "lucide-react";
|
|
|
|
interface BaseCardDataType {
|
|
id: string | number;
|
|
name: string;
|
|
type: string;
|
|
icon?: React.JSX.Element;
|
|
iconColor?: string;
|
|
status: {
|
|
label: string;
|
|
icon?: React.JSX.Element;
|
|
color?: string;
|
|
} | null;
|
|
description: string;
|
|
tags?: string[];
|
|
statistics?: { label: string; value: string | number }[];
|
|
updatedAt?: string;
|
|
}
|
|
|
|
interface CardViewProps<T> {
|
|
data: T[];
|
|
pagination: {
|
|
[key: string]: any;
|
|
current: number;
|
|
pageSize: number;
|
|
total: number;
|
|
};
|
|
operations:
|
|
| {
|
|
key: string;
|
|
label: string;
|
|
danger?: boolean;
|
|
icon?: React.JSX.Element;
|
|
onClick?: (item: T) => void;
|
|
}[]
|
|
| ((item: T) => ItemType[]);
|
|
loading?: boolean;
|
|
onView?: (item: T) => void;
|
|
onFavorite?: (item: T) => void;
|
|
isFavorite?: (item: T) => boolean;
|
|
}
|
|
|
|
// 标签渲染组件
|
|
const TagsRenderer = ({ tags }: { tags?: any[] }) => {
|
|
const [visibleTags, setVisibleTags] = useState<any[]>([]);
|
|
const [hiddenTags, setHiddenTags] = useState<any[]>([]);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!tags || tags.length === 0) return;
|
|
|
|
const calculateVisibleTags = () => {
|
|
if (!containerRef.current) return;
|
|
|
|
const containerWidth = containerRef.current.offsetWidth;
|
|
const tempDiv = document.createElement("div");
|
|
tempDiv.style.visibility = "hidden";
|
|
tempDiv.style.position = "absolute";
|
|
tempDiv.style.top = "-9999px";
|
|
tempDiv.className = "flex flex-wrap gap-1";
|
|
document.body.appendChild(tempDiv);
|
|
|
|
let totalWidth = 0;
|
|
let visibleCount = 0;
|
|
const tagElements: HTMLElement[] = [];
|
|
|
|
// 为每个tag创建临时元素来测量宽度
|
|
tags.forEach((tag, index) => {
|
|
const tagElement = document.createElement("span");
|
|
tagElement.className = "ant-tag ant-tag-default";
|
|
tagElement.style.margin = "2px";
|
|
tagElement.textContent = typeof tag === "string" ? tag : tag.name;
|
|
tempDiv.appendChild(tagElement);
|
|
tagElements.push(tagElement);
|
|
|
|
const tagWidth = tagElement.offsetWidth + 4; // 加上gap的宽度
|
|
|
|
// 如果不是最后一个标签,需要预留+n标签的空间
|
|
const plusTagWidth = index < tags.length - 1 ? 35 : 0; // +n标签大约35px宽度
|
|
|
|
if (totalWidth + tagWidth + plusTagWidth <= containerWidth) {
|
|
totalWidth += tagWidth;
|
|
visibleCount++;
|
|
} else {
|
|
// 如果当前标签放不下,且已经有可见标签,则停止
|
|
if (visibleCount > 0) return;
|
|
// 如果是第一个标签就放不下,至少显示一个
|
|
if (index === 0) {
|
|
totalWidth += tagWidth;
|
|
visibleCount = 1;
|
|
}
|
|
}
|
|
});
|
|
|
|
document.body.removeChild(tempDiv);
|
|
|
|
setVisibleTags(tags.slice(0, visibleCount));
|
|
setHiddenTags(tags.slice(visibleCount));
|
|
};
|
|
|
|
// 延迟执行以确保DOM已渲染
|
|
const timer = setTimeout(calculateVisibleTags, 0);
|
|
|
|
// 监听窗口大小变化
|
|
const handleResize = () => {
|
|
calculateVisibleTags();
|
|
};
|
|
|
|
window.addEventListener("resize", handleResize);
|
|
|
|
return () => {
|
|
clearTimeout(timer);
|
|
window.removeEventListener("resize", handleResize);
|
|
};
|
|
}, [tags]);
|
|
|
|
if (!tags || tags.length === 0) return null;
|
|
|
|
const popoverContent = (
|
|
<div className="max-w-xs">
|
|
<div className="flex flex-wrap gap-1">
|
|
{hiddenTags.map((tag, index) => (
|
|
<Tag key={index}>{typeof tag === "string" ? tag : tag.name}</Tag>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div ref={containerRef} className="flex flex-wrap gap-1 w-full">
|
|
{visibleTags.map((tag, index) => (
|
|
<Tag key={index}>{typeof tag === "string" ? tag : tag.name}</Tag>
|
|
))}
|
|
{hiddenTags.length > 0 && (
|
|
<Popover
|
|
content={popoverContent}
|
|
title="更多标签"
|
|
trigger="hover"
|
|
placement="topLeft"
|
|
>
|
|
<Tag className="cursor-pointer bg-gray-100 border-gray-300 text-gray-600 hover:bg-gray-200">
|
|
+{hiddenTags.length}
|
|
</Tag>
|
|
</Popover>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
function CardView<T extends BaseCardDataType>(props: CardViewProps<T>) {
|
|
const {
|
|
data,
|
|
pagination,
|
|
operations,
|
|
loading,
|
|
onView,
|
|
onFavorite,
|
|
isFavorite,
|
|
} = props;
|
|
|
|
if (data.length === 0) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center h-full text-gray-500">
|
|
<Empty />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ops = (item) =>
|
|
typeof operations === "function" ? operations(item) : operations;
|
|
|
|
return (
|
|
<div className="flex-overflow-hidden">
|
|
<div className="overflow-auto grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
|
|
{data.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className="border-card p-4 bg-white hover:shadow-lg transition-shadow duration-200"
|
|
>
|
|
<div className="flex flex-col space-y-4 h-full">
|
|
<div
|
|
className="flex flex-col space-y-4 h-full"
|
|
onClick={() => onView?.(item)}
|
|
style={{ cursor: onView ? "pointer" : "default" }}
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-center gap-3 min-w-0">
|
|
{item?.icon && (
|
|
<div
|
|
className={`flex-shrink-0 w-12 h-12 bg-gradient-to-br from-sky-300 to-blue-500 text-white rounded-lg flex items-center justify-center`}
|
|
>
|
|
<div className="w-6 h-6 text-gray-50">{item?.icon}</div>
|
|
</div>
|
|
)}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<h3
|
|
className={`text-base flex-1 text-ellipsis overflow-hidden whitespace-nowrap font-semibold text-gray-900 truncate`}
|
|
>
|
|
{item?.name}
|
|
</h3>
|
|
{item?.status && (
|
|
<Tag color={item?.status?.color}>
|
|
<div className="flex items-center gap-2 text-xs py-0.5">
|
|
{item?.status?.icon && (
|
|
<span>{item?.status?.icon}</span>
|
|
)}
|
|
<span>{item?.status?.label}</span>
|
|
</div>
|
|
</Tag>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{onFavorite && (
|
|
<StarFilled
|
|
style={{
|
|
fontSize: "16px",
|
|
color: isFavorite?.(item) ? "#ffcc00ff" : "#d1d5db",
|
|
cursor: "pointer",
|
|
}}
|
|
onClick={() => onFavorite?.(item)}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex-1 flex flex-col justify-end">
|
|
{/* Tags */}
|
|
<TagsRenderer tags={item?.tags || []} />
|
|
|
|
{/* Description */}
|
|
<p className="text-gray-600 text-xs text-ellipsis overflow-hidden whitespace-nowrap text-xs line-clamp-2 mt-2">
|
|
<Tooltip title={item?.description}>
|
|
{item?.description}
|
|
</Tooltip>
|
|
</p>
|
|
|
|
{/* Statistics */}
|
|
<div className="grid grid-cols-2 gap-4 py-3">
|
|
{item?.statistics?.map((stat, idx) => (
|
|
<div key={idx}>
|
|
<div className="text-sm text-gray-500 overflow-hidden whitespace-nowrap text-ellipsis w-full">
|
|
{stat?.label}:
|
|
</div>
|
|
<div className="text-base font-semibold text-gray-900 overflow-hidden whitespace-nowrap text-ellipsis w-full">
|
|
{stat?.value}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex items-center justify-between pt-3 border-t border-t-gray-200">
|
|
<div className=" text-gray-500 text-right">
|
|
<div className="flex items-center gap-1">
|
|
<ClockCircleOutlined className="w-4 h-4" />{" "}
|
|
{formatDateTime(item?.updatedAt)}
|
|
</div>
|
|
</div>
|
|
{operations && (
|
|
<ActionDropdown
|
|
actions={ops(item)}
|
|
onAction={(key) => {
|
|
const operation = ops(item).find((op) => op.key === key);
|
|
if (operation?.onClick) {
|
|
operation.onClick(item);
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="flex justify-end mt-6">
|
|
<Pagination {...pagination} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default CardView;
|