Compare commits

..

28 Commits

Author SHA1 Message Date
Archer
11848b8f44 v4.4.5-3 (#357) 2023-09-26 21:17:13 +08:00
epoh
a11e0bd9c3 Update chatglm2.md (#354) 2023-09-26 15:06:38 +08:00
Archer
f6552d0d4f v4.4.5-2 (#355) 2023-09-26 14:31:37 +08:00
epoh
38d4db5d5f Rename requirement.txt to requirements.txt (#352) 2023-09-26 09:38:14 +08:00
Archer
63cd379682 Add share link hook (#351) 2023-09-25 23:12:42 +08:00
Archer
9136c9306a Add OpenAPI docs;Correct the glm document (#346) 2023-09-25 14:28:44 +08:00
Byte Sound
c9db9f33ea Update intro.md (#348)
错别字,市区改为时区
2023-09-25 13:33:30 +08:00
Archer
3d7178d06f monorepo packages (#344) 2023-09-24 18:02:09 +08:00
Archer
a4ff5a3f73 perf: api key (#342) 2023-09-23 20:28:03 +08:00
Archer
814c5b3d3c Add bill of training and rate of file upload (#339) 2023-09-21 21:02:44 +08:00
Chen X
e7e0677291 Docs:add-workflow-case-全能助手 (#334) 2023-09-21 15:57:42 +08:00
Archer
823f4b7ad1 Optimize the structure and naming of projects (#335) 2023-09-21 14:49:56 +08:00
Carson Yang
a3c77480f7 Add action for translating Non-English issues content to English (#333)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-21 14:19:54 +08:00
Archer
e367265dbb feat: function call prompt version (#331) 2023-09-21 12:27:48 +08:00
Archer
7e0deb29e0 Add SSE controller; fix share page login failed (#330) 2023-09-20 16:34:32 +08:00
Archer
0d94db4331 fix: ts and default dataset (#329) 2023-09-20 11:43:49 +08:00
Carson Yang
177482b33a Docs: fix code block highlight (#328)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-20 11:43:35 +08:00
Archer
63b183a9fe fix: mark modal cannot select folder (#327) 2023-09-20 11:26:17 +08:00
Carson Yang
858117f8c0 Docs: update font to LXGW WenKai (#325)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-19 21:22:04 +08:00
Archer
ac4355d2e1 Add modal to show completion response data (#324) 2023-09-19 20:31:45 +08:00
Archer
ce7da2db66 Optimize chat reponse data (#322) 2023-09-19 16:10:30 +08:00
Archer
0a4a1def1e fix: connected error (#318) 2023-09-19 07:54:50 +08:00
Archer
35f4deca76 Revert "Feature: 高级编排自动布局 (#314)" (#319)
This reverts commit ba1451a0e9.
2023-09-18 23:44:44 +08:00
jaden
ba1451a0e9 Feature: 高级编排自动布局 (#314)
* feat: adFlow auto layout

* chore: delete file and build pnpm lock file
2023-09-18 23:39:19 +08:00
Archer
40d69e6e20 version (#317) 2023-09-18 21:56:38 +08:00
Sr
b8ba947ba8 feat: Added defaultOpen Attribute for iframe (#302)
* feat: Added defaultOpen Attribute for iframe

This commit introduces a new attribute `defaultOpen` for the iframe created in `iframe.js`. The `defaultOpen` attribute allows the iframe to be visible by default when the page loads. This new feature enhances the user experience by providing an option to display the chatbot window immediately after the page is loaded, without requiring user interaction.

* Update iframe.js

code standard
2023-09-18 21:27:08 +08:00
Archer
06be57815e v4.4.3 (#316) 2023-09-18 21:26:42 +08:00
Carson Yang
81e37a5736 Update architecture diagram (#315)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-18 21:26:15 +08:00
611 changed files with 20122 additions and 4398 deletions

View File

@@ -0,0 +1,19 @@
name: 'Github Rebot for issues-translator'
on:
issues:
types: [ opened ]
issue_comment:
types: [ created ]
jobs:
translate:
permissions:
issues: write
discussions: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: true
BOT_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿

View File

@@ -55,8 +55,6 @@ jobs:
# Step 4 - Builds the site using Hugo
- name: Build
run: cd docSite && hugo mod get -u github.com/colinwilson/lotusdocs && hugo -v --minify
env:
HUGO_BASEURL: ${{ vars.BASE_URL }}
# Step 5 - Push our generated site to Vercel
- name: Deploy to Vercel

View File

@@ -55,8 +55,6 @@ jobs:
# Step 4 - Builds the site using Hugo
- name: Build
run: cd docSite && hugo mod get -u github.com/colinwilson/lotusdocs && hugo -v --minify
env:
HUGO_BASEURL: ${{ vars.BASE_URL }}
# Step 5 - Push our generated site to Vercel
- name: Deploy to Vercel

View File

@@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
paths:
- 'client/**'
- 'projects/app/**'
branches:
- 'main'
tags:
@@ -49,8 +49,8 @@ jobs:
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
cd client && \
docker buildx build \
--build-arg name=app \
--platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt image" \
@@ -64,6 +64,7 @@ jobs:
push-to-docker-hub:
needs: build-fastgpt-images
runs-on: ubuntu-20.04
if: github.repository == 'labring/FastGPT'
steps:
- name: Checkout code
uses: actions/checkout@v3
@@ -87,6 +88,7 @@ jobs:
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
push-to-ali-hub:
needs: build-fastgpt-images
if: github.repository == 'labring/FastGPT'
runs-on: ubuntu-20.04
steps:
- name: Checkout code

View File

@@ -1,10 +1,10 @@
{
"editor.formatOnSave": true,
"editor.mouseWheelZoom": true,
"typescript.tsdk": "client/node_modules/typescript/lib",
"typescript.tsdk": "node_modules/typescript/lib",
"prettier.prettierPath": "./node_modules/prettier",
"i18n-ally.localesPaths": [
"client/public/locales"
"projects/app/public/locales"
],
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.keystyle": "nested",

72
Dockerfile Normal file
View File

@@ -0,0 +1,72 @@
# Install dependencies only when needed
FROM node:current-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat && npm install -g pnpm
WORKDIR /app
ARG name
# copy packages and one project
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY ./packages ./packages
COPY ./projects/$name/package.json ./projects/$name/package.json
COPY ./projects/$name/pnpm-lock.yaml ./projects/$name/pnpm-lock.yaml
RUN \
[ -f pnpm-lock.yaml ] && pnpm install || \
(echo "Lockfile not found." && exit 1)
RUN pnpm prune
# Rebuild the source code only when needed
FROM node:current-alpine AS builder
WORKDIR /app
ARG name
# copy common node_modules and one project node_modules
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages ./packages
COPY ./projects/$name ./projects/$name
COPY --from=deps /app/projects/$name/node_modules ./projects/$name/node_modules
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY ./packages ./packages
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm install -g pnpm
RUN pnpm --filter=$name run build
FROM node:current-alpine AS runner
WORKDIR /app
ARG name
# create user and use it
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl \
&& apk add ca-certificates \
&& update-ca-certificates
# copy running files
COPY --from=builder /app/projects/$name/public ./projects/$name/public
COPY --from=builder /app/projects/$name/next.config.js ./projects/$name/next.config.js
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/static ./projects/$name/.next/static
# copy package.json to version file
COPY --from=builder /app/projects/$name/package.json ./package.json
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT=3000
EXPOSE 3000
USER nextjs
ENV serverPath=./projects/$name/server.js
ENTRYPOINT ["sh","-c","node ${serverPath}"]

View File

@@ -81,7 +81,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
* [系统配置文件说明](https://doc.fastgpt.run/docs/development/configuration/)
* [多模型配置](https://doc.fastgpt.run/docs/installation/one-api/)
* [版本升级](https://doc.fastgpt.run/docs/installation/upgrading)
* [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F)
* [API 文档](https://doc.fastgpt.run/docs/development/openapi?pre_pathname=%2Fdrive%2Fhome%2F)
## 🏘️ 社区交流群
@@ -118,4 +118,4 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
1. 允许作为后台服务直接商用,但不允许直接使用 saas 服务商用。
2. 需保留相关版权信息。
3. 完整请查看 [FastGPT Open Source License](./LICENSE)
4. 联系方式yujinlong@sealos.io, [点击查看定价策略](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)
4. 联系方式yujinlong@sealos.io, [点击查看定价策略](https://doc.fastgpt.run/docs/commercial)

View File

@@ -1,65 +0,0 @@
# Install dependencies only when needed
FROM node:current-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat && npm install -g pnpm
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json ./
COPY pnpm-lock.yaml* ./
RUN \
[ -f pnpm-lock.yaml ] && pnpm fetch || \
(echo "Lockfile not found." && exit 1)
# Rebuild the source code only when needed
FROM node:current-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY pnpm-lock.yaml* ./
COPY package.json ./
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm install -g pnpm
RUN \
[ -f pnpm-lock.yaml ] && (pnpm --offline install && pnpm run build) || \
(echo "Lockfile not found." && exit 1)
# Production image, copy all the files and run next
FROM node:current-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl \
&& apk add ca-certificates \
&& update-ca-certificates
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# COPY --from=builder /app/.env* .
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]

View File

@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,7 +0,0 @@
### Fast GPT V4.4.1
1. 新增 - 知识库目录结构
2. 新增 - 分享链接支持配置 IP 限流、过期时间、最大额度等
3. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/)
4. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
5. [点击查看商业版](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)

View File

@@ -1,16 +0,0 @@
import { GET, POST, DELETE } from './request';
import { UserOpenApiKey } from '@/types/openapi';
/**
* crete a api key
*/
export const createAOpenApiKey = () => POST<string>('/openapi/postKey');
/**
* get api keys
*/
export const getOpenApiKeys = () => GET<UserOpenApiKey[]>('/openapi/getKeys');
/**
* delete api by id
*/
export const delOpenApiById = (id: string) => DELETE(`/openapi/delKey?id=${id}`);

View File

@@ -1,106 +0,0 @@
import { GET, POST, PUT, DELETE } from '../request';
import type { DatasetItemType, KbItemType, KbListItemType, KbPathItemType } from '@/types/plugin';
import { TrainingModeEnum } from '@/constants/plugin';
import {
Props as PushDataProps,
Response as PushDateResponse
} from '@/pages/api/openapi/kb/pushData';
import {
Props as SearchTestProps,
Response as SearchTestResponse
} from '@/pages/api/openapi/kb/searchTest';
import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData';
import type { KbUpdateParams, CreateKbParams, GetKbDataListProps } from '../request/kb';
import { QuoteItemType } from '@/types/chat';
import { KbTypeEnum } from '@/constants/kb';
import { getToken } from '@/utils/user';
import download from 'downloadjs';
/* knowledge base */
export const getKbList = (data: { parentId?: string; type?: `${KbTypeEnum}` }) =>
GET<KbListItemType[]>(`/plugins/kb/list`, data);
export const getAllDataset = () => GET<KbListItemType[]>(`/plugins/kb/allDataset`);
export const getKbPaths = (parentId?: string) =>
GET<KbPathItemType[]>('/plugins/kb/paths', { parentId });
export const getKbById = (id: string) => GET<KbItemType>(`/plugins/kb/detail?id=${id}`);
export const postCreateKb = (data: CreateKbParams) => POST<string>(`/plugins/kb/create`, data);
export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, data);
export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`);
/* kb data */
export const getKbDataList = (data: GetKbDataListProps) =>
POST(`/plugins/kb/data/getDataList`, data);
/**
* export and download data
*/
export const exportDataset = (data: { kbId: string }) =>
fetch(`/api/plugins/kb/data/exportAll?kbId=${data.kbId}`, {
method: 'GET',
headers: {
token: getToken()
}
})
.then(async (res) => {
if (!res.ok) {
const data = await res.json();
throw new Error(data?.message || 'Export failed');
}
return res.blob();
})
.then((blob) => download(blob, 'dataset.csv', 'text/csv'));
/**
* 获取模型正在拆分数据的数量
*/
export const getTrainingData = (data: { kbId: string; init: boolean }) =>
POST<{
qaListLen: number;
vectorListLen: number;
}>(`/plugins/kb/data/getTrainingData`, data);
/* get length of system training queue */
export const getTrainingQueueLen = () => GET<number>(`/plugins/kb/data/getQueueLen`);
export const getKbDataItemById = (dataId: string) =>
GET<QuoteItemType>(`/plugins/kb/data/getDataById`, { dataId });
/**
* 直接push数据
*/
export const postKbDataFromList = (data: PushDataProps) =>
POST<PushDateResponse>(`/openapi/kb/pushData`, data);
/**
* insert one data to dataset
*/
export const insertData2Kb = (data: { kbId: string; data: DatasetItemType }) =>
POST<string>(`/plugins/kb/data/insertData`, data);
/**
* 更新一条数据
*/
export const putKbDataById = (data: UpdateDataProps) => PUT('/openapi/kb/updateData', data);
/**
* 删除一条知识库数据
*/
export const delOneKbDataByDataId = (dataId: string) =>
DELETE(`/openapi/kb/delDataById?dataId=${dataId}`);
/**
* 拆分数据
*/
export const postSplitData = (data: {
kbId: string;
chunks: string[];
prompt: string;
mode: `${TrainingModeEnum}`;
}) => POST(`/openapi/text/pushData`, data);
export const searchText = (data: SearchTestProps) =>
POST<SearchTestResponse>(`/openapi/kb/searchTest`, data);

View File

@@ -1,24 +0,0 @@
import { KbTypeEnum } from '@/constants/kb';
import type { RequestPaging } from '@/types';
export type KbUpdateParams = {
id: string;
parentId?: string;
tags?: string;
name?: string;
avatar?: string;
};
export type CreateKbParams = {
parentId?: string;
name: string;
tags: string[];
avatar: string;
vectorModel?: string;
type: `${KbTypeEnum}`;
};
export type GetKbDataListProps = RequestPaging & {
kbId: string;
searchText: string;
fileId: string;
};

View File

@@ -1,8 +0,0 @@
import { GET, POST } from './request';
export const textCensor = (data: { text: string }) =>
POST<{ code?: number; message: string }>('/plugins/censor/text_baidu', data).then((res) => {
if (res?.code === 5000) {
return Promise.reject(res.message);
}
});

View File

@@ -1,143 +0,0 @@
import React, { useState } from 'react';
import {
Box,
Button,
Flex,
ModalFooter,
ModalBody,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
IconButton
} from '@chakra-ui/react';
import { getOpenApiKeys, createAOpenApiKey, delOpenApiById } from '@/api/openapi';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading';
import dayjs from 'dayjs';
import { AddIcon, DeleteIcon } from '@chakra-ui/icons';
import { getErrText, useCopyData } from '@/utils/tools';
import { useToast } from '@/hooks/useToast';
import MyIcon from '../Icon';
import MyModal from '../MyModal';
const APIKeyModal = ({ onClose }: { onClose: () => void }) => {
const { Loading } = useLoading();
const { toast } = useToast();
const {
data: apiKeys = [],
isLoading: isGetting,
refetch
} = useQuery(['getOpenApiKeys'], getOpenApiKeys);
const [apiKey, setApiKey] = useState('');
const { copyData } = useCopyData();
const { mutate: onclickCreateApiKey, isLoading: isCreating } = useMutation({
mutationFn: () => createAOpenApiKey(),
onSuccess(res) {
setApiKey(res);
refetch();
},
onError(err) {
toast({
status: 'warning',
title: getErrText(err)
});
}
});
const { mutate: onclickRemove, isLoading: isDeleting } = useMutation({
mutationFn: async (id: string) => delOpenApiById(id),
onSuccess() {
refetch();
}
});
return (
<MyModal isOpen onClose={onClose} w={'600px'}>
<Box py={3} px={5}>
<Box fontWeight={'bold'} fontSize={'2xl'}>
API
</Box>
<Box fontSize={'sm'} color={'myGray.600'}>
API 使~
</Box>
</Box>
<ModalBody minH={'300px'} maxH={['70vh', '500px']} overflow={'overlay'}>
<TableContainer mt={2} position={'relative'}>
<Table>
<Thead>
<Tr>
<Th>Api Key</Th>
<Th></Th>
<Th>使</Th>
<Th />
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{apiKeys.map(({ id, apiKey, createTime, lastUsedTime }) => (
<Tr key={id}>
<Td>{apiKey}</Td>
<Td>{dayjs(createTime).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>
{lastUsedTime
? dayjs(lastUsedTime).format('YYYY/MM/DD HH:mm:ss')
: '没有使用过'}
</Td>
<Td>
<IconButton
icon={<DeleteIcon />}
size={'xs'}
aria-label={'delete'}
variant={'base'}
colorScheme={'gray'}
onClick={() => onclickRemove(id)}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</ModalBody>
<ModalFooter>
<Button
variant="base"
leftIcon={<AddIcon color={'myGray.600'} fontSize={'sm'} />}
onClick={() => onclickCreateApiKey()}
>
</Button>
</ModalFooter>
<Loading loading={isGetting || isCreating || isDeleting} fixed={false} />
<MyModal isOpen={!!apiKey} w={'400px'} onClose={() => setApiKey('')}>
<Box py={3} px={5}>
<Box fontWeight={'bold'} fontSize={'2xl'}>
API
</Box>
<Box fontSize={'sm'} color={'myGray.600'}>
~
</Box>
</Box>
<ModalBody>
<Flex bg={'myGray.100'} px={3} py={2} cursor={'pointer'} onClick={() => copyData(apiKey)}>
<Box flex={1}>{apiKey}</Box>
<MyIcon name={'copy'} w={'16px'}></MyIcon>
</Flex>
</ModalBody>
<ModalFooter>
<Button variant="base" onClick={() => setApiKey('')}>
</Button>
</ModalFooter>
</MyModal>
</MyModal>
);
};
export default APIKeyModal;

View File

@@ -1,71 +0,0 @@
import React, { useMemo } from 'react';
import { Box, ModalBody, useTheme, Flex } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@/types/chat';
import { useTranslation } from 'react-i18next';
import MyModal from '../MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
const ResponseModal = ({
response,
onClose
}: {
response: ChatHistoryItemResType[];
onClose: () => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const formatResponse = useMemo(
() =>
response.map((item) => {
const copy = { ...item };
delete copy.completeMessages;
delete copy.quoteList;
return copy;
}),
[response]
);
return (
<MyModal
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
minW={['90vw', '600px']}
title={
<Flex alignItems={'center'}>
{t('chat.Complete Response')}
<MyTooltip
label={
'moduleName: 模型名\nprice: 价格倍率100000\nmodel?: 模型名\ntokens?: token 消耗\n\nanswer?: 回答内容\nquestion?: 问题\ntemperature?: 温度\nmaxToken?: 最大 tokens\n\nsimilarity?: 相似度\nlimit?: 单次搜索结果\n\ncqList?: 问题分类列表\ncqResult?: 分类结果\n\nextractDescription?: 内容提取描述\nextractResult?: 提取结果'
}
>
<QuestionOutlineIcon ml={2} />
</MyTooltip>
</Flex>
}
isCentered
>
<ModalBody>
{formatResponse.map((item, i) => (
<Box
key={i}
p={2}
pt={[0, 2]}
borderRadius={'lg'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}
whiteSpace={'pre-wrap'}
>
{JSON.stringify(item, null, 2)}
</Box>
))}
</ModalBody>
</MyModal>
);
};
export default ResponseModal;

View File

@@ -1,58 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, Chat } from '@/service/mongo';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 1000 } = req.body as { limit: number };
let skip = 0;
const total = await Chat.countDocuments({
chatId: { $exists: false }
});
let promise = Promise.resolve();
console.log(total);
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit, skipVal))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number, skip: number) {
// 遍历 app
const chats = await Chat.find(
{
chatId: { $exists: false }
},
'_id'
).limit(limit);
await Promise.all(
chats.map((chat) =>
Chat.findByIdAndUpdate(chat._id, {
chatId: String(chat._id),
source: 'online'
})
)
);
}

View File

@@ -1,98 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, Chat, ChatItem } from '@/service/mongo';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 100 } = req.body as { limit: number };
let skip = 0;
const total = await Chat.countDocuments({
content: { $exists: true, $not: { $size: 0 } },
isInit: { $ne: true }
});
const totalChat = await Chat.aggregate([
{
$project: {
contentLength: { $size: '$content' }
}
},
{
$group: {
_id: null,
totalLength: { $sum: '$contentLength' }
}
}
]);
console.log('chatLen:', total, totalChat);
let promise = Promise.resolve();
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number) {
// 遍历 app
const chats = await Chat.find(
{
content: { $exists: true, $not: { $size: 0 } },
isInit: { $ne: true }
},
'_id userId appId chatId content'
)
.sort({ updateTime: -1 })
.limit(limit);
await Promise.all(
chats.map(async (chat) => {
const inserts = chat.content
.map((item) => ({
dataId: nanoid(),
chatId: chat.chatId,
userId: chat.userId,
appId: chat.appId,
obj: item.obj,
value: item.value,
responseData: item.responseData
}))
.filter((item) => item.chatId && item.userId && item.appId && item.obj && item.value);
try {
await Promise.all(inserts.map((item) => ChatItem.create(item)));
await Chat.findByIdAndUpdate(chat._id, {
isInit: true
});
} catch (error) {
console.log(error);
await ChatItem.deleteMany({ chatId: chat.chatId });
}
})
);
}

View File

@@ -1,27 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, OutLink } from '@/service/mongo';
import { OutLinkTypeEnum } from '@/constants/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
await OutLink.updateMany(
{},
{
$set: { type: OutLinkTypeEnum.share }
}
);
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -1,446 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, App } from '@/service/mongo';
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { FlowInputItemType } from '@/types/flow';
const chatModelInput = ({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt,
kbList
}: {
model: string;
temperature: number;
maxToken: number;
systemPrompt: string;
limitPrompt: string;
kbList: { kbId: string }[];
}): FlowInputItemType[] => [
{
key: 'model',
value: model,
type: 'custom',
label: '对话模型',
connected: true
},
{
key: 'temperature',
value: temperature,
label: '温度',
type: 'slider',
connected: true
},
{
key: 'maxToken',
value: maxToken,
type: 'custom',
label: '回复上限',
connected: true
},
{
key: 'systemPrompt',
value: systemPrompt,
type: 'textarea',
label: '系统提示词',
connected: true
},
{
key: 'limitPrompt',
label: '限定词',
type: 'textarea',
value: limitPrompt,
connected: true
},
{
key: 'switch',
type: 'target',
label: '触发器',
connected: kbList.length > 0
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
connected: kbList.length > 0
},
{
key: 'history',
type: 'target',
label: '聊天记录',
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: '用户问题',
connected: true
}
];
const chatTemplate = ({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt
}: {
model: string;
temperature: number;
maxToken: number;
systemPrompt: string;
limitPrompt: string;
}) => {
return [
{
flowType: FlowModuleTypeEnum.questionInput,
inputs: [
{
key: 'userChatInput',
connected: true
}
],
outputs: [
{
key: 'userChatInput',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
},
{
flowType: FlowModuleTypeEnum.historyNode,
inputs: [
{
key: 'maxContext',
value: 10,
connected: true
},
{
key: 'history',
connected: true
}
],
outputs: [
{
key: 'history',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
],
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
moduleId: 'history'
},
{
flowType: FlowModuleTypeEnum.chatNode,
inputs: chatModelInput({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt,
kbList: []
}),
outputs: [
{
key: TaskResponseKeyEnum.answerText,
targets: []
}
],
position: {
x: 981.9682828103937,
y: 890.014595014464
},
moduleId: 'chatModule'
}
];
};
const kbTemplate = ({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt,
kbList = [],
searchSimilarity,
searchLimit,
searchEmptyText
}: {
model: string;
temperature: number;
maxToken: number;
systemPrompt: string;
limitPrompt: string;
kbList: { kbId: string }[];
searchSimilarity: number;
searchLimit: number;
searchEmptyText: string;
}) => {
return [
{
flowType: FlowModuleTypeEnum.questionInput,
inputs: [
{
key: 'userChatInput',
connected: true
}
],
outputs: [
{
key: 'userChatInput',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
},
{
moduleId: 'kbSearch',
key: 'userChatInput'
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
},
{
flowType: FlowModuleTypeEnum.historyNode,
inputs: [
{
key: 'maxContext',
value: 10,
connected: true
},
{
key: 'history',
connected: true
}
],
outputs: [
{
key: 'history',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
],
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
moduleId: 'history'
},
{
flowType: FlowModuleTypeEnum.kbSearchNode,
inputs: [
{
key: 'kbList',
value: kbList,
connected: true
},
{
key: 'similarity',
value: searchSimilarity,
connected: true
},
{
key: 'limit',
value: searchLimit,
connected: true
},
{
key: 'switch',
connected: false
},
{
key: 'userChatInput',
connected: true
}
],
outputs: [
{
key: 'isEmpty',
targets: searchEmptyText
? [
{
moduleId: 'emptyText',
key: 'switch'
}
]
: [
{
moduleId: 'chatModule',
key: 'switch'
}
]
},
{
key: 'unEmpty',
targets: [
{
moduleId: 'chatModule',
key: 'switch'
}
]
},
{
key: 'quoteQA',
targets: [
{
moduleId: 'chatModule',
key: 'quoteQA'
}
]
}
],
position: {
x: 956.0838440206068,
y: 887.462827870246
},
moduleId: 'kbSearch'
},
...(searchEmptyText
? [
{
flowType: FlowModuleTypeEnum.answerNode,
inputs: [
{
key: 'switch',
connected: true
},
{
key: SpecialInputKeyEnum.answerText,
value: searchEmptyText,
connected: true
}
],
outputs: [],
position: {
x: 1553.5815811529146,
y: 637.8753731306779
},
moduleId: 'emptyText'
}
]
: []),
{
flowType: FlowModuleTypeEnum.chatNode,
inputs: chatModelInput({ model, temperature, maxToken, systemPrompt, limitPrompt, kbList }),
outputs: [
{
key: TaskResponseKeyEnum.answerText,
targets: []
}
],
position: {
x: 1551.71405495818,
y: 977.4911578918461
},
moduleId: 'chatModule'
}
];
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 1000 } = req.body as { limit: number };
let skip = 0;
const total = await App.countDocuments();
let promise = Promise.resolve();
console.log(total);
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit, skipVal))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number, skip: number) {
// 遍历 app
const apps = await App.find(
{
chat: { $ne: null },
modules: { $exists: false }
// userId: '63f9a14228d2a688d8dc9e1b'
},
'_id chat'
).limit(limit);
return Promise.all(
apps.map(async (app) => {
if (!app.chat) return app;
const modules = (() => {
if (app.chat.relatedKbs.length === 0) {
return chatTemplate({
model: app.chat.chatModel,
temperature: app.chat.temperature,
maxToken: app.chat.maxToken,
systemPrompt: app.chat.systemPrompt,
limitPrompt: app.chat.limitPrompt
});
} else {
return kbTemplate({
model: app.chat.chatModel,
temperature: app.chat.temperature,
maxToken: app.chat.maxToken,
systemPrompt: app.chat.systemPrompt,
limitPrompt: app.chat.limitPrompt,
kbList: app.chat.relatedKbs.map((id) => ({ kbId: id })),
searchEmptyText: app.chat.searchEmptyText,
searchLimit: app.chat.searchLimit,
searchSimilarity: app.chat.searchSimilarity
});
}
})();
await App.findByIdAndUpdate(app.id, {
modules
});
return modules;
})
);
}

View File

@@ -1,37 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, OpenApi } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { UserOpenApiKey } from '@/types/openapi';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const findResponse = await OpenApi.find({ userId }).sort({ _id: -1 });
// jus save four data
const apiKeys = findResponse.map<UserOpenApiKey>(
({ _id, apiKey, createTime, lastUsedTime }) => {
return {
id: _id,
apiKey: `******${apiKey.substring(apiKey.length - 4)}`,
createTime,
lastUsedTime
};
}
);
jsonRes(res, {
data: apiKeys
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,86 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authKb, authUser } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
import { PgDatasetTableName } from '@/constants/plugin';
import { insertKbItem, PgClient } from '@/service/pg';
import { getVectorModel } from '@/service/utils/data';
import { getVector } from '@/pages/api/openapi/plugin/vector';
import { DatasetItemType } from '@/types/plugin';
import { countPromptTokens } from '@/utils/common/tiktoken';
export type Props = {
kbId: string;
data: DatasetItemType;
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { kbId, data = { q: '', a: '' } } = req.body as Props;
if (!kbId || !data?.q) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req });
// auth kb
const kb = await authKb({ kbId, userId });
const q = data?.q?.replace(/\\n/g, '\n').trim().replace(/'/g, '"');
const a = data?.a?.replace(/\\n/g, '\n').trim().replace(/'/g, '"');
// token check
const token = countPromptTokens(q, 'system');
if (token > getVectorModel(kb.vectorModel).maxToken) {
throw new Error('Over Tokens');
}
const { rows: existsRows } = await PgClient.query(`
SELECT COUNT(*) > 0 AS exists
FROM ${PgDatasetTableName}
WHERE md5(q)=md5('${q}') AND md5(a)=md5('${a}') AND user_id='${userId}' AND kb_id='${kbId}'
`);
const exists = existsRows[0]?.exists || false;
if (exists) {
throw new Error('已经存在完全一致的数据');
}
const { vectors } = await getVector({
model: kb.vectorModel,
input: [q],
userId
});
const response = await insertKbItem({
userId,
kbId,
data: [
{
q,
a,
source: data.source,
vector: vectors[0]
}
]
});
// @ts-ignore
const id = response?.rows?.[0]?.id || '';
jsonRes(res, {
data: id
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});

View File

@@ -1,96 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Box, Divider, Flex, useTheme, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools';
import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import { useGlobalStore } from '@/store/global';
import { feConfigs } from '@/store/static';
const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), {
ssr: false
});
const API = ({ appId }: { appId: string }) => {
const theme = useTheme();
const { copyData } = useCopyData();
const [baseUrl, setBaseUrl] = useState('https://fastgpt.run/api/openapi');
const {
isOpen: isOpenAPIModal,
onOpen: onOpenAPIModal,
onClose: onCloseAPIModal
} = useDisclosure();
const [isLoaded, setIsLoaded] = useState(false);
const { isPc } = useGlobalStore();
useEffect(() => {
setBaseUrl(`${location.origin}/api/openapi`);
}, []);
return (
<Flex flexDirection={'column'} pt={[0, 5]} h={'100%'}>
<Flex px={5} alignItems={'center'}>
<Box flex={1}>
AppId:
<Box
as={'span'}
ml={2}
fontWeight={'bold'}
cursor={'pointer'}
onClick={() => copyData(appId, '已复制 AppId')}
>
{appId}
</Box>
</Box>
{isPc && (
<>
<Flex
bg={'myWhite.600'}
py={2}
px={4}
borderRadius={'md'}
cursor={'pointer'}
onClick={() => copyData(baseUrl, '已复制 API 地址')}
>
<Box border={theme.borders.md} px={2} borderRadius={'md'} fontSize={'sm'}>
API服务器
</Box>
<Box ml={2} color={'myGray.900'} fontSize={['sm', 'md']}>
{baseUrl}
</Box>
</Flex>
<Button
ml={3}
leftIcon={<MyIcon name={'apikey'} w={'16px'} color={''} />}
variant={'base'}
onClick={onOpenAPIModal}
>
API
</Button>
</>
)}
</Flex>
<Divider mt={3} />
<Box flex={'1 0 0'} h={0}>
<Skeleton h="100%" isLoaded={isLoaded} fadeDuration={2}>
<iframe
style={{
width: '100%',
height: '100%'
}}
src={
feConfigs?.openAPIUrl ||
'https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh'
}
frameBorder="0"
onLoad={() => setIsLoaded(true)}
onError={() => setIsLoaded(true)}
/>
</Skeleton>
</Box>
{isOpenAPIModal && <APIKeyModal onClose={onCloseAPIModal} />}
</Flex>
);
};
export default API;

View File

@@ -1,59 +0,0 @@
import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Flex, Textarea } from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import Container from '../modules/Container';
import { SystemInputEnum } from '@/constants/app';
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { welcomeTextTip } from '@/constants/flow/ModuleTemplate';
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, moduleId, onChangeNode } = data;
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
[inputs]
);
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<>
<Flex mb={1} alignItems={'center'}>
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
<Box></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
{welcomeText && (
<Textarea
className="nodrag"
rows={6}
resize={'both'}
defaultValue={welcomeText.value}
bg={'myWhite.500'}
placeholder={welcomeTextTip}
onChange={(e) => {
onChangeNode({
moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: {
...welcomeText,
value: e.target.value
}
});
}}
/>
)}
</>
</Container>
</NodeCard>
</>
);
};
export default React.memo(NodeUserGuide);

View File

@@ -1,373 +0,0 @@
import React, { useMemo, useState } from 'react';
import {
Card,
Flex,
Box,
Button,
ModalBody,
ModalHeader,
ModalFooter,
useTheme,
Textarea,
Grid,
Divider
} from '@chakra-ui/react';
import { getKbPaths } from '@/api/plugins/kb';
import Avatar from '@/components/Avatar';
import { useForm } from 'react-hook-form';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import type { SelectedKbType } from '@/types/plugin';
import { useGlobalStore } from '@/store/global';
import { useToast } from '@/hooks/useToast';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyIcon from '@/components/Icon';
import { KbTypeEnum } from '@/constants/kb';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { useDatasetStore } from '@/store/dataset';
import { feConfigs } from '@/store/static';
export type KbParamsType = {
searchSimilarity: number;
searchLimit: number;
searchEmptyText: string;
};
export const KBSelectModal = ({
activeKbs = [],
onChange,
onClose
}: {
activeKbs: SelectedKbType;
onChange: (e: SelectedKbType) => void;
onClose: () => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const [selectedKbList, setSelectedKbList] = useState<SelectedKbType>(activeKbs);
const { isPc } = useGlobalStore();
const { toast } = useToast();
const [parentId, setParentId] = useState<string>();
const { myKbList, loadKbList, datasets, loadAllDatasets } = useDatasetStore();
const { data } = useQuery(['loadKbList', parentId], () => {
return Promise.all([loadKbList(parentId), getKbPaths(parentId)]);
});
useQuery(['loadAllDatasets'], loadAllDatasets);
const paths = useMemo(
() => [
{
parentId: '',
parentName: t('kb.My Dataset')
},
...(data?.[1] || [])
],
[data, t]
);
const filterKbList = useMemo(() => {
return {
selected: datasets.filter((item) => selectedKbList.find((kb) => kb.kbId === item._id)),
unSelected: myKbList.filter((item) => !selectedKbList.find((kb) => kb.kbId === item._id))
};
}, [myKbList, datasets, selectedKbList]);
return (
<MyModal
isOpen={true}
isCentered={!isPc}
maxW={['90vw', '800px']}
w={'800px'}
onClose={onClose}
>
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalHeader>
{!!parentId ? (
<Flex
flex={1}
userSelect={'none'}
fontSize={['sm', 'lg']}
fontWeight={'normal'}
color={'myGray.900'}
>
{paths.map((item, i) => (
<Flex key={item.parentId} mr={2} alignItems={'center'}>
<Box
fontSize={'lg'}
borderRadius={'md'}
{...(i === paths.length - 1
? {
cursor: 'default'
}
: {
cursor: 'pointer',
_hover: {
color: 'myBlue.600'
},
onClick: () => {
setParentId(item.parentId);
}
})}
>
{item.parentName}
</Box>
{i !== paths.length - 1 && (
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
)}
</Flex>
))}
</Flex>
) : (
<Box>({selectedKbList.length})</Box>
)}
{isPc && (
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
</Box>
)}
</ModalHeader>
<ModalBody
flex={['1 0 0', '0 0 auto']}
maxH={'80vh'}
overflowY={'auto'}
display={'grid'}
userSelect={'none'}
>
<Grid
h={'auto'}
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={3}
>
{filterKbList.selected.map((item) =>
(() => {
return (
<Card
key={item._id}
p={3}
border={theme.borders.base}
boxShadow={'sm'}
bg={'myBlue.300'}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['24px', '28px']}></Avatar>
<Box flex={'1 0 0'} mx={3}>
{item.name}
</Box>
<MyIcon
name={'delete'}
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'red.500' }}
onClick={() => {
setSelectedKbList((state) => state.filter((kb) => kb.kbId !== item._id));
}}
/>
</Flex>
</Card>
);
})()
)}
</Grid>
{filterKbList.selected.length > 0 && <Divider my={3} />}
<Grid
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={3}
>
{filterKbList.unSelected.map((item) =>
(() => {
return (
<MyTooltip
key={item._id}
label={
item.type === KbTypeEnum.dataset
? t('kb.Select Dataset')
: t('kb.Select Folder')
}
>
<Card
p={3}
border={theme.borders.base}
boxShadow={'sm'}
h={'80px'}
cursor={'pointer'}
_hover={{
boxShadow: 'md'
}}
onClick={() => {
if (item.type === KbTypeEnum.folder) {
setParentId(item._id);
} else if (item.type === KbTypeEnum.dataset) {
const vectorModel = selectedKbList[0]?.vectorModel?.model;
if (vectorModel && vectorModel !== item.vectorModel.model) {
return toast({
status: 'warning',
title: '仅能选择同一个索引模型的知识库'
});
}
setSelectedKbList((state) => [
...state,
{ kbId: item._id, vectorModel: item.vectorModel }
]);
}
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['24px', '28px']}></Avatar>
<Box
className="textEllipsis"
ml={3}
fontWeight={'bold'}
fontSize={['md', 'lg', 'xl']}
>
{item.name}
</Box>
</Flex>
<Flex justifyContent={'flex-end'} alignItems={'center'} fontSize={'sm'}>
{item.type === KbTypeEnum.folder ? (
<Box color={'myGray.500'}>{t('Folder')}</Box>
) : (
<>
<MyIcon mr={1} name="kbTest" w={'12px'} />
<Box color={'myGray.500'}>{item.vectorModel.name}</Box>
</>
)}
</Flex>
</Card>
</MyTooltip>
);
})()
)}
</Grid>
{filterKbList.unSelected.length === 0 && (
<Flex mt={5} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
西~
</Box>
</Flex>
)}
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
// filter out the kb that is not in the kList
const filterKbList = selectedKbList.filter((kb) => {
return datasets.find((item) => item._id === kb.kbId);
});
onClose();
onChange(filterKbList);
}}
>
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export const KbParamsModal = ({
searchEmptyText,
searchLimit,
searchSimilarity,
onClose,
onChange
}: KbParamsType & { onClose: () => void; onChange: (e: KbParamsType) => void }) => {
const [refresh, setRefresh] = useState(false);
const { register, setValue, getValues, handleSubmit } = useForm<KbParamsType>({
defaultValues: {
searchEmptyText,
searchLimit,
searchSimilarity
}
});
return (
<MyModal isOpen={true} onClose={onClose} title={'搜索参数调整'} minW={['90vw', '600px']}>
<Flex flexDirection={'column'}>
<ModalBody>
<Box display={['block', 'flex']} py={5} pt={[0, 5]}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
<MyTooltip
label={'不同索引模型的相似度有区别,请通过搜索测试来选择合适的数值'}
forceShow
>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<MySlider
markList={[
{ label: '0', value: 0 },
{ label: '1', value: 1 }
]}
min={0}
max={1}
step={0.01}
value={getValues('searchSimilarity')}
onChange={(val) => {
setValue('searchSimilarity', val);
setRefresh(!refresh);
}}
/>
</Box>
<Box display={['block', 'flex']} py={8}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
</Box>
<Box flex={1}>
<MySlider
markList={[
{ label: '1', value: 1 },
{ label: '20', value: 20 }
]}
min={1}
max={20}
value={getValues('searchLimit')}
onChange={(val) => {
setValue('searchLimit', val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 100px'} mb={[2, 0]}>
</Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={`若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文${feConfigs?.systemTitle} 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。`}
{...register('searchEmptyText')}
></Textarea>
</Box>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
</Button>
<Button
onClick={() => {
onClose();
handleSubmit(onChange)();
}}
>
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export default KBSelectModal;

View File

@@ -1,175 +0,0 @@
import { connectToDatabase, Bill, User, OutLink } from '../mongo';
import { BillSourceEnum } from '@/constants/user';
import { getModel } from '../utils/data';
import { ChatHistoryItemResType } from '@/types/chat';
import { formatPrice } from '@/utils/user';
import { addLog } from '../utils/tools';
export const pushTaskBill = async ({
appName,
appId,
userId,
source,
shareId,
response
}: {
appName: string;
appId: string;
userId: string;
source: `${BillSourceEnum}`;
shareId?: string;
response: ChatHistoryItemResType[];
}) => {
const total = response.reduce((sum, item) => sum + item.price, 0);
await Promise.allSettled([
Bill.create({
userId,
appName,
appId,
total,
source,
list: response.map((item) => ({
moduleName: item.moduleName,
amount: item.price || 0,
model: item.model,
tokenLen: item.tokens
}))
}),
User.findByIdAndUpdate(userId, {
$inc: { balance: -total }
}),
...(shareId
? [
updateShareChatBill({
shareId,
total
})
]
: [])
]);
addLog.info(`finish completions`, {
source,
userId,
price: formatPrice(total)
});
};
export const updateShareChatBill = async ({
shareId,
total
}: {
shareId: string;
total: number;
}) => {
try {
await OutLink.findOneAndUpdate(
{ shareId },
{
$inc: { total },
lastTime: new Date()
}
);
} catch (err) {
addLog.error('update shareChat error', err);
}
};
export const pushQABill = async ({
userId,
totalTokens,
appName
}: {
userId: string;
totalTokens: number;
appName: string;
}) => {
addLog.info('splitData generate success', { totalTokens });
let billId;
try {
await connectToDatabase();
// 获取模型单价格, 都是用 gpt35 拆分
const unitPrice = global.qaModel.price || 3;
// 计算价格
const total = unitPrice * totalTokens;
// 插入 Bill 记录
const res = await Bill.create({
userId,
appName,
tokenLen: totalTokens,
total
});
billId = res._id;
// 账号扣费
await User.findByIdAndUpdate(userId, {
$inc: { balance: -total }
});
} catch (err) {
addLog.error('Create completions bill error', err);
billId && Bill.findByIdAndDelete(billId);
}
};
export const pushGenerateVectorBill = async ({
userId,
tokenLen,
model
}: {
userId: string;
tokenLen: number;
model: string;
}) => {
let billId;
try {
await connectToDatabase();
try {
// 计算价格. 至少为1
const vectorModel =
global.vectorModels.find((item) => item.model === model) || global.vectorModels[0];
const unitPrice = vectorModel.price || 0.2;
let total = unitPrice * tokenLen;
total = total > 1 ? total : 1;
// 插入 Bill 记录
const res = await Bill.create({
userId,
model: vectorModel.model,
appName: '索引生成',
total,
list: [
{
moduleName: '索引生成',
amount: total,
model: vectorModel.model,
tokenLen
}
]
});
billId = res._id;
// 账号扣费
await User.findByIdAndUpdate(userId, {
$inc: { balance: -total }
});
} catch (err) {
addLog.error('Create generateVector bill error', err);
billId && Bill.findByIdAndDelete(billId);
}
} catch (error) {
console.log(error);
}
};
export const countModelPrice = ({ model, tokens }: { model: string; tokens: number }) => {
const modelData = getModel(model);
if (!modelData) return 0;
return modelData.price * tokens;
};

View File

@@ -1,23 +0,0 @@
import { Schema, model, models, Model } from 'mongoose';
import { OpenApiSchema } from '@/types/mongoSchema';
const OpenApiSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
apiKey: {
type: String,
required: true
},
createTime: {
type: Date,
default: () => new Date()
},
lastUsedTime: {
type: Date
}
});
export const OpenApi: Model<OpenApiSchema> = models['openapi'] || model('openapi', OpenApiSchema);

View File

@@ -1,106 +0,0 @@
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { ChatContextFilter } from '@/service/common/tiktoken';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { countModelPrice } from '@/service/events/pushBill';
import { UserModelSchema } from '@/types/mongoSchema';
import { getModel } from '@/service/utils/data';
import { SystemInputEnum } from '@/constants/app';
import { SpecialInputKeyEnum } from '@/constants/flow';
export type CQProps = {
systemPrompt?: string;
history?: ChatItemType[];
[SystemInputEnum.userChatInput]: string;
userOpenaiAccount: UserModelSchema['openaiAccount'];
[SpecialInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
};
export type CQResponse = {
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
[key: string]: any;
};
const agentModel = 'gpt-3.5-turbo';
const agentFunName = 'agent_user_question';
const maxTokens = 3000;
/* request openai chat */
export const dispatchClassifyQuestion = async (props: Record<string, any>): Promise<CQResponse> => {
const { agents, systemPrompt, history = [], userChatInput, userOpenaiAccount } = props as CQProps;
if (!userChatInput) {
return Promise.reject('Input is empty');
}
const messages: ChatItemType[] = [
...(systemPrompt
? [
{
obj: ChatRoleEnum.System,
value: systemPrompt
}
]
: []),
...history,
{
obj: ChatRoleEnum.Human,
value: userChatInput
}
];
const filterMessages = ChatContextFilter({
messages,
maxTokens
});
const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false });
// function body
const agentFunction = {
name: agentFunName,
description: '判断用户问题的类型属于哪方面,返回对应的枚举字段',
parameters: {
type: 'object',
properties: {
type: {
type: 'string',
description: agents.map((item) => `${item.value},返回:'${item.key}'`).join(''),
enum: agents.map((item) => item.key)
}
},
required: ['type']
}
};
const chatAPI = getAIChatApi(userOpenaiAccount);
const response = await chatAPI.createChatCompletion(
{
model: agentModel,
temperature: 0,
messages: [...adaptMessages],
function_call: { name: agentFunName },
functions: [agentFunction]
},
{
...axiosConfig(userOpenaiAccount)
}
);
const arg = JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '');
const tokens = response.data.usage?.total_tokens || 0;
const result = agents.find((item) => item.key === arg?.type) || agents[0];
return {
[result.key]: 1,
[TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.CQ,
price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }),
model: getModel(agentModel)?.name || agentModel,
tokens,
cqList: agents,
cqResult: result.value
}
};
};

View File

@@ -1,129 +0,0 @@
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { ChatContextFilter } from '@/service/common/tiktoken';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import type { ContextExtractAgentItemType } from '@/types/app';
import { ContextExtractEnum } from '@/constants/flow/flowField';
import { countModelPrice } from '@/service/events/pushBill';
import { UserModelSchema } from '@/types/mongoSchema';
import { getModel } from '@/service/utils/data';
export type Props = {
userOpenaiAccount: UserModelSchema['openaiAccount'];
history?: ChatItemType[];
[ContextExtractEnum.content]: string;
[ContextExtractEnum.extractKeys]: ContextExtractAgentItemType[];
[ContextExtractEnum.description]: string;
};
export type Response = {
[ContextExtractEnum.success]?: boolean;
[ContextExtractEnum.failed]?: boolean;
[ContextExtractEnum.fields]: string;
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
};
const agentModel = 'gpt-3.5-turbo';
const agentFunName = 'agent_extract_data';
const maxTokens = 4000;
export async function dispatchContentExtract({
userOpenaiAccount,
content,
extractKeys,
history = [],
description
}: Props): Promise<Response> {
if (!content) {
return Promise.reject('Input is empty');
}
const messages: ChatItemType[] = [
...history,
{
obj: ChatRoleEnum.Human,
value: content
}
];
const filterMessages = ChatContextFilter({
messages,
maxTokens
});
const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false });
const properties: Record<
string,
{
type: string;
description: string;
}
> = {};
extractKeys.forEach((item) => {
properties[item.key] = {
type: 'string',
description: item.desc
};
});
// function body
const agentFunction = {
name: agentFunName,
description: `${description}\n如果内容不存在返回空字符串。`,
parameters: {
type: 'object',
properties,
required: extractKeys.filter((item) => item.required).map((item) => item.key)
}
};
const chatAPI = getAIChatApi(userOpenaiAccount);
const response = await chatAPI.createChatCompletion(
{
model: agentModel,
temperature: 0,
messages: [...adaptMessages],
function_call: { name: agentFunName },
functions: [agentFunction]
},
{
...axiosConfig(userOpenaiAccount)
}
);
const arg: Record<string, any> = (() => {
try {
return JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '{}');
} catch (error) {
return {};
}
})();
// auth fields
let success = !extractKeys.find((item) => !arg[item.key]);
// auth empty value
if (success) {
for (const key in arg) {
if (arg[key] === '') {
success = false;
break;
}
}
}
const tokens = response.data.usage?.total_tokens || 0;
return {
[ContextExtractEnum.success]: success ? true : undefined,
[ContextExtractEnum.failed]: success ? undefined : true,
[ContextExtractEnum.fields]: JSON.stringify(arg),
...arg,
[TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.Extract,
price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }),
model: getModel(agentModel)?.name || agentModel,
tokens,
extractDescription: description,
extractResult: arg
}
};
}

View File

@@ -1,79 +0,0 @@
import { PRICE_SCALE } from '@/constants/common';
import { IpLimit } from '@/service/common/ipLimit/schema';
import { authBalanceByUid, AuthUserTypeEnum } from '@/service/utils/auth';
import { OutLinkSchema } from '@/types/support/outLink';
import { OutLink } from './schema';
export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: string | null }) {
// get outLink
const outLink = await OutLink.findOne({
shareId
});
if (!outLink) {
return Promise.reject('分享链接无效');
}
const uid = String(outLink.userId);
// authBalance
const user = await authBalanceByUid(uid);
// limit auth
await authOutLinkLimit({ outLink, ip });
return {
user,
userId: String(outLink.userId),
appId: String(outLink.appId),
authType: AuthUserTypeEnum.token,
responseDetail: outLink.responseDetail
};
}
export async function authOutLinkLimit({
outLink,
ip
}: {
outLink: OutLinkSchema;
ip?: string | null;
}) {
if (!ip || !outLink.limit) {
return;
}
if (outLink.limit.expiredTime && outLink.limit.expiredTime.getTime() < Date.now()) {
return Promise.reject('分享链接已过期');
}
if (outLink.limit.credit > -1 && outLink.total > outLink.limit.credit * PRICE_SCALE) {
return Promise.reject('链接超出使用限制');
}
const ipLimit = await IpLimit.findOne({ ip, eventId: outLink._id });
try {
if (!ipLimit) {
await IpLimit.create({
eventId: outLink._id,
ip,
account: outLink.limit.QPM - 1
});
return;
}
// over one minute
const diffTime = Date.now() - ipLimit.lastMinute.getTime();
if (diffTime >= 60 * 1000) {
ipLimit.account = outLink.limit.QPM - 1;
ipLimit.lastMinute = new Date();
return await ipLimit.save();
}
if (ipLimit.account <= 0) {
return Promise.reject(
`每分钟仅能请求 ${outLink.limit.QPM} 次, ${60 - Math.round(diffTime / 1000)}s 后重试~`
);
}
ipLimit.account = ipLimit.account - 1;
await ipLimit.save();
} catch (error) {}
}

View File

@@ -1,6 +0,0 @@
export interface UserOpenApiKey {
id: string;
apiKey: string;
createTime: Date;
lastUsedTime?: Date;
}

View File

@@ -1,65 +0,0 @@
import { FileStatusEnum } from '@/constants/kb';
import { VectorModelItemType } from './model';
import type { kbSchema } from './mongoSchema';
export type SelectedKbType = { kbId: string; vectorModel: VectorModelItemType }[];
export type KbListItemType = Omit<kbSchema, 'vectorModel'> & {
vectorModel: VectorModelItemType;
};
export type KbPathItemType = {
parentId: string;
parentName: string;
};
/* kb type */
export interface KbItemType {
_id: string;
avatar: string;
name: string;
userId: string;
vectorModel: VectorModelItemType;
tags: string;
}
export type KbFileItemType = {
id: string;
size: number;
filename: string;
uploadTime: Date;
chunkLength: number;
status: `${FileStatusEnum}`;
};
export type DatasetItemType = {
q: string; // 提问词
a: string; // 原文
source?: string;
file_id?: string;
};
export type KbDataItemType = DatasetItemType & {
id: string;
};
export type KbTestItemType = {
id: string;
kbId: string;
text: string;
time: Date;
results: (KbDataItemType & { score: number })[];
};
export type FetchResultItem = {
url: string;
content: string;
};
export type FileInfo = {
id: string;
filename: string;
size: number;
contentType: string;
encoding: string;
uploadDate: Date;
};

View File

@@ -38,6 +38,11 @@ footer a:hover {
}
*/
.medium-zoom-overlay,
.medium-zoom-image--opened {
z-index: 1999;
}
/* 徽章样式 */
.github-badge {
display: inline-block;

View File

@@ -48,15 +48,30 @@ $code-block-padding-top: {{ if eq .Site.Params.docs.prism true -}}0{{ else }}1.2
@import "custom/components/forms";
@import "custom/components/table";
@import "custom/components/tabs";
@import "custom/components/tooltip";
// Pages
@import "custom/pages/features";
@import "custom/pages/helper";
// Plugins
{{ if eq .Site.Params.docs.prism true -}}@import "custom/plugins/prism/prism";{{ end }}
// Prism / Chroma
{{- if eq .Site.Params.docs.prism true }}
@import {{ printf "'%s%s'" "custom/plugins/prism/themes/" (.Site.Params.docs.prismTheme | default "lotusdocs") }}; // current prism theme
@import "custom/plugins/prism/prism";
{{- else }}
@import "custom/plugins/chroma/default";
{{- end -}}
// FlexSearch
{{ if or (not (isset .Site.Params.flexsearch "enabled")) (eq .Site.Params.flexsearch.enabled true) -}}@import "custom/plugins/flexsearch/flexsearch";{{ end }}
// Feedback Widget
{{ if .Site.Params.feedback.enabled | default false -}}@import "custom/plugins/feedback/feedback";{{ end}}
// Mermaid
@import "custom/plugins/mermaid/mermaid";
// change
@import "custom/pages/custom";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -3,8 +3,8 @@
"baseUrl": ".",
"paths": {
"*": [
"../../../../../Library/Caches/hugo_cache/modules/filecache/modules/pkg/mod/github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2@v2.21100.20000/package/dist/cjs/popper.js/*",
"../../../../../Library/Caches/hugo_cache/modules/filecache/modules/pkg/mod/github.com/twbs/bootstrap@v5.3.0+incompatible/js/*"
"../../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2@v2.21100.20000/package/dist/cjs/popper.js/*",
"../../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/twbs/bootstrap@v5.3.0+incompatible/js/*"
]
}
}

View File

@@ -20,20 +20,24 @@ weight: 20
1. 自定义 title 和 logo
2. 用户注册,支付 (已有微信扫码支付,后续会补充支付方式)
3. 团队空间 (下期开发)
4. 完善的 OpenAPI
5. 高级编排额外插件
6. 后台管理系统 (已有,持续更新
3. API 访问限制,可配置:额度、过期时间
4. 团队空间 (计划)
5. 完善的 OpenAPI计划
6. 高级编排额外插件(计划
7. 后台管理系统
a. 查询:用户、支付、应用、知识库
b. 变更:用户
c. 新增:用户
{{% /alert %}}
### 商业版定价
#### 交付费用
+ 使用 [Sealos 公有云](https://sealos.io)交付1w/年/套 (直接在 Sealos 公有云充值,便可**免费获取 FastGPT 商业版 License**,同时您充值的金额可用于部署其他云资源,相当于白嫖了一个 FastGPT 商业版)。
+ 渠道商使用 Sealos 交付:返现 20% 成交额。
+ 私有服务器交付2w/年/套(如需部署支持,按技术服务费计算)
+ 渠道商私有服务器交付1.3w/年/套(渠道商合同单独约谈,累计 5 套以上可签)
+ 使用 [Sealos 公有云](https://sealos.io)部署1万元/年/套 (无部署费用。赠送 8000 sealos 公有云额度,可用于 FastGPT 或其他云资源)。
+ 渠道商使用 Sealos 部署:返现 20% 成交额。
+ 私有服务器部署2万元/年/套(如需部署支持,按技术服务费计算)
+ 渠道商私有服务器部署1.3万元/年/套(渠道商合同单独约谈,累计 5 套以上可签)
#### 用户注册数量费用(按注册量算,不计量分享和 API
@@ -69,7 +73,8 @@ weight: 20
## 联系方式
通过邮箱联系 yujinlong@sealos.io
微信: allence1004
邮箱: yujinlong@sealos.io
## QA
@@ -77,7 +82,7 @@ weight: 20
完整版应用 = 开源版镜像 + 商业版镜像
我们会提供一个商业版镜像给你使用,还会提供一个简单的后台管理系统(目前只设置了简单的查询功能)
我们会提供一个商业版镜像给你使用,该镜像需要一个 license 启动license 有效期为 1 年。此外,还会提供一个简单的后台管理系统(目前只设置了简单的查询功能)
2. 二次开发如何操作?

View File

@@ -62,19 +62,18 @@ Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 和 M3E 模型:
```json
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"VectorModels": [
{
"model": "text-embedding-ada-002",

View File

@@ -48,8 +48,8 @@ ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本,
1. 根据上面的环境配置配置好环境,具体教程自行 GPT
2. 下载 [python 文件](https://github.com/labring/FastGPT/blob/main/files/models/ChatGLM2/openai_api.py)
3. 在命令行输入命令 `pip install -r requirments.txt`
4. 打开你需要启动的 py 文件,在代码的第 76 行配置 token这里的 token 只是加一层验证,防止接口被人盗用;
5. 执行命令 `python openai_api.py 16`。这里的数字根据上面的配置进行选择。
4. 打开你需要启动的 py 文件,在代码的 `verify_token` 方法中配置 token这里的 token 只是加一层验证,防止接口被人盗用;
5. 执行命令 `python openai_api.py --model_name 16`。这里的数字根据上面的配置进行选择。
然后等待模型下载,直到模型加载完毕为止。如果出现报错先问 GPT。
@@ -99,21 +99,21 @@ Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One
## 接入 FastGPT
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 和 M3E 模型:
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 模型:
```json
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
]
```
## 测试使用

View File

@@ -66,7 +66,7 @@ Authorization 为 sk-key。model 为刚刚在 One API 填写的自定义模型
"defaultToken": 500,
"maxToken": 1800
}
],
]
```
## 测试使用

View File

@@ -7,7 +7,7 @@ toc: true
weight: 520
---
由于环境变量不利于配置复杂的内容,新版 FastGPT 采用了 ConfigMap 的形式挂载配置文件,你可以在 `client/data/config.json` 看到默认的配置文件。可以参考 [docker-compose 快速部署](/docs/installation/docker/) 来挂载配置文件。
由于环境变量不利于配置复杂的内容,新版 FastGPT 采用了 ConfigMap 的形式挂载配置文件,你可以在 `projects/app/data/config.json` 看到默认的配置文件。可以参考 [docker-compose 快速部署](/docs/installation/docker/) 来挂载配置文件。
**开发环境下**,你需要将示例配置文件 `config.json` 复制成 `config.local.json` 文件才会生效。
@@ -22,17 +22,6 @@ weight: 520
这里介绍一些基础的配置字段:
```json
// 这个配置会控制前端的一些样式
"FeConfig": {
"show_emptyChat": true, // 对话页面,空内容时,是否展示介绍页
"show_register": false, // 是否展示注册按键(包括忘记密码,注册账号和三方登录)
"show_appStore": false, // 是否展示应用市场(不过目前权限还没做好,放开也没用)
"show_userDetail": false, // 是否展示用户详情账号余额、OpenAI 绑定)
"show_git": true, // 是否展示 Git
"systemTitle": "FastGPT", // 系统的 title
"authorText": "Made by FastGPT Team.", // 签名
},
...
...
// 这个配置文件是系统级参数
"SystemParams": {
@@ -47,22 +36,11 @@ weight: 520
```json
{
"FeConfig": {
"show_emptyChat": true,
"show_register": false,
"show_appStore": false,
"show_userDetail": false,
"show_git": true,
"systemTitle": "FastGPT",
"authorText": "Made by FastGPT Team.",
"scripts": []
},
"SystemParams": {
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"pgIvfflatProbe": 20
},
"plugins": {},
"ChatModels": [
{
"model": "gpt-3.5-turbo",
@@ -92,12 +70,6 @@ weight: 520
"defaultSystem": ""
}
],
"QAModel": {
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0
},
"VectorModels": [
{
"model": "text-embedding-ada-002",
@@ -106,6 +78,28 @@ weight: 520
"defaultToken": 500,
"maxToken": 3000
}
]
],
"QAModel": {
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"maxToken": 0,
"price": 0
},
"ExtractModel": {
"model": "gpt-3.5-turbo-16k",
"functionCall": true,
"name": "GPT35-16k",
"maxToken": 0,
"price": 0,
"prompt": ""
},
"CQModel": {
"model": "gpt-3.5-turbo-16k",
"functionCall": true,
"name": "GPT35-16k",
"maxToken": 0,
"price": 0,
"prompt": ""
}
}
```

View File

@@ -12,7 +12,7 @@ weight: 510
## Tips
1. 用户默认的区为 `Asia/Shanghai`,非 linux 环境时候,获取系统时间会异常,本地开发时候,可以将用户的时区调整成 UTC+0
1. 用户默认的区为 `Asia/Shanghai`,非 linux 环境时候,获取系统时间会异常,本地开发时候,可以将用户的时区调整成 UTC+0
## 前置依赖项
@@ -41,7 +41,9 @@ weight: 510
git clone git@github.com:<github_username>/FastGPT.git
```
client 目录下为 FastGPT 核心代码。NextJS 框架前后端放在一起API 服务位于 `src/pages/api` 目录内。
**projects 目录下为 FastGPT 应用代码。NextJS 框架前后端放在一起API 服务位于 `src/pages/api` 目录内。**
**packages 目录为相关的共用包。**
### 安装数据库
@@ -68,15 +70,15 @@ client 目录下为 FastGPT 核心代码。NextJS 框架前后端放在一起,
### 运行
```bash
cd client
pnpm i
cd projects/app # FastGPT 主程序
pnpm dev
```
### 镜像打包
```bash
docker build -t dockername/fastgpt .
docker build -t dockername/fastgpt --build-arg name=app .
```
## 创建拉取请求

View File

@@ -0,0 +1,531 @@
---
title: 'OpenAPI 使用'
description: 'FastGPT OpenAPI 文档'
icon: 'api'
draft: false
toc: true
weight: 512
---
# 基本配置
```
baseUrl: "https://fastgpt.run/api"
headers: {
Authorization: "Bearer apikey"
}
```
# 如何获取 API Key
FastGPT 的 API Key 有 2 类,一类是全局通用的 key一类是携带了 AppId 也就是有应用标记的 key。
| 通用key | 应用特定 key |
| --------------------- | --------------------- |
| ![](/imgs/fastgpt-api2.png) | ![](/imgs/fastgpt-api.png) |
# 接口
## 发起对话
{{% alert icon="🤖 " context="success" %}}
该接口 API Key 需使用应用特定的 key否则会报错。
{{% /alert %}}
对话接口兼容 openai 的接口!如果你有第三方项目,可以直接通过修改 BaseUrl 和 Authorization 来访问 FastGpt 应用。缺点是你无法获取到响应的token值。
请求内容
- headers.Authorization: Bearer apikey
- chatId: string | undefined 。
- 为 undefined 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
- 为非空字符串时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录。并拼接 messages 数组最后一个内容作为完整请求。(自行确保 chatId 唯一,长度不限)
- messages: 与 openai gpt 接口完全一致。
- detail: 是否返回详细值(模块状态响应的完整结果会通过event进行区分
- variables: 变量。一个对象,效果同全局变量。
**请求示例:**
```bash
curl --location --request POST 'https://fastgpt.run/api/openapi/v1/chat/completions' \
--header 'Authorization: Bearer apikey' \
--header 'Content-Type: application/json' \
--data-raw '{
"chatId":"111",
"stream":false,
"detail": false,
"variables": {
"cTime": "2022/2/2 22:22"
},
"messages": [
{
"content": "导演是谁",
"role": "user"
}
]
}'
```
{{< tabs tabTotal="3" >}}
{{< tab tabName="detail=false 响应" >}}
{{< markdownify >}}
```bash
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"电"},"index":0,"finish_reason":null}]}
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"影"},"index":0,"finish_reason":null}]}
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"《"},"index":0,"finish_reason":null}]}
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="detail=true 响应" >}}
{{< markdownify >}}
```bash
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"电"},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"影"},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"《"},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"铃"},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"芽"},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"。"},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}
event: answer
data: {"id":"","object":"","created":0,"choices":[{"delta":{},"index":0,"finish_reason":"stop"}]}
event: answer
data: [DONE]
event: appStreamResponse
data: [{"moduleName":"KB Search","price":1.2000000000000002,"model":"Embedding-2","tokens":6,"similarity":0.61,"limit":3},{"moduleName":"AI Chat","price":463.5,"model":"FastAI-4k","tokens":309,"question":"导演是谁","answer":"电影《铃芽之旅》的导演是新海诚。","maxToken":2050,"quoteList":[{"kb_id":"646627f4f7b896cfd8910e38","id":"8099","q":"本作的主人公是谁?","a":"本作的主人公是名叫铃芽的少女。","source":"手动修改"},{"kb_id":"646627f4f7b896cfd8910e38","id":"8686","q":"电影《铃芽之旅》男主角是谁?","a":"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。","source":""},{"kb_id":"646627f4f7b896cfd8910e38","id":"19339","q":"电影《铃芽之旅》的导演是谁22","a":"电影《铃芽之旅》的导演是新海诚。","source":"手动修改"}],"completeMessages":[{"obj":"System","value":"下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁22\n电影《铃芽之旅》的导演是新海诚。]\n"},{"obj":"System","value":"1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"},{"obj":"System","value":"你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"},{"obj":"Human","value":"导演是谁"},{"obj":"AI","value":"电影《铃芽之旅》的导演是新海诚。"}]}]
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="stream=false,detail=true 响应" >}}
{{< markdownify >}}
```json
{
"responseData": [ // 不同模块的响应值, 不同版本具体值可能有差异,可先 log 自行查看最新值。
{
"moduleName": "KB Search",
"price": 1.2000000000000002,
"model": "Embedding-2",
"tokens": 6,
"similarity": 0.61,
"limit": 3
},
{
"moduleName": "AI Chat",
"price": 454.5,
"model": "FastAI-4k",
"tokens": 303,
"question": "导演是谁",
"answer": "电影《铃芽之旅》的导演是新海诚。",
"maxToken": 2050,
"quoteList": [
{
"kb_id": "646627f4f7b896cfd8910e38",
"id": "8099",
"q": "本作的主人公是谁?",
"a": "本作的主人公是名叫铃芽的少女。",
"source": "手动修改"
},
{
"kb_id": "646627f4f7b896cfd8910e38",
"id": "8686",
"q": "电影《铃芽之旅》男主角是谁?",
"a": "电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",
"source": ""
},
{
"kb_id": "646627f4f7b896cfd8910e38",
"id": "19339",
"q": "电影《铃芽之旅》的导演是谁22",
"a": "电影《铃芽之旅》的导演是新海诚。",
"source": "手动修改"
}
],
"completeMessages": [
{
"obj": "System",
"value": "下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁22\n电影《铃芽之旅》的导演是新海诚。]\n"
},
{
"obj": "System",
"value": "1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"
},
{
"obj": "System",
"value": "你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"
},
{
"obj": "Human",
"value": "导演是谁"
},
{
"obj": "AI",
"value": "电影《铃芽之旅》的导演是新海诚。"
}
]
}
],
"id": "",
"model": "",
"usage": {
"prompt_tokens": 1,
"completion_tokens": 1,
"total_tokens": 1
},
"choices": [
{
"message": {
"role": "assistant",
"content": "电影《铃芽之旅》的导演是新海诚。"
},
"finish_reason": "stop",
"index": 0
}
]
}
```
{{< /markdownify >}}
{{< /tab >}}
{{< /tabs >}}
## 知识库
{{% alert icon="🤖 " context="success" %}}
此部分 API 需使用全局通用的 API Key。
{{% /alert %}}
### 如何获取知识库IDkbId
![](/imgs/getKbId.png)
### 知识库添加数据
{{< tabs tabTotal="4" >}}
{{< tab tabName="请求示例" >}}
{{< markdownify >}}
```bash
curl --location --request POST 'https://fastgpt.run/api/core/dataset/data/pushData' \
--header 'Authorization: Bearer apikey' \
--header 'Content-Type: application/json' \
--data-raw '{
    "kbId": "64663f451ba1676dbdef0499",
"mode": "index",
"prompt": "qa 拆分引导词index 模式下可以忽略",
"billId": "可选。如果有这个值,本次的数据会被聚合到一个订单中,这个值可以重复使用。可以参考 [创建训练订单] 获取该值。",
    "data": [
        {
            "a": "test",
            "q": "1111",
        },
        {
            "a": "test2",
            "q": "22222"
        }
    ]
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="参数说明" >}}
{{< markdownify >}}
```json
{
"kbId": "知识库的ID可以在知识库详情查看。",
"mode": "index | qa ", // index 模式: 直接将 q 转成向量存起来a 直接入库。qa 模式: 只关注 data 里的 q将 q 丢给大模型,让其根据 prompt 拆分成 qa 问答对。
"prompt": "拆分提示词,需严格按照模板,建议不要传入。",
"data": [
{
"q": "生成索引的内容index 模式下最大 tokens 为3000建议不超过 1000",
"a": "预期回答/补充"
},
{
"q": "生成索引的内容qa 模式下最大 tokens 为10000建议 8000 左右",
"a": "预期回答/补充"
}
]
}
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="响应例子" >}}
{{< markdownify >}}
```json
{
"code": 200,
"statusText": "",
"data": {
"insertLen": 1 // 最终插入成功的数量,可能因为超出 tokens 或者插入异常index 可以重复插入,会自动去重
}
}
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="QA Prompt 模板" >}}
{{< markdownify >}}
{{theme}} 里的内容可以换成数据的主题。默认为:它们可能包含多个主题内容
```
我会给你一段文本,{{theme}},学习它们,并整理学习成果,要求为:
1. 提出最多 25 个问题。
2. 给出每个问题的答案。
3. 答案要详细完整,答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 markdown 元素。
4. 按格式返回多个问题和答案:
Q1: 问题。
A1: 答案。
Q2:
A2:
……
我的文本:"""{{text}}"""
```
{{< /markdownify >}}
{{< /tab >}}
{{< /tabs >}}
### 搜索测试
{{< tabs tabTotal="2" >}}
{{< tab tabName="请求示例" >}}
{{< markdownify >}}
```bash
curl --location --request POST 'https://fastgpt.run/api/core/dataset/searchTest' \
--header 'Authorization: Bearer apiKey' \
--header 'Content-Type: application/json' \
--data-raw '{
"kbId": "xxxxx",
"text": "导演是谁"
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="响应示例" >}}
{{< markdownify >}}
返回 top12 结果
```bash
{
"code": 200,
"statusText": "",
"data": [
{
"id": "5613327",
"q": "该人有获奖情况吗?",
"a": "该人获得过2020/07全国大学生服务外包大赛国家一等奖和2021/05国家创新创业计划立项的获奖情况。",
"source": "余金隆简历.pdf",
"score": 0.41556452839298963
},
......
]
}
```
{{< /markdownify >}}
{{< /tab >}}
{{< /tabs >}}
## 订单
### 创建训练订单
**请求示例**
```bash
curl --location --request POST 'https://fastgpt.run/api/common/bill/createTrainingBill' \
--header 'Authorization: Bearer {{apikey}}' \
--header 'Content-Type: application/json' \
--data-raw ''
```
**响应结果**
data 为 billId可用于 api 添加数据时进行账单聚合。
```json
{
"code": 200,
"statusText": "",
"message": "",
"data": "65112ab717c32018f4156361"
}
```
## 免登录分享链接校验(内测中)
免登录链接配置中,增加了`凭证校验服务器`后,使用分享链接时会向服务器发起请求,校验链接是否可用,并在每次对话结束后,向服务器发送对话结果。下面以`host`来表示`凭证校验服务器`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下:
```json
{
"success": true,
"message": "错误提示"
}
```
![](/imgs/sharelinkProcess.png)
### 分享链接中增加额外 query
增加一个 query: authToken。例如
原始的链接https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192
完整链接: https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345
发出校验请求时候,会在`body`中携带 token={{authToken}} 的参数。
### 初始化校验
**FastGPT 发出的请求**
```bash
curl --location --request POST '{{host}}/shareAuth/init' \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "sintdolore"
}'
```
### 对话前校验
**FastGPT 发出的请求**
```bash
curl --location --request POST '{{host}}/shareAuth/start' \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "sintdolore",
"question": "用户问题",
}'
```
### 对话结果上报
**FastGPT 发出的请求**
```bash
curl --location --request POST '{{host}}/shareAuth/finish' \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "sint dolore",
"responseData": [
{
"moduleName": "KB Search",
"price": 1.2000000000000002,
"model": "Embedding-2",
"tokens": 6,
"similarity": 0.61,
"limit": 3
},
{
"moduleName": "AI Chat",
"price": 454.5,
"model": "FastAI-4k",
"tokens": 303,
"question": "导演是谁",
"answer": "电影《铃芽之旅》的导演是新海诚。",
"maxToken": 2050,
"quoteList": [
{
"kb_id": "646627f4f7b896cfd8910e38",
"id": "8099",
"q": "本作的主人公是谁?",
"a": "本作的主人公是名叫铃芽的少女。",
"source": "手动修改"
},
{
"kb_id": "646627f4f7b896cfd8910e38",
"id": "8686",
"q": "电影《铃芽之旅》男主角是谁?",
"a": "电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",
"source": ""
},
{
"kb_id": "646627f4f7b896cfd8910e38",
"id": "19339",
"q": "电影《铃芽之旅》的导演是谁22",
"a": "电影《铃芽之旅》的导演是新海诚。",
"source": "手动修改"
}
],
"completeMessages": [
{
"obj": "System",
"value": "下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁22\n电影《铃芽之旅》的导演是新海诚。]\n"
},
{
"obj": "System",
"value": "1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"
},
{
"obj": "System",
"value": "你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"
},
{
"obj": "Human",
"value": "导演是谁"
},
{
"obj": "AI",
"value": "电影《铃芽之旅》的导演是新海诚。"
}
]
}
]
}'
```
响应值与 chat 接口相同,增加了一个 token。可以重点关注`responseData`里的值price 与实际价格的倍率为`100000`
**此接口无需响应值**
# 使用案例
- [接入 NextWeb/ChatGPT web 等应用](/docs/use-cases/openapi)
- [接入 onwechat](/docs/use-cases/onwechat)
- [接入 飞书](/docs/use-cases/feishu)

View File

@@ -92,7 +92,7 @@ CHAT_API_KEY=sk-xxxxxx
### 2. 修改 FastGPT 配置文件
可以在 `/client/src/data/config.json` 里找到配置文件(本地开发需要复制成 config.local.json配置文件中有一项是对话模型配置
可以在 `/projects/app/src/data/config.json` 里找到配置文件(本地开发需要复制成 config.local.json配置文件中有一项是对话模型配置
```json
"ChatModels": [

View File

@@ -21,4 +21,8 @@ Sealos 的服务器在国外,不需要额外处理网络问题,无需服务
> 用户名:`root`
>
> 密码就是刚刚一键部署时设置的环境变量
> 密码就是刚刚一键部署时设置的环境变量
## 部署架构图
![](/imgs/sealos-fastgpt.webp)

View File

@@ -0,0 +1,23 @@
---
title: '升级到 V4.4.2'
description: 'FastGPT 从旧版本升级到 V4.4.2 操作指南'
icon: 'upgrade'
draft: false
toc: true
weight: 993
---
## 执行初始化 API
发起 1 个 HTTP 请求(记得携带 `headers.rootkey`,这个值是环境变量里的)
1. https://xxxxx/api/admin/initv442
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv442' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
会给初始化 Mongo 的 Bill 表的索引,之前过期时间有误。

View File

@@ -0,0 +1,31 @@
---
title: 'V4.4.5'
description: 'FastGPT V4.4.5 更新(需执行升级脚本)'
icon: 'upgrade'
draft: false
toc: true
weight: 992
---
## 执行初始化 API
发起 1 个 HTTP 请求(记得携带 `headers.rootkey`,这个值是环境变量里的)
1. https://xxxxx/api/admin/initv445
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv445' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
初始化了 variable 模块,将其合并到用户引导模块中。
## 功能介绍
### Fast GPT V4.4.5
1. 新增 - 下一步指引选项,可以通过模型生成 3 个预测问题。
2. 新增 - 分享链接 hook 身份校验。
3. 新增 - Api Key 使用。增加别名、额度限制和过期时间。自带 appId无需额外连接。
4. 优化 - 全局变量与开场白合并成同一模块。

View File

@@ -1,7 +1,7 @@
---
weight: 760
title: "版本升级"
description: "FastGPT 升级指南"
title: "版本更新/升级操作"
description: "FastGPT 版本更新介绍及升级操作"
icon: upgrade
draft: false
images: []

View File

@@ -78,7 +78,7 @@ FastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入
## 知识库核心流程图
![](/imgs/KBProcess.jpg)
![](/imgs/functional-arch.webp)
## 免责声明

View File

@@ -7,11 +7,15 @@ toc: true
weight: 10
---
[OpenAI 的 API 官方计费模式](https://openai.com/pricing#language-models)为:按每次 API 请求内容和返回内容 tokens 长度来定价。每个模型具有不同的计价方式,以每 1,000 个 tokens 消耗为单位定价。其中 1,000 个 tokens 约为 750 个英文单词。平台的 tokens 数量计算算法与 OpenAI 一致,您可以随时通过「使用记录」来查看余额消耗明细的说明,来对比计算是否一致。
## Tokens 说明
[OpenAI 的 API 官方计费模式](https://openai.com/pricing#language-models)为:按每次 API 请求内容和返回内容 tokens 长度来定价。每个模型具有不同的计价方式,以每 1,000 个 tokens 消耗为单位定价。其中 1,000 个 tokens 约为 900 个英文,约 600 个中文(不是很准确,与上下长度有关,相同的词出现越多,词:Tokens 的比例越大)。平台的 tokens 数量计算算法与 OpenAI 一致,您可以随时通过「使用记录」来查看余额消耗明细的说明,来对比计算是否一致。
![](/imgs/fastgpt-price.png)
以下是详细的价格表:
## FastGPT 线上计费
目前FastGPT 线上计费也仅按 Tokens 使用数量为准。以下是详细的计费表(最新定价以线上表格为准,可在点击充值后实时获取):
{{< table "table-hover table-striped-columns" >}}
| 计费项 | 价格: 元/ 1K tokens包含上下文 |

View File

@@ -11,16 +11,14 @@ weight: 322
[Feishu OpenAI GitHub 地址](https://github.com/ConnectAI-E/Feishu-OpenAI)
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更第三方应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/openai/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更第三方应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
## 1. 获取 FastGPT 的 OpenAPI 秘钥
依次选择应用 -> 「API 访问」然后点击「API 密钥」来创建密钥。 [参考这篇文章](/docs/use-cases/openai/)
依次选择应用 -> 「API 访问」然后点击「API 密钥」来创建密钥。 [参考这篇文章](/docs/use-cases/openapi/)
![](/imgs/fastgpt-api.png)
利用刚复制的 API 秘钥加上 AppId 组合成一个新的秘钥,格式为:`API 秘钥-AppId`,例如:`fastgpt-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb`
## 2. 部署飞书服务
推荐使用 Railway 一键部署
@@ -35,7 +33,7 @@ FastGPT 集成**重点参数:**
```bash
#上一步FastGPT的OpenAPI 秘钥
OPENAI_KEY=fastgpt-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb
OPENAI_KEY=fastgpt-z51pkjqm9nrk03a1rx2funoy
#调用OpenAI的BaseUrl要换成FastGPT的
API_URL=https://fastgpt.run/api/openapi
```

View File

@@ -73,7 +73,7 @@ weight: 340
![手动录入知识库结果](/imgs/9.png)
导入结果如上图。可以看到,我们均采用的是问答对的格式,而不是粗略的直接导入。目的就是为了模拟用户问题,进一步的提高向量搜索的匹配效果。可以为同一个问题设置多种问法,效果更佳。
FastGPT 还提供了 openapi 功能,你可以在本地对特殊格式的文件进行处理后,再上传到 FastGPT具体可以参考[FastGPT Api Docs](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh)
FastGPT 还提供了 openapi 功能,你可以在本地对特殊格式的文件进行处理后,再上传到 FastGPT具体可以参考[FastGPT Api Docs](https://doc.fastgpt.run/docs/development/openapi)
## 知识库微调和参数调整

View File

@@ -4,14 +4,14 @@ description: "FastGPT 对接 chatgpt-on-wechat"
icon: "chat"
draft: false
toc: true
weight: 320
weight: 312
---
# 1 分钟对接 chatgpt-on-wechat
[chatgpt-on-wechat GitHub 地址](https://github.com/zhayujie/chatgpt-on-wechat)
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更原来的应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/openai/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更原来的应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
## 1. 获取 OpenAPI 秘钥
@@ -23,15 +23,10 @@ weight: 320
![](/imgs/fastgpt-api.png)
## 2. 组合带应用 ID 的秘钥
利用刚复制的 API 秘钥加上 AppId 组合成一个新的秘钥,格式为:`API 秘钥-AppId`,例如:`fastgpt-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb`
这个秘钥将会调用指定的应用。
## 3. 创建 docker-compose.yml 文件
只需要修改 `OPEN_AI_API_KEY``OPEN_AI_API_BASE` 两个环境变量即可。其中 `OPEN_AI_API_KEY` 为第二步的组合秘钥,`OPEN_AI_API_BASE` 为 FastGPT 的 OpenAPI 地址,例如:`https://fastgpt.run/api/openapi/v1`
只需要修改 `OPEN_AI_API_KEY``OPEN_AI_API_BASE` 两个环境变量即可。其中 `OPEN_AI_API_KEY` 为第一步获取的秘钥,`OPEN_AI_API_BASE` 为 FastGPT 的 OpenAPI 地址,例如:`https://fastgpt.run/api/openapi/v1`
随便找一个目录,创建一个 docker-compose.yml 文件,将下面的代码复制进去。
@@ -44,7 +39,7 @@ services:
security_opt:
- seccomp:unconfined
environment:
OPEN_AI_API_KEY: 'fastgpt-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb'
OPEN_AI_API_KEY: 'fastgpt-z51pkjqm9nrk03a1rx2funoy'
OPEN_AI_API_BASE: 'https://fastgpt.run/api/openapi/v1'
MODEL: 'gpt-3.5-turbo'
CHANNEL_TYPE: 'wx'

View File

@@ -4,7 +4,7 @@ description: "通过与 OpenAI 兼容的 API 对接第三方应用"
icon: "model_training"
draft: false
toc: true
weight: 330
weight: 311
---
## 获取 API 秘钥
@@ -17,15 +17,16 @@ weight: 330
![](/imgs/fastgpt-api.png)
## 组合秘钥
{{% alert icon="🍅" context="success" %}}
Tips: 安全起见,你可以设置一个额度或者过期时间,放置 key 被滥用。
{{% /alert %}}
利用刚复制的 API 秘钥加上 AppId 组合成一个新的秘钥,格式为:`API 秘钥-AppId`,例如:`fastgpt-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb`
## 替换三方应用的变量
```bash
OPENAI_API_BASE_URL: https://fastgpt.run/api/openapi (改成自己部署的域名)
OPENAI_API_KEY = 组合秘钥
OPENAI_API_KEY = 上一步获取到的秘钥
```
**[ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web) 示例:**

View File

@@ -1,12 +1,14 @@
---
title: "提示词 & 限定词"
description: "FastGPT 提示词 & 限定词说明"
title: "提示词 & 引用提示词"
description: "FastGPT 提示词 & 引用提示词说明"
icon: "sign_language"
draft: false
toc: true
weight: 310
---
限定词从 V4.4.3 版本后去除,被“引用提示词”和“引用模板”替代。
# AI 对话消息组成
传递给 AI 模型的消息是一个数组FastGPT 中这个数组的组成形式为:
@@ -14,12 +16,9 @@ weight: 310
```json
[
config.json
]
```
@@ -27,43 +26,84 @@ weight: 310
Tips: 可以通过点击上下文按键查看完整的
{{% /alert %}}
# 引用内容结构
# 引用模板和提示词设计
知识库采用 QA 对的格式存储,在转义成字符串时候会对应的转成 instruction 和 output。搜索引导词中会对这两个字段做说明不需要重复补充。
知识库采用 QA 对的格式存储,在转义成字符串时候会根据**引用模板**来进行格式化。知识库包含 3 个变量: q,a 和 source可以通过 {{q}} {{a}} {{source}} 按需引入。下面一个模板例子:
{{% alert icon="🤖" context="success" %}}
三引号引用的内容是我提供给你的知识库它们拥有最高优先级。instruction 是相关介绍output 是预期回答或补充。
{{% /alert %}}
**引用模板**
```
"""
{instruction:"本作的故事背景是什么?",output:"本作的故事背景是发生在日本灾难时期的东北地区。"}
{instruction:"电影《铃芽之旅》讲述了什么故事?",output:"电影《铃芽之旅》讲述了少女岩户铃芽和关门师宗像草太为了关闭灾难之门展开的冒险旅程。"}
{instruction:"电影《铃芽之旅》的故事背景是什么?",output:"日本"}
"""
{instruction:"{{q}}",output:"{{a}}",source:"{{source}}"}
```
搜索到的知识库,会自动将 q,a,source 替换成对应的内容。每条搜索到的内容,会通过 `\n` 隔开。例如:
```
{instruction:"电影《铃芽之旅》的导演是谁?",output:"电影《铃芽之旅》的导演是新海诚。",source:"手动输入"}
{instruction:"本作的主人公是谁?",output:"本作的主人公是名叫铃芽的少女。",source:""}
{instruction:"电影《铃芽之旅》男主角是谁?",output:"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",source:""}
{instruction:"电影《铃芽之旅》的编剧是谁22",output:"新海诚是本片的编剧。",source:"手动输入"}
```
**引用提示词**
引用模板需要和引用提示词一起使用,提示词中可以写引用模板的格式说明以及对话的要求等。可以使用 {{quote}} 来使用 **引用模板**,使用 {{question}} 来引入问题。例如:
```
你的背景知识:
"""
{{quote}}
"""
对话要求:
1. 背景知识是最新的,其中 instruction 是相关介绍output 是预期回答或补充。
2. 使用背景知识回答问题。
3. 背景知识无法回答问题时,你可以礼貌的的回答用户问题。
我的问题是:"{{question}}"
```
# 提示词案例
## 仅回复知识库里的内容
{{% alert icon="🤖" context="warning" %}}
**限定词**里添加:
**引用提示词**里添加:
```
你的背景知识:
"""
{{quote}}
"""
对话要求:
1. 回答前,请先判断背景知识是否足够回答问题,如果无法回答,请直接回复:“对不起,我无法回答你的问题~”。
2. 背景知识是最新的,其中 instruction 是相关介绍output 是预期回答或补充。
3. 使用背景知识回答问题。
我的问题是:"{{question}}"
```
回答内容限制:你目前仅能回答三引号中提及的内容,超出引用的内容,请直接回复:“我不知道”
{{% /alert %}}
## 说明引用来源
注意,限定词会一定程度上打断上下文连贯性,且并不是 100% 生效。随着上下文和引用长度越多,限定词的效果会被削弱。实在控不住,用 GPT4 吧。
**引用模板:**
## 为回答添加引用序号
```
{instruction:"{{q}}",output:"{{a}}",source:"{{source}}"}
```
如果你希望回答内容带上引用的第几条的序号,可以参考下面的提示词:
**引用提示词:**
{{% alert icon="🤖" context="warning" %}}
**提示词**里添加:
```
你的背景知识:
"""
{{quote}}
"""
对话要求:
1. 背景知识是最新的,其中 instruction 是相关介绍output 是预期回答或补充source是背景来源。
2. 使用背景知识回答问题。
3. 在回答问题后,你需要给出本次回答对应的背景来源,来源展示格式如下:
我希望你的回答会附加上引用的序号:
1.每个 {instruction,output} 包裹的内容是一条引用
2.从上往下,序列号从 1-n
3.回答的内容应使用 [1][2] 这个特殊的格式来标记引用序列号
{{% /alert %}}
这是AI作答。本次知识来源
1. source1
2. source2
......
我的问题是:"{{question}}"
```

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
module fastgpt-docs
go 1.23
go 1.21
require (
github.com/colinwilson/lotusdocs v0.0.0-20230821033552-c5bcbdd9df80 // indirect
github.com/colinwilson/lotusdocs v0.0.0-20230919015602-a9717caaab14 // indirect
github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20003 // indirect
)

View File

@@ -4,6 +4,8 @@ github.com/colinwilson/lotusdocs v0.0.0-20230820063310-51255ddcf986 h1:IZb47oZD5
github.com/colinwilson/lotusdocs v0.0.0-20230820063310-51255ddcf986/go.mod h1:9zu2REJDi+zdPRcR5/bRYSUR7gkNF4NQLvV38SEoCP8=
github.com/colinwilson/lotusdocs v0.0.0-20230821033552-c5bcbdd9df80 h1:jKZF8sqr/q34TF0batU4q/qs1VSj22AvVjJlO1y+BSk=
github.com/colinwilson/lotusdocs v0.0.0-20230821033552-c5bcbdd9df80/go.mod h1:9zu2REJDi+zdPRcR5/bRYSUR7gkNF4NQLvV38SEoCP8=
github.com/colinwilson/lotusdocs v0.0.0-20230919015602-a9717caaab14 h1:ORzVQia2njOTCs/5cTQZW0Y+YRZlupgwtGx3umABTcc=
github.com/colinwilson/lotusdocs v0.0.0-20230919015602-a9717caaab14/go.mod h1:9zu2REJDi+zdPRcR5/bRYSUR7gkNF4NQLvV38SEoCP8=
github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20003 h1:pt/JGVD5YYRsVVijOHPZI6YKTUvbR4e0hgV9B0S6rbI=
github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20003/go.mod h1:mvM05r93HiefwoaxQTaYiJxtJAhTebwQtU1Xh/J+Okk=
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000/go.mod h1:mFberT6ZtcchrsDtfvJM7aAH2bDKLdOnruUHl0hlapI=

View File

@@ -52,8 +52,8 @@ defaultContentLanguage = 'zh-cn'
["JetBrains Mono", "500, 700"]
]
sans_serif_font = "Inter" # Default is System font
secondary_font = "Inter" # Default is System font
sans_serif_font = "LXGW WenKai Screen" # Default is System font
secondary_font = "LXGW WenKai Screen" # Default is System font
mono_font = "JetBrains Mono" # Default is System font
[params.footer]
@@ -72,12 +72,14 @@ defaultContentLanguage = 'zh-cn'
# pathName = "docs" # path name for documentation site | default "docs"
# themeColor = "cyan" # (optional) - Set theme accent colour. Options include: blue (default), green, red, yellow, emerald, cardinal, magenta, cyan
# themeColor = "blue" # (optional) - Set theme accent colour. Options include: blue (default), green, red, yellow, emerald, cardinal, magenta, cyan
darkMode = true # enable dark mode option? default false
prism = true # enable syntax highlighting via Prism
prismTheme = "lotusdocs" # (optional) - Set theme for PrismJS. Options include: lotusdocs (default), solarized-light, twilight, lucario
# gitinfo
repoURL = "https://github.com/labring/FastGPT" # Git repository URL for your site
repoBranch = "main" # Name of your Git repository branch

View File

@@ -1,11 +1,3 @@
<!-- change -->
<style>
.medium-zoom-overlay,
.medium-zoom-image--opened {
z-index: 1999;
}
</style>
{{ $dayjs := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/dayjs.min.js") }}
{{ $relativeTime := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/relativeTime.min.js") }}
{{ $app := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/app.js") -}}
@@ -37,7 +29,10 @@
{{ if eq .Site.Params.docs.prism true -}}
{{ $prism := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/prism.js") }}
{{ $prism := $prism | js.Build -}}
{{- $opts := dict
"params" (dict "langPath" (urls.JoinPath .Site.BaseURL "docs/js/components/"))
-}}
{{ $prism := $prism | js.Build $opts -}}
{{ $slice = $slice | append $prism -}}
{{ end -}}

View File

@@ -102,4 +102,6 @@
{{- template "_internal/google_analytics.html" . -}}
{{- end -}}
{{- end -}}
<!-- change -->
<link rel="stylesheet" href="https://jsdelivr.icloudnative.io/npm/lxgw-wenkai-screen-webfont@1.1.0/style.css" />
</head>

View File

@@ -19,6 +19,7 @@
--primary-100: var(--blue-100);
--primary-200: var(--blue-200);
--primary-300: var(--blue-300);
--primary-400: var(--blue-400);
--primary-800: var(--blue-800);
--primary-hsl: var(--blue-500-hsl);
--primary-50-hsl: var(--blue-50-hsl);
@@ -99,7 +100,7 @@
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: Inter;
--bs-font-sans-serif: LXGW WenKai Screen;
--bs-font-monospace: JetBrains Mono;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
@@ -9575,7 +9576,7 @@ html {
background-color: var(--body-bg); }
body {
font-family: Inter;
font-family: LXGW WenKai Screen;
overflow-x: hidden !important;
font-size: 1rem;
color: var(--body-color);
@@ -9765,7 +9766,6 @@ i.title-icon {
.docs-content .main-content code {
font-size: inherit;
color: var(--text-default);
font-weight: 400;
padding: 1px 2px;
background: var(--inline-code-bg);
@@ -9774,14 +9774,11 @@ i.title-icon {
.docs-content .main-content pre {
margin: 0;
background-color: var(--code-block-bg) !important;
border-radius: 4px;
padding: 0; }
.docs-content .main-content pre code {
color: #f5fbff;
font-size: 0.8rem;
display: block;
background: var(--code-block-bg);
border: none;
overflow-x: auto;
line-height: 1.5;
@@ -10123,12 +10120,14 @@ i.title-icon {
:root {
--toc-link-hover-bg-color: var(--gray-300);
--toc-mobile-btn-bg-color: var(--gray-100);
--toc-mobile-menu-bg-color: var(--white); }
--toc-mobile-menu-bg-color: var(--white);
--toc-mobile-link-hover-color: var(--primary); }
[data-dark-mode] {
--toc-link-hover-bg-color: var(--gray-700);
--toc-mobile-btn-bg-color: var(--gray-900);
--toc-mobile-menu-bg-color: var(--body-bg); }
--toc-mobile-menu-bg-color: var(--body-bg);
--toc-mobile-link-hover-color: var(--primary-300); }
.docs-toc-mobile {
position: sticky;
@@ -10136,6 +10135,10 @@ i.title-icon {
padding-left: calc(var(--bs-gutter-x) * 1.05);
padding-right: calc(var(--bs-gutter-x) * 1.05);
z-index: 20; }
.docs-toc-mobile a {
color: var(--text-default); }
.docs-toc-mobile a:hover {
color: var(--toc-mobile-link-hover-color); }
.docs-toc-mobile .dropdown-toggle {
margin-bottom: 16px;
background: var(--toc-mobile-btn-bg-color);
@@ -10820,6 +10823,43 @@ table td:last-child, table th:last-child {
.tab-content {
margin-bottom: 0.8rem; }
:root {
--tooltip-bg: var(--white);
--tooltip-border-color: var(--content-link-color);
--tooltip-drop-shadow-color: var(--gray-500);
--tooltip-link-color: var(--gray-700); }
[data-dark-mode] {
--tooltip-bg: var(--dark-alt);
--tooltip-border-color: var(--primary-300);
--tooltip-drop-shadow-color: var(--gray-900);
--tooltip-link-color: var(--gray-500); }
.tooltip {
--bs-tooltip-bg: var(--tooltip-bg);
--bs-tooltip-opacity: 1.0;
--bs-tooltip-font-size: 0.575rem;
--bs-tooltip-max-width: 300px; }
.tooltip-inner {
text-align: left;
border: 2px solid var(--tooltip-border-color);
border-width: 2px 2px 2px 8px;
filter: drop-shadow(4px 4px 5px var(--tooltip-drop-shadow-color));
--bs-tooltip-border-radius: 4px; }
.tooltip-inner a {
color: var(--tooltip-link-color);
font-size: 0.85rem;
line-height: 1.55; }
.tooltip-inner a p {
margin-bottom: 0.2rem;
color: var(--text-muted);
font-weight: 600; }
.tooltip-inner a strong {
font-size: 0.975rem;
line-height: 2;
color: var(--text-default); }
:root {
--feature-icon-color: var(--primary);
--feature-icon-faint: var(--gray-200);
@@ -11217,11 +11257,14 @@ h4:hover a,
visibility: visible;
text-decoration: none; }
/* PrismJS 1.29.0
/**
* prism.js lotus docs for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/chriskempson/tomorrow-theme
* @author Colin Wilson
* Lotus Docs theme
*
* Adapted from a theme based on:
* https://github.com/chriskempson/tomorrow-theme
*
* @author Colin Wilson <github.com/colinwilson>
* @version 1.0
*/
:root {
--prism-code-bg: #212d63;
@@ -11234,35 +11277,7 @@ h4:hover a,
code[class*="language-"],
pre[class*="language-"] {
color: #f5fbff !important;
background: var(--prism-code-bg) !important;
border: none !important;
font-family: JetBrains Mono;
font-size: 0.8rem;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
scrollbar-width: thin;
scrollbar-color: var(--prism-code-scrollbar-thumb-color) var(--prism-code-bg); }
code[class*="language-"]::-webkit-scrollbar,
pre[class*="language-"]::-webkit-scrollbar {
height: 5px;
background: var(--prism-code-bg); }
code[class*="language-"]::-webkit-scrollbar-thumb,
pre[class*="language-"]::-webkit-scrollbar-thumb {
background: var(--prism-code-scrollbar-thumb-color); }
background: var(--prism-code-bg) !important; }
/* Code blocks */
pre[class*="language-"] {
@@ -11279,6 +11294,19 @@ pre[class*="language-"] {
border-radius: .3em;
white-space: normal; }
.line-highlight:before,
.line-highlight[data-end]:after {
background-color: var(--blue-400); }
[data-copy-state="copy"] span:empty::before {
background-color: var(--gray-400); }
[data-copy-state="copy"] span:empty:hover::before {
background-color: var(--white); }
[data-copy-state="copy-success"] span:empty::before {
background-color: var(--emerald-200); }
.token.comment,
.token.block-comment,
.token.prolog,
@@ -11347,6 +11375,44 @@ pre[class*="language-"] {
.token.inserted {
color: green; }
/* PrismJS 1.29.0 */
code {
color: var(--text-default); }
.docs-content .main-content pre {
background-color: var(--prism-code-bg) !important; }
code[class*="language-"],
pre[class*="language-"] {
border: none !important;
font-family: JetBrains Mono;
font-size: 0.8rem;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
scrollbar-width: thin;
scrollbar-color: var(--prism-code-scrollbar-thumb-color) var(--prism-code-bg); }
code[class*="language-"]::-webkit-scrollbar,
pre[class*="language-"]::-webkit-scrollbar {
height: 5px;
background: var(--prism-code-bg); }
code[class*="language-"]::-webkit-scrollbar-thumb,
pre[class*="language-"]::-webkit-scrollbar-thumb {
background: var(--prism-code-scrollbar-thumb-color); }
pre[data-line] {
position: relative;
padding: 0 !important; }
@@ -11387,7 +11453,6 @@ pre[data-line] {
left: .6em;
min-width: 1.5em;
padding: 0 .5em;
background-color: var(--blue-400);
color: #f5f2f0;
font: bold 95%/1.3 sans-serif;
text-align: center;
@@ -11510,21 +11575,16 @@ div.code-toolbar > .toolbar > .toolbar-item > button {
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='48' width='48' viewBox='0 0 48 48'%3E%3Cpath d='M9 43.95q-1.2 0-2.1-.9-.9-.9-.9-2.1V10.8h3v30.15h23.7v3Zm6-6q-1.2 0-2.1-.9-.9-.9-.9-2.1v-28q0-1.2.9-2.1.9-.9 2.1-.9h22q1.2 0 2.1.9.9.9.9 2.1v28q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h22v-28H15v28Zm0 0v-28 28Z'/%3E%3C/svg%3E");
-webkit-mask-size: contain;
mask-size: contain;
background-color: var(--gray-400);
display: block;
height: 24px;
width: 24px; }
[data-copy-state="copy"] span:empty:hover::before {
background-color: var(--white); }
[data-copy-state="copy-success"] span:empty::before {
content: "";
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='48' width='48' viewBox='0 0 48 48'%3E%3Cpath d='M18.9 35.7 7.7 24.5l2.15-2.15 9.05 9.05 19.2-19.2 2.15 2.15Z'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='48' width='48' viewBox='0 0 48 48'%3E%3Cpath d='M18.9 35.7 7.7 24.5l2.15-2.15 9.05 9.05 19.2-19.2 2.15 2.15Z'/%3E%3C/svg%3E");
-webkit-mask-size: contain;
mask-size: contain;
background-color: var(--emerald-200);
display: block;
height: 24px;
width: 24px; }
@@ -11918,6 +11978,33 @@ div.code-toolbar > .toolbar > .toolbar-item > span:focus {
width: 20.1rem;
padding-left: 1rem; } }
/* Mermaid */
.docs-content .main-content pre.mermaid {
background-color: transparent !important;
text-align: center !important; }
.docs-content .main-content pre.mermaid .messageText {
fill: var(--text-default) !important; }
.docs-content .main-content pre.mermaid .messageLine0,
.docs-content .main-content pre.mermaid .messageLine1 {
stroke: var(--text-default) !important; }
.docs-content .main-content pre.mermaid #arrowhead path,
.docs-content .main-content pre.mermaid #crosshead path {
fill: var(--text-default) !important;
stroke: var(--text-default) !important; }
.docs-content .main-content pre.mermaid .edgePaths path {
stroke: var(--text-default) !important; }
.docs-content .main-content pre.mermaid .marker {
fill: var(--text-default) !important;
stroke: var(--text-default) !important; }
.docs-content .main-content pre.mermaid .grid .tick {
stroke: var(--text-default) !important; }
.docs-content .main-content pre.mermaid .grid .tick text {
fill: var(--text-default) !important; }
.docs-content .main-content pre.mermaid line {
stroke: var(--text-default) !important; }
.docs-content .main-content pre.mermaid text {
fill: var(--text-default) !important; }
.docs-content .main-content img, .docs-content .main-content svg:not(.gitinfo svg):not(a svg) {
max-width: 80% !important;
height: auto;
@@ -11952,6 +12039,10 @@ footer a:hover {
text-decoration: none !important;
}
*/
.medium-zoom-overlay,
.medium-zoom-image--opened {
z-index: 1999; }
/* 徽章样式 */
.github-badge {
display: inline-block;

View File

@@ -81,7 +81,7 @@
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: Inter;
--bs-font-sans-serif: LXGW WenKai Screen;
--bs-font-monospace: JetBrains Mono;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
@@ -9438,7 +9438,7 @@ textarea.form-control-lg {
src: local("Material Icons"), local("MaterialIcons-Outlined"), url("../docs/fonts/material-symbols-outlined.woff2") format("woff2"); }
body {
font-family: Inter;
font-family: LXGW WenKai Screen;
overflow-x: hidden !important;
font-size: 1rem;
color: #3C4257;

View File

@@ -8,4 +8,4 @@ starlette==0.27.0
tiktoken==0.4.0
torch==2.0.1
transformers==4.31.0
uvicorn==0.23.2
uvicorn==0.23.2

View File

@@ -1,6 +1,6 @@
{
"name": "fastgpt",
"version": "3.7",
"version": "4.0",
"private": true,
"scripts": {
"prepare": "husky install",

View File

@@ -0,0 +1 @@
export const PRICE_SCALE = 100000;

View File

@@ -0,0 +1,9 @@
/* bill common */
import { PRICE_SCALE } from './constants';
/**
* dataset price / PRICE_SCALE = real price
*/
export const formatPrice = (val = 0, multiple = 1) => {
return Number(((val / PRICE_SCALE) * multiple).toFixed(10));
};

View File

@@ -0,0 +1,4 @@
{
"name": "@fastgpt/common",
"version": "1.0.0"
}

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
"exclude": ["node_modules"]
}

View File

@@ -1,4 +1,4 @@
import { UserModelSchema } from '@/types/mongoSchema';
import { UserModelSchema } from '../user/type';
import { Configuration, OpenAIApi } from 'openai';
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';

View File

@@ -0,0 +1 @@
export { ChatCompletionRequestMessageRoleEnum } from 'openai';

1
packages/core/aiApi/type.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export type { CreateChatCompletionRequest, ChatCompletionRequestMessage } from 'openai';

13
packages/core/init.ts Normal file
View File

@@ -0,0 +1,13 @@
import tunnel from 'tunnel';
export function initHttpAgent() {
// proxy obj
if (process.env.AXIOS_PROXY_HOST && process.env.AXIOS_PROXY_PORT) {
global.httpsAgent = tunnel.httpsOverHttp({
proxy: {
host: process.env.AXIOS_PROXY_HOST,
port: +process.env.AXIOS_PROXY_PORT
}
});
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@fastgpt/core",
"version": "1.0.0",
"dependencies": {
"openai": "^3.3.0",
"tunnel": "^0.0.6"
},
"devDependencies": {
"@types/tunnel": "^0.0.4"
}
}

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
"exclude": ["node_modules"]
}

5
packages/core/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
import type { Agent } from 'http';
declare global {
var httpsAgent: Agent;
}

19
packages/core/user/type.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
export type UserModelSchema = {
_id: string;
username: string;
password: string;
avatar: string;
balance: number;
promotionRate: number;
inviterId?: string;
openaiKey: string;
createTime: number;
timezone: string;
openaiAccount?: {
key: string;
baseUrl: string;
};
limit: {
exportKbTime?: Date;
};
};

View File

@@ -0,0 +1,4 @@
{
"name": "@fastgpt/support",
"version": "1.0.0"
}

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
"exclude": ["node_modules"]
}

11952
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,3 @@
packages:
- 'packages/*'
- 'projects/*'

View File

@@ -1,9 +1,6 @@
{
"FeConfig": {
"show_emptyChat": true,
"show_register": false,
"show_appStore": false,
"show_userDetail": false,
"show_contact": true,
"show_git": true,
"show_doc": true,
@@ -62,5 +59,29 @@
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0
},
"ExtractModel": {
"model": "gpt-3.5-turbo-16k",
"functionCall": true,
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0,
"prompt": ""
},
"CQModel": {
"model": "gpt-3.5-turbo-16k",
"functionCall": true,
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0,
"prompt": ""
},
"QGModel": {
"model": "gpt-3.5-turbo",
"name": "GPT35-4k",
"maxToken": 4000,
"price": 0,
"prompt": "",
"functionCall": false
}
}

View File

@@ -1,12 +1,13 @@
/** @type {import('next').NextConfig} */
const { i18n } = require('./next-i18next.config');
const path = require('path');
const nextConfig = {
i18n,
output: 'standalone',
reactStrictMode: false,
compress: true,
transpilePackages: ['@fastgpt/*'],
webpack(config, { isServer }) {
if (!isServer) {
config.resolve = {
@@ -30,6 +31,9 @@ const nextConfig = {
};
return config;
},
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../')
}
};

Some files were not shown because too many files have changed in this diff Show More