* 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>
This commit is contained in:
Archer 2024-04-27 12:21:01 +08:00 committed by GitHub
parent c8412e7dc9
commit d407e87dd9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
87 changed files with 1607 additions and 1779 deletions

29
.vscode/nextapi.code-snippets vendored Normal file
View File

@ -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<any>): Promise<{}> {",
" $1",
" return {}",
"}",
"",
"export default NextAPI(handler);"
],
"description": "FastGPT Next API template"
}
}

View File

@ -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私有部署错误务必提供详细的日志否则很难排查。
## 二、通用问题
@ -91,3 +94,8 @@ FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应
OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。
可以`exec`进入容器,`env`查看环境变量是否生效。
### bad_response_status_code bad response status code 503
1. 模型服务不可用
2. ....

View File

@ -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) => {

View File

@ -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[];

9
packages/global/core/app/version.d.ts vendored Normal file
View File

@ -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[];
};

View File

@ -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';

View File

@ -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 (

View File

@ -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: [
{

View File

@ -1,11 +1,7 @@
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
) {
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, {
@ -13,7 +9,4 @@ export function withNextCors(handler: NextApiHandler): NextApiHandler {
origin: origin,
optionsSuccessStatus: 200
});
return handler(req, res);
};
}

View File

@ -18,9 +18,10 @@ export const jsonRes = <T = any>(
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 = <T = any>(
msg = error?.error?.message;
}
addLog.error(`response error: ${msg}`, error);
addLog.error(`Api response error: ${url}, ${msg}`, error);
}
res.status(code).json({

View File

@ -169,7 +169,16 @@ class PgClass {
}
async query<T extends QueryResultRow = any>(sql: string) {
const pg = await connectPg();
return pg.query<T>(sql);
const start = Date.now();
return pg.query<T>(sql).then((res) => {
const time = Date.now() - start;
if (time > 300) {
addLog.warn(`pg query time: ${time}ms, sql: ${sql}`);
}
return res;
});
}
}

View File

@ -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 = <T extends AppSchema['modules'] | undefined>({
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 || []
};
};

View File

@ -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<AppType> =
models[appCollectionName] || model(appCollectionName, AppSchema);
models[AppCollectionName] || model(AppCollectionName, AppSchema);
MongoApp.syncIndexes();

View File

@ -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<AppVersionSchemaType> =
models[AppVersionCollectionName] || model(AppVersionCollectionName, AppVersionSchema);
MongoAppVersion.syncIndexes();

View File

@ -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: {

View File

@ -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: {

View File

@ -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);
});
}
// 运行完一轮后,清除连线的状态,避免污染进程

View File

@ -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<DispatchNodeResultTy
const {
params,
runtimeNodes,
variables,
node: { nodeId }
} = props;
const { condition, ifElseList } = params;
const listResult = ifElseList.map((item) => {
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 || '');
});

View File

@ -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: {

View File

@ -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);

View File

@ -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'),

View File

@ -0,0 +1,6 @@
<svg t="1714186779322" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1213"
width="128" height="128">
<path
d="M957.78379975 134.65795581c-2.56331135 83.17185735-20.44423515 163.12467052-50.30317593 240.57510884-35.69958418 92.67796903-87.96862781 174.24030494-161.20251807 241.63355181-5.94396957 5.46839892-7.80783541 11.32361235-7.01830818 19.026796 2.5328431 24.92705029 4.5596486 49.91503711 7.12295994 74.84208611 5.9598665 58.12425835-14.32010468 105.71575589-61.01609761 139.47861502-50.06340414 36.20694738-102.1695094 69.6412786-153.84243498 103.55515469-32.57194606 21.38212813-72.60730269 1.98176462-76.2277327-36.80306673-4.17150847-44.53539301-7.45016355-89.16219079-10.5340864-133.78633995-1.53533793-22.33459502-0.93921859-22.52932723-22.48428636-25.38937392-57.18636407-7.56938794-101.94563325-34.8213021-132.68418171-84.27401429-15.81040175-25.46488176-23.6473811-53.61097493-26.40410034-83.18642866-0.81866989-8.86495278-4.3808133-11.13020574-12.70925895-11.59252939-46.28003447-2.57788397-92.52960201-5.3332789-138.74869996-8.8199132-38.44173211-2.92098321-57.93085045-43.61339568-37.32367695-77.56966271 14.4671474-23.86993165 29.87483771-47.17288918 44.81888136-70.76065808 16.1071371-25.38937393 32.07915418-50.85425569 48.21675953-76.21316007 29.53173846-46.41383019 72.1012638-71.48924752 126.5905208-71.93699984 31.2008721-0.23844748 62.46003078 5.27366671 93.64633157 8.64107663 5.89892998 0.64115892 10.0863341-0.16426396 14.16908774-4.73848387 72.39799786-81.24970362 162.00661664-136.08206117 263.446211-172.76457792 57.33473241-20.74096921 116.33859714-34.28479495 176.89237242-40.54271983 30.51467231-3.17400331 61.23864946-4.32120112 91.72285352 1.34060572 16.61450031 3.08392285 17.46363973 3.77012135 20.02562678 20.05609502C956.4869093 101.71509088 958.51238919 118.07524806 957.78379975 134.65795581L957.78379975 134.65795581zM813.20902898 339.81696881c0.87828208-71.05871775-57.73744384-132.1635715-127.410515-133.309445-72.35428259-1.20680999-135.02361821 51.61331338-136.73779142 134.08439962-1.44525748 68.77756914 60.61338619 131.07466028 129.88374722 131.55155523C752.69897026 472.67866149 812.29895435 413.94371248 813.20902898 339.81696881L813.20902898 339.81696881zM195.18722288 640.09538918c13.69219278 0.17883529 21.29204896 6.24335355 26.19479682 20.29321819 19.25067215 55.04033549 54.28127779 96.80443794 105.06002435 125.11479508 11.20438926 6.24335355 23.0207935 11.30904103 35.22401354 15.48187512 21.93320788 7.50845143 26.93928317 28.96476307 10.66788212 45.47328728-24.10705481 24.43558273-48.46977833 48.60357404-72.56226181 73.02590976-7.97209938 8.07542683-16.74829602 11.90383476-27.95268527 7.71775497-11.38322454-4.2470176-16.88209172-12.72383026-17.46363974-24.55480712-0.35767186-7.49520313-0.17883529-15.03544713-0.35767188-22.55846986-0.31263099-13.81274148-0.44775102-13.91739325-13.11197036-9.71541653-27.08765021 9.01464541-54.11701383 18.10347435-81.20466405 27.10354714-7.9866707 2.66796441-16.00248532 3.77012135-23.81032074-0.67030285-11.44416105-6.51226928-15.76403785-18.14851392-11.160674-32.46729429 8.99874849-27.93811395 18.43200097-55.71196266 27.64005428-83.57456879 3.99267318-12.09856827 3.7396531-12.35158706-9.26766421-12.55956629-7.83830367-0.11922438-15.70442566 0.01457133-23.54140371-0.37224318-11.4136928-0.55107846-19.5341592-6.09498652-23.75070856-16.94170392-4.14104022-10.56455466-1.49029705-19.77260668 6.19831398-27.54997384 25.15092515-25.40526957 50.42240031-50.68999172 75.6620816-76.00518086C182.68726879 642.31560258 188.64713398 639.45423026 195.18722288 640.09538918L195.18722288 640.09538918z"
p-id="1214"></path>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -54,12 +54,7 @@ const MultipleRowSelect = ({
bg: 'primary.50',
color: 'primary.600'
}}
{...(item.value === selectedValue
? {
color: 'primary.600'
}
: {
onClick: () => {
onClick={() => {
const newValue = [...cloneValue];
newValue[index] = item.value;
setCloneValue(newValue);
@ -67,8 +62,12 @@ const MultipleRowSelect = ({
onSelect(newValue);
onClose();
}
}}
{...(item.value === selectedValue
? {
color: 'primary.600'
}
})}
: {})}
>
{item.label}
</Flex>

View File

@ -0,0 +1,24 @@
import { useTranslation } from 'next-i18next';
import { useEffect } from 'react';
export const useBeforeunload = (props?: { callback?: () => 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]);
};

View File

@ -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,

3
pnpm-lock.yaml generated
View File

@ -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)

