From d407e87dd97c2742d19f17eb082f1202f9afb6f6 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Sat, 27 Apr 2024 12:21:01 +0800 Subject: [PATCH] 4.8-fix (#1305) * fix if-else find variables (#92) * fix if-else find variables * change workflow output type * fix tooltip style * fix * 4.8 (#93) * api middleware * perf: app version histories * faq * perf: value type show * fix: ts * fix: Run the same node multiple times * feat: auto save workflow * perf: auto save workflow --------- Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com> --- .vscode/nextapi.code-snippets | 29 ++ docSite/content/docs/development/faq.md | 12 +- packages/global/common/string/time.ts | 1 + packages/global/core/app/type.d.ts | 3 +- packages/global/core/app/version.d.ts | 9 + packages/global/core/workflow/constants.ts | 13 +- .../global/core/workflow/runtime/utils.ts | 2 +- .../workflow/template/system/datasetConcat.ts | 24 +- packages/service/common/middle/cors.ts | 25 +- packages/service/common/response/index.ts | 5 +- .../service/common/vectorStore/pg/index.ts | 11 +- packages/service/core/app/controller.ts | 65 +++++ packages/service/core/app/schema.ts | 6 +- packages/service/core/app/versionSchema.ts | 36 +++ packages/service/core/chat/chatItemSchema.ts | 4 +- packages/service/core/chat/chatSchema.ts | 4 +- .../service/core/workflow/dispatch/index.ts | 11 +- .../core/workflow/dispatch/tools/runIfElse.ts | 39 +-- packages/service/support/outLink/schema.ts | 4 +- .../service/support/permission/auth/app.ts | 6 +- .../web/components/common/Icon/constants.ts | 1 + .../common/Icon/icons/common/publishFill.svg | 6 + .../common/MySelect/MultipleRowSelect.tsx | 21 +- .../web/components/common/MyTooltip/index.tsx | 2 +- packages/web/hooks/useBeforeunload.ts | 24 ++ packages/web/hooks/useConfirm.tsx | 2 +- pnpm-lock.yaml | 3 - projects/app/next.config.js | 1 + projects/app/package.json | 1 - projects/app/public/locales/en/common.json | 13 + projects/app/public/locales/zh/common.json | 23 +- .../core/workflow/Flow/FlowProvider.tsx | 16 +- .../workflow/Flow/nodes/NodeDatasetConcat.tsx | 41 +-- .../core/workflow/Flow/nodes/NodeIfElse.tsx | 8 +- .../workflow/Flow/nodes/NodePluginOutput.tsx | 20 +- .../workflow/Flow/nodes/NodeWorkflowStart.tsx | 2 +- .../Flow/nodes/render/FieldEditModal.tsx | 16 +- .../Flow/nodes/render/Handle/ToolHandle.tsx | 24 +- .../workflow/Flow/nodes/render/NodeCard.tsx | 3 + .../Flow/nodes/render/RenderInput/Label.tsx | 69 ++--- .../templates/SettingQuotePrompt.tsx | 4 +- .../Flow/nodes/render/RenderOutput/Label.tsx | 16 +- .../Flow/nodes/render/TargetHandle.tsx | 57 ---- .../Flow/nodes/render/ValueTypeLabel.tsx | 42 +-- .../Flow/nodes/render/VariableTable.tsx | 13 +- .../support/laf/LafAccountModal.tsx | 2 +- .../app/src}/global/core/app/api.d.ts | 17 +- projects/app/src/pages/api/core/app/create.ts | 100 ++++--- projects/app/src/pages/api/core/app/del.ts | 97 ++++--- .../app/src/pages/api/core/app/detail.tsx | 31 +-- .../app/src/pages/api/core/app/getChatLogs.ts | 250 +++++++++--------- projects/app/src/pages/api/core/app/list.ts | 52 ++-- projects/app/src/pages/api/core/app/update.ts | 136 +++------- .../src/pages/api/core/app/updateTeamTasg.ts | 81 ------ .../src/pages/api/core/app/version/publish.ts | 54 ++++ projects/app/src/pages/api/core/chat/init.ts | 24 +- .../src/pages/api/core/chat/outLink/init.ts | 26 +- .../app/src/pages/api/core/chat/team/init.ts | 20 +- .../src/pages/api/core/dataset/data/delete.ts | 58 ++-- .../src/pages/api/core/dataset/data/detail.ts | 41 ++- .../pages/api/core/dataset/data/insertData.ts | 168 ++++++------ .../src/pages/api/core/dataset/data/list.ts | 97 ++++--- .../pages/api/core/dataset/data/pushData.ts | 84 +++--- .../src/pages/api/core/dataset/data/update.ts | 84 +++--- .../src/pages/api/core/dataset/exportAll.ts | 149 +++++------ .../app/src/pages/api/core/dataset/list.ts | 76 +++--- .../src/pages/api/core/dataset/searchTest.ts | 161 ++++++----- .../src/pages/api/v1/audio/transcriptions.ts | 8 +- .../app/src/pages/api/v1/chat/completions.ts | 30 ++- projects/app/src/pages/api/v1/embeddings.ts | 5 +- .../app/detail/components/FlowEdit/Header.tsx | 167 +++++++----- .../app/detail/components/FlowEdit/index.tsx | 18 +- .../detail/components/SimpleEdit/EditForm.tsx | 9 +- .../components/SimpleEdit/TagsEditModal.tsx | 4 +- projects/app/src/pages/app/detail/index.tsx | 16 -- .../src/pages/appStore/components/list.tsx | 91 ------- projects/app/src/pages/appStore/index.tsx | 99 ------- projects/app/src/pages/plugin/edit/Header.tsx | 202 +++++++------- projects/app/src/pages/plugin/edit/index.tsx | 23 +- projects/app/src/service/middle/entry.ts | 30 +++ projects/app/src/web/core/app/api.ts | 30 +-- .../app/src/web/core/app/store/useAppStore.ts | 19 +- projects/app/src/web/core/app/versionApi.ts | 5 + projects/app/src/web/core/workflow/adapt.ts | 2 +- .../web/core/workflow/constants/dataType.ts | 80 +++--- .../src/web/core/workflow/constants/index.ts | 1 - projects/app/src/web/core/workflow/utils.ts | 2 +- 87 files changed, 1607 insertions(+), 1779 deletions(-) create mode 100644 .vscode/nextapi.code-snippets create mode 100644 packages/global/core/app/version.d.ts create mode 100644 packages/service/core/app/controller.ts create mode 100644 packages/service/core/app/versionSchema.ts create mode 100644 packages/web/components/common/Icon/icons/common/publishFill.svg create mode 100644 packages/web/hooks/useBeforeunload.ts delete mode 100644 projects/app/src/components/core/workflow/Flow/nodes/render/TargetHandle.tsx rename {packages => projects/app/src}/global/core/app/api.d.ts (52%) delete mode 100644 projects/app/src/pages/api/core/app/updateTeamTasg.ts create mode 100644 projects/app/src/pages/api/core/app/version/publish.ts delete mode 100644 projects/app/src/pages/appStore/components/list.tsx delete mode 100644 projects/app/src/pages/appStore/index.tsx create mode 100644 projects/app/src/service/middle/entry.ts create mode 100644 projects/app/src/web/core/app/versionApi.ts delete mode 100644 projects/app/src/web/core/workflow/constants/index.ts diff --git a/.vscode/nextapi.code-snippets b/.vscode/nextapi.code-snippets new file mode 100644 index 000000000..899fc468a --- /dev/null +++ b/.vscode/nextapi.code-snippets @@ -0,0 +1,29 @@ +{ + // Place your FastGPT 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + "Next api template": { + "scope": "javascript,typescript", + "prefix": "nextapi", + "body": [ + "import type { NextApiRequest, NextApiResponse } from 'next';", + "import { NextAPI } from '@/service/middle/entry';", + "", + "type Props = {};", + "", + "type Response = {};", + "", + "async function handler(req: NextApiRequest, res: NextApiResponse): Promise<{}> {", + " $1", + " return {}", + "}", + "", + "export default NextAPI(handler);" + ], + "description": "FastGPT Next API template" + } +} \ No newline at end of file diff --git a/docSite/content/docs/development/faq.md b/docSite/content/docs/development/faq.md index 7b9f6f2b1..bcfed0b92 100644 --- a/docSite/content/docs/development/faq.md +++ b/docSite/content/docs/development/faq.md @@ -13,7 +13,10 @@ images: [] 1. `docker ps -a` 查看所有容器运行状态,检查是否全部 running,如有异常,尝试`docker logs 容器名`查看对应日志。 2. 容器都运行正常的,`docker logs 容器名` 查看报错日志 -3. 无法解决时,可以找找[Issue](https://github.com/labring/FastGPT/issues),或新提 Issue,私有部署错误,务必提供详细的日志,否则很难排查。 +3. 带有`requestId`的,都是 OneAPI 提示错误,大部分都是因为模型接口报错。 +4. 无法解决时,可以找找[Issue](https://github.com/labring/FastGPT/issues),或新提 Issue,私有部署错误,务必提供详细的日志,否则很难排查。 + + ## 二、通用问题 @@ -90,4 +93,9 @@ FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应 OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。 -可以`exec`进入容器,`env`查看环境变量是否生效。 \ No newline at end of file +可以`exec`进入容器,`env`查看环境变量是否生效。 + +### bad_response_status_code bad response status code 503 + +1. 模型服务不可用 +2. .... \ No newline at end of file diff --git a/packages/global/common/string/time.ts b/packages/global/common/string/time.ts index c80feea87..4b3211102 100644 --- a/packages/global/common/string/time.ts +++ b/packages/global/common/string/time.ts @@ -4,6 +4,7 @@ import cronParser from 'cron-parser'; export const formatTime2YMDHM = (time?: Date) => time ? dayjs(time).format('YYYY-MM-DD HH:mm') : ''; export const formatTime2YMD = (time?: Date) => (time ? dayjs(time).format('YYYY-MM-DD') : ''); +export const formatTime2HM = (time: Date = new Date()) => dayjs(time).format('HH:mm'); /* cron time parse */ export const cronParser2Fields = (cronString: string) => { diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 60adb1b5d..0da1d8b6a 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -6,7 +6,7 @@ import { VariableInputEnum } from '../workflow/constants'; import { SelectedDatasetType } from '../workflow/api'; import { DatasetSearchModeEnum } from '../dataset/constants'; import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d'; -import { StoreEdgeItemType } from 'core/workflow/type/edge'; +import { StoreEdgeItemType } from '../workflow/type/edge'; export interface AppSchema { _id: string; @@ -18,6 +18,7 @@ export interface AppSchema { avatar: string; intro: string; updateTime: number; + modules: StoreNodeItemType[]; edges: StoreEdgeItemType[]; diff --git a/packages/global/core/app/version.d.ts b/packages/global/core/app/version.d.ts new file mode 100644 index 000000000..7b0dbba63 --- /dev/null +++ b/packages/global/core/app/version.d.ts @@ -0,0 +1,9 @@ +import { StoreNodeItemType } from '../workflow/type'; +import { StoreEdgeItemType } from '../workflow/type/edge'; + +export type AppVersionSchemaType = { + appId: string; + time: Date; + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; +}; diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts index 97791ef2e..02bc2ee14 100644 --- a/packages/global/core/workflow/constants.ts +++ b/packages/global/core/workflow/constants.ts @@ -14,18 +14,21 @@ export enum WorkflowIOValueTypeEnum { string = 'string', number = 'number', boolean = 'boolean', + object = 'object', + arrayString = 'arrayString', + arrayNumber = 'arrayNumber', + arrayBoolean = 'arrayBoolean', + arrayObject = 'arrayObject', any = 'any', chatHistory = 'chatHistory', datasetQuote = 'datasetQuote', + dynamic = 'dynamic', // plugin special type selectApp = 'selectApp', - selectDataset = 'selectDataset', - - // tool - tools = 'tools' + selectDataset = 'selectDataset' } /* reg: modulename key */ @@ -173,3 +176,5 @@ export enum RuntimeEdgeStatusEnum { 'active' = 'active', 'skipped' = 'skipped' } + +export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID'; diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts index 9f04a2e4c..353981b61 100644 --- a/packages/global/core/workflow/runtime/utils.ts +++ b/packages/global/core/workflow/runtime/utils.ts @@ -4,7 +4,7 @@ import { FlowNodeTypeEnum } from '../node/constant'; import { StoreNodeItemType } from '../type'; import { StoreEdgeItemType } from '../type/edge'; import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type'; -import { VARIABLE_NODE_ID } from '../../../../../projects/app/src/web/core/workflow/constants/index'; +import { VARIABLE_NODE_ID } from '../constants'; export const initWorkflowEdgeStatus = (edges: StoreEdgeItemType[]): RuntimeEdgeItemType[] => { return ( diff --git a/packages/global/core/workflow/template/system/datasetConcat.ts b/packages/global/core/workflow/template/system/datasetConcat.ts index ad3b9136e..60b4d1779 100644 --- a/packages/global/core/workflow/template/system/datasetConcat.ts +++ b/packages/global/core/workflow/template/system/datasetConcat.ts @@ -10,16 +10,26 @@ import { NodeOutputKeyEnum, FlowNodeTemplateTypeEnum } from '../../constants'; -import { Input_Template_Dataset_Quote } from '../input'; import { getNanoid } from '../../../../common/string/tools'; import { getHandleConfig } from '../utils'; import { FlowNodeInputItemType } from '../../type/io.d'; -export const getOneQuoteInputTemplate = (key = getNanoid()): FlowNodeInputItemType => ({ - ...Input_Template_Dataset_Quote, +const defaultQuoteKey = 'defaultQuoteKey'; + +export const getOneQuoteInputTemplate = ({ + key = getNanoid(), + index +}: { + key?: string; + index: number; +}): FlowNodeInputItemType => ({ key, - renderTypeList: [FlowNodeInputTypeEnum.custom], - description: '' + renderTypeList: [FlowNodeInputTypeEnum.reference], + label: `引用${index}`, + debugLabel: '知识库引用', + canEdit: key !== defaultQuoteKey, + description: '', + valueType: WorkflowIOValueTypeEnum.datasetQuote }); export const DatasetConcatModule: FlowNodeTemplateType = { @@ -37,7 +47,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = { key: NodeInputKeyEnum.datasetMaxTokens, renderTypeList: [FlowNodeInputTypeEnum.custom], label: '最大 Tokens', - value: 1500, + value: 3000, valueType: WorkflowIOValueTypeEnum.number }, { @@ -45,7 +55,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = { renderTypeList: [FlowNodeInputTypeEnum.custom], label: '' }, - getOneQuoteInputTemplate() + getOneQuoteInputTemplate({ key: defaultQuoteKey, index: 1 }) ], outputs: [ { diff --git a/packages/service/common/middle/cors.ts b/packages/service/common/middle/cors.ts index 261b03efb..775dc31e1 100644 --- a/packages/service/common/middle/cors.ts +++ b/packages/service/common/middle/cors.ts @@ -1,19 +1,12 @@ -import type { NextApiResponse, NextApiHandler, NextApiRequest } from 'next'; +import type { NextApiResponse, NextApiRequest } from 'next'; import NextCors from 'nextjs-cors'; -export function withNextCors(handler: NextApiHandler): NextApiHandler { - return async function nextApiHandlerWrappedWithNextCors( - req: NextApiRequest, - res: NextApiResponse - ) { - const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; - const origin = req.headers.origin; - await NextCors(req, res, { - methods, - origin: origin, - optionsSuccessStatus: 200 - }); - - return handler(req, res); - }; +export async function withNextCors(req: NextApiRequest, res: NextApiResponse) { + const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; + const origin = req.headers.origin; + await NextCors(req, res, { + methods, + origin: origin, + optionsSuccessStatus: 200 + }); } diff --git a/packages/service/common/response/index.ts b/packages/service/common/response/index.ts index 6ff806dec..8a804e7fa 100644 --- a/packages/service/common/response/index.ts +++ b/packages/service/common/response/index.ts @@ -18,9 +18,10 @@ export const jsonRes = ( message?: string; data?: T; error?: any; + url?: string; } ) => { - const { code = 200, message = '', data = null, error } = props || {}; + const { code = 200, message = '', data = null, error, url } = props || {}; const errResponseKey = typeof error === 'string' ? error : error?.message; // Specified error @@ -47,7 +48,7 @@ export const jsonRes = ( msg = error?.error?.message; } - addLog.error(`response error: ${msg}`, error); + addLog.error(`Api response error: ${url}, ${msg}`, error); } res.status(code).json({ diff --git a/packages/service/common/vectorStore/pg/index.ts b/packages/service/common/vectorStore/pg/index.ts index 68c60eb7f..cbb8c975d 100644 --- a/packages/service/common/vectorStore/pg/index.ts +++ b/packages/service/common/vectorStore/pg/index.ts @@ -169,7 +169,16 @@ class PgClass { } async query(sql: string) { const pg = await connectPg(); - return pg.query(sql); + const start = Date.now(); + return pg.query(sql).then((res) => { + const time = Date.now() - start; + + if (time > 300) { + addLog.warn(`pg query time: ${time}ms, sql: ${sql}`); + } + + return res; + }); } } diff --git a/packages/service/core/app/controller.ts b/packages/service/core/app/controller.ts new file mode 100644 index 000000000..cc5927d90 --- /dev/null +++ b/packages/service/core/app/controller.ts @@ -0,0 +1,65 @@ +import { AppSchema } from '@fastgpt/global/core/app/type'; +import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { getLLMModel } from '../ai/model'; +import { MongoAppVersion } from './versionSchema'; + +export const beforeUpdateAppFormat = ({ + nodes +}: { + nodes: T; +}) => { + if (nodes) { + let maxTokens = 3000; + + nodes.forEach((item) => { + if ( + item.flowNodeType === FlowNodeTypeEnum.chatNode || + item.flowNodeType === FlowNodeTypeEnum.tools + ) { + const model = + item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || ''; + const chatModel = getLLMModel(model); + const quoteMaxToken = chatModel.quoteMaxToken || 3000; + + maxTokens = Math.max(maxTokens, quoteMaxToken); + } + }); + + nodes.forEach((item) => { + if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) { + item.inputs.forEach((input) => { + if (input.key === NodeInputKeyEnum.datasetMaxTokens) { + const val = input.value as number; + if (val > maxTokens) { + input.value = maxTokens; + } + } + }); + } + }); + } + + return { + nodes + }; +}; + +export const getAppLatestVersion = async (appId: string, app?: AppSchema) => { + const version = await MongoAppVersion.findOne({ + appId + }).sort({ + time: -1 + }); + + if (version) { + return { + nodes: version.nodes, + edges: version.edges + }; + } + return { + nodes: app?.modules || [], + edges: app?.edges || [] + }; +}; diff --git a/packages/service/core/app/schema.ts b/packages/service/core/app/schema.ts index 1492efb70..0990f4f7b 100644 --- a/packages/service/core/app/schema.ts +++ b/packages/service/core/app/schema.ts @@ -8,7 +8,7 @@ import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; -export const appCollectionName = 'apps'; +export const AppCollectionName = 'apps'; const AppSchema = new Schema({ teamId: { @@ -46,6 +46,8 @@ const AppSchema = new Schema({ type: Date, default: () => new Date() }, + + // tmp store modules: { type: Array, default: [] @@ -92,6 +94,6 @@ try { } export const MongoApp: Model = - models[appCollectionName] || model(appCollectionName, AppSchema); + models[AppCollectionName] || model(AppCollectionName, AppSchema); MongoApp.syncIndexes(); diff --git a/packages/service/core/app/versionSchema.ts b/packages/service/core/app/versionSchema.ts new file mode 100644 index 000000000..907b44ceb --- /dev/null +++ b/packages/service/core/app/versionSchema.ts @@ -0,0 +1,36 @@ +import { connectionMongo, type Model } from '../../common/mongo'; +const { Schema, model, models } = connectionMongo; +import { AppVersionSchemaType } from '@fastgpt/global/core/app/version'; + +export const AppVersionCollectionName = 'app.versions'; + +const AppVersionSchema = new Schema({ + appId: { + type: Schema.Types.ObjectId, + ref: AppVersionCollectionName, + required: true + }, + time: { + type: Date, + default: () => new Date() + }, + nodes: { + type: Array, + default: [] + }, + edges: { + type: Array, + default: [] + } +}); + +try { + AppVersionSchema.index({ appId: 1, time: -1 }); +} catch (error) { + console.log(error); +} + +export const MongoAppVersion: Model = + models[AppVersionCollectionName] || model(AppVersionCollectionName, AppVersionSchema); + +MongoAppVersion.syncIndexes(); diff --git a/packages/service/core/chat/chatItemSchema.ts b/packages/service/core/chat/chatItemSchema.ts index 6948cdf21..78a212c6a 100644 --- a/packages/service/core/chat/chatItemSchema.ts +++ b/packages/service/core/chat/chatItemSchema.ts @@ -7,7 +7,7 @@ import { TeamCollectionName, TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; -import { appCollectionName } from '../app/schema'; +import { AppCollectionName } from '../app/schema'; import { userCollectionName } from '../../support/user/schema'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; @@ -40,7 +40,7 @@ const ChatItemSchema = new Schema({ }, appId: { type: Schema.Types.ObjectId, - ref: appCollectionName, + ref: AppCollectionName, required: true }, time: { diff --git a/packages/service/core/chat/chatSchema.ts b/packages/service/core/chat/chatSchema.ts index 5483b3e88..588e437f1 100644 --- a/packages/service/core/chat/chatSchema.ts +++ b/packages/service/core/chat/chatSchema.ts @@ -6,7 +6,7 @@ import { TeamCollectionName, TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; -import { appCollectionName } from '../app/schema'; +import { AppCollectionName } from '../app/schema'; export const chatCollectionName = 'chat'; @@ -31,7 +31,7 @@ const ChatSchema = new Schema({ }, appId: { type: Schema.Types.ObjectId, - ref: appCollectionName, + ref: AppCollectionName, required: true }, updateTime: { diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index e07491739..e8bd3e1c5 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -220,9 +220,16 @@ export async function dispatchWorkFlow({ ).then((result) => { const flat = result.flat(); if (flat.length === 0) return; - // update output + + // Update the node output at the end of the run and get the next nodes const nextNodes = flat.map((item) => nodeOutput(item.node, item.result)).flat(); - return checkNodeCanRun(nextNodes); + + // Remove repeat nodes(Make sure that the node is only executed once) + const filterNextNodes = nextNodes.filter( + (node, index, self) => self.findIndex((t) => t.nodeId === node.nodeId) === index + ); + + return checkNodeCanRun(filterNextNodes); }); } // 运行完一轮后,清除连线的状态,避免污染进程 diff --git a/packages/service/core/workflow/dispatch/tools/runIfElse.ts b/packages/service/core/workflow/dispatch/tools/runIfElse.ts index 373954580..edcf27ac5 100644 --- a/packages/service/core/workflow/dispatch/tools/runIfElse.ts +++ b/packages/service/core/workflow/dispatch/tools/runIfElse.ts @@ -8,6 +8,7 @@ import { } from '@fastgpt/global/core/workflow/template/system/ifElse/type'; import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; +import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils'; type Props = ModuleDispatchProps<{ [NodeInputKeyEnum.condition]: IfElseConditionType; @@ -20,20 +21,21 @@ function checkCondition(condition: VariableConditionEnum, variableValue: any, va [VariableConditionEnum.isNotEmpty]: () => !!variableValue, [VariableConditionEnum.equalTo]: () => variableValue === value, [VariableConditionEnum.notEqual]: () => variableValue !== value, - [VariableConditionEnum.greaterThan]: () => variableValue > Number(value), - [VariableConditionEnum.lessThan]: () => variableValue < Number(value), - [VariableConditionEnum.greaterThanOrEqualTo]: () => variableValue >= Number(value), - [VariableConditionEnum.lessThanOrEqualTo]: () => variableValue <= Number(value), - [VariableConditionEnum.include]: () => variableValue.includes(value), - [VariableConditionEnum.notInclude]: () => !variableValue.includes(value), - [VariableConditionEnum.startWith]: () => variableValue.startsWith(value), - [VariableConditionEnum.endWith]: () => variableValue.endsWith(value), - [VariableConditionEnum.lengthEqualTo]: () => variableValue.length === Number(value), - [VariableConditionEnum.lengthNotEqualTo]: () => variableValue.length !== Number(value), - [VariableConditionEnum.lengthGreaterThan]: () => variableValue.length > Number(value), - [VariableConditionEnum.lengthGreaterThanOrEqualTo]: () => variableValue.length >= Number(value), - [VariableConditionEnum.lengthLessThan]: () => variableValue.length < Number(value), - [VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue.length <= Number(value) + [VariableConditionEnum.greaterThan]: () => Number(variableValue) > Number(value), + [VariableConditionEnum.lessThan]: () => Number(variableValue) < Number(value), + [VariableConditionEnum.greaterThanOrEqualTo]: () => Number(variableValue) >= Number(value), + [VariableConditionEnum.lessThanOrEqualTo]: () => Number(variableValue) <= Number(value), + [VariableConditionEnum.include]: () => variableValue?.includes(value), + [VariableConditionEnum.notInclude]: () => !variableValue?.includes(value), + [VariableConditionEnum.startWith]: () => variableValue?.startsWith(value), + [VariableConditionEnum.endWith]: () => variableValue?.endsWith(value), + [VariableConditionEnum.lengthEqualTo]: () => variableValue?.length === Number(value), + [VariableConditionEnum.lengthNotEqualTo]: () => variableValue?.length !== Number(value), + [VariableConditionEnum.lengthGreaterThan]: () => variableValue?.length > Number(value), + [VariableConditionEnum.lengthGreaterThanOrEqualTo]: () => + variableValue?.length >= Number(value), + [VariableConditionEnum.lengthLessThan]: () => variableValue?.length < Number(value), + [VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue?.length <= Number(value) }; return (operations[condition] || (() => false))(); @@ -43,15 +45,18 @@ export const dispatchIfElse = async (props: Props): Promise { const { variable, condition: variableCondition, value } = item; - const variableValue = runtimeNodes - .find((node) => node.nodeId === variable[0]) - ?.outputs?.find((item) => item.id === variable[1])?.value; + const variableValue = getReferenceVariableValue({ + value: variable, + variables, + nodes: runtimeNodes + }); return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || ''); }); diff --git a/packages/service/support/outLink/schema.ts b/packages/service/support/outLink/schema.ts index 666f56701..af0c4b29a 100644 --- a/packages/service/support/outLink/schema.ts +++ b/packages/service/support/outLink/schema.ts @@ -6,7 +6,7 @@ import { TeamCollectionName, TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; -import { appCollectionName } from '../../core/app/schema'; +import { AppCollectionName } from '../../core/app/schema'; const OutLinkSchema = new Schema({ shareId: { @@ -25,7 +25,7 @@ const OutLinkSchema = new Schema({ }, appId: { type: Schema.Types.ObjectId, - ref: appCollectionName, + ref: AppCollectionName, required: true }, type: { diff --git a/packages/service/support/permission/auth/app.ts b/packages/service/support/permission/auth/app.ts index 14c72e233..c8d4525f4 100644 --- a/packages/service/support/permission/auth/app.ts +++ b/packages/service/support/permission/auth/app.ts @@ -30,12 +30,10 @@ export async function authApp({ // get app const app = await MongoApp.findOne({ _id: appId, teamId }).lean(); if (!app) { - return Promise.reject(AppErrEnum.unAuthApp); + return Promise.reject(AppErrEnum.unExist); } - const isOwner = - role !== TeamMemberRoleEnum.visitor && - (String(app.tmbId) === tmbId || role === TeamMemberRoleEnum.owner); + const isOwner = String(app.tmbId) === tmbId; const canWrite = isOwner || (app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor); diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 830f0c49e..8896b248d 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -40,6 +40,7 @@ export const iconPaths = { 'common/paramsLight': () => import('./icons/common/paramsLight.svg'), 'common/playFill': () => import('./icons/common/playFill.svg'), 'common/playLight': () => import('./icons/common/playLight.svg'), + 'common/publishFill': () => import('./icons/common/publishFill.svg'), 'common/questionLight': () => import('./icons/common/questionLight.svg'), 'common/refreshLight': () => import('./icons/common/refreshLight.svg'), 'common/resultLight': () => import('./icons/common/resultLight.svg'), diff --git a/packages/web/components/common/Icon/icons/common/publishFill.svg b/packages/web/components/common/Icon/icons/common/publishFill.svg new file mode 100644 index 000000000..f4bec3711 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/publishFill.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/MySelect/MultipleRowSelect.tsx b/packages/web/components/common/MySelect/MultipleRowSelect.tsx index 6bc901d92..4b19991f3 100644 --- a/packages/web/components/common/MySelect/MultipleRowSelect.tsx +++ b/packages/web/components/common/MySelect/MultipleRowSelect.tsx @@ -54,21 +54,20 @@ const MultipleRowSelect = ({ bg: 'primary.50', color: 'primary.600' }} + onClick={() => { + const newValue = [...cloneValue]; + newValue[index] = item.value; + setCloneValue(newValue); + if (!hasChildren) { + onSelect(newValue); + onClose(); + } + }} {...(item.value === selectedValue ? { color: 'primary.600' } - : { - onClick: () => { - const newValue = [...cloneValue]; - newValue[index] = item.value; - setCloneValue(newValue); - if (!hasChildren) { - onSelect(newValue); - onClose(); - } - } - })} + : {})} > {item.label} diff --git a/packages/web/components/common/MyTooltip/index.tsx b/packages/web/components/common/MyTooltip/index.tsx index 8f63782d7..69e9bf3fa 100644 --- a/packages/web/components/common/MyTooltip/index.tsx +++ b/packages/web/components/common/MyTooltip/index.tsx @@ -12,7 +12,7 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ... any; tip?: string }) => { + const { t } = useTranslation('common'); + + const { tip = t('Confirm to leave the page'), callback } = props || {}; + + useEffect(() => { + const listen = + process.env.NODE_ENV !== 'production' + ? (e: any) => { + e.preventDefault(); + e.returnValue = tip; + callback?.(); + } + : () => {}; + window.addEventListener('beforeunload', listen); + + return () => { + window.removeEventListener('beforeunload', listen); + }; + }, [tip, callback]); +}; diff --git a/packages/web/hooks/useConfirm.tsx b/packages/web/hooks/useConfirm.tsx index 62fc0b5dd..3bcc2d623 100644 --- a/packages/web/hooks/useConfirm.tsx +++ b/packages/web/hooks/useConfirm.tsx @@ -59,7 +59,7 @@ export const useConfirm = (props?: { onClose, ConfirmModal: useCallback( ({ - closeText = t('common.Close'), + closeText = t('common.Cancel'), confirmText = t('common.Confirm'), isLoading, bg, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf7fc6cc8..9b41d73bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -356,9 +356,6 @@ importers: '@fortaine/fetch-event-source': specifier: ^3.0.6 version: 3.0.6 - '@node-rs/jieba': - specifier: 1.10.0 - version: 1.10.0 '@tanstack/react-query': specifier: ^4.24.10 version: 4.24.10(react-dom@18.2.0)(react@18.2.0) diff --git a/projects/app/next.config.js b/projects/app/next.config.js index a23e9f668..d94f87400 100644 --- a/projects/app/next.config.js +++ b/projects/app/next.config.js @@ -35,6 +35,7 @@ const nextConfig = { if (isServer) { config.externals.push('isolated-vm'); config.externals.push('worker_threads'); + config.externals.push('@node-rs/jieba'); if (config.name === 'server') { // config.output.globalObject = 'self'; diff --git a/projects/app/package.json b/projects/app/package.json index 18b6d3a71..093cf16f3 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -23,7 +23,6 @@ "@fastgpt/service": "workspace:*", "@fastgpt/web": "workspace:*", "@fortaine/fetch-event-source": "^3.0.6", - "@node-rs/jieba": "1.10.0", "@tanstack/react-query": "^4.24.10", "@types/nprogress": "^0.2.0", "axios": "^1.5.1", diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index 75dea5c78..0c1cba7f4 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -73,6 +73,7 @@ "Confirm Import": "Import", "Confirm Move": "Move here", "Confirm Update": "Update", + "Confirm to leave the page": "Are you sure to leave this page?", "Copy": "Copy", "Copy Successful": "Copy Successful", "Course": "", @@ -278,6 +279,7 @@ "Api request desc": "Access to the existing system through API, or enterprise micro, flying book, etc", "App intro": "App intro", "App params config": "App Config", + "Auto Save time": "Auto-saved: {{time}}", "Chat Variable": "", "Config schedule plan": "Config schedule config", "Config whisper": "Config whisper", @@ -289,6 +291,11 @@ "Max histories": "Dialog round", "Max tokens": "Max tokens", "Name and avatar": "Avatar & Name", + "Onclick to save": "Save", + "Publish": "Publish", + "Publish Confirm": "Sure to release the app? App status is immediately updated across all publishing channels.", + "Publish Failed": "Publish error", + "Publish Success": "Publish Success", "Question Guide": "Question Guide", "Question Guide Tip": "At the end of the conversation, three leading questions will be asked.", "Quote prompt": "Quote prompt", @@ -1035,11 +1042,16 @@ }, "valueType": { "any": "Any", + "arrayBoolean": "Array Boolean", + "arrayNumber": "Array Number", + "arrayObject": "Array Object", + "arrayString": "Array String", "boolean": "Boolean", "chatHistory": "History", "datasetQuote": "Dataset Quote", "dynamicTargetInput": "Dynamic Input", "number": "Number", + "object": "Object", "selectApp": "Select App", "selectDataset": "Select Dataset", "string": "String", @@ -1128,6 +1140,7 @@ "textarea": "Textarea" }, "tool": { + "Handle": "Tool handle", "Select Tool": "Select Tool" } } diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index cd2611f45..ff47933b8 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -73,6 +73,7 @@ "Confirm Import": "确认导入", "Confirm Move": "移动到这", "Confirm Update": "确认更新", + "Confirm to leave the page": "确认离开该页面?", "Copy": "复制", "Copy Successful": "复制成功", "Course": "", @@ -278,6 +279,7 @@ "Api request desc": "通过 API 接入到已有系统中,或企微、飞书等", "App intro": "应用介绍", "App params config": "应用配置", + "Auto Save time": "自动保存: {{time}}", "Chat Variable": "对话框变量", "Config schedule plan": "配置定时执行", "Config whisper": "配置语音输入", @@ -289,6 +291,11 @@ "Max histories": "聊天记录数量", "Max tokens": "回复上限", "Name and avatar": "头像 & 名称", + "Onclick to save": "点击保存", + "Publish": "发布", + "Publish Confirm": "确认发布应用?会立即更新所有发布渠道的应用状态。", + "Publish Failed": "发布失败", + "Publish Success": "发布成功", "Question Guide": "猜你想问", "Question Guide Tip": "对话结束后,会为生成 3 个引导性问题。", "Quote prompt": "引用模板提示词", @@ -689,7 +696,7 @@ "Data file progress": "数据上传进度", "Data process params": "数据处理参数", "Down load csv template": "点击下载 CSV 模板", - "Embedding Estimated Price Tips": "仅使用索引模型,消耗少量Tokens: {{price}}积分/1k Tokens", + "Embedding Estimated Price Tips": "仅使用索引模型,消耗少量AI积分: {{price}}积分/1k Tokens", "Estimated Price": "预估价格: {{amount}}{{unit}}", "Estimated Price Tips": "QA计费为\n输入: {{charsPointsPrice}}积分/1k Tokens", "Estimated points": "预估消耗 {{points}} 积分", @@ -716,7 +723,7 @@ "Preview chunks": "预览分段(最多5段)", "Preview raw text": "预览源文本(最多3000字)", "Process way": "处理方式", - "QA Estimated Price Tips": "需调用文件处理模型,需要消耗较多Tokens: {{price}}积分/1k Tokens", + "QA Estimated Price Tips": "需调用文件处理模型,需要消耗较多AI积分: {{price}}积分/1k Tokens", "QA Import": "QA拆分", "QA Import Tip": "根据一定规则,将文本拆成一段较大的段落,调用 AI 为该段落生成问答对。有非常高的检索精度,但是会丢失很多内容细节。", "Re Preview": "重新生成预览", @@ -1036,11 +1043,16 @@ }, "valueType": { "any": "任意", + "arrayBoolean": "布尔数组", + "arrayNumber": "数字数组", + "arrayObject": "对象数组", + "arrayString": "字符串数组", "boolean": "布尔", - "chatHistory": "聊天记录", - "datasetQuote": "引用内容", + "chatHistory": "历史记录", + "datasetQuote": "知识库类型", "dynamicTargetInput": "动态字段输入", "number": "数字", + "object": "对象", "selectApp": "应用选择", "selectDataset": "知识库选择", "string": "字符串", @@ -1129,6 +1141,7 @@ "textarea": "多行输入框" }, "tool": { + "Handle": "工具连接器", "Select Tool": "选择工具" } } @@ -1421,7 +1434,7 @@ "user": { "AI point standard": "AI积分标准", "Avatar": "头像", - "Go laf env": "点击前往 laf 获取 PAT 凭证。", + "Go laf env": "点击前往 {{env}} 获取 PAT 凭证。", "Laf account course": "查看绑定 laf 账号教程。", "Laf account intro": "绑定你的laf账号后,你将可以在工作流中使用 laf 模块,实现在线编写代码。", "Need to login": "请先登录", diff --git a/projects/app/src/components/core/workflow/Flow/FlowProvider.tsx b/projects/app/src/components/core/workflow/Flow/FlowProvider.tsx index eb4cff832..9b32f8306 100644 --- a/projects/app/src/components/core/workflow/Flow/FlowProvider.tsx +++ b/projects/app/src/components/core/workflow/Flow/FlowProvider.tsx @@ -36,6 +36,7 @@ import { postWorkflowDebug } from '@/web/core/workflow/api'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; +import { delay } from '@fastgpt/global/common/system/utils'; type OnChange = (changes: ChangesType[]) => void; @@ -83,7 +84,7 @@ export type useFlowProviderStoreType = { sourceHandle?: string | undefined; targetHandle?: string | undefined; }) => void; - initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => void; + initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise; splitToolInputs: ( inputs: FlowNodeInputItemType[], nodeId: string @@ -147,7 +148,10 @@ const StateContext = createContext({ hasToolNode: false, connectingEdge: undefined, basicNodeTemplates: [], - initData: function (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }): void { + initData: function (e: { + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; + }): Promise { throw new Error('Function not implemented.'); }, hoverNodeId: undefined, @@ -608,16 +612,14 @@ export const FlowProvider = ({ ); const initData = useCallback( - (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { + async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item }))); setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item }))); - setTimeout(() => { - onFixView(); - }, 100); + await delay(200); }, - [setEdges, setNodes, onFixView] + [setEdges, setNodes] ); useEffect(() => { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx index e10a4f48a..450cabbd1 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx @@ -10,13 +10,11 @@ import { SmallAddIcon } from '@chakra-ui/icons'; import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat'; import { useFlowProviderStore } from '../FlowProvider'; -import MyIcon from '@fastgpt/web/components/common/Icon'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import MySlider from '@/components/Slider'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import RenderOutput from './render/RenderOutput'; -import Reference from './render/RenderInput/templates/Reference'; import IOTitle from '../components/IOTitle'; const NodeDatasetConcat = ({ data, selected }: NodeProps) => { @@ -47,46 +45,13 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps) => { return maxTokens; }, [llmModelList, nodeList]); - const RenderQuoteList = useMemo(() => { - return ( - - {quotes.map((quote, i) => ( - - - - {t('core.chat.Quote')} - {i + 1} - - { - onChangeNode({ - nodeId, - type: 'delInput', - key: quote.key - }); - }} - /> - - - - ))} - - ); - }, [nodeId, onChangeNode, quotes, t]); - const onAddField = useCallback(() => { onChangeNode({ nodeId, type: 'addInput', - value: getOneQuoteInputTemplate() + value: getOneQuoteInputTemplate({ index: quotes.length + 1 }) }); - }, [nodeId, onChangeNode]); + }, [nodeId, onChangeNode, quotes.length]); const CustomComponent = useMemo(() => { return { @@ -141,7 +106,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps) => { - {RenderQuoteList} + {/* {RenderQuoteList} */} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx index 960d0ba22..9da7eda68 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx @@ -123,7 +123,7 @@ const NodeIfElse = ({ data, selected }: NodeProps) => { onChangeNode({ nodeId, type: 'updateInput', - key: 'condition', + key: NodeInputKeyEnum.condition, value: { ...conditionInput, value: conditionInput.value === 'OR' ? 'AND' : 'OR' @@ -297,7 +297,11 @@ const ConditionSelect = ({ valueType === WorkflowIOValueTypeEnum.datasetQuote || valueType === WorkflowIOValueTypeEnum.dynamic || valueType === WorkflowIOValueTypeEnum.selectApp || - valueType === WorkflowIOValueTypeEnum.tools + valueType === WorkflowIOValueTypeEnum.arrayBoolean || + valueType === WorkflowIOValueTypeEnum.arrayNumber || + valueType === WorkflowIOValueTypeEnum.arrayObject || + valueType === WorkflowIOValueTypeEnum.arrayString || + valueType === WorkflowIOValueTypeEnum.object ) return arrayConditionList; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx index 611d5e8bb..731c39878 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx @@ -5,21 +5,14 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import dynamic from 'next/dynamic'; import { Box, Button, Flex } from '@chakra-ui/react'; import { SmallAddIcon } from '@chakra-ui/icons'; -import { - FlowNodeInputTypeEnum, - FlowNodeOutputTypeEnum -} from '@fastgpt/global/core/workflow/node/constant'; +import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import Container from '../components/Container'; import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; -import { - FlowNodeInputItemType, - FlowNodeOutputItemType -} from '@fastgpt/global/core/workflow/type/io'; +import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { useTranslation } from 'next-i18next'; import { useFlowProviderStore } from '../FlowProvider'; import RenderInput from './render/RenderInput'; -import { getNanoid } from '@fastgpt/global/common/string/tools'; const FieldEditModal = dynamic(() => import('./render/FieldEditModal')); @@ -37,7 +30,7 @@ const createEditField: EditInputFieldMapType = { const NodePluginOutput = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); - const { nodeId, inputs, outputs } = data; + const { nodeId, inputs } = data; const { onChangeNode } = useFlowProviderStore(); const [createField, setCreateField] = useState(); @@ -93,13 +86,6 @@ const NodePluginOutput = ({ data, selected }: NodeProps) => { canEdit: true, editField: createEditField }; - const newOutput: FlowNodeOutputItemType = { - id: getNanoid(), - key: data.key, - valueType: data.valueType, - label: data.label, - type: FlowNodeOutputTypeEnum.static - }; onChangeNode({ nodeId, diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx index 22f05de71..885d2ef3b 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx @@ -22,7 +22,7 @@ const NodeStart = ({ data, selected }: NodeProps) => { }} {...data} > - + diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/FieldEditModal.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/FieldEditModal.tsx index 7dc9566fa..0122a6a24 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/FieldEditModal.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/FieldEditModal.tsx @@ -149,13 +149,6 @@ const FieldEditModal = ({ [showDynamicInputSelect, t] ); - const dataTypeSelectList = Object.values(FlowValueTypeMap) - .slice(0, -2) - .map((item) => ({ - label: t(item.label), - value: item.value - })); - const { register, getValues, setValue, handleSubmit, watch } = useForm({ defaultValues: { ...defaultValue, @@ -224,6 +217,13 @@ const FieldEditModal = ({ return inputType === FlowNodeInputTypeEnum.addInputParam; }, [inputType]); + const slicedTypeMap = Object.values(FlowValueTypeMap).slice(0, -1); + + const dataTypeSelectList = slicedTypeMap.map((item) => ({ + label: t(item.label), + value: item.value + })); + const onSubmitSuccess = useCallback( (data: EditNodeFieldType) => { data.key = data?.key?.trim(); @@ -270,7 +270,7 @@ const FieldEditModal = ({ changeKey: !keys.includes(data.key) }); }, - [defaultField.key, keys, onSubmit, showValueTypeSelect, t, toast] + [defaultField.key, inputTypeList, keys, onSubmit, showValueTypeSelect, t, toast] ); const onSubmitError = useCallback( (e: Object) => { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx index 573438a81..7f939362a 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx @@ -28,17 +28,9 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => { (connectingEdge?.handleId !== NodeOutputKeyEnum.selectedTools || edges.some((edge) => edge.targetHandle === getHandleId(nodeId, 'target', 'top'))); - const valueTypeMap = FlowValueTypeMap[WorkflowIOValueTypeEnum.tools]; - const Render = useMemo(() => { return ( - + { ); - }, [handleId, hidden, t, valueTypeMap?.description, valueTypeMap?.label]); + }, [handleId, hidden, t]); return Render; }; @@ -72,8 +64,6 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => { const { t } = useTranslation(); const { setEdges } = useFlowProviderStore(); - const valueTypeMap = FlowValueTypeMap[WorkflowIOValueTypeEnum.tools]; - /* onConnect edge, delete tool input and switch */ const onConnect = useCallback( (e: Connection) => { @@ -90,13 +80,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => { const Render = useMemo(() => { return ( - + { ); - }, [onConnect, t, valueTypeMap?.description, valueTypeMap?.label]); + }, [onConnect, t]); return Render; }; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx index 926890ee9..6e68f8b39 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx @@ -513,11 +513,14 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({ {/* result */} {debugResult.showResult && ( {/* Status header */} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx index fa4c167e9..25f0eb137 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx @@ -1,7 +1,4 @@ -import { - FlowNodeInputItemType, - FlowNodeOutputItemType -} from '@fastgpt/global/core/workflow/type/io.d'; +import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'next-i18next'; import { useFlowProviderStore } from '../../../FlowProvider'; @@ -14,7 +11,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import dynamic from 'next/dynamic'; import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; -import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import ValueTypeLabel from '../ValueTypeLabel'; const FieldEditModal = dynamic(() => import('../FieldEditModal')); @@ -37,16 +33,10 @@ const InputLabel = ({ nodeId, input }: Props) => { renderTypeList, valueType, canEdit, - key, - value + key } = input; const [editField, setEditField] = useState(); - const valueTypeLabel = useMemo( - () => (valueType ? t(FlowValueTypeMap[valueType]?.label) : ''), - [t, valueType] - ); - const onChangeRenderType = useCallback( (e: string) => { const index = renderTypeList.findIndex((item) => item === e) || 0; @@ -84,35 +74,35 @@ const InputLabel = ({ nodeId, input }: Props) => { )} {/* value type */} - {renderType === FlowNodeInputTypeEnum.reference && !!valueTypeLabel && ( - {valueTypeLabel} - )} + {renderType === FlowNodeInputTypeEnum.reference && } {/* edit config */} {canEdit && ( <> - - setEditField({ - inputType: renderTypeList[0], - valueType: valueType, - key, - label, - description, - isToolInput: !!toolDescription, - defaultValue: input.defaultValue, - maxLength: input.maxLength, - max: input.max, - min: input.min, - dynamicParamDefaultValue: input.dynamicParamDefaultValue - }) - } - /> + {input.editField && Object.keys(input.editField).length > 0 && ( + + setEditField({ + inputType: renderTypeList[0], + valueType: valueType, + key, + label, + description, + isToolInput: !!toolDescription, + defaultValue: input.defaultValue, + maxLength: input.maxLength, + max: input.max, + min: input.min, + dynamicParamDefaultValue: input.dynamicParamDefaultValue + }) + } + /> + )} { selectedTypeIndex, t, toolDescription, - valueType, - valueTypeLabel + valueType ]); return RenderLabel; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx index 500e8f1df..5b2dcec07 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx @@ -20,7 +20,7 @@ import { import { QuestionOutlineIcon } from '@chakra-ui/icons'; import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; import PromptTemplate from '@/components/PromptTemplate'; -import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import MyIcon from '@fastgpt/web/components/common/Icon'; import Reference from './Reference'; import { getSystemVariables } from '@/web/core/app/utils'; @@ -152,7 +152,7 @@ const SettingQuotePrompt = (props: RenderInputProps) => { {t('core.module.Dataset quote.label')} - {t('core.module.valueType.datasetQuote')} + (valueType ? t(FlowValueTypeMap[valueType]?.label) : '-'), - [t, valueType] - ); - const Render = useMemo(() => { return ( @@ -34,11 +28,15 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu } : {})} > - + {t(label)} {description && } - {valueTypeLabel} + {output.type === FlowNodeOutputTypeEnum.source && ( ); - }, [description, output.key, output.type, label, nodeId, t, valueTypeLabel]); + }, [output.type, output.key, t, label, description, valueType, nodeId]); return Render; }; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/TargetHandle.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/TargetHandle.tsx deleted file mode 100644 index 0bc0af821..000000000 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/TargetHandle.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useMemo } from 'react'; -import { Box, BoxProps } from '@chakra-ui/react'; -import { Handle, OnConnect, Position } from 'reactflow'; -import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; -import MyTooltip from '@/components/MyTooltip'; -import { useTranslation } from 'next-i18next'; -import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; - -interface Props extends BoxProps { - handleKey: string; - valueType?: WorkflowIOValueTypeEnum; -} - -const TargetHandle = ({ handleKey, valueType, ...props }: Props) => { - const { t } = useTranslation(); - - const valType = valueType ?? WorkflowIOValueTypeEnum.any; - const valueStyle = useMemo( - () => - valueType && FlowValueTypeMap[valueType] - ? FlowValueTypeMap[valueType]?.handlerStyle - : FlowValueTypeMap[WorkflowIOValueTypeEnum.any]?.handlerStyle, - [valueType] - ); - - return ( - - - - - - ); -}; - -export default React.memo(TargetHandle); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/ValueTypeLabel.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/ValueTypeLabel.tsx index d704d1a57..6d027a3ca 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/ValueTypeLabel.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/ValueTypeLabel.tsx @@ -1,21 +1,33 @@ +import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import { Box } from '@chakra-ui/react'; +import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import React from 'react'; -const ValueTypeLabel = ({ children }: { children: React.ReactNode }) => { - return ( - - {children} - - ); +const ValueTypeLabel = ({ valueType }: { valueType?: WorkflowIOValueTypeEnum }) => { + const valueTypeData = valueType ? FlowValueTypeMap[valueType] : undefined; + + const label = valueTypeData?.label || ''; + const description = valueTypeData?.description || ''; + + return !!label ? ( + + + {label} + + + ) : null; }; export default React.memo(ValueTypeLabel); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/VariableTable.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/VariableTable.tsx index c6a2b8066..a5527a51f 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/VariableTable.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/VariableTable.tsx @@ -1,17 +1,6 @@ import React from 'react'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { - Box, - Table, - Thead, - Tbody, - Tr, - Th, - Td, - TableContainer, - Button, - Flex -} from '@chakra-ui/react'; +import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import type { EditInputFieldMapType, diff --git a/projects/app/src/components/support/laf/LafAccountModal.tsx b/projects/app/src/components/support/laf/LafAccountModal.tsx index 89d6d311c..987437c70 100644 --- a/projects/app/src/components/support/laf/LafAccountModal.tsx +++ b/projects/app/src/components/support/laf/LafAccountModal.tsx @@ -108,7 +108,7 @@ const LafAccountModal = ({ - {t('support.user.Go laf env')} + {t('support.user.Go laf env', { env: feConfigs.lafEnv })} diff --git a/packages/global/core/app/api.d.ts b/projects/app/src/global/core/app/api.d.ts similarity index 52% rename from packages/global/core/app/api.d.ts rename to projects/app/src/global/core/app/api.d.ts index 80c06e769..8e609084c 100644 --- a/packages/global/core/app/api.d.ts +++ b/projects/app/src/global/core/app/api.d.ts @@ -1,6 +1,5 @@ -import type { LLMModelItemType } from '../ai/model.d'; -import { AppTypeEnum } from './constants'; -import { AppSchema } from './type'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { AppSchema } from '@fastgpt/global/core/app/type'; export type CreateAppParams = { name?: string; @@ -10,13 +9,19 @@ export type CreateAppParams = { edges?: AppSchema['edges']; }; -export interface AppUpdateParams { +export type AppUpdateParams = { name?: string; type?: `${AppTypeEnum}`; avatar?: string; intro?: string; - modules?: AppSchema['modules']; + nodes?: AppSchema['modules']; edges?: AppSchema['edges']; permission?: AppSchema['permission']; teamTags?: AppSchema['teamTags']; -} +}; + +export type PostPublishAppProps = { + type: `${AppTypeEnum}`; + nodes: AppSchema['modules']; + edges: AppSchema['edges']; +}; diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index c05cd98b1..bc68101e7 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -1,52 +1,68 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import type { CreateAppParams } from '@fastgpt/global/core/app/api.d'; +import type { CreateAppParams } from '@/global/core/app/api.d'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { MongoApp } from '@fastgpt/service/core/app/schema'; import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit'; +import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; +import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema'; +import { NextAPI } from '@/service/middle/entry'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { - name = 'APP', - avatar, - type = AppTypeEnum.advanced, - modules, - edges - } = req.body as CreateAppParams; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { + name = 'APP', + avatar, + type = AppTypeEnum.advanced, + modules, + edges + } = req.body as CreateAppParams; - if (!name || !Array.isArray(modules)) { - throw new Error('缺少参数'); - } - - // 凭证校验 - const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); - - // 上限校验 - await checkTeamAppLimit(teamId); - - // 创建模型 - const response = await MongoApp.create({ - avatar, - name, - teamId, - tmbId, - modules, - edges, - type, - version: 'v2' - }); - - jsonRes(res, { - data: response._id - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); + if (!name || !Array.isArray(modules)) { + throw new Error('缺少参数'); } + + // 凭证校验 + const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); + + // 上限校验 + await checkTeamAppLimit(teamId); + + // 创建模型 + const appId = await mongoSessionRun(async (session) => { + const [{ _id: appId }] = await MongoApp.create( + [ + { + avatar, + name, + teamId, + tmbId, + modules, + edges, + type, + version: 'v2' + } + ], + { session } + ); + + await MongoAppVersion.create( + [ + { + appId, + nodes: modules, + edges + } + ], + { session } + ); + + return appId; + }); + + jsonRes(res, { + data: appId + }); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/del.ts b/projects/app/src/pages/api/core/app/del.ts index 081bb04ef..ff6cdbe67 100644 --- a/projects/app/src/pages/api/core/app/del.ts +++ b/projects/app/src/pages/api/core/app/del.ts @@ -1,60 +1,59 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; +import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema'; +import { NextAPI } from '@/service/middle/entry'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { appId } = req.query as { appId: string }; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { appId } = req.query as { appId: string }; - if (!appId) { - throw new Error('参数错误'); - } - - // 凭证校验 - await authApp({ req, authToken: true, appId, per: 'owner' }); - - // 删除对应的聊天 - await mongoSessionRun(async (session) => { - await MongoChatItem.deleteMany( - { - appId - }, - { session } - ); - await MongoChat.deleteMany( - { - appId - }, - { session } - ); - // 删除分享链接 - await MongoOutLink.deleteMany( - { - appId - }, - { session } - ); - // delete app - await MongoApp.deleteOne( - { - _id: appId - }, - { session } - ); - }); - - jsonRes(res); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); + if (!appId) { + throw new Error('参数错误'); } + + // 凭证校验 + await authApp({ req, authToken: true, appId, per: 'owner' }); + + // 删除对应的聊天 + await mongoSessionRun(async (session) => { + await MongoChatItem.deleteMany( + { + appId + }, + { session } + ); + await MongoChat.deleteMany( + { + appId + }, + { session } + ); + // 删除分享链接 + await MongoOutLink.deleteMany( + { + appId + }, + { session } + ); + // delete version + await MongoAppVersion.deleteMany( + { + appId + }, + { session } + ); + // delete app + await MongoApp.deleteOne( + { + _id: appId + }, + { session } + ); + }); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/detail.tsx b/projects/app/src/pages/api/core/app/detail.tsx index 78e9c8381..63dcd644b 100644 --- a/projects/app/src/pages/api/core/app/detail.tsx +++ b/projects/app/src/pages/api/core/app/detail.tsx @@ -2,27 +2,20 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { authApp } from '@fastgpt/service/support/permission/auth/app'; +import { NextAPI } from '@/service/middle/entry'; /* 获取我的模型 */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { appId } = req.query as { appId: string }; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { appId } = req.query as { appId: string }; - if (!appId) { - throw new Error('参数错误'); - } - - // 凭证校验 - const { app } = await authApp({ req, authToken: true, appId, per: 'w' }); - - jsonRes(res, { - data: app - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); + if (!appId) { + throw new Error('参数错误'); } + + // 凭证校验 + const { app } = await authApp({ req, authToken: true, appId, per: 'w' }); + + return app; } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/getChatLogs.ts b/projects/app/src/pages/api/core/app/getChatLogs.ts index 584d6478f..1dcbe2d1e 100644 --- a/projects/app/src/pages/api/core/app/getChatLogs.ts +++ b/projects/app/src/pages/api/core/app/getChatLogs.ts @@ -1,6 +1,4 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import type { PagingData } from '@/types'; import { AppLogsListItemType } from '@/types/app'; @@ -9,144 +7,140 @@ import { addDays } from 'date-fns'; import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d'; import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema'; +import { NextAPI } from '@/service/middle/entry'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { - pageNum = 1, - pageSize = 20, - appId, - dateStart = addDays(new Date(), -7), - dateEnd = new Date() - } = req.body as GetAppChatLogsParams; +async function handler( + req: NextApiRequest, + res: NextApiResponse +): Promise> { + const { + pageNum = 1, + pageSize = 20, + appId, + dateStart = addDays(new Date(), -7), + dateEnd = new Date() + } = req.body as GetAppChatLogsParams; - if (!appId) { - throw new Error('缺少参数'); + if (!appId) { + throw new Error('缺少参数'); + } + + // 凭证校验 + const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' }); + + const where = { + teamId: new Types.ObjectId(teamId), + appId: new Types.ObjectId(appId), + updateTime: { + $gte: new Date(dateStart), + $lte: new Date(dateEnd) } + }; - // 凭证校验 - const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' }); - - const where = { - teamId: new Types.ObjectId(teamId), - appId: new Types.ObjectId(appId), - updateTime: { - $gte: new Date(dateStart), - $lte: new Date(dateEnd) - } - }; - - const [data, total] = await Promise.all([ - MongoChat.aggregate([ - { $match: where }, - { - $sort: { - userBadFeedbackCount: -1, - userGoodFeedbackCount: -1, - customFeedbacksCount: -1, - updateTime: -1 - } - }, - { $skip: (pageNum - 1) * pageSize }, - { $limit: pageSize }, - { - $lookup: { - from: ChatItemCollectionName, - let: { chatId: '$chatId' }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { $eq: ['$appId', new Types.ObjectId(appId)] }, - { $eq: ['$chatId', '$$chatId'] } - ] - } - } - }, - { - $project: { - userGoodFeedback: 1, - userBadFeedback: 1, - customFeedbacks: 1, - adminFeedback: 1 - } - } - ], - as: 'chatitems' - } - }, - { - $addFields: { - userGoodFeedbackCount: { - $size: { - $filter: { - input: '$chatitems', - as: 'item', - cond: { $ifNull: ['$$item.userGoodFeedback', false] } + const [data, total] = await Promise.all([ + MongoChat.aggregate([ + { $match: where }, + { + $sort: { + userBadFeedbackCount: -1, + userGoodFeedbackCount: -1, + customFeedbacksCount: -1, + updateTime: -1 + } + }, + { $skip: (pageNum - 1) * pageSize }, + { $limit: pageSize }, + { + $lookup: { + from: ChatItemCollectionName, + let: { chatId: '$chatId' }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { $eq: ['$appId', new Types.ObjectId(appId)] }, + { $eq: ['$chatId', '$$chatId'] } + ] } } }, - userBadFeedbackCount: { - $size: { - $filter: { - input: '$chatitems', - as: 'item', - cond: { $ifNull: ['$$item.userBadFeedback', false] } - } + { + $project: { + userGoodFeedback: 1, + userBadFeedback: 1, + customFeedbacks: 1, + adminFeedback: 1 } - }, - customFeedbacksCount: { - $size: { - $filter: { - input: '$chatitems', - as: 'item', - cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] } - } + } + ], + as: 'chatitems' + } + }, + { + $addFields: { + userGoodFeedbackCount: { + $size: { + $filter: { + input: '$chatitems', + as: 'item', + cond: { $ifNull: ['$$item.userGoodFeedback', false] } } - }, - markCount: { - $size: { - $filter: { - input: '$chatitems', - as: 'item', - cond: { $ifNull: ['$$item.adminFeedback', false] } - } + } + }, + userBadFeedbackCount: { + $size: { + $filter: { + input: '$chatitems', + as: 'item', + cond: { $ifNull: ['$$item.userBadFeedback', false] } + } + } + }, + customFeedbacksCount: { + $size: { + $filter: { + input: '$chatitems', + as: 'item', + cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] } + } + } + }, + markCount: { + $size: { + $filter: { + input: '$chatitems', + as: 'item', + cond: { $ifNull: ['$$item.adminFeedback', false] } } } } - }, - { - $project: { - _id: 1, - id: '$chatId', - title: 1, - source: 1, - time: '$updateTime', - messageCount: { $size: '$chatitems' }, - userGoodFeedbackCount: 1, - userBadFeedbackCount: 1, - customFeedbacksCount: 1, - markCount: 1 - } } - ]), - MongoChat.countDocuments(where) - ]); - - jsonRes>(res, { - data: { - pageNum, - pageSize, - data, - total + }, + { + $project: { + _id: 1, + id: '$chatId', + title: 1, + source: 1, + time: '$updateTime', + messageCount: { $size: '$chatitems' }, + userGoodFeedbackCount: 1, + userBadFeedbackCount: 1, + customFeedbacksCount: 1, + markCount: 1 + } } - }); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } + ]), + MongoChat.countDocuments(where) + ]); + + return { + pageNum, + pageSize, + data, + total + }; } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts index b4b45deef..4ca9f1711 100644 --- a/projects/app/src/pages/api/core/app/list.ts +++ b/projects/app/src/pages/api/core/app/list.ts @@ -1,38 +1,30 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; import { MongoApp } from '@fastgpt/service/core/app/schema'; import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; import { AppListItemType } from '@fastgpt/global/core/app/type'; import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; +import { NextAPI } from '@/service/middle/entry'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - // 凭证校验 - const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true }); +async function handler(req: NextApiRequest, res: NextApiResponse): Promise { + // 凭证校验 + const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true }); - // 根据 userId 获取模型信息 - const myApps = await MongoApp.find( - { ...mongoRPermission({ teamId, tmbId, role }) }, - '_id avatar name intro tmbId permission' - ).sort({ - updateTime: -1 - }); - jsonRes(res, { - data: myApps.map((app) => ({ - _id: app._id, - avatar: app.avatar, - name: app.name, - intro: app.intro, - isOwner: teamOwner || String(app.tmbId) === tmbId, - permission: app.permission - })) - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } + // 根据 userId 获取模型信息 + const myApps = await MongoApp.find( + { ...mongoRPermission({ teamId, tmbId, role }) }, + '_id avatar name intro tmbId permission' + ).sort({ + updateTime: -1 + }); + + return myApps.map((app) => ({ + _id: app._id, + avatar: app.avatar, + name: app.name, + intro: app.intro, + isOwner: teamOwner || String(app.tmbId) === tmbId, + permission: app.permission + })); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/update.ts b/projects/app/src/pages/api/core/app/update.ts index a83343317..83f9150d6 100644 --- a/projects/app/src/pages/api/core/app/update.ts +++ b/projects/app/src/pages/api/core/app/update.ts @@ -2,106 +2,50 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoApp } from '@fastgpt/service/core/app/schema'; -import type { AppUpdateParams } from '@fastgpt/global/core/app/api'; +import type { AppUpdateParams } from '@/global/core/app/api'; import { authApp } from '@fastgpt/service/support/permission/auth/app'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { getLLMModel } from '@fastgpt/service/core/ai/model'; -import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils'; -import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time'; import { getScheduleTriggerApp } from '@/service/core/app/utils'; +import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller'; +import { NextAPI } from '@/service/middle/entry'; /* 获取我的模型 */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { - name, - avatar, - type, - intro, - modules: nodes, - edges, - permission, - teamTags - } = req.body as AppUpdateParams; - const { appId } = req.query as { appId: string }; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { name, avatar, type, intro, nodes, edges, permission, teamTags } = + req.body as AppUpdateParams; + const { appId } = req.query as { appId: string }; - if (!appId) { - throw new Error('appId is empty'); - } - - // 凭证校验 - await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' }); - - // format nodes data - // 1. dataset search limit, less than model quoteMaxToken - if (nodes) { - let maxTokens = 3000; - - nodes.forEach((item) => { - if ( - item.flowNodeType === FlowNodeTypeEnum.chatNode || - item.flowNodeType === FlowNodeTypeEnum.tools - ) { - const model = - item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || ''; - const chatModel = getLLMModel(model); - const quoteMaxToken = chatModel.quoteMaxToken || 3000; - - maxTokens = Math.max(maxTokens, quoteMaxToken); - } - }); - - nodes.forEach((item) => { - if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) { - item.inputs.forEach((input) => { - if (input.key === NodeInputKeyEnum.datasetMaxTokens) { - const val = input.value as number; - if (val > maxTokens) { - input.value = maxTokens; - } - } - }); - } - }); - } - // 2. get schedule plan - const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(nodes || [])); - - // 更新模型 - await MongoApp.updateOne( - { - _id: appId - }, - { - name, - type, - avatar, - intro, - permission, - version: 'v2', - teamTags: teamTags, - ...(nodes && { - modules: nodes - }), - ...(edges && { - edges - }), - scheduledTriggerConfig, - scheduledTriggerNextTime: scheduledTriggerConfig - ? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig) - : null - } - ); - - getScheduleTriggerApp(); - - jsonRes(res); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); + if (!appId) { + throw new Error('appId is empty'); } + + // 凭证校验 + await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' }); + + // format nodes data + // 1. dataset search limit, less than model quoteMaxToken + const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes }); + + // 更新模型 + await MongoApp.updateOne( + { + _id: appId + }, + { + name, + type, + avatar, + intro, + permission, + version: 'v2', + ...(teamTags && teamTags), + ...(formatNodes && { + modules: formatNodes + }), + ...(edges && { + edges + }) + } + ); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/updateTeamTasg.ts b/projects/app/src/pages/api/core/app/updateTeamTasg.ts deleted file mode 100644 index de3b3af2f..000000000 --- a/projects/app/src/pages/api/core/app/updateTeamTasg.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import { MongoApp } from '@fastgpt/service/core/app/schema'; -import type { AppUpdateParams } from '@fastgpt/global/core/app/api'; -import { authApp } from '@fastgpt/service/support/permission/auth/app'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { getLLMModel } from '@fastgpt/service/core/ai/model'; - -/* 获取我的模型 */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { name, avatar, type, intro, modules, permission, teamTags } = - req.body as AppUpdateParams; - const { appId } = req.query as { appId: string }; - - if (!appId) { - throw new Error('appId is empty'); - } - - // 凭证校验 - await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' }); - - // check modules - // 1. dataset search limit, less than model quoteMaxToken - if (modules) { - let maxTokens = 3000; - - modules.forEach((item) => { - if (item.flowNodeType === FlowNodeTypeEnum.chatNode) { - const model = - item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || ''; - const chatModel = getLLMModel(model); - const quoteMaxToken = chatModel.quoteMaxToken || 3000; - - maxTokens = Math.max(maxTokens, quoteMaxToken); - } - }); - - modules.forEach((item) => { - if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) { - item.inputs.forEach((input) => { - if (input.key === NodeInputKeyEnum.datasetMaxTokens) { - const val = input.value as number; - if (val > maxTokens) { - input.value = maxTokens; - } - } - }); - } - }); - } - - // 更新模型 - await MongoApp.findOneAndUpdate( - { - _id: appId - }, - { - name, - type, - avatar, - intro, - permission, - teamTags: teamTags, - ...(modules && { - modules - }) - } - ); - - jsonRes(res); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/core/app/version/publish.ts b/projects/app/src/pages/api/core/app/version/publish.ts new file mode 100644 index 000000000..e921b8e80 --- /dev/null +++ b/projects/app/src/pages/api/core/app/version/publish.ts @@ -0,0 +1,54 @@ +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 { PostPublishAppProps } from '@/global/core/app/api'; + +type Response = {}; + +async function handler(req: NextApiRequest, res: NextApiResponse): Promise<{}> { + const { appId } = req.query as { appId: string }; + const { nodes = [], edges = [], type } = req.body as PostPublishAppProps; + + await authApp({ appId, req, per: 'w', authToken: true }); + + const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes }); + + const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(formatNodes || [])); + + await mongoSessionRun(async (session) => { + // create version histories + await MongoAppVersion.create( + [ + { + appId, + nodes: formatNodes, + edges + } + ], + { session } + ); + + // update app + await MongoApp.findByIdAndUpdate(appId, { + modules: formatNodes, + edges, + updateTime: new Date(), + version: 'v2', + type, + scheduledTriggerConfig, + scheduledTriggerNextTime: scheduledTriggerConfig + ? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig) + : null + }); + }); + + return {}; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/chat/init.ts b/projects/app/src/pages/api/core/chat/init.ts index dc3e9471d..cdeb5eac1 100644 --- a/projects/app/src/pages/api/core/chat/init.ts +++ b/projects/app/src/pages/api/core/chat/init.ts @@ -9,6 +9,7 @@ import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { getChatItems } from '@fastgpt/service/core/chat/controller'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; +import { getAppLatestVersion } from '@fastgpt/service/core/app/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -40,14 +41,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } // get app and history - const { history } = await getChatItems({ - appId, - chatId, - limit: 30, - field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ - DispatchNodeResponseKeyEnum.nodeResponse - } ${loadCustomFeedbacks ? 'customFeedbacks' : ''}` - }); + const [{ history }, { nodes }] = await Promise.all([ + getChatItems({ + appId, + chatId, + limit: 30, + field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ + DispatchNodeResponseKeyEnum.nodeResponse + } ${loadCustomFeedbacks ? 'customFeedbacks' : ''}` + }), + getAppLatestVersion(app._id, app) + ]); jsonRes(res, { data: { @@ -58,8 +62,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) variables: chat?.variables || {}, history, app: { - userGuideModule: getGuideModule(app.modules), - chatModels: getChatModelNameListByModules(app.modules), + userGuideModule: getGuideModule(nodes), + chatModels: getChatModelNameListByModules(nodes), name: app.name, avatar: app.avatar, intro: app.intro diff --git a/projects/app/src/pages/api/core/chat/outLink/init.ts b/projects/app/src/pages/api/core/chat/outLink/init.ts index fb4d831de..fdc5677cc 100644 --- a/projects/app/src/pages/api/core/chat/outLink/init.ts +++ b/projects/app/src/pages/api/core/chat/outLink/init.ts @@ -14,6 +14,7 @@ import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { getAppLatestVersion } from '@fastgpt/service/core/app/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -40,14 +41,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error(ChatErrEnum.unAuthChat); } - const { history } = await getChatItems({ - appId: app._id, - chatId, - limit: 30, - field: `dataId obj value userGoodFeedback userBadFeedback ${ - shareChat.responseDetail ? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` : '' - } ` - }); + const [{ history }, { nodes }] = await Promise.all([ + getChatItems({ + appId: app._id, + chatId, + limit: 30, + field: `dataId obj value userGoodFeedback userBadFeedback ${ + shareChat.responseDetail + ? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` + : '' + } ` + }), + getAppLatestVersion(app._id, app) + ]); // pick share response field history.forEach((item) => { @@ -66,8 +72,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) variables: chat?.variables || {}, history, app: { - userGuideModule: getGuideModule(app.modules), - chatModels: getChatModelNameListByModules(app.modules), + userGuideModule: getGuideModule(nodes), + chatModels: getChatModelNameListByModules(nodes), name: app.name, avatar: app.avatar, intro: app.intro diff --git a/projects/app/src/pages/api/core/chat/team/init.ts b/projects/app/src/pages/api/core/chat/team/init.ts index e335e6d1f..7693dff09 100644 --- a/projects/app/src/pages/api/core/chat/team/init.ts +++ b/projects/app/src/pages/api/core/chat/team/init.ts @@ -14,6 +14,7 @@ import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { getAppLatestVersion } from '@fastgpt/service/core/app/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -46,12 +47,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } // get app and history - const { history } = await getChatItems({ - appId, - chatId, - limit: 30, - field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` - }); + const [{ history }, { nodes }] = await Promise.all([ + getChatItems({ + appId, + chatId, + limit: 30, + field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` + }), + getAppLatestVersion(app._id, app) + ]); // pick share response field history.forEach((item) => { @@ -69,8 +73,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) variables: chat?.variables || {}, history, app: { - userGuideModule: getGuideModule(app.modules), - chatModels: getChatModelNameListByModules(app.modules), + userGuideModule: getGuideModule(nodes), + chatModels: getChatModelNameListByModules(nodes), name: app.name, avatar: app.avatar, intro: app.intro diff --git a/projects/app/src/pages/api/core/dataset/data/delete.ts b/projects/app/src/pages/api/core/dataset/data/delete.ts index 708f21ada..452dbb2e2 100644 --- a/projects/app/src/pages/api/core/dataset/data/delete.ts +++ b/projects/app/src/pages/api/core/dataset/data/delete.ts @@ -1,40 +1,32 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; -import { connectToDatabase } from '@/service/mongo'; import { authDatasetData } from '@/service/support/permission/auth/dataset'; import { deleteDatasetData } from '@/service/core/dataset/data/controller'; +import { NextAPI } from '@/service/middle/entry'; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { id: dataId } = req.query as { - id: string; - }; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { id: dataId } = req.query as { + id: string; + }; - if (!dataId) { - throw new Error('dataId is required'); - } - - // 凭证校验 - const { teamId, datasetData } = await authDatasetData({ - req, - authToken: true, - authApiKey: true, - dataId, - per: 'w' - }); - - await deleteDatasetData(datasetData); - - jsonRes(res, { - data: 'success' - }); - } catch (err) { - console.log(err); - jsonRes(res, { - code: 500, - error: err - }); + if (!dataId) { + throw new Error('dataId is required'); } -}); + + // 凭证校验 + const { teamId, datasetData } = await authDatasetData({ + req, + authToken: true, + authApiKey: true, + dataId, + per: 'w' + }); + + await deleteDatasetData(datasetData); + + jsonRes(res, { + data: 'success' + }); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/data/detail.ts b/projects/app/src/pages/api/core/dataset/data/detail.ts index 57f93c29f..c3b88ffd4 100644 --- a/projects/app/src/pages/api/core/dataset/data/detail.ts +++ b/projects/app/src/pages/api/core/dataset/data/detail.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { authDatasetData } from '@/service/support/permission/auth/dataset'; +import { NextAPI } from '@/service/middle/entry'; export type Response = { id: string; @@ -10,29 +11,23 @@ export type Response = { source: string; }; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { id: dataId } = req.query as { - id: string; - }; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { id: dataId } = req.query as { + id: string; + }; - // 凭证校验 - const { datasetData } = await authDatasetData({ - req, - authToken: true, - authApiKey: true, - dataId, - per: 'r' - }); + // 凭证校验 + const { datasetData } = await authDatasetData({ + req, + authToken: true, + authApiKey: true, + dataId, + per: 'r' + }); - jsonRes(res, { - data: datasetData - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } + jsonRes(res, { + data: datasetData + }); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/data/insertData.ts b/projects/app/src/pages/api/core/dataset/data/insertData.ts index c376b98b7..07ac8141d 100644 --- a/projects/app/src/pages/api/core/dataset/data/insertData.ts +++ b/projects/app/src/pages/api/core/dataset/data/insertData.ts @@ -5,7 +5,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { countPromptTokens } from '@fastgpt/service/common/string/tiktoken/index'; import { getVectorModel } from '@fastgpt/service/core/ai/model'; import { hasSameValue } from '@/service/core/dataset/data/utils'; @@ -16,92 +15,87 @@ import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push'; import { InsertOneDatasetDataProps } from '@/global/core/dataset/api'; import { simpleText } from '@fastgpt/global/common/string/tools'; import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; +import { NextAPI } from '@/service/middle/entry'; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { collectionId, q, a, indexes } = req.body as InsertOneDatasetDataProps; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { collectionId, q, a, indexes } = req.body as InsertOneDatasetDataProps; - if (!q) { - throw new Error('q is required'); - } - - if (!collectionId) { - throw new Error('collectionId is required'); - } - - // 凭证校验 - const { teamId, tmbId } = await authDatasetCollection({ - req, - authToken: true, - authApiKey: true, - collectionId, - per: 'w' - }); - - await checkDatasetLimit({ - teamId, - insertLen: 1 - }); - - // auth collection and get dataset - const [ - { - datasetId: { _id: datasetId, vectorModel } - } - ] = await Promise.all([getCollectionWithDataset(collectionId)]); - - // format data - const formatQ = simpleText(q); - const formatA = simpleText(a); - const formatIndexes = indexes?.map((item) => ({ - ...item, - text: simpleText(item.text) - })); - - // token check - const token = await countPromptTokens(formatQ + formatA, ''); - const vectorModelData = getVectorModel(vectorModel); - - if (token > vectorModelData.maxToken) { - return Promise.reject('Q Over Tokens'); - } - - // Duplicate data check - await hasSameValue({ - teamId, - datasetId, - collectionId, - q: formatQ, - a: formatA - }); - - const { insertId, tokens } = await insertData2Dataset({ - teamId, - tmbId, - datasetId, - collectionId, - q: formatQ, - a: formatA, - chunkIndex: 0, - model: vectorModelData.model, - indexes: formatIndexes - }); - - pushGenerateVectorUsage({ - teamId, - tmbId, - tokens, - model: vectorModelData.model - }); - - jsonRes(res, { - data: insertId - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); + if (!q) { + throw new Error('q is required'); } -}); + + if (!collectionId) { + throw new Error('collectionId is required'); + } + + // 凭证校验 + const { teamId, tmbId } = await authDatasetCollection({ + req, + authToken: true, + authApiKey: true, + collectionId, + per: 'w' + }); + + await checkDatasetLimit({ + teamId, + insertLen: 1 + }); + + // auth collection and get dataset + const [ + { + datasetId: { _id: datasetId, vectorModel } + } + ] = await Promise.all([getCollectionWithDataset(collectionId)]); + + // format data + const formatQ = simpleText(q); + const formatA = simpleText(a); + const formatIndexes = indexes?.map((item) => ({ + ...item, + text: simpleText(item.text) + })); + + // token check + const token = await countPromptTokens(formatQ + formatA, ''); + const vectorModelData = getVectorModel(vectorModel); + + if (token > vectorModelData.maxToken) { + return Promise.reject('Q Over Tokens'); + } + + // Duplicate data check + await hasSameValue({ + teamId, + datasetId, + collectionId, + q: formatQ, + a: formatA + }); + + const { insertId, tokens } = await insertData2Dataset({ + teamId, + tmbId, + datasetId, + collectionId, + q: formatQ, + a: formatA, + chunkIndex: 0, + model: vectorModelData.model, + indexes: formatIndexes + }); + + pushGenerateVectorUsage({ + teamId, + tmbId, + tokens, + model: vectorModelData.model + }); + + jsonRes(res, { + data: insertId + }); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/data/list.ts b/projects/app/src/pages/api/core/dataset/data/list.ts index 54d1fa27f..776218616 100644 --- a/projects/app/src/pages/api/core/dataset/data/list.ts +++ b/projects/app/src/pages/api/core/dataset/data/list.ts @@ -7,62 +7,57 @@ import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { PagingData } from '@/types'; import { replaceRegChars } from '@fastgpt/global/common/string/tools'; +import { NextAPI } from '@/service/middle/entry'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - let { - pageNum = 1, - pageSize = 10, - searchText = '', - collectionId - } = req.body as GetDatasetDataListProps; +async function handler(req: NextApiRequest, res: NextApiResponse) { + let { + pageNum = 1, + pageSize = 10, + searchText = '', + collectionId + } = req.body as GetDatasetDataListProps; - pageSize = Math.min(pageSize, 30); + pageSize = Math.min(pageSize, 30); - // 凭证校验 - const { teamId, collection } = await authDatasetCollection({ - req, - authToken: true, - authApiKey: true, - collectionId, - per: 'r' - }); + // 凭证校验 + const { teamId, collection } = await authDatasetCollection({ + req, + authToken: true, + authApiKey: true, + collectionId, + per: 'r' + }); - searchText = replaceRegChars(searchText).replace(/'/g, ''); + searchText = replaceRegChars(searchText).replace(/'/g, ''); - const match = { - teamId, - datasetId: collection.datasetId._id, - collectionId, - ...(searchText - ? { - $or: [{ q: new RegExp(searchText, 'i') }, { a: new RegExp(searchText, 'i') }] - } - : {}) - }; + const match = { + teamId, + datasetId: collection.datasetId._id, + collectionId, + ...(searchText + ? { + $or: [{ q: new RegExp(searchText, 'i') }, { a: new RegExp(searchText, 'i') }] + } + : {}) + }; - const [data, total] = await Promise.all([ - MongoDatasetData.find(match, '_id datasetId collectionId q a chunkIndex') - .sort({ chunkIndex: 1, updateTime: -1 }) - .skip((pageNum - 1) * pageSize) - .limit(pageSize) - .lean(), - MongoDatasetData.countDocuments(match) - ]); + const [data, total] = await Promise.all([ + MongoDatasetData.find(match, '_id datasetId collectionId q a chunkIndex') + .sort({ chunkIndex: 1, updateTime: -1 }) + .skip((pageNum - 1) * pageSize) + .limit(pageSize) + .lean(), + MongoDatasetData.countDocuments(match) + ]); - jsonRes>(res, { - data: { - pageNum, - pageSize, - data, - total - } - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } + jsonRes>(res, { + data: { + pageNum, + pageSize, + data, + total + } + }); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/data/pushData.ts b/projects/app/src/pages/api/core/dataset/data/pushData.ts index 3003c82bc..5eb9ca084 100644 --- a/projects/app/src/pages/api/core/dataset/data/pushData.ts +++ b/projects/app/src/pages/api/core/dataset/data/pushData.ts @@ -2,7 +2,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import type { PushDatasetDataProps, PushDatasetDataResponse @@ -11,53 +10,48 @@ import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/ import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils'; import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller'; +import { NextAPI } from '@/service/middle/entry'; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const body = req.body as PushDatasetDataProps; - const { collectionId, data } = body; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const body = req.body as PushDatasetDataProps; + const { collectionId, data } = body; - if (!collectionId || !Array.isArray(data)) { - throw new Error('collectionId or data is empty'); - } - - if (data.length > 200) { - throw new Error('Data is too long, max 200'); - } - - // 凭证校验 - const { teamId, tmbId, collection } = await authDatasetCollection({ - req, - authToken: true, - authApiKey: true, - collectionId, - per: 'w' - }); - - // auth dataset limit - await checkDatasetLimit({ - teamId, - insertLen: predictDataLimitLength(collection.trainingType, data) - }); - - jsonRes(res, { - data: await pushDataListToTrainingQueue({ - ...body, - teamId, - tmbId, - datasetId: collection.datasetId._id, - agentModel: collection.datasetId.agentModel, - vectorModel: collection.datasetId.vectorModel - }) - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); + if (!collectionId || !Array.isArray(data)) { + throw new Error('collectionId or data is empty'); } -}); + + if (data.length > 200) { + throw new Error('Data is too long, max 200'); + } + + // 凭证校验 + const { teamId, tmbId, collection } = await authDatasetCollection({ + req, + authToken: true, + authApiKey: true, + collectionId, + per: 'w' + }); + + // auth dataset limit + await checkDatasetLimit({ + teamId, + insertLen: predictDataLimitLength(collection.trainingType, data) + }); + + jsonRes(res, { + data: await pushDataListToTrainingQueue({ + ...body, + teamId, + tmbId, + datasetId: collection.datasetId._id, + agentModel: collection.datasetId.agentModel, + vectorModel: collection.datasetId.vectorModel + }) + }); +} + +export default NextAPI(handler); export const config = { api: { diff --git a/projects/app/src/pages/api/core/dataset/data/update.ts b/projects/app/src/pages/api/core/dataset/data/update.ts index a2d7d1d96..2237988ea 100644 --- a/projects/app/src/pages/api/core/dataset/data/update.ts +++ b/projects/app/src/pages/api/core/dataset/data/update.ts @@ -1,59 +1,53 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { connectToDatabase } from '@/service/mongo'; import { updateData2Dataset } from '@/service/core/dataset/data/controller'; import { authDatasetData } from '@/service/support/permission/auth/dataset'; import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push'; import { UpdateDatasetDataProps } from '@/global/core/dataset/api'; import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; +import { NextAPI } from '@/service/middle/entry'; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { id, q = '', a, indexes = [] } = req.body as UpdateDatasetDataProps; +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { id, q = '', a, indexes = [] } = req.body as UpdateDatasetDataProps; - // auth data permission - const { - collection: { - datasetId: { vectorModel } - }, - teamId, - tmbId - } = await authDatasetData({ - req, - authToken: true, - authApiKey: true, - dataId: id, - per: 'w' - }); + // auth data permission + const { + collection: { + datasetId: { vectorModel } + }, + teamId, + tmbId + } = await authDatasetData({ + req, + authToken: true, + authApiKey: true, + dataId: id, + per: 'w' + }); - // auth team balance - await checkDatasetLimit({ - teamId, - insertLen: 1 - }); + // auth team balance + await checkDatasetLimit({ + teamId, + insertLen: 1 + }); - const { tokens } = await updateData2Dataset({ - dataId: id, - q, - a, - indexes, - model: vectorModel - }); + const { tokens } = await updateData2Dataset({ + dataId: id, + q, + a, + indexes, + model: vectorModel + }); - pushGenerateVectorUsage({ - teamId, - tmbId, - tokens, - model: vectorModel - }); + pushGenerateVectorUsage({ + teamId, + tmbId, + tokens, + model: vectorModel + }); - jsonRes(res); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -}); + jsonRes(res); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/exportAll.ts b/projects/app/src/pages/api/core/dataset/exportAll.ts index 4fed878ac..1e887794f 100644 --- a/projects/app/src/pages/api/core/dataset/exportAll.ts +++ b/projects/app/src/pages/api/core/dataset/exportAll.ts @@ -1,94 +1,85 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes, responseWriteController } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; +import { responseWriteController } from '@fastgpt/service/common/response'; import { addLog } from '@fastgpt/service/common/system/log'; import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { findDatasetAndAllChildren } from '@fastgpt/service/core/dataset/controller'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { checkExportDatasetLimit, updateExportDatasetLimit } from '@fastgpt/service/support/user/utils'; +import { NextAPI } from '@/service/middle/entry'; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - let { datasetId } = req.query as { - datasetId: string; - }; +async function handler(req: NextApiRequest, res: NextApiResponse) { + let { datasetId } = req.query as { + datasetId: string; + }; - if (!datasetId || !global.pgClient) { - throw new Error('缺少参数'); - } - - // 凭证校验 - const { teamId } = await authDataset({ req, authToken: true, datasetId, per: 'w' }); - - await checkExportDatasetLimit({ - teamId, - limitMinutes: global.feConfigs?.limit?.exportDatasetLimitMinutes - }); - - const datasets = await findDatasetAndAllChildren({ - teamId, - datasetId, - fields: '_id' - }); - - res.setHeader('Content-Type', 'text/csv; charset=utf-8;'); - res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv; '); - - const cursor = MongoDatasetData.find<{ - _id: string; - collectionId: { name: string }; - q: string; - a: string; - }>( - { - teamId, - datasetId: { $in: datasets.map((d) => d._id) } - }, - 'q a' - ) - .limit(50000) - .cursor(); - - const write = responseWriteController({ - res, - readStream: cursor - }); - - write(`\uFEFFindex,content`); - - cursor.on('data', (doc) => { - const q = doc.q.replace(/"/g, '""') || ''; - const a = doc.a.replace(/"/g, '""') || ''; - - write(`\n"${q}","${a}"`); - }); - - cursor.on('end', () => { - cursor.close(); - res.end(); - }); - - cursor.on('error', (err) => { - addLog.error(`export dataset error`, err); - res.status(500); - res.end(); - }); - - updateExportDatasetLimit(teamId); - } catch (err) { - res.status(500); - addLog.error(`export dataset error`, err); - jsonRes(res, { - code: 500, - error: err - }); + if (!datasetId || !global.pgClient) { + throw new Error('缺少参数'); } -}); + + // 凭证校验 + const { teamId } = await authDataset({ req, authToken: true, datasetId, per: 'w' }); + + await checkExportDatasetLimit({ + teamId, + limitMinutes: global.feConfigs?.limit?.exportDatasetLimitMinutes + }); + + const datasets = await findDatasetAndAllChildren({ + teamId, + datasetId, + fields: '_id' + }); + + res.setHeader('Content-Type', 'text/csv; charset=utf-8;'); + res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv; '); + + const cursor = MongoDatasetData.find<{ + _id: string; + collectionId: { name: string }; + q: string; + a: string; + }>( + { + teamId, + datasetId: { $in: datasets.map((d) => d._id) } + }, + 'q a' + ) + .limit(50000) + .cursor(); + + const write = responseWriteController({ + res, + readStream: cursor + }); + + write(`\uFEFFindex,content`); + + cursor.on('data', (doc) => { + const q = doc.q.replace(/"/g, '""') || ''; + const a = doc.a.replace(/"/g, '""') || ''; + + write(`\n"${q}","${a}"`); + }); + + cursor.on('end', () => { + cursor.close(); + res.end(); + }); + + cursor.on('error', (err) => { + addLog.error(`export dataset error`, err); + res.status(500); + res.end(); + }); + + updateExportDatasetLimit(teamId); +} + +export default NextAPI(handler); export const config = { api: { diff --git a/projects/app/src/pages/api/core/dataset/list.ts b/projects/app/src/pages/api/core/dataset/list.ts index e0f0429fa..1bfddb1e3 100644 --- a/projects/app/src/pages/api/core/dataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/list.ts @@ -1,54 +1,48 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; import { getVectorModel } from '@fastgpt/service/core/ai/model'; +import { NextAPI } from '@/service/middle/entry'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` }; - // 凭证校验 - const { teamId, tmbId, teamOwner, role, canWrite } = await authUserRole({ - req, - authToken: true, - authApiKey: true - }); +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` }; + // 凭证校验 + const { teamId, tmbId, teamOwner, role, canWrite } = await authUserRole({ + req, + authToken: true, + authApiKey: true + }); - const datasets = await MongoDataset.find({ - ...mongoRPermission({ teamId, tmbId, role }), - ...(parentId !== undefined && { parentId: parentId || null }), - ...(type && { type }) - }) - .sort({ updateTime: -1 }) - .lean(); + const datasets = await MongoDataset.find({ + ...mongoRPermission({ teamId, tmbId, role }), + ...(parentId !== undefined && { parentId: parentId || null }), + ...(type && { type }) + }) + .sort({ updateTime: -1 }) + .lean(); - const data = await Promise.all( - datasets.map((item) => ({ - _id: item._id, - parentId: item.parentId, - avatar: item.avatar, - name: item.name, - intro: item.intro, - type: item.type, - permission: item.permission, - canWrite, - isOwner: teamOwner || String(item.tmbId) === tmbId, - vectorModel: getVectorModel(item.vectorModel) - })) - ); + const data = await Promise.all( + datasets.map((item) => ({ + _id: item._id, + parentId: item.parentId, + avatar: item.avatar, + name: item.name, + intro: item.intro, + type: item.type, + permission: item.permission, + canWrite, + isOwner: teamOwner || String(item.tmbId) === tmbId, + vectorModel: getVectorModel(item.vectorModel) + })) + ); - jsonRes(res, { - data - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } + jsonRes(res, { + data + }); } + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/searchTest.ts b/projects/app/src/pages/api/core/dataset/searchTest.ts index 0ce217156..fa07e8138 100644 --- a/projects/app/src/pages/api/core/dataset/searchTest.ts +++ b/projects/app/src/pages/api/core/dataset/searchTest.ts @@ -1,8 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/api.d'; -import { connectToDatabase } from '@/service/mongo'; import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push'; import { searchDatasetData } from '@fastgpt/service/core/dataset/search/controller'; @@ -14,95 +12,90 @@ import { checkTeamAIPoints, checkTeamReRankPermission } from '@fastgpt/service/support/permission/teamLimit'; +import { NextAPI } from '@/service/middle/entry'; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { - datasetId, - text, - limit = 1500, - similarity, - searchMode, - usingReRank, +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { + datasetId, + text, + limit = 1500, + similarity, + searchMode, + usingReRank, - datasetSearchUsingExtensionQuery = false, - datasetSearchExtensionModel, - datasetSearchExtensionBg = '' - } = req.body as SearchTestProps; + datasetSearchUsingExtensionQuery = false, + datasetSearchExtensionModel, + datasetSearchExtensionBg = '' + } = req.body as SearchTestProps; - if (!datasetId || !text) { - throw new Error('缺少参数'); - } - const start = Date.now(); + if (!datasetId || !text) { + throw new Error('缺少参数'); + } + const start = Date.now(); - // auth dataset role - const { dataset, teamId, tmbId, apikey } = await authDataset({ - req, - authToken: true, - authApiKey: true, - datasetId, - per: 'r' - }); - // auth balance - await checkTeamAIPoints(teamId); + // auth dataset role + const { dataset, teamId, tmbId, apikey } = await authDataset({ + req, + authToken: true, + authApiKey: true, + datasetId, + per: 'r' + }); + // auth balance + await checkTeamAIPoints(teamId); - // query extension - const extensionModel = - datasetSearchUsingExtensionQuery && datasetSearchExtensionModel - ? getLLMModel(datasetSearchExtensionModel) - : undefined; - const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({ - query: text, - extensionModel, - extensionBg: datasetSearchExtensionBg - }); + // query extension + const extensionModel = + datasetSearchUsingExtensionQuery && datasetSearchExtensionModel + ? getLLMModel(datasetSearchExtensionModel) + : undefined; + const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({ + query: text, + extensionModel, + extensionBg: datasetSearchExtensionBg + }); - const { searchRes, tokens, ...result } = await searchDatasetData({ - teamId, - reRankQuery: rewriteQuery, - queries: concatQueries, - model: dataset.vectorModel, - limit: Math.min(limit, 20000), - similarity, - datasetIds: [datasetId], - searchMode, - usingReRank: usingReRank && (await checkTeamReRankPermission(teamId)) - }); + const { searchRes, tokens, ...result } = await searchDatasetData({ + teamId, + reRankQuery: rewriteQuery, + queries: concatQueries, + model: dataset.vectorModel, + limit: Math.min(limit, 20000), + similarity, + datasetIds: [datasetId], + searchMode, + usingReRank: usingReRank && (await checkTeamReRankPermission(teamId)) + }); - // push bill - const { totalPoints } = pushGenerateVectorUsage({ - teamId, - tmbId, - tokens, - model: dataset.vectorModel, - source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt, + // push bill + const { totalPoints } = pushGenerateVectorUsage({ + teamId, + tmbId, + tokens, + model: dataset.vectorModel, + source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt, - ...(aiExtensionResult && - extensionModel && { - extensionModel: extensionModel.name, - extensionTokens: aiExtensionResult.tokens - }) - }); - if (apikey) { - updateApiKeyUsage({ - apikey, - totalPoints: totalPoints - }); - } - - jsonRes(res, { - data: { - list: searchRes, - duration: `${((Date.now() - start) / 1000).toFixed(3)}s`, - usingQueryExtension: !!aiExtensionResult, - ...result - } - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err + ...(aiExtensionResult && + extensionModel && { + extensionModel: extensionModel.name, + extensionTokens: aiExtensionResult.tokens + }) + }); + if (apikey) { + updateApiKeyUsage({ + apikey, + totalPoints: totalPoints }); } -}); + + jsonRes(res, { + data: { + list: searchRes, + duration: `${((Date.now() - start) / 1000).toFixed(3)}s`, + usingQueryExtension: !!aiExtensionResult, + ...result + } + }); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/v1/audio/transcriptions.ts b/projects/app/src/pages/api/v1/audio/transcriptions.ts index 2c311879f..fe1a21921 100644 --- a/projects/app/src/pages/api/v1/audio/transcriptions.ts +++ b/projects/app/src/pages/api/v1/audio/transcriptions.ts @@ -1,6 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { getUploadModel } from '@fastgpt/service/common/file/multer'; import { removeFilesByPaths } from '@fastgpt/service/common/file/utils'; import fs from 'fs'; @@ -10,12 +9,13 @@ import { authChatCert } from '@/service/support/permission/auth/chat'; import { MongoApp } from '@fastgpt/service/core/app/schema'; import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils'; import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; +import { NextAPI } from '@/service/middle/entry'; const upload = getUploadModel({ maxSize: 2 }); -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { +async function handler(req: NextApiRequest, res: NextApiResponse) { let filePaths: string[] = []; try { @@ -81,7 +81,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } removeFilesByPaths(filePaths); -}); +} + +export default NextAPI(handler); export const config = { api: { diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 03aeb8ad7..1c45cc170 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -3,7 +3,6 @@ import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { sseErrRes, jsonRes } from '@fastgpt/service/common/response'; import { addLog } from '@fastgpt/service/common/system/log'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch'; @@ -42,6 +41,9 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti import { dispatchWorkFlowV1 } from '@fastgpt/service/core/workflow/dispatchV1'; import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils'; +import { NextAPI } from '@/service/middle/entry'; +import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema'; +import { getAppLatestVersion } from '@fastgpt/service/core/app/controller'; type FastGptWebChatProps = { chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history @@ -73,7 +75,7 @@ type AuthResponseType = { outLinkUserId?: string; }; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { +async function handler(req: NextApiRequest, res: NextApiResponse) { res.on('close', () => { res.end(); }); @@ -163,13 +165,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex }); })(); - // get and concat history - const { history } = await getChatItems({ - appId: app._id, - chatId, - limit: 30, - field: `dataId obj value` - }); + // 1. get and concat history; 2. get app workflow + const [{ history }, { nodes, edges }] = await Promise.all([ + getChatItems({ + appId: app._id, + chatId, + limit: 30, + field: `dataId obj value` + }), + getAppLatestVersion(app._id, app) + ]); const concatHistories = history.concat(chatMessages); const responseChatItemId: string | undefined = messages[messages.length - 1].dataId; @@ -185,8 +190,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex appId: String(app._id), chatId, responseChatItemId, - runtimeNodes: storeNodes2RuntimeNodes(app.modules, getDefaultEntryNodeIds(app.modules)), - runtimeEdges: initWorkflowEdgeStatus(app.edges), + runtimeNodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)), + runtimeEdges: initWorkflowEdgeStatus(edges), variables: { ...variables, userChatInput: text @@ -349,7 +354,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex }); } } -}); +} +export default NextAPI(handler); export const config = { api: { diff --git a/projects/app/src/pages/api/v1/embeddings.ts b/projects/app/src/pages/api/v1/embeddings.ts index c754e3cfa..f0f171a73 100644 --- a/projects/app/src/pages/api/v1/embeddings.ts +++ b/projects/app/src/pages/api/v1/embeddings.ts @@ -1,7 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push'; import { connectToDatabase } from '@/service/mongo'; import { getVectorsByText } from '@fastgpt/service/core/ai/embedding'; @@ -19,7 +18,7 @@ type Props = { type: `${EmbeddingTypeEnm}`; }; -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { +export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { let { input, model, billId, type } = req.body as Props; await connectToDatabase(); @@ -80,4 +79,4 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex error: err }); } -}); +} diff --git a/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx b/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx index 5c952df66..f8bc15b61 100644 --- a/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx +++ b/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx @@ -24,6 +24,10 @@ import { checkWorkflowNodeAndConnection, filterSensitiveNodesData } from '@/web/core/workflow/utils'; +import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import { useQuery } from '@tanstack/react-query'; +import { formatTime2HM } from '@fastgpt/global/common/string/time'; const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); @@ -50,13 +54,14 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ const { toast } = useToast(); const { t } = useTranslation(); const { copyData } = useCopyData(); - const { openConfirm: openConfirmOut, ConfirmModal } = useConfirm({ - content: t('core.app.edit.Out Ad Edit') + const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({ + content: t('core.app.Publish Confirm') }); const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure(); - const { updateAppDetail } = useAppStore(); + const { publishApp, updateAppDetail } = useAppStore(); const { edges, onUpdateNodeError } = useFlowProviderStore(); const [isSaving, setIsSaving] = useState(false); + const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save')); const flowData2StoreDataAndCheck = useCallback(async () => { const { nodes } = await getWorkflowStore(); @@ -75,48 +80,95 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ } }, [edges, onUpdateNodeError, t, toast]); - const onclickSave = useCallback( - async ({ nodes, edges }: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { - setIsSaving(true); + const onclickSave = useCallback(async () => { + const { nodes } = await getWorkflowStore(); + + if (nodes.length === 0) return null; + setIsSaving(true); + + const storeWorkflow = flowNode2StoreNodes({ nodes, edges }); + + try { + await updateAppDetail(app._id, { + ...storeWorkflow, + type: AppTypeEnum.advanced, + //@ts-ignore + version: 'v2' + }); + + setSaveLabel( + t('core.app.Auto Save time', { + time: formatTime2HM() + }) + ); + ChatTestRef.current?.resetChatTest(); + } catch (error) {} + + setIsSaving(false); + + return null; + }, [updateAppDetail, app._id, edges, ChatTestRef, t]); + + const onclickPublish = useCallback(async () => { + setIsSaving(true); + const data = await flowData2StoreDataAndCheck(); + if (data) { try { - await updateAppDetail(app._id, { - modules: nodes, - edges, + await publishApp(app._id, { + ...data, type: AppTypeEnum.advanced, - permission: undefined, //@ts-ignore version: 'v2' }); toast({ status: 'success', - title: t('common.Save Success') + title: t('core.app.Publish Success') }); ChatTestRef.current?.resetChatTest(); } catch (error) { toast({ status: 'warning', - title: getErrText(error, t('common.Save Failed')) + title: getErrText(error, t('core.app.Publish Failed')) }); } - setIsSaving(false); - }, - [ChatTestRef, app._id, t, toast, updateAppDetail] - ); + } + + setIsSaving(false); + }, [flowData2StoreDataAndCheck, publishApp, app._id, toast, t, ChatTestRef]); const saveAndBack = useCallback(async () => { try { - const data = await flowData2StoreDataAndCheck(); - if (data) { - await onclickSave(data); - } + await onclickSave(); onClose(); - } catch (error) { - toast({ - status: 'warning', - title: getErrText(error) - }); + } catch (error) {} + }, [onClose, onclickSave]); + + const onExportWorkflow = useCallback(async () => { + const data = await flowData2StoreDataAndCheck(); + if (data) { + copyData( + JSON.stringify( + { + nodes: filterSensitiveNodesData(data.nodes), + edges: data.edges + }, + null, + 2 + ), + t('app.Export Config Successful') + ); } - }, [flowData2StoreDataAndCheck, onClose, onclickSave, toast]); + }, [copyData, flowData2StoreDataAndCheck, t]); + + useBeforeunload({ + callback: onclickSave, + tip: t('core.common.tip.leave page') + }); + + useQuery(['autoSave'], onclickSave, { + refetchInterval: 20 * 1000, + enabled: !!app._id + }); const Render = useMemo(() => { return ( @@ -139,12 +191,29 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ variant={'whiteBase'} aria-label={''} isLoading={isSaving} - onClick={openConfirmOut(saveAndBack, onClose)} + onClick={saveAndBack} /> - - {app.name} + + + {app.name} + + + + {saveLabel} + + + + { - const data = await flowData2StoreDataAndCheck(); - if (data) { - copyData( - JSON.stringify( - { - nodes: filterSensitiveNodesData(data.nodes), - edges: data.edges - }, - null, - 2 - ), - t('app.Export Config Successful') - ); - } - } + onClick: onExportWorkflow } ]} /> @@ -202,34 +256,27 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ - + ); }, [ ConfirmModal, app.name, - copyData, flowData2StoreDataAndCheck, isSaving, - onClose, + onExportWorkflow, onOpenImport, + onclickPublish, onclickSave, - openConfirmOut, + openConfigPublish, saveAndBack, + saveLabel, setWorkflowTestData, t, theme.borders.base diff --git a/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx b/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx index 4933dfc94..acd7dda6f 100644 --- a/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx @@ -19,17 +19,15 @@ const Render = ({ app, onClose }: Props) => { const { initData } = useFlowProviderStore(); + const workflowStringData = JSON.stringify({ + nodes: app.modules || [], + edges: app.edges || [] + }); + useEffect(() => { if (!isV2Workflow) return; - initData( - JSON.parse( - JSON.stringify({ - nodes: app.modules || [], - edges: app.edges || [] - }) - ) - ); - }, [isV2Workflow, app.edges, app.modules]); + initData(JSON.parse(workflowStringData)); + }, [isV2Workflow, initData, workflowStringData]); useEffect(() => { if (!isV2Workflow) { @@ -37,7 +35,7 @@ const Render = ({ app, onClose }: Props) => { initData(JSON.parse(JSON.stringify(v1Workflow2V2((app.modules || []) as any)))); })(); } - }, [app.modules, isV2Workflow, openConfirm]); + }, [app.modules, initData, isV2Workflow, openConfirm]); const memoRender = useMemo(() => { return } />; diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx index 1e73cdf41..ea9b109e8 100644 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx @@ -60,7 +60,7 @@ const EditForm = ({ const theme = useTheme(); const router = useRouter(); const { t } = useTranslation(); - const { appDetail, updateAppDetail } = useAppStore(); + const { appDetail, publishApp } = useAppStore(); const { loadAllDatasets, allDatasets } = useDatasetStore(); const { isPc, llmModelList } = useSystemStore(); @@ -122,11 +122,10 @@ const EditForm = ({ mutationFn: async (data: AppSimpleEditFormType) => { const { nodes, edges } = form2AppWorkflow(data); - await updateAppDetail(appDetail._id, { - modules: nodes, + await publishApp(appDetail._id, { + nodes, edges, - type: AppTypeEnum.simple, - permission: undefined + type: AppTypeEnum.simple }); }, successToast: t('common.Save Success'), diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/TagsEditModal.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/TagsEditModal.tsx index 1c114dac0..1592ae2b1 100644 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/TagsEditModal.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleEdit/TagsEditModal.tsx @@ -28,13 +28,13 @@ const TagsEditModal = ({ onClose }: { onClose: () => void }) => { const { t } = useTranslation(); const { appDetail } = useAppStore(); const { toast } = useToast(); - const { replaceAppDetail } = useAppStore(); + const { updateAppDetail } = useAppStore(); const [selectedTags, setSelectedTags] = useState(appDetail?.teamTags || []); // submit config const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({ mutationFn: async () => { - await replaceAppDetail(appDetail._id, { + await updateAppDetail(appDetail._id, { teamTags: selectedTags }); }, diff --git a/projects/app/src/pages/app/detail/index.tsx b/projects/app/src/pages/app/detail/index.tsx index 77a337fad..9a56dae21 100644 --- a/projects/app/src/pages/app/detail/index.tsx +++ b/projects/app/src/pages/app/detail/index.tsx @@ -82,22 +82,6 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]); - useEffect(() => { - const listen = - process.env.NODE_ENV === 'production' - ? (e: any) => { - e.preventDefault(); - e.returnValue = t('core.common.tip.leave page'); - } - : () => {}; - window.addEventListener('beforeunload', listen); - - return () => { - window.removeEventListener('beforeunload', listen); - clearAppModules(); - }; - }, []); - useQuery([appId], () => loadAppDetail(appId, true), { onError(err: any) { toast({ diff --git a/projects/app/src/pages/appStore/components/list.tsx b/projects/app/src/pages/appStore/components/list.tsx deleted file mode 100644 index e4361eaff..000000000 --- a/projects/app/src/pages/appStore/components/list.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; -import { Box, Flex, Button, Card } from '@chakra-ui/react'; -import type { ShareAppItem } from '@/types/app'; -import { useRouter } from 'next/router'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import Avatar from '@/components/Avatar'; -import MyTooltip from '@/components/MyTooltip'; - -const ShareModelList = ({ - models = [], - onclickCollection -}: { - models: ShareAppItem[]; - onclickCollection: (appId: string) => void; -}) => { - const router = useRouter(); - - return ( - <> - {models.map((model) => ( - - - - - {model.name} - - - - - {model.intro || '这个应用还没有介绍~'} - - - - - onclickCollection(model._id)} - > - - {model.share.collection} - - - - - - - ))} - - ); -}; - -export default ShareModelList; diff --git a/projects/app/src/pages/appStore/index.tsx b/projects/app/src/pages/appStore/index.tsx deleted file mode 100644 index 5d69d148f..000000000 --- a/projects/app/src/pages/appStore/index.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useState, useRef, useCallback } from 'react'; -import { Box, Flex, Grid } from '@chakra-ui/react'; -import { getShareModelList, triggerModelCollection } from '@/web/core/app/api'; -import type { ShareAppItem } from '@/types/app'; -import ShareModelList from './components/list'; -import { serviceSideProps } from '@/web/common/utils/i18n'; -import { usePagination } from '@fastgpt/web/hooks/usePagination'; -import { useLoading } from '@fastgpt/web/hooks/useLoading'; - -const modelList = () => { - const { Loading } = useLoading(); - const lastSearch = useRef(''); - const [searchText, setSearchText] = useState(''); - /* 加载模型 */ - const { - data: models, - isLoading, - Pagination, - getData, - pageNum - } = usePagination({ - api: getShareModelList, - pageSize: 24, - params: { - searchText - } - }); - - const onclickCollection = useCallback( - async (appId: string) => { - try { - await triggerModelCollection(appId); - getData(pageNum); - } catch (error) { - console.log(error); - } - }, - [getData, pageNum] - ); - - return ( - - - - AI 应用市场 - - {/* - setSearchText(e.target.value)} - onBlur={() => { - if (searchText === lastSearch.current) return; - getData(1); - lastSearch.current = searchText; - }} - onKeyDown={(e) => { - if (searchText === lastSearch.current) return; - if (e.key === 'Enter') { - getData(1); - lastSearch.current = searchText; - } - }} - /> - */} - - - - - - - - - - - ); -}; - -export async function getServerSideProps(content: any) { - return { - props: { - ...(await serviceSideProps(content)) - } - }; -} - -export default modelList; diff --git a/projects/app/src/pages/plugin/edit/Header.tsx b/projects/app/src/pages/plugin/edit/Header.tsx index 4ab8a98bd..3a131a0d1 100644 --- a/projects/app/src/pages/plugin/edit/Header.tsx +++ b/projects/app/src/pages/plugin/edit/Header.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react'; import { PluginItemSchema } from '@fastgpt/global/core/plugin/type'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; @@ -7,16 +7,14 @@ import { useCopyData } from '@/web/common/hooks/useCopyData'; import dynamic from 'next/dynamic'; import MyIcon from '@fastgpt/web/components/common/Icon'; import MyTooltip from '@/components/MyTooltip'; -import { filterExportModules, flowNode2StoreNodes } from '@/components/core/workflow/utils'; +import { flowNode2StoreNodes } from '@/components/core/workflow/utils'; import { putUpdatePlugin } from '@/web/core/plugin/api'; -import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import { useToast } from '@fastgpt/web/hooks/useToast'; import MyMenu from '@fastgpt/web/components/common/MyMenu'; import { getWorkflowStore, useFlowProviderStore } from '@/components/core/workflow/Flow/FlowProvider'; -import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { checkWorkflowNodeAndConnection, filterSensitiveNodesData @@ -51,107 +49,109 @@ const Header = ({ plugin, onClose }: Props) => { }, [edges, onUpdateNodeError, t, toast]); const { mutate: onclickSave, isLoading } = useRequest({ - mutationFn: ({ nodes, edges }: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { - return putUpdatePlugin({ - id: plugin._id, - modules: nodes, - edges - }); - }, - successToast: '保存配置成功', - errorToast: '保存配置异常' + mutationFn: async () => { + const workflow = await flowData2StoreDataAndCheck(); + if (workflow) { + await putUpdatePlugin({ + id: plugin._id, + modules: workflow.nodes, + edges: workflow.edges + }); + toast({ + status: 'success', + title: t('common.Save Success') + }); + } + } }); - return ( - <> - - - } - variant={'whiteBase'} - aria-label={''} - onClick={() => { - onClose(); - }} - /> - - - {plugin.name} - + const onCopy = useCallback(async () => { + const data = await flowData2StoreDataAndCheck(); + if (data) { + copyData( + JSON.stringify( + { + nodes: filterSensitiveNodesData(data.nodes), + edges: data.edges + }, + null, + 2 + ), + t('app.Export Config Successful') + ); + } + }, [copyData, flowData2StoreDataAndCheck, t]); - } - aria-label={''} - size={'sm'} - variant={'whitePrimary'} - /> - } - menuList={[ - { label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport }, - { - label: t('app.Export Configs'), - icon: 'export', - onClick: async () => { - const data = await flowData2StoreDataAndCheck(); - if (data) { - copyData( - JSON.stringify( - { - nodes: filterSensitiveNodesData(data.nodes), - edges: data.edges - }, - null, - 2 - ), - t('app.Export Config Successful') - ); - } - } - } - ]} - /> - {/* - } - size={'smSquare'} - aria-label={'save'} - variant={'whitePrimary'} - onClick={async () => { - const modules = await flowData2StoreDataAndCheck(); - if (modules) { - setPreviewModules(modules); - } - }} - /> - */} - - - {isOpenImport && } - - ); + + } + variant={'whiteBase'} + aria-label={''} + onClick={() => { + onClose(); + }} + /> + + + {plugin.name} + + + } + aria-label={''} + size={'sm'} + variant={'whitePrimary'} + /> + } + menuList={[ + { label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport }, + { + label: t('app.Export Configs'), + icon: 'export', + onClick: onCopy + } + ]} + /> + + + {isOpenImport && } + + ); + }, [ + isLoading, + isOpenImport, + onClose, + onCloseImport, + onCopy, + onOpenImport, + onclickSave, + plugin.name, + t, + theme.borders.base + ]); + + return Render; }; export default React.memo(Header); diff --git a/projects/app/src/pages/plugin/edit/index.tsx b/projects/app/src/pages/plugin/edit/index.tsx index 661ae164f..ee818b4fe 100644 --- a/projects/app/src/pages/plugin/edit/index.tsx +++ b/projects/app/src/pages/plugin/edit/index.tsx @@ -13,6 +13,7 @@ import { getErrText } from '@fastgpt/global/common/error/utils'; import { useTranslation } from 'next-i18next'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; +import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; type Props = { pluginId: string }; @@ -42,18 +43,16 @@ const Render = ({ pluginId }: Props) => { '检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大,会导致许多工作流无法正常排布,请重新手动连接工作流。如仍异常,可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试,无需点击保存,点击保存为新版工作流。' }); + const workflowStringData = JSON.stringify({ + nodes: pluginDetail?.modules || [], + edges: pluginDetail?.edges || [] + }); + useEffect(() => { if (isV2Workflow) { - initData( - JSON.parse( - JSON.stringify({ - nodes: pluginDetail?.modules || [], - edges: pluginDetail?.edges || [] - }) - ) - ); + initData(JSON.parse(workflowStringData)); } - }, [isV2Workflow, pluginDetail?.edges, pluginDetail?.modules]); + }, [initData, isV2Workflow, workflowStringData]); useEffect(() => { if (!isV2Workflow && pluginDetail) { @@ -61,7 +60,11 @@ const Render = ({ pluginId }: Props) => { initData(JSON.parse(JSON.stringify(v1Workflow2V2((pluginDetail.modules || []) as any)))); })(); } - }, [isV2Workflow, openConfirm, pluginDetail]); + }, [initData, isV2Workflow, openConfirm, pluginDetail]); + + useBeforeunload({ + tip: t('core.common.tip.leave page') + }); return pluginDetail ? ( <> diff --git a/projects/app/src/service/middle/entry.ts b/projects/app/src/service/middle/entry.ts new file mode 100644 index 000000000..ac15b6441 --- /dev/null +++ b/projects/app/src/service/middle/entry.ts @@ -0,0 +1,30 @@ +import { jsonRes } from '@fastgpt/service/common/response'; +import type { NextApiResponse, NextApiHandler, NextApiRequest } from 'next'; +import { connectToDatabase } from '../mongo'; +import { withNextCors } from '@fastgpt/service/common/middle/cors'; + +export const NextAPI = (...args: NextApiHandler[]): NextApiHandler => { + return async function api(req: NextApiRequest, res: NextApiResponse) { + try { + await Promise.all([withNextCors(req, res), connectToDatabase()]); + + let response = null; + for (const handler of args) { + response = await handler(req, res); + } + + if (!res.writableFinished) { + return jsonRes(res, { + code: 200, + data: response + }); + } + } catch (error) { + return jsonRes(res, { + code: 500, + error, + url: req.url + }); + } + }; +}; diff --git a/projects/app/src/web/core/app/api.ts b/projects/app/src/web/core/app/api.ts index 72cf960a7..8a3f9069c 100644 --- a/projects/app/src/web/core/app/api.ts +++ b/projects/app/src/web/core/app/api.ts @@ -1,9 +1,7 @@ import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; import type { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d'; -import { RequestPaging } from '@/types/index'; -import { addDays } from 'date-fns'; import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d'; -import type { CreateAppParams, AppUpdateParams } from '@fastgpt/global/core/app/api.d'; +import { AppUpdateParams, CreateAppParams } from '@/global/core/app/api'; /** * 获取模型列表 @@ -31,32 +29,6 @@ export const getModelById = (id: string) => GET(`/core/app/detail */ export const putAppById = (id: string, data: AppUpdateParams) => PUT(`/core/app/update?appId=${id}`, data); -export const replaceAppById = (id: string, data: AppUpdateParams) => - PUT(`/core/app/updateTeamTasg?appId=${id}`, data); - -// updateTeamTasg -export const putAppTagsById = (id: string, data: AppUpdateParams) => - PUT(`/core/app/updateTeamTasg?appId=${id}`, data); -/* 共享市场 */ -/** - * 获取共享市场模型 - */ -export const getShareModelList = (data: { searchText?: string } & RequestPaging) => - POST(`/core/app/share/getModels`, data); - -/** - * 收藏/取消收藏模型 - */ -export const triggerModelCollection = (appId: string) => - POST(`/core/app/share/collection?appId=${appId}`); - -// ====================== data -export const getAppTotalUsage = (data: { appId: string }) => - POST<{ date: String; total: number }[]>(`/core/app/data/totalUsage`, { - ...data, - start: addDays(new Date(), -13), - end: addDays(new Date(), 1) - }).then((res) => (res.length === 0 ? [{ date: new Date(), total: 0 }] : res)); // =================== chat logs export const getAppChatLogs = (data: GetAppChatLogsParams) => POST(`/core/app/getChatLogs`, data); diff --git a/projects/app/src/web/core/app/store/useAppStore.ts b/projects/app/src/web/core/app/store/useAppStore.ts index d04653ba8..8f6bec3b3 100644 --- a/projects/app/src/web/core/app/store/useAppStore.ts +++ b/projects/app/src/web/core/app/store/useAppStore.ts @@ -1,10 +1,12 @@ import { create } from 'zustand'; import { devtools, persist } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; -import { getMyApps, getModelById, putAppById, replaceAppById } from '@/web/core/app/api'; +import { getMyApps, getModelById, putAppById } from '@/web/core/app/api'; import { defaultApp } from '@/constants/app'; -import type { AppUpdateParams } from '@fastgpt/global/core/app/api.d'; +import type { AppUpdateParams } from '@/global/core/app/api.d'; import { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d'; +import { PostPublishAppProps } from '@/global/core/app/api'; +import { postPublishApp } from '../versionApi'; type State = { myApps: AppListItemType[]; @@ -12,7 +14,7 @@ type State = { appDetail: AppDetailType; loadAppDetail: (id: string, init?: boolean) => Promise; updateAppDetail(appId: string, data: AppUpdateParams): Promise; - replaceAppDetail(appId: string, data: AppUpdateParams): Promise; + publishApp(appId: string, data: PostPublishAppProps): Promise; clearAppModules(): void; }; @@ -44,19 +46,22 @@ export const useAppStore = create()( set((state) => { state.appDetail = { ...state.appDetail, - ...data + ...data, + modules: data?.nodes || state.appDetail.modules }; }); }, - async replaceAppDetail(appId: string, data: AppUpdateParams) { - await replaceAppById(appId, { ...get().appDetail, ...data }); + async publishApp(appId: string, data: PostPublishAppProps) { + await postPublishApp(appId, data); set((state) => { state.appDetail = { ...state.appDetail, - ...data + ...data, + modules: data?.nodes || state.appDetail.modules }; }); }, + clearAppModules() { set((state) => { state.appDetail = { diff --git a/projects/app/src/web/core/app/versionApi.ts b/projects/app/src/web/core/app/versionApi.ts new file mode 100644 index 000000000..1d1657dc7 --- /dev/null +++ b/projects/app/src/web/core/app/versionApi.ts @@ -0,0 +1,5 @@ +import { PostPublishAppProps } from '@/global/core/app/api'; +import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; + +export const postPublishApp = (appId: string, data: PostPublishAppProps) => + POST(`/core/app/version/publish?appId=${appId}`, data); diff --git a/projects/app/src/web/core/workflow/adapt.ts b/projects/app/src/web/core/workflow/adapt.ts index f29f341d6..74903e606 100644 --- a/projects/app/src/web/core/workflow/adapt.ts +++ b/projects/app/src/web/core/workflow/adapt.ts @@ -15,7 +15,7 @@ import { FlowNodeTemplateType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; -import { VARIABLE_NODE_ID } from './constants'; +import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants'; import { getHandleId, splitGuideModule } from '@fastgpt/global/core/workflow/utils'; import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { LLMModelTypeEnum } from '@fastgpt/global/core/ai/constants'; diff --git a/projects/app/src/web/core/workflow/constants/dataType.ts b/projects/app/src/web/core/workflow/constants/dataType.ts index b09c5e45b..7dc227288 100644 --- a/projects/app/src/web/core/workflow/constants/dataType.ts +++ b/projects/app/src/web/core/workflow/constants/dataType.ts @@ -2,34 +2,47 @@ import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants export const FlowValueTypeMap = { [WorkflowIOValueTypeEnum.string]: { - handlerStyle: { - borderColor: '#36ADEF' - }, - label: 'core.module.valueType.string', + label: 'string', value: WorkflowIOValueTypeEnum.string, description: '' }, [WorkflowIOValueTypeEnum.number]: { - handlerStyle: { - borderColor: '#FB7C3C' - }, - label: 'core.module.valueType.number', + label: 'number', value: WorkflowIOValueTypeEnum.number, description: '' }, [WorkflowIOValueTypeEnum.boolean]: { - handlerStyle: { - borderColor: '#E7D118' - }, - label: 'core.module.valueType.boolean', + label: 'boolean', value: WorkflowIOValueTypeEnum.boolean, description: '' }, + [WorkflowIOValueTypeEnum.object]: { + label: 'object', + value: WorkflowIOValueTypeEnum.object, + description: '' + }, + [WorkflowIOValueTypeEnum.arrayString]: { + label: 'array', + value: WorkflowIOValueTypeEnum.arrayString, + description: '' + }, + [WorkflowIOValueTypeEnum.arrayNumber]: { + label: 'array', + value: WorkflowIOValueTypeEnum.arrayNumber, + description: '' + }, + [WorkflowIOValueTypeEnum.arrayBoolean]: { + label: 'array', + value: WorkflowIOValueTypeEnum.arrayBoolean, + description: '' + }, + [WorkflowIOValueTypeEnum.arrayObject]: { + label: 'array', + value: WorkflowIOValueTypeEnum.arrayObject, + description: '' + }, [WorkflowIOValueTypeEnum.chatHistory]: { - handlerStyle: { - borderColor: '#00A9A6' - }, - label: 'core.module.valueType.chatHistory', + label: '历史记录', value: WorkflowIOValueTypeEnum.chatHistory, description: `{ obj: System | Human | AI; @@ -37,10 +50,7 @@ export const FlowValueTypeMap = { }[]` }, [WorkflowIOValueTypeEnum.datasetQuote]: { - handlerStyle: { - borderColor: '#A558C9' - }, - label: 'core.module.valueType.datasetQuote', + label: '知识库引用', value: WorkflowIOValueTypeEnum.datasetQuote, description: `{ id: string; @@ -52,42 +62,22 @@ export const FlowValueTypeMap = { a: string }[]` }, - [WorkflowIOValueTypeEnum.any]: { - handlerStyle: { - borderColor: '#9CA2A8' - }, - label: 'core.module.valueType.any', - value: WorkflowIOValueTypeEnum.any, - description: '' - }, [WorkflowIOValueTypeEnum.selectApp]: { - handlerStyle: { - borderColor: '#6a6efa' - }, - label: 'core.module.valueType.selectApp', + label: '选择应用', value: WorkflowIOValueTypeEnum.selectApp, description: '' }, [WorkflowIOValueTypeEnum.selectDataset]: { - handlerStyle: { - borderColor: '#21ba45' - }, - label: 'core.module.valueType.selectDataset', + label: '选择知识库', value: WorkflowIOValueTypeEnum.selectDataset, description: '' }, - [WorkflowIOValueTypeEnum.tools]: { - handlerStyle: { - borderColor: '#21ba45' - }, - label: 'core.module.valueType.tools', - value: WorkflowIOValueTypeEnum.tools, + [WorkflowIOValueTypeEnum.any]: { + label: 'any', + value: WorkflowIOValueTypeEnum.any, description: '' }, [WorkflowIOValueTypeEnum.dynamic]: { - handlerStyle: { - borderColor: '#9CA2A8' - }, label: '动态数据', value: WorkflowIOValueTypeEnum.any, description: '' diff --git a/projects/app/src/web/core/workflow/constants/index.ts b/projects/app/src/web/core/workflow/constants/index.ts deleted file mode 100644 index 0928bce01..000000000 --- a/projects/app/src/web/core/workflow/constants/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID'; diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index 01b26f880..491d0d5bc 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -14,7 +14,7 @@ import { EmptyNode } from '@fastgpt/global/core/workflow/template/system/emptyNo import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { systemConfigNode2VariableNode } from './adapt'; -import { VARIABLE_NODE_ID } from './constants'; +import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; export const nodeTemplate2FlowNode = ({