Commit ecc45a58 by Finley Ge Committed by GitHub

chore: plugin (#7056)

* fix: workflow system tool use pluginModule

* fix: zip upload cancel && bump plugin sdk version
parent 602e4306
......@@ -13,7 +13,7 @@
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@bany/curl-to-json": "^1.2.8",
"@fastgpt-plugin/sdk-client": "0.0.1-alpha.8",
"@fastgpt-plugin/sdk-client": "0.0.1-alpha.9",
"axios": "catalog:",
"ipaddr.js": "catalog:",
"cron-parser": "^4.9.0",
......
Subproject commit 1ebfaac6a4e6faef2c01d4b6785add3debf7285f
Subproject commit 35458b567a3b719987bdd4e9a18dc747a8b34495
......@@ -15,11 +15,17 @@ import type { GetAdminSystemToolsResponseType } from '@fastgpt/global/openapi/co
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants';
import type { UploadPkgPluginResponseType } from '@fastgpt/global/openapi/core/plugin/admin/api';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import {
getUploadedPluginSourceName as getSourceName,
removeUploadedPluginFileByRow
} from './ImportPluginModal.utils';
type UploadPkgPluginItemType = UploadPkgPluginResponseType['plugins'][number];
type UploadPkgPluginFailureType = NonNullable<UploadPkgPluginResponseType['failed']>[number];
type UploadedPluginFile = SelectFileItemType & {
rowId: string;
status: 'uploading' | 'parsing' | 'success' | 'error' | 'duplicate';
sourceName?: string;
errorMsg?: string;
......@@ -35,9 +41,6 @@ type UploadedPluginFile = SelectFileItemType & {
const isPluginDuplicated = (tools: GetAdminSystemToolsResponseType, pluginId: string) =>
tools.some((tool) => tool.id === `${AppToolSourceEnum.systemTool}-${pluginId}`);
const getSourceName = (file: Pick<UploadedPluginFile, 'name' | 'sourceName'>) =>
file.sourceName || file.name;
const safeDecodeURIComponent = (value: string) => {
try {
return decodeURIComponent(value);
......@@ -48,6 +51,8 @@ const safeDecodeURIComponent = (value: string) => {
const isZipFileName = (name: string) => name.toLowerCase().endsWith('.zip');
const buildUploadRowId = (prefix: string) => `${prefix}-${getNanoid(8)}`;
const resolveSuccessSourceFiles = ({
files,
failedSourceNameSet,
......@@ -117,6 +122,7 @@ const ImportPluginModal = ({
return {
...file,
rowId: buildUploadRowId(file.rowId),
sourceName,
status: isDuplicated ? 'duplicate' : 'success',
toolId,
......@@ -180,6 +186,7 @@ const ImportPluginModal = ({
return {
...baseFile,
rowId: buildUploadRowId(baseFile.rowId),
name: displayName,
sourceName,
status: 'error',
......@@ -249,6 +256,7 @@ const ImportPluginModal = ({
? buildUploadedPluginFile(sourceFile, parseResult, nameSuffix)
: ({
file: files[0].file,
rowId: buildUploadRowId(files[0].rowId),
icon: parseResult.icon || files[0].icon,
name:
parseI18nString(parseResult.name || '', i18n.language) || parseResult.pluginId,
......@@ -312,6 +320,7 @@ const ImportPluginModal = ({
async (files: SelectFileItemType[]) => {
const newUploadedFiles: UploadedPluginFile[] = files.map((f) => ({
...f,
rowId: buildUploadRowId('upload'),
status: 'uploading' as const
}));
setUploadedFiles((prev) => [...prev, ...newUploadedFiles]);
......@@ -352,9 +361,15 @@ const ImportPluginModal = ({
};
const handleDelete = (file: UploadedPluginFile) => {
const sourceName = getSourceName(file);
setUploadedFiles((prev) => prev.filter((f) => (f.sourceName || f.name) !== sourceName));
setSelectFiles((prev) => prev.filter((f) => f.name !== sourceName));
const { nextUploadedFiles, sourceNameToRemove } = removeUploadedPluginFileByRow(
uploadedFiles,
file
);
setUploadedFiles(nextUploadedFiles);
if (sourceNameToRemove) {
setSelectFiles((prev) => prev.filter((f) => f.name !== sourceNameToRemove));
}
};
const { runAsync: handleConfirmImport, loading: confirmLoading } = useRequest(
......@@ -440,8 +455,8 @@ const ImportPluginModal = ({
{uploadedFiles.length > 0 && (
<VStack mt={1} gap={1}>
{uploadedFiles.map((item, index) => (
<Flex key={index} w={'full'} fontSize={'12px'}>
{uploadedFiles.map((item) => (
<Flex key={item.rowId} w={'full'} fontSize={'12px'}>
<Flex w={'20%'} px={1} py={'15px'} align={'center'} gap={2}>
<Avatar src={item.icon} borderRadius={'xs'} w={'20px'} />
<Box
......
export type UploadedPluginSourceItem = {
rowId: string;
name: string;
sourceName?: string;
};
export const getUploadedPluginSourceName = (
file: Pick<UploadedPluginSourceItem, 'name' | 'sourceName'>
) => file.sourceName || file.name;
/**
* 按上传结果行删除插件,zip 拆出的多条插件记录共享 sourceName,
* 因此删除时必须使用 rowId 定位单行,只在同源记录全部删除后释放源文件。
*/
export const removeUploadedPluginFileByRow = <T extends UploadedPluginSourceItem>(
files: T[],
file: T
) => {
const sourceName = getUploadedPluginSourceName(file);
const nextUploadedFiles = files.filter((item) => item.rowId !== file.rowId);
const hasSameSourceFile = nextUploadedFiles.some(
(item) => getUploadedPluginSourceName(item) === sourceName
);
return {
nextUploadedFiles,
sourceNameToRemove: hasSameSourceFile ? undefined : sourceName
};
};
import { describe, expect, it } from 'vitest';
import { removeUploadedPluginFileByRow } from '@/pageComponents/config/ImportPluginModal.utils';
describe('ImportPluginModal utils', () => {
it('should remove only the selected plugin row from a zip upload result', () => {
const files = [
{ rowId: 'zip-row-a', name: 'tools.zip / A', sourceName: 'tools.zip' },
{ rowId: 'zip-row-b', name: 'tools.zip / B', sourceName: 'tools.zip' },
{ rowId: 'single-row', name: 'single.pkg' }
];
const result = removeUploadedPluginFileByRow(files, files[0]);
expect(result.nextUploadedFiles.map((file) => file.rowId)).toEqual(['zip-row-b', 'single-row']);
expect(result.sourceNameToRemove).toBeUndefined();
});
it('should release the source zip after deleting the last plugin row from it', () => {
const files = [
{ rowId: 'zip-row-b', name: 'tools.zip / B', sourceName: 'tools.zip' },
{ rowId: 'single-row', name: 'single.pkg' }
];
const result = removeUploadedPluginFileByRow(files, files[0]);
expect(result.nextUploadedFiles.map((file) => file.rowId)).toEqual(['single-row']);
expect(result.sourceNameToRemove).toBe('tools.zip');
});
it('should release a standalone pkg after deleting it', () => {
const files = [
{ rowId: 'zip-row-b', name: 'tools.zip / B', sourceName: 'tools.zip' },
{ rowId: 'single-row', name: 'single.pkg' }
];
const result = removeUploadedPluginFileByRow(files, files[1]);
expect(result.nextUploadedFiles.map((file) => file.rowId)).toEqual(['zip-row-b']);
expect(result.sourceNameToRemove).toBe('single.pkg');
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment