Publish histories (#1331)

* fix http plugin edge (#95)

* fix http plugin edge

* use getHandleId

* perf: i18n file

* feat: histories list

* perf: request lock

* fix: ts

* move box components

* fix: edit form refresh

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-04-30 12:42:13 +08:00
committed by GitHub
parent a0c1320d47
commit db6fc53840
46 changed files with 741 additions and 129 deletions

View File

@@ -17,7 +17,7 @@
"", "",
"type Response = {};", "type Response = {};",
"", "",
"async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {", "async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<Response> {",
" $1", " $1",
" return {}", " return {}",
"}", "}",

View File

@@ -4,7 +4,7 @@
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"prettier.prettierPath": "", "prettier.prettierPath": "",
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"projects/app/public/locales", "projects/app/i18n",
], ],
"i18n-ally.enabledParsers": ["json"], "i18n-ally.enabledParsers": ["json"],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",

View File

@@ -2,6 +2,7 @@ import { StoreNodeItemType } from '../workflow/type';
import { StoreEdgeItemType } from '../workflow/type/edge'; import { StoreEdgeItemType } from '../workflow/type/edge';
export type AppVersionSchemaType = { export type AppVersionSchemaType = {
_id: string;
appId: string; appId: string;
time: Date; time: Date;
nodes: StoreNodeItemType[]; nodes: StoreNodeItemType[];

View File

@@ -14,6 +14,7 @@ import { CreateOnePluginParams } from '../controller';
import { StoreNodeItemType } from '../../workflow/type'; import { StoreNodeItemType } from '../../workflow/type';
import { HttpImgUrl } from '../../../common/file/image/constants'; import { HttpImgUrl } from '../../../common/file/image/constants';
import SwaggerParser from '@apidevtools/swagger-parser'; import SwaggerParser from '@apidevtools/swagger-parser';
import { getHandleId } from '../../../core/workflow/utils';
export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema> => { export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema> => {
try { try {
@@ -378,14 +379,14 @@ export const httpApiSchema2Plugins = async ({
{ {
source: pluginInputId, source: pluginInputId,
target: httpId, target: httpId,
sourcePort: `${pluginInputId}-source-right`, sourceHandle: getHandleId(pluginInputId, 'source', 'right'),
targetPort: `${httpId}-target-left` targetHandle: getHandleId(httpId, 'target', 'left')
}, },
{ {
source: httpId, source: httpId,
target: pluginOutputId, target: pluginOutputId,
sourcePort: `${httpId}-source-right`, sourceHandle: getHandleId(httpId, 'source', 'right'),
targetPort: `${pluginOutputId}-target-left` targetHandle: getHandleId(pluginOutputId, 'target', 'left')
} }
]; ];

View File

@@ -83,41 +83,3 @@ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [
lafModule, lafModule,
ifElseNode ifElseNode
]; ];
export const moduleTemplatesList: nodeTemplateListType = [
{
type: FlowNodeTemplateTypeEnum.systemInput,
label: 'core.module.template.System input module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.textAnswer,
label: 'core.module.template.Response module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.functionCall,
label: 'core.module.template.Function module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.tools,
label: 'core.module.template.Tool module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.externalCall,
label: 'core.module.template.External module',
list: []
},
{
type: FlowNodeTemplateTypeEnum.personalPlugin,
label: '',
list: []
},
{
type: FlowNodeTemplateTypeEnum.other,
label: '其他',
list: []
}
];

8
packages/web/common/fetch/type.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
export type PaginationProps<T = {}> = T & {
current: number;
pageSize: number;
};
export type PaginationResponse<T = any> = {
total: number;
list: T[];
};

View File

@@ -142,10 +142,12 @@ export const iconPaths = {
import('./icons/core/workflow/inputType/selectLLM.svg'), import('./icons/core/workflow/inputType/selectLLM.svg'),
'core/workflow/inputType/switch': () => import('./icons/core/workflow/inputType/switch.svg'), 'core/workflow/inputType/switch': () => import('./icons/core/workflow/inputType/switch.svg'),
'core/workflow/inputType/textarea': () => import('./icons/core/workflow/inputType/textarea.svg'), 'core/workflow/inputType/textarea': () => import('./icons/core/workflow/inputType/textarea.svg'),
'core/workflow/revertVersion': () => import('./icons/core/workflow/revertVersion.svg'),
'core/workflow/runError': () => import('./icons/core/workflow/runError.svg'), 'core/workflow/runError': () => import('./icons/core/workflow/runError.svg'),
'core/workflow/runSkip': () => import('./icons/core/workflow/runSkip.svg'), 'core/workflow/runSkip': () => import('./icons/core/workflow/runSkip.svg'),
'core/workflow/runSuccess': () => import('./icons/core/workflow/runSuccess.svg'), 'core/workflow/runSuccess': () => import('./icons/core/workflow/runSuccess.svg'),
'core/workflow/running': () => import('./icons/core/workflow/running.svg'), 'core/workflow/running': () => import('./icons/core/workflow/running.svg'),
'core/workflow/versionHistories': () => import('./icons/core/workflow/versionHistories.svg'),
date: () => import('./icons/date.svg'), date: () => import('./icons/date.svg'),
delete: () => import('./icons/delete.svg'), delete: () => import('./icons/delete.svg'),
edit: () => import('./icons/edit.svg'), edit: () => import('./icons/edit.svg'),

View File

@@ -1,6 +1,4 @@
<svg t="1714186779322" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1213" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 19" fill="none">
width="128" height="128"> <path fill-rule="evenodd" clip-rule="evenodd"
<path d="M13.8412 2.25558L14.04 2.28053C14.5655 2.35304 15.261 2.4505 15.6547 2.84502C15.9775 3.16702 16.1015 3.69097 16.1756 4.15878L16.2192 4.45974C16.3175 5.16925 16.3432 6.09863 16.1756 7.13093C15.845 9.16824 14.7604 11.5993 12.037 13.4814C12.0222 13.628 12.0214 13.7762 12.0245 13.9243L12.0323 14.1457C12.0448 14.4864 12.0573 14.8264 11.9622 15.1578C11.814 15.6723 11.2862 16.0115 10.7926 16.2548L10.5509 16.3694L10.2391 16.5066C9.65587 16.7545 8.86839 17.0087 8.37485 16.5144C8.07857 16.2189 7.95694 15.7854 7.85792 15.3542L7.82128 15.1928C7.77999 14.9891 7.72794 14.7876 7.66534 14.5894C7.62636 14.4755 7.58426 14.3594 7.53903 14.2424C7.48926 14.3035 7.43643 14.3621 7.38076 14.4178C7.11177 14.6868 6.71023 14.8747 6.37965 15.0065C6.01865 15.1492 5.6101 15.2739 5.23196 15.3768L5.03781 15.4283L4.66591 15.5211L4.32908 15.5991L3.92599 15.6848L3.67415 15.7339C3.54855 15.7572 3.41918 15.7495 3.29719 15.7117C3.17521 15.6738 3.06427 15.6068 2.97395 15.5165C2.88363 15.4261 2.81664 15.3152 2.77875 15.1932C2.74086 15.0712 2.73321 14.9419 2.75646 14.8163L2.82352 14.4802L2.94359 13.936L3.04027 13.5384L3.11356 13.2577C3.21648 12.8803 3.34123 12.4718 3.48469 12.1115C3.61567 11.7802 3.80358 11.3786 4.07257 11.1096L4.13494 11.0496L4.08504 11.0293C3.95179 10.9787 3.81683 10.9326 3.68039 10.8913L3.46442 10.8251C2.92332 10.6613 2.34479 10.4851 1.98536 10.1249C1.54874 9.68906 1.6961 9.02556 1.90661 8.47354L1.99238 8.25991L2.13038 7.94803L2.24499 7.70633C2.48825 7.21357 2.82742 6.68573 3.34201 6.53759C3.61489 6.45962 3.8987 6.45494 4.18406 6.46274L4.35559 6.4682C4.57858 6.47599 4.80079 6.48457 5.01832 6.46352C6.90047 3.73931 9.33152 2.65477 11.3688 2.32419C12.1861 2.19014 13.0177 2.16706 13.8412 2.25558ZM6.19096 12.138C6.06825 12.0472 5.9216 11.9944 5.76915 11.9861C5.61671 11.9777 5.46518 12.0143 5.33331 12.0913L5.24755 12.149L5.17504 12.2137L5.07758 12.3369C4.87486 12.63 4.75947 13.0355 4.6698 13.4144L4.5856 13.7793L4.54583 13.9453L4.69475 13.9095L5.02066 13.8346C5.47132 13.7294 5.97109 13.5929 6.27829 13.3161C6.41186 13.1826 6.49243 13.0052 6.50505 12.8168C6.51767 12.6284 6.46147 12.4418 6.3469 12.2916L6.28297 12.2184L6.26425 12.2004L6.19096 12.138ZM11.7914 6.70054C11.6466 6.55571 11.4748 6.44082 11.2856 6.36242C11.0964 6.28402 10.8936 6.24365 10.6888 6.24361C10.4841 6.24357 10.2813 6.28387 10.0921 6.36221C9.90285 6.44054 9.73092 6.55537 9.58609 6.70015C9.44126 6.84493 9.32636 7.01682 9.24796 7.206C9.16956 7.39518 9.12919 7.59795 9.12915 7.80274C9.12912 8.00752 9.16942 8.21031 9.24775 8.39952C9.32609 8.58873 9.44092 8.76065 9.5857 8.90548C9.87809 9.19798 10.2747 9.36235 10.6883 9.36242C11.1019 9.36249 11.4985 9.19827 11.791 8.90587C12.0835 8.61348 12.2479 8.21687 12.248 7.80329C12.248 7.38971 12.0838 6.99304 11.7914 6.70054Z" />
d="M957.78379975 134.65795581c-2.56331135 83.17185735-20.44423515 163.12467052-50.30317593 240.57510884-35.69958418 92.67796903-87.96862781 174.24030494-161.20251807 241.63355181-5.94396957 5.46839892-7.80783541 11.32361235-7.01830818 19.026796 2.5328431 24.92705029 4.5596486 49.91503711 7.12295994 74.84208611 5.9598665 58.12425835-14.32010468 105.71575589-61.01609761 139.47861502-50.06340414 36.20694738-102.1695094 69.6412786-153.84243498 103.55515469-32.57194606 21.38212813-72.60730269 1.98176462-76.2277327-36.80306673-4.17150847-44.53539301-7.45016355-89.16219079-10.5340864-133.78633995-1.53533793-22.33459502-0.93921859-22.52932723-22.48428636-25.38937392-57.18636407-7.56938794-101.94563325-34.8213021-132.68418171-84.27401429-15.81040175-25.46488176-23.6473811-53.61097493-26.40410034-83.18642866-0.81866989-8.86495278-4.3808133-11.13020574-12.70925895-11.59252939-46.28003447-2.57788397-92.52960201-5.3332789-138.74869996-8.8199132-38.44173211-2.92098321-57.93085045-43.61339568-37.32367695-77.56966271 14.4671474-23.86993165 29.87483771-47.17288918 44.81888136-70.76065808 16.1071371-25.38937393 32.07915418-50.85425569 48.21675953-76.21316007 29.53173846-46.41383019 72.1012638-71.48924752 126.5905208-71.93699984 31.2008721-0.23844748 62.46003078 5.27366671 93.64633157 8.64107663 5.89892998 0.64115892 10.0863341-0.16426396 14.16908774-4.73848387 72.39799786-81.24970362 162.00661664-136.08206117 263.446211-172.76457792 57.33473241-20.74096921 116.33859714-34.28479495 176.89237242-40.54271983 30.51467231-3.17400331 61.23864946-4.32120112 91.72285352 1.34060572 16.61450031 3.08392285 17.46363973 3.77012135 20.02562678 20.05609502C956.4869093 101.71509088 958.51238919 118.07524806 957.78379975 134.65795581L957.78379975 134.65795581zM813.20902898 339.81696881c0.87828208-71.05871775-57.73744384-132.1635715-127.410515-133.309445-72.35428259-1.20680999-135.02361821 51.61331338-136.73779142 134.08439962-1.44525748 68.77756914 60.61338619 131.07466028 129.88374722 131.55155523C752.69897026 472.67866149 812.29895435 413.94371248 813.20902898 339.81696881L813.20902898 339.81696881zM195.18722288 640.09538918c13.69219278 0.17883529 21.29204896 6.24335355 26.19479682 20.29321819 19.25067215 55.04033549 54.28127779 96.80443794 105.06002435 125.11479508 11.20438926 6.24335355 23.0207935 11.30904103 35.22401354 15.48187512 21.93320788 7.50845143 26.93928317 28.96476307 10.66788212 45.47328728-24.10705481 24.43558273-48.46977833 48.60357404-72.56226181 73.02590976-7.97209938 8.07542683-16.74829602 11.90383476-27.95268527 7.71775497-11.38322454-4.2470176-16.88209172-12.72383026-17.46363974-24.55480712-0.35767186-7.49520313-0.17883529-15.03544713-0.35767188-22.55846986-0.31263099-13.81274148-0.44775102-13.91739325-13.11197036-9.71541653-27.08765021 9.01464541-54.11701383 18.10347435-81.20466405 27.10354714-7.9866707 2.66796441-16.00248532 3.77012135-23.81032074-0.67030285-11.44416105-6.51226928-15.76403785-18.14851392-11.160674-32.46729429 8.99874849-27.93811395 18.43200097-55.71196266 27.64005428-83.57456879 3.99267318-12.09856827 3.7396531-12.35158706-9.26766421-12.55956629-7.83830367-0.11922438-15.70442566 0.01457133-23.54140371-0.37224318-11.4136928-0.55107846-19.5341592-6.09498652-23.75070856-16.94170392-4.14104022-10.56455466-1.49029705-19.77260668 6.19831398-27.54997384 25.15092515-25.40526957 50.42240031-50.68999172 75.6620816-76.00518086C182.68726879 642.31560258 188.64713398 639.45423026 195.18722288 640.09538918L195.18722288 640.09538918z"
p-id="1214"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M4.31146 8.0038C4.78201 5.34167 7.10812 3.31934 9.90528 3.31934C13.0427 3.31934 15.586 5.8627 15.586 9.0001C15.586 12.1375 13.0427 14.6809 9.90528 14.6809C8.99212 14.6809 8.01118 14.3988 7.15115 13.9231C6.28734 13.4453 5.60309 12.8045 5.234 12.1428C5.0322 11.7811 4.57537 11.6514 4.21364 11.8532C3.8519 12.055 3.72225 12.5119 3.92404 12.8736C4.46219 13.8383 5.38209 14.6588 6.42511 15.2357C7.47191 15.8147 8.70047 16.1809 9.90528 16.1809C13.8711 16.1809 17.086 12.9659 17.086 9.0001C17.086 5.03428 13.8711 1.81934 9.90528 1.81934C6.43524 1.81934 3.54116 4.28003 2.87078 7.55181L2.46483 6.84336C2.2589 6.48396 1.80061 6.35956 1.44122 6.56549C1.08182 6.77142 0.957418 7.22971 1.16335 7.58911L2.4808 9.88833C2.62411 10.1384 2.8896 10.2747 3.15895 10.2651C3.29431 10.27 3.43265 10.2383 3.55876 10.1658L5.88309 8.83098C6.24228 8.6247 6.36624 8.16629 6.15996 7.80709C5.95367 7.4479 5.49526 7.32394 5.13607 7.53023L4.31146 8.0038ZM8.96233 9.52028C8.96177 9.55065 8.96306 9.58096 8.96616 9.61102C8.97694 9.71784 9.01015 9.81804 9.06098 9.90684C9.11368 9.99929 9.18665 10.0815 9.27792 10.1462C9.30081 10.1625 9.32465 10.1775 9.34934 10.1912L11.4739 11.4178C11.8326 11.6249 12.2913 11.502 12.4984 11.1433C12.7055 10.7846 12.5826 10.3259 12.2239 10.1188L10.4623 9.10176V6.92775C10.4623 6.51353 10.1265 6.17775 9.71233 6.17775C9.29811 6.17775 8.96233 6.51353 8.96233 6.92775V9.52028Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8.99992 2.08659C5.18166 2.08659 2.08634 5.1819 2.08634 9.00017C2.08634 12.8184 5.18166 15.9137 8.99992 15.9137C12.8182 15.9137 15.9135 12.8184 15.9135 9.00017C15.9135 5.1819 12.8182 2.08659 8.99992 2.08659ZM0.419678 9.00017C0.419678 4.26143 4.26118 0.419922 8.99992 0.419922C13.7387 0.419922 17.5802 4.26143 17.5802 9.00017C17.5802 13.7389 13.7387 17.5804 8.99992 17.5804C4.26118 17.5804 0.419678 13.7389 0.419678 9.00017ZM8.99992 3.51869C9.46016 3.51869 9.83326 3.89178 9.83326 4.35202V8.48514L12.4714 9.80419C12.883 10.01 13.0499 10.5106 12.844 10.9222C12.6382 11.3339 12.1377 11.5007 11.726 11.2949L8.62725 9.74552C8.34493 9.60436 8.16659 9.31581 8.16659 9.00017V4.35202C8.16659 3.89178 8.53969 3.51869 8.99992 3.51869Z"
fill="#3370FF" />
</svg>

After

Width:  |  Height:  |  Size: 891 B

View File

@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 19" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" <path fill-rule="evenodd" clip-rule="evenodd"
d="M10 3.08634C6.18178 3.08634 3.08647 6.18166 3.08647 9.99992C3.08647 13.8182 6.18178 16.9135 10 16.9135C13.8183 16.9135 16.9136 13.8182 16.9136 9.99992C16.9136 6.18166 13.8183 3.08634 10 3.08634ZM1.4198 9.99992C1.4198 5.26118 5.26131 1.41968 10 1.41968C14.7388 1.41968 18.5803 5.26118 18.5803 9.99992C18.5803 14.7387 14.7388 18.5802 10 18.5802C5.26131 18.5802 1.4198 14.7387 1.4198 9.99992ZM10 4.51844C10.4603 4.51844 10.8334 4.89154 10.8334 5.35178V9.48489L13.4715 10.8039C13.8831 11.0098 14.05 11.5103 13.8442 11.922C13.6383 12.3336 13.1378 12.5005 12.7261 12.2947L9.62737 10.7453C9.34505 10.6041 9.16671 10.3156 9.16671 9.99992V5.35178C9.16671 4.89154 9.53981 4.51844 10 4.51844Z" /> d="M9.00005 3.27783C5.56362 3.27783 2.77783 6.06362 2.77783 9.50005C2.77783 12.9365 5.56362 15.7223 9.00005 15.7223C12.4365 15.7223 15.2223 12.9365 15.2223 9.50005C15.2223 6.06362 12.4365 3.27783 9.00005 3.27783ZM1.27783 9.50005C1.27783 5.23519 4.73519 1.77783 9.00005 1.77783C13.2649 1.77783 16.7223 5.23519 16.7223 9.50005C16.7223 13.7649 13.2649 17.2223 9.00005 17.2223C4.73519 17.2223 1.27783 13.7649 1.27783 9.50005ZM9.00005 4.56672C9.41427 4.56672 9.75005 4.90251 9.75005 5.31672V9.03653L12.1244 10.2237C12.4948 10.4089 12.645 10.8594 12.4598 11.2299C12.2745 11.6004 11.824 11.7506 11.4535 11.5653L8.66464 10.1709C8.41056 10.0438 8.25005 9.78413 8.25005 9.50005V5.31672C8.25005 4.90251 8.58584 4.56672 9.00005 4.56672Z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 866 B

View File

@@ -0,0 +1,19 @@
import React, { forwardRef } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import Loading from '../MyLoading';
type Props = BoxProps & {
isLoading?: boolean;
text?: string;
};
const MyBox = ({ text, isLoading, children, ...props }: Props, ref: any) => {
return (
<Box ref={ref} position={'relative'} {...props}>
{isLoading && <Loading fixed={false} text={text} />}
{children}
</Box>
);
};
export default forwardRef(MyBox);

View File

@@ -0,0 +1,80 @@
import React from 'react';
import MyIcon from '../Icon';
import { Flex, Image, Box, CloseButton, FlexProps } from '@chakra-ui/react';
import { useLoading } from '../../../hooks/useLoading';
type Props = FlexProps & {
onClose: () => void;
iconSrc?: string;
title?: any;
isLoading?: boolean;
showMask?: boolean;
};
const CustomRightDrawer = ({
onClose,
iconSrc,
title,
maxW = ['90vw', '30vw'],
children,
isLoading,
showMask = true,
...props
}: Props) => {
const { Loading } = useLoading();
return (
<Flex
flexDirection={'column'}
position={'fixed'}
right={0}
bg={'white'}
zIndex={100}
maxW={maxW}
w={'100%'}
h={'90vh'}
borderLeftRadius={'lg'}
border={'base'}
boxShadow={'2'}
{...props}
>
<Flex
display={'flex'}
alignItems={'center'}
fontWeight={500}
background={'#FBFBFC'}
borderBottom={'1px solid #F4F6F8'}
roundedTop={'lg'}
py={'10px'}
px={5}
>
{iconSrc && (
<>
{iconSrc.startsWith('/') ? (
<Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />
) : (
<MyIcon mr={3} name={iconSrc as any} w={'20px'} />
)}
</>
)}
<Box flex={'1'} fontSize={'lg'}>
{title}
</Box>
<CloseButton position={'relative'} fontSize={'sm'} top={0} right={0} onClick={onClose} />
</Flex>
<Box
flex={'1 0 0'}
py={props.py ?? 3}
px={props.px ?? 5}
overflow={props?.overflow ?? 'auto'}
display={'flex'}
flexDirection={'column'}
>
{children}
</Box>
<Loading loading={isLoading} fixed={false} />
</Flex>
);
};
export default React.memo(CustomRightDrawer);

View File

@@ -0,0 +1,41 @@
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { nodeTemplateListType } from '@fastgpt/global/core/workflow/type';
import { TFunction } from 'next-i18next';
export const workflowNodeTemplateList = (t: TFunction): nodeTemplateListType => [
{
type: FlowNodeTemplateTypeEnum.systemInput,
label: t('core.module.template.System input module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.textAnswer,
label: t('core.module.template.Response module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.functionCall,
label: t('core.module.template.Function module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.tools,
label: t('core.module.template.Tool module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.externalCall,
label: t('core.module.template.External module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.personalPlugin,
label: '',
list: []
},
{
type: FlowNodeTemplateTypeEnum.other,
label: t('common.Other'),
list: []
}
];

View File

@@ -0,0 +1,122 @@
import { useRef, useState, useEffect } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { useToast } from './useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { PaginationProps, PaginationResponse } from '../common/fetch/type';
import { useBoolean, useLockFn, useMemoizedFn, useMount, useScroll, useVirtualList } from 'ahooks';
import MyBox from '../components/common/MyBox';
import { useTranslation } from 'next-i18next';
export function useScrollPagination<
TParams extends PaginationProps,
TData extends PaginationResponse
>(
api: (data: TParams) => Promise<TData>,
{
itemHeight = 50,
overscan = 10,
pageSize = 10,
defaultParams = {}
}: {
itemHeight: number;
overscan?: number;
pageSize?: number;
defaultParams?: Record<string, any>;
}
) {
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef(null);
const noMore = useRef(false);
const { toast } = useToast();
const [current, setCurrent] = useState(1);
const [data, setData] = useState<TData['list']>([]);
const [isLoading, { setTrue, setFalse }] = useBoolean(false);
const [list] = useVirtualList(data, {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight,
overscan
});
const loadData = useLockFn(async (num: number = current) => {
if (noMore.current) return;
setTrue();
try {
const res = await api({
current: num,
pageSize,
...defaultParams
} as TParams);
setCurrent(num);
if (num === 1) {
// reload
setData(res.list);
noMore.current = res.list.length >= res.total;
} else {
const totalLength = data.length + res.list.length;
noMore.current = totalLength >= res.total;
setData((prev) => [...prev, ...res.list]);
}
} catch (error: any) {
toast({
title: getErrText(error, '获取数据异常'),
status: 'error'
});
console.log(error);
}
setFalse();
});
const ScrollList = useMemoizedFn(
({
children,
isLoading,
...props
}: { children: React.ReactNode; isLoading?: boolean } & BoxProps) => {
return (
<MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}>
<Box ref={wrapperRef}>{children}</Box>
{noMore.current && (
<Box pb={2} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}>
{t('common.No more data')}
</Box>
)}
</MyBox>
);
}
);
useMount(() => {
loadData(1);
});
const scroll = useScroll(containerRef);
useEffect(() => {
if (!containerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadData(current + 1);
}
}, [scroll]);
return {
containerRef,
list,
isLoading,
ScrollList,
fetchData: loadData
};
}

View File

@@ -18,6 +18,7 @@
"@lexical/utils": "0.12.6", "@lexical/utils": "0.12.6",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@tanstack/react-query": "^4.24.10", "@tanstack/react-query": "^4.24.10",
"ahooks": "^3.7.11",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"i18next": "23.10.0", "i18next": "23.10.0",

3
pnpm-lock.yaml generated
View File

@@ -259,6 +259,9 @@ importers:
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^4.24.10 specifier: ^4.24.10
version: 4.24.10(react-dom@18.2.0)(react@18.2.0) version: 4.24.10(react-dom@18.2.0)(react@18.2.0)
ahooks:
specifier: ^3.7.11
version: 3.7.11(react@18.2.0)
date-fns: date-fns:
specifier: 2.30.0 specifier: 2.30.0
version: 2.30.0 version: 2.30.0

View File

@@ -14,7 +14,7 @@
], ],
"plugin.inlang.i18next": { "plugin.inlang.i18next": {
"pathPattern": { "pathPattern": {
"common": "./projects/app/public/locales/{languageTag}/common.json" "common": "./projects/app/i18n/{languageTag}/common.json"
}, },
"variableReferencePattern": [ "variableReferencePattern": [
"{{", "}}" "{{", "}}"

View File

@@ -117,6 +117,7 @@
"Name is empty": "Name is empty", "Name is empty": "Name is empty",
"New Create": "Create", "New Create": "Create",
"Next Step": "Next", "Next Step": "Next",
"No more data": "No more",
"Not open": "Close", "Not open": "Close",
"Number of words": "{{amount}} words", "Number of words": "{{amount}} words",
"OK": "OK", "OK": "OK",
@@ -1101,6 +1102,7 @@
"Check Failed": "Workflow verification fails. Check whether the node or connection is normal", "Check Failed": "Workflow verification fails. Check whether the node or connection is normal",
"Confirm stop debug": "Do you want to terminate debugging? Debugging information is not retained.", "Confirm stop debug": "Do you want to terminate debugging? Debugging information is not retained.",
"Copy node": "Copy node", "Copy node": "Copy node",
"Current workflow": "Current workflow",
"Custom inputs": "Inputs", "Custom inputs": "Inputs",
"Custom outputs": "Outputs", "Custom outputs": "Outputs",
"Custom variable": "Custom variable", "Custom variable": "Custom variable",
@@ -1148,6 +1150,11 @@
"target": "Target Data", "target": "Target Data",
"textarea": "Textarea" "textarea": "Textarea"
}, },
"publish": {
"OnRevert version": "Click back to that version",
"OnRevert version confirm": "Are you sure to roll back the version?",
"histories": "Publiish histories"
},
"tool": { "tool": {
"Handle": "Tool handle", "Handle": "Tool handle",
"Select Tool": "Select Tool" "Select Tool": "Select Tool"

View File

@@ -117,6 +117,7 @@
"Name is empty": "名称不能为空", "Name is empty": "名称不能为空",
"New Create": "新建", "New Create": "新建",
"Next Step": "下一步", "Next Step": "下一步",
"No more data": "没有更多了~",
"Not open": "未开启", "Not open": "未开启",
"Number of words": "{{amount}}字", "Number of words": "{{amount}}字",
"OK": "好的", "OK": "好的",
@@ -1103,6 +1104,7 @@
"Check Failed": "工作流校验失败,请检查节点是否正确填值,以及连线是否正常", "Check Failed": "工作流校验失败,请检查节点是否正确填值,以及连线是否正常",
"Confirm stop debug": "确认终止调试?调试信息将会不保留。", "Confirm stop debug": "确认终止调试?调试信息将会不保留。",
"Copy node": "已复制节点", "Copy node": "已复制节点",
"Current workflow": "当前工作流",
"Custom inputs": "自定义输入", "Custom inputs": "自定义输入",
"Custom outputs": "自定义输出", "Custom outputs": "自定义输出",
"Custom variable": "自定义变量", "Custom variable": "自定义变量",
@@ -1150,6 +1152,11 @@
"target": "外部数据", "target": "外部数据",
"textarea": "多行输入框" "textarea": "多行输入框"
}, },
"publish": {
"OnRevert version": "点击回退到该版本",
"OnRevert version confirm": "确认回退该版本?",
"histories": "发布记录"
},
"tool": { "tool": {
"Handle": "工具连接器", "Handle": "工具连接器",
"Select Tool": "选择工具" "Select Tool": "选择工具"

View File

@@ -9,7 +9,6 @@ module.exports = {
locales: ['en', 'zh'], locales: ['en', 'zh'],
localeDetection: false localeDetection: false
}, },
localePath: localePath: typeof window === 'undefined' ? require('path').resolve('./i18n') : '/i18n',
typeof window === 'undefined' ? require('path').resolve('./public/locales') : '/public/locales',
reloadOnPrerender: process.env.NODE_ENV === 'development' reloadOnPrerender: process.env.NODE_ENV === 'development'
}; };

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { useTheme, type BoxProps } from '@chakra-ui/react'; import { useTheme, type BoxProps } from '@chakra-ui/react';
import MyBox from '../common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
const PageContainer = ({ const PageContainer = ({
children, children,

View File

@@ -1,19 +0,0 @@
import React from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import Loading from '@fastgpt/web/components/common/MyLoading';
type Props = BoxProps & {
isLoading?: boolean;
text?: string;
};
const MyBox = ({ text, isLoading, children, ...props }: Props) => {
return (
<Box position={'relative'} {...props}>
{isLoading && <Loading fixed={false} text={text} />}
{children}
</Box>
);
};
export default MyBox;

View File

@@ -7,7 +7,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants'; import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants';
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal')); const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));

View File

@@ -5,7 +5,7 @@ import React, { Dispatch, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import ParentPaths from '@/components/common/ParentPaths'; import ParentPaths from '@/components/common/ParentPaths';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
type PathItemType = { type PathItemType = {
parentId: string; parentId: string;

View File

@@ -14,7 +14,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getPreviewPluginModule } from '@/web/core/plugin/api'; import { getPreviewPluginModule } from '@/web/core/plugin/api';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { moduleTemplatesList } from '@fastgpt/global/core/workflow/template/constants'; import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs'; import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow'; import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
@@ -259,7 +259,7 @@ const RenderList = React.memo(function RenderList({
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const formatTemplates = useMemo<nodeTemplateListType>(() => { const formatTemplates = useMemo<nodeTemplateListType>(() => {
const copy: nodeTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList)); const copy: nodeTemplateListType = JSON.parse(JSON.stringify(workflowNodeTemplateList(t)));
templates.forEach((item) => { templates.forEach((item) => {
const index = copy.findIndex((template) => template.type === item.templateType); const index = copy.findIndex((template) => template.type === item.templateType);
if (index === -1) return; if (index === -1) return;

View File

@@ -0,0 +1,179 @@
import React, { useState } from 'react';
import { getPublishList, postRevertVersion } from '@/web/core/app/versionApi';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer';
import { useTranslation } from 'next-i18next';
import { useMemoizedFn } from 'ahooks';
import { Box, Button, Flex } from '@chakra-ui/react';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
const PublishHistoriesSlider = () => {
const { t } = useTranslation();
const { openConfirm, ConfirmModal } = useConfirm({
content: t('core.workflow.publish.OnRevert version confirm')
});
const { appDetail, setAppDetail } = useAppStore();
const appId = useContextSelector(WorkflowContext, (e) => e.appId);
const setIsShowVersionHistories = useContextSelector(
WorkflowContext,
(e) => e.setIsShowVersionHistories
);
const initData = useContextSelector(WorkflowContext, (e) => e.initData);
const [selectedHistoryId, setSelectedHistoryId] = useState<string>();
const { list, ScrollList, isLoading } = useScrollPagination(getPublishList, {
itemHeight: 49,
overscan: 20,
pageSize: 30,
defaultParams: {
appId
}
});
const onClose = useMemoizedFn(() => {
setIsShowVersionHistories(false);
});
const onPreview = useMemoizedFn((data: AppVersionSchemaType) => {
setSelectedHistoryId(data._id);
initData({
nodes: data.nodes,
edges: data.edges
});
});
const onCloseSlider = useMemoizedFn(() => {
setSelectedHistoryId(undefined);
initData({
nodes: appDetail.modules,
edges: appDetail.edges
});
onClose();
});
const { mutate: onRevert, isLoading: isReverting } = useRequest({
mutationFn: async (data: AppVersionSchemaType) => {
if (!appId) return;
await postRevertVersion(appId, {
versionId: data._id,
editNodes: appDetail.modules,
editEdges: appDetail.edges
});
setAppDetail({
...appDetail,
modules: data.nodes,
edges: data.edges
});
onCloseSlider();
}
});
const showLoading = isLoading || isReverting;
return (
<>
<CustomRightDrawer
onClose={onCloseSlider}
iconSrc="core/workflow/versionHistories"
title={t('core.workflow.publish.histories')}
maxW={'300px'}
px={0}
showMask={false}
mt={'60px'}
overflow={'unset'}
>
<Button
mx={'20px'}
variant={'whitePrimary'}
mb={1}
isDisabled={!selectedHistoryId}
onClick={() => {
setSelectedHistoryId(undefined);
initData({
nodes: appDetail.modules,
edges: appDetail.edges
});
}}
>
{t('core.workflow.Current workflow')}
</Button>
<ScrollList isLoading={showLoading} flex={'1 0 0'} px={5}>
{list.map((data, index) => {
const item = data.data;
return (
<Flex
key={data.index}
alignItems={'center'}
py={4}
px={3}
borderRadius={'md'}
cursor={'pointer'}
fontWeight={500}
_hover={{
bg: 'primary.50'
}}
{...(selectedHistoryId === item._id && {
color: 'primary.600'
})}
onClick={() => onPreview(item)}
>
<Box
w={'12px'}
h={'12px'}
borderWidth={'2px'}
borderColor={'primary.600'}
borderRadius={'50%'}
position={'relative'}
{...(index !== list.length - 1 && {
_after: {
content: '""',
height: '40px',
width: '2px',
bgColor: 'myGray.250',
position: 'absolute',
top: '10px',
left: '3px'
}
})}
></Box>
<Box ml={3} flex={'1 0 0'}>
{formatTime2YMDHM(item.time)}
</Box>
{item._id === selectedHistoryId && (
<MyTooltip label={t('core.workflow.publish.OnRevert version')}>
<MyIcon
name={'core/workflow/revertVersion'}
w={'20px'}
color={'primary.600'}
onClick={(e) => {
e.stopPropagation();
openConfirm(() => onRevert(item))();
}}
/>
</MyTooltip>
)}
</Flex>
);
})}
</ScrollList>
</CustomRightDrawer>
<ConfirmModal />
</>
);
};
export default React.memo(PublishHistoriesSlider);

View File

@@ -36,10 +36,12 @@ import { createContext } from 'use-context-selector';
import { defaultRunningStatus } from './constants'; import { defaultRunningStatus } from './constants';
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils'; import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
type OnChange<ChangesType> = (changes: ChangesType[]) => void; type OnChange<ChangesType> = (changes: ChangesType[]) => void;
type WorkflowContextType = { type WorkflowContextType = {
appId?: string;
mode: 'app' | 'plugin'; mode: 'app' | 'plugin';
basicNodeTemplates: FlowNodeTemplateType[]; basicNodeTemplates: FlowNodeTemplateType[];
filterAppIds?: string[]; filterAppIds?: string[];
@@ -83,7 +85,6 @@ type WorkflowContextType = {
}; };
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise<void>; initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise<void>;
// debug
// debug // debug
workflowDebugData: workflowDebugData:
| { | {
@@ -103,6 +104,10 @@ type WorkflowContextType = {
runtimeEdges: RuntimeEdgeItemType[]; runtimeEdges: RuntimeEdgeItemType[];
}) => Promise<void>; }) => Promise<void>;
onStopNodeDebug: () => void; onStopNodeDebug: () => void;
// version history
isShowVersionHistories: boolean;
setIsShowVersionHistories: React.Dispatch<React.SetStateAction<boolean>>;
}; };
type ContextValueProps = Pick< type ContextValueProps = Pick<
@@ -201,6 +206,10 @@ export const WorkflowContext = createContext<WorkflowContextType>({
}, },
onChangeNode: function (e: FlowNodeChangeProps): void { onChangeNode: function (e: FlowNodeChangeProps): void {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
},
isShowVersionHistories: false,
setIsShowVersionHistories: function (value: React.SetStateAction<boolean>): void {
throw new Error('Function not implemented.');
} }
}); });
@@ -616,6 +625,10 @@ const WorkflowContextProvider = ({
[onNextNodeDebug, onStopNodeDebug] [onNextNodeDebug, onStopNodeDebug]
); );
/* Version histories */
const [isShowVersionHistories, setIsShowVersionHistories] = useState(false);
/* event bus */
useEffect(() => { useEffect(() => {
eventBus.on(EventNameEnum.requestWorkflowStore, () => { eventBus.on(EventNameEnum.requestWorkflowStore, () => {
eventBus.emit(EventNameEnum.receiveWorkflowStore, { eventBus.emit(EventNameEnum.receiveWorkflowStore, {
@@ -630,6 +643,7 @@ const WorkflowContextProvider = ({
return ( return (
<WorkflowContext.Provider <WorkflowContext.Provider
value={{ value={{
appId,
reactFlowWrapper, reactFlowWrapper,
...value, ...value,
// node // node
@@ -661,7 +675,11 @@ const WorkflowContextProvider = ({
workflowDebugData, workflowDebugData,
onNextNodeDebug, onNextNodeDebug,
onStartNodeDebug, onStartNodeDebug,
onStopNodeDebug onStopNodeDebug,
// version history
isShowVersionHistories,
setIsShowVersionHistories
}} }}
> >
{children} {children}

View File

@@ -25,3 +25,10 @@ export type PostPublishAppProps = {
nodes: AppSchema['modules']; nodes: AppSchema['modules'];
edges: AppSchema['edges']; edges: AppSchema['edges'];
}; };
export type PostRevertAppProps = {
versionId: string;
// edit workflow
editNodes: AppSchema['modules'];
editEdges: AppSchema['edges'];
};

View File

@@ -26,7 +26,7 @@ import {
billTypeMap billTypeMap
} from '@fastgpt/global/support/wallet/bill/constants'; } from '@fastgpt/global/support/wallet/bill/constants';
// import { usePagination } from '@/web/common/hooks/usePagination'; // import { usePagination } from '@/web/common/hooks/usePagination';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants'; import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
import MySelect from '@fastgpt/web/components/common/MySelect'; import MySelect from '@fastgpt/web/components/common/MySelect';

View File

@@ -0,0 +1,34 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middle/entry';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
type Props = PaginationProps<{
appId: string;
}>;
type Response = PaginationResponse<AppVersionSchemaType>;
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<Response> {
const { current, pageSize, appId } = req.body as Props;
const [result, total] = await Promise.all([
MongoAppVersion.find({
appId
})
.sort({
time: -1
})
.skip((current - 1) * pageSize)
.limit(pageSize),
MongoAppVersion.countDocuments({ appId })
]);
return {
total,
list: result
};
}
export default NextAPI(handler);

View File

@@ -0,0 +1,73 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middle/entry';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { PostRevertAppProps } from '@/global/core/app/api';
type Response = {};
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
const { appId } = req.query as { appId: string };
const { editNodes = [], editEdges = [], versionId } = req.body as PostRevertAppProps;
await authApp({ appId, req, per: 'w', authToken: true });
const version = await MongoAppVersion.findOne({
_id: versionId,
appId
});
if (!version) {
throw new Error('version not found');
}
const { nodes: formatEditNodes } = beforeUpdateAppFormat({ nodes: editNodes });
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(version.nodes));
await mongoSessionRun(async (session) => {
// 为编辑中的数据创建一个版本
await MongoAppVersion.create(
[
{
appId,
nodes: formatEditNodes,
edges: editEdges
}
],
{ session }
);
// 为历史版本再创建一个版本
await MongoAppVersion.create(
[
{
appId,
nodes: version.nodes,
edges: version.edges
}
],
{ session }
);
// update app
await MongoApp.findByIdAndUpdate(appId, {
modules: version.nodes,
edges: version.edges,
updateTime: new Date(),
scheduledTriggerConfig,
scheduledTriggerNextTime: scheduledTriggerConfig
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
: null
});
});
return {};
}
export default NextAPI(handler);

View File

@@ -22,12 +22,15 @@ import {
} from '@/web/core/workflow/utils'; } from '@/web/core/workflow/utils';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useQuery } from '@tanstack/react-query';
import { formatTime2HM } from '@fastgpt/global/common/string/time'; import { formatTime2HM } from '@fastgpt/global/common/string/time';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context'; import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
import { useInterval } from 'ahooks';
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
const PublishHistories = dynamic(
() => import('@/components/core/workflow/components/PublishHistoriesSlider')
);
type Props = { app: AppSchema; onClose: () => void }; type Props = { app: AppSchema; onClose: () => void };
@@ -55,7 +58,6 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({ const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({
content: t('core.app.Publish Confirm') content: t('core.app.Publish Confirm')
}); });
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { publishApp, updateAppDetail } = useAppStore(); const { publishApp, updateAppDetail } = useAppStore();
const edges = useContextSelector(WorkflowContext, (v) => v.edges); const edges = useContextSelector(WorkflowContext, (v) => v.edges);
@@ -63,6 +65,17 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save')); const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save'));
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError); const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const isShowVersionHistories = useContextSelector(
WorkflowContext,
(v) => v.isShowVersionHistories
);
const setIsShowVersionHistories = useContextSelector(
WorkflowContext,
(v) => v.setIsShowVersionHistories
);
const flowData2StoreDataAndCheck = useCallback(async () => { const flowData2StoreDataAndCheck = useCallback(async () => {
const { nodes } = await getWorkflowStore(); const { nodes } = await getWorkflowStore();
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges }); const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
@@ -81,6 +94,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
}, [edges, onUpdateNodeError, t, toast]); }, [edges, onUpdateNodeError, t, toast]);
const onclickSave = useCallback(async () => { const onclickSave = useCallback(async () => {
if (isShowVersionHistories) return;
const { nodes } = await getWorkflowStore(); const { nodes } = await getWorkflowStore();
if (nodes.length === 0) return null; if (nodes.length === 0) return null;
@@ -107,7 +121,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
setIsSaving(false); setIsSaving(false);
return null; return null;
}, [updateAppDetail, app._id, edges, t]); }, [isShowVersionHistories, edges, updateAppDetail, app._id, t]);
const onclickPublish = useCallback(async () => { const onclickPublish = useCallback(async () => {
setIsSaving(true); setIsSaving(true);
@@ -160,15 +174,16 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
} }
}, [copyData, flowData2StoreDataAndCheck, t]); }, [copyData, flowData2StoreDataAndCheck, t]);
// effect
useBeforeunload({ useBeforeunload({
callback: onclickSave, callback: onclickSave,
tip: t('core.common.tip.leave page') tip: t('core.common.tip.leave page')
}); });
useQuery(['autoSave'], onclickSave, { useInterval(() => {
refetchInterval: 20 * 1000, if (!app._id) return;
enabled: !!app._id onclickSave();
}); }, 20000);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
@@ -180,6 +195,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
alignItems={'center'} alignItems={'center'}
userSelect={'none'} userSelect={'none'}
bg={'myGray.25'} bg={'myGray.25'}
h={'67px'}
> >
<IconButton <IconButton
size={'smSquare'} size={'smSquare'}
@@ -193,23 +209,25 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
isLoading={isSaving} isLoading={isSaving}
onClick={saveAndBack} onClick={saveAndBack}
/> />
<Box ml={[3, 5]}> <Box ml={[2, 4]}>
<Box fontSize={['md', 'lg']} fontWeight={'bold'}> <Box fontSize={['md', 'lg']} fontWeight={'bold'}>
{app.name} {app.name}
</Box> </Box>
<MyTooltip label={t('core.app.Onclick to save')}> {!isShowVersionHistories && (
<Box <MyTooltip label={t('core.app.Onclick to save')}>
fontSize={'sm'} <Box
mt={1} fontSize={'sm'}
display={'inline-block'} mt={1}
borderRadius={'xs'} display={'inline-block'}
cursor={'pointer'} borderRadius={'xs'}
onClick={onclickSave} cursor={'pointer'}
color={'myGray.500'} onClick={onclickSave}
> color={'myGray.500'}
{saveLabel} >
</Box> {saveLabel}
</MyTooltip> </Box>
</MyTooltip>
)}
</Box> </Box>
<Box flex={1} /> <Box flex={1} />
@@ -217,7 +235,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<MyMenu <MyMenu
Button={ Button={
<IconButton <IconButton
mr={[3, 5]} mr={[2, 4]}
icon={<MyIcon name={'more'} w={'14px'} p={2} />} icon={<MyIcon name={'more'} w={'14px'} p={2} />}
aria-label={''} aria-label={''}
size={'sm'} size={'sm'}
@@ -238,10 +256,19 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
]} ]}
/> />
<Button <IconButton
mr={[3, 5]} mr={[2, 4]}
icon={<MyIcon name={'history'} w={'18px'} />}
aria-label={''}
size={'sm'} size={'sm'}
leftIcon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />} w={'30px'}
variant={'whitePrimary'}
onClick={() => setIsShowVersionHistories(true)}
/>
<Button
size={'sm'}
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
variant={'whitePrimary'} variant={'whitePrimary'}
onClick={async () => { onClick={async () => {
const data = await flowData2StoreDataAndCheck(); const data = await flowData2StoreDataAndCheck();
@@ -250,17 +277,20 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
} }
}} }}
> >
{t('core.Chat test')} {t('core.workflow.Debug')}
</Button> </Button>
<Button {!isShowVersionHistories && (
size={'sm'} <Button
isLoading={isSaving} ml={[2, 4]}
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />} size={'sm'}
onClick={openConfigPublish(onclickPublish)} isLoading={isSaving}
> leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
{t('core.app.Publish')} onClick={openConfigPublish(onclickPublish)}
</Button> >
{t('core.app.Publish')}
</Button>
)}
</Flex> </Flex>
<ConfirmModal confirmText={t('core.app.Publish')} /> <ConfirmModal confirmText={t('core.app.Publish')} />
</> </>
@@ -275,8 +305,10 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
onclickPublish, onclickPublish,
onclickSave, onclickSave,
openConfigPublish, openConfigPublish,
isShowVersionHistories,
saveAndBack, saveAndBack,
saveLabel, saveLabel,
setIsShowVersionHistories,
setWorkflowTestData, setWorkflowTestData,
t, t,
theme.borders.base theme.borders.base
@@ -286,6 +318,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<> <>
{Render} {Render}
{isOpenImport && <ImportSettings onClose={onCloseImport} />} {isOpenImport && <ImportSettings onClose={onCloseImport} />}
{isShowVersionHistories && <PublishHistories />}
</> </>
); );
}); });

View File

@@ -28,7 +28,7 @@ import { getInitChatInfo } from '@/web/core/chat/api';
import Tag from '@fastgpt/web/components/common/Tag/index'; import Tag from '@fastgpt/web/components/common/Tag/index';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { usePagination } from '@fastgpt/web/hooks/usePagination'; import { usePagination } from '@fastgpt/web/hooks/usePagination';
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker'; import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
import { formatChatValue2InputType } from '@/components/ChatBox/utils'; import { formatChatValue2InputType } from '@/components/ChatBox/utils';

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useTransition } from 'react'; import React, { useEffect, useMemo, useTransition } from 'react';
import { Box, Flex, Grid, BoxProps, useTheme, useDisclosure, Button } from '@chakra-ui/react'; import { Box, Flex, Grid, BoxProps, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons'; import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import { useFieldArray, UseFormReturn } from 'react-hook-form'; import { useFieldArray, UseFormReturn } from 'react-hook-form';
@@ -28,6 +28,7 @@ import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d';
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete'; import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
import { TTSTypeEnum } from '@/constants/app'; import { TTSTypeEnum } from '@/constants/app';
import { getSystemVariables } from '@/web/core/app/utils'; import { getSystemVariables } from '@/web/core/app/utils';
import { useUpdate } from 'ahooks';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
@@ -65,6 +66,7 @@ const EditForm = ({
const { allDatasets } = useDatasetStore(); const { allDatasets } = useDatasetStore();
const { llmModelList } = useSystemStore(); const { llmModelList } = useSystemStore();
const [, startTst] = useTransition(); const [, startTst] = useTransition();
const refresh = useUpdate();
const { setValue, getValues, handleSubmit, control, watch } = editForm; const { setValue, getValues, handleSubmit, control, watch } = editForm;
@@ -72,7 +74,6 @@ const EditForm = ({
control, control,
name: 'dataset.datasets' name: 'dataset.datasets'
}); });
const selectedTools = watch('selectedTools');
const { const {
isOpen: isOpenDatasetSelect, isOpen: isOpenDatasetSelect,
@@ -106,6 +107,7 @@ const EditForm = ({
const tts = getValues('userGuide.tts'); const tts = getValues('userGuide.tts');
const whisperConfig = getValues('userGuide.whisper'); const whisperConfig = getValues('userGuide.whisper');
const postQuestionGuide = getValues('userGuide.questionGuide'); const postQuestionGuide = getValues('userGuide.questionGuide');
const selectedTools = watch('selectedTools');
const selectDatasets = useMemo( const selectDatasets = useMemo(
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)), () => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
@@ -131,6 +133,16 @@ const EditForm = ({
errorToast: t('common.Save Failed') errorToast: t('common.Save Failed')
}); });
useEffect(() => {
const wat = watch((data) => {
refresh();
});
return () => {
wat.unsubscribe();
};
}, []);
return ( return (
<Box> <Box>
{/* title */} {/* title */}
@@ -459,7 +471,9 @@ const EditForm = ({
{isOpenToolsSelect && ( {isOpenToolsSelect && (
<ToolSelectModal <ToolSelectModal
selectedTools={selectedTools} selectedTools={selectedTools}
onAddTool={(e) => setValue('selectedTools', [...selectedTools, e])} onAddTool={(e) => {
setValue('selectedTools', [...selectedTools, e]);
}}
onRemoveTool={(e) => { onRemoveTool={(e) => {
setValue( setValue(
'selectedTools', 'selectedTools',

View File

@@ -29,7 +29,7 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { AddIcon } from '@chakra-ui/icons'; import { AddIcon } from '@chakra-ui/icons';
import { getPreviewPluginModule } from '@/web/core/plugin/api'; import { getPreviewPluginModule } from '@/web/core/plugin/api';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import ParentPaths from '@/components/common/ParentPaths'; import ParentPaths from '@/components/common/ParentPaths';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants'; import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';

View File

@@ -23,7 +23,6 @@ const MyApps = () => {
const router = useRouter(); const router = useRouter();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { myApps, loadMyApps } = useAppStore(); const { myApps, loadMyApps } = useAppStore();
const [teamsTags, setTeamTags] = useState([]);
const { openConfirm, ConfirmModal } = useConfirm({ const { openConfirm, ConfirmModal } = useConfirm({
title: '删除提示', title: '删除提示',
content: '确认删除该应用所有信息?' content: '确认删除该应用所有信息?'

View File

@@ -26,7 +26,7 @@ import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils'; import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat'; import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type'; import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';

View File

@@ -32,7 +32,7 @@ import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils'; import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import SliderApps from './components/SliderApps'; import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';

View File

@@ -60,7 +60,7 @@ import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type'; import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
import { DatasetCollectionSyncResultEnum } from '@fastgpt/global/core/dataset/constants'; import { DatasetCollectionSyncResultEnum } from '@fastgpt/global/core/dataset/constants';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { usePagination } from '@fastgpt/web/hooks/usePagination'; import { usePagination } from '@fastgpt/web/hooks/usePagination';
import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants'; import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants';

View File

@@ -1,4 +1,4 @@
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { Box, FlexProps } from '@chakra-ui/react'; import { Box, FlexProps } from '@chakra-ui/react';

View File

@@ -23,7 +23,7 @@ import DeleteIcon from '@fastgpt/web/components/common/Icon/delete';
import { defaultCollectionDetail } from '@/constants/dataset'; import { defaultCollectionDetail } from '@/constants/dataset';
import { getDocPath } from '@/web/common/system/doc'; import { getDocPath } from '@/web/common/system/doc';
import RawSourceBox from '@/components/core/dataset/RawSourceBox'; import RawSourceBox from '@/components/core/dataset/RawSourceBox';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';

View File

@@ -27,7 +27,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag'; import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
import Head from 'next/head'; import Head from 'next/head';
import MyBox from '@/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
const DataCard = dynamic(() => import('./components/DataCard')); const DataCard = dynamic(() => import('./components/DataCard'));
const Test = dynamic(() => import('./components/Test')); const Test = dynamic(() => import('./components/Test'));

View File

@@ -1,5 +1,5 @@
import 'i18next'; import 'i18next';
//import common from '../../public/locales/en/common.json'; //import common from '../../i18n/en/common.json';
interface I18nNamespaces { interface I18nNamespaces {
common: any; common: any;

View File

@@ -16,6 +16,7 @@ type State = {
updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>; updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>;
publishApp(appId: string, data: PostPublishAppProps): Promise<void>; publishApp(appId: string, data: PostPublishAppProps): Promise<void>;
clearAppModules(): void; clearAppModules(): void;
setAppDetail(data: AppDetailType): void;
}; };
export const useAppStore = create<State>()( export const useAppStore = create<State>()(
@@ -61,6 +62,11 @@ export const useAppStore = create<State>()(
}; };
}); });
}, },
setAppDetail(data: AppDetailType) {
set((state) => {
state.appDetail = data;
});
},
clearAppModules() { clearAppModules() {
set((state) => { set((state) => {

View File

@@ -1,5 +1,13 @@
import { PostPublishAppProps } from '@/global/core/app/api'; import { PostPublishAppProps, PostRevertAppProps } from '@/global/core/app/api';
import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
export const postPublishApp = (appId: string, data: PostPublishAppProps) => export const postPublishApp = (appId: string, data: PostPublishAppProps) =>
POST(`/core/app/version/publish?appId=${appId}`, data); POST(`/core/app/version/publish?appId=${appId}`, data);
export const getPublishList = (data: PaginationProps<{ appId: string }>) =>
POST<PaginationResponse<AppVersionSchemaType>>('/core/app/version/list', data);
export const postRevertVersion = (appId: string, data: PostRevertAppProps) =>
POST(`/core/app/version/revert?appId=${appId}`, data);