Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11848b8f44 | ||
|
|
a11e0bd9c3 | ||
|
|
f6552d0d4f | ||
|
|
38d4db5d5f | ||
|
|
63cd379682 | ||
|
|
9136c9306a | ||
|
|
c9db9f33ea | ||
|
|
3d7178d06f | ||
|
|
a4ff5a3f73 | ||
|
|
814c5b3d3c | ||
|
|
e7e0677291 | ||
|
|
823f4b7ad1 | ||
|
|
a3c77480f7 | ||
|
|
e367265dbb | ||
|
|
7e0deb29e0 | ||
|
|
0d94db4331 | ||
|
|
177482b33a | ||
|
|
63b183a9fe | ||
|
|
858117f8c0 | ||
|
|
ac4355d2e1 | ||
|
|
ce7da2db66 | ||
|
|
0a4a1def1e | ||
|
|
35f4deca76 | ||
|
|
ba1451a0e9 | ||
|
|
40d69e6e20 | ||
|
|
b8ba947ba8 | ||
|
|
06be57815e | ||
|
|
81e37a5736 | ||
|
|
b8ea546b3f | ||
|
|
0bb31b985d | ||
|
|
453824260f | ||
|
|
a8fdffc3e9 | ||
|
|
24164d9454 | ||
|
|
4365a94ea9 | ||
|
|
7c1ec04380 | ||
|
|
09b6365321 | ||
|
|
eb2e383cc7 |
19
.github/workflows/bot-issues-translator.yml
vendored
Normal 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. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
|
||||
2
.github/workflows/deploy-docs.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/deploy-preview.yml
vendored
@@ -2,6 +2,10 @@ name: deploy-docs-preview
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'docSite/**'
|
||||
branches:
|
||||
- 'main'
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
@@ -51,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
|
||||
|
||||
16
.github/workflows/fastgpt-image.yml
vendored
@@ -3,7 +3,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'client/**'
|
||||
- 'projects/app/**'
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
@@ -25,6 +25,13 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
driver-opts: network=host
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@@ -38,24 +45,26 @@ jobs:
|
||||
else
|
||||
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build and publish image for main branch or tag push event
|
||||
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" \
|
||||
--label "org.opencontainers.image.licenses=MIT" \
|
||||
--push \
|
||||
--cache-from=type=local,src=/tmp/.buildx-cache \
|
||||
--cache-to=type=local,dest=/tmp/.buildx-cache \
|
||||
-t ${DOCKER_REPO_TAGGED} \
|
||||
-f Dockerfile \
|
||||
.
|
||||
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
|
||||
@@ -79,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
|
||||
|
||||
4
.vscode/settings.json
vendored
@@ -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
@@ -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}"]
|
||||
@@ -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)
|
||||
|
||||
## 🏘️ 社区交流群
|
||||
|
||||
@@ -93,7 +93,6 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||
|
||||
- [FastGPT 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
|
||||
- [docker 部署教程视频](https://www.bilibili.com/video/BV1jo4y147fT/)
|
||||
- [公众号接入视频教程](https://www.bilibili.com/video/BV1xh4y1t7fy/)
|
||||
- [FastGPT 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
|
||||
|
||||
## 💪 相关项目
|
||||
@@ -105,6 +104,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||
|
||||
## 🤝 第三方生态
|
||||
|
||||
- [OnWeChat 个人微信/企微机器人](https://doc.fastgpt.run/docs/use-cases/onwechat/)
|
||||
- [luolinAI: 企微机器人,开箱即用](https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot)
|
||||
|
||||
## 🌟 Star History
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
5
client/next-env.d.ts
vendored
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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}`);
|
||||
@@ -1,93 +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';
|
||||
|
||||
/* 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 const getExportDataList = (data: { kbId: string }) =>
|
||||
GET<[string, string, string][]>(`/plugins/kb/data/exportModelData`, data, {
|
||||
timeout: 600000
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取模型正在拆分数据的数量
|
||||
*/
|
||||
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);
|
||||
24
client/src/api/request/kb.d.ts
vendored
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,110 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { IconProps } from '@chakra-ui/react';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
|
||||
const map = {
|
||||
appFill: require('./icons/fill/app.svg').default,
|
||||
appLight: require('./icons/light/app.svg').default,
|
||||
copy: require('./icons/copy.svg').default,
|
||||
chatSend: require('./icons/chatSend.svg').default,
|
||||
delete: require('./icons/delete.svg').default,
|
||||
stop: require('./icons/stop.svg').default,
|
||||
collectionLight: require('./icons/collectionLight.svg').default,
|
||||
collectionSolid: require('./icons/collectionSolid.svg').default,
|
||||
empty: require('./icons/empty.svg').default,
|
||||
back: require('./icons/back.svg').default,
|
||||
backFill: require('./icons/fill/back.svg').default,
|
||||
more: require('./icons/more.svg').default,
|
||||
tabbarChat: require('./icons/phoneTabbar/chat.svg').default,
|
||||
tabbarModel: require('./icons/phoneTabbar/app.svg').default,
|
||||
tabbarMore: require('./icons/phoneTabbar/more.svg').default,
|
||||
tabbarMe: require('./icons/phoneTabbar/me.svg').default,
|
||||
closeSolid: require('./icons/closeSolid.svg').default,
|
||||
wx: require('./icons/wx.svg').default,
|
||||
out: require('./icons/out.svg').default,
|
||||
git: require('./icons/git.svg').default,
|
||||
gitFill: require('./icons/fill/git.svg').default,
|
||||
googleFill: require('./icons/fill/google.svg').default,
|
||||
menu: require('./icons/menu.svg').default,
|
||||
edit: require('./icons/edit.svg').default,
|
||||
inform: require('./icons/inform.svg').default,
|
||||
export: require('./icons/export.svg').default,
|
||||
text: require('./icons/text.svg').default,
|
||||
history: require('./icons/history.svg').default,
|
||||
kbTest: require('./icons/kbTest.svg').default,
|
||||
date: require('./icons/date.svg').default,
|
||||
apikey: require('./icons/apikey.svg').default,
|
||||
save: require('./icons/save.svg').default,
|
||||
minus: require('./icons/minus.svg').default,
|
||||
chat: require('./icons/light/chat.svg').default,
|
||||
chatFill: require('./icons/fill/chat.svg').default,
|
||||
clear: require('./icons/light/clear.svg').default,
|
||||
apiLight: require('./icons/light/appApi.svg').default,
|
||||
overviewLight: require('./icons/light/overview.svg').default,
|
||||
settingLight: require('./icons/light/setting.svg').default,
|
||||
shareLight: require('./icons/light/share.svg').default,
|
||||
dbLight: require('./icons/light/db.svg').default,
|
||||
dbFill: require('./icons/fill/db.svg').default,
|
||||
appStoreLight: require('./icons/light/appStore.svg').default,
|
||||
appStoreFill: require('./icons/fill/appStore.svg').default,
|
||||
meLight: require('./icons/light/me.svg').default,
|
||||
meFill: require('./icons/fill/me.svg').default,
|
||||
welcomeText: require('./icons/modules/welcomeText.svg').default,
|
||||
variable: require('./icons/modules/variable.svg').default,
|
||||
setTop: require('./icons/light/setTop.svg').default,
|
||||
fullScreenLight: require('./icons/light/fullScreen.svg').default,
|
||||
voice: require('./icons/voice.svg').default,
|
||||
html: require('./icons/file/html.svg').default,
|
||||
pdf: require('./icons/file/pdf.svg').default,
|
||||
markdown: require('./icons/file/markdown.svg').default,
|
||||
importLight: require('./icons/light/import.svg').default,
|
||||
manualImport: require('./icons/file/manualImport.svg').default,
|
||||
indexImport: require('./icons/file/indexImport.svg').default,
|
||||
csvImport: require('./icons/file/csv.svg').default,
|
||||
qaImport: require('./icons/file/qaImport.svg').default,
|
||||
uploadFile: require('./icons/file/uploadFile.svg').default,
|
||||
closeLight: require('./icons/light/close.svg').default,
|
||||
customTitle: require('./icons/light/customTitle.svg').default,
|
||||
billRecordLight: require('./icons/light/billRecord.svg').default,
|
||||
informLight: require('./icons/light/inform.svg').default,
|
||||
payRecordLight: require('./icons/light/payRecord.svg').default,
|
||||
loginoutLight: require('./icons/light/loginout.svg').default,
|
||||
chatModelTag: require('./icons/light/chatModelTag.svg').default,
|
||||
language_en: require('./icons/language/en.svg').default,
|
||||
language_zh: require('./icons/language/zh.svg').default,
|
||||
outlink_share: require('./icons/outlink/share.svg').default,
|
||||
outlink_iframe: require('./icons/outlink/iframe.svg').default,
|
||||
addCircle: require('./icons/circle/add.svg').default,
|
||||
playFill: require('./icons/fill/play.svg').default,
|
||||
courseLight: require('./icons/light/course.svg').default,
|
||||
promotionLight: require('./icons/light/promotion.svg').default,
|
||||
logsLight: require('./icons/light/logs.svg').default,
|
||||
badLight: require('./icons/light/bad.svg').default,
|
||||
markLight: require('./icons/light/mark.svg').default,
|
||||
retryLight: require('./icons/light/retry.svg').default,
|
||||
rightArrowLight: require('./icons/light/rightArrow.svg').default,
|
||||
searchLight: require('./icons/light/search.svg').default,
|
||||
plusFill: require('./icons/fill/plus.svg').default,
|
||||
moveLight: require('./icons/light/move.svg').default
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof map;
|
||||
|
||||
const MyIcon = (
|
||||
{ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & IconProps,
|
||||
ref: any
|
||||
) => {
|
||||
return map[name] ? (
|
||||
<Icon
|
||||
as={map[name]}
|
||||
w={w}
|
||||
h={h}
|
||||
boxSizing={'content-box'}
|
||||
verticalAlign={'top'}
|
||||
fill={'currentcolor'}
|
||||
{...props}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default React.forwardRef(MyIcon);
|
||||
@@ -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'
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,61 +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 type { ChatItemType } from '@/types/chat';
|
||||
import { countOpenAIToken } from '@/utils/plugin/openai';
|
||||
|
||||
type Props = {
|
||||
messages: ChatItemType[];
|
||||
model: string;
|
||||
maxLen: number;
|
||||
};
|
||||
type Response = ChatItemType[];
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await authUser({ req });
|
||||
|
||||
const { messages, model, maxLen } = req.body as Props;
|
||||
|
||||
if (!Array.isArray(messages) || !model || !maxLen) {
|
||||
throw new Error('params is error');
|
||||
}
|
||||
|
||||
return jsonRes<Response>(res, {
|
||||
data: gpt_chatItemTokenSlice({
|
||||
messages,
|
||||
maxToken: maxLen
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function gpt_chatItemTokenSlice({
|
||||
messages,
|
||||
maxToken
|
||||
}: {
|
||||
messages: ChatItemType[];
|
||||
maxToken: number;
|
||||
}) {
|
||||
let result: ChatItemType[] = [];
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msgs = [...result, messages[i]];
|
||||
|
||||
const tokens = countOpenAIToken({ messages: msgs });
|
||||
|
||||
if (tokens < maxToken) {
|
||||
result = msgs;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.length === 0 && messages[0] ? [messages[0]] : result;
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, User } from '@/service/mongo';
|
||||
import { authUser } from '@/service/utils/auth';
|
||||
import { PgClient } from '@/service/pg';
|
||||
import { PgDatasetTableName } from '@/constants/plugin';
|
||||
import { findAllChildrenIds } from '../delete';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
let { kbId } = req.query as {
|
||||
kbId: string;
|
||||
};
|
||||
|
||||
if (!kbId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 凭证校验
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const exportIds = [kbId, ...(await findAllChildrenIds(kbId))];
|
||||
console.log(exportIds);
|
||||
|
||||
const thirtyMinutesAgo = new Date(
|
||||
Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000
|
||||
);
|
||||
|
||||
// auth export times
|
||||
const authTimes = await User.findOne(
|
||||
{
|
||||
_id: userId,
|
||||
$or: [
|
||||
{ 'limit.exportKbTime': { $exists: false } },
|
||||
{ 'limit.exportKbTime': { $lte: thirtyMinutesAgo } }
|
||||
]
|
||||
},
|
||||
'_id limit'
|
||||
);
|
||||
|
||||
if (!authTimes) {
|
||||
const minutes = `${global.feConfigs?.limit?.exportLimitMinutes || 0} 分钟`;
|
||||
throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`);
|
||||
}
|
||||
|
||||
const where: any = [
|
||||
['user_id', userId],
|
||||
'AND',
|
||||
`kb_id IN (${exportIds.map((id) => `'${id}'`).join(',')})`
|
||||
];
|
||||
// 从 pg 中获取所有数据
|
||||
const pgData = await PgClient.select<{ q: string; a: string; source: string }>(
|
||||
PgDatasetTableName,
|
||||
{
|
||||
where,
|
||||
fields: ['q', 'a', 'source'],
|
||||
order: [{ field: 'id', mode: 'DESC' }],
|
||||
limit: 1000000
|
||||
}
|
||||
);
|
||||
|
||||
const data: [string, string, string][] = pgData.rows.map((item) => [
|
||||
item.q.replace(/\n/g, '\\n'),
|
||||
item.a.replace(/\n/g, '\\n'),
|
||||
item.source
|
||||
]);
|
||||
|
||||
// update export time
|
||||
await User.findByIdAndUpdate(userId, {
|
||||
'limit.exportKbTime': new Date()
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: {
|
||||
sizeLimit: '200mb'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,88 +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 { modelToolMap } from '@/utils/plugin';
|
||||
import { getVectorModel } from '@/service/utils/data';
|
||||
import { getVector } from '@/pages/api/openapi/plugin/vector';
|
||||
import { DatasetItemType } from '@/types/plugin';
|
||||
|
||||
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 = modelToolMap.countTokens({
|
||||
messages: [{ obj: 'System', value: q }]
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,92 +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';
|
||||
|
||||
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="https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh"
|
||||
frameBorder="0"
|
||||
onLoad={() => setIsLoaded(true)}
|
||||
onError={() => setIsLoaded(true)}
|
||||
/>
|
||||
</Skeleton>
|
||||
</Box>
|
||||
{isOpenAPIModal && <APIKeyModal onClose={onCloseAPIModal} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default API;
|
||||
@@ -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);
|
||||
@@ -1,374 +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';
|
||||
|
||||
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为了连贯上下文,FastGPT 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。'
|
||||
}
|
||||
{...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;
|
||||
@@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { feConfigs } from '@/store/static';
|
||||
import { serviceSideProps } from '@/utils/i18n';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import Navbar from './components/Navbar';
|
||||
import Hero from './components/Hero';
|
||||
import Ability from './components/Ability';
|
||||
import Choice from './components/Choice';
|
||||
import Footer from './components/Footer';
|
||||
import Loading from '@/components/Loading';
|
||||
|
||||
const Home = ({ homeUrl = '/' }: { homeUrl: string }) => {
|
||||
const router = useRouter();
|
||||
|
||||
if (homeUrl !== '/') {
|
||||
router.replace(homeUrl);
|
||||
}
|
||||
|
||||
return homeUrl === '/' ? (
|
||||
<Box id="home" bg={'myWhite.600'} h={'100vh'} overflowY={'auto'} overflowX={'hidden'}>
|
||||
<Box position={'fixed'} zIndex={10} top={0} left={0} right={0}>
|
||||
<Navbar />
|
||||
</Box>
|
||||
<Box maxW={'1200px'} pt={'70px'} m={'auto'}>
|
||||
<Hero />
|
||||
<Ability />
|
||||
<Box my={[4, 6]}>
|
||||
<Choice />
|
||||
</Box>
|
||||
</Box>
|
||||
{feConfigs?.show_git && (
|
||||
<Box bg={'white'}>
|
||||
<Footer />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
...(await serviceSideProps(content)),
|
||||
homeUrl: process.env.HOME_URL || '/'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default Home;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
@@ -1,107 +0,0 @@
|
||||
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
||||
import { ChatContextFilter } from '@/service/utils/chat/index';
|
||||
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({
|
||||
model: agentModel,
|
||||
prompts: messages,
|
||||
maxTokens
|
||||
});
|
||||
const adaptMessages = adaptChatItem_openAI({ 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
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,131 +0,0 @@
|
||||
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
||||
import { ChatContextFilter } from '@/service/utils/chat/index';
|
||||
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({
|
||||
// @ts-ignore
|
||||
model: agentModel,
|
||||
prompts: messages,
|
||||
maxTokens
|
||||
});
|
||||
const adaptMessages = adaptChatItem_openAI({ 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
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
6
client/src/types/openapi.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
export interface UserOpenApiKey {
|
||||
id: string;
|
||||
apiKey: string;
|
||||
createTime: Date;
|
||||
lastUsedTime?: Date;
|
||||
}
|
||||
65
client/src/types/plugin.d.ts
vendored
@@ -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;
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import { countOpenAIToken, openAiSliceTextByToken } from './openai';
|
||||
import { gpt_chatItemTokenSlice } from '@/pages/api/openapi/text/gptMessagesSlice';
|
||||
|
||||
export const modelToolMap = {
|
||||
countTokens: countOpenAIToken,
|
||||
sliceText: openAiSliceTextByToken,
|
||||
tokenSlice: gpt_chatItemTokenSlice
|
||||
};
|
||||
@@ -1,100 +0,0 @@
|
||||
import { encoding_for_model } from '@dqbd/tiktoken';
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||
import axios from 'axios';
|
||||
import type { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
|
||||
|
||||
export const getOpenAiEncMap = () => {
|
||||
if (typeof window !== 'undefined' && window.OpenAiEncMap) {
|
||||
return window.OpenAiEncMap;
|
||||
}
|
||||
if (typeof global !== 'undefined' && global.OpenAiEncMap) {
|
||||
return global.OpenAiEncMap;
|
||||
}
|
||||
const enc = encoding_for_model('gpt-3.5-turbo', {
|
||||
'<|im_start|>': 100264,
|
||||
'<|im_end|>': 100265,
|
||||
'<|im_sep|>': 100266
|
||||
});
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.OpenAiEncMap = enc;
|
||||
}
|
||||
if (typeof global !== 'undefined') {
|
||||
global.OpenAiEncMap = enc;
|
||||
}
|
||||
|
||||
return enc;
|
||||
};
|
||||
|
||||
export const adaptChatItem_openAI = ({
|
||||
messages,
|
||||
reserveId
|
||||
}: {
|
||||
messages: ChatItemType[];
|
||||
reserveId: boolean;
|
||||
}): MessageItemType[] => {
|
||||
const map = {
|
||||
[ChatRoleEnum.AI]: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
[ChatRoleEnum.Human]: ChatCompletionRequestMessageRoleEnum.User,
|
||||
[ChatRoleEnum.System]: ChatCompletionRequestMessageRoleEnum.System
|
||||
};
|
||||
return messages.map((item) => ({
|
||||
...(reserveId && { dataId: item.dataId }),
|
||||
role: map[item.obj] || ChatCompletionRequestMessageRoleEnum.System,
|
||||
content: item.value || ''
|
||||
}));
|
||||
};
|
||||
|
||||
export function countOpenAIToken({ messages }: { messages: ChatItemType[] }) {
|
||||
const adaptMessages = adaptChatItem_openAI({ messages, reserveId: true });
|
||||
const token = adaptMessages.reduce((sum, item) => {
|
||||
const text = `${item.role}\n${item.content}`;
|
||||
|
||||
/* use textLen as tokens if encode error */
|
||||
const tokens = (() => {
|
||||
try {
|
||||
const enc = getOpenAiEncMap();
|
||||
const encodeText = enc.encode(text);
|
||||
return encodeText.length + 3; // 补充估算值
|
||||
} catch (error) {
|
||||
return text.length;
|
||||
}
|
||||
})();
|
||||
|
||||
return sum + tokens;
|
||||
}, 0);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export const openAiSliceTextByToken = ({ text, length }: { text: string; length: number }) => {
|
||||
const enc = getOpenAiEncMap();
|
||||
|
||||
try {
|
||||
const encodeText = enc.encode(text);
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(enc.decode(encodeText.slice(0, length)));
|
||||
} catch (error) {
|
||||
return text.slice(0, length);
|
||||
}
|
||||
};
|
||||
|
||||
export const authOpenAiKey = async (key: string) => {
|
||||
return axios
|
||||
.get('https://ccdbwscohpmu.cloud.sealos.io/openai/v1/dashboard/billing/subscription', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${key}`
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.data.access_until) {
|
||||
return Promise.resolve('OpenAI Key 可能无效');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return Promise.reject(err?.response?.data?.error?.message || 'OpenAI Key 可能无效');
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
.docs-content .main-content img, .docs-content .main-content svg:not(.gitinfo svg) {
|
||||
.docs-content .main-content img, .docs-content .main-content svg:not(.gitinfo svg):not(a svg) {
|
||||
max-width: 80% !important;
|
||||
height: auto;
|
||||
display: block !important;
|
||||
@@ -23,7 +23,77 @@ div.code-toolbar {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
p {
|
||||
li p {
|
||||
margin-top: 1rem !important;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
height: 118px !important;
|
||||
}
|
||||
|
||||
/*
|
||||
footer a:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
*/
|
||||
|
||||
.medium-zoom-overlay,
|
||||
.medium-zoom-image--opened {
|
||||
z-index: 1999;
|
||||
}
|
||||
|
||||
/* 徽章样式 */
|
||||
.github-badge {
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
text-shadow: none;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
line-height: 15px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.github-badge .badge-subject {
|
||||
display: inline-block;
|
||||
background-color: #4D4D4D;
|
||||
padding: 4px 4px 4px 6px;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.github-badge .badge-value {
|
||||
display: inline-block;
|
||||
padding: 4px 6px 4px 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.github-badge .bg-brightgreen {
|
||||
background-color: #4DC820 !important;
|
||||
}
|
||||
.github-badge .bg-orange {
|
||||
background-color: #FFA500 !important;
|
||||
}
|
||||
.github-badge .bg-yellow {
|
||||
background-color: #D8B024 !important;
|
||||
}
|
||||
.github-badge .bg-blueviolet {
|
||||
background-color: #8833D7 !important;
|
||||
}
|
||||
.github-badge .bg-pink {
|
||||
background-color: #F26BAE !important;
|
||||
}
|
||||
.github-badge .bg-red {
|
||||
background-color: #e05d44 !important;
|
||||
}
|
||||
.github-badge .bg-blue {
|
||||
background-color: #007EC6 !important;
|
||||
}
|
||||
.github-badge .bg-lightgrey {
|
||||
background-color: #9F9F9F !important;
|
||||
}
|
||||
.github-badge .bg-grey, .github-badge .bg-gray {
|
||||
background-color: #555 !important;
|
||||
}
|
||||
.github-badge .bg-lightgrey, .github-badge .bg-lightgray {
|
||||
background-color: #9f9f9f !important;
|
||||
}
|
||||
@@ -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";
|
||||
14
docSite/assets/images/logos/mark.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="26" height="26" viewBox="0 0 1041 1348" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M340.837 0.33933L681.068 0.338989V0.455643C684.032 0.378397 686.999 0.339702 689.967 0.339702C735.961 0.3397 781.504 9.62899 823.997 27.6772C866.49 45.7254 905.099 72.1791 937.622 105.528C970.144 138.877 995.942 178.467 1013.54 222.04C1031.14 265.612 1040.2 312.312 1040.2 359.474L340.836 359.474L340.836 1347.84C296.157 1347.84 251.914 1338.55 210.636 1320.49C169.357 1302.43 131.85 1275.95 100.257 1242.58C68.6636 1209.21 43.6023 1169.59 26.5041 1125.99C11.3834 1087.43 2.75216 1046.42 0.957956 1004.81H0.605869L0.605897 368.098H0.70363C0.105752 341.831 2.23741 315.443 7.14306 289.411C20.2709 219.745 52.6748 155.754 100.257 105.528C147.839 55.3017 208.462 21.0975 274.461 7.24017C296.426 2.62833 318.657 0.339101 340.837 0.33933Z" fill="url(#paint0_linear_1172_228)"/>
|
||||
<path d="M633.639 904.645H513.029V576.37H635.422V576.377C678.161 576.607 720.454 585.093 759.951 601.37C799.997 617.874 836.384 642.064 867.033 672.559C897.683 703.054 921.996 739.257 938.583 779.101C955.171 818.944 963.709 861.648 963.709 904.775H633.639V904.645Z" fill="url(#paint1_linear_1172_228)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1172_228" x1="520.404" y1="0.338989" x2="520.404" y2="1347.84" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#326DFF"/>
|
||||
<stop offset="1" stop-color="#8EAEFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1172_228" x1="738.369" y1="576.37" x2="738.369" y2="904.775" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#326DFF"/>
|
||||
<stop offset="1" stop-color="#8EAEFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 436 KiB |
BIN
docSite/assets/imgs/fastgpt-api2.png
Normal file
|
After Width: | Height: | Size: 335 KiB |
BIN
docSite/assets/imgs/feishu-env.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
docSite/assets/imgs/feishu-res.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
docSite/assets/imgs/functional-arch.webp
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
docSite/assets/imgs/getKbId.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
docSite/assets/imgs/sealos-fastgpt.webp
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
docSite/assets/imgs/sharelinkProcess.png
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
docSite/assets/imgs/versatile_assistant_1.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
docSite/assets/imgs/versatile_assistant_2.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docSite/assets/imgs/versatile_assistant_3.jpg
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
docSite/assets/imgs/versatile_assistant_4.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
docSite/assets/imgs/versatile_assistant_5.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
docSite/assets/imgs/versatile_assistant_6.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
docSite/assets/imgs/versatile_assistant_7.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
@@ -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/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. 二次开发如何操作?
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 测试使用
|
||||
|
||||
@@ -66,7 +66,7 @@ Authorization 为 sk-key。model 为刚刚在 One API 填写的自定义模型
|
||||
"defaultToken": 500,
|
||||
"maxToken": 1800
|
||||
}
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
## 测试使用
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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 .
|
||||
```
|
||||
|
||||
## 创建拉取请求
|
||||
|
||||
531
docSite/content/docs/development/openApi.md
Normal 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 |
|
||||
| --------------------- | --------------------- |
|
||||
|  |  |
|
||||
|
||||
# 接口
|
||||
|
||||
## 发起对话
|
||||
|
||||
{{% 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 %}}
|
||||
|
||||
### 如何获取知识库ID(kbId)
|
||||
|
||||

|
||||
|
||||
### 知识库添加数据
|
||||
|
||||
{{< 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": "错误提示"
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 分享链接中增加额外 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)
|
||||
@@ -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": [
|
||||
|
||||
@@ -21,4 +21,8 @@ Sealos 的服务器在国外,不需要额外处理网络问题,无需服务
|
||||
|
||||
> 用户名:`root`
|
||||
>
|
||||
> 密码就是刚刚一键部署时设置的环境变量
|
||||
> 密码就是刚刚一键部署时设置的环境变量
|
||||
|
||||
## 部署架构图
|
||||
|
||||

|
||||
23
docSite/content/docs/installation/upgrading/442.md
Normal 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 表的索引,之前过期时间有误。
|
||||
|
||||
31
docSite/content/docs/installation/upgrading/445.md
Normal 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. 优化 - 全局变量与开场白合并成同一模块。
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
weight: 760
|
||||
title: "版本升级"
|
||||
description: "FastGPT 升级指南"
|
||||
title: "版本更新/升级操作"
|
||||
description: "FastGPT 版本更新介绍及升级操作"
|
||||
icon: upgrade
|
||||
draft: false
|
||||
images: []
|
||||
|
||||
@@ -78,7 +78,7 @@ FastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入
|
||||
|
||||
## 知识库核心流程图
|
||||
|
||||

|
||||

|
||||
|
||||
## 免责声明
|
||||
|
||||
|
||||
@@ -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 一致,您可以随时通过「使用记录」来查看余额消耗明细的说明,来对比计算是否一致。
|
||||
|
||||

|
||||
|
||||
以下是详细的价格表:
|
||||
|
||||
## FastGPT 线上计费
|
||||
|
||||
目前,FastGPT 线上计费也仅按 Tokens 使用数量为准。以下是详细的计费表(最新定价以线上表格为准,可在点击充值后实时获取):
|
||||
|
||||
{{< table "table-hover table-striped-columns" >}}
|
||||
| 计费项 | 价格: 元/ 1K tokens(包含上下文) |
|
||||
|
||||
68
docSite/content/docs/use-cases/feishu.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: " 接入飞书 "
|
||||
description: "FastGPT 接入飞书机器人 "
|
||||
icon: "chat"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 322
|
||||
---
|
||||
|
||||
# FastGPT 一分钟接入飞书
|
||||
|
||||
[Feishu OpenAI GitHub 地址](https://github.com/ConnectAI-E/Feishu-OpenAI)
|
||||
|
||||
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更第三方应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
|
||||
|
||||
## 1. 获取 FastGPT 的 OpenAPI 秘钥
|
||||
|
||||
依次选择应用 -> 「API 访问」,然后点击「API 密钥」来创建密钥。 [参考这篇文章](/docs/use-cases/openapi/)
|
||||
|
||||

|
||||
|
||||
## 2. 部署飞书服务
|
||||
|
||||
推荐使用 Railway 一键部署
|
||||
|
||||
[](https://railway.app/template/10D-TF?referralCode=oMcVS2)
|
||||
|
||||
参考环境变量配置:
|
||||
|
||||

|
||||
|
||||
FastGPT 集成**重点参数:**
|
||||
|
||||
```bash
|
||||
#上一步FastGPT的OpenAPI 秘钥
|
||||
OPENAI_KEY=fastgpt-z51pkjqm9nrk03a1rx2funoy
|
||||
#调用OpenAI的BaseUrl要换成FastGPT的
|
||||
API_URL=https://fastgpt.run/api/openapi
|
||||
```
|
||||
|
||||
## 3. 创建飞书机器人
|
||||
|
||||
1. 前往 [开发者平台](https://open.feishu.cn/app?lang=zh-CN) 创建应用 , 并获取到 APPID 和 Secret
|
||||
2. 前往`应用功能-机器人`, 创建机器人
|
||||
3. 从 cpolar、serverless 或 Railway 获得公网地址,在飞书机器人后台的 `事件订阅` 板块填写。例如,
|
||||
- `http://xxxx.r6.cpolar.top` 为 cpolar 暴露的公网地址
|
||||
- `/webhook/event` 为统一的应用路由
|
||||
- 最终的回调地址为 `http://xxxx.r6.cpolar.top/webhook/event`
|
||||
4. 在飞书机器人后台的 `机器人` 板块,填写消息卡片请求网址。例如,
|
||||
- `http://xxxx.r6.cpolar.top` 为 cpolar 暴露的公网地址
|
||||
- `/webhook/card` 为统一的应用路由
|
||||
- 最终的消息卡片请求网址为 `http://xxxx.r6.cpolar.top/webhook/card`
|
||||
5. 在事件订阅板块,搜索三个词`机器人进群`、 `接收消息`、 `消息已读`, 把他们后面所有的权限全部勾选。 进入权限管理界面,搜索`图片`, 勾选`获取与上传图片或文件资源`。 最终会添加下列回调事件
|
||||
- im:resource(获取与上传图片或文件资源)
|
||||
- im:message
|
||||
- im:message.group_at_msg(获取群组中所有消息)
|
||||
- im:message.group_at_msg:readonly(接收群聊中 @ 机器人消息事件)
|
||||
- im:message.p2p_msg(获取用户发给机器人的单聊消息)
|
||||
- im:message.p2p_msg:readonly(读取用户发给机器人的单聊消息)
|
||||
- im:message:send_as_bot(获取用户在群组中 @ 机器人的消息)
|
||||
- im:chat:readonly(获取群组信息)
|
||||
- im:chat(获取与更新群组信息)
|
||||
|
||||
## 4. 测试飞书机器人
|
||||
|
||||
私聊机器人,或者群里艾特它,就可以基于 FastGPT 的应用进行回答啦
|
||||
|
||||

|
||||
@@ -73,7 +73,7 @@ weight: 340
|
||||

|
||||
|
||||
导入结果如上图。可以看到,我们均采用的是问答对的格式,而不是粗略的直接导入。目的就是为了模拟用户问题,进一步的提高向量搜索的匹配效果。可以为同一个问题设置多种问法,效果更佳。
|
||||
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)
|
||||
|
||||
## 知识库微调和参数调整
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
---
|
||||
title: "对接 OnWeChat"
|
||||
description: "FastGPT 对接 OnWeChat"
|
||||
title: "对接 chatgpt-on-wechat"
|
||||
description: "FastGPT 对接 chatgpt-on-wechat"
|
||||
icon: "chat"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 320
|
||||
weight: 312
|
||||
---
|
||||
|
||||
# 1 分钟对接 OnWeChat
|
||||
# 1 分钟对接 chatgpt-on-wechat
|
||||
|
||||
[OnWeChat GitHub 地址](https://github.com/zhayujie/chatgpt-on-wechat)
|
||||
[chatgpt-on-wechat GitHub 地址](https://github.com/zhayujie/chatgpt-on-wechat)
|
||||
|
||||
由于 FastGPT 的 OpenAPI 功能和 GPT 的对齐,可以无需变更原来的应用即可使用 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
|
||||
|
||||

|
||||
|
||||
## 2. 组合带应用 ID 的秘钥
|
||||
|
||||
利用刚复制的 API 秘钥加上 AppId 组合成一个新的秘钥,格式为:`API 秘钥-AppId`,例如:`fastgpt-z51pkjqm9nrk03a1rx2funoy-642adec15f04d67d4613efdb`。
|
||||
## 3. 创建 docker-compose.yml 文件
|
||||
|
||||
这个秘钥将会调用指定的应用。
|
||||
|
||||
## 3. 创建 OnWeChat 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'
|
||||
@@ -67,9 +62,9 @@ services:
|
||||
|
||||
```
|
||||
|
||||
## 4. 运行 OnWeChat
|
||||
## 4. 运行 chatgpt-on-wechat
|
||||
|
||||
```base
|
||||
```bash
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
## 组合秘钥
|
||||
{{% 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) 示例:**
|
||||
@@ -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}}"
|
||||
```
|
||||
|
||||
1535
docSite/content/docs/workflow/examples/versatile_assistant.md
Normal 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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,22 +42,20 @@
|
||||
{{ end -}}
|
||||
<!-- change -->
|
||||
<div class="docs-content col-12 {{ if .IsNode }}{{ else }}{{ if site.Params.docs.toc | default true }}{{ if and (ne .Params.toc false) }}col-xl-9{{else}}{{end}}{{ else }}{{ end }}{{ end }} mt-0">
|
||||
<div class="mb-3">
|
||||
<div class="mb-0 d-flex">
|
||||
{{ if site.Params.docs.titleIcon | default false }}
|
||||
<i class="material-icons title-icon me-2">{{- .Params.icon | default "article" }}</i>
|
||||
{{ end }}
|
||||
<h1 class="content-title mb-0">
|
||||
{{ if site.Params.docs.titleIcon | default false }}
|
||||
<i class="material-icons me-0">{{- .Params.icon | default "article" }}</i>
|
||||
{{ end }}
|
||||
<span class="title-text">
|
||||
{{ $currentPage.Title }}
|
||||
</span>
|
||||
{{ $currentPage.Title }}
|
||||
{{ if .Draft }}
|
||||
<span class="badge bg-default fs-6 mb-1 align-middle">DRAFT</span>
|
||||
{{ end }}
|
||||
</h1>
|
||||
{{ if site.Params.docs.descriptions | default false }}
|
||||
<p class="lead mb-0">{{ $currentPage.Description | markdownify }}</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ if site.Params.docs.descriptions | default false }}
|
||||
<p class="lead mb-3">{{ $currentPage.Description | markdownify }}</p>
|
||||
{{ end }}
|
||||
<div id="content" class="main-content" {{ if eq .Site.Params.docs.toc true -}}data-bs-spy="scroll" data-bs-root-margin="0px 0px -65%" data-bs-target="#toc-mobile"{{ end }}>
|
||||
{{ block "main" . }}{{ end }}
|
||||
</div>
|
||||
|
||||
19
docSite/layouts/partials/docs/footer.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- Footer Start -->
|
||||
<footer class="shadow py-3 d-print-none">
|
||||
<div class="row align-items-center" style="height: 90px">
|
||||
<div class="col">
|
||||
<div class="text-sm-start text-center mx-md-2">
|
||||
<p class="mb-0">
|
||||
{{ $yearToken := (cond (isset .Site.Params (lower "copyrightYearToken")) $.Site.Params.copyrightYearToken ":YEAR:") }}
|
||||
{{ replace $.Site.Params.footer.copyright $yearToken (string (now.Format "2006")) | markdownify }}
|
||||
</p>
|
||||
<p class="github-badge">
|
||||
<span class="badge-subject">云操作系统</span><span class="badge-value bg-blue"><a style="color:#fff" href="https://sealos.io/" target="_blank">Sealos</a></span>
|
||||
<span class="badge-subject">云开发</span><span class="badge-value bg-brightgreen"><a style="color:#fff" href="https://laf.run" target="_blank">Laf</a></span>
|
||||
<span class="badge-subject">云原生存储</span><span class="badge-value bg-orange"><a style="color:#fff" href="https://github.com/labring/sealfs" target="_blank">Sealfs</a></span>
|
||||
</p>
|
||||
</div>
|
||||
</div><!--end col-->
|
||||
</div><!--end row-->
|
||||
</footer><!--end footer-->
|
||||
<!-- End -->
|
||||
@@ -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 -}}
|
||||
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
{{ $iconPath = "images/social/bitbucket_icon.svg" }}
|
||||
{{ end }}
|
||||
|
||||
<!-- change -->
|
||||
{{ $repoURL = $repoURL | append "docSite/content" .Site.LanguagePrefix $filePath }}
|
||||
{{ $repoURL = delimit $repoURL "/" }}
|
||||
{{ $editPageURL := replaceRE "(https?://)|(/)+" "$1$2" $repoURL }}
|
||||
|
||||
<!-- change -->
|
||||
<div class="gitinfo d-flex flex-wrap justify-content-between align-items-center opacity-85 {{ if .Site.Params.docs.editPage -}}pt-3{{ else }}visually-hidden{{ end }}">
|
||||
{{ if .Site.Params.docs.editPage | default false -}}
|
||||
<div id="edit-this-page" class="mt-1">
|
||||
@@ -28,7 +30,7 @@
|
||||
{{ .Content | safeHTML }}
|
||||
{{ end }}
|
||||
</span>
|
||||
{{ i18n "edit_page" }}
|
||||
{{ i18n "edit_page" }} <!-- change -->
|
||||
<!-- <span class="material-icons size-20 align-text-bottom text-primary">open_in_new</span> -->
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,76 +1,107 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>
|
||||
{{- $url := replace .Permalink ( printf "%s" .Site.BaseURL) "" }} {{- if eq $url "/" }} {{-
|
||||
.Site.Title }} {{- else }} {{- if .Params.heading }} {{ .Params.heading }} {{ else }} {{- if eq
|
||||
.Title .Site.Title }} {{- .Title }} {{- else }} {{ .Site.Params.docs.Title | default
|
||||
(.Site.Title) }} | {{ .Title }} {{- end }} {{- end }} {{- end -}}
|
||||
</title>
|
||||
{{- if not hugo.IsProduction }}
|
||||
<meta name="robots" content="noindex" />
|
||||
{{- end }}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="A Modern Documentation Theme for Hugo" />
|
||||
<meta name="keywords" content="Documentation, Hugo, Hugo Theme, Bootstrap" />
|
||||
<meta name="author" content="Colin Wilson - Lotus Labs" />
|
||||
<meta name="email" content="support@aigis.uk" />
|
||||
<meta name="website" content="https://lotusdocs.dev" />
|
||||
<meta name="Version" content="v0.1.0" />
|
||||
<!-- favicon -->
|
||||
{{ block "favicon" . }}{{ partialCached (printf "%s/%s" ($.Scratch.Get "pathName")
|
||||
"head/favicon.html") . }}{{ end }}
|
||||
<!-- Dark Mode -->
|
||||
{{ if eq .Site.Params.docs.darkMode true -}} {{ $darkModeInit := resources.Get (printf "/%s/%s"
|
||||
($.Scratch.Get "pathName") "js/darkmode-init.js") | js.Build | minify -}}
|
||||
<script>
|
||||
{
|
||||
{
|
||||
$darkModeInit.Content | safeJS;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{ end -}}
|
||||
<!-- FlexSearch -->
|
||||
{{ if or (not (isset .Site.Params.flexsearch "enabled")) (eq .Site.Params.flexsearch.enabled true)
|
||||
-}} {{ if and (.Site.Params.docsearch.appID) (.Site.Params.docsearch.apiKey) -}} {{ else }} {{
|
||||
$flexSearch := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName")
|
||||
"js/flexsearch.bundle.js") }} {{- if not .Site.IsServer }} {{ $flexSearch := $flexSearch | minify
|
||||
| fingerprint "sha384" }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{ $flexSearch.Permalink }}"
|
||||
integrity="{{ $flexSearch.Data.Integrity }}"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
{{ else }}
|
||||
<script type="text/javascript" src="{{ $flexSearch.Permalink }}"></script>
|
||||
{{ end }} {{ end }} {{ end }}
|
||||
<!-- Google Fonts -->
|
||||
{{- partialCached "google-fonts" . }}
|
||||
<!-- Custom CSS -->
|
||||
{{- $options := dict "enableSourceMap" true }} {{- if hugo.IsProduction}} {{- $options := dict
|
||||
"enableSourceMap" false "outputStyle" "compressed" }} {{- end }} {{- $style := resources.Get
|
||||
(printf "/%s/%s" ($.Scratch.Get "pathName") "scss/style.scss") }} {{- $style = $style |
|
||||
resources.ExecuteAsTemplate (printf "/%s/%s" ($.Scratch.Get "pathName") "scss/style.scss") . |
|
||||
resources.ToCSS $options }} {{- if hugo.IsProduction }} {{- $style = $style | minify | fingerprint
|
||||
"sha384" }} {{- end -}}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ $style.RelPermalink }}"
|
||||
{{
|
||||
if
|
||||
hugo.IsProduction
|
||||
}}integrity="{{ $style.Data.Integrity }}"
|
||||
{{
|
||||
end
|
||||
-}}
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<!-- Plausible Analytics Config -->
|
||||
{{- if not .Site.IsServer }} {{ if and (.Site.Params.plausible.scriptURL | default
|
||||
"https://plausible.io") (.Site.Params.plausible.dataDomain) -}} {{- partialCached (printf "%s/%s"
|
||||
($.Scratch.Get "pathName") "head/plausible") . }} {{- end -}} {{- end -}}
|
||||
<!-- Google Analytics v4 Config -->
|
||||
{{- if not .Site.IsServer }} {{- if .Site.GoogleAnalytics }} {{- template
|
||||
"_internal/google_analytics.html" . -}} {{- end -}} {{- end -}}
|
||||
</head>
|
||||
<meta charset="utf-8" />
|
||||
<title>
|
||||
{{- $url := replace .Permalink ( printf "%s" .Site.BaseURL) "" }}
|
||||
{{- if eq $url "/" }}
|
||||
{{- .Site.Title }}
|
||||
{{- else }}
|
||||
{{- if .Params.heading }}
|
||||
{{ .Params.heading }}
|
||||
{{ else }}
|
||||
{{- if eq .Title .Site.Title }}
|
||||
{{- .Title }}
|
||||
{{- else }}
|
||||
{{- .Title }} | {{ .Site.Params.docs.Title | default (.Site.Title) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
</title>
|
||||
{{- if not hugo.IsProduction }}
|
||||
<meta name="robots" content="noindex">
|
||||
{{- end }}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{{- with .Description | default ($.Param "description") }}
|
||||
<meta name="description" content="{{ . }}">
|
||||
{{- end }}
|
||||
<meta name="keywords" content="Documentation, Hugo, Hugo Theme, Bootstrap" />
|
||||
<meta name="author" content="Colin Wilson - Lotus Labs" />
|
||||
<meta name="email" content="support@aigis.uk" />
|
||||
<meta name="website" content="https://lotusdocs.dev" />
|
||||
<meta name="Version" content="v0.1.0" />
|
||||
<!-- favicon -->
|
||||
{{ block "favicon" . }}{{ partialCached (printf "%s/%s" ($.Scratch.Get "pathName") "head/favicon.html") . }}{{ end }}
|
||||
<!-- Dark Mode -->
|
||||
{{ if eq .Site.Params.docs.darkMode true -}}
|
||||
{{ $darkModeInit := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/darkmode-init.js") | js.Build | minify -}}
|
||||
<script>{{ $darkModeInit.Content | safeJS }}</script>
|
||||
{{ end -}}
|
||||
<!-- FlexSearch -->
|
||||
{{ if or (not (isset .Site.Params.flexsearch "enabled")) (eq .Site.Params.flexsearch.enabled true) -}}
|
||||
{{ if and (.Site.Params.docsearch.appID) (.Site.Params.docsearch.apiKey) -}}
|
||||
{{ else }}
|
||||
{{ $flexSearch := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/flexsearch.bundle.js") }}
|
||||
{{- if not .Site.IsServer }}
|
||||
{{ $flexSearch := $flexSearch | minify | fingerprint "sha384" }}
|
||||
<script type="text/javascript" src="{{ $flexSearch.Permalink }}" integrity="{{ $flexSearch.Data.Integrity }}" crossorigin="anonymous"></script>
|
||||
{{ else }}
|
||||
<script type="text/javascript" src="{{ $flexSearch.Permalink }}"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<!-- Google Fonts -->
|
||||
{{- partialCached "google-fonts" . }}
|
||||
<!-- Custom CSS -->
|
||||
{{- $options := dict "enableSourceMap" true }}
|
||||
{{- if hugo.IsProduction}}
|
||||
{{- $options := dict "enableSourceMap" false "outputStyle" "compressed" }}
|
||||
{{- end }}
|
||||
{{- $style := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "scss/style.scss") }}
|
||||
{{- $style = $style | resources.ExecuteAsTemplate (printf "/%s/%s" ($.Scratch.Get "pathName") "scss/style.scss") . | resources.ToCSS $options }}
|
||||
{{- if hugo.IsProduction }}
|
||||
{{- $style = $style | minify | fingerprint "sha384" }}
|
||||
{{- end -}}
|
||||
<link rel="stylesheet" href="{{ $style.RelPermalink }}" {{ if hugo.IsProduction }}integrity="{{ $style.Data.Integrity }}"{{ end -}} crossorigin="anonymous">
|
||||
<!-- Katex CSS -->
|
||||
{{- if .Params.katex -}}
|
||||
{{- $options := dict "enableSourceMap" true }}
|
||||
{{- if hugo.IsProduction}}
|
||||
{{- $options := dict "enableSourceMap" false "outputStyle" "compressed" }}
|
||||
{{- end -}}
|
||||
{{- $katexCSS := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "scss/katex.scss") }}
|
||||
{{- $katexCSS = $katexCSS | resources.ExecuteAsTemplate (printf "/%s/%s" ($.Scratch.Get "pathName") "scss/katex.scss") . | resources.ToCSS $options }}
|
||||
{{- if hugo.IsProduction }}
|
||||
{{- $katexCSS = $katexCSS | minify | fingerprint "sha384" }}
|
||||
{{- end -}}
|
||||
<link rel="stylesheet" href="{{ $katexCSS.RelPermalink }}" {{ if hugo.IsProduction }}integrity="{{ $katexCSS.Data.Integrity }}"{{ end -}} crossorigin="anonymous">
|
||||
{{- end -}}
|
||||
<!-- Katex JS -->
|
||||
{{- if .Params.katex -}}
|
||||
{{ $katex := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/katex.js") }}
|
||||
{{ $katexAutoRender := resources.Get (printf "/%s/%s" ($.Scratch.Get "pathName") "js/auto-render.js") }}
|
||||
{{ if hugo.IsProduction }}
|
||||
{{ $katex = $katex | minify | fingerprint "sha384" }}
|
||||
{{ $katexAutoRender = $katexAutoRender | minify | fingerprint "sha384" }}
|
||||
{{- end -}}
|
||||
<script src="{{ $katex.RelPermalink }}" {{ if hugo.IsProduction }}integrity="{{ $katex.Data.Integrity }}"{{ end -}} defer></script>
|
||||
<script src="{{ $katexAutoRender.RelPermalink }}" {{ if hugo.IsProduction }}integrity="{{ $katexAutoRender.Data.Integrity }}"{{ end -}} defer></script>
|
||||
{{ end -}}
|
||||
|
||||
<!-- Katex Config -->
|
||||
{{ if .Params.katex }}
|
||||
{{- partialCached (printf "%s/%s" ($.Scratch.Get "pathName") "footer/katex.html") . -}}
|
||||
{{ end }}
|
||||
<!-- Plausible Analytics Config -->
|
||||
{{- if not .Site.IsServer }}
|
||||
{{ if and (.Site.Params.plausible.scriptURL | default "https://plausible.io") (.Site.Params.plausible.dataDomain) -}}
|
||||
{{- partialCached (printf "%s/%s" ($.Scratch.Get "pathName") "head/plausible") . }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<!-- Google Analytics v4 Config -->
|
||||
{{- if not .Site.IsServer }}
|
||||
{{- if .Site.GoogleAnalytics }}
|
||||
{{- 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>
|
||||
@@ -1,4 +1,5 @@
|
||||
<toc>
|
||||
<!-- change -->
|
||||
<div class="fw-bold text-uppercase mb-2">{{ .Title }}</div>
|
||||
{{ if eq .Site.Params.docs.scrollSpy true -}}
|
||||
{{ .TableOfContents | replaceRE "<nav id=\"TableOfContents\">" "<nav id=\"toc\">" | safeHTML }}
|
||||
|
||||
@@ -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);
|
||||
@@ -9638,11 +9639,14 @@ img {
|
||||
order: 1; }
|
||||
|
||||
.docs-content .main-content a {
|
||||
font-weight: 600;
|
||||
color: var(--content-link-color); }
|
||||
.docs-content .main-content a:hover {
|
||||
text-decoration: underline 2px var(--primary-200);
|
||||
text-underline-offset: 2.5px !important;
|
||||
transition: 0s !important; }
|
||||
.docs-content .main-content a code {
|
||||
color: var(--content-link-color); }
|
||||
|
||||
.docs-content .main-content #edit-this-page a:hover,
|
||||
.docs-content .main-content #list-item a:hover {
|
||||
@@ -9663,22 +9667,24 @@ img {
|
||||
font-weight: 700;
|
||||
color: var(--body-color); }
|
||||
|
||||
.docs-content .main-content a {
|
||||
font-weight: 600; }
|
||||
|
||||
.docs-content .content-title {
|
||||
font-weight: 700; }
|
||||
.docs-content .content-title i {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
color: var(--content-icon-color);
|
||||
background-color: var(--content-icon-bg);
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
border-radius: 5px; }
|
||||
font-weight: 700;
|
||||
align-self: center; }
|
||||
|
||||
i.title-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
color: var(--content-icon-color);
|
||||
background-color: var(--content-icon-bg);
|
||||
display: inline-flex !important;
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
border-radius: 5px; }
|
||||
@media (max-width: 768px) {
|
||||
i.title-icon {
|
||||
align-self: auto; } }
|
||||
.docs-content p.lead {
|
||||
color: var(--text-muted);
|
||||
font-weight: 400; }
|
||||
@@ -9692,17 +9698,22 @@ img {
|
||||
.docs-content p.lead {
|
||||
font-size: 1rem; } }
|
||||
|
||||
.docs-content .main-content img {
|
||||
max-width: 100%; }
|
||||
.docs-content .main-content img,
|
||||
.docs-content .main-content svg {
|
||||
max-width: 100%;
|
||||
height: auto; }
|
||||
|
||||
.docs-content .main-content a svg {
|
||||
vertical-align: middle;
|
||||
padding-bottom: 0.25rem;
|
||||
margin-left: 3px; }
|
||||
|
||||
.docs-content .main-content ul {
|
||||
list-style: none;
|
||||
line-height: 26px;
|
||||
padding-left: 0; }
|
||||
.docs-content .main-content ul li {
|
||||
.docs-content .main-content ul > li {
|
||||
position: relative;
|
||||
padding-left: 32px; }
|
||||
.docs-content .main-content ul li::before {
|
||||
.docs-content .main-content ul > li::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
@@ -9713,13 +9724,12 @@ img {
|
||||
background: var(--gray-500); }
|
||||
|
||||
.docs-content .main-content ol {
|
||||
list-style: none;
|
||||
line-height: 26px; }
|
||||
.docs-content .main-content ol li {
|
||||
counter-reset: listitem; }
|
||||
.docs-content .main-content ol > li {
|
||||
counter-increment: listitem;
|
||||
position: relative;
|
||||
padding-left: 32px; }
|
||||
.docs-content .main-content ol li::before {
|
||||
.docs-content .main-content ol > li::before {
|
||||
content: counter(listitem);
|
||||
background: var(--ordered-list-bg);
|
||||
color: var(--ordered-list-color);
|
||||
@@ -9735,6 +9745,11 @@ img {
|
||||
left: 0;
|
||||
top: 3px; }
|
||||
|
||||
.docs-content .main-content ol,
|
||||
.docs-content .main-content ul {
|
||||
list-style: none;
|
||||
line-height: 26px; }
|
||||
|
||||
.docs-content .main-content blockquote {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.25rem;
|
||||
@@ -9751,8 +9766,7 @@ img {
|
||||
|
||||
.docs-content .main-content code {
|
||||
font-size: inherit;
|
||||
color: var(--text-default);
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
padding: 1px 2px;
|
||||
background: var(--inline-code-bg);
|
||||
border: var(--inline-code-border);
|
||||
@@ -9760,14 +9774,11 @@ img {
|
||||
|
||||
.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;
|
||||
@@ -9822,11 +9833,20 @@ img {
|
||||
overflow-y: overlay;
|
||||
position: relative;
|
||||
border-right: 1px solid var(--sidebar-border-color);
|
||||
scrollbar-width: none; }
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--sidebar-bg) var(--sidebar-bg); }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-content.desktop {
|
||||
overflow-y: hidden; }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-content::-webkit-scrollbar {
|
||||
display: none; }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-content:hover {
|
||||
scrollbar-color: var(--sidebar-scrollbar-thumb-color) var(--sidebar-bg); }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-content::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 8px; }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-content::-webkit-scrollbar-track {
|
||||
background: var(--sidebar-bg);
|
||||
display: none; }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-content:hover::-webkit-scrollbar-thumb {
|
||||
background: var(--sidebar-scrollbar-thumb-color); }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-brand {
|
||||
background: var(--sidebar-bg);
|
||||
padding: 10px 20px;
|
||||
@@ -9837,6 +9857,7 @@ img {
|
||||
.page-wrapper .sidebar-wrapper .sidebar-brand > a {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
flex-grow: 1;
|
||||
transition: all 0.3s ease; }
|
||||
.page-wrapper .sidebar-wrapper .sidebar-brand > a:focus {
|
||||
outline: none; }
|
||||
@@ -10099,12 +10120,14 @@ img {
|
||||
: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;
|
||||
@@ -10112,6 +10135,10 @@ img {
|
||||
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);
|
||||
@@ -10580,25 +10607,16 @@ button:not(:disabled) {
|
||||
background-size: 1.5rem;
|
||||
filter: invert(1) grayscale(100%) brightness(200%); }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.alert-dismissible .btn-close {
|
||||
background-size: 1.25rem; } }
|
||||
|
||||
[data-global-alert="closed"] #announcement {
|
||||
display: none; }
|
||||
|
||||
.alert code {
|
||||
background: #e7e8e9;
|
||||
color: #000;
|
||||
padding: 0.25rem 0.5rem; }
|
||||
|
||||
:root {
|
||||
--card-title-color: var(--text-default);
|
||||
--card-text-color: var(--text-muted); }
|
||||
|
||||
[data-dark-mode] {
|
||||
--card-title-color: var(--text-default);
|
||||
--card-text-color: #6c757d; }
|
||||
--card-text-color: var(--gray-500); }
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
@@ -10617,7 +10635,8 @@ button:not(:disabled) {
|
||||
color: var(--card-title-color); }
|
||||
|
||||
.card-text {
|
||||
color: var(--card-text-color); }
|
||||
color: var(--card-text-color);
|
||||
font-weight: 500; }
|
||||
|
||||
:root {
|
||||
--form-border-color: var(--gray-200);
|
||||
@@ -10655,12 +10674,12 @@ button:not(:disabled) {
|
||||
background-color: var(--body-bg);
|
||||
text-align: left; }
|
||||
.form-control:focus {
|
||||
border-color: #2f55d4;
|
||||
border-color: var(--primary);
|
||||
box-shadow: none; }
|
||||
.form-control[readonly] {
|
||||
background-color: #fff; }
|
||||
background-color: var(--white); }
|
||||
.form-control:disabled {
|
||||
background-color: #dee2e6; }
|
||||
background-color: var(--gray-300); }
|
||||
.form-control::placeholder {
|
||||
color: var(--form-control-placeholder-color); }
|
||||
|
||||
@@ -10673,7 +10692,7 @@ button:not(:disabled) {
|
||||
border: 1px solid var(--form-check-input-border-color);
|
||||
background-color: var(--body-bg); }
|
||||
.form-check-input:focus {
|
||||
border-color: #2f55d4;
|
||||
border-color: var(--primary);
|
||||
box-shadow: none; }
|
||||
.form-check-input.form-check-input:checked {
|
||||
background-color: var(--form-check-input-background-color);
|
||||
@@ -10699,7 +10718,6 @@ table td:last-child, table th:last-child {
|
||||
.table {
|
||||
--bs-table-color: var(--text-default);
|
||||
--bs-table-bg: transparent;
|
||||
--bs-table-border-color: var(--bs-border-color);
|
||||
--bs-table-accent-bg: none;
|
||||
--bs-table-striped-color: var(--text-default);
|
||||
--bs-table-striped-bg: var(--alert-primary-bg);
|
||||
@@ -10805,12 +10823,51 @@ 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);
|
||||
--feature-link-hover-bg-color: var(--primary); }
|
||||
|
||||
[data-dark-mode] {
|
||||
--feature-icon-color: var(--primary-300);
|
||||
--feature-icon-faint: var(--gray-900);
|
||||
--feature-link-hover-bg-color: var(--gray-900); }
|
||||
|
||||
.features .icon {
|
||||
@@ -10834,6 +10891,10 @@ table td:last-child, table th:last-child {
|
||||
.features.feature-full-bg .icon-color {
|
||||
color: var(--feature-icon-color) !important; }
|
||||
|
||||
.features.feature-full-bg .icon-faint {
|
||||
color: var(--feature-icon-faint) !important;
|
||||
transition: all 0.5s ease; }
|
||||
|
||||
.features.feature-full-bg:hover {
|
||||
background-color: var(--feature-link-hover-bg-color) !important; }
|
||||
|
||||
@@ -11128,30 +11189,6 @@ table td:last-child, table th:last-child {
|
||||
.rounded-lg {
|
||||
border-radius: 30px !important; }
|
||||
|
||||
.border-primary {
|
||||
border-color: #2f55d4 !important; }
|
||||
|
||||
.border-secondary {
|
||||
border-color: #6c757d !important; }
|
||||
|
||||
.border-success {
|
||||
border-color: #2eca8b !important; }
|
||||
|
||||
.border-info {
|
||||
border-color: #17a2b8 !important; }
|
||||
|
||||
.border-warning {
|
||||
border-color: #f17425 !important; }
|
||||
|
||||
.border-danger {
|
||||
border-color: #e43f52 !important; }
|
||||
|
||||
.border-light {
|
||||
border-color: #f8f9fa !important; }
|
||||
|
||||
.border-dark {
|
||||
border-color: #0e1420 !important; }
|
||||
|
||||
.bg-white-color {
|
||||
background: var(--bg-white-color); }
|
||||
|
||||
@@ -11220,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;
|
||||
@@ -11237,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-"] {
|
||||
@@ -11282,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,
|
||||
@@ -11350,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; }
|
||||
@@ -11390,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;
|
||||
@@ -11513,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; }
|
||||
@@ -11921,7 +11978,34 @@ div.code-toolbar > .toolbar > .toolbar-item > span:focus {
|
||||
width: 20.1rem;
|
||||
padding-left: 1rem; } }
|
||||
|
||||
.docs-content .main-content img, .docs-content .main-content svg:not(.gitinfo svg) {
|
||||
/* 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;
|
||||
display: block !important;
|
||||
@@ -11943,8 +12027,74 @@ div.code-toolbar {
|
||||
width: 12px;
|
||||
z-index: 1; }
|
||||
|
||||
p {
|
||||
li p {
|
||||
margin-top: 1rem !important;
|
||||
margin-bottom: 1rem; }
|
||||
|
||||
footer {
|
||||
height: 118px !important; }
|
||||
|
||||
/*
|
||||
footer a:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
*/
|
||||
.medium-zoom-overlay,
|
||||
.medium-zoom-image--opened {
|
||||
z-index: 1999; }
|
||||
|
||||
/* 徽章样式 */
|
||||
.github-badge {
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
text-shadow: none;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
line-height: 15px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px; }
|
||||
|
||||
.github-badge .badge-subject {
|
||||
display: inline-block;
|
||||
background-color: #4D4D4D;
|
||||
padding: 4px 4px 4px 6px;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px; }
|
||||
|
||||
.github-badge .badge-value {
|
||||
display: inline-block;
|
||||
padding: 4px 6px 4px 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px; }
|
||||
|
||||
.github-badge .bg-brightgreen {
|
||||
background-color: #4DC820 !important; }
|
||||
|
||||
.github-badge .bg-orange {
|
||||
background-color: #FFA500 !important; }
|
||||
|
||||
.github-badge .bg-yellow {
|
||||
background-color: #D8B024 !important; }
|
||||
|
||||
.github-badge .bg-blueviolet {
|
||||
background-color: #8833D7 !important; }
|
||||
|
||||
.github-badge .bg-pink {
|
||||
background-color: #F26BAE !important; }
|
||||
|
||||
.github-badge .bg-red {
|
||||
background-color: #e05d44 !important; }
|
||||
|
||||
.github-badge .bg-blue {
|
||||
background-color: #007EC6 !important; }
|
||||
|
||||
.github-badge .bg-lightgrey {
|
||||
background-color: #9F9F9F !important; }
|
||||
|
||||
.github-badge .bg-grey, .github-badge .bg-gray {
|
||||
background-color: #555 !important; }
|
||||
|
||||
.github-badge .bg-lightgrey, .github-badge .bg-lightgray {
|
||||
background-color: #9f9f9f !important; }
|
||||
|
||||
/*# sourceMappingURL=style.css.map */
|
||||
@@ -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;
|
||||
@@ -9465,139 +9465,134 @@ p {
|
||||
@media (max-width: 767px) {
|
||||
.four-oh-four {
|
||||
min-height: calc(100vh - 241px); } }
|
||||
#topnav .logo {
|
||||
float: left;
|
||||
color: #3c4858 !important; }
|
||||
#topnav .logo .l-dark,
|
||||
#topnav .logo .logo-dark-mode {
|
||||
display: none; }
|
||||
#topnav .logo .l-light,
|
||||
#topnav .logo .logo-light-mode {
|
||||
display: inline-block; }
|
||||
#topnav .logo:focus {
|
||||
outline: none; }
|
||||
|
||||
#topnav #navigation.toggle-menu {
|
||||
position: relative;
|
||||
display: block;
|
||||
#topnav {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border: none; }
|
||||
#topnav #navigation.toggle-menu .toggle-menu-item {
|
||||
display: block; }
|
||||
#topnav #navigation.toggle-menu .toggle-menu-item > li {
|
||||
float: none;
|
||||
margin: 0 16px !important;
|
||||
text-align: center; }
|
||||
#topnav #navigation.toggle-menu .toggle-menu-item > li > a {
|
||||
padding: 16px 0;
|
||||
min-height: auto;
|
||||
font-size: 18px; }
|
||||
|
||||
#topnav .navbar-toggle {
|
||||
background-color: transparent;
|
||||
z-index: 999;
|
||||
border: 0;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer; }
|
||||
#topnav .navbar-toggle .lines {
|
||||
width: 20px;
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 4px 0 0 0;
|
||||
height: 18px; }
|
||||
#topnav .navbar-toggle span {
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: #0066ff;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
transition: transform .5s ease; }
|
||||
#topnav .navbar-toggle span:last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
#topnav .navbar-toggle.open span {
|
||||
position: absolute; }
|
||||
#topnav .navbar-toggle.open span:first-child {
|
||||
top: 6px;
|
||||
transform: rotate(45deg); }
|
||||
#topnav .navbar-toggle.open span:nth-child(2) {
|
||||
visibility: hidden; }
|
||||
#topnav .navbar-toggle.open span:last-child {
|
||||
width: 100%;
|
||||
top: 6px;
|
||||
transform: rotate(-45deg); }
|
||||
#topnav .navbar-toggle.open span:hover {
|
||||
background-color: #0066ff; }
|
||||
|
||||
#topnav .navbar-toggle:hover, #topnav .navbar-toggle:focus,
|
||||
#topnav .navbar-toggle .navigation-menu > li > a:hover, #topnav .navbar-toggle:focus {
|
||||
background-color: transparent; }
|
||||
|
||||
#topnav .navigation-menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
#topnav .navigation-menu > li {
|
||||
-webkit-transition: all .5s ease;
|
||||
transition: all .5s ease; }
|
||||
#topnav .logo {
|
||||
float: left;
|
||||
display: block;
|
||||
color: #3c4858 !important; }
|
||||
#topnav .logo .l-dark,
|
||||
#topnav .logo .logo-dark-mode {
|
||||
display: none; }
|
||||
#topnav .logo .l-light,
|
||||
#topnav .logo .logo-light-mode {
|
||||
display: inline-block; }
|
||||
#topnav .logo:focus {
|
||||
outline: none; }
|
||||
#topnav #navigation.toggle-menu {
|
||||
position: relative;
|
||||
margin: 0 10px; }
|
||||
#topnav .navigation-menu > li:hover > a, #topnav .navigation-menu > li.active > a {
|
||||
display: block;
|
||||
top: 0;
|
||||
border: none; }
|
||||
#topnav #navigation.toggle-menu .toggle-menu-item {
|
||||
display: block; }
|
||||
#topnav #navigation.toggle-menu .toggle-menu-item > li {
|
||||
float: none;
|
||||
margin: 0 16px !important;
|
||||
text-align: center; }
|
||||
#topnav #navigation.toggle-menu .toggle-menu-item > li > a {
|
||||
padding: 16px 0;
|
||||
min-height: auto;
|
||||
font-size: 18px; }
|
||||
#topnav .navbar-toggle {
|
||||
border: 0;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer; }
|
||||
#topnav .navbar-toggle .lines {
|
||||
width: 20px;
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 4px 0 0 0;
|
||||
height: 18px; }
|
||||
#topnav .navbar-toggle span {
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: #0066ff;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
transition: transform .5s ease; }
|
||||
#topnav .navbar-toggle span:last-child {
|
||||
margin-bottom: 0; }
|
||||
#topnav .navbar-toggle.open span {
|
||||
position: absolute; }
|
||||
#topnav .navbar-toggle.open span:first-child {
|
||||
top: 6px;
|
||||
transform: rotate(45deg); }
|
||||
#topnav .navbar-toggle.open span:nth-child(2) {
|
||||
visibility: hidden; }
|
||||
#topnav .navbar-toggle.open span:last-child {
|
||||
width: 100%;
|
||||
top: 6px;
|
||||
transform: rotate(-45deg); }
|
||||
#topnav .navbar-toggle.open span:hover {
|
||||
background-color: #0066ff; }
|
||||
#topnav .navbar-toggle:hover, #topnav .navbar-toggle:focus,
|
||||
#topnav .navbar-toggle .navigation-menu > li > a:hover, #topnav .navbar-toggle:focus {
|
||||
background-color: transparent; }
|
||||
#topnav .navigation-menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
#topnav .navigation-menu > li {
|
||||
float: left;
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0 10px; }
|
||||
#topnav .navigation-menu > li:hover > a, #topnav .navigation-menu > li.active > a {
|
||||
color: #0066ff !important; }
|
||||
#topnav .navigation-menu > li > a {
|
||||
display: flex;
|
||||
color: #3c4858;
|
||||
font-size: 15px;
|
||||
background-color: transparent !important;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
line-height: 24px;
|
||||
font-family: var(--bs-font-sans-serif);
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
align-items: center; }
|
||||
#topnav .navigation-menu > li > a:hover, #topnav .navigation-menu > li > a:active {
|
||||
color: #0066ff; }
|
||||
#topnav.scroll {
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
box-shadow: 0 0 3px rgba(60, 72, 88, 0.15); }
|
||||
#topnav.scroll .navigation-menu > li > a {
|
||||
color: #3c4858; }
|
||||
#topnav.scroll .navigation-menu > li > .menu-arrow {
|
||||
border-color: #3c4858; }
|
||||
#topnav.scroll .navigation-menu > li:hover > a, #topnav.scroll .navigation-menu > li.active > a {
|
||||
color: #0066ff; }
|
||||
#topnav.scroll .navigation-menu > li:hover > .menu-arrow, #topnav.scroll .navigation-menu > li.active > .menu-arrow {
|
||||
border-color: #0066ff; }
|
||||
#topnav.nav-sticky {
|
||||
background: #fff;
|
||||
-webkit-box-shadow: 0 0 3px rgba(60, 72, 88, 0.15);
|
||||
box-shadow: 0 0 3px rgba(60, 72, 88, 0.15); }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li > a {
|
||||
color: #3c4858; }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li.active > a {
|
||||
color: #0066ff !important; }
|
||||
#topnav .navigation-menu > li > a {
|
||||
display: flex;
|
||||
color: #3c4858;
|
||||
font-size: 15px;
|
||||
background-color: transparent !important;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
line-height: 24px;
|
||||
font-family: var(--bs-font-sans-serif);
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
align-items: center; }
|
||||
#topnav .navigation-menu > li > a:hover, #topnav .navigation-menu > li > a:active {
|
||||
color: #0066ff; }
|
||||
|
||||
#topnav.scroll {
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
box-shadow: 0 0 3px rgba(60, 72, 88, 0.15); }
|
||||
#topnav.scroll .navigation-menu > li > a {
|
||||
color: #3c4858; }
|
||||
#topnav.scroll .navigation-menu > li > .menu-arrow {
|
||||
border-color: #3c4858; }
|
||||
#topnav.scroll .navigation-menu > li:hover > a, #topnav.scroll .navigation-menu > li.active > a {
|
||||
color: #0066ff; }
|
||||
#topnav.scroll .navigation-menu > li:hover > .menu-arrow, #topnav.scroll .navigation-menu > li.active > .menu-arrow {
|
||||
border-color: #0066ff; }
|
||||
|
||||
#topnav.defaultscroll.dark-menubar .logo {
|
||||
line-height: 70px; }
|
||||
|
||||
#topnav.defaultscroll.scroll .logo {
|
||||
line-height: 62px; }
|
||||
|
||||
#topnav.defaultscroll.scroll.dark-menubar .logo {
|
||||
line-height: 62px; }
|
||||
|
||||
#topnav.nav-sticky {
|
||||
background: #fff;
|
||||
box-shadow: 0 0 3px rgba(60, 72, 88, 0.15); }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li > a {
|
||||
color: #3c4858; }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li.active > a {
|
||||
color: #0066ff !important; }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li:hover > .menu-arrow, #topnav.nav-sticky .navigation-menu.nav-light > li.active > .menu-arrow {
|
||||
border-color: #0066ff !important; }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li:hover > a, #topnav.nav-sticky .navigation-menu.nav-light > li.active > a {
|
||||
color: #0066ff !important; }
|
||||
#topnav.nav-sticky.tagline-height {
|
||||
top: 0 !important; }
|
||||
#topnav.nav-sticky .logo .l-dark {
|
||||
display: inline-block; }
|
||||
#topnav.nav-sticky .logo .l-light {
|
||||
display: none; }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li:hover > .menu-arrow, #topnav.nav-sticky .navigation-menu.nav-light > li.active > .menu-arrow {
|
||||
border-color: #0066ff !important; }
|
||||
#topnav.nav-sticky .navigation-menu.nav-light > li:hover > a, #topnav.nav-sticky .navigation-menu.nav-light > li.active > a {
|
||||
color: #0066ff !important; }
|
||||
#topnav.nav-sticky.tagline-height {
|
||||
top: 0 !important; }
|
||||
#topnav.nav-sticky .logo .l-dark {
|
||||
display: inline-block; }
|
||||
#topnav.nav-sticky .logo .l-light {
|
||||
display: none; }
|
||||
|
||||
.logo {
|
||||
font-weight: 700;
|
||||
@@ -11011,6 +11006,10 @@ span.menu-icon {
|
||||
max-width: 100%;
|
||||
height: auto; } }
|
||||
|
||||
.hero {
|
||||
background-size: cover;
|
||||
padding-top: 70px; }
|
||||
|
||||
.icv {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -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
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fastgpt",
|
||||
"version": "3.7",
|
||||
"version": "4.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
|
||||
1
packages/common/bill/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const PRICE_SCALE = 100000;
|
||||
9
packages/common/bill/index.ts
Normal 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));
|
||||
};
|
||||
4
packages/common/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@fastgpt/common",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
21
packages/common/tsconfig.json
Normal 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"]
|
||||
}
|
||||
@@ -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';
|
||||
1
packages/core/aiApi/constant.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||
1
packages/core/aiApi/type.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export type { CreateChatCompletionRequest, ChatCompletionRequestMessage } from 'openai';
|
||||
13
packages/core/init.ts
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
11
packages/core/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
21
packages/core/tsconfig.json
Normal 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"]
|
||||
}
|
||||