* 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 容器名`查看对应日志。 1. `docker ps -a` 查看所有容器运行状态,检查是否全部 running如有异常尝试`docker logs 容器名`查看对应日志。
2. 容器都运行正常的,`docker logs 容器名` 查看报错日志 2. 容器都运行正常的,`docker logs 容器名` 查看报错日志
3. 无法解决时,可以找找[Issue](https://github.com/labring/FastGPT/issues),或新提 Issue私有部署错误务必提供详细的日志否则很难排查。 3. 带有`requestId`的,都是 OneAPI 提示错误,大部分都是因为模型接口报错。
4. 无法解决时,可以找找[Issue](https://github.com/labring/FastGPT/issues),或新提 Issue私有部署错误务必提供详细的日志否则很难排查。
## 二、通用问题 ## 二、通用问题
@ -90,4 +93,9 @@ FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应
OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。 OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。
可以`exec`进入容器,`env`查看环境变量是否生效。 可以`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) => export const formatTime2YMDHM = (time?: Date) =>
time ? dayjs(time).format('YYYY-MM-DD HH:mm') : ''; time ? dayjs(time).format('YYYY-MM-DD HH:mm') : '';
export const formatTime2YMD = (time?: Date) => (time ? dayjs(time).format('YYYY-MM-DD') : ''); 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 */ /* cron time parse */
export const cronParser2Fields = (cronString: string) => { export const cronParser2Fields = (cronString: string) => {

View File

@ -6,7 +6,7 @@ import { VariableInputEnum } from '../workflow/constants';
import { SelectedDatasetType } from '../workflow/api'; import { SelectedDatasetType } from '../workflow/api';
import { DatasetSearchModeEnum } from '../dataset/constants'; import { DatasetSearchModeEnum } from '../dataset/constants';
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d'; 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 { export interface AppSchema {
_id: string; _id: string;
@ -18,6 +18,7 @@ export interface AppSchema {
avatar: string; avatar: string;
intro: string; intro: string;
updateTime: number; updateTime: number;
modules: StoreNodeItemType[]; modules: StoreNodeItemType[];
edges: StoreEdgeItemType[]; 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', string = 'string',
number = 'number', number = 'number',
boolean = 'boolean', boolean = 'boolean',
object = 'object',
arrayString = 'arrayString',
arrayNumber = 'arrayNumber',
arrayBoolean = 'arrayBoolean',
arrayObject = 'arrayObject',
any = 'any', any = 'any',
chatHistory = 'chatHistory', chatHistory = 'chatHistory',
datasetQuote = 'datasetQuote', datasetQuote = 'datasetQuote',
dynamic = 'dynamic', dynamic = 'dynamic',
// plugin special type // plugin special type
selectApp = 'selectApp', selectApp = 'selectApp',
selectDataset = 'selectDataset', selectDataset = 'selectDataset'
// tool
tools = 'tools'
} }
/* reg: modulename key */ /* reg: modulename key */
@ -173,3 +176,5 @@ export enum RuntimeEdgeStatusEnum {
'active' = 'active', 'active' = 'active',
'skipped' = 'skipped' '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 { StoreNodeItemType } from '../type';
import { StoreEdgeItemType } from '../type/edge'; import { StoreEdgeItemType } from '../type/edge';
import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type'; 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[] => { export const initWorkflowEdgeStatus = (edges: StoreEdgeItemType[]): RuntimeEdgeItemType[] => {
return ( return (

View File

@ -10,16 +10,26 @@ import {
NodeOutputKeyEnum, NodeOutputKeyEnum,
FlowNodeTemplateTypeEnum FlowNodeTemplateTypeEnum
} from '../../constants'; } from '../../constants';
import { Input_Template_Dataset_Quote } from '../input';
import { getNanoid } from '../../../../common/string/tools'; import { getNanoid } from '../../../../common/string/tools';
import { getHandleConfig } from '../utils'; import { getHandleConfig } from '../utils';
import { FlowNodeInputItemType } from '../../type/io.d'; import { FlowNodeInputItemType } from '../../type/io.d';
export const getOneQuoteInputTemplate = (key = getNanoid()): FlowNodeInputItemType => ({ const defaultQuoteKey = 'defaultQuoteKey';
...Input_Template_Dataset_Quote,
export const getOneQuoteInputTemplate = ({
key = getNanoid(),
index
}: {
key?: string;
index: number;
}): FlowNodeInputItemType => ({
key, key,
renderTypeList: [FlowNodeInputTypeEnum.custom], renderTypeList: [FlowNodeInputTypeEnum.reference],
description: '' label: `引用${index}`,
debugLabel: '知识库引用',
canEdit: key !== defaultQuoteKey,
description: '',
valueType: WorkflowIOValueTypeEnum.datasetQuote
}); });
export const DatasetConcatModule: FlowNodeTemplateType = { export const DatasetConcatModule: FlowNodeTemplateType = {
@ -37,7 +47,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
key: NodeInputKeyEnum.datasetMaxTokens, key: NodeInputKeyEnum.datasetMaxTokens,
renderTypeList: [FlowNodeInputTypeEnum.custom], renderTypeList: [FlowNodeInputTypeEnum.custom],
label: '最大 Tokens', label: '最大 Tokens',
value: 1500, value: 3000,
valueType: WorkflowIOValueTypeEnum.number valueType: WorkflowIOValueTypeEnum.number
}, },
{ {
@ -45,7 +55,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
renderTypeList: [FlowNodeInputTypeEnum.custom], renderTypeList: [FlowNodeInputTypeEnum.custom],
label: '' label: ''
}, },
getOneQuoteInputTemplate() getOneQuoteInputTemplate({ key: defaultQuoteKey, index: 1 })
], ],
outputs: [ outputs: [
{ {

View File

@ -1,19 +1,12 @@
import type { NextApiResponse, NextApiHandler, NextApiRequest } from 'next'; import type { NextApiResponse, NextApiRequest } from 'next';
import NextCors from 'nextjs-cors'; import NextCors from 'nextjs-cors';
export function withNextCors(handler: NextApiHandler): NextApiHandler { export async function withNextCors(req: NextApiRequest, res: NextApiResponse) {
return async function nextApiHandlerWrappedWithNextCors( const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
req: NextApiRequest, const origin = req.headers.origin;
res: NextApiResponse await NextCors(req, res, {
) { methods,
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; origin: origin,
const origin = req.headers.origin; optionsSuccessStatus: 200
await NextCors(req, res, { });
methods,
origin: origin,
optionsSuccessStatus: 200
});
return handler(req, res);
};
} }

View File

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

View File

@ -169,7 +169,16 @@ class PgClass {
} }
async query<T extends QueryResultRow = any>(sql: string) { async query<T extends QueryResultRow = any>(sql: string) {
const pg = await connectPg(); 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 TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant'; } from '@fastgpt/global/support/user/team/constant';
export const appCollectionName = 'apps'; export const AppCollectionName = 'apps';
const AppSchema = new Schema({ const AppSchema = new Schema({
teamId: { teamId: {
@ -46,6 +46,8 @@ const AppSchema = new Schema({
type: Date, type: Date,
default: () => new Date() default: () => new Date()
}, },
// tmp store
modules: { modules: {
type: Array, type: Array,
default: [] default: []
@ -92,6 +94,6 @@ try {
} }
export const MongoApp: Model<AppType> = export const MongoApp: Model<AppType> =
models[appCollectionName] || model(appCollectionName, AppSchema); models[AppCollectionName] || model(AppCollectionName, AppSchema);
MongoApp.syncIndexes(); 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, TeamCollectionName,
TeamMemberCollectionName TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant'; } from '@fastgpt/global/support/user/team/constant';
import { appCollectionName } from '../app/schema'; import { AppCollectionName } from '../app/schema';
import { userCollectionName } from '../../support/user/schema'; import { userCollectionName } from '../../support/user/schema';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
@ -40,7 +40,7 @@ const ChatItemSchema = new Schema({
}, },
appId: { appId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: appCollectionName, ref: AppCollectionName,
required: true required: true
}, },
time: { time: {

View File

@ -6,7 +6,7 @@ import {
TeamCollectionName, TeamCollectionName,
TeamMemberCollectionName TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant'; } from '@fastgpt/global/support/user/team/constant';
import { appCollectionName } from '../app/schema'; import { AppCollectionName } from '../app/schema';
export const chatCollectionName = 'chat'; export const chatCollectionName = 'chat';
@ -31,7 +31,7 @@ const ChatSchema = new Schema({
}, },
appId: { appId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: appCollectionName, ref: AppCollectionName,
required: true required: true
}, },
updateTime: { updateTime: {

View File

@ -220,9 +220,16 @@ export async function dispatchWorkFlow({
).then((result) => { ).then((result) => {
const flat = result.flat(); const flat = result.flat();
if (flat.length === 0) return; 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(); 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'; } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type'; import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils';
type Props = ModuleDispatchProps<{ type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.condition]: IfElseConditionType; [NodeInputKeyEnum.condition]: IfElseConditionType;
@ -20,20 +21,21 @@ function checkCondition(condition: VariableConditionEnum, variableValue: any, va
[VariableConditionEnum.isNotEmpty]: () => !!variableValue, [VariableConditionEnum.isNotEmpty]: () => !!variableValue,
[VariableConditionEnum.equalTo]: () => variableValue === value, [VariableConditionEnum.equalTo]: () => variableValue === value,
[VariableConditionEnum.notEqual]: () => variableValue !== value, [VariableConditionEnum.notEqual]: () => variableValue !== value,
[VariableConditionEnum.greaterThan]: () => variableValue > Number(value), [VariableConditionEnum.greaterThan]: () => Number(variableValue) > Number(value),
[VariableConditionEnum.lessThan]: () => variableValue < Number(value), [VariableConditionEnum.lessThan]: () => Number(variableValue) < Number(value),
[VariableConditionEnum.greaterThanOrEqualTo]: () => variableValue >= Number(value), [VariableConditionEnum.greaterThanOrEqualTo]: () => Number(variableValue) >= Number(value),
[VariableConditionEnum.lessThanOrEqualTo]: () => variableValue <= Number(value), [VariableConditionEnum.lessThanOrEqualTo]: () => Number(variableValue) <= Number(value),
[VariableConditionEnum.include]: () => variableValue.includes(value), [VariableConditionEnum.include]: () => variableValue?.includes(value),
[VariableConditionEnum.notInclude]: () => !variableValue.includes(value), [VariableConditionEnum.notInclude]: () => !variableValue?.includes(value),
[VariableConditionEnum.startWith]: () => variableValue.startsWith(value), [VariableConditionEnum.startWith]: () => variableValue?.startsWith(value),
[VariableConditionEnum.endWith]: () => variableValue.endsWith(value), [VariableConditionEnum.endWith]: () => variableValue?.endsWith(value),
[VariableConditionEnum.lengthEqualTo]: () => variableValue.length === Number(value), [VariableConditionEnum.lengthEqualTo]: () => variableValue?.length === Number(value),
[VariableConditionEnum.lengthNotEqualTo]: () => variableValue.length !== Number(value), [VariableConditionEnum.lengthNotEqualTo]: () => variableValue?.length !== Number(value),
[VariableConditionEnum.lengthGreaterThan]: () => variableValue.length > Number(value), [VariableConditionEnum.lengthGreaterThan]: () => variableValue?.length > Number(value),
[VariableConditionEnum.lengthGreaterThanOrEqualTo]: () => variableValue.length >= Number(value), [VariableConditionEnum.lengthGreaterThanOrEqualTo]: () =>
[VariableConditionEnum.lengthLessThan]: () => variableValue.length < Number(value), variableValue?.length >= Number(value),
[VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue.length <= Number(value) [VariableConditionEnum.lengthLessThan]: () => variableValue?.length < Number(value),
[VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue?.length <= Number(value)
}; };
return (operations[condition] || (() => false))(); return (operations[condition] || (() => false))();
@ -43,15 +45,18 @@ export const dispatchIfElse = async (props: Props): Promise<DispatchNodeResultTy
const { const {
params, params,
runtimeNodes, runtimeNodes,
variables,
node: { nodeId } node: { nodeId }
} = props; } = props;
const { condition, ifElseList } = params; const { condition, ifElseList } = params;
const listResult = ifElseList.map((item) => { const listResult = ifElseList.map((item) => {
const { variable, condition: variableCondition, value } = item; const { variable, condition: variableCondition, value } = item;
const variableValue = runtimeNodes const variableValue = getReferenceVariableValue({
.find((node) => node.nodeId === variable[0]) value: variable,
?.outputs?.find((item) => item.id === variable[1])?.value; variables,
nodes: runtimeNodes
});
return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || ''); return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || '');
}); });

View File

@ -6,7 +6,7 @@ import {
TeamCollectionName, TeamCollectionName,
TeamMemberCollectionName TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant'; } from '@fastgpt/global/support/user/team/constant';
import { appCollectionName } from '../../core/app/schema'; import { AppCollectionName } from '../../core/app/schema';
const OutLinkSchema = new Schema({ const OutLinkSchema = new Schema({
shareId: { shareId: {
@ -25,7 +25,7 @@ const OutLinkSchema = new Schema({
}, },
appId: { appId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: appCollectionName, ref: AppCollectionName,
required: true required: true
}, },
type: { type: {

View File

@ -30,12 +30,10 @@ export async function authApp({
// get app // get app
const app = await MongoApp.findOne({ _id: appId, teamId }).lean(); const app = await MongoApp.findOne({ _id: appId, teamId }).lean();
if (!app) { if (!app) {
return Promise.reject(AppErrEnum.unAuthApp); return Promise.reject(AppErrEnum.unExist);
} }
const isOwner = const isOwner = String(app.tmbId) === tmbId;
role !== TeamMemberRoleEnum.visitor &&
(String(app.tmbId) === tmbId || role === TeamMemberRoleEnum.owner);
const canWrite = const canWrite =
isOwner || isOwner ||
(app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor); (app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor);

View File

@ -40,6 +40,7 @@ export const iconPaths = {
'common/paramsLight': () => import('./icons/common/paramsLight.svg'), 'common/paramsLight': () => import('./icons/common/paramsLight.svg'),
'common/playFill': () => import('./icons/common/playFill.svg'), 'common/playFill': () => import('./icons/common/playFill.svg'),
'common/playLight': () => import('./icons/common/playLight.svg'), 'common/playLight': () => import('./icons/common/playLight.svg'),
'common/publishFill': () => import('./icons/common/publishFill.svg'),
'common/questionLight': () => import('./icons/common/questionLight.svg'), 'common/questionLight': () => import('./icons/common/questionLight.svg'),
'common/refreshLight': () => import('./icons/common/refreshLight.svg'), 'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
'common/resultLight': () => import('./icons/common/resultLight.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,21 +54,20 @@ const MultipleRowSelect = ({
bg: 'primary.50', bg: 'primary.50',
color: 'primary.600' color: 'primary.600'
}} }}
onClick={() => {
const newValue = [...cloneValue];
newValue[index] = item.value;
setCloneValue(newValue);
if (!hasChildren) {
onSelect(newValue);
onClose();
}
}}
{...(item.value === selectedValue {...(item.value === selectedValue
? { ? {
color: 'primary.600' color: 'primary.600'
} }
: { : {})}
onClick: () => {
const newValue = [...cloneValue];
newValue[index] = item.value;
setCloneValue(newValue);
if (!hasChildren) {
onSelect(newValue);
onClose();
}
}
})}
> >
{item.label} {item.label}
</Flex> </Flex>

View File

@ -12,7 +12,7 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...
<Tooltip <Tooltip
className="tooltip" className="tooltip"
bg={'white'} bg={'white'}
arrowShadowColor={' rgba(0,0,0,0.05)'} arrowShadowColor={'rgba(0,0,0,0.05)'}
hasArrow hasArrow
arrowSize={12} arrowSize={12}
offset={[-15, 15]} offset={[-15, 15]}

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, onClose,
ConfirmModal: useCallback( ConfirmModal: useCallback(
({ ({
closeText = t('common.Close'), closeText = t('common.Cancel'),
confirmText = t('common.Confirm'), confirmText = t('common.Confirm'),
isLoading, isLoading,
bg, bg,

3
pnpm-lock.yaml generated
View File

@ -356,9 +356,6 @@ importers:
'@fortaine/fetch-event-source': '@fortaine/fetch-event-source':
specifier: ^3.0.6 specifier: ^3.0.6
version: 3.0.6 version: 3.0.6
'@node-rs/jieba':
specifier: 1.10.0
version: 1.10.0
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^4.24.10 specifier: ^4.24.10
version: 4.24.10(react-dom@18.2.0)(react@18.2.0) version: 4.24.10(react-dom@18.2.0)(react@18.2.0)

View File

@ -35,6 +35,7 @@ const nextConfig = {
if (isServer) { if (isServer) {
config.externals.push('isolated-vm'); config.externals.push('isolated-vm');
config.externals.push('worker_threads'); config.externals.push('worker_threads');
config.externals.push('@node-rs/jieba');
if (config.name === 'server') { if (config.name === 'server') {
// config.output.globalObject = 'self'; // config.output.globalObject = 'self';

View File

@ -23,7 +23,6 @@
"@fastgpt/service": "workspace:*", "@fastgpt/service": "workspace:*",
"@fastgpt/web": "workspace:*", "@fastgpt/web": "workspace:*",
"@fortaine/fetch-event-source": "^3.0.6", "@fortaine/fetch-event-source": "^3.0.6",
"@node-rs/jieba": "1.10.0",
"@tanstack/react-query": "^4.24.10", "@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"axios": "^1.5.1", "axios": "^1.5.1",

View File

@ -73,6 +73,7 @@
"Confirm Import": "Import", "Confirm Import": "Import",
"Confirm Move": "Move here", "Confirm Move": "Move here",
"Confirm Update": "Update", "Confirm Update": "Update",
"Confirm to leave the page": "Are you sure to leave this page?",
"Copy": "Copy", "Copy": "Copy",
"Copy Successful": "Copy Successful", "Copy Successful": "Copy Successful",
"Course": "", "Course": "",
@ -278,6 +279,7 @@
"Api request desc": "Access to the existing system through API, or enterprise micro, flying book, etc", "Api request desc": "Access to the existing system through API, or enterprise micro, flying book, etc",
"App intro": "App intro", "App intro": "App intro",
"App params config": "App Config", "App params config": "App Config",
"Auto Save time": "Auto-saved: {{time}}",
"Chat Variable": "", "Chat Variable": "",
"Config schedule plan": "Config schedule config", "Config schedule plan": "Config schedule config",
"Config whisper": "Config whisper", "Config whisper": "Config whisper",
@ -289,6 +291,11 @@
"Max histories": "Dialog round", "Max histories": "Dialog round",
"Max tokens": "Max tokens", "Max tokens": "Max tokens",
"Name and avatar": "Avatar & Name", "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": "Question Guide",
"Question Guide Tip": "At the end of the conversation, three leading questions will be asked.", "Question Guide Tip": "At the end of the conversation, three leading questions will be asked.",
"Quote prompt": "Quote prompt", "Quote prompt": "Quote prompt",
@ -1035,11 +1042,16 @@
}, },
"valueType": { "valueType": {
"any": "Any", "any": "Any",
"arrayBoolean": "Array Boolean",
"arrayNumber": "Array Number",
"arrayObject": "Array Object",
"arrayString": "Array String",
"boolean": "Boolean", "boolean": "Boolean",
"chatHistory": "History", "chatHistory": "History",
"datasetQuote": "Dataset Quote", "datasetQuote": "Dataset Quote",
"dynamicTargetInput": "Dynamic Input", "dynamicTargetInput": "Dynamic Input",
"number": "Number", "number": "Number",
"object": "Object",
"selectApp": "Select App", "selectApp": "Select App",
"selectDataset": "Select Dataset", "selectDataset": "Select Dataset",
"string": "String", "string": "String",
@ -1128,6 +1140,7 @@
"textarea": "Textarea" "textarea": "Textarea"
}, },
"tool": { "tool": {
"Handle": "Tool handle",
"Select Tool": "Select Tool" "Select Tool": "Select Tool"
} }
} }

View File

@ -73,6 +73,7 @@
"Confirm Import": "确认导入", "Confirm Import": "确认导入",
"Confirm Move": "移动到这", "Confirm Move": "移动到这",
"Confirm Update": "确认更新", "Confirm Update": "确认更新",
"Confirm to leave the page": "确认离开该页面?",
"Copy": "复制", "Copy": "复制",
"Copy Successful": "复制成功", "Copy Successful": "复制成功",
"Course": "", "Course": "",
@ -278,6 +279,7 @@
"Api request desc": "通过 API 接入到已有系统中,或企微、飞书等", "Api request desc": "通过 API 接入到已有系统中,或企微、飞书等",
"App intro": "应用介绍", "App intro": "应用介绍",
"App params config": "应用配置", "App params config": "应用配置",
"Auto Save time": "自动保存: {{time}}",
"Chat Variable": "对话框变量", "Chat Variable": "对话框变量",
"Config schedule plan": "配置定时执行", "Config schedule plan": "配置定时执行",
"Config whisper": "配置语音输入", "Config whisper": "配置语音输入",
@ -289,6 +291,11 @@
"Max histories": "聊天记录数量", "Max histories": "聊天记录数量",
"Max tokens": "回复上限", "Max tokens": "回复上限",
"Name and avatar": "头像 & 名称", "Name and avatar": "头像 & 名称",
"Onclick to save": "点击保存",
"Publish": "发布",
"Publish Confirm": "确认发布应用?会立即更新所有发布渠道的应用状态。",
"Publish Failed": "发布失败",
"Publish Success": "发布成功",
"Question Guide": "猜你想问", "Question Guide": "猜你想问",
"Question Guide Tip": "对话结束后,会为生成 3 个引导性问题。", "Question Guide Tip": "对话结束后,会为生成 3 个引导性问题。",
"Quote prompt": "引用模板提示词", "Quote prompt": "引用模板提示词",
@ -689,7 +696,7 @@
"Data file progress": "数据上传进度", "Data file progress": "数据上传进度",
"Data process params": "数据处理参数", "Data process params": "数据处理参数",
"Down load csv template": "点击下载 CSV 模板", "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": "预估价格: {{amount}}{{unit}}",
"Estimated Price Tips": "QA计费为\n输入: {{charsPointsPrice}}积分/1k Tokens", "Estimated Price Tips": "QA计费为\n输入: {{charsPointsPrice}}积分/1k Tokens",
"Estimated points": "预估消耗 {{points}} 积分", "Estimated points": "预估消耗 {{points}} 积分",
@ -716,7 +723,7 @@
"Preview chunks": "预览分段最多5段", "Preview chunks": "预览分段最多5段",
"Preview raw text": "预览源文本最多3000字", "Preview raw text": "预览源文本最多3000字",
"Process way": "处理方式", "Process way": "处理方式",
"QA Estimated Price Tips": "需调用文件处理模型,需要消耗较多Tokens: {{price}}积分/1k Tokens", "QA Estimated Price Tips": "需调用文件处理模型,需要消耗较多AI积分: {{price}}积分/1k Tokens",
"QA Import": "QA拆分", "QA Import": "QA拆分",
"QA Import Tip": "根据一定规则,将文本拆成一段较大的段落,调用 AI 为该段落生成问答对。有非常高的检索精度,但是会丢失很多内容细节。", "QA Import Tip": "根据一定规则,将文本拆成一段较大的段落,调用 AI 为该段落生成问答对。有非常高的检索精度,但是会丢失很多内容细节。",
"Re Preview": "重新生成预览", "Re Preview": "重新生成预览",
@ -1036,11 +1043,16 @@
}, },
"valueType": { "valueType": {
"any": "任意", "any": "任意",
"arrayBoolean": "布尔数组",
"arrayNumber": "数字数组",
"arrayObject": "对象数组",
"arrayString": "字符串数组",
"boolean": "布尔", "boolean": "布尔",
"chatHistory": "聊天记录", "chatHistory": "历史记录",
"datasetQuote": "引用内容", "datasetQuote": "知识库类型",
"dynamicTargetInput": "动态字段输入", "dynamicTargetInput": "动态字段输入",
"number": "数字", "number": "数字",
"object": "对象",
"selectApp": "应用选择", "selectApp": "应用选择",
"selectDataset": "知识库选择", "selectDataset": "知识库选择",
"string": "字符串", "string": "字符串",
@ -1129,6 +1141,7 @@
"textarea": "多行输入框" "textarea": "多行输入框"
}, },
"tool": { "tool": {
"Handle": "工具连接器",
"Select Tool": "选择工具" "Select Tool": "选择工具"
} }
} }
@ -1421,7 +1434,7 @@
"user": { "user": {
"AI point standard": "AI积分标准", "AI point standard": "AI积分标准",
"Avatar": "头像", "Avatar": "头像",
"Go laf env": "点击前往 laf 获取 PAT 凭证。", "Go laf env": "点击前往 {{env}} 获取 PAT 凭证。",
"Laf account course": "查看绑定 laf 账号教程。", "Laf account course": "查看绑定 laf 账号教程。",
"Laf account intro": "绑定你的laf账号后你将可以在工作流中使用 laf 模块,实现在线编写代码。", "Laf account intro": "绑定你的laf账号后你将可以在工作流中使用 laf 模块,实现在线编写代码。",
"Need to login": "请先登录", "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 { getErrText } from '@fastgpt/global/common/error/utils';
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils'; import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { delay } from '@fastgpt/global/common/system/utils';
type OnChange<ChangesType> = (changes: ChangesType[]) => void; type OnChange<ChangesType> = (changes: ChangesType[]) => void;
@ -83,7 +84,7 @@ export type useFlowProviderStoreType = {
sourceHandle?: string | undefined; sourceHandle?: string | undefined;
targetHandle?: string | undefined; targetHandle?: string | undefined;
}) => void; }) => void;
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => void; initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise<void>;
splitToolInputs: ( splitToolInputs: (
inputs: FlowNodeInputItemType[], inputs: FlowNodeInputItemType[],
nodeId: string nodeId: string
@ -147,7 +148,10 @@ const StateContext = createContext<useFlowProviderStoreType>({
hasToolNode: false, hasToolNode: false,
connectingEdge: undefined, connectingEdge: undefined,
basicNodeTemplates: [], basicNodeTemplates: [],
initData: function (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }): void { initData: function (e: {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}): Promise<void> {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
hoverNodeId: undefined, hoverNodeId: undefined,
@ -608,16 +612,14 @@ export const FlowProvider = ({
); );
const initData = useCallback( const initData = useCallback(
(e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item }))); setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item })));
setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item }))); setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })));
setTimeout(() => { await delay(200);
onFixView();
}, 100);
}, },
[setEdges, setNodes, onFixView] [setEdges, setNodes]
); );
useEffect(() => { useEffect(() => {

View File

@ -10,13 +10,11 @@ import { SmallAddIcon } from '@chakra-ui/icons';
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat'; import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat';
import { useFlowProviderStore } from '../FlowProvider'; import { useFlowProviderStore } from '../FlowProvider';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider'; import MySlider from '@/components/Slider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import RenderOutput from './render/RenderOutput'; import RenderOutput from './render/RenderOutput';
import Reference from './render/RenderInput/templates/Reference';
import IOTitle from '../components/IOTitle'; import IOTitle from '../components/IOTitle';
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
@ -47,46 +45,13 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return maxTokens; return maxTokens;
}, [llmModelList, nodeList]); }, [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(() => { const onAddField = useCallback(() => {
onChangeNode({ onChangeNode({
nodeId, nodeId,
type: 'addInput', type: 'addInput',
value: getOneQuoteInputTemplate() value: getOneQuoteInputTemplate({ index: quotes.length + 1 })
}); });
}, [nodeId, onChangeNode]); }, [nodeId, onChangeNode, quotes.length]);
const CustomComponent = useMemo(() => { const CustomComponent = useMemo(() => {
return { return {
@ -141,7 +106,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<NodeCard minW={'400px'} selected={selected} {...data}> <NodeCard minW={'400px'} selected={selected} {...data}>
<Container position={'relative'}> <Container position={'relative'}>
<RenderInput nodeId={nodeId} flowInputList={inputs} CustomComponent={CustomComponent} /> <RenderInput nodeId={nodeId} flowInputList={inputs} CustomComponent={CustomComponent} />
{RenderQuoteList} {/* {RenderQuoteList} */}
</Container> </Container>
<Container> <Container>
<IOTitle text={t('common.Output')} /> <IOTitle text={t('common.Output')} />

View File

@ -123,7 +123,7 @@ const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
onChangeNode({ onChangeNode({
nodeId, nodeId,
type: 'updateInput', type: 'updateInput',
key: 'condition', key: NodeInputKeyEnum.condition,
value: { value: {
...conditionInput, ...conditionInput,
value: conditionInput.value === 'OR' ? 'AND' : 'OR' value: conditionInput.value === 'OR' ? 'AND' : 'OR'
@ -297,7 +297,11 @@ const ConditionSelect = ({
valueType === WorkflowIOValueTypeEnum.datasetQuote || valueType === WorkflowIOValueTypeEnum.datasetQuote ||
valueType === WorkflowIOValueTypeEnum.dynamic || valueType === WorkflowIOValueTypeEnum.dynamic ||
valueType === WorkflowIOValueTypeEnum.selectApp || valueType === WorkflowIOValueTypeEnum.selectApp ||
valueType === WorkflowIOValueTypeEnum.tools valueType === WorkflowIOValueTypeEnum.arrayBoolean ||
valueType === WorkflowIOValueTypeEnum.arrayNumber ||
valueType === WorkflowIOValueTypeEnum.arrayObject ||
valueType === WorkflowIOValueTypeEnum.arrayString ||
valueType === WorkflowIOValueTypeEnum.object
) )
return arrayConditionList; return arrayConditionList;

View File

@ -5,21 +5,14 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { Box, Button, Flex } from '@chakra-ui/react'; import { Box, Button, Flex } from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons'; import { SmallAddIcon } from '@chakra-ui/icons';
import { import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import Container from '../components/Container'; import Container from '../components/Container';
import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import { import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
FlowNodeInputItemType,
FlowNodeOutputItemType
} from '@fastgpt/global/core/workflow/type/io';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider'; import { useFlowProviderStore } from '../FlowProvider';
import RenderInput from './render/RenderInput'; import RenderInput from './render/RenderInput';
import { getNanoid } from '@fastgpt/global/common/string/tools';
const FieldEditModal = dynamic(() => import('./render/FieldEditModal')); const FieldEditModal = dynamic(() => import('./render/FieldEditModal'));
@ -37,7 +30,7 @@ const createEditField: EditInputFieldMapType = {
const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs, outputs } = data; const { nodeId, inputs } = data;
const { onChangeNode } = useFlowProviderStore(); const { onChangeNode } = useFlowProviderStore();
const [createField, setCreateField] = useState<EditNodeFieldType>(); const [createField, setCreateField] = useState<EditNodeFieldType>();
@ -93,13 +86,6 @@ const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
canEdit: true, canEdit: true,
editField: createEditField editField: createEditField
}; };
const newOutput: FlowNodeOutputItemType = {
id: getNanoid(),
key: data.key,
valueType: data.valueType,
label: data.label,
type: FlowNodeOutputTypeEnum.static
};
onChangeNode({ onChangeNode({
nodeId, nodeId,

View File

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

View File

@ -149,13 +149,6 @@ const FieldEditModal = ({
[showDynamicInputSelect, t] [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>({ const { register, getValues, setValue, handleSubmit, watch } = useForm<EditNodeFieldType>({
defaultValues: { defaultValues: {
...defaultValue, ...defaultValue,
@ -224,6 +217,13 @@ const FieldEditModal = ({
return inputType === FlowNodeInputTypeEnum.addInputParam; return inputType === FlowNodeInputTypeEnum.addInputParam;
}, [inputType]); }, [inputType]);
const slicedTypeMap = Object.values(FlowValueTypeMap).slice(0, -1);
const dataTypeSelectList = slicedTypeMap.map((item) => ({
label: t(item.label),
value: item.value
}));
const onSubmitSuccess = useCallback( const onSubmitSuccess = useCallback(
(data: EditNodeFieldType) => { (data: EditNodeFieldType) => {
data.key = data?.key?.trim(); data.key = data?.key?.trim();
@ -270,7 +270,7 @@ const FieldEditModal = ({
changeKey: !keys.includes(data.key) changeKey: !keys.includes(data.key)
}); });
}, },
[defaultField.key, keys, onSubmit, showValueTypeSelect, t, toast] [defaultField.key, inputTypeList, keys, onSubmit, showValueTypeSelect, t, toast]
); );
const onSubmitError = useCallback( const onSubmitError = useCallback(
(e: Object) => { (e: Object) => {

View File

@ -28,17 +28,9 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
(connectingEdge?.handleId !== NodeOutputKeyEnum.selectedTools || (connectingEdge?.handleId !== NodeOutputKeyEnum.selectedTools ||
edges.some((edge) => edge.targetHandle === getHandleId(nodeId, 'target', 'top'))); edges.some((edge) => edge.targetHandle === getHandleId(nodeId, 'target', 'top')));
const valueTypeMap = FlowValueTypeMap[WorkflowIOValueTypeEnum.tools];
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
<MyTooltip <MyTooltip label={t('core.workflow.tool.Handle')} shouldWrapChildren={false}>
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<Handle <Handle
style={{ style={{
borderRadius: '0', borderRadius: '0',
@ -63,7 +55,7 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
</Handle> </Handle>
</MyTooltip> </MyTooltip>
); );
}, [handleId, hidden, t, valueTypeMap?.description, valueTypeMap?.label]); }, [handleId, hidden, t]);
return Render; return Render;
}; };
@ -72,8 +64,6 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { setEdges } = useFlowProviderStore(); const { setEdges } = useFlowProviderStore();
const valueTypeMap = FlowValueTypeMap[WorkflowIOValueTypeEnum.tools];
/* onConnect edge, delete tool input and switch */ /* onConnect edge, delete tool input and switch */
const onConnect = useCallback( const onConnect = useCallback(
(e: Connection) => { (e: Connection) => {
@ -90,13 +80,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
<MyTooltip <MyTooltip label={t('core.workflow.tool.Handle')} shouldWrapChildren={false}>
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<Handle <Handle
style={{ style={{
borderRadius: '0', borderRadius: '0',
@ -120,7 +104,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
</Handle> </Handle>
</MyTooltip> </MyTooltip>
); );
}, [onConnect, t, valueTypeMap?.description, valueTypeMap?.label]); }, [onConnect, t]);
return Render; return Render;
}; };

View File

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

View File

@ -1,7 +1,4 @@
import { import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
FlowNodeInputItemType,
FlowNodeOutputItemType
} from '@fastgpt/global/core/workflow/type/io.d';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../../../FlowProvider'; import { useFlowProviderStore } from '../../../FlowProvider';
@ -14,7 +11,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; 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 { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import ValueTypeLabel from '../ValueTypeLabel'; import ValueTypeLabel from '../ValueTypeLabel';
const FieldEditModal = dynamic(() => import('../FieldEditModal')); const FieldEditModal = dynamic(() => import('../FieldEditModal'));
@ -37,16 +33,10 @@ const InputLabel = ({ nodeId, input }: Props) => {
renderTypeList, renderTypeList,
valueType, valueType,
canEdit, canEdit,
key, key
value
} = input; } = input;
const [editField, setEditField] = useState<EditNodeFieldType>(); const [editField, setEditField] = useState<EditNodeFieldType>();
const valueTypeLabel = useMemo(
() => (valueType ? t(FlowValueTypeMap[valueType]?.label) : ''),
[t, valueType]
);
const onChangeRenderType = useCallback( const onChangeRenderType = useCallback(
(e: string) => { (e: string) => {
const index = renderTypeList.findIndex((item) => item === e) || 0; const index = renderTypeList.findIndex((item) => item === e) || 0;
@ -84,35 +74,35 @@ const InputLabel = ({ nodeId, input }: Props) => {
)} )}
</Box> </Box>
{/* value type */} {/* value type */}
{renderType === FlowNodeInputTypeEnum.reference && !!valueTypeLabel && ( {renderType === FlowNodeInputTypeEnum.reference && <ValueTypeLabel valueType={valueType} />}
<ValueTypeLabel>{valueTypeLabel}</ValueTypeLabel>
)}
{/* edit config */} {/* edit config */}
{canEdit && ( {canEdit && (
<> <>
<MyIcon {input.editField && Object.keys(input.editField).length > 0 && (
name={'common/settingLight'} <MyIcon
w={'14px'} name={'common/settingLight'}
cursor={'pointer'} w={'14px'}
ml={3} cursor={'pointer'}
color={'myGray.600'} ml={3}
_hover={{ color: 'primary.500' }} color={'myGray.600'}
onClick={() => _hover={{ color: 'primary.500' }}
setEditField({ onClick={() =>
inputType: renderTypeList[0], setEditField({
valueType: valueType, inputType: renderTypeList[0],
key, valueType: valueType,
label, key,
description, label,
isToolInput: !!toolDescription, description,
defaultValue: input.defaultValue, isToolInput: !!toolDescription,
maxLength: input.maxLength, defaultValue: input.defaultValue,
max: input.max, maxLength: input.maxLength,
min: input.min, max: input.max,
dynamicParamDefaultValue: input.dynamicParamDefaultValue min: input.min,
}) dynamicParamDefaultValue: input.dynamicParamDefaultValue
} })
/> }
/>
)}
<MyIcon <MyIcon
className="delete" className="delete"
name={'delete'} name={'delete'}
@ -207,8 +197,7 @@ const InputLabel = ({ nodeId, input }: Props) => {
selectedTypeIndex, selectedTypeIndex,
t, t,
toolDescription, toolDescription,
valueType, valueType
valueTypeLabel
]); ]);
return RenderLabel; return RenderLabel;

View File

@ -20,7 +20,7 @@ import {
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import PromptTemplate from '@/components/PromptTemplate'; 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 MyIcon from '@fastgpt/web/components/common/Icon';
import Reference from './Reference'; import Reference from './Reference';
import { getSystemVariables } from '@/web/core/app/utils'; import { getSystemVariables } from '@/web/core/app/utils';
@ -152,7 +152,7 @@ const SettingQuotePrompt = (props: RenderInputProps) => {
<Box position={'relative'} color={'myGray.600'} fontWeight={'medium'}> <Box position={'relative'} color={'myGray.600'} fontWeight={'medium'}>
{t('core.module.Dataset quote.label')} {t('core.module.Dataset quote.label')}
</Box> </Box>
<ValueTypeLabel>{t('core.module.valueType.datasetQuote')}</ValueTypeLabel> <ValueTypeLabel valueType={WorkflowIOValueTypeEnum.datasetQuote} />
<MyTooltip label={t('core.module.Setting quote prompt')}> <MyTooltip label={t('core.module.Setting quote prompt')}>
<MyIcon <MyIcon

View File

@ -6,7 +6,6 @@ import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/const
import { SourceHandle } from '../Handle'; import { SourceHandle } from '../Handle';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { Position } from 'reactflow'; import { Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import ValueTypeLabel from '../ValueTypeLabel'; import ValueTypeLabel from '../ValueTypeLabel';
@ -14,11 +13,6 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
const { t } = useTranslation(); const { t } = useTranslation();
const { label = '', description, valueType } = output; const { label = '', description, valueType } = output;
const valueTypeLabel = useMemo(
() => (valueType ? t(FlowValueTypeMap[valueType]?.label) : '-'),
[t, valueType]
);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
<Box position={'relative'}> <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)} {t(label)}
</Box> </Box>
{description && <QuestionTip label={t(description)} />} {description && <QuestionTip label={t(description)} />}
<ValueTypeLabel>{valueTypeLabel}</ValueTypeLabel> <ValueTypeLabel valueType={valueType} />
</Flex> </Flex>
{output.type === FlowNodeOutputTypeEnum.source && ( {output.type === FlowNodeOutputTypeEnum.source && (
<SourceHandle <SourceHandle
@ -50,7 +48,7 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
)} )}
</Box> </Box>
); );
}, [description, output.key, output.type, label, nodeId, t, valueTypeLabel]); }, [output.type, output.key, t, label, description, valueType, nodeId]);
return Render; 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,21 +1,33 @@
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { Box } from '@chakra-ui/react'; 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'; import React from 'react';
const ValueTypeLabel = ({ children }: { children: React.ReactNode }) => { const ValueTypeLabel = ({ valueType }: { valueType?: WorkflowIOValueTypeEnum }) => {
return ( const valueTypeData = valueType ? FlowValueTypeMap[valueType] : undefined;
<Box
bg={'myGray.100'} const label = valueTypeData?.label || '';
color={'myGray.500'} const description = valueTypeData?.description || '';
border={'base'}
borderRadius={'sm'} return !!label ? (
ml={2} <MyTooltip label={description}>
px={1} <Box
py={0.5} bg={'myGray.100'}
fontSize={'11px'} color={'myGray.500'}
> border={'base'}
{children} borderRadius={'sm'}
</Box> ml={2}
); px={1}
h={6}
display={'flex'}
alignItems={'center'}
fontSize={'11px'}
>
{label}
</Box>
</MyTooltip>
) : null;
}; };
export default React.memo(ValueTypeLabel); export default React.memo(ValueTypeLabel);

View File

@ -1,17 +1,6 @@
import React from 'react'; import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
Box,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Button,
Flex
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import type { import type {
EditInputFieldMapType, EditInputFieldMapType,

View File

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

View File

@ -1,6 +1,5 @@
import type { LLMModelItemType } from '../ai/model.d'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppTypeEnum } from './constants'; import { AppSchema } from '@fastgpt/global/core/app/type';
import { AppSchema } from './type';
export type CreateAppParams = { export type CreateAppParams = {
name?: string; name?: string;
@ -10,13 +9,19 @@ export type CreateAppParams = {
edges?: AppSchema['edges']; edges?: AppSchema['edges'];
}; };
export interface AppUpdateParams { export type AppUpdateParams = {
name?: string; name?: string;
type?: `${AppTypeEnum}`; type?: `${AppTypeEnum}`;
avatar?: string; avatar?: string;
intro?: string; intro?: string;
modules?: AppSchema['modules']; nodes?: AppSchema['modules'];
edges?: AppSchema['edges']; edges?: AppSchema['edges'];
permission?: AppSchema['permission']; permission?: AppSchema['permission'];
teamTags?: AppSchema['teamTags']; teamTags?: AppSchema['teamTags'];
} };
export type PostPublishAppProps = {
type: `${AppTypeEnum}`;
nodes: AppSchema['modules'];
edges: AppSchema['edges'];
};

View File

@ -1,52 +1,68 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import type { CreateAppParams } from '@/global/core/app/api.d';
import type { CreateAppParams } from '@fastgpt/global/core/app/api.d';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const {
await connectToDatabase(); name = 'APP',
const { avatar,
name = 'APP', type = AppTypeEnum.advanced,
avatar, modules,
type = AppTypeEnum.advanced, edges
modules, } = req.body as CreateAppParams;
edges
} = req.body as CreateAppParams;
if (!name || !Array.isArray(modules)) { if (!name || !Array.isArray(modules)) {
throw new Error('缺少参数'); throw new Error('缺少参数');
}
// 凭证校验
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
// 上限校验
await checkTeamAppLimit(teamId);
// 创建模型
const response = await MongoApp.create({
avatar,
name,
teamId,
tmbId,
modules,
edges,
type,
version: 'v2'
});
jsonRes(res, {
data: response._id
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
} }
// 凭证校验
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
// 上限校验
await checkTeamAppLimit(teamId);
// 创建模型
const appId = await mongoSessionRun(async (session) => {
const [{ _id: appId }] = await MongoApp.create(
[
{
avatar,
name,
teamId,
tmbId,
modules,
edges,
type,
version: 'v2'
}
],
{ session }
);
await MongoAppVersion.create(
[
{
appId,
nodes: modules,
edges
}
],
{ session }
);
return appId;
});
jsonRes(res, {
data: appId
});
} }
export default NextAPI(handler);

View File

@ -1,60 +1,59 @@
import type { NextApiRequest, NextApiResponse } from 'next'; 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 { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoApp } from '@fastgpt/service/core/app/schema';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { appId } = req.query as { appId: string };
await connectToDatabase();
const { appId } = req.query as { appId: string };
if (!appId) { if (!appId) {
throw new Error('参数错误'); throw new Error('参数错误');
}
// 凭证校验
await authApp({ req, authToken: true, appId, per: 'owner' });
// 删除对应的聊天
await mongoSessionRun(async (session) => {
await MongoChatItem.deleteMany(
{
appId
},
{ session }
);
await MongoChat.deleteMany(
{
appId
},
{ session }
);
// 删除分享链接
await MongoOutLink.deleteMany(
{
appId
},
{ session }
);
// delete app
await MongoApp.deleteOne(
{
_id: appId
},
{ session }
);
});
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
} }
// 凭证校验
await authApp({ req, authToken: true, appId, per: 'owner' });
// 删除对应的聊天
await mongoSessionRun(async (session) => {
await MongoChatItem.deleteMany(
{
appId
},
{ session }
);
await MongoChat.deleteMany(
{
appId
},
{ session }
);
// 删除分享链接
await MongoOutLink.deleteMany(
{
appId
},
{ session }
);
// delete version
await MongoAppVersion.deleteMany(
{
appId
},
{ session }
);
// delete app
await MongoApp.deleteOne(
{
_id: appId
},
{ session }
);
});
} }
export default NextAPI(handler);

View File

@ -2,27 +2,20 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authApp } from '@fastgpt/service/support/permission/auth/app'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { appId } = req.query as { appId: string };
await connectToDatabase();
const { appId } = req.query as { appId: string };
if (!appId) { if (!appId) {
throw new Error('参数错误'); throw new Error('参数错误');
}
// 凭证校验
const { app } = await authApp({ req, authToken: true, appId, per: 'w' });
jsonRes(res, {
data: app
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
} }
// 凭证校验
const { app } = await authApp({ req, authToken: true, appId, per: 'w' });
return app;
} }
export default NextAPI(handler);

View File

@ -1,6 +1,4 @@
import type { NextApiRequest, NextApiResponse } from 'next'; 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 { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import type { PagingData } from '@/types'; import type { PagingData } from '@/types';
import { AppLogsListItemType } from '@/types/app'; import { AppLogsListItemType } from '@/types/app';
@ -9,144 +7,140 @@ import { addDays } from 'date-fns';
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d'; import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema'; import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema';
import { NextAPI } from '@/service/middle/entry';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { async function handler(
try { req: NextApiRequest,
await connectToDatabase(); res: NextApiResponse
const { ): Promise<PagingData<AppLogsListItemType>> {
pageNum = 1, const {
pageSize = 20, pageNum = 1,
appId, pageSize = 20,
dateStart = addDays(new Date(), -7), appId,
dateEnd = new Date() dateStart = addDays(new Date(), -7),
} = req.body as GetAppChatLogsParams; dateEnd = new Date()
} = req.body as GetAppChatLogsParams;
if (!appId) { if (!appId) {
throw new Error('缺少参数'); throw new Error('缺少参数');
}
// 凭证校验
const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' });
const where = {
teamId: new Types.ObjectId(teamId),
appId: new Types.ObjectId(appId),
updateTime: {
$gte: new Date(dateStart),
$lte: new Date(dateEnd)
} }
};
// 凭证校验 const [data, total] = await Promise.all([
const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' }); MongoChat.aggregate([
{ $match: where },
const where = { {
teamId: new Types.ObjectId(teamId), $sort: {
appId: new Types.ObjectId(appId), userBadFeedbackCount: -1,
updateTime: { userGoodFeedbackCount: -1,
$gte: new Date(dateStart), customFeedbacksCount: -1,
$lte: new Date(dateEnd) updateTime: -1
} }
}; },
{ $skip: (pageNum - 1) * pageSize },
const [data, total] = await Promise.all([ { $limit: pageSize },
MongoChat.aggregate([ {
{ $match: where }, $lookup: {
{ from: ChatItemCollectionName,
$sort: { let: { chatId: '$chatId' },
userBadFeedbackCount: -1, pipeline: [
userGoodFeedbackCount: -1, {
customFeedbacksCount: -1, $match: {
updateTime: -1 $expr: {
} $and: [
}, { $eq: ['$appId', new Types.ObjectId(appId)] },
{ $skip: (pageNum - 1) * pageSize }, { $eq: ['$chatId', '$$chatId'] }
{ $limit: pageSize }, ]
{
$lookup: {
from: ChatItemCollectionName,
let: { chatId: '$chatId' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$appId', new Types.ObjectId(appId)] },
{ $eq: ['$chatId', '$$chatId'] }
]
}
}
},
{
$project: {
userGoodFeedback: 1,
userBadFeedback: 1,
customFeedbacks: 1,
adminFeedback: 1
}
}
],
as: 'chatitems'
}
},
{
$addFields: {
userGoodFeedbackCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.userGoodFeedback', false] }
} }
} }
}, },
userBadFeedbackCount: { {
$size: { $project: {
$filter: { userGoodFeedback: 1,
input: '$chatitems', userBadFeedback: 1,
as: 'item', customFeedbacks: 1,
cond: { $ifNull: ['$$item.userBadFeedback', false] } adminFeedback: 1
}
} }
}, }
customFeedbacksCount: { ],
$size: { as: 'chatitems'
$filter: { }
input: '$chatitems', },
as: 'item', {
cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] } $addFields: {
} userGoodFeedbackCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.userGoodFeedback', false] }
} }
}, }
markCount: { },
$size: { userBadFeedbackCount: {
$filter: { $size: {
input: '$chatitems', $filter: {
as: 'item', input: '$chatitems',
cond: { $ifNull: ['$$item.adminFeedback', false] } as: 'item',
} cond: { $ifNull: ['$$item.userBadFeedback', false] }
}
}
},
customFeedbacksCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] }
}
}
},
markCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.adminFeedback', false] }
} }
} }
} }
},
{
$project: {
_id: 1,
id: '$chatId',
title: 1,
source: 1,
time: '$updateTime',
messageCount: { $size: '$chatitems' },
userGoodFeedbackCount: 1,
userBadFeedbackCount: 1,
customFeedbacksCount: 1,
markCount: 1
}
} }
]), },
MongoChat.countDocuments(where) {
]); $project: {
_id: 1,
jsonRes<PagingData<AppLogsListItemType>>(res, { id: '$chatId',
data: { title: 1,
pageNum, source: 1,
pageSize, time: '$updateTime',
data, messageCount: { $size: '$chatitems' },
total userGoodFeedbackCount: 1,
userBadFeedbackCount: 1,
customFeedbacksCount: 1,
markCount: 1
}
} }
}); ]),
} catch (error) { MongoChat.countDocuments(where)
jsonRes(res, { ]);
code: 500,
error return {
}); pageNum,
} pageSize,
data,
total
};
} }
export default NextAPI(handler);

View File

@ -1,38 +1,30 @@
import type { NextApiRequest, NextApiResponse } from 'next'; 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 { MongoApp } from '@fastgpt/service/core/app/schema';
import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
import { AppListItemType } from '@fastgpt/global/core/app/type'; import { AppListItemType } from '@fastgpt/global/core/app/type';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<AppListItemType[]> {
try { // 凭证校验
await connectToDatabase(); const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true });
// 凭证校验
const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true });
// 根据 userId 获取模型信息 // 根据 userId 获取模型信息
const myApps = await MongoApp.find( const myApps = await MongoApp.find(
{ ...mongoRPermission({ teamId, tmbId, role }) }, { ...mongoRPermission({ teamId, tmbId, role }) },
'_id avatar name intro tmbId permission' '_id avatar name intro tmbId permission'
).sort({ ).sort({
updateTime: -1 updateTime: -1
}); });
jsonRes<AppListItemType[]>(res, {
data: myApps.map((app) => ({ return myApps.map((app) => ({
_id: app._id, _id: app._id,
avatar: app.avatar, avatar: app.avatar,
name: app.name, name: app.name,
intro: app.intro, intro: app.intro,
isOwner: teamOwner || String(app.tmbId) === tmbId, isOwner: teamOwner || String(app.tmbId) === tmbId,
permission: app.permission permission: app.permission
})) }));
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
} }
export default NextAPI(handler);

View File

@ -2,106 +2,50 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { MongoApp } from '@fastgpt/service/core/app/schema'; 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 { 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 { 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { name, avatar, type, intro, nodes, edges, permission, teamTags } =
await connectToDatabase(); req.body as AppUpdateParams;
const { const { appId } = req.query as { appId: string };
name,
avatar,
type,
intro,
modules: nodes,
edges,
permission,
teamTags
} = req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
if (!appId) { if (!appId) {
throw new Error('appId is empty'); throw new Error('appId is empty');
}
// 凭证校验
await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' });
// format nodes data
// 1. dataset search limit, less than model quoteMaxToken
if (nodes) {
let maxTokens = 3000;
nodes.forEach((item) => {
if (
item.flowNodeType === FlowNodeTypeEnum.chatNode ||
item.flowNodeType === FlowNodeTypeEnum.tools
) {
const model =
item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
const quoteMaxToken = chatModel.quoteMaxToken || 3000;
maxTokens = Math.max(maxTokens, quoteMaxToken);
}
});
nodes.forEach((item) => {
if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
item.inputs.forEach((input) => {
if (input.key === NodeInputKeyEnum.datasetMaxTokens) {
const val = input.value as number;
if (val > maxTokens) {
input.value = maxTokens;
}
}
});
}
});
}
// 2. get schedule plan
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(nodes || []));
// 更新模型
await MongoApp.updateOne(
{
_id: appId
},
{
name,
type,
avatar,
intro,
permission,
version: 'v2',
teamTags: teamTags,
...(nodes && {
modules: nodes
}),
...(edges && {
edges
}),
scheduledTriggerConfig,
scheduledTriggerNextTime: scheduledTriggerConfig
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
: null
}
);
getScheduleTriggerApp();
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
} }
// 凭证校验
await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' });
// format nodes data
// 1. dataset search limit, less than model quoteMaxToken
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
// 更新模型
await MongoApp.updateOne(
{
_id: appId
},
{
name,
type,
avatar,
intro,
permission,
version: 'v2',
...(teamTags && teamTags),
...(formatNodes && {
modules: formatNodes
}),
...(edges && {
edges
})
}
);
} }
export default NextAPI(handler);

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 { getChatItems } from '@fastgpt/service/core/chat/controller';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@ -40,14 +41,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
} }
// get app and history // get app and history
const { history } = await getChatItems({ const [{ history }, { nodes }] = await Promise.all([
appId, getChatItems({
chatId, appId,
limit: 30, chatId,
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ limit: 30,
DispatchNodeResponseKeyEnum.nodeResponse field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}` DispatchNodeResponseKeyEnum.nodeResponse
}); } ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`
}),
getAppLatestVersion(app._id, app)
]);
jsonRes<InitChatResponse>(res, { jsonRes<InitChatResponse>(res, {
data: { data: {
@ -58,8 +62,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
variables: chat?.variables || {}, variables: chat?.variables || {},
history, history,
app: { app: {
userGuideModule: getGuideModule(app.modules), userGuideModule: getGuideModule(nodes),
chatModels: getChatModelNameListByModules(app.modules), chatModels: getChatModelNameListByModules(nodes),
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar,
intro: app.intro 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 { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@ -40,14 +41,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw new Error(ChatErrEnum.unAuthChat); throw new Error(ChatErrEnum.unAuthChat);
} }
const { history } = await getChatItems({ const [{ history }, { nodes }] = await Promise.all([
appId: app._id, getChatItems({
chatId, appId: app._id,
limit: 30, chatId,
field: `dataId obj value userGoodFeedback userBadFeedback ${ limit: 30,
shareChat.responseDetail ? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` : '' field: `dataId obj value userGoodFeedback userBadFeedback ${
} ` shareChat.responseDetail
}); ? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
: ''
} `
}),
getAppLatestVersion(app._id, app)
]);
// pick share response field // pick share response field
history.forEach((item) => { history.forEach((item) => {
@ -66,8 +72,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
variables: chat?.variables || {}, variables: chat?.variables || {},
history, history,
app: { app: {
userGuideModule: getGuideModule(app.modules), userGuideModule: getGuideModule(nodes),
chatModels: getChatModelNameListByModules(app.modules), chatModels: getChatModelNameListByModules(nodes),
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar,
intro: app.intro 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 { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils'; import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@ -46,12 +47,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
} }
// get app and history // get app and history
const { history } = await getChatItems({ const [{ history }, { nodes }] = await Promise.all([
appId, getChatItems({
chatId, appId,
limit: 30, chatId,
field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}` limit: 30,
}); field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
}),
getAppLatestVersion(app._id, app)
]);
// pick share response field // pick share response field
history.forEach((item) => { history.forEach((item) => {
@ -69,8 +73,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
variables: chat?.variables || {}, variables: chat?.variables || {},
history, history,
app: { app: {
userGuideModule: getGuideModule(app.modules), userGuideModule: getGuideModule(nodes),
chatModels: getChatModelNameListByModules(app.modules), chatModels: getChatModelNameListByModules(nodes),
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar,
intro: app.intro intro: app.intro

View File

@ -1,40 +1,32 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; 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 { authDatasetData } from '@/service/support/permission/auth/dataset';
import { deleteDatasetData } from '@/service/core/dataset/data/controller'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { id: dataId } = req.query as {
await connectToDatabase(); id: string;
const { id: dataId } = req.query as { };
id: string;
};
if (!dataId) { if (!dataId) {
throw new Error('dataId is required'); throw new Error('dataId is required');
}
// 凭证校验
const { teamId, datasetData } = await authDatasetData({
req,
authToken: true,
authApiKey: true,
dataId,
per: 'w'
});
await deleteDatasetData(datasetData);
jsonRes(res, {
data: 'success'
});
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
error: err
});
} }
});
// 凭证校验
const { teamId, datasetData } = await authDatasetData({
req,
authToken: true,
authApiKey: true,
dataId,
per: 'w'
});
await deleteDatasetData(datasetData);
jsonRes(res, {
data: 'success'
});
}
export default NextAPI(handler);

View File

@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authDatasetData } from '@/service/support/permission/auth/dataset'; import { authDatasetData } from '@/service/support/permission/auth/dataset';
import { NextAPI } from '@/service/middle/entry';
export type Response = { export type Response = {
id: string; id: string;
@ -10,29 +11,23 @@ export type Response = {
source: string; source: string;
}; };
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { id: dataId } = req.query as {
await connectToDatabase(); id: string;
const { id: dataId } = req.query as { };
id: string;
};
// 凭证校验 // 凭证校验
const { datasetData } = await authDatasetData({ const { datasetData } = await authDatasetData({
req, req,
authToken: true, authToken: true,
authApiKey: true, authApiKey: true,
dataId, dataId,
per: 'r' per: 'r'
}); });
jsonRes(res, { jsonRes(res, {
data: datasetData 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 type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { countPromptTokens } from '@fastgpt/service/common/string/tiktoken/index'; import { countPromptTokens } from '@fastgpt/service/common/string/tiktoken/index';
import { getVectorModel } from '@fastgpt/service/core/ai/model'; import { getVectorModel } from '@fastgpt/service/core/ai/model';
import { hasSameValue } from '@/service/core/dataset/data/utils'; import { hasSameValue } from '@/service/core/dataset/data/utils';
@ -16,92 +15,87 @@ import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { InsertOneDatasetDataProps } from '@/global/core/dataset/api'; import { InsertOneDatasetDataProps } from '@/global/core/dataset/api';
import { simpleText } from '@fastgpt/global/common/string/tools'; import { simpleText } from '@fastgpt/global/common/string/tools';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { collectionId, q, a, indexes } = req.body as InsertOneDatasetDataProps;
await connectToDatabase();
const { collectionId, q, a, indexes } = req.body as InsertOneDatasetDataProps;
if (!q) { if (!q) {
throw new Error('q is required'); throw new Error('q is required');
}
if (!collectionId) {
throw new Error('collectionId is required');
}
// 凭证校验
const { teamId, tmbId } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
collectionId,
per: 'w'
});
await checkDatasetLimit({
teamId,
insertLen: 1
});
// auth collection and get dataset
const [
{
datasetId: { _id: datasetId, vectorModel }
}
] = await Promise.all([getCollectionWithDataset(collectionId)]);
// format data
const formatQ = simpleText(q);
const formatA = simpleText(a);
const formatIndexes = indexes?.map((item) => ({
...item,
text: simpleText(item.text)
}));
// token check
const token = await countPromptTokens(formatQ + formatA, '');
const vectorModelData = getVectorModel(vectorModel);
if (token > vectorModelData.maxToken) {
return Promise.reject('Q Over Tokens');
}
// Duplicate data check
await hasSameValue({
teamId,
datasetId,
collectionId,
q: formatQ,
a: formatA
});
const { insertId, tokens } = await insertData2Dataset({
teamId,
tmbId,
datasetId,
collectionId,
q: formatQ,
a: formatA,
chunkIndex: 0,
model: vectorModelData.model,
indexes: formatIndexes
});
pushGenerateVectorUsage({
teamId,
tmbId,
tokens,
model: vectorModelData.model
});
jsonRes<string>(res, {
data: insertId
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
} }
});
if (!collectionId) {
throw new Error('collectionId is required');
}
// 凭证校验
const { teamId, tmbId } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
collectionId,
per: 'w'
});
await checkDatasetLimit({
teamId,
insertLen: 1
});
// auth collection and get dataset
const [
{
datasetId: { _id: datasetId, vectorModel }
}
] = await Promise.all([getCollectionWithDataset(collectionId)]);
// format data
const formatQ = simpleText(q);
const formatA = simpleText(a);
const formatIndexes = indexes?.map((item) => ({
...item,
text: simpleText(item.text)
}));
// token check
const token = await countPromptTokens(formatQ + formatA, '');
const vectorModelData = getVectorModel(vectorModel);
if (token > vectorModelData.maxToken) {
return Promise.reject('Q Over Tokens');
}
// Duplicate data check
await hasSameValue({
teamId,
datasetId,
collectionId,
q: formatQ,
a: formatA
});
const { insertId, tokens } = await insertData2Dataset({
teamId,
tmbId,
datasetId,
collectionId,
q: formatQ,
a: formatA,
chunkIndex: 0,
model: vectorModelData.model,
indexes: formatIndexes
});
pushGenerateVectorUsage({
teamId,
tmbId,
tokens,
model: vectorModelData.model
});
jsonRes<string>(res, {
data: insertId
});
}
export default NextAPI(handler);

View File

@ -7,62 +7,57 @@ import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { PagingData } from '@/types'; import { PagingData } from '@/types';
import { replaceRegChars } from '@fastgpt/global/common/string/tools'; import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import { NextAPI } from '@/service/middle/entry';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { let {
await connectToDatabase(); pageNum = 1,
let { pageSize = 10,
pageNum = 1, searchText = '',
pageSize = 10, collectionId
searchText = '', } = req.body as GetDatasetDataListProps;
collectionId
} = req.body as GetDatasetDataListProps;
pageSize = Math.min(pageSize, 30); pageSize = Math.min(pageSize, 30);
// 凭证校验 // 凭证校验
const { teamId, collection } = await authDatasetCollection({ const { teamId, collection } = await authDatasetCollection({
req, req,
authToken: true, authToken: true,
authApiKey: true, authApiKey: true,
collectionId, collectionId,
per: 'r' per: 'r'
}); });
searchText = replaceRegChars(searchText).replace(/'/g, ''); searchText = replaceRegChars(searchText).replace(/'/g, '');
const match = { const match = {
teamId, teamId,
datasetId: collection.datasetId._id, datasetId: collection.datasetId._id,
collectionId, collectionId,
...(searchText ...(searchText
? { ? {
$or: [{ q: new RegExp(searchText, 'i') }, { a: new RegExp(searchText, 'i') }] $or: [{ q: new RegExp(searchText, 'i') }, { a: new RegExp(searchText, 'i') }]
} }
: {}) : {})
}; };
const [data, total] = await Promise.all([ const [data, total] = await Promise.all([
MongoDatasetData.find(match, '_id datasetId collectionId q a chunkIndex') MongoDatasetData.find(match, '_id datasetId collectionId q a chunkIndex')
.sort({ chunkIndex: 1, updateTime: -1 }) .sort({ chunkIndex: 1, updateTime: -1 })
.skip((pageNum - 1) * pageSize) .skip((pageNum - 1) * pageSize)
.limit(pageSize) .limit(pageSize)
.lean(), .lean(),
MongoDatasetData.countDocuments(match) MongoDatasetData.countDocuments(match)
]); ]);
jsonRes<PagingData<DatasetDataListItemType>>(res, { jsonRes<PagingData<DatasetDataListItemType>>(res, {
data: { data: {
pageNum, pageNum,
pageSize, pageSize,
data, data,
total 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 type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import type { import type {
PushDatasetDataProps, PushDatasetDataProps,
PushDatasetDataResponse PushDatasetDataResponse
@ -11,53 +10,48 @@ import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils'; import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const body = req.body as PushDatasetDataProps;
await connectToDatabase(); const { collectionId, data } = body;
const body = req.body as PushDatasetDataProps;
const { collectionId, data } = body;
if (!collectionId || !Array.isArray(data)) { if (!collectionId || !Array.isArray(data)) {
throw new Error('collectionId or data is empty'); throw new Error('collectionId or data is empty');
}
if (data.length > 200) {
throw new Error('Data is too long, max 200');
}
// 凭证校验
const { teamId, tmbId, collection } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
collectionId,
per: 'w'
});
// auth dataset limit
await checkDatasetLimit({
teamId,
insertLen: predictDataLimitLength(collection.trainingType, data)
});
jsonRes<PushDatasetDataResponse>(res, {
data: await pushDataListToTrainingQueue({
...body,
teamId,
tmbId,
datasetId: collection.datasetId._id,
agentModel: collection.datasetId.agentModel,
vectorModel: collection.datasetId.vectorModel
})
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
} }
});
if (data.length > 200) {
throw new Error('Data is too long, max 200');
}
// 凭证校验
const { teamId, tmbId, collection } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
collectionId,
per: 'w'
});
// auth dataset limit
await checkDatasetLimit({
teamId,
insertLen: predictDataLimitLength(collection.trainingType, data)
});
jsonRes<PushDatasetDataResponse>(res, {
data: await pushDataListToTrainingQueue({
...body,
teamId,
tmbId,
datasetId: collection.datasetId._id,
agentModel: collection.datasetId.agentModel,
vectorModel: collection.datasetId.vectorModel
})
});
}
export default NextAPI(handler);
export const config = { export const config = {
api: { api: {

View File

@ -1,59 +1,53 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { updateData2Dataset } from '@/service/core/dataset/data/controller'; import { updateData2Dataset } from '@/service/core/dataset/data/controller';
import { authDatasetData } from '@/service/support/permission/auth/dataset'; import { authDatasetData } from '@/service/support/permission/auth/dataset';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push'; import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { UpdateDatasetDataProps } from '@/global/core/dataset/api'; import { UpdateDatasetDataProps } from '@/global/core/dataset/api';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit'; 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>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { id, q = '', a, indexes = [] } = req.body as UpdateDatasetDataProps;
await connectToDatabase();
const { id, q = '', a, indexes = [] } = req.body as UpdateDatasetDataProps;
// auth data permission // auth data permission
const { const {
collection: { collection: {
datasetId: { vectorModel } datasetId: { vectorModel }
}, },
teamId, teamId,
tmbId tmbId
} = await authDatasetData({ } = await authDatasetData({
req, req,
authToken: true, authToken: true,
authApiKey: true, authApiKey: true,
dataId: id, dataId: id,
per: 'w' per: 'w'
}); });
// auth team balance // auth team balance
await checkDatasetLimit({ await checkDatasetLimit({
teamId, teamId,
insertLen: 1 insertLen: 1
}); });
const { tokens } = await updateData2Dataset({ const { tokens } = await updateData2Dataset({
dataId: id, dataId: id,
q, q,
a, a,
indexes, indexes,
model: vectorModel model: vectorModel
}); });
pushGenerateVectorUsage({ pushGenerateVectorUsage({
teamId, teamId,
tmbId, tmbId,
tokens, tokens,
model: vectorModel model: vectorModel
}); });
jsonRes(res); jsonRes(res);
} catch (err) { }
jsonRes(res, {
code: 500, export default NextAPI(handler);
error: err
});
}
});

View File

@ -1,94 +1,85 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes, responseWriteController } from '@fastgpt/service/common/response'; import { responseWriteController } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { findDatasetAndAllChildren } from '@fastgpt/service/core/dataset/controller'; import { findDatasetAndAllChildren } from '@fastgpt/service/core/dataset/controller';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { import {
checkExportDatasetLimit, checkExportDatasetLimit,
updateExportDatasetLimit updateExportDatasetLimit
} from '@fastgpt/service/support/user/utils'; } from '@fastgpt/service/support/user/utils';
import { NextAPI } from '@/service/middle/entry';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { let { datasetId } = req.query as {
await connectToDatabase(); datasetId: string;
let { datasetId } = req.query as { };
datasetId: string;
};
if (!datasetId || !global.pgClient) { if (!datasetId || !global.pgClient) {
throw new Error('缺少参数'); throw new Error('缺少参数');
}
// 凭证校验
const { teamId } = await authDataset({ req, authToken: true, datasetId, per: 'w' });
await checkExportDatasetLimit({
teamId,
limitMinutes: global.feConfigs?.limit?.exportDatasetLimitMinutes
});
const datasets = await findDatasetAndAllChildren({
teamId,
datasetId,
fields: '_id'
});
res.setHeader('Content-Type', 'text/csv; charset=utf-8;');
res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv; ');
const cursor = MongoDatasetData.find<{
_id: string;
collectionId: { name: string };
q: string;
a: string;
}>(
{
teamId,
datasetId: { $in: datasets.map((d) => d._id) }
},
'q a'
)
.limit(50000)
.cursor();
const write = responseWriteController({
res,
readStream: cursor
});
write(`\uFEFFindex,content`);
cursor.on('data', (doc) => {
const q = doc.q.replace(/"/g, '""') || '';
const a = doc.a.replace(/"/g, '""') || '';
write(`\n"${q}","${a}"`);
});
cursor.on('end', () => {
cursor.close();
res.end();
});
cursor.on('error', (err) => {
addLog.error(`export dataset error`, err);
res.status(500);
res.end();
});
updateExportDatasetLimit(teamId);
} catch (err) {
res.status(500);
addLog.error(`export dataset error`, err);
jsonRes(res, {
code: 500,
error: err
});
} }
});
// 凭证校验
const { teamId } = await authDataset({ req, authToken: true, datasetId, per: 'w' });
await checkExportDatasetLimit({
teamId,
limitMinutes: global.feConfigs?.limit?.exportDatasetLimitMinutes
});
const datasets = await findDatasetAndAllChildren({
teamId,
datasetId,
fields: '_id'
});
res.setHeader('Content-Type', 'text/csv; charset=utf-8;');
res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv; ');
const cursor = MongoDatasetData.find<{
_id: string;
collectionId: { name: string };
q: string;
a: string;
}>(
{
teamId,
datasetId: { $in: datasets.map((d) => d._id) }
},
'q a'
)
.limit(50000)
.cursor();
const write = responseWriteController({
res,
readStream: cursor
});
write(`\uFEFFindex,content`);
cursor.on('data', (doc) => {
const q = doc.q.replace(/"/g, '""') || '';
const a = doc.a.replace(/"/g, '""') || '';
write(`\n"${q}","${a}"`);
});
cursor.on('end', () => {
cursor.close();
res.end();
});
cursor.on('error', (err) => {
addLog.error(`export dataset error`, err);
res.status(500);
res.end();
});
updateExportDatasetLimit(teamId);
}
export default NextAPI(handler);
export const config = { export const config = {
api: { api: {

View File

@ -1,54 +1,48 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d'; import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
import { getVectorModel } from '@fastgpt/service/core/ai/model'; import { getVectorModel } from '@fastgpt/service/core/ai/model';
import { NextAPI } from '@/service/middle/entry';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` };
await connectToDatabase(); // 凭证校验
const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` }; const { teamId, tmbId, teamOwner, role, canWrite } = await authUserRole({
// 凭证校验 req,
const { teamId, tmbId, teamOwner, role, canWrite } = await authUserRole({ authToken: true,
req, authApiKey: true
authToken: true, });
authApiKey: true
});
const datasets = await MongoDataset.find({ const datasets = await MongoDataset.find({
...mongoRPermission({ teamId, tmbId, role }), ...mongoRPermission({ teamId, tmbId, role }),
...(parentId !== undefined && { parentId: parentId || null }), ...(parentId !== undefined && { parentId: parentId || null }),
...(type && { type }) ...(type && { type })
}) })
.sort({ updateTime: -1 }) .sort({ updateTime: -1 })
.lean(); .lean();
const data = await Promise.all( const data = await Promise.all(
datasets.map<DatasetListItemType>((item) => ({ datasets.map<DatasetListItemType>((item) => ({
_id: item._id, _id: item._id,
parentId: item.parentId, parentId: item.parentId,
avatar: item.avatar, avatar: item.avatar,
name: item.name, name: item.name,
intro: item.intro, intro: item.intro,
type: item.type, type: item.type,
permission: item.permission, permission: item.permission,
canWrite, canWrite,
isOwner: teamOwner || String(item.tmbId) === tmbId, isOwner: teamOwner || String(item.tmbId) === tmbId,
vectorModel: getVectorModel(item.vectorModel) vectorModel: getVectorModel(item.vectorModel)
})) }))
); );
jsonRes<DatasetListItemType[]>(res, { jsonRes<DatasetListItemType[]>(res, {
data 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 type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; 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 type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/api.d';
import { connectToDatabase } from '@/service/mongo';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push'; import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { searchDatasetData } from '@fastgpt/service/core/dataset/search/controller'; import { searchDatasetData } from '@fastgpt/service/core/dataset/search/controller';
@ -14,95 +12,90 @@ import {
checkTeamAIPoints, checkTeamAIPoints,
checkTeamReRankPermission checkTeamReRankPermission
} from '@fastgpt/service/support/permission/teamLimit'; } from '@fastgpt/service/support/permission/teamLimit';
import { NextAPI } from '@/service/middle/entry';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const {
await connectToDatabase(); datasetId,
const { text,
datasetId, limit = 1500,
text, similarity,
limit = 1500, searchMode,
similarity, usingReRank,
searchMode,
usingReRank,
datasetSearchUsingExtensionQuery = false, datasetSearchUsingExtensionQuery = false,
datasetSearchExtensionModel, datasetSearchExtensionModel,
datasetSearchExtensionBg = '' datasetSearchExtensionBg = ''
} = req.body as SearchTestProps; } = req.body as SearchTestProps;
if (!datasetId || !text) { if (!datasetId || !text) {
throw new Error('缺少参数'); throw new Error('缺少参数');
} }
const start = Date.now(); const start = Date.now();
// auth dataset role // auth dataset role
const { dataset, teamId, tmbId, apikey } = await authDataset({ const { dataset, teamId, tmbId, apikey } = await authDataset({
req, req,
authToken: true, authToken: true,
authApiKey: true, authApiKey: true,
datasetId, datasetId,
per: 'r' per: 'r'
}); });
// auth balance // auth balance
await checkTeamAIPoints(teamId); await checkTeamAIPoints(teamId);
// query extension // query extension
const extensionModel = const extensionModel =
datasetSearchUsingExtensionQuery && datasetSearchExtensionModel datasetSearchUsingExtensionQuery && datasetSearchExtensionModel
? getLLMModel(datasetSearchExtensionModel) ? getLLMModel(datasetSearchExtensionModel)
: undefined; : undefined;
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({ const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
query: text, query: text,
extensionModel, extensionModel,
extensionBg: datasetSearchExtensionBg extensionBg: datasetSearchExtensionBg
}); });
const { searchRes, tokens, ...result } = await searchDatasetData({ const { searchRes, tokens, ...result } = await searchDatasetData({
teamId, teamId,
reRankQuery: rewriteQuery, reRankQuery: rewriteQuery,
queries: concatQueries, queries: concatQueries,
model: dataset.vectorModel, model: dataset.vectorModel,
limit: Math.min(limit, 20000), limit: Math.min(limit, 20000),
similarity, similarity,
datasetIds: [datasetId], datasetIds: [datasetId],
searchMode, searchMode,
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId)) usingReRank: usingReRank && (await checkTeamReRankPermission(teamId))
}); });
// push bill // push bill
const { totalPoints } = pushGenerateVectorUsage({ const { totalPoints } = pushGenerateVectorUsage({
teamId, teamId,
tmbId, tmbId,
tokens, tokens,
model: dataset.vectorModel, model: dataset.vectorModel,
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt, source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
...(aiExtensionResult && ...(aiExtensionResult &&
extensionModel && { extensionModel && {
extensionModel: extensionModel.name, extensionModel: extensionModel.name,
extensionTokens: aiExtensionResult.tokens extensionTokens: aiExtensionResult.tokens
}) })
}); });
if (apikey) { if (apikey) {
updateApiKeyUsage({ updateApiKeyUsage({
apikey, apikey,
totalPoints: totalPoints totalPoints: totalPoints
});
}
jsonRes<SearchTestResponse>(res, {
data: {
list: searchRes,
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
usingQueryExtension: !!aiExtensionResult,
...result
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
}); });
} }
});
jsonRes<SearchTestResponse>(res, {
data: {
list: searchRes,
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
usingQueryExtension: !!aiExtensionResult,
...result
}
});
}
export default NextAPI(handler);

View File

@ -1,6 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { getUploadModel } from '@fastgpt/service/common/file/multer'; import { getUploadModel } from '@fastgpt/service/common/file/multer';
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils'; import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
import fs from 'fs'; 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 { MongoApp } from '@fastgpt/service/core/app/schema';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils'; import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { NextAPI } from '@/service/middle/entry';
const upload = getUploadModel({ const upload = getUploadModel({
maxSize: 2 maxSize: 2
}); });
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
let filePaths: string[] = []; let filePaths: string[] = [];
try { try {
@ -81,7 +81,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
} }
removeFilesByPaths(filePaths); removeFilesByPaths(filePaths);
}); }
export default NextAPI(handler);
export const config = { export const config = {
api: { 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 { authCert } from '@fastgpt/service/support/permission/auth/common';
import { sseErrRes, jsonRes } from '@fastgpt/service/common/response'; import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
import { addLog } from '@fastgpt/service/common/system/log'; 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 { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch'; 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 { dispatchWorkFlowV1 } from '@fastgpt/service/core/workflow/dispatchV1';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils'; 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 = { type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
@ -73,7 +75,7 @@ type AuthResponseType = {
outLinkUserId?: string; outLinkUserId?: string;
}; };
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { async function handler(req: NextApiRequest, res: NextApiResponse) {
res.on('close', () => { res.on('close', () => {
res.end(); res.end();
}); });
@ -163,13 +165,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}); });
})(); })();
// get and concat history // 1. get and concat history; 2. get app workflow
const { history } = await getChatItems({ const [{ history }, { nodes, edges }] = await Promise.all([
appId: app._id, getChatItems({
chatId, appId: app._id,
limit: 30, chatId,
field: `dataId obj value` limit: 30,
}); field: `dataId obj value`
}),
getAppLatestVersion(app._id, app)
]);
const concatHistories = history.concat(chatMessages); const concatHistories = history.concat(chatMessages);
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId; 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), appId: String(app._id),
chatId, chatId,
responseChatItemId, responseChatItemId,
runtimeNodes: storeNodes2RuntimeNodes(app.modules, getDefaultEntryNodeIds(app.modules)), runtimeNodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
runtimeEdges: initWorkflowEdgeStatus(app.edges), runtimeEdges: initWorkflowEdgeStatus(edges),
variables: { variables: {
...variables, ...variables,
userChatInput: text userChatInput: text
@ -349,7 +354,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}); });
} }
} }
}); }
export default NextAPI(handler);
export const config = { export const config = {
api: { api: {

View File

@ -1,7 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; 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 { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { getVectorsByText } from '@fastgpt/service/core/ai/embedding'; import { getVectorsByText } from '@fastgpt/service/core/ai/embedding';
@ -19,7 +18,7 @@ type Props = {
type: `${EmbeddingTypeEnm}`; type: `${EmbeddingTypeEnm}`;
}; };
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
let { input, model, billId, type } = req.body as Props; let { input, model, billId, type } = req.body as Props;
await connectToDatabase(); await connectToDatabase();
@ -80,4 +79,4 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
error: err error: err
}); });
} }
}); }

