Compare commits

...

6 Commits

Author SHA1 Message Date
Archer
15dc7b220e perf: snap code (#3350)
* perf: readme

* perf: snap code
2024-12-09 18:27:40 +08:00
heheer
c64d629a6a fix: loop node snapshot & infinite reload (#3349) 2024-12-09 18:18:33 +08:00
Archer
1a294c1fd3 perf: load plugin groups code;perf: system plugin schema;fix: special variables replace;perf: retry cron app job (#3347)
* perf: load plugin groups code

* perf: system plugin schema

* feat: retry cron app job

* fix: special variables replace
2024-12-09 17:18:07 +08:00
Archer
a4f6128a89 Update 4814.md (#3345) 2024-12-09 14:50:54 +08:00
heheer
021ec0595d fix: plugin group api position & team selector optional display (#3339) 2024-12-09 09:55:10 +08:00
YeYuheng
6ceee7cb5e marker doc (#3335)
* marker doc

* marker doc
2024-12-07 20:28:40 +08:00
24 changed files with 219 additions and 171 deletions

View File

@@ -27,9 +27,6 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
<a href="/#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE"> <a href="/#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">
<img height="21" src="https://img.shields.io/badge/相关项目-7d09f1?style=flat-square" alt="project"> <img height="21" src="https://img.shields.io/badge/相关项目-7d09f1?style=flat-square" alt="project">
</a> </a>
<a href="https://github.com/labring/FastGPT/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=7d09f1" alt="license">
</a>
</p> </p>
https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409bd33f6d4 https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409bd33f6d4

View File

@@ -21,9 +21,11 @@ PDF 是一个相对复杂的文件格式,在 FastGPT 内置的 pdf 解析器
参考文档 [Marker 安装教程](https://github.com/labring/FastGPT/tree/main/python/pdf-marker),安装 Marker 模型。封装的 API 已经适配了 FastGPT 自定义解析服务。 参考文档 [Marker 安装教程](https://github.com/labring/FastGPT/tree/main/python/pdf-marker),安装 Marker 模型。封装的 API 已经适配了 FastGPT 自定义解析服务。
这里介绍快速 Docker 按照的方法: 这里介绍快速 Docker 安装的方法:
``` ```dockerfile
docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest
docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest
``` ```
### 2. 添加 FastGPT 环境变量 ### 2. 添加 FastGPT 环境变量

View File

@@ -36,7 +36,7 @@ milvus版本使用v4.8.14-milvus-fix 镜像。
2. 新增 - 重写 chatContext对话测试也会有日志并且刷新后不会丢失对话。 2. 新增 - 重写 chatContext对话测试也会有日志并且刷新后不会丢失对话。
3. 新增 - 分享链接支持配置是否允许查看原文。 3. 新增 - 分享链接支持配置是否允许查看原文。
4. 新增 - 新的 doc2x 插件。 4. 新增 - 新的 doc2x 插件。
5. 新增 - 繁体中文-台湾 5. 新增 - 繁体中文。
6. 新增 - 分析链接和 chat api 支持传入自定义 uid。 6. 新增 - 分析链接和 chat api 支持传入自定义 uid。
7. 商业版新增 - 微软 oauth 登录 7. 商业版新增 - 微软 oauth 登录
8. 优化 - 工作流 ui 细节。 8. 优化 - 工作流 ui 细节。

View File

@@ -46,3 +46,4 @@ weight: 809
14. 修复 - 语言播放鉴权问题。 14. 修复 - 语言播放鉴权问题。
15. 修复 - 插件应用知识库引用上限始终为 3000 15. 修复 - 插件应用知识库引用上限始终为 3000
16. 修复 - 工作流编辑记录存储上限,去掉本地存储,增加异常离开时,强制自动保存。 16. 修复 - 工作流编辑记录存储上限,去掉本地存储,增加异常离开时,强制自动保存。
17. 修复 - 工作流特殊变量替换问题。($开头的字符串无法替换)

View File

@@ -4,3 +4,15 @@ export const delay = (ms: number) =>
resolve(''); resolve('');
}, ms); }, ms);
}); });
export const retryFn = async <T>(fn: () => Promise<T>, retryTimes = 3): Promise<T> => {
try {
return fn();
} catch (error) {
if (retryTimes > 0) {
await delay(500);
return retryFn(fn, retryTimes - 1);
}
return Promise.reject(error);
}
};