View File

@ -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';

View File

@ -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",

View File

@ -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"
}
}

View File

@ -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": "请先登录",

View File

@ -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<ChangesType> = (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<void>;
splitToolInputs: (
inputs: FlowNodeInputItemType[],
nodeId: string
@ -147,7 +148,10 @@ const StateContext = createContext<useFlowProviderStoreType>({
hasToolNode: false,
connectingEdge: undefined,
basicNodeTemplates: [],
initData: function (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }): void {
initData: function (e: {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}): Promise<void> {
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(() => {

View File

@ -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<FlowNodeItemType>) => {
@ -47,46 +45,13 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return maxTokens;
}, [llmModelList, nodeList]);
const RenderQuoteList = useMemo(() => {
return (
<Box mt={-2}>
{quotes.map((quote, i) => (
<Box key={quote.key} _notLast={{ mb: 4 }}>
<Flex alignItems={'center'}>
<Box fontWeight={'medium'} color={'myGray.600'}>
{t('core.chat.Quote')}
{i + 1}
</Box>
<MyIcon
ml={2}
w={'14px'}
name={'delete'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{ color: 'red.600' }}
onClick={() => {
onChangeNode({
nodeId,
type: 'delInput',
key: quote.key
});
}}
/>
</Flex>
<Reference nodeId={nodeId} item={quote} />
</Box>
))}
</Box>
);
}, [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<FlowNodeItemType>) => {
<NodeCard minW={'400px'} selected={selected} {...data}>
<Container position={'relative'}>
<RenderInput nodeId={nodeId} flowInputList={inputs} CustomComponent={CustomComponent} />
{RenderQuoteList}
{/* {RenderQuoteList} */}
</Container>
<Container>
<IOTitle text={t('common.Output')} />

View File

@ -123,7 +123,7 @@ const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
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;

View File

@ -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<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs } = data;
const { nodeId, inputs } = data;
const { onChangeNode } = useFlowProviderStore();
const [createField, setCreateField] = useState<EditNodeFieldType>();
@ -93,13 +86,6 @@ const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
canEdit: true,
editField: createEditField
};
const newOutput: FlowNodeOutputItemType = {
id: getNanoid(),
key: data.key,
valueType: data.valueType,
label: data.label,
type: FlowNodeOutputTypeEnum.static
};
onChangeNode({
nodeId,

View File

@ -22,7 +22,7 @@ const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
}}
{...data}
>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'} textAlign={'end'}>
<Container>
<IOTitle text={t('common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>

View File

@ -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<EditNodeFieldType>({
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) => {

View File

@ -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 (
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<MyTooltip label={t('core.workflow.tool.Handle')} shouldWrapChildren={false}>
<Handle
style={{
borderRadius: '0',
@ -63,7 +55,7 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
</Handle>
</MyTooltip>
);
}, [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 (
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<MyTooltip label={t('core.workflow.tool.Handle')} shouldWrapChildren={false}>
<Handle
style={{
borderRadius: '0',
@ -120,7 +104,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
</Handle>
</MyTooltip>
);
}, [onConnect, t, valueTypeMap?.description, valueTypeMap?.label]);
}, [onConnect, t]);
return Render;
};

View File

@ -513,11 +513,14 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
{/* result */}
{debugResult.showResult && (
<Card
className="nowheel"
position={'absolute'}
right={'-430px'}
top={0}
zIndex={10}
w={'420px'}
maxH={'540px'}
overflowY={'auto'}
border={'base'}
>
{/* Status header */}

View File

@ -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<EditNodeFieldType>();
const valueTypeLabel = useMemo(
() => (valueType ? t(FlowValueTypeMap[valueType]?.label) : ''),
[t, valueType]
);
const onChangeRenderType = useCallback(
(e: string) => {
const index = renderTypeList.findIndex((item) => item === e) || 0;
@ -84,12 +74,11 @@ const InputLabel = ({ nodeId, input }: Props) => {
)}
</Box>
{/* value type */}
{renderType === FlowNodeInputTypeEnum.reference && !!valueTypeLabel && (
<ValueTypeLabel>{valueTypeLabel}</ValueTypeLabel>
)}
{renderType === FlowNodeInputTypeEnum.reference && <ValueTypeLabel valueType={valueType} />}
{/* edit config */}
{canEdit && (
<>
{input.editField && Object.keys(input.editField).length > 0 && (
<MyIcon
name={'common/settingLight'}
w={'14px'}
@ -113,6 +102,7 @@ const InputLabel = ({ nodeId, input }: Props) => {
})
}
/>
)}
<MyIcon
className="delete"
name={'delete'}
@ -207,8 +197,7 @@ const InputLabel = ({ nodeId, input }: Props) => {
selectedTypeIndex,
t,
toolDescription,
valueType,
valueTypeLabel
valueType
]);
return RenderLabel;

View File

@ -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) => {
<Box position={'relative'} color={'myGray.600'} fontWeight={'medium'}>
{t('core.module.Dataset quote.label')}
</Box>
<ValueTypeLabel>{t('core.module.valueType.datasetQuote')}</ValueTypeLabel>
<ValueTypeLabel valueType={WorkflowIOValueTypeEnum.datasetQuote} />
<MyTooltip label={t('core.module.Setting quote prompt')}>
<MyIcon

View File

@ -6,7 +6,6 @@ import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/const
import { SourceHandle } from '../Handle';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import ValueTypeLabel from '../ValueTypeLabel';
@ -14,11 +13,6 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
const { t } = useTranslation();
const { label = '', description, valueType } = output;
const valueTypeLabel = useMemo(
() => (valueType ? t(FlowValueTypeMap[valueType]?.label) : '-'),
[t, valueType]
);
const Render = useMemo(() => {
return (
<Box position={'relative'}>
@ -34,11 +28,15 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
}
: {})}
>
<Box position={'relative'} mr={1}>
<Box
position={'relative'}
mr={1}
ml={output.type === FlowNodeOutputTypeEnum.source ? 1 : 0}
>
{t(label)}
</Box>
{description && <QuestionTip label={t(description)} />}
<ValueTypeLabel>{valueTypeLabel}</ValueTypeLabel>
<ValueTypeLabel valueType={valueType} />
</Flex>
{output.type === FlowNodeOutputTypeEnum.source && (
<SourceHandle
@ -50,7 +48,7 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
)}
</Box>
);
}, [description, output.key, output.type, label, nodeId, t, valueTypeLabel]);
}, [output.type, output.key, t, label, description, valueType, nodeId]);
return Render;
};

View File

@ -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 (
<Box
position={'absolute'}
top={'50%'}
left={'-18px'}
transform={'translate(0,-50%)'}
{...props}
>
<MyTooltip
label={t('app.module.type', {
type: t(FlowValueTypeMap[valType]?.label),
description: FlowValueTypeMap[valType]?.description
})}
>
<Handle
style={{
width: '14px',
height: '14px',
borderWidth: '3.5px',
backgroundColor: 'white',
...valueStyle
}}
type="target"
id={handleKey}
position={Position.Left}
/>
</MyTooltip>
</Box>
);
};
export default React.memo(TargetHandle);

View File

@ -1,8 +1,17 @@
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 (
const ValueTypeLabel = ({ valueType }: { valueType?: WorkflowIOValueTypeEnum }) => {
const valueTypeData = valueType ? FlowValueTypeMap[valueType] : undefined;
const label = valueTypeData?.label || '';
const description = valueTypeData?.description || '';
return !!label ? (
<MyTooltip label={description}>
<Box
bg={'myGray.100'}
color={'myGray.500'}
@ -10,12 +19,15 @@ const ValueTypeLabel = ({ children }: { children: React.ReactNode }) => {
borderRadius={'sm'}
ml={2}
px={1}
py={0.5}
h={6}
display={'flex'}
alignItems={'center'}
fontSize={'11px'}
>
{children}
{label}
</Box>
);
</MyTooltip>
) : null;
};
export default React.memo(ValueTypeLabel);

View File

@ -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,

View File

@ -108,7 +108,7 @@ const LafAccountModal = ({
</Box>
<Box>
<Link textDecoration={'underline'} href={`${feConfigs.lafEnv}/`} isExternal>
{t('support.user.Go laf env')}
{t('support.user.Go laf env', { env: feConfigs.lafEnv })}
</Link>
</Box>
</Box>

View File

@ -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'];
};

View File

@ -1,15 +1,15 @@
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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const {
name = 'APP',
avatar,
@ -29,7 +29,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await checkTeamAppLimit(teamId);
// 创建模型
const response = await MongoApp.create({
const appId = await mongoSessionRun(async (session) => {
const [{ _id: appId }] = await MongoApp.create(
[
{
avatar,
name,
teamId,
@ -38,15 +41,28 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
edges,
type,
version: 'v2'
}
],
{ session }
);
await MongoAppVersion.create(
[
{
appId,
nodes: modules,
edges
}
],
{ session }
);
return appId;
});
jsonRes(res, {
data: response._id
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
data: appId
});
}
}
export default NextAPI(handler);

View File

@ -1,16 +1,14 @@
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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { appId } = req.query as { appId: string };
if (!appId) {
@ -41,6 +39,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
},
{ session }
);
// delete version
await MongoAppVersion.deleteMany(
{
appId
},
{ session }
);
// delete app
await MongoApp.deleteOne(
{
@ -49,12 +54,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{ session }
);
});
}
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export default NextAPI(handler);

View File

@ -2,11 +2,10 @@ 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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { appId } = req.query as { appId: string };
if (!appId) {
@ -16,13 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 凭证校验
const { app } = await authApp({ req, authToken: true, appId, per: 'w' });
jsonRes(res, {
data: app
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
return app;
}
export default NextAPI(handler);

View File

@ -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,10 +7,12 @@ 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();
async function handler(
req: NextApiRequest,
res: NextApiResponse
): Promise<PagingData<AppLogsListItemType>> {
const {
pageNum = 1,
pageSize = 20,
@ -135,18 +135,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
MongoChat.countDocuments(where)
]);
jsonRes<PagingData<AppLogsListItemType>>(res, {
data: {
return {
pageNum,
pageSize,
data,
total
};
}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
export default NextAPI(handler);

View File

@ -1,14 +1,11 @@
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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<AppListItemType[]> {
// 凭证校验
const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true });
@ -19,20 +16,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
).sort({
updateTime: -1
});
jsonRes<AppListItemType[]>(res, {
data: myApps.map((app) => ({
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
}))
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}));
}
export default NextAPI(handler);

View File

@ -2,29 +2,16 @@ 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<any>) {
try {
await connectToDatabase();
const {
name,
avatar,
type,
intro,
modules: nodes,
edges,
permission,
teamTags
} = req.body as AppUpdateParams;
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { name, avatar, type, intro, nodes, edges, permission, teamTags } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
if (!appId) {
@ -36,38 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 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 || []));
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
// 更新模型
await MongoApp.updateOne(
@ -81,27 +37,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
intro,
permission,
version: 'v2',
teamTags: teamTags,
...(nodes && {
modules: nodes
...(teamTags && teamTags),
...(formatNodes && {
modules: formatNodes
}),
...(edges && {
edges
}),
scheduledTriggerConfig,
scheduledTriggerNextTime: scheduledTriggerConfig
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
: null
})
}
);
getScheduleTriggerApp();
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export default NextAPI(handler);

View File

@ -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<any>) {
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
});
}
}

View File

@ -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<any>): 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);

View File

@ -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({
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<InitChatResponse>(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

View File

@ -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({
const [{ history }, { nodes }] = await Promise.all([
getChatItems({
appId: app._id,
chatId,
limit: 30,
field: `dataId obj value userGoodFeedback userBadFeedback ${
shareChat.responseDetail ? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` : ''
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

View File

@ -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({
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

View File

@ -1,13 +1,10 @@
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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { id: dataId } = req.query as {
id: string;
};
@ -30,11 +27,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
jsonRes(res, {
data: 'success'
});
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
error: err
});
}
});
export default NextAPI(handler);

View File

@ -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,9 +11,7 @@ export type Response = {
source: string;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { id: dataId } = req.query as {
id: string;
};
@ -29,10 +28,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
jsonRes(res, {
data: datasetData
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export default NextAPI(handler);

View File

@ -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,10 +15,9 @@ 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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { collectionId, q, a, indexes } = req.body as InsertOneDatasetDataProps;
if (!q) {
@ -98,10 +96,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
jsonRes<string>(res, {
data: insertId
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});
export default NextAPI(handler);

View File

@ -7,10 +7,9 @@ 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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
let {
pageNum = 1,
pageSize = 10,
@ -59,10 +58,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
total
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export default NextAPI(handler);

View File

@ -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,10 +10,9 @@ 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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const body = req.body as PushDatasetDataProps;
const { collectionId, data } = body;
@ -51,13 +49,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
vectorModel: collection.datasetId.vectorModel
})
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});
export default NextAPI(handler);
export const config = {
api: {

View File

@ -1,16 +1,14 @@
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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { id, q = '', a, indexes = [] } = req.body as UpdateDatasetDataProps;
// auth data permission
@ -50,10 +48,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});
export default NextAPI(handler);

View File

@ -1,19 +1,16 @@
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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
let { datasetId } = req.query as {
datasetId: string;
};
@ -80,15 +77,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
updateExportDatasetLimit(teamId);
} catch (err) {
res.status(500);
addLog.error(`export dataset error`, err);
jsonRes(res, {
code: 500,
error: err
});
}
});
export default NextAPI(handler);
export const config = {
api: {

View File

@ -1,16 +1,14 @@
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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` };
// 凭证校验
const { teamId, tmbId, teamOwner, role, canWrite } = await authUserRole({
@ -45,10 +43,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
jsonRes<DatasetListItemType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export default NextAPI(handler);

View File

@ -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,10 +12,9 @@ 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<any>) {
try {
await connectToDatabase();
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const {
datasetId,
text,
@ -99,10 +96,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
...result
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});
export default NextAPI(handler);

View File

@ -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<any>) {
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
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: {

View File

@ -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({
// 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: {

View File

@ -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<any>) {
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
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
});
}
});
}

View File

@ -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[] }) => {
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, {
modules: nodes,
edges,
...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 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]
);
}, [flowData2StoreDataAndCheck, publishApp, app._id, toast, t, ChatTestRef]);
const saveAndBack = useCallback(async () => {
try {
await onclickSave();
onClose();
} catch (error) {}
}, [onClose, onclickSave]);
const onExportWorkflow = useCallback(async () => {
const data = await flowData2StoreDataAndCheck();
if (data) {
await onclickSave(data);
copyData(
JSON.stringify(
{
nodes: filterSensitiveNodesData(data.nodes),
edges: data.edges
},
null,
2
),
t('app.Export Config Successful')
);
}
onClose();
} catch (error) {
toast({
status: 'warning',
title: getErrText(error)
}, [copyData, flowData2StoreDataAndCheck, t]);
useBeforeunload({
callback: onclickSave,
tip: t('core.common.tip.leave page')
});
useQuery(['autoSave'], onclickSave, {
refetchInterval: 20 * 1000,
enabled: !!app._id
});
}
}, [flowData2StoreDataAndCheck, onClose, onclickSave, toast]);
const Render = useMemo(() => {
return (
@ -139,11 +191,28 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
variant={'whiteBase'}
aria-label={''}
isLoading={isSaving}
onClick={openConfirmOut(saveAndBack, onClose)}
onClick={saveAndBack}
/>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
<Box ml={[3, 5]}>
<Box fontSize={['md', 'lg']} fontWeight={'bold'}>
{app.name}
</Box>
<MyTooltip label={t('core.app.Onclick to save')}>
<Box
fontSize={'sm'}
mt={1}
display={'inline-block'}
borderRadius={'xs'}
cursor={'pointer'}
onClick={onclickSave}
color={'myGray.500'}
>
{saveLabel}
</Box>
</MyTooltip>
</Box>
<Box flex={1} />
<MyMenu
Button={
@ -164,22 +233,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
{
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')
);
}
}
onClick: onExportWorkflow
}
]}
/>
@ -202,34 +256,27 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<Button
size={'sm'}
isLoading={isSaving}
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={async () => {
const modules = await flowData2StoreDataAndCheck();
if (modules) {
onclickSave(modules);
}
}}
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
onClick={openConfigPublish(onclickPublish)}
>
{t('common.Save')}
{t('core.app.Publish')}
</Button>
</Flex>
<ConfirmModal
closeText={t('core.app.edit.UnSave')}
confirmText={t('core.app.edit.Save and out')}
/>
<ConfirmModal confirmText={t('core.app.Publish')} />
</>
);
}, [
ConfirmModal,
app.name,
copyData,
flowData2StoreDataAndCheck,
isSaving,
onClose,
onExportWorkflow,
onOpenImport,
onclickPublish,
onclickSave,
openConfirmOut,
openConfigPublish,
saveAndBack,
saveLabel,
setWorkflowTestData,
t,
theme.borders.base

View File

@ -19,17 +19,15 @@ const Render = ({ app, onClose }: Props) => {
const { initData } = useFlowProviderStore();
useEffect(() => {
if (!isV2Workflow) return;
initData(
JSON.parse(
JSON.stringify({
const workflowStringData = JSON.stringify({
nodes: app.modules || [],
edges: app.edges || []
})
)
);
}, [isV2Workflow, app.edges, app.modules]);
});
useEffect(() => {
if (!isV2Workflow) return;
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 <Flow Header={<Header app={app} onClose={onClose} />} />;

View File

@ -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'),

View File

@ -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<string[]>(appDetail?.teamTags || []);
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => {
await replaceAppDetail(appDetail._id, {
await updateAppDetail(appDetail._id, {
teamTags: selectedTags
});
},

View File

@ -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({

View File

@ -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) => (
<Card
key={model._id}
display={'flex'}
w={'100%'}
flexDirection={'column'}
p={4}
borderRadius={'md'}
border={'1px solid '}
userSelect={'none'}
boxShadow={'none'}
borderColor={'myGray.200'}
_hover={{
boxShadow: 'lg'
}}
>
<Flex alignItems={'center'}>
<Avatar
src={model.avatar}
w={['28px', '36px']}
h={['28px', '36px']}
borderRadius={'50%'}
/>
<Box fontWeight={'bold'} fontSize={'lg'} ml={5}>
{model.name}
</Box>
</Flex>
<MyTooltip label={model.intro}>
<Box
className={'textEllipsis3'}
flex={1}
my={4}
fontSize={'sm'}
wordBreak={'break-all'}
color={'blackAlpha.600'}
>
{model.intro || '这个应用还没有介绍~'}
</Box>
</MyTooltip>
<Flex justifyContent={'space-between'}>
<Flex
alignItems={'center'}
cursor={'pointer'}
color={model.isCollection ? 'primary.600' : 'blackAlpha.700'}
onClick={() => onclickCollection(model._id)}
>
<MyIcon
mr={1}
name={model.isCollection ? 'collectionSolid' : 'collectionLight'}
w={'16px'}
/>
{model.share.collection}
</Flex>
<Box>
<Button
size={'sm'}
variant={'whitePrimary'}
w={['60px', '70px']}
onClick={() => router.push(`/chat?appId=${model._id}`)}
>
</Button>
</Box>
</Flex>
</Card>
))}
</>
);
};
export default ShareModelList;

View File

@ -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<ShareAppItem>({
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 (
<Box px={[5, 10]} py={[4, 6]} position={'relative'} minH={'109vh'}>
<Flex alignItems={'center'} mb={2}>
<Box className={'textlg'} fontWeight={'bold'} fontSize={'3xl'}>
AI
</Box>
{/* <Box mt={[2, 0]} textAlign={'right'}>
<Input
w={['200px', '250px']}
size={'sm'}
value={searchText}
placeholder="搜索应用,回车确认"
onChange={(e) => 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;
}
}}
/>
</Box> */}
</Flex>
<Grid
templateColumns={[
'repeat(1,1fr)',
'repeat(2,1fr)',
'repeat(3,1fr)',
'repeat(4,1fr)',
'repeat(5,1fr)'
]}
gridGap={4}
mt={4}
>
<ShareModelList models={models} onclickCollection={onclickCollection} />
</Grid>
<Flex mt={4} justifyContent={'center'}>
<Pagination />
</Flex>
<Loading loading={isLoading} />
</Box>
);
};
export async function getServerSideProps(content: any) {
return {
props: {
...(await serviceSideProps(content))
}
};
}
export default modelList;

View File

@ -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,17 +49,40 @@ const Header = ({ plugin, onClose }: Props) => {
}, [edges, onUpdateNodeError, t, toast]);
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: ({ nodes, edges }: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
return putUpdatePlugin({
mutationFn: async () => {
const workflow = await flowData2StoreDataAndCheck();
if (workflow) {
await putUpdatePlugin({
id: plugin._id,
modules: nodes,
edges
modules: workflow.nodes,
edges: workflow.edges
});
},
successToast: '保存配置成功',
errorToast: '保存配置异常'
toast({
status: 'success',
title: t('common.Save Success')
});
}
}
});
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]);
const Render = useMemo(() => {
return (
<>
<Flex
@ -101,50 +122,15 @@ const Header = ({ plugin, onClose }: Props) => {
{
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')
);
}
}
onClick: onCopy
}
]}
/>
{/* <MyTooltip label={t('module.Preview Plugin')}>
<IconButton
mr={[3, 5]}
icon={<MyIcon name={'core/modules/previewLight'} w={['14px', '16px']} />}
size={'smSquare'}
aria-label={'save'}
variant={'whitePrimary'}
onClick={async () => {
const modules = await flowData2StoreDataAndCheck();
if (modules) {
setPreviewModules(modules);
}
}}
/>
</MyTooltip> */}
<Button
size={'sm'}
isLoading={isLoading}
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={async () => {
const modules = await flowData2StoreDataAndCheck();
if (modules) {
onclickSave(modules);
}
}}
onClick={onclickSave}
>
{t('common.Save')}
</Button>
@ -152,6 +138,20 @@ const Header = ({ plugin, onClose }: Props) => {
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
</>
);
}, [
isLoading,
isOpenImport,
onClose,
onCloseImport,
onCopy,
onOpenImport,
onclickSave,
plugin.name,
t,
theme.borders.base
]);
return Render;
};
export default React.memo(Header);

View File

@ -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你可以直接点击测试进行调试无需点击保存点击保存为新版工作流。'
});
useEffect(() => {
if (isV2Workflow) {
initData(
JSON.parse(
JSON.stringify({
const workflowStringData = JSON.stringify({
nodes: pluginDetail?.modules || [],
edges: pluginDetail?.edges || []
})
)
);
});
useEffect(() => {
if (isV2Workflow) {
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 ? (
<>

View File

@ -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
});
}
};
};

View File

@ -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<AppDetailType>(`/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<number>(`/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);

View File

@ -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<AppDetailType>;
updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>;
replaceAppDetail(appId: string, data: AppUpdateParams): Promise<void>;
publishApp(appId: string, data: PostPublishAppProps): Promise<void>;
clearAppModules(): void;
};
@ -44,19 +46,22 @@ export const useAppStore = create<State>()(
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 = {

View File

@ -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);

View File

@ -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';

View File

@ -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.chatHistory]: {
handlerStyle: {
borderColor: '#00A9A6'
[WorkflowIOValueTypeEnum.object]: {
label: 'object',
value: WorkflowIOValueTypeEnum.object,
description: ''
},
label: 'core.module.valueType.chatHistory',
[WorkflowIOValueTypeEnum.arrayString]: {
label: 'array<string>',
value: WorkflowIOValueTypeEnum.arrayString,
description: ''
},
[WorkflowIOValueTypeEnum.arrayNumber]: {
label: 'array<number>',
value: WorkflowIOValueTypeEnum.arrayNumber,
description: ''
},
[WorkflowIOValueTypeEnum.arrayBoolean]: {
label: 'array<boolean>',
value: WorkflowIOValueTypeEnum.arrayBoolean,
description: ''
},
[WorkflowIOValueTypeEnum.arrayObject]: {
label: 'array<object>',
value: WorkflowIOValueTypeEnum.arrayObject,
description: ''
},
[WorkflowIOValueTypeEnum.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: ''

View File

@ -1 +0,0 @@
export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID';

View File

@ -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 = ({