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 @@ ...@@ -13,7 +13,7 @@
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.1.0", "@apidevtools/swagger-parser": "^10.1.0",
"@bany/curl-to-json": "^1.2.8", "@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:", "axios": "catalog:",
"ipaddr.js": "catalog:", "ipaddr.js": "catalog:",
"cron-parser": "^4.9.0", "cron-parser": "^4.9.0",
......
Subproject commit 1ebfaac6a4e6faef2c01d4b6785add3debf7285f Subproject commit 35458b567a3b719987bdd4e9a18dc747a8b34495
...@@ -15,11 +15,17 @@ import type { GetAdminSystemToolsResponseType } from '@fastgpt/global/openapi/co ...@@ -15,11 +15,17 @@ import type { GetAdminSystemToolsResponseType } from '@fastgpt/global/openapi/co
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants'; import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants';
import type { UploadPkgPluginResponseType } from '@fastgpt/global/openapi/core/plugin/admin/api'; 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 UploadPkgPluginItemType = UploadPkgPluginResponseType['plugins'][number];
type UploadPkgPluginFailureType = NonNullable<UploadPkgPluginResponseType['failed']>[number]; type UploadPkgPluginFailureType = NonNullable<UploadPkgPluginResponseType['failed']>[number];
type UploadedPluginFile = SelectFileItemType & { type UploadedPluginFile = SelectFileItemType & {
rowId: string;
status: 'uploading' | 'parsing' | 'success' | 'error' | 'duplicate'; status: 'uploading' | 'parsing' | 'success' | 'error' | 'duplicate';
sourceName?: string; sourceName?: string;
errorMsg?: string; errorMsg?: string;
...@@ -35,9 +41,6 @@ type UploadedPluginFile = SelectFileItemType & { ...@@ -35,9 +41,6 @@ type UploadedPluginFile = SelectFileItemType & {
const isPluginDuplicated = (tools: GetAdminSystemToolsResponseType, pluginId: string) => const isPluginDuplicated = (tools: GetAdminSystemToolsResponseType, pluginId: string) =>
tools.some((tool) => tool.id === `${AppToolSourceEnum.systemTool}-${pluginId}`); tools.some((tool) => tool.id === `${AppToolSourceEnum.systemTool}-${pluginId}`);
const getSourceName = (file: Pick<UploadedPluginFile, 'name' | 'sourceName'>) =>
file.sourceName || file.name;
const safeDecodeURIComponent = (value: string) => { const safeDecodeURIComponent = (value: string) => {
try { try {
return decodeURIComponent(value); return decodeURIComponent(value);
...@@ -48,6 +51,8 @@ const safeDecodeURIComponent = (value: string) => { ...@@ -48,6 +51,8 @@ const safeDecodeURIComponent = (value: string) => {
const isZipFileName = (name: string) => name.toLowerCase().endsWith('.zip'); const isZipFileName = (name: string) => name.toLowerCase().endsWith('.zip');
const buildUploadRowId = (prefix: string) => `${prefix}-${getNanoid(8)}`;
const resolveSuccessSourceFiles = ({ const resolveSuccessSourceFiles = ({
files, files,
failedSourceNameSet, failedSourceNameSet,
...@@ -117,6 +122,7 @@ const ImportPluginModal = ({ ...@@ -117,6 +122,7 @@ const ImportPluginModal = ({
return { return {
...file, ...file,
rowId: buildUploadRowId(file.rowId),
sourceName, sourceName,
status: isDuplicated ? 'duplicate' : 'success', status: isDuplicated ? 'duplicate' : 'success',
toolId, toolId,
...@@ -180,6 +186,7 @@ const ImportPluginModal = ({ ...@@ -180,6 +186,7 @@ const ImportPluginModal = ({
return { return {
...baseFile, ...baseFile,
rowId: buildUploadRowId(baseFile.rowId),
name: displayName, name: displayName,
sourceName, sourceName,
status: 'error', status: 'error',
...@@ -249,6 +256,7 @@ const ImportPluginModal = ({ ...@@ -249,6 +256,7 @@ const ImportPluginModal = ({
? buildUploadedPluginFile(sourceFile, parseResult, nameSuffix) ? buildUploadedPluginFile(sourceFile, parseResult, nameSuffix)
: ({ : ({
file: files[0].file, file: files[0].file,
rowId: buildUploadRowId(files[0].rowId),
icon: parseResult.icon || files[0].icon, icon: parseResult.icon || files[0].icon,
name: name:
parseI18nString(parseResult.name || '', i18n.language) || parseResult.pluginId, parseI18nString(parseResult.name || '', i18n.language) || parseResult.pluginId,
...@@ -312,6 +320,7 @@ const ImportPluginModal = ({ ...@@ -312,6 +320,7 @@ const ImportPluginModal = ({
async (files: SelectFileItemType[]) => { async (files: SelectFileItemType[]) => {
const newUploadedFiles: UploadedPluginFile[] = files.map((f) => ({ const newUploadedFiles: UploadedPluginFile[] = files.map((f) => ({
...f, ...f,
rowId: buildUploadRowId('upload'),
status: 'uploading' as const status: 'uploading' as const
})); }));
setUploadedFiles((prev) => [...prev, ...newUploadedFiles]); setUploadedFiles((prev) => [...prev, ...newUploadedFiles]);
...@@ -352,9 +361,15 @@ const ImportPluginModal = ({ ...@@ -352,9 +361,15 @@ const ImportPluginModal = ({
}; };
const handleDelete = (file: UploadedPluginFile) => { const handleDelete = (file: UploadedPluginFile) => {
const sourceName = getSourceName(file); const { nextUploadedFiles, sourceNameToRemove } = removeUploadedPluginFileByRow(
setUploadedFiles((prev) => prev.filter((f) => (f.sourceName || f.name) !== sourceName)); uploadedFiles,
setSelectFiles((prev) => prev.filter((f) => f.name !== sourceName)); file
);
setUploadedFiles(nextUploadedFiles);
if (sourceNameToRemove) {
setSelectFiles((prev) => prev.filter((f) => f.name !== sourceNameToRemove));
}
}; };
const { runAsync: handleConfirmImport, loading: confirmLoading } = useRequest( const { runAsync: handleConfirmImport, loading: confirmLoading } = useRequest(
...@@ -440,8 +455,8 @@ const ImportPluginModal = ({ ...@@ -440,8 +455,8 @@ const ImportPluginModal = ({
{uploadedFiles.length > 0 && ( {uploadedFiles.length > 0 && (
<VStack mt={1} gap={1}> <VStack mt={1} gap={1}>
{uploadedFiles.map((item, index) => ( {uploadedFiles.map((item) => (
<Flex key={index} w={'full'} fontSize={'12px'}> <Flex key={item.rowId} w={'full'} fontSize={'12px'}>
<Flex w={'20%'} px={1} py={'15px'} align={'center'} gap={2}> <Flex w={'20%'} px={1} py={'15px'} align={'center'} gap={2}>
<Avatar src={item.icon} borderRadius={'xs'} w={'20px'} /> <Avatar src={item.icon} borderRadius={'xs'} w={'20px'} />
<Box <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