View File

@@ -34,6 +34,9 @@ export type DatasetSchemaType = {
inheritPermission: boolean; inheritPermission: boolean;
apiServer?: APIFileServer; apiServer?: APIFileServer;
syncSchedule?: { cronString: string; timezone: string };
syncNextTime?: Date;
// abandon // abandon
externalReadUrl?: string; externalReadUrl?: string;
defaultPermission?: number; defaultPermission?: number;

View File

@@ -321,7 +321,7 @@ export function replaceEditorVariable({
})(); })();
const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g'); const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g');
text = text.replace(regex, formatVal); text = text.replace(regex, () => formatVal);
}); });
return text || ''; return text || '';

View File

@@ -10,8 +10,7 @@ const SystemPluginSchema = new Schema({
required: true required: true
}, },
isActive: { isActive: {
type: Boolean, type: Boolean
required: true
}, },
inputConfig: { inputConfig: {
type: Array, type: Array,

View File

@@ -13,7 +13,7 @@ export type SystemPluginConfigSchemaType = {
hasTokenFee: boolean; hasTokenFee: boolean;
isActive: boolean; isActive: boolean;
pluginOrder: number; pluginOrder: number;
inputConfig: SystemPluginTemplateItemType['inputConfig']; inputConfig?: SystemPluginTemplateItemType['inputConfig'];
customConfig?: { customConfig?: {
name: string; name: string;

View File

@@ -121,6 +121,6 @@ const AppSchema = new Schema({
AppSchema.index({ teamId: 1, updateTime: -1 }); AppSchema.index({ teamId: 1, updateTime: -1 });
AppSchema.index({ teamId: 1, type: 1 }); AppSchema.index({ teamId: 1, type: 1 });
AppSchema.index({ scheduledTriggerConfig: 1, intervalNextTime: -1 }); AppSchema.index({ scheduledTriggerConfig: 1, scheduledTriggerNextTime: -1 });
export const MongoApp = getMongoModel<AppType>(AppCollectionName, AppSchema); export const MongoApp = getMongoModel<AppType>(AppCollectionName, AppSchema);

View File

@@ -91,6 +91,18 @@ const DatasetSchema = new Schema({
type: Object type: Object
}, },
syncSchedule: {
cronString: {
type: String
},
timezone: {
type: String
}
},
syncNextTime: {
type: Date
},
// abandoned // abandoned
externalReadUrl: { externalReadUrl: {
type: String type: String
@@ -100,6 +112,7 @@ const DatasetSchema = new Schema({
try { try {
DatasetSchema.index({ teamId: 1 }); DatasetSchema.index({ teamId: 1 });
DatasetSchema.index({ syncSchedule: 1, syncNextTime: -1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -1,5 +1,6 @@
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { i18nT } from '../../i18n/utils'; import { i18nT } from '../../i18n/utils';
import type { PluginGroupSchemaType, TGroupType } from '../../../service/core/app/plugin/type';
export const workflowNodeTemplateList = [ export const workflowNodeTemplateList = [
{ {
@@ -49,10 +50,7 @@ export const workflowNodeTemplateList = [
} }
]; ];
export const systemPluginTemplateList: { export const systemPluginTemplateList: TGroupType[] = [
typeId: string;
typeName: string;
}[] = [
{ {
typeId: FlowNodeTemplateTypeEnum.tools, typeId: FlowNodeTemplateTypeEnum.tools,
typeName: i18nT('common:navbar.Tools') typeName: i18nT('common:navbar.Tools')
@@ -74,7 +72,7 @@ export const systemPluginTemplateList: {
typeName: i18nT('common:common.Other') typeName: i18nT('common:common.Other')
} }
]; ];
export const defaultGroup = { export const defaultGroup: PluginGroupSchemaType = {
groupId: 'systemPlugin', groupId: 'systemPlugin',
groupAvatar: 'common/navbar/pluginLight', groupAvatar: 'common/navbar/pluginLight',
groupName: i18nT('common:core.module.template.System Plugin'), groupName: i18nT('common:core.module.template.System Plugin'),

View File

@@ -32,8 +32,9 @@ export const useI18nLng = () => {
}); });
const currentLng = i18n?.language; const currentLng = i18n?.language;
await i18n?.changeLanguage?.(lang); if (!currentLng) return;
await i18n?.changeLanguage?.(lang);
if (currentLng !== lang) { if (currentLng !== lang) {
window?.location?.reload?.(); window?.location?.reload?.();
} }

View File

@@ -115,7 +115,7 @@ const Navbar = ({ unread }: { unread: number }) => {
borderRadius={'50%'} borderRadius={'50%'}
overflow={'hidden'} overflow={'hidden'}
cursor={'pointer'} cursor={'pointer'}
onClick={() => router.push('/account')} onClick={() => router.push('/account/info')}
> >
<Avatar w={'2rem'} h={'2rem'} src={userInfo?.avatar} borderRadius={'50%'} /> <Avatar w={'2rem'} h={'2rem'} src={userInfo?.avatar} borderRadius={'50%'} />
</Box> </Box>

View File

@@ -161,6 +161,7 @@ const AccountContainer = ({
<Box mb={3}> <Box mb={3}>
<LightRowTabs<TabEnum> <LightRowTabs<TabEnum>
m={'auto'} m={'auto'}
w={'100%'}
size={isPc ? 'md' : 'sm'} size={isPc ? 'md' : 'sm'}
list={tabList.map((item) => ({ list={tabList.map((item) => ({
value: item.value, value: item.value,

View File

@@ -61,10 +61,9 @@ const AiPointsModal = dynamic(() =>
const Info = () => { const Info = () => {
const { isPc } = useSystem(); const { isPc } = useSystem();
const { teamPlanStatus } = useUserStore(); const { teamPlanStatus, initUserInfo } = useUserStore();
const standardPlan = teamPlanStatus?.standardConstants; const standardPlan = teamPlanStatus?.standardConstants;
const { isOpen: isOpenContact, onClose: onCloseContact, onOpen: onOpenContact } = useDisclosure(); const { isOpen: isOpenContact, onClose: onCloseContact, onOpen: onOpenContact } = useDisclosure();
const { initUserInfo } = useUserStore();
useQuery(['init'], initUserInfo); useQuery(['init'], initUserInfo);
@@ -112,15 +111,13 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
const theme = useTheme(); const theme = useTheme();
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo, updateUserInfo } = useUserStore(); const { userInfo, updateUserInfo, teamPlanStatus } = useUserStore();
const { reset } = useForm<UserUpdateParams>({ const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType defaultValues: userInfo as UserType
}); });
const { teamPlanStatus } = useUserStore();
const standardPlan = teamPlanStatus?.standardConstants; const standardPlan = teamPlanStatus?.standardConstants;
const { isPc } = useSystem(); const { isPc } = useSystem();
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter();
const { const {
isOpen: isOpenConversionModal, isOpen: isOpenConversionModal,
@@ -305,12 +302,14 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
)} )}
</Flex> </Flex>
)} )}
<Flex mt={6} alignItems={'center'}> {feConfigs.isPlus && (
<Box {...labelStyles}>{t('account_info:user_team_team_name')}:&nbsp;</Box> <Flex mt={6} alignItems={'center'}>
<Flex flex={'1 0 0'} w={0} align={'center'}> <Box {...labelStyles}>{t('account_info:user_team_team_name')}:&nbsp;</Box>
<TeamSelector height={'28px'} w={'100%'} showManage /> <Flex flex={'1 0 0'} w={0} align={'center'}>
<TeamSelector height={'28px'} w={'100%'} showManage />
</Flex>
</Flex> </Flex>
</Flex> )}
{feConfigs?.isPlus && (userInfo?.team?.balance ?? 0) > 0 && ( {feConfigs?.isPlus && (userInfo?.team?.balance ?? 0) > 0 && (
<Box mt={6} whiteSpace={'nowrap'}> <Box mt={6} whiteSpace={'nowrap'}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>

View File

@@ -292,63 +292,7 @@ export const useWorkflow = () => {
const { getIntersectingNodes } = useReactFlow(); const { getIntersectingNodes } = useReactFlow();
const { isDowningCtrl } = useKeyboard(); const { isDowningCtrl } = useKeyboard();
// Loop node size and position const { resetParentNodeSizeAndPosition } = useLoopNode();
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
const { childNodes, loopNode } = nodes.reduce(
(acc, node) => {
if (node.data.parentNodeId === parentId) {
acc.childNodes.push(node);
}
if (node.id === parentId) {
acc.loopNode = node;
}
return acc;
},
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
);
if (!loopNode) return;
const rect = getNodesBounds(childNodes);
// Calculate parent node size with minimum width/height constraints
const width = Math.max(rect.width + 80, 840);
const height = Math.max(rect.height + 80, 600);
const offsetHeight =
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
?.value ?? 83;
// Update parentNode size and position
onChangeNode({
nodeId: parentId,
type: 'updateInput',
key: NodeInputKeyEnum.nodeWidth,
value: {
...Input_Template_Node_Width,
value: width
}
});
onChangeNode({
nodeId: parentId,
type: 'updateInput',
key: NodeInputKeyEnum.nodeHeight,
value: {
...Input_Template_Node_Height,
value: height
}
});
// Update parentNode position
onNodesChange([
{
id: parentId,
type: 'position',
position: {
x: rect.x - 70,
y: rect.y - offsetHeight - 240
}
}
]);
});
/* helper line */ /* helper line */
const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine>(); const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine>();
@@ -704,7 +648,7 @@ export const useWorkflow = () => {
chatConfig: appDetail.chatConfig chatConfig: appDetail.chatConfig
}); });
}, },
[nodes, edges, appDetail.chatConfig, pushPastSnapshot], [nodes, edges, appDetail.chatConfig],
{ wait: 500 } { wait: 500 }
); );
@@ -721,7 +665,73 @@ export const useWorkflow = () => {
helperLineVertical, helperLineVertical,
onNodeDragStop, onNodeDragStop,
onPaneContextMenu, onPaneContextMenu,
onPaneClick, onPaneClick
};
};
export const useLoopNode = () => {
const nodes = useContextSelector(WorkflowInitContext, (state) => state.nodes);
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
const { childNodes, loopNode } = nodes.reduce(
(acc, node) => {
if (node.data.parentNodeId === parentId) {
acc.childNodes.push(node);
}
if (node.id === parentId) {
acc.loopNode = node;
}
return acc;
},
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
);
if (!loopNode) return;
const rect = getNodesBounds(childNodes);
// Calculate parent node size with minimum width/height constraints
const width = Math.max(rect.width + 80, 840);
const height = Math.max(rect.height + 80, 600);
const offsetHeight =
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
?.value ?? 83;
// Update parentNode size and position
onChangeNode({
nodeId: parentId,
type: 'updateInput',
key: NodeInputKeyEnum.nodeWidth,
value: {
...Input_Template_Node_Width,
value: width
}
});
onChangeNode({
nodeId: parentId,
type: 'updateInput',
key: NodeInputKeyEnum.nodeHeight,
value: {
...Input_Template_Node_Height,
value: height
}
});
// Update parentNode position
onNodesChange([
{
id: parentId,
type: 'position',
position: {
x: rect.x - 70,
y: rect.y - offsetHeight - 240
}
}
]);
});
return {
resetParentNodeSizeAndPosition resetParentNodeSizeAndPosition
}; };
}; };

View File

@@ -31,7 +31,7 @@ import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
import { AppContext } from '../../../../context'; import { AppContext } from '../../../../context';
import { isValidArrayReferenceValue } from '@fastgpt/global/core/workflow/utils'; import { isValidArrayReferenceValue } from '@fastgpt/global/core/workflow/utils';
import { ReferenceArrayValueType } from '@fastgpt/global/core/workflow/type/io'; import { ReferenceArrayValueType } from '@fastgpt/global/core/workflow/type/io';
import { useWorkflow } from '../../hooks/useWorkflow'; import { useLoopNode } from '../../hooks/useWorkflow';
import { useSize } from 'ahooks'; import { useSize } from 'ahooks';
const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
@@ -41,7 +41,7 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const appDetail = useContextSelector(AppContext, (v) => v.appDetail); const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const { resetParentNodeSizeAndPosition } = useWorkflow(); const { resetParentNodeSizeAndPosition } = useLoopNode();
const { const {
nodeWidth, nodeWidth,
@@ -50,8 +50,12 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
loopNodeInputHeight = Input_Template_LOOP_NODE_OFFSET loopNodeInputHeight = Input_Template_LOOP_NODE_OFFSET
} = useMemo(() => { } = useMemo(() => {
return { return {
nodeWidth: inputs.find((input) => input.key === NodeInputKeyEnum.nodeWidth)?.value, nodeWidth: Number(
nodeHeight: inputs.find((input) => input.key === NodeInputKeyEnum.nodeHeight)?.value, inputs.find((input) => input.key === NodeInputKeyEnum.nodeWidth)?.value?.toFixed(0)
),
nodeHeight: Number(
inputs.find((input) => input.key === NodeInputKeyEnum.nodeHeight)?.value?.toFixed(0)
),
loopInputArray: inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray), loopInputArray: inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray),
loopNodeInputHeight: inputs.find( loopNodeInputHeight: inputs.find(
(input) => input.key === NodeInputKeyEnum.loopNodeInputHeight (input) => input.key === NodeInputKeyEnum.loopNodeInputHeight

View File

@@ -2,7 +2,6 @@ import { postWorkflowDebug } from '@/web/core/workflow/api';
import { import {
checkWorkflowNodeAndConnection, checkWorkflowNodeAndConnection,
compareSnapshot, compareSnapshot,
simplifyWorkflowNodes,
storeEdgesRenderEdge, storeEdgesRenderEdge,
storeNode2FlowNode storeNode2FlowNode
} from '@/web/core/workflow/utils'; } from '@/web/core/workflow/utils';
@@ -832,11 +831,11 @@ const WorkflowContextProvider = ({
const undo = useMemoizedFn(() => { const undo = useMemoizedFn(() => {
if (past.length > 1) { if (past.length > 1) {
forbiddenSaveSnapshot.current = true; forbiddenSaveSnapshot.current = true;
// Current version is the first one, so we need to reset the second one
const firstPast = past[0]; const firstPast = past[1];
resetSnapshot(firstPast); resetSnapshot(firstPast);
setFuture((future) => [firstPast, ...future]); setFuture((future) => [past[0], ...future]);
setPast((past) => past.slice(1)); setPast((past) => past.slice(1));
} }
}); });
@@ -937,10 +936,10 @@ const WorkflowContextProvider = ({
if (isInit && past.length === 0) { if (isInit && past.length === 0) {
setPast([ setPast([
{ {
nodes: nodes,
edges: edges,
title: t(`app:app.version_initial`), title: t(`app:app.version_initial`),
isSaved: true, isSaved: true,
nodes: simplifyWorkflowNodes(nodes),
edges,
chatConfig: e.chatConfig || appDetail.chatConfig chatConfig: e.chatConfig || appDetail.chatConfig
} }
]); ]);

View File

@@ -12,12 +12,14 @@ import { TimerIdEnum } from '@fastgpt/service/common/system/timerLock/constants'
import { addHours } from 'date-fns'; import { addHours } from 'date-fns';
import { getScheduleTriggerApp } from '@/service/core/app/utils'; import { getScheduleTriggerApp } from '@/service/core/app/utils';
// Try to run train every minute
const setTrainingQueueCron = () => { const setTrainingQueueCron = () => {
setCron('*/1 * * * *', () => { setCron('*/1 * * * *', () => {
startTrainingQueue(); startTrainingQueue();
}); });
}; };
// Clear tmp upload files every ten minutes
const setClearTmpUploadFilesCron = () => { const setClearTmpUploadFilesCron = () => {
// Clear tmp upload files every ten minutes // Clear tmp upload files every ten minutes
setCron('*/10 * * * *', () => { setCron('*/10 * * * *', () => {
@@ -61,6 +63,7 @@ const clearInvalidDataCron = () => {
}); });
}; };
// Run app timer trigger every hour
const scheduleTriggerAppCron = () => { const scheduleTriggerAppCron = () => {
setCron('0 */1 * * *', async () => { setCron('0 */1 * * *', async () => {
if ( if (

View File

@@ -3,7 +3,7 @@ import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { defaultApp } from '@/web/core/app/constants'; import { defaultApp } from '@/web/core/app/constants';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time'; import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { delay } from '@fastgpt/global/common/system/utils'; import { delay, retryFn } from '@fastgpt/global/common/system/utils';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { import {
getWorkflowEntryNodeIds, getWorkflowEntryNodeIds,
@@ -18,63 +18,72 @@ import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
export const getScheduleTriggerApp = async () => { export const getScheduleTriggerApp = async () => {
// 1. Find all the app // 1. Find all the app
const apps = await MongoApp.find({ const apps = await retryFn(() => {
scheduledTriggerConfig: { $ne: null }, return MongoApp.find({
scheduledTriggerNextTime: { $lte: new Date() } scheduledTriggerConfig: { $ne: null },
scheduledTriggerNextTime: { $lte: new Date() }
});
}); });
// 2. Run apps // 2. Run apps
await Promise.allSettled( await Promise.allSettled(
apps.map(async (app) => { apps.map(async (app) => {
if (!app.scheduledTriggerConfig) return;
// random delay 0 ~ 60s
await delay(Math.floor(Math.random() * 60 * 1000));
const { user } = await getUserChatInfoAndAuthTeamPoints(app.tmbId);
try { try {
const { flowUsages } = await dispatchWorkFlow({ if (!app.scheduledTriggerConfig) return;
chatId: getNanoid(), // random delay 0 ~ 60s
user, await delay(Math.floor(Math.random() * 60 * 1000));
mode: 'chat', const { user } = await getUserChatInfoAndAuthTeamPoints(app.tmbId);
runningAppInfo: {
id: String(app._id), await retryFn(async () => {
teamId: String(app.teamId), if (!app.scheduledTriggerConfig) return;
tmbId: String(app.tmbId)
}, const { flowUsages } = await dispatchWorkFlow({
uid: String(app.tmbId), chatId: getNanoid(),
runtimeNodes: storeNodes2RuntimeNodes(app.modules, getWorkflowEntryNodeIds(app.modules)), user,
runtimeEdges: initWorkflowEdgeStatus(app.edges), mode: 'chat',
variables: {}, runningAppInfo: {
query: [ id: String(app._id),
{ teamId: String(app.teamId),
type: ChatItemValueTypeEnum.text, tmbId: String(app.tmbId)
text: { },
content: app.scheduledTriggerConfig?.defaultPrompt uid: String(app.tmbId),
runtimeNodes: storeNodes2RuntimeNodes(
app.modules,
getWorkflowEntryNodeIds(app.modules)
),
runtimeEdges: initWorkflowEdgeStatus(app.edges),
variables: {},
query: [
{
type: ChatItemValueTypeEnum.text,
text: {
content: app.scheduledTriggerConfig?.defaultPrompt
}
} }
} ],
], chatConfig: defaultApp.chatConfig,
chatConfig: defaultApp.chatConfig, histories: [],
histories: [], stream: false,
stream: false, maxRunTimes: WORKFLOW_MAX_RUN_TIMES
maxRunTimes: WORKFLOW_MAX_RUN_TIMES });
}); pushChatUsage({
pushChatUsage({ appName: app.name,
appName: app.name, appId: app._id,
appId: app._id, teamId: String(app.teamId),
teamId: String(app.teamId), tmbId: String(app.tmbId),
tmbId: String(app.tmbId), source: UsageSourceEnum.cronJob,
source: UsageSourceEnum.cronJob, flowUsages
flowUsages });
}); });
// update next time
app.scheduledTriggerNextTime = getNextTimeByCronStringAndTimezone(
app.scheduledTriggerConfig
);
await app.save();
} catch (error) { } catch (error) {
addLog.error('Schedule trigger error', error); addLog.warn('Schedule trigger error', { error });
} }
// update next time
app.scheduledTriggerNextTime = getNextTimeByCronStringAndTimezone(app.scheduledTriggerConfig);
await app.save();
return;
}) })
); );
}; };

View File

@@ -14,6 +14,8 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { GetSystemPluginTemplatesBody } from '@/pages/api/core/app/plugin/getSystemPluginTemplates'; import { GetSystemPluginTemplatesBody } from '@/pages/api/core/app/plugin/getSystemPluginTemplates';
import { PluginGroupSchemaType } from '@fastgpt/service/core/app/plugin/type'; import { PluginGroupSchemaType } from '@fastgpt/service/core/app/plugin/type';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { defaultGroup } from '@fastgpt/web/core/workflow/constants';
/* ============ team plugin ============== */ /* ============ team plugin ============== */
export const getTeamPlugTemplates = (data?: ListAppBody) => export const getTeamPlugTemplates = (data?: ListAppBody) =>
@@ -41,8 +43,11 @@ export const getTeamPlugTemplates = (data?: ListAppBody) =>
export const getSystemPlugTemplates = (data: GetSystemPluginTemplatesBody) => export const getSystemPlugTemplates = (data: GetSystemPluginTemplatesBody) =>
POST<NodeTemplateListItemType[]>('/core/app/plugin/getSystemPluginTemplates', data); POST<NodeTemplateListItemType[]>('/core/app/plugin/getSystemPluginTemplates', data);
export const getPluginGroups = () => export const getPluginGroups = () => {
GET<PluginGroupSchemaType[]>('/proApi/core/app/plugin/getPluginGroups'); return useSystemStore.getState()?.feConfigs?.isPlus
? GET<PluginGroupSchemaType[]>('/proApi/core/app/plugin/getPluginGroups')
: Promise.resolve([defaultGroup]);
};
export const getSystemPluginPaths = (parentId: ParentIdType) => { export const getSystemPluginPaths = (parentId: ParentIdType) => {
if (!parentId) return Promise.resolve<ParentTreePathItemType[]>([]); if (!parentId) return Promise.resolve<ParentTreePathItemType[]>([]);

View File

@@ -619,7 +619,6 @@ export const compareSnapshot = (
return nodes return nodes
.filter((node) => { .filter((node) => {
if (!node) return; if (!node) return;
if (FlowNodeTypeEnum.systemConfig === node.type) return;
return true; return true;
}) })
@@ -634,7 +633,8 @@ export const compareSnapshot = (
key: input.key, key: input.key,
selectedTypeIndex: input.selectedTypeIndex ?? 0, selectedTypeIndex: input.selectedTypeIndex ?? 0,
renderTypeLis: input.renderTypeList, renderTypeLis: input.renderTypeList,
valueType: input.valueType, // set to arrayAny for loopInputArray to skip valueType comparison
// valueType: input.key === NodeInputKeyEnum.loopInputArray ? 'arrayAny' : input.valueType,
value: input.value ?? undefined value: input.value ?? undefined
})), })),
outputs: node.data.outputs.map((item: FlowNodeOutputItemType) => ({ outputs: node.data.outputs.map((item: FlowNodeOutputItemType) => ({
@@ -661,13 +661,3 @@ export const compareSnapshot = (
return isEqual(node1, node2); return isEqual(node1, node2);
}; };
// remove node size
export const simplifyWorkflowNodes = (nodes: Node[]) => {
return nodes.map((node) => ({
id: node.id,
type: node.type,
position: node.position,
data: node.data
}));
};

View File

@@ -72,20 +72,22 @@ export PROCESSES_PER_GPU="1"
# 镜像打包和部署 # 镜像打包和部署
## 打包镜像 ## 本地构建镜像
在 `pdf-marker` 根目录下执行: 1. 在 `pdf-marker` 根目录下执行:
```bash ```bash
sudo docker build -t model_pdf -f Dockerfile . sudo docker build -t model_pdf -f Dockerfile .
```
2. 运行容器
```bash
sudo docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 model_pdf
```
## 快速构建镜像
```dockerfile
docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest
docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest
``` ```
## 运行容器
```bash
sudo docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 model_pdf
```
# 访问示例 # 访问示例
用Post方法访问端口为 `7321 ` 的 `v1/parse/file` 服务 用Post方法访问端口为 `7321 ` 的 `v1/parse/file` 服务