This commit is contained in:
Archer
2024-05-28 14:47:10 +08:00
committed by GitHub
parent d9f5f4ede0
commit 9639139b52
58 changed files with 4715 additions and 283 deletions

View File

@@ -20,6 +20,8 @@ CHAT_API_KEY=sk-xxxx
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/fastgpt?authSource=admin
# PG 数据库连接参数
PG_URL=postgresql://username:password@host:port/postgres
# code sandbox url
SANDBOX_URL=http://localhost:3001
# 商业版地址
PRO_URL=
# 首页路径

86
projects/app/Dockerfile Normal file
View File

@@ -0,0 +1,86 @@
# --------- install dependence -----------
FROM node:18.17-alpine AS mainDeps
WORKDIR /app
ARG proxy
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
# if proxy exists, set proxy
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npmmirror.com
# copy packages and one project
COPY pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ./packages ./packages
COPY ./projects/app/package.json ./projects/app/package.json
RUN [ -f pnpm-lock.yaml ] || (echo "Lockfile not found." && exit 1)
RUN pnpm i
# --------- builder -----------
FROM node:18.17-alpine AS builder
WORKDIR /app
ARG proxy
# copy common node_modules and one project node_modules
COPY package.json pnpm-workspace.yaml .npmrc ./
COPY --from=mainDeps /app/node_modules ./node_modules
COPY --from=mainDeps /app/packages ./packages
COPY ./projects/app ./projects/app
COPY --from=mainDeps /app/projects/app/node_modules ./projects/app/node_modules
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN pnpm --filter=app build
# --------- runner -----------
FROM node:18.17-alpine AS runner
WORKDIR /app
ARG proxy
# create user and use it
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache curl ca-certificates \
&& update-ca-certificates
# copy running files
COPY --from=builder /app/projects/app/public /app/projects/app/public
COPY --from=builder /app/projects/app/next.config.js /app/projects/app/next.config.js
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/standalone /app/
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/static /app/projects/app/.next/static
# copy server chunks
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/server/chunks /app/projects/app/.next/server/chunks
# copy worker
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/server/worker /app/projects/app/.next/server/worker
# copy tiktoken but not copy ./node_modules/tiktoken/encoders
COPY --from=mainDeps /app/node_modules/tiktoken ./node_modules/tiktoken
RUN rm -rf ./node_modules/tiktoken/encoders
# copy package.json to version file
COPY --from=builder /app/projects/app/package.json ./package.json
# copy config
COPY ./projects/app/data /app/data
RUN chown -R nextjs:nodejs /app/data
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT=3000
EXPOSE 3000
USER nextjs
ENV serverPath=./projects/app/server.js
ENTRYPOINT ["sh","-c","node --max-old-space-size=4096 ${serverPath}"]

View File

@@ -1,6 +1,7 @@
{
"Add new": "Add new",
"App": "App",
"Code editor": "Code edit",
"Export": "Export",
"Folder": "Folder",
"Is open": "Opened",

View File

@@ -1,3 +1,12 @@
{
"Field required": "Required"
"Code": "Code",
"Field required": "Required",
"code": {
"Reset template": "Reset template",
"Reset template confirm": "Are you sure to restore the code template? Be careful to save the current code."
},
"response": {
"Custom inputs": "Custom inputs",
"Custom outputs": "Custom outputs"
}
}

View File

@@ -1,6 +1,7 @@
{
"Add new": "新增",
"App": "应用",
"Code editor": "代码编辑",
"Export": "导出",
"Folder": "文件夹",
"Is open": "是否开启",

View File

@@ -1,3 +1,12 @@
{
"Field required": "必填"
"Code": "代码",
"Field required": "必填",
"code": {
"Reset template": "还原模板",
"Reset template confirm": "确认还原代码模板?请注意保存当前代码。"
},
"response": {
"Custom inputs": "自定义输入",
"Custom outputs": "自定义输出"
}
}

View File

@@ -89,7 +89,7 @@ const nextConfig = {
transpilePackages: ['@fastgpt/*', 'ahooks'],
experimental: {
// 优化 Server Components 的构建和运行,避免不必要的客户端打包。
serverComponentsExternalPackages: ['mongoose', 'pg'],
serverComponentsExternalPackages: ['mongoose', 'pg', '@node-rs/jieba'],
outputFileTracingRoot: path.join(__dirname, '../../')
}
};

View File

@@ -0,0 +1,24 @@
<svg t="1716862994060" class="icon" viewBox="0 0 1218 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4475"
width="256" height="256">
<path
d="M1169.592417 975.469194V145.592417c0-53.383886-43.677725-97.061611-97.061611-97.061611H145.592417c-53.383886 0-97.061611 43.677725-97.061611 97.061611v829.876777h1121.061611z"
fill="#7CDEDC" p-id="4476"></path>
<path
d="M1193.85782 975.469194V145.592417c0-66.899716-54.427299-121.327014-121.327014-121.327014H145.592417C78.692701 24.265403 24.265403 78.692701 24.265403 145.592417v854.14218h1169.592417v-24.265403z m-1121.061611 0V145.592417c0-40.139829 32.656379-72.796209 72.796208-72.796208h926.938389c40.139829 0 72.796209 32.656379 72.796208 72.796208v829.876777l24.265403-24.265403H48.530806l24.265403 24.265403z"
fill="#6E6E96" p-id="4477"></path>
<path d="M48.530806 237.800948h1145.327014v48.530806H48.530806z" fill="#6E6E96" p-id="4478"></path>
<path
d="M1072.530806 48.530806H145.592417c-53.383886 0-97.061611 43.677725-97.061611 97.061611v111.620853h1121.061611V145.592417c0-53.383886-43.677725-97.061611-97.061611-97.061611z"
fill="#FFF491" p-id="4479"></path>
<path
d="M1072.530806 24.265403H145.592417C78.692701 24.265403 24.265403 78.692701 24.265403 145.592417v135.886256h1169.592417V145.592417c0-66.899716-54.427299-121.327014-121.327014-121.327014z m72.796208 121.327014v111.620853l24.265403-24.265403H48.530806l24.265403 24.265403V145.592417c0-40.139829 32.656379-72.796209 72.796208-72.796208h926.938389c40.139829 0 72.796209 32.656379 72.796208 72.796208zM374.725763 431.74946l-167.43128 167.436132L190.138844 616.341232l17.155639 17.15564 167.43128 167.43128 34.320986-34.31128-167.43128-167.43128v34.31128l167.43128-167.426427zM813.934408 466.070445l167.431279 167.426427v-34.31128l-167.431279 167.43128 34.311279 34.31128 167.43128-167.43128L1032.832607 616.341232l-17.15564-17.15564-167.43128-167.436132z"
fill="#6E6E96" p-id="4480"></path>
<path
d="M531.412322 618.767773m-41.251185 0a41.251185 41.251185 0 1 0 82.50237 0 41.251185 41.251185 0 1 0-82.50237 0Z"
fill="#6E6E96" p-id="4481"></path>
<path
d="M706.123223 618.767773m-41.251185 0a41.251185 41.251185 0 1 0 82.50237 0 41.251185 41.251185 0 1 0-82.50237 0Z"
fill="#6E6E96" p-id="4482"></path>
<path d="M72.796209 281.478673h1072.530805v53.383886H72.796209z" fill="#6E6E96" opacity=".15" p-id="4483"></path>
<path d="M72.796209 189.270142h1072.530805v43.677725H72.796209z" fill="#6E6E96" opacity=".15" p-id="4484"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
import React, { useCallback, useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image, BoxProps } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
@@ -12,42 +12,68 @@ import Markdown from '../Markdown';
import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import { formatNumber } from '@fastgpt/global/common/math/tools';
import { useI18n } from '@/web/context/I18n';
function RowRender({
children,
mb,
label,
...props
}: { children: React.ReactNode; label: string } & BoxProps) {
return (
<Box mb={3}>
<Box fontSize={['sm', 'md']} mb={mb} flex={'0 0 90px'}>
{label}:
</Box>
<Box borderRadius={'sm'} fontSize={'sm'} bg={'myGray.50'} {...props}>
{children}
</Box>
</Box>
);
}
function Row({
label,
value,
rawDom
}: {
label: string;
value?: string | number | boolean;
value?: string | number | boolean | object;
rawDom?: React.ReactNode;
}) {
const { t } = useTranslation();
const theme = useTheme();
const val = value || rawDom;
const strValue = `${value}`;
const isCodeBlock = strValue.startsWith('~~~json');
const isObject = typeof value === 'object';
return val !== undefined && val !== '' && val !== 'undefined' ? (
<Box mb={3}>
<Box fontSize={['sm', 'md']} mb={isCodeBlock ? 0 : 1} flex={'0 0 90px'}>
{t(label)}:
</Box>
<Box
borderRadius={'sm'}
fontSize={'sm'}
bg={'myGray.50'}
{...(isCodeBlock
? { transform: 'translateY(-3px)' }
: value
? { px: 3, py: 2, border: theme.borders.base }
: {})}
>
{value && <Markdown source={strValue} />}
const formatValue = useMemo(() => {
if (isObject) {
return `~~~json\n${JSON.stringify(value, null, 2)}`;
}
return `${value}`;
}, [isObject, value]);
if (rawDom) {
return (
<RowRender label={label} mb={1}>
{rawDom}
</Box>
</Box>
) : null;
</RowRender>
);
}
if (val === undefined || val === '' || val === 'undefined') return null;
return (
<RowRender
label={label}
mb={isObject ? 0 : 1}
{...(isObject
? { transform: 'translateY(-3px)' }
: value
? { px: 3, py: 2, border: theme.borders.base }
: {})}
>
<Markdown source={formatValue} />
</RowRender>
);
}
const WholeResponseModal = ({
@@ -98,6 +124,7 @@ export const ResponseBox = React.memo(function ResponseBox({
}) {
const theme = useTheme();
const { t } = useTranslation();
const { workflowT } = useI18n();
const list = useMemo(
() =>
@@ -251,47 +278,26 @@ export const ResponseBox = React.memo(function ResponseBox({
label={t('core.chat.response.module extract description')}
value={activeModule?.extractDescription}
/>
{activeModule?.extractResult && (
<Row
label={t('core.chat.response.module extract result')}
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
/>
)}
<Row
label={t('core.chat.response.module extract result')}
value={activeModule?.extractResult}
/>
</>
{/* http */}
<>
{activeModule?.headers && (
<Row
label={'Headers'}
value={`~~~json\n${JSON.stringify(activeModule?.headers, null, 2)}`}
/>
)}
{activeModule?.params && (
<Row
label={'Params'}
value={`~~~json\n${JSON.stringify(activeModule?.params, null, 2)}`}
/>
)}
{activeModule?.body && (
<Row label={'Body'} value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`} />
)}
{activeModule?.httpResult && (
<Row
label={t('core.chat.response.module http result')}
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
/>
)}
<Row label={'Headers'} value={activeModule?.headers} />
<Row label={'Params'} value={activeModule?.params} />
<Row label={'Body'} value={activeModule?.body} />
<Row
label={t('core.chat.response.module http result')}
value={activeModule?.httpResult}
/>
</>
{/* plugin */}
<>
{activeModule?.pluginOutput && (
<Row
label={t('core.chat.response.plugin output')}
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
/>
)}
<Row label={t('core.chat.response.plugin output')} value={activeModule?.pluginOutput} />
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('core.chat.response.Plugin response detail')}
@@ -310,6 +316,10 @@ export const ResponseBox = React.memo(function ResponseBox({
rawDom={<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />}
/>
)}
{/* code */}
<Row label={workflowT('response.Custom inputs')} value={activeModule?.customInputs} />
<Row label={workflowT('response.Custom outputs')} value={activeModule?.customOutputs} />
</Box>
</>
);

View File

@@ -58,7 +58,8 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
),
[FlowNodeTypeEnum.lafModule]: dynamic(() => import('./nodes/NodeLaf')),
[FlowNodeTypeEnum.ifElseNode]: dynamic(() => import('./nodes/NodeIfElse')),
[FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate'))
[FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate')),
[FlowNodeTypeEnum.code]: dynamic(() => import('./nodes/NodeCode'))
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge

View File

@@ -0,0 +1,107 @@
import React, { useCallback, useMemo } from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Container from '../components/Container';
import RenderInput from './render/RenderInput';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import IOTitle from '../components/IOTitle';
import RenderToolInput from './render/RenderToolInput';
import RenderOutput from './render/RenderOutput';
import CodeEditor from '@fastgpt/web/components/common/Textarea/CodeEditor';
import { Box, Flex } from '@chakra-ui/react';
import { useI18n } from '@/web/context/I18n';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { JS_TEMPLATE } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { workflowT } = useI18n();
const { nodeId, inputs, outputs } = data;
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
const { ConfirmModal, openConfirm } = useConfirm({
content: workflowT('code.Reset template confirm')
});
const CustomComponent = useMemo(
() => ({
[NodeInputKeyEnum.code]: (item: FlowNodeInputItemType) => {
return (
<Box>
<Flex mb={1} alignItems={'flex-end'}>
<Box flex={'1'}>{workflowT('Code')}</Box>
<Box
cursor={'pointer'}
color={'primary.500'}
fontSize={'xs'}
onClick={openConfirm(() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: JS_TEMPLATE
}
});
})}
>
{workflowT('code.Reset template')}
</Box>
</Flex>
<CodeEditor
bg={'white'}
borderRadius={'sm'}
value={item.value}
onChange={(e) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
}}
/>
</Box>
);
}
}),
[nodeId, onChangeNode, openConfirm, workflowT]
);
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
{toolInputs.length > 0 && (
<>
<Container>
<IOTitle text={t('core.module.tool.Tool input')} />
<RenderToolInput nodeId={nodeId} inputs={toolInputs} />
</Container>
</>
)}
<Container>
<IOTitle text={t('common.Input')} />
<RenderInput
nodeId={nodeId}
flowInputList={commonInputs}
CustomComponent={CustomComponent}
/>
</Container>
<Container>
<IOTitle text={t('common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
<ConfirmModal />
</NodeCard>
);
};
export default React.memo(NodeCode);

View File

@@ -72,7 +72,6 @@ const AddInputParam = (props: RenderInputProps) => {
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
mr={'-5px'}
onClick={() => setEditField(item.dynamicParamDefaultValue ?? {})}
>
{t('common.Add New')}

View File

@@ -13,6 +13,7 @@ import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
const RenderList: {
types: `${FlowNodeOutputTypeEnum}`[];
@@ -61,6 +62,7 @@ const RenderOutput = ({
<Box position={'relative'} fontWeight={'medium'}>
{t('core.workflow.Custom outputs')}
</Box>
<QuestionTip ml={1} label={addOutput.description} />
<Box flex={'1 0 0'} />
<Button
variant={'whitePrimary'}

View File

@@ -148,7 +148,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if ((await targetCol.countDocuments()) > 1) {
// 除了root
console.log('team_members 中有数据,无法自动将 buffer.tts 迁移到 team_members请手动操作');
console.log('team_members 中有数据,无法自动将 team.tts 迁移到 team_members请手动操作');
} else {
await sourceCol.rename('team_members', { dropTarget: true });
console.log('success rename team.members -> team_members');