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">
<img height="21" src="https://img.shields.io/badge/相关项目-7d09f1?style=flat-square" alt="project">
</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>
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 自定义解析服务。
这里介绍快速 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 环境变量

View File

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

View File

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

View File

@@ -4,3 +4,15 @@ export const delay = (ms: number) =>
resolve('');
}, 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;
apiServer?: APIFileServer;
syncSchedule?: { cronString: string; timezone: string };
syncNextTime?: Date;
// abandon
externalReadUrl?: string;
defaultPermission?: number;

View File

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

View File

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

View File

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

View File

@@ -121,6 +121,6 @@ const AppSchema = new Schema({
AppSchema.index({ teamId: 1, updateTime: -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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -292,63 +292,7 @@ export const useWorkflow = () => {
const { getIntersectingNodes } = useReactFlow();
const { isDowningCtrl } = useKeyboard();
// Loop node size and position
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
}
}
]);
});
const { resetParentNodeSizeAndPosition } = useLoopNode();
/* helper line */
const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine>();
@@ -704,7 +648,7 @@ export const useWorkflow = () => {
chatConfig: appDetail.chatConfig
});
},
[nodes, edges, appDetail.chatConfig, pushPastSnapshot],
[nodes, edges, appDetail.chatConfig],
{ wait: 500 }
);
@@ -721,7 +665,73 @@ export const useWorkflow = () => {
helperLineVertical,
onNodeDragStop,
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
};
};

View File

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

View File

@@ -2,7 +2,6 @@ import { postWorkflowDebug } from '@/web/core/workflow/api';
import {
checkWorkflowNodeAndConnection,
compareSnapshot,
simplifyWorkflowNodes,
storeEdgesRenderEdge,
storeNode2FlowNode
} from '@/web/core/workflow/utils';
@@ -832,11 +831,11 @@ const WorkflowContextProvider = ({
const undo = useMemoizedFn(() => {
if (past.length > 1) {
forbiddenSaveSnapshot.current = true;
const firstPast = past[0];
// Current version is the first one, so we need to reset the second one
const firstPast = past[1];
resetSnapshot(firstPast);
setFuture((future) => [firstPast, ...future]);
setFuture((future) => [past[0], ...future]);
setPast((past) => past.slice(1));
}
});
@@ -937,10 +936,10 @@ const WorkflowContextProvider = ({
if (isInit && past.length === 0) {
setPast([
{
nodes: nodes,
edges: edges,
title: t(`app:app.version_initial`),
isSaved: true,
nodes: simplifyWorkflowNodes(nodes),
edges,
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 { getScheduleTriggerApp } from '@/service/core/app/utils';
// Try to run train every minute
const setTrainingQueueCron = () => {
setCron('*/1 * * * *', () => {
startTrainingQueue();
});
};
// Clear tmp upload files every ten minutes
const setClearTmpUploadFilesCron = () => {
// Clear tmp upload files every ten minutes
setCron('*/10 * * * *', () => {
@@ -61,6 +63,7 @@ const clearInvalidDataCron = () => {
});
};
// Run app timer trigger every hour
const scheduleTriggerAppCron = () => {
setCron('0 */1 * * *', async () => {
if (

View File

@@ -3,7 +3,7 @@ import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { defaultApp } from '@/web/core/app/constants';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
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 {
getWorkflowEntryNodeIds,
@@ -18,63 +18,72 @@ import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
export const getScheduleTriggerApp = async () => {
// 1. Find all the app
const apps = await MongoApp.find({
scheduledTriggerConfig: { $ne: null },
scheduledTriggerNextTime: { $lte: new Date() }
const apps = await retryFn(() => {
return MongoApp.find({
scheduledTriggerConfig: { $ne: null },
scheduledTriggerNextTime: { $lte: new Date() }
});
});
// 2. Run apps
await Promise.allSettled(
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 {
const { flowUsages } = await dispatchWorkFlow({
chatId: getNanoid(),
user,
mode: 'chat',
runningAppInfo: {
id: String(app._id),
teamId: String(app.teamId),
tmbId: String(app.tmbId)
},
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
if (!app.scheduledTriggerConfig) return;
// random delay 0 ~ 60s
await delay(Math.floor(Math.random() * 60 * 1000));
const { user } = await getUserChatInfoAndAuthTeamPoints(app.tmbId);
await retryFn(async () => {
if (!app.scheduledTriggerConfig) return;
const { flowUsages } = await dispatchWorkFlow({
chatId: getNanoid(),
user,
mode: 'chat',
runningAppInfo: {
id: String(app._id),
teamId: String(app.teamId),
tmbId: String(app.tmbId)
},
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,
histories: [],
stream: false,
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
});
pushChatUsage({
appName: app.name,
appId: app._id,
teamId: String(app.teamId),
tmbId: String(app.tmbId),
source: UsageSourceEnum.cronJob,
flowUsages
],
chatConfig: defaultApp.chatConfig,
histories: [],
stream: false,
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
});
pushChatUsage({
appName: app.name,
appId: app._id,
teamId: String(app.teamId),
tmbId: String(app.tmbId),
source: UsageSourceEnum.cronJob,
flowUsages
});
});
// update next time
app.scheduledTriggerNextTime = getNextTimeByCronStringAndTimezone(
app.scheduledTriggerConfig
);
await app.save();
} 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 { GetSystemPluginTemplatesBody } from '@/pages/api/core/app/plugin/getSystemPluginTemplates';
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 ============== */
export const getTeamPlugTemplates = (data?: ListAppBody) =>
@@ -41,8 +43,11 @@ export const getTeamPlugTemplates = (data?: ListAppBody) =>
export const getSystemPlugTemplates = (data: GetSystemPluginTemplatesBody) =>
POST<NodeTemplateListItemType[]>('/core/app/plugin/getSystemPluginTemplates', data);
export const getPluginGroups = () =>
GET<PluginGroupSchemaType[]>('/proApi/core/app/plugin/getPluginGroups');
export const getPluginGroups = () => {
return useSystemStore.getState()?.feConfigs?.isPlus
? GET<PluginGroupSchemaType[]>('/proApi/core/app/plugin/getPluginGroups')
: Promise.resolve([defaultGroup]);
};
export const getSystemPluginPaths = (parentId: ParentIdType) => {
if (!parentId) return Promise.resolve<ParentTreePathItemType[]>([]);

View File

@@ -619,7 +619,6 @@ export const compareSnapshot = (
return nodes
.filter((node) => {
if (!node) return;
if (FlowNodeTypeEnum.systemConfig === node.type) return;
return true;
})
@@ -634,7 +633,8 @@ export const compareSnapshot = (
key: input.key,
selectedTypeIndex: input.selectedTypeIndex ?? 0,
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
})),
outputs: node.data.outputs.map((item: FlowNodeOutputItemType) => ({
@@ -661,13 +661,3 @@ export const compareSnapshot = (
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
sudo docker build -t model_pdf -f Dockerfile .
```bash
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` 服务