View File

@ -24,6 +24,10 @@ import {
checkWorkflowNodeAndConnection, checkWorkflowNodeAndConnection,
filterSensitiveNodesData filterSensitiveNodesData
} from '@/web/core/workflow/utils'; } 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')); const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
@ -50,13 +54,14 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const { toast } = useToast(); const { toast } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const { openConfirm: openConfirmOut, ConfirmModal } = useConfirm({ const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({
content: t('core.app.edit.Out Ad Edit') content: t('core.app.Publish Confirm')
}); });
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure(); const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { updateAppDetail } = useAppStore(); const { publishApp, updateAppDetail } = useAppStore();
const { edges, onUpdateNodeError } = useFlowProviderStore(); const { edges, onUpdateNodeError } = useFlowProviderStore();
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save'));
const flowData2StoreDataAndCheck = useCallback(async () => { const flowData2StoreDataAndCheck = useCallback(async () => {
const { nodes } = await getWorkflowStore(); const { nodes } = await getWorkflowStore();
@ -75,48 +80,95 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
} }
}, [edges, onUpdateNodeError, t, toast]); }, [edges, onUpdateNodeError, t, toast]);
const onclickSave = useCallback( const onclickSave = useCallback(async () => {
async ({ nodes, edges }: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { const { nodes } = await getWorkflowStore();
setIsSaving(true);
if (nodes.length === 0) return null;
setIsSaving(true);
const storeWorkflow = flowNode2StoreNodes({ nodes, edges });
try {
await updateAppDetail(app._id, {
...storeWorkflow,
type: AppTypeEnum.advanced,
//@ts-ignore
version: 'v2'
});
setSaveLabel(
t('core.app.Auto Save time', {
time: formatTime2HM()
})
);
ChatTestRef.current?.resetChatTest();
} catch (error) {}
setIsSaving(false);
return null;
}, [updateAppDetail, app._id, edges, ChatTestRef, t]);
const onclickPublish = useCallback(async () => {
setIsSaving(true);
const data = await flowData2StoreDataAndCheck();
if (data) {
try { try {
await updateAppDetail(app._id, { await publishApp(app._id, {
modules: nodes, ...data,
edges,
type: AppTypeEnum.advanced, type: AppTypeEnum.advanced,
permission: undefined,
//@ts-ignore //@ts-ignore
version: 'v2' version: 'v2'
}); });
toast({ toast({
status: 'success', status: 'success',
title: t('common.Save Success') title: t('core.app.Publish Success')
}); });
ChatTestRef.current?.resetChatTest(); ChatTestRef.current?.resetChatTest();
} catch (error) { } catch (error) {
toast({ toast({
status: 'warning', status: 'warning',
title: getErrText(error, t('common.Save Failed')) title: getErrText(error, t('core.app.Publish Failed'))
}); });
} }
setIsSaving(false); }
},
[ChatTestRef, app._id, t, toast, updateAppDetail] setIsSaving(false);
); }, [flowData2StoreDataAndCheck, publishApp, app._id, toast, t, ChatTestRef]);
const saveAndBack = useCallback(async () => { const saveAndBack = useCallback(async () => {
try { try {
const data = await flowData2StoreDataAndCheck(); await onclickSave();
if (data) {
await onclickSave(data);
}
onClose(); onClose();
} catch (error) { } catch (error) {}
toast({ }, [onClose, onclickSave]);
status: 'warning',
title: getErrText(error) const onExportWorkflow = useCallback(async () => {
}); const data = await flowData2StoreDataAndCheck();
if (data) {
copyData(
JSON.stringify(
{
nodes: filterSensitiveNodesData(data.nodes),
edges: data.edges
},
null,
2
),
t('app.Export Config Successful')
);
} }
}, [flowData2StoreDataAndCheck, onClose, onclickSave, toast]); }, [copyData, flowData2StoreDataAndCheck, t]);
useBeforeunload({
callback: onclickSave,
tip: t('core.common.tip.leave page')
});
useQuery(['autoSave'], onclickSave, {
refetchInterval: 20 * 1000,
enabled: !!app._id
});
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
@ -139,12 +191,29 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
variant={'whiteBase'} variant={'whiteBase'}
aria-label={''} aria-label={''}
isLoading={isSaving} isLoading={isSaving}
onClick={openConfirmOut(saveAndBack, onClose)} onClick={saveAndBack}
/> />
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}> <Box ml={[3, 5]}>
{app.name} <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>
<Box flex={1} />
<MyMenu <MyMenu
Button={ Button={
<IconButton <IconButton
@ -164,22 +233,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
{ {
label: t('app.Export Configs'), label: t('app.Export Configs'),
icon: 'export', icon: 'export',
onClick: async () => { onClick: onExportWorkflow
const data = await flowData2StoreDataAndCheck();
if (data) {
copyData(
JSON.stringify(
{
nodes: filterSensitiveNodesData(data.nodes),
edges: data.edges
},
null,
2
),
t('app.Export Config Successful')
);
}
}
} }
]} ]}
/> />
@ -202,34 +256,27 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<Button <Button
size={'sm'} size={'sm'}
isLoading={isSaving} isLoading={isSaving}
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />} leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
onClick={async () => { onClick={openConfigPublish(onclickPublish)}
const modules = await flowData2StoreDataAndCheck();
if (modules) {
onclickSave(modules);
}
}}
> >
{t('common.Save')} {t('core.app.Publish')}
</Button> </Button>
</Flex> </Flex>
<ConfirmModal <ConfirmModal confirmText={t('core.app.Publish')} />
closeText={t('core.app.edit.UnSave')}
confirmText={t('core.app.edit.Save and out')}
/>
</> </>
); );
}, [ }, [
ConfirmModal, ConfirmModal,
app.name, app.name,
copyData,
flowData2StoreDataAndCheck, flowData2StoreDataAndCheck,
isSaving, isSaving,
onClose, onExportWorkflow,
onOpenImport, onOpenImport,
onclickPublish,
onclickSave, onclickSave,
openConfirmOut, openConfigPublish,
saveAndBack, saveAndBack,
saveLabel,
setWorkflowTestData, setWorkflowTestData,
t, t,
theme.borders.base theme.borders.base

View File

@ -19,17 +19,15 @@ const Render = ({ app, onClose }: Props) => {
const { initData } = useFlowProviderStore(); const { initData } = useFlowProviderStore();
const workflowStringData = JSON.stringify({
nodes: app.modules || [],
edges: app.edges || []
});
useEffect(() => { useEffect(() => {
if (!isV2Workflow) return; if (!isV2Workflow) return;
initData( initData(JSON.parse(workflowStringData));
JSON.parse( }, [isV2Workflow, initData, workflowStringData]);
JSON.stringify({
nodes: app.modules || [],
edges: app.edges || []
})
)
);
}, [isV2Workflow, app.edges, app.modules]);
useEffect(() => { useEffect(() => {
if (!isV2Workflow) { if (!isV2Workflow) {
@ -37,7 +35,7 @@ const Render = ({ app, onClose }: Props) => {
initData(JSON.parse(JSON.stringify(v1Workflow2V2((app.modules || []) as any)))); initData(JSON.parse(JSON.stringify(v1Workflow2V2((app.modules || []) as any))));
})(); })();
} }
}, [app.modules, isV2Workflow, openConfirm]); }, [app.modules, initData, isV2Workflow, openConfirm]);
const memoRender = useMemo(() => { const memoRender = useMemo(() => {
return <Flow Header={<Header app={app} onClose={onClose} />} />; return <Flow Header={<Header app={app} onClose={onClose} />} />;

View File

@ -60,7 +60,7 @@ const EditForm = ({
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { appDetail, updateAppDetail } = useAppStore(); const { appDetail, publishApp } = useAppStore();
const { loadAllDatasets, allDatasets } = useDatasetStore(); const { loadAllDatasets, allDatasets } = useDatasetStore();
const { isPc, llmModelList } = useSystemStore(); const { isPc, llmModelList } = useSystemStore();
@ -122,11 +122,10 @@ const EditForm = ({
mutationFn: async (data: AppSimpleEditFormType) => { mutationFn: async (data: AppSimpleEditFormType) => {
const { nodes, edges } = form2AppWorkflow(data); const { nodes, edges } = form2AppWorkflow(data);
await updateAppDetail(appDetail._id, { await publishApp(appDetail._id, {
modules: nodes, nodes,
edges, edges,
type: AppTypeEnum.simple, type: AppTypeEnum.simple
permission: undefined
}); });
}, },
successToast: t('common.Save Success'), successToast: t('common.Save Success'),

View File

@ -28,13 +28,13 @@ const TagsEditModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appDetail } = useAppStore(); const { appDetail } = useAppStore();
const { toast } = useToast(); const { toast } = useToast();
const { replaceAppDetail } = useAppStore(); const { updateAppDetail } = useAppStore();
const [selectedTags, setSelectedTags] = useState<string[]>(appDetail?.teamTags || []); const [selectedTags, setSelectedTags] = useState<string[]>(appDetail?.teamTags || []);
// submit config // submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({ const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => { mutationFn: async () => {
await replaceAppDetail(appDetail._id, { await updateAppDetail(appDetail._id, {
teamTags: selectedTags teamTags: selectedTags
}); });
}, },

View File

@ -82,22 +82,6 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]); 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), { useQuery([appId], () => loadAppDetail(appId, true), {
onError(err: any) { onError(err: any) {
toast({ 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 { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type'; import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
@ -7,16 +7,14 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip'; 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 { putUpdatePlugin } from '@/web/core/plugin/api';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import MyMenu from '@fastgpt/web/components/common/MyMenu'; import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { import {
getWorkflowStore, getWorkflowStore,
useFlowProviderStore useFlowProviderStore
} from '@/components/core/workflow/Flow/FlowProvider'; } from '@/components/core/workflow/Flow/FlowProvider';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { import {
checkWorkflowNodeAndConnection, checkWorkflowNodeAndConnection,
filterSensitiveNodesData filterSensitiveNodesData
@ -51,107 +49,109 @@ const Header = ({ plugin, onClose }: Props) => {
}, [edges, onUpdateNodeError, t, toast]); }, [edges, onUpdateNodeError, t, toast]);
const { mutate: onclickSave, isLoading } = useRequest({ const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: ({ nodes, edges }: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { mutationFn: async () => {
return putUpdatePlugin({ const workflow = await flowData2StoreDataAndCheck();
id: plugin._id, if (workflow) {
modules: nodes, await putUpdatePlugin({
edges id: plugin._id,
}); modules: workflow.nodes,
}, edges: workflow.edges
successToast: '保存配置成功', });
errorToast: '保存配置异常' toast({
status: 'success',
title: t('common.Save Success')
});
}
}
}); });
return ( const onCopy = useCallback(async () => {
<> const data = await flowData2StoreDataAndCheck();
<Flex if (data) {
py={3} copyData(
px={[2, 5, 8]} JSON.stringify(
borderBottom={theme.borders.base} {
alignItems={'center'} nodes: filterSensitiveNodesData(data.nodes),
userSelect={'none'} edges: data.edges
> },
<MyTooltip label={t('common.Back')} offset={[10, 10]}> null,
<IconButton 2
size={'smSquare'} ),
icon={<MyIcon name={'common/backLight'} w={'14px'} />} t('app.Export Config Successful')
variant={'whiteBase'} );
aria-label={''} }
onClick={() => { }, [copyData, flowData2StoreDataAndCheck, t]);
onClose();
}}
/>
</MyTooltip>
<Box ml={[3, 5]} fontSize={['md', '2xl']} flex={1}>
{plugin.name}
</Box>
<MyMenu const Render = useMemo(() => {
Button={ return (
<IconButton <>
mr={[3, 5]} <Flex
icon={<MyIcon name={'more'} w={'14px'} p={2} />} py={3}
aria-label={''} px={[2, 5, 8]}
size={'sm'} borderBottom={theme.borders.base}
variant={'whitePrimary'} alignItems={'center'}
/> userSelect={'none'}
}
menuList={[
{ label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport },
{
label: t('app.Export Configs'),
icon: 'export',
onClick: async () => {
const data = await flowData2StoreDataAndCheck();
if (data) {
copyData(
JSON.stringify(
{
nodes: filterSensitiveNodesData(data.nodes),
edges: data.edges
},
null,
2
),
t('app.Export Config Successful')
);
}
}
}
]}
/>
{/* <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);
}
}}
> >
{t('common.Save')} <MyTooltip label={t('common.Back')} offset={[10, 10]}>
</Button> <IconButton
</Flex> size={'smSquare'}
{isOpenImport && <ImportSettings onClose={onCloseImport} />} icon={<MyIcon name={'common/backLight'} w={'14px'} />}
</> variant={'whiteBase'}
); aria-label={''}
onClick={() => {
onClose();
}}
/>
</MyTooltip>
<Box ml={[3, 5]} fontSize={['md', '2xl']} flex={1}>
{plugin.name}
</Box>
<MyMenu
Button={
<IconButton
mr={[3, 5]}
icon={<MyIcon name={'more'} w={'14px'} p={2} />}
aria-label={''}
size={'sm'}
variant={'whitePrimary'}
/>
}
menuList={[
{ label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport },
{
label: t('app.Export Configs'),
icon: 'export',
onClick: onCopy
}
]}
/>
<Button
size={'sm'}
isLoading={isLoading}
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={onclickSave}
>
{t('common.Save')}
</Button>
</Flex>
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
</>
);
}, [
isLoading,
isOpenImport,
onClose,
onCloseImport,
onCopy,
onOpenImport,
onclickSave,
plugin.name,
t,
theme.borders.base
]);
return Render;
}; };
export default React.memo(Header); 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 { useTranslation } from 'next-i18next';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
type Props = { pluginId: string }; type Props = { pluginId: string };
@ -42,18 +43,16 @@ const Render = ({ pluginId }: Props) => {
'检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致许多工作流无法正常排布请重新手动连接工作流。如仍异常可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试无需点击保存点击保存为新版工作流。' '检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致许多工作流无法正常排布请重新手动连接工作流。如仍异常可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试无需点击保存点击保存为新版工作流。'
}); });
const workflowStringData = JSON.stringify({
nodes: pluginDetail?.modules || [],
edges: pluginDetail?.edges || []
});
useEffect(() => { useEffect(() => {
if (isV2Workflow) { if (isV2Workflow) {
initData( initData(JSON.parse(workflowStringData));
JSON.parse(
JSON.stringify({
nodes: pluginDetail?.modules || [],
edges: pluginDetail?.edges || []
})
)
);
} }
}, [isV2Workflow, pluginDetail?.edges, pluginDetail?.modules]); }, [initData, isV2Workflow, workflowStringData]);
useEffect(() => { useEffect(() => {
if (!isV2Workflow && pluginDetail) { if (!isV2Workflow && pluginDetail) {
@ -61,7 +60,11 @@ const Render = ({ pluginId }: Props) => {
initData(JSON.parse(JSON.stringify(v1Workflow2V2((pluginDetail.modules || []) as any)))); 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 ? ( 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 { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d'; 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 { 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) => export const putAppById = (id: string, data: AppUpdateParams) =>
PUT(`/core/app/update?appId=${id}`, data); 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 // =================== chat logs
export const getAppChatLogs = (data: GetAppChatLogsParams) => POST(`/core/app/getChatLogs`, data); export const getAppChatLogs = (data: GetAppChatLogsParams) => POST(`/core/app/getChatLogs`, data);

View File

@ -1,10 +1,12 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; 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 { 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 { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d';
import { PostPublishAppProps } from '@/global/core/app/api';
import { postPublishApp } from '../versionApi';
type State = { type State = {
myApps: AppListItemType[]; myApps: AppListItemType[];
@ -12,7 +14,7 @@ type State = {
appDetail: AppDetailType; appDetail: AppDetailType;
loadAppDetail: (id: string, init?: boolean) => Promise<AppDetailType>; loadAppDetail: (id: string, init?: boolean) => Promise<AppDetailType>;
updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>; updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>;
replaceAppDetail(appId: string, data: AppUpdateParams): Promise<void>; publishApp(appId: string, data: PostPublishAppProps): Promise<void>;
clearAppModules(): void; clearAppModules(): void;
}; };
@ -44,19 +46,22 @@ export const useAppStore = create<State>()(
set((state) => { set((state) => {
state.appDetail = { state.appDetail = {
...state.appDetail, ...state.appDetail,
...data ...data,
modules: data?.nodes || state.appDetail.modules
}; };
}); });
}, },
async replaceAppDetail(appId: string, data: AppUpdateParams) { async publishApp(appId: string, data: PostPublishAppProps) {
await replaceAppById(appId, { ...get().appDetail, ...data }); await postPublishApp(appId, data);
set((state) => { set((state) => {
state.appDetail = { state.appDetail = {
...state.appDetail, ...state.appDetail,
...data ...data,
modules: data?.nodes || state.appDetail.modules
}; };
}); });
}, },
clearAppModules() { clearAppModules() {
set((state) => { set((state) => {
state.appDetail = { 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, FlowNodeTemplateType,
StoreNodeItemType StoreNodeItemType
} from '@fastgpt/global/core/workflow/type'; } 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 { getHandleId, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { LLMModelTypeEnum } from '@fastgpt/global/core/ai/constants'; 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 = { export const FlowValueTypeMap = {
[WorkflowIOValueTypeEnum.string]: { [WorkflowIOValueTypeEnum.string]: {
handlerStyle: { label: 'string',
borderColor: '#36ADEF'
},
label: 'core.module.valueType.string',
value: WorkflowIOValueTypeEnum.string, value: WorkflowIOValueTypeEnum.string,
description: '' description: ''
}, },
[WorkflowIOValueTypeEnum.number]: { [WorkflowIOValueTypeEnum.number]: {
handlerStyle: { label: 'number',
borderColor: '#FB7C3C'
},
label: 'core.module.valueType.number',
value: WorkflowIOValueTypeEnum.number, value: WorkflowIOValueTypeEnum.number,
description: '' description: ''
}, },
[WorkflowIOValueTypeEnum.boolean]: { [WorkflowIOValueTypeEnum.boolean]: {
handlerStyle: { label: 'boolean',
borderColor: '#E7D118'
},
label: 'core.module.valueType.boolean',
value: WorkflowIOValueTypeEnum.boolean, value: WorkflowIOValueTypeEnum.boolean,
description: '' description: ''
}, },
[WorkflowIOValueTypeEnum.object]: {
label: 'object',
value: WorkflowIOValueTypeEnum.object,
description: ''
},
[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]: { [WorkflowIOValueTypeEnum.chatHistory]: {
handlerStyle: { label: '历史记录',
borderColor: '#00A9A6'
},
label: 'core.module.valueType.chatHistory',
value: WorkflowIOValueTypeEnum.chatHistory, value: WorkflowIOValueTypeEnum.chatHistory,
description: `{ description: `{
obj: System | Human | AI; obj: System | Human | AI;
@ -37,10 +50,7 @@ export const FlowValueTypeMap = {
}[]` }[]`
}, },
[WorkflowIOValueTypeEnum.datasetQuote]: { [WorkflowIOValueTypeEnum.datasetQuote]: {
handlerStyle: { label: '知识库引用',
borderColor: '#A558C9'
},
label: 'core.module.valueType.datasetQuote',
value: WorkflowIOValueTypeEnum.datasetQuote, value: WorkflowIOValueTypeEnum.datasetQuote,
description: `{ description: `{
id: string; id: string;
@ -52,42 +62,22 @@ export const FlowValueTypeMap = {
a: string a: string
}[]` }[]`
}, },
[WorkflowIOValueTypeEnum.any]: {
handlerStyle: {
borderColor: '#9CA2A8'
},
label: 'core.module.valueType.any',
value: WorkflowIOValueTypeEnum.any,
description: ''
},
[WorkflowIOValueTypeEnum.selectApp]: { [WorkflowIOValueTypeEnum.selectApp]: {
handlerStyle: { label: '选择应用',
borderColor: '#6a6efa'
},
label: 'core.module.valueType.selectApp',
value: WorkflowIOValueTypeEnum.selectApp, value: WorkflowIOValueTypeEnum.selectApp,
description: '' description: ''
}, },
[WorkflowIOValueTypeEnum.selectDataset]: { [WorkflowIOValueTypeEnum.selectDataset]: {
handlerStyle: { label: '选择知识库',
borderColor: '#21ba45'
},
label: 'core.module.valueType.selectDataset',
value: WorkflowIOValueTypeEnum.selectDataset, value: WorkflowIOValueTypeEnum.selectDataset,
description: '' description: ''
}, },
[WorkflowIOValueTypeEnum.tools]: { [WorkflowIOValueTypeEnum.any]: {
handlerStyle: { label: 'any',
borderColor: '#21ba45' value: WorkflowIOValueTypeEnum.any,
},
label: 'core.module.valueType.tools',
value: WorkflowIOValueTypeEnum.tools,
description: '' description: ''
}, },
[WorkflowIOValueTypeEnum.dynamic]: { [WorkflowIOValueTypeEnum.dynamic]: {
handlerStyle: {
borderColor: '#9CA2A8'
},
label: '动态数据', label: '动态数据',
value: WorkflowIOValueTypeEnum.any, value: WorkflowIOValueTypeEnum.any,
description: '' 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 { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { systemConfigNode2VariableNode } from './adapt'; 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'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
export const nodeTemplate2FlowNode = ({ export const nodeTemplate2FlowNode = ({