This commit is contained in:
Archer 2023-11-15 11:36:25 +08:00 committed by GitHub
parent 592e1a93a2
commit bfd8be5df0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
181 changed files with 2499 additions and 1552 deletions

View File

@ -0,0 +1,52 @@
name: Build FastGPT images in Personal warehouse
on:
workflow_dispatch:
push:
paths:
- 'projects/app/**'
- 'packages/**'
branches:
- 'main'
jobs:
build-fastgpt-images:
runs-on: ubuntu-20.04
if: github.repository != 'labring/FastGPT'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: network=host
- name: Cache Docker layers
uses: actions/cache@v3
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:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
- name: Build and publish image for main branch or tag push event
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
docker buildx build \
--build-arg name=app \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt image" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.

View File

@ -5,8 +5,6 @@ on:
paths: paths:
- 'projects/app/**' - 'projects/app/**'
- 'packages/**' - 'packages/**'
branches:
- 'main'
tags: tags:
- 'v*.*.*' - 'v*.*.*'
jobs: jobs:
@ -53,9 +51,8 @@ jobs:
docker buildx build \ docker buildx build \
--build-arg name=app \ --build-arg name=app \
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \ --label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt image" \ --label "org.opencontainers.image.description=fastgpt image" \
--label "org.opencontainers.image.licenses=Apache" \
--push \ --push \
--cache-from=type=local,src=/tmp/.buildx-cache \ --cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \ --cache-to=type=local,dest=/tmp/.buildx-cache \

View File

@ -24,7 +24,7 @@ jobs:
with: with:
driver-opts: network=host driver-opts: network=host
- name: Cache Docker layers - name: Cache Docker layers
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: /tmp/.buildx-cache path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }} key: ${{ runner.os }}-buildx-${{ github.sha }}
@ -48,6 +48,7 @@ jobs:
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \ --label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-pr image" \ --label "org.opencontainers.image.description=fastgpt-pr image" \
--label "org.opencontainers.image.licenses=Apache" \ --label "org.opencontainers.image.licenses=Apache" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \ --cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \ --cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \ -t ${DOCKER_REPO_TAGGED} \

View File

@ -7,20 +7,20 @@ toc: true
weight: 836 weight: 836
--- ---
未正式发布 # V4.6 版本加入了简单的团队功能,可以邀请其他用户进来管理资源。该版本升级后无法执行旧的升级脚本,且无法回退
V4.6 版本加入了简单的团队功能,可以邀请其他用户进来管理资源。该版本升级后无法执行旧的升级脚本,且无法回退。 # 1. 更新镜像并变更配置文件
## 1. 更新镜像并变更配置文件 更新镜像至 latest 或者 v4.6 版本。商业版镜像更新至 V0.2.1
更新镜像至 latest 或者 v4.6 版本。商业版镜像更新至 V0.2.
最新配置可参考: [V46版本最新 config.json](/docs/development/configuration),商业镜像配置文件也更新,参考最新的飞书文档。 最新配置可参考: [V46版本最新 config.json](/docs/development/configuration),商业镜像配置文件也更新,参考最新的飞书文档。
## 2. 执行初始化 API # 2. 执行初始化 API
发起 1 个 HTTP 请求({{rootkey}} 替换成环境变量里的`rootkey`{{host}}替换成自己域名) 发起 2 个 HTTP 请求({{rootkey}} 替换成环境变量里的`rootkey`{{host}}替换成自己域名)
**该初始化接口可能速度很慢返回超时不用管注意看日志即可需要注意的是需确保initv46成功后在执行initv46-2**
1. https://xxxxx/api/admin/initv46 1. https://xxxxx/api/admin/initv46
@ -30,16 +30,25 @@ curl --location --request POST 'https://{{host}}/api/admin/initv46' \
--header 'Content-Type: application/json' --header 'Content-Type: application/json'
``` ```
2. https://xxxxx/api/admin/initv46-2
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv46-2' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
初始化内容: 初始化内容:
1. 创建默认团队 1. 创建默认团队
2. 初始化 Mongo 所有资源的团队字段 2. 初始化 Mongo 所有资源的团队字段
3. 初始化 Pg 的字段 3. 初始化 Pg 的字段
4. 初始化 Mongo Data
**该初始化接口可能速度很慢,返回超时不用管,注意看日志即可**
## 功能介绍 # V4.6功能介绍
### Fast GPT V4.6
1. 新增 - 团队空间 1. 新增 - 团队空间
2. 新增 - 多路向量(多个向量映射一组数据)
3. 新增 - tts语音
4. 线上环境新增 - ReRank向量召回提高召回精度
5. 优化 - 知识库导出,可直接触发流下载,无需等待转圈圈

View File

@ -0,0 +1,131 @@
import { getErrText } from '../error/utils';
import { countPromptTokens } from './tiktoken';
/**
* text split into chunks
* maxLen - one chunk len. max: 3500
* overlapLen - The size of the before and after Text
* maxLen > overlapLen
* markdown
*/
export const splitText2Chunks = (props: { text: string; maxLen: number; overlapLen?: number }) => {
const { text = '', maxLen, overlapLen = Math.floor(maxLen * 0.2) } = props;
const tempMarker = 'SPLIT_HERE_SPLIT_HERE';
const stepReg: Record<number, RegExp> = {
0: /^(#\s[^\n]+)\n/gm,
1: /^(##\s[^\n]+)\n/gm,
2: /^(###\s[^\n]+)\n/gm,
3: /^(####\s[^\n]+)\n/gm,
4: /(\n\n)/g,
5: /([\n])/g,
6: /[。]|(?!<[^a-zA-Z])\.\s/g,
7: /([]|!\s|\?\s)/g,
8: /([]|;\s)/g,
9: /([]|,\s)/g
};
const splitTextRecursively = ({
text = '',
step,
lastChunk,
overlayChunk
}: {
text: string;
step: number;
lastChunk: string;
overlayChunk: string;
}) => {
if (text.length <= maxLen) {
return [text];
}
const reg = stepReg[step];
const isMarkdownSplit = step < 4;
if (!reg) {
// use slice-maxLen to split text
const chunks: string[] = [];
let chunk = '';
for (let i = 0; i < text.length; i += maxLen - overlapLen) {
chunk = text.slice(i, i + maxLen);
chunks.push(chunk);
}
return chunks;
}
// split text by special char
const splitTexts = text
.replace(reg, isMarkdownSplit ? `${tempMarker}$1` : `$1${tempMarker}`)
.split(`${tempMarker}`)
.filter((part) => part);
let chunks: string[] = [];
for (let i = 0; i < splitTexts.length; i++) {
let text = splitTexts[i];
let chunkToken = countPromptTokens(lastChunk, '');
const textToken = countPromptTokens(text, '');
// next chunk is too large / new chunk is too large(The current chunk must be smaller than maxLen)
if (textToken >= maxLen || chunkToken + textToken > maxLen * 1.4) {
// last chunk is too large, push it to chunks, not add to next chunk
if (chunkToken > maxLen * 0.7) {
chunks.push(lastChunk);
lastChunk = '';
overlayChunk = '';
}
// chunk is small, insert to next chunks
const innerChunks = splitTextRecursively({
text,
step: step + 1,
lastChunk,
overlayChunk
});
if (innerChunks.length === 0) continue;
chunks = chunks.concat(innerChunks);
lastChunk = '';
overlayChunk = '';
continue;
}
// size less than maxLen, push text to last chunk
lastChunk += text;
chunkToken += textToken; // Definitely less than 1.4 * maxLen
// size over lapLen, push it to next chunk
if (
overlapLen !== 0 &&
!isMarkdownSplit &&
chunkToken >= maxLen - overlapLen &&
textToken < overlapLen
) {
overlayChunk += text;
}
if (chunkToken >= maxLen) {
chunks.push(lastChunk);
lastChunk = overlayChunk;
overlayChunk = '';
}
}
/* If the last chunk is independent, it needs to be push chunks. */
if (lastChunk && chunks[chunks.length - 1] && !chunks[chunks.length - 1].endsWith(lastChunk)) {
chunks.push(lastChunk);
}
return chunks;
};
try {
const chunks = splitTextRecursively({ text, step: 0, lastChunk: '', overlayChunk: '' });
const tokens = chunks.reduce((sum, chunk) => sum + countPromptTokens(chunk, 'system'), 0);
return {
chunks,
tokens
};
} catch (err) {
throw new Error(getErrText(err));
}
};

View File

@ -1,8 +1,8 @@
/* Only the token of gpt-3.5-turbo is used */ /* Only the token of gpt-3.5-turbo is used */
import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import type { ChatItemType } from '../../../core/chat/type';
import { Tiktoken } from 'js-tiktoken/lite'; import { Tiktoken } from 'js-tiktoken/lite';
import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { adaptChat2GptMessages } from '../../../core/chat/adapt';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant'; import { ChatCompletionRequestMessageRoleEnum } from '../../../core/ai/constant';
import encodingJson from './cl100k_base.json'; import encodingJson from './cl100k_base.json';
/* init tikToken obj */ /* init tikToken obj */
@ -55,17 +55,6 @@ export function countMessagesTokens({ messages }: { messages: ChatItemType[] })
return totalTokens; return totalTokens;
} }
export function sliceTextByTokens({ text, length }: { text: string; length: number }) {
const enc = getTikTokenEnc();
try {
const encodeText = enc.encode(text);
return enc.decode(encodeText.slice(0, length));
} catch (error) {
return text.slice(0, length);
}
}
/* slice messages from top to bottom by maxTokens */ /* slice messages from top to bottom by maxTokens */
export function sliceMessagesTB({ export function sliceMessagesTB({
messages, messages,

View File

@ -0,0 +1,5 @@
import type { Tiktoken } from 'js-tiktoken';
declare global {
var TikToken: Tiktoken;
}

View File

@ -1,13 +1,15 @@
import crypto from 'crypto'; import crypto from 'crypto';
/* check string is a web link */
export function strIsLink(str?: string) { export function strIsLink(str?: string) {
if (!str) return false; if (!str) return false;
if (/^((http|https)?:\/\/|www\.|\/)[^\s/$.?#].[^\s]*$/i.test(str)) return true; if (/^((http|https)?:\/\/|www\.|\/)[^\s/$.?#].[^\s]*$/i.test(str)) return true;
return false; return false;
} }
export const hashStr = (psw: string) => { /* hash string */
return crypto.createHash('sha256').update(psw).digest('hex'); export const hashStr = (str: string) => {
return crypto.createHash('sha256').update(str).digest('hex');
}; };
/* simple text, remove chinese space and extra \n */ /* simple text, remove chinese space and extra \n */
@ -20,3 +22,16 @@ export const simpleText = (text: string) => {
return text; return text;
}; };
/*
replace {{variable}} to value
*/
export function replaceVariable(text: string, obj: Record<string, string | number>) {
for (const key in obj) {
const val = obj[key];
if (!['string', 'number'].includes(typeof val)) continue;
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), String(val));
}
return text || '';
}

5
packages/global/core/ai/api.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
export type PostReRankProps = {
query: string;
inputs: { id: string; text: string }[];
};
export type PostReRankResponse = { id: string; score: number }[];

View File

@ -1,7 +1,7 @@
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '../../core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '../../core/chat/constants';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant'; import { ChatCompletionRequestMessageRoleEnum } from '../../core/ai/constant';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; import type { ChatMessageItemType } from '../../core/ai/type.d';
const chat2Message = { const chat2Message = {
[ChatRoleEnum.AI]: ChatCompletionRequestMessageRoleEnum.Assistant, [ChatRoleEnum.AI]: ChatCompletionRequestMessageRoleEnum.Assistant,

20
packages/global/core/dataset/api.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
import { DatasetDataIndexItemType } from './type';
/* ================= dataset ===================== */
/* ================= collection ===================== */
/* ================= data ===================== */
export type PgSearchRawType = {
id: string;
team_id: string;
tmb_id: string;
collection_id: string;
data_id: string;
score: number;
};
export type PushDatasetDataChunkProps = {
q: string; // embedding content
a?: string; // bonus content
indexes?: Omit<DatasetDataIndexItemType, 'dataId'>[];
};

View File

@ -36,29 +36,54 @@ export const DatasetCollectionTypeMap = {
} }
}; };
export enum TrainingModeEnum { export enum DatasetDataIndexTypeEnum {
'qa' = 'qa', chunk = 'chunk',
'index' = 'index' qa = 'qa',
summary = 'summary',
hypothetical = 'hypothetical',
custom = 'custom'
} }
export const TrainingTypeMap = { export const DatasetDataIndexTypeMap = {
[TrainingModeEnum.qa]: 'qa', [DatasetDataIndexTypeEnum.chunk]: {
[TrainingModeEnum.index]: 'index' name: 'dataset.data.indexes.chunk'
};
export enum DatasetSpecialIdEnum {
manual = 'manual',
mark = 'mark'
}
export const datasetSpecialIdMap = {
[DatasetSpecialIdEnum.manual]: {
name: 'kb.Manual Data',
sourceName: 'kb.Manual Input'
}, },
[DatasetSpecialIdEnum.mark]: { [DatasetDataIndexTypeEnum.summary]: {
name: 'kb.Mark Data', name: 'dataset.data.indexes.summary'
sourceName: 'kb.Manual Mark' },
[DatasetDataIndexTypeEnum.hypothetical]: {
name: 'dataset.data.indexes.hypothetical'
},
[DatasetDataIndexTypeEnum.qa]: {
name: 'dataset.data.indexes.qa'
},
[DatasetDataIndexTypeEnum.custom]: {
name: 'dataset.data.indexes.custom'
} }
}; };
export const datasetSpecialIds: string[] = [DatasetSpecialIdEnum.manual, DatasetSpecialIdEnum.mark];
export enum TrainingModeEnum {
'chunk' = 'chunk',
'qa' = 'qa'
// 'hypothetical' = 'hypothetical',
// 'summary' = 'summary',
// 'multipleIndex' = 'multipleIndex'
}
export const TrainingTypeMap = {
[TrainingModeEnum.chunk]: {
name: 'chunk'
},
[TrainingModeEnum.qa]: {
name: 'qa'
}
// [TrainingModeEnum.hypothetical]: {
// name: 'hypothetical'
// },
// [TrainingModeEnum.summary]: {
// name: 'summary'
// },
// [TrainingModeEnum.multipleIndex]: {
// name: 'multipleIndex'
// }
};
export const FolderAvatarSrc = '/imgs/files/folder.svg'; export const FolderAvatarSrc = '/imgs/files/folder.svg';

View File

@ -0,0 +1,27 @@
import type { DatasetDataIndexItemType, DatasetDataSchemaType } from './type';
export type CreateDatasetDataProps = {
teamId: string;
tmbId: string;
datasetId: string;
collectionId: string;
q: string;
a?: string;
indexes?: Omit<DatasetDataIndexItemType, 'dataId'>[];
};
export type UpdateDatasetDataProps = {
dataId: string;
q?: string;
a?: string;
indexes?: (Omit<DatasetDataIndexItemType, 'dataId'> & {
dataId?: string; // pg data id
})[];
};
export type PatchIndexesProps = {
type: 'create' | 'update' | 'delete';
index: Omit<DatasetDataIndexItemType, 'dataId'> & {
dataId?: string;
};
};

View File

@ -1,6 +1,14 @@
import type { VectorModelItemType } from '../../core/ai/model.d';
import { PermissionTypeEnum } from '../../support/permission/constant'; import { PermissionTypeEnum } from '../../support/permission/constant';
import { DatasetCollectionTypeEnum, DatasetTypeEnum, TrainingModeEnum } from './constant'; import { PushDatasetDataChunkProps } from './api';
import {
DatasetCollectionTypeEnum,
DatasetDataIndexTypeEnum,
DatasetTypeEnum,
TrainingModeEnum
} from './constant';
/* schema */
export type DatasetSchemaType = { export type DatasetSchemaType = {
_id: string; _id: string;
parentId: string; parentId: string;
@ -33,13 +41,33 @@ export type DatasetCollectionSchemaType = {
}; };
}; };
export type DatasetDataIndexItemType = {
defaultIndex: boolean;
dataId: string; // pg data id
type: `${DatasetDataIndexTypeEnum}`;
text: string;
};
export type DatasetDataSchemaType = {
_id: string;
userId: string;
teamId: string;
tmbId: string;
datasetId: string;
collectionId: string;
datasetId: string;
collectionId: string;
q: string; // large chunks or question
a: string; // answer or custom content
indexes: DatasetDataIndexItemType[];
};
export type DatasetTrainingSchemaType = { export type DatasetTrainingSchemaType = {
_id: string; _id: string;
userId: string; userId: string;
teamId: string; teamId: string;
tmbId: string; tmbId: string;
datasetId: string; datasetId: string;
datasetCollectionId: string; collectionId: string;
billId: string; billId: string;
expireAt: Date; expireAt: Date;
lockTime: Date; lockTime: Date;
@ -48,6 +76,7 @@ export type DatasetTrainingSchemaType = {
prompt: string; prompt: string;
q: string; q: string;
a: string; a: string;
indexes: Omit<DatasetDataIndexItemType, 'dataId'>[];
}; };
export type CollectionWithDatasetType = Omit<DatasetCollectionSchemaType, 'datasetId'> & { export type CollectionWithDatasetType = Omit<DatasetCollectionSchemaType, 'datasetId'> & {
@ -55,41 +84,31 @@ export type CollectionWithDatasetType = Omit<DatasetCollectionSchemaType, 'datas
}; };
/* ================= dataset ===================== */ /* ================= dataset ===================== */
export type DatasetItemType = Omit<DatasetSchemaType, 'vectorModel'> & {
/* ================= collection ===================== */ vectorModel: VectorModelItemType;
export type DatasetCollectionItemType = DatasetCollectionSchemaType & { isOwner: boolean;
canWrite: boolean; canWrite: boolean;
}; };
/* ================= collection ===================== */
export type DatasetCollectionItemType = CollectionWithDatasetType & {
canWrite: boolean;
sourceName: string;
sourceId?: string;
};
/* ================= data ===================== */ /* ================= data ===================== */
export type PgRawDataItemType = { export type DatasetDataItemType = {
id: string;
q: string;
a: string;
team_id: string;
tmb_id: string;
dataset_id: string;
collection_id: string;
};
export type PgDataItemType = {
id: string;
q: string;
a: string;
teamId: string;
tmbId: string;
datasetId: string;
collectionId: string;
};
export type DatasetChunkItemType = {
q: string;
a: string;
};
export type DatasetDataItemType = DatasetChunkItemType & {
id: string; id: string;
datasetId: string; datasetId: string;
collectionId: string; collectionId: string;
sourceName: string; sourceName: string;
sourceId?: string; sourceId?: string;
q: string;
a: string;
indexes: DatasetDataIndexItemType[];
isOwner: boolean;
canWrite: boolean;
}; };
/* --------------- file ---------------------- */ /* --------------- file ---------------------- */
@ -109,9 +128,6 @@ export type DatasetFileSchema = {
}; };
/* ============= search =============== */ /* ============= search =============== */
export type SearchDataResultItemType = PgRawDataItemType & {
score: number;
};
export type SearchDataResponseItemType = DatasetDataItemType & { export type SearchDataResponseItemType = DatasetDataItemType & {
score: number; score: number;
}; };

View File

@ -1,4 +1,4 @@
import { DatasetCollectionTypeEnum } from './constant'; import { DatasetCollectionTypeEnum, DatasetDataIndexTypeEnum } from './constant';
import { getFileIcon } from '../../common/file/icon'; import { getFileIcon } from '../../common/file/icon';
import { strIsLink } from '../../common/string/tools'; import { strIsLink } from '../../common/string/tools';
@ -44,3 +44,14 @@ export function getSourceNameIcon({
} }
return '/imgs/files/collection.svg'; return '/imgs/files/collection.svg';
} }
export function getDefaultIndex(props?: { q?: string; a?: string; dataId?: string }) {
const { q = '', a, dataId } = props || {};
const qaStr = `${q}\n${a}`.trim();
return {
defaultIndex: true,
type: a ? DatasetDataIndexTypeEnum.qa : DatasetDataIndexTypeEnum.chunk,
text: a ? qaStr : q,
dataId
};
}

View File

@ -0,0 +1,3 @@
import { VectorModelItemType } from '../ai/model.d';
export type SelectedDatasetType = { datasetId: string; vectorModel: VectorModelItemType }[];

View File

@ -6,7 +6,8 @@
"timezones-list": "^3.0.2", "timezones-list": "^3.0.2",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"encoding": "^0.1.13", "encoding": "^0.1.13",
"openai": "^4.16.1" "openai": "^4.16.1",
"js-tiktoken": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.8.5" "@types/node": "^20.8.5"

View File

@ -24,7 +24,8 @@ export const TeamMemberRoleMap = {
export enum TeamMemberStatusEnum { export enum TeamMemberStatusEnum {
waiting = 'waiting', waiting = 'waiting',
active = 'active', active = 'active',
reject = 'reject' reject = 'reject',
leave = 'leave'
} }
export const TeamMemberStatusMap = { export const TeamMemberStatusMap = {
[TeamMemberStatusEnum.waiting]: { [TeamMemberStatusEnum.waiting]: {
@ -38,5 +39,10 @@ export const TeamMemberStatusMap = {
[TeamMemberStatusEnum.reject]: { [TeamMemberStatusEnum.reject]: {
label: 'user.team.member.reject', label: 'user.team.member.reject',
color: 'red.600' color: 'red.600'
},
[TeamMemberStatusEnum.leave]: {
label: 'user.team.member.leave',
color: 'red.600'
} }
}; };
export const leaveStatus = { $ne: TeamMemberStatusEnum.leave };

View File

@ -37,4 +37,7 @@ export type UpdateInviteProps = {
tmbId: string; tmbId: string;
status: TeamMemberSchema['status']; status: TeamMemberSchema['status'];
}; };
export type InviteMemberResponse = Record<'invite' | 'inValid' | 'inTeam', string[]>; export type InviteMemberResponse = Record<
'invite' | 'inValid' | 'inTeam',
{ username: string; userId: string }[]
>;

View File

@ -16,6 +16,7 @@ export type TeamMemberSchema = {
teamId: string; teamId: string;
userId: string; userId: string;
createTime: Date; createTime: Date;
name: string;
role: `${TeamMemberRoleEnum}`; role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`; status: `${TeamMemberStatusEnum}`;
defaultTeam: boolean; defaultTeam: boolean;
@ -25,6 +26,7 @@ export type TeamItemType = {
userId: string; userId: string;
teamId: string; teamId: string;
teamName: string; teamName: string;
memberName: string;
avatar: string; avatar: string;
balance: number; balance: number;
tmbId: string; tmbId: string;
@ -39,7 +41,7 @@ export type TeamMemberItemType = {
userId: string; userId: string;
tmbId: string; tmbId: string;
teamId: string; teamId: string;
memberUsername: string; memberName: string;
avatar: string; avatar: string;
role: `${TeamMemberRoleEnum}`; role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`; status: `${TeamMemberStatusEnum}`;

View File

@ -15,7 +15,7 @@ export type BillSchema = CreateBillProps & {
export type BillItemType = { export type BillItemType = {
id: string; id: string;
username: string; memberName: string;
time: Date; time: Date;
appName: string; appName: string;
source: BillSchema['source']; source: BillSchema['source'];

View File

@ -15,9 +15,6 @@ interface ResponseDataType {
* *
*/ */
function requestStart(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig { function requestStart(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig {
if (config.headers) {
config.headers.rootkey = process.env.ROOT_KEY;
}
return config; return config;
} }
@ -62,7 +59,8 @@ const instance = axios.create({
timeout: 60000, // 超时时间 timeout: 60000, // 超时时间
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json',
'Cache-Control': 'no-cache' 'Cache-Control': 'no-cache',
rootkey: process.env.ROOT_KEY
} }
}); });

View File

@ -171,8 +171,7 @@ export async function initPg() {
tmb_id VARCHAR(50) NOT NULL, tmb_id VARCHAR(50) NOT NULL,
dataset_id VARCHAR(50) NOT NULL, dataset_id VARCHAR(50) NOT NULL,
collection_id VARCHAR(50) NOT NULL, collection_id VARCHAR(50) NOT NULL,
q TEXT NOT NULL, data_id VARCHAR(50) NOT NULL
a TEXT
); );
CREATE INDEX IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 24, ef_construction = 64); CREATE INDEX IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 24, ef_construction = 64);
`); `);

View File

@ -1,22 +1,7 @@
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import type { NextApiResponse } from 'next'; import { countMessagesTokens, countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { countMessagesTokens, countPromptTokens } from '@/global/common/tiktoken'; import { adaptRole_Chat2Message } from '@fastgpt/global/core/chat/adapt';
import { adaptRole_Chat2Message } from '@/utils/common/adapt/message';
export type ChatCompletionResponseType = {
streamResponse: any;
responseMessages: ChatItemType[];
responseText: string;
totalTokens: number;
};
export type StreamResponseType = {
chatResponse: any;
messages: ChatItemType[];
res: NextApiResponse;
model: string;
[key: string]: any;
};
/* slice chat context by tokens */ /* slice chat context by tokens */
export function ChatContextFilter({ export function ChatContextFilter({

View File

@ -56,8 +56,7 @@ const DatasetCollectionSchema = new Schema({
ref: 'dataset.files' ref: 'dataset.files'
}, },
rawLink: { rawLink: {
type: String, type: String
default: ''
}, },
// 451 初始化 // 451 初始化
pgCollectionId: { pgCollectionId: {

View File

@ -1,5 +1,25 @@
import { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type'; import { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type';
import { MongoDatasetCollection } from './collection/schema'; import { MongoDatasetCollection } from './collection/schema';
import { MongoDataset } from './schema';
/* ============= dataset ========== */
/* find all datasetId by top datasetId */
export async function findDatasetIdTreeByTopDatasetId(
id: string,
result: string[] = []
): Promise<string[]> {
let allChildrenIds = [...result];
// find children
const children = await MongoDataset.find({ parentId: id });
for (const child of children) {
const grandChildrenIds = await findDatasetIdTreeByTopDatasetId(child._id, result);
allChildrenIds = allChildrenIds.concat(grandChildrenIds);
}
return [String(id), ...allChildrenIds];
}
export async function getCollectionWithDataset(collectionId: string) { export async function getCollectionWithDataset(collectionId: string) {
const data = ( const data = (

View File

@ -0,0 +1,78 @@
import { connectionMongo, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d';
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { DatasetCollectionName } from '../schema';
import { DatasetColCollectionName } from '../collection/schema';
import { DatasetDataIndexTypeMap } from '@fastgpt/global/core/dataset/constant';
export const DatasetDataCollectionName = 'dataset.datas';
const DatasetDataSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
tmbId: {
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName,
required: true
},
datasetId: {
type: Schema.Types.ObjectId,
ref: DatasetCollectionName,
required: true
},
collectionId: {
type: Schema.Types.ObjectId,
ref: DatasetColCollectionName,
required: true
},
q: {
type: String,
required: true
},
a: {
type: String,
default: ''
},
indexes: {
type: [
{
defaultIndex: {
type: Boolean,
default: false
},
type: {
type: String,
enum: Object.keys(DatasetDataIndexTypeMap),
required: true
},
dataId: {
type: String,
required: true
},
text: {
type: String,
required: true
}
}
],
default: []
}
});
try {
DatasetDataSchema.index({ userId: 1 });
DatasetDataSchema.index({ datasetId: 1 });
DatasetDataSchema.index({ collectionId: 1 });
} catch (error) {
console.log(error);
}
export const MongoDatasetData: Model<DatasetDataSchemaType> =
models[DatasetDataCollectionName] || model(DatasetDataCollectionName, DatasetDataSchema);

View File

@ -2,7 +2,7 @@
import { connectionMongo, type Model } from '../../../common/mongo'; import { connectionMongo, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo; const { Schema, model, models } = connectionMongo;
import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type'; import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type';
import { TrainingTypeMap } from '@fastgpt/global/core/dataset/constant'; import { DatasetDataIndexTypeMap, TrainingTypeMap } from '@fastgpt/global/core/dataset/constant';
import { DatasetColCollectionName } from '../collection/schema'; import { DatasetColCollectionName } from '../collection/schema';
import { DatasetCollectionName } from '../schema'; import { DatasetCollectionName } from '../schema';
import { import {
@ -33,12 +33,13 @@ const TrainingDataSchema = new Schema({
ref: DatasetCollectionName, ref: DatasetCollectionName,
required: true required: true
}, },
datasetCollectionId: { collectionId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: DatasetColCollectionName, ref: DatasetColCollectionName,
required: true required: true
}, },
billId: { billId: {
// concat bill
type: String, type: String,
default: '' default: ''
}, },
@ -48,6 +49,7 @@ const TrainingDataSchema = new Schema({
required: true required: true
}, },
expireAt: { expireAt: {
// It will be deleted after 7 days
type: Date, type: Date,
default: () => new Date() default: () => new Date()
}, },
@ -56,6 +58,7 @@ const TrainingDataSchema = new Schema({
default: () => new Date('2000/1/1') default: () => new Date('2000/1/1')
}, },
model: { model: {
// ai model
type: String, type: String,
required: true required: true
}, },
@ -71,13 +74,29 @@ const TrainingDataSchema = new Schema({
a: { a: {
type: String, type: String,
default: '' default: ''
},
indexes: {
type: [
{
type: {
type: String,
enum: Object.keys(DatasetDataIndexTypeMap),
required: true
},
text: {
type: String,
required: true
}
}
],
default: []
} }
}); });
try { try {
TrainingDataSchema.index({ lockTime: 1 }); TrainingDataSchema.index({ lockTime: 1 });
TrainingDataSchema.index({ userId: 1 }); TrainingDataSchema.index({ userId: 1 });
TrainingDataSchema.index({ datasetCollectionId: 1 }); TrainingDataSchema.index({ collectionId: 1 });
TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 }); TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@ -23,6 +23,7 @@ const PromotionRecordSchema = new Schema({
enum: ['pay', 'register'] enum: ['pay', 'register']
}, },
amount: { amount: {
// 1 * PRICE_SCALE
type: Number, type: Number,
required: true required: true
} }

View File

@ -30,7 +30,7 @@ export const pushResult2Remote = async ({
shareId?: string; shareId?: string;
responseData?: any[]; responseData?: any[];
}) => { }) => {
if (!shareId || !authToken) return; if (!shareId || !authToken || !global.systemEnv.pluginBaseUrl) return;
try { try {
const outLink = await MongoOutLink.findOne({ const outLink = await MongoOutLink.findOne({
shareId shareId

View File

@ -1,5 +1,7 @@
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { parseHeaderCert } from '../controller'; import { parseHeaderCert } from '../controller';
import { AuthModeType } from '../type'; import { AuthModeType } from '../type';
import { authOutLinkValid } from './outLink';
export const authCert = async (props: AuthModeType) => { export const authCert = async (props: AuthModeType) => {
const result = await parseHeaderCert(props); const result = await parseHeaderCert(props);
@ -10,3 +12,22 @@ export const authCert = async (props: AuthModeType) => {
canWrite: true canWrite: true
}; };
}; };
export async function authCertAndShareId({
shareId,
...props
}: AuthModeType & { shareId?: string }) {
if (!shareId) {
return authCert(props);
}
const { app } = await authOutLinkValid({ shareId });
return {
teamId: String(app.teamId),
tmbId: String(app.tmbId),
authType: AuthUserTypeEnum.outLink,
apikey: '',
isOwner: false,
canWrite: false
};
}

View File

@ -27,11 +27,11 @@ export async function authDataset({
} }
> { > {
const result = await parseHeaderCert(props); const result = await parseHeaderCert(props);
const { userId, teamId, tmbId } = result; const { teamId, tmbId } = result;
const { role } = await getTeamInfoByTmbId({ tmbId }); const { role } = await getTeamInfoByTmbId({ tmbId });
const { dataset, isOwner, canWrite } = await (async () => { const { dataset, isOwner, canWrite } = await (async () => {
const dataset = (await MongoDataset.findOne({ _id: datasetId, teamId }))?.toJSON(); const dataset = (await MongoDataset.findOne({ _id: datasetId, teamId }))?.toObject();
if (!dataset) { if (!dataset) {
return Promise.reject(DatasetErrEnum.unAuthDataset); return Promise.reject(DatasetErrEnum.unAuthDataset);

View File

@ -6,7 +6,6 @@ import { getTeamInfoByTmbId } from '../../user/team/controller';
import { MongoOpenApi } from '../../openapi/schema'; import { MongoOpenApi } from '../../openapi/schema';
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi'; import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
export async function authOpenApiKeyCrud({ export async function authOpenApiKeyCrud({
id, id,

View File

@ -37,13 +37,11 @@ export async function authUserRole(props: AuthModeType): Promise<
teamOwner: boolean; teamOwner: boolean;
} }
> { > {
const { userId, teamId, tmbId } = await parseHeaderCert(props); const result = await parseHeaderCert(props);
const { role: userRole, canWrite } = await getTeamInfoByTmbId({ tmbId }); const { role: userRole, canWrite } = await getTeamInfoByTmbId({ tmbId: result.tmbId });
return { return {
userId, ...result,
teamId,
tmbId,
isOwner: true, isOwner: true,
role: userRole, role: userRole,
teamOwner: userRole === TeamMemberRoleEnum.owner, teamOwner: userRole === TeamMemberRoleEnum.owner,

View File

@ -4,57 +4,42 @@ import {
TeamMemberRoleEnum, TeamMemberRoleEnum,
TeamMemberStatusEnum, TeamMemberStatusEnum,
TeamCollectionName, TeamCollectionName,
TeamMemberCollectionName TeamMemberCollectionName,
leaveStatus
} from '@fastgpt/global/support/user/team/constant'; } from '@fastgpt/global/support/user/team/constant';
export async function getTeamInfoByTmbId({ async function getTeam(match: Record<string, any>): Promise<TeamItemType> {
tmbId,
userId
}: {
tmbId?: string;
userId?: string;
}): Promise<TeamItemType> {
if (!tmbId && !userId) {
return Promise.reject('tmbId or userId is required');
}
const db = connectionMongo?.connection?.db; const db = connectionMongo?.connection?.db;
const TeamMember = db.collection(TeamMemberCollectionName); const TeamMember = db.collection(TeamMemberCollectionName);
const results = await TeamMember.aggregate([ const results = await TeamMember.aggregate([
{ {
$match: tmbId $match: match
? {
_id: new Types.ObjectId(tmbId)
}
: {
userId: new Types.ObjectId(userId),
defaultTeam: true
}
}, },
{ {
$lookup: { $lookup: {
from: TeamCollectionName, // 关联的集合名 from: TeamCollectionName,
localField: 'teamId', // TeamMember 集合中用于关联的字段 localField: 'teamId',
foreignField: '_id', // Team 集合中用于关联的字段 foreignField: '_id',
as: 'team' // 查询结果中的字段名,存放关联查询的结果 as: 'team'
} }
}, },
{ {
$unwind: '$team' // 将查询结果中的 team 字段展开,变成一个对象 $unwind: '$team'
} }
]).toArray(); ]).toArray();
const tmb = results[0]; const tmb = results[0];
if (!tmb) { if (!tmb) {
return Promise.reject('team not exist'); return Promise.reject('member not exist');
} }
return { return {
userId: String(tmb.userId), userId: String(tmb.userId),
teamId: String(tmb.teamId), teamId: String(tmb.teamId),
teamName: tmb.team.name, teamName: tmb.team.name,
memberName: tmb.name,
avatar: tmb.team.avatar, avatar: tmb.team.avatar,
balance: tmb.team.balance, balance: tmb.team.balance,
tmbId: String(tmb._id), tmbId: String(tmb._id),
@ -65,11 +50,31 @@ export async function getTeamInfoByTmbId({
maxSize: tmb.team.maxSize maxSize: tmb.team.maxSize
}; };
} }
export async function getTeamInfoByTmbId({ tmbId }: { tmbId: string }) {
if (!tmbId) {
return Promise.reject('tmbId or userId is required');
}
return getTeam({
_id: new Types.ObjectId(tmbId),
status: leaveStatus
});
}
export async function getUserDefaultTeam({ userId }: { userId: string }) {
if (!userId) {
return Promise.reject('tmbId or userId is required');
}
return getTeam({
userId: new Types.ObjectId(userId),
defaultTeam: true
});
}
export async function createDefaultTeam({ export async function createDefaultTeam({
userId, userId,
teamName = 'My Team', teamName = 'My Team',
avatar = '/icon/logo.svg', avatar = '/icon/logo.svg',
balance = 0, balance,
maxSize = 5 maxSize = 5
}: { }: {
userId: string; userId: string;
@ -103,6 +108,7 @@ export async function createDefaultTeam({
await TeamMember.insertOne({ await TeamMember.insertOne({
teamId: insertedId, teamId: insertedId,
userId, userId,
name: 'Owner',
role: TeamMemberRoleEnum.owner, role: TeamMemberRoleEnum.owner,
status: TeamMemberStatusEnum.active, status: TeamMemberStatusEnum.active,
createTime: new Date(), createTime: new Date(),
@ -116,7 +122,7 @@ export async function createDefaultTeam({
}, },
{ {
$set: { $set: {
balance, ...(balance !== undefined && { balance }),
maxSize maxSize
} }
} }

View File

@ -36,6 +36,7 @@ const BillSchema = new Schema({
default: () => new Date() default: () => new Date()
}, },
total: { total: {
// 1 * PRICE_SCALE
type: Number, type: Number,
required: true required: true
}, },

42
pnpm-lock.yaml generated
View File

@ -51,6 +51,9 @@ importers:
encoding: encoding:
specifier: ^0.1.13 specifier: ^0.1.13
version: registry.npmmirror.com/encoding@0.1.13 version: registry.npmmirror.com/encoding@0.1.13
js-tiktoken:
specifier: ^1.0.7
version: registry.npmmirror.com/js-tiktoken@1.0.7
openai: openai:
specifier: ^4.16.1 specifier: ^4.16.1
version: registry.npmmirror.com/openai@4.16.1(encoding@0.1.13) version: registry.npmmirror.com/openai@4.16.1(encoding@0.1.13)
@ -200,12 +203,6 @@ importers:
immer: immer:
specifier: ^9.0.19 specifier: ^9.0.19
version: registry.npmmirror.com/immer@9.0.21 version: registry.npmmirror.com/immer@9.0.21
js-cookie:
specifier: ^3.0.5
version: registry.npmmirror.com/js-cookie@3.0.5
js-tiktoken:
specifier: ^1.0.7
version: registry.npmmirror.com/js-tiktoken@1.0.7
jschardet: jschardet:
specifier: ^3.0.0 specifier: ^3.0.0
version: registry.npmmirror.com/jschardet@3.0.0 version: registry.npmmirror.com/jschardet@3.0.0
@ -242,9 +239,6 @@ importers:
papaparse: papaparse:
specifier: ^5.4.1 specifier: ^5.4.1
version: registry.npmmirror.com/papaparse@5.4.1 version: registry.npmmirror.com/papaparse@5.4.1
pg-query-stream:
specifier: ^4.5.3
version: registry.npmmirror.com/pg-query-stream@4.5.3(pg@8.11.3)
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: registry.npmmirror.com/react@18.2.0 version: registry.npmmirror.com/react@18.2.0
@ -8721,13 +8715,6 @@ packages:
set-function-name: registry.npmmirror.com/set-function-name@2.0.1 set-function-name: registry.npmmirror.com/set-function-name@2.0.1
dev: true dev: true
registry.npmmirror.com/js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz}
name: js-cookie
version: 3.0.5
engines: {node: '>=14'}
dev: false
registry.npmmirror.com/js-sdsl@4.4.2: registry.npmmirror.com/js-sdsl@4.4.2:
resolution: {integrity: sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.4.2.tgz} resolution: {integrity: sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.4.2.tgz}
name: js-sdsl name: js-sdsl
@ -10629,17 +10616,6 @@ packages:
version: 2.6.2 version: 2.6.2
dev: false dev: false
registry.npmmirror.com/pg-cursor@2.10.3(pg@8.11.3):
resolution: {integrity: sha512-rDyBVoqPVnx/PTmnwQAYgusSeAKlTL++gmpf5klVK+mYMFEqsOc6VHHZnPKc/4lOvr4r6fiMuoxSFuBF1dx4FQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-cursor/-/pg-cursor-2.10.3.tgz}
id: registry.npmmirror.com/pg-cursor/2.10.3
name: pg-cursor
version: 2.10.3
peerDependencies:
pg: ^8
dependencies:
pg: registry.npmmirror.com/pg@8.11.3
dev: false
registry.npmmirror.com/pg-int8@1.0.1: registry.npmmirror.com/pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz} resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz}
name: pg-int8 name: pg-int8
@ -10669,18 +10645,6 @@ packages:
name: pg-protocol name: pg-protocol
version: 1.6.0 version: 1.6.0
registry.npmmirror.com/pg-query-stream@4.5.3(pg@8.11.3):
resolution: {integrity: sha512-ufa94r/lHJdjAm3+zPZEO0gXAmCb4tZPaOt7O76mjcxdL/HxwTuryy76km+u0odBBgtfdKFYq/9XGfiYeQF0yA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-query-stream/-/pg-query-stream-4.5.3.tgz}
id: registry.npmmirror.com/pg-query-stream/4.5.3
name: pg-query-stream
version: 4.5.3
peerDependencies:
pg: ^8
dependencies:
pg: registry.npmmirror.com/pg@8.11.3
pg-cursor: registry.npmmirror.com/pg-cursor@2.10.3(pg@8.11.3)
dev: false
registry.npmmirror.com/pg-types@2.2.0: registry.npmmirror.com/pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz} resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz}
name: pg-types name: pg-types

View File

@ -8,5 +8,8 @@ module.exports = {
defaultLocale: 'zh', defaultLocale: 'zh',
locales: ['en', 'zh', 'zh-Hans', 'zh-CN'], locales: ['en', 'zh', 'zh-Hans', 'zh-CN'],
localeDetection: false localeDetection: false
} },
localePath:
typeof window === 'undefined' ? require('path').resolve('./public/locales') : '/locales',
reloadOnPrerender: process.env.NODE_ENV === 'development'
}; };

View File

@ -33,8 +33,6 @@
"hyperdown": "^2.4.29", "hyperdown": "^2.4.29",
"i18next": "^22.5.1", "i18next": "^22.5.1",
"immer": "^9.0.19", "immer": "^9.0.19",
"js-cookie": "^3.0.5",
"js-tiktoken": "^1.0.7",
"jschardet": "^3.0.0", "jschardet": "^3.0.0",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
@ -47,7 +45,6 @@
"next-i18next": "^13.3.0", "next-i18next": "^13.3.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"pg-query-stream": "^4.5.3",
"react": "18.2.0", "react": "18.2.0",
"react-day-picker": "^8.7.1", "react-day-picker": "^8.7.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",

View File

@ -1,9 +1,12 @@
### Fast GPT V4.6 ### Fast GPT V4.6
1. 新增 - 团队空间试用版。 1. 新增 - 团队空间
2. 新增 - OpenAI语音播报。 2. 新增 - 多路向量(多个向量映射一组数据)
3. [知识库结构详解](https://doc.fastgpt.in/docs/use-cases/datasetengine/) 3. 新增 - tts语音
4. [知识库提示词详解](https://doc.fastgpt.in/docs/use-cases/ai_settings/#引用模板--引用提示词) 4. 线上环境新增 - ReRank向量召回提高召回精度
5. [使用文档](https://doc.fastgpt.in/docs/intro/) 5. 优化 - 知识库导出,可直接触发流下载,无需等待转圈圈
6. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow) 6. [知识库结构详解](https://doc.fastgpt.in/docs/use-cases/datasetengine/)
7. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/) 7. [知识库提示词详解](https://doc.fastgpt.in/docs/use-cases/ai_settings/#引用模板--引用提示词)
8. [使用文档](https://doc.fastgpt.in/docs/intro/)
9. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow)
10. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)

View File

@ -292,17 +292,32 @@
"Select One Collection To Store": "Select the collection to store" "Select One Collection To Store": "Select the collection to store"
}, },
"data": { "data": {
"Add Index": "Add Index",
"Can not delete tip": "No modification permission", "Can not delete tip": "No modification permission",
"Can not edit": "No edit permission", "Can not edit": "No edit permission",
"Custom Index Number": "Custom index{{number}}",
"Default Index": "Default Index",
"Delete Success Tip": "",
"Delete Tip": "Confirm to delete the data?", "Delete Tip": "Confirm to delete the data?",
"File import": "File Import", "File import": "File Import",
"Index Edit": "Data Index",
"Index Placeholder": "Enter the index text content",
"Input Data": "Import Data", "Input Data": "Import Data",
"Input Success Tip": "Succeeded in importing data", "Input Success Tip": "Succeeded in importing data",
"Update Data": "Update Data", "Update Data": "Update Data",
"Update Success Tip": "Update data successfully" "Update Success Tip": "Update data successfully",
"edit": {
"Content": "Content",
"Course": "Document",
"Delete": "Delete",
"Index": "Index({{amount}})"
}
}, },
"deleteDatasetTips": "Are you sure to delete the knowledge base? Data cannot be recovered after deletion, please confirm!", "deleteDatasetTips": "Are you sure to delete the knowledge base? Data cannot be recovered after deletion, please confirm!",
"deleteFolderTips": "Are you sure to delete this folder and all the knowledge bases it contains? Data cannot be recovered after deletion, please confirm!" "deleteFolderTips": "Are you sure to delete this folder and all the knowledge bases it contains? Data cannot be recovered after deletion, please confirm!",
"test": {
"noResult": "Search results are empty"
}
}, },
"error": { "error": {
"team": { "team": {
@ -461,9 +476,11 @@
"Bill Detail": "Bill Detail", "Bill Detail": "Bill Detail",
"Change": "Change", "Change": "Change",
"Copy invite url": "Copy invitation link", "Copy invite url": "Copy invitation link",
"Edit name": "Click to modify nickname",
"Invite Url": "Invite Url", "Invite Url": "Invite Url",
"Invite url tip": "Friends who register through this link will be permanently bound to you, and you will get a certain balance reward when they recharge. In addition, when friends register with their mobile phone number, you will get 5 yuan reward immediately.", "Invite url tip": "Friends who register through this link will be permanently bound to you, and you will get a certain balance reward when they recharge. In addition, when friends register with their mobile phone number, you will get 5 yuan reward immediately.",
"Language": "Language", "Language": "Language",
"Member Name": "Name",
"Notice": "Notice", "Notice": "Notice",
"Old password is error": "Old password is error", "Old password is error": "Old password is error",
"OpenAI Account Setting": "OpenAI Account Setting", "OpenAI Account Setting": "OpenAI Account Setting",
@ -513,12 +530,15 @@
"Leave Team Failed": "Leave Team Failed", "Leave Team Failed": "Leave Team Failed",
"Manage": "Team Manage", "Manage": "Team Manage",
"Member": "Member", "Member": "Member",
"Member Name": "Member",
"Over Max Member Tip": "Team max {{max}} people", "Over Max Member Tip": "Team max {{max}} people",
"Personal Team": "Personal", "Personal Team": "Personal",
"Processing invitations": "Processing invitations", "Processing invitations": "Processing invitations",
"Processing invitations Tips": "You have {{amount}} of team invitations that need to be processed", "Processing invitations Tips": "You have {{amount}} of team invitations that need to be processed",
"Reinvite": "Reinvite", "Reinvite": "Reinvite",
"Remove Member Confirm Tip": "Confirm to move {{username}} off the team", "Remove Member Confirm Tip": "Are you sure you want to move {{username}} off the team? All its resources are transferred to the team creator's account.",
"Remove Member Failed": "Removing a team member failed",
"Remove Member Success": "Removing a team member succeeded",
"Remove Member Tip": "Move out team", "Remove Member Tip": "Move out team",
"Role": "Role", "Role": "Role",
"Select Team": "Select Team", "Select Team": "Select Team",

View File

@ -292,17 +292,32 @@
"Select One Collection To Store": "选择一个文件进行存储" "Select One Collection To Store": "选择一个文件进行存储"
}, },
"data": { "data": {
"Add Index": "新增自定义索引",
"Can not delete tip": "无修改权限", "Can not delete tip": "无修改权限",
"Can not edit": "无编辑权限", "Can not edit": "无编辑权限",
"Custom Index Number": "自定义索引{{number}}",
"Default Index": "默认索引",
"Delete Success Tip": "删除成功",
"Delete Tip": "确认删除该条数据?", "Delete Tip": "确认删除该条数据?",
"File import": "文件导入", "File import": "文件导入",
"Index Edit": "数据索引",
"Index Placeholder": "输入索引文本内容",
"Input Data": "导入新数据", "Input Data": "导入新数据",
"Input Success Tip": "导入数据成功", "Input Success Tip": "导入数据成功",
"Update Data": "更新数据", "Update Data": "更新数据",
"Update Success Tip": "更新数据成功" "Update Success Tip": "更新数据成功",
"edit": {
"Content": "数据内容",
"Course": "说明文档",
"Delete": "删除数据",
"Index": "数据索引({{amount}})"
}
}, },
"deleteDatasetTips": "确认删除该知识库?删除后数据无法恢复,请确认!", "deleteDatasetTips": "确认删除该知识库?删除后数据无法恢复,请确认!",
"deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!" "deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!",
"test": {
"noResult": "搜索结果为空"
}
}, },
"error": { "error": {
"team": { "team": {
@ -461,9 +476,11 @@
"Bill Detail": "账单详情", "Bill Detail": "账单详情",
"Change": "变更", "Change": "变更",
"Copy invite url": "复制邀请链接", "Copy invite url": "复制邀请链接",
"Edit name": "点击修改昵称",
"Invite Url": "邀请链接", "Invite Url": "邀请链接",
"Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外好友使用手机号注册时你将立即获得 5 元奖励。\n奖励会发送到您的默认团队中。", "Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外好友使用手机号注册时你将立即获得 5 元奖励。\n奖励会发送到您的默认团队中。",
"Language": "语言", "Language": "语言",
"Member Name": "昵称",
"Notice": "通知", "Notice": "通知",
"Old password is error": "旧密码错误", "Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置", "OpenAI Account Setting": "OpenAI 账号配置",
@ -513,12 +530,15 @@
"Leave Team Failed": "离开团队异常", "Leave Team Failed": "离开团队异常",
"Manage": "团队管理", "Manage": "团队管理",
"Member": "成员", "Member": "成员",
"Member Name": "成员名",
"Over Max Member Tip": "团队最多{{max}}人", "Over Max Member Tip": "团队最多{{max}}人",
"Personal Team": "个人团队", "Personal Team": "个人团队",
"Processing invitations": "处理邀请", "Processing invitations": "处理邀请",
"Processing invitations Tips": "你有{{amount}}个需要处理的团队邀请", "Processing invitations Tips": "你有{{amount}}个需要处理的团队邀请",
"Reinvite": "重新邀请", "Reinvite": "重新邀请",
"Remove Member Confirm Tip": "确认将 {{username}} 移出团队?", "Remove Member Confirm Tip": "确认将 {{username}} 移出团队?其所有资源将转让到团队创建者的账户内。",
"Remove Member Failed": "移除团队成员异常",
"Remove Member Success": "移除团队成员成功",
"Remove Member Tip": "移出团队", "Remove Member Tip": "移出团队",
"Role": "身份", "Role": "身份",
"Select Team": "团队选择", "Select Team": "团队选择",

View File

@ -10,13 +10,12 @@ import InputDataModal, {
type InputDataType type InputDataType
} from '@/pages/dataset/detail/components/InputDataModal'; } from '@/pages/dataset/detail/components/InputDataModal';
import MyModal from '../MyModal'; import MyModal from '../MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import MyTooltip from '../MyTooltip'; import MyTooltip from '../MyTooltip';
import NextLink from 'next/link'; import NextLink from 'next/link';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useUserStore } from '@/web/support/user/useUserStore';
const QuoteModal = ({ const QuoteModal = ({
rawSearch = [], rawSearch = [],
@ -29,10 +28,9 @@ const QuoteModal = ({
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { userInfo } = useUserStore();
const { toast } = useToast(); const { toast } = useToast();
const { setIsLoading, Loading } = useLoading(); const { setIsLoading, Loading } = useLoading();
const [editInputData, setEditInputData] = useState<InputDataType & { datasetId: string }>(); const [editInputData, setEditInputData] = useState<InputDataType & { collectionId: string }>();
const isShare = useMemo(() => router.pathname === '/chat/share', [router.pathname]); const isShare = useMemo(() => router.pathname === '/chat/share', [router.pathname]);
@ -99,7 +97,7 @@ const QuoteModal = ({
color={'black'} color={'black'}
sourceName={item.sourceName} sourceName={item.sourceName}
sourceId={item.sourceId} sourceId={item.sourceId}
addr={!isShare} canView={!isShare}
/> />
<Box flex={1} /> <Box flex={1} />
{!isShare && ( {!isShare && (
@ -131,7 +129,7 @@ const QuoteModal = ({
<MyTooltip label={t('core.dataset.Quote Length')}> <MyTooltip label={t('core.dataset.Quote Length')}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} /> <MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
{item.q.length + item.a.length} {item.q.length + (item.a?.length || 0)}
</Flex> </Flex>
</MyTooltip> </MyTooltip>
{!isShare && item.score && ( {!isShare && item.score && (
@ -183,7 +181,6 @@ const QuoteModal = ({
</MyModal> </MyModal>
{editInputData && editInputData.id && ( {editInputData && editInputData.id && (
<InputDataModal <InputDataModal
canWrite={userInfo?.team?.canWrite || false}
onClose={() => setEditInputData(undefined)} onClose={() => setEditInputData(undefined)}
onSuccess={() => { onSuccess={() => {
console.log('更新引用成功'); console.log('更新引用成功');
@ -191,8 +188,8 @@ const QuoteModal = ({
onDelete={() => { onDelete={() => {
console.log('删除引用成功'); console.log('删除引用成功');
}} }}
datasetId={editInputData.datasetId} defaultValue={editInputData}
defaultValues={editInputData} collectionId={editInputData.collectionId}
/> />
)} )}
</> </>

View File

@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react'; import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useMemo, useState } from 'react';
import { ModalBody, useTheme, ModalFooter, Button, Box, Card, Flex, Grid } from '@chakra-ui/react'; import { ModalBody, useTheme, ModalFooter, Button, Box, Card, Flex, Grid } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import Avatar from '../Avatar'; import Avatar from '../Avatar';
@ -8,6 +8,7 @@ import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { AdminFbkType } from '@fastgpt/global/core/chat/type.d'; import { AdminFbkType } from '@fastgpt/global/core/chat/type.d';
import SelectCollections from '@/web/core/dataset/components/SelectCollections'; import SelectCollections from '@/web/core/dataset/components/SelectCollections';
import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils';
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal')); const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
@ -163,15 +164,13 @@ const SelectMarkCollection = ({
{adminMarkData.datasetId && adminMarkData.collectionId && ( {adminMarkData.datasetId && adminMarkData.collectionId && (
<InputDataModal <InputDataModal
onClose={onClose} onClose={onClose}
datasetId={adminMarkData.datasetId} collectionId={adminMarkData.collectionId}
defaultValues={{ defaultValue={{
id: adminMarkData.dataId, id: adminMarkData.dataId,
collectionId: adminMarkData.collectionId,
sourceName: '手动标注',
q: adminMarkData.q, q: adminMarkData.q,
a: adminMarkData.a a: adminMarkData.a,
indexes: [getDefaultIndex({ dataId: `${Date.now()}` })]
}} }}
canWrite
onSuccess={(data) => { onSuccess={(data) => {
if (!data.q || !adminMarkData.datasetId || !adminMarkData.collectionId || !data.id) { if (!data.q || !adminMarkData.datasetId || !adminMarkData.collectionId || !data.id) {
return onClose(); return onClose();

View File

@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image } from '@chakra-ui/react'; import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate'; import { ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate';
import Tabs from '../Tabs'; import Tabs from '../Tabs';

View File

@ -30,7 +30,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { feConfigs } from '@/web/common/system/staticData'; import { feConfigs } from '@/web/common/system/staticData';
import { eventBus } from '@/web/common/utils/eventbus'; import { eventBus } from '@/web/common/utils/eventbus';
import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { useMarkdown } from '@/web/common/hooks/useMarkdown'; import { useMarkdown } from '@/web/common/hooks/useMarkdown';
import { ModuleItemType } from '@fastgpt/global/core/module/type.d'; import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { VariableInputEnum } from '@/constants/app'; import { VariableInputEnum } from '@/constants/app';
@ -41,7 +41,7 @@ import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import { adminUpdateChatFeedback, userUpdateChatFeedback } from '@/web/core/chat/api'; import { adminUpdateChatFeedback, userUpdateChatFeedback } from '@/web/core/chat/api';
import type { AdminMarkType } from './SelectMarkCollection'; import type { AdminMarkType } from './SelectMarkCollection';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Button, ModalFooter, ModalBody } from '@chakra-ui/react'; import { Button, ModalFooter, ModalBody } from '@chakra-ui/react';
import MyModal from '../MyModal'; import MyModal from '../MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import Markdown from '../Markdown'; import Markdown from '../Markdown';
const md = ` const md = `

View File

@ -1,48 +0,0 @@
import React, { useState } from 'react';
import { Menu, MenuButton, MenuItem, MenuList, MenuButtonProps } from '@chakra-ui/react';
import { getLangStore, LangEnum, setLangStore, langMap } from '@/web/common/utils/i18n';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next';
import { useRouter } from 'next/router';
const Language = (props: MenuButtonProps) => {
const router = useRouter();
const { i18n } = useTranslation();
const [language, setLanguage] = useState<`${LangEnum}`>(getLangStore());
return (
<Menu autoSelect={false}>
<MenuButton
{...props}
sx={{
'& span': {
flex: 0
}
}}
>
<MyIcon name={langMap[language].icon as any} w={['18px', '22px']} />
</MenuButton>
<MenuList w="max-content" minW="120px">
{Object.entries(langMap).map(([key, lang]) => (
<MenuItem
key={key}
display="flex"
alignItems="center"
onClick={() => {
const lang = key as `${LangEnum}`;
setLangStore(lang);
setLanguage(lang);
i18n?.changeLanguage?.(lang);
router.reload();
}}
>
{lang.label}
</MenuItem>
))}
</MenuList>
</Menu>
);
};
export default React.memo(Language);

View File

@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Flex, Box } from '@chakra-ui/react'; import { Flex, Box } from '@chakra-ui/react';
import { useChatStore } from '@/web/core/chat/storeChat'; import { useChatStore } from '@/web/core/chat/storeChat';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import Badge from '../Badge'; import Badge from '../Badge';
import MyIcon from '../Icon'; import MyIcon from '../Icon';

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Box, Grid } from '@chakra-ui/react'; import { Box, Grid } from '@chakra-ui/react';
import type { GridProps } from '@chakra-ui/react'; import type { GridProps } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
// @ts-ignore // @ts-ignore
interface Props extends GridProps { interface Props extends GridProps {

View File

@ -2,7 +2,7 @@ import MyIcon from '@/components/Icon';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
const ParentPaths = (props: { const ParentPaths = (props: {
paths?: ParentTreePathItemType[]; paths?: ParentTreePathItemType[];

View File

@ -10,7 +10,7 @@ import {
useTheme useTheme
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useToast } from '@/web/common/hooks/useToast'; import { useToast } from '@/web/common/hooks/useToast';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
type Props = BoxProps & { defaultValues: string[]; onUpdate: (e: string[]) => void }; type Props = BoxProps & { defaultValues: string[]; onUpdate: (e: string[]) => void };

View File

@ -2,7 +2,7 @@ import { getDatasets, getDatasetPaths } from '@/web/core/dataset/api';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import React, { Dispatch, useMemo, useState } from 'react'; import React, { Dispatch, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { Box, Flex, ModalHeader } from '@chakra-ui/react'; import { Box, Flex, ModalHeader } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';

View File

@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { EditFormType } from '@/web/core/app/basicSettings'; import { EditFormType } from '@/web/core/app/basicSettings';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { import {

View File

@ -14,14 +14,14 @@ import {
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import type { SelectedDatasetType } from '@/types/core/dataset'; import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import { useToast } from '@/web/common/hooks/useToast'; import { useToast } from '@/web/common/hooks/useToast';
import MySlider from '@/components/Slider'; import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { feConfigs } from '@/web/common/system/staticData'; import { feConfigs } from '@/web/common/system/staticData';
import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal'; import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal';

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react'; import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useToast } from '@/web/common/hooks/useToast'; import { useToast } from '@/web/common/hooks/useToast';
import { useFlowProviderStore } from './FlowProvider'; import { useFlowProviderStore } from './FlowProvider';

View File

@ -4,7 +4,7 @@ import MyModal from '@/components/MyModal';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import type { SelectAppItemType } from '@fastgpt/global/core/module/type'; import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useLoading } from '@/web/common/hooks/useLoading'; import { useLoading } from '@/web/common/hooks/useLoading';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';

View File

@ -10,7 +10,7 @@ import Avatar from '@/components/Avatar';
import { useFlowProviderStore } from './FlowProvider'; import { useFlowProviderStore } from './FlowProvider';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import { appModule2FlowNode } from '@/utils/adapt'; import { appModule2FlowNode } from '@/utils/adapt';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';

View File

@ -13,7 +13,7 @@ import { useForm } from 'react-hook-form';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant'; import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import MySelect from '@/components/Select'; import MySelect from '@/components/Select';
const typeSelectList = [ const typeSelectList = [

View File

@ -5,7 +5,7 @@ import Avatar from '@/components/Avatar';
import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d'; import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useEditTitle } from '@/web/common/hooks/useEditTitle'; import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import { useToast } from '@/web/common/hooks/useToast'; import { useToast } from '@/web/common/hooks/useToast';
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider'; import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';

View File

@ -15,7 +15,7 @@ import {
FlowNodeValTypeEnum, FlowNodeValTypeEnum,
FlowNodeSpecialInputKeyEnum FlowNodeSpecialInputKeyEnum
} from '@fastgpt/global/core/module/node/constant'; } from '@fastgpt/global/core/module/node/constant';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import SourceHandle from '../render/SourceHandle'; import SourceHandle from '../render/SourceHandle';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import { onChangeNode } from '../../FlowProvider'; import { onChangeNode } from '../../FlowProvider';

View File

@ -26,12 +26,12 @@ import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import TargetHandle from './TargetHandle'; import TargetHandle from './TargetHandle';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { AIChatProps } from '@/types/core/aiChat'; import { AIChatProps } from '@/types/core/aiChat';
import { chatModelList } from '@/web/common/system/staticData'; import { chatModelList } from '@/web/common/system/staticData';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { SelectedDatasetType } from '@/types/core/dataset'; import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal'; import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';

View File

@ -33,7 +33,7 @@ import dayjs from 'dayjs';
import { AddIcon, QuestionOutlineIcon } from '@chakra-ui/icons'; import { AddIcon, QuestionOutlineIcon } from '@chakra-ui/icons';
import { useCopyData } from '@/web/common/hooks/useCopyData'; import { useCopyData } from '@/web/common/hooks/useCopyData';
import { feConfigs } from '@/web/common/system/staticData'; import { feConfigs } from '@/web/common/system/staticData';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant'; import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
import { Box, Flex, FlexProps } from '@chakra-ui/react'; import { Box, Flex, FlexProps } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
const PermissionIconText = ({ const PermissionIconText = ({
permission, permission,

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import MyRadio from '@/components/Radio'; import MyRadio from '@/components/Radio';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
const PermissionRadio = ({ const PermissionRadio = ({
value, value,

View File

@ -1,6 +1,6 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgAndUpload } from '@/web/common/file/controller'; import { compressImgAndUpload } from '@/web/common/file/controller';
import { useToast } from '@/web/common/hooks/useToast'; import { useToast } from '@/web/common/hooks/useToast';

View File

@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react'; import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react';
import TagTextarea from '@/components/common/Textarea/TagTextarea'; import TagTextarea from '@/components/common/Textarea/TagTextarea';
import MySelect from '@/components/Select'; import MySelect from '@/components/Select';
@ -57,8 +57,8 @@ const InviteModal = ({
undefined, undefined,
t('user.team.Invite Member Success Tip', { t('user.team.Invite Member Success Tip', {
success: res.invite.length, success: res.invite.length,
inValid: res.inValid.join(', '), inValid: res.inValid.map((item) => item.username).join(', '),
inTeam: res.inTeam.join(', ') inTeam: res.inTeam.map((item) => item.username).join(', ')
}) })
)(); )();
}, },

View File

@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { import {
getTeamList, getTeamList,
@ -100,7 +100,9 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
mutationFn: delRemoveMember, mutationFn: delRemoveMember,
onSuccess() { onSuccess() {
refetchMembers(); refetchMembers();
} },
successToast: t('user.team.Remove Member Success'),
errorToast: t('user.team.Remove Member Failed')
}); });
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({ const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
mutationFn: async (teamId?: string) => { mutationFn: async (teamId?: string) => {
@ -306,7 +308,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
<Td display={'flex'} alignItems={'center'}> <Td display={'flex'} alignItems={'center'}>
<Avatar src={item.avatar} w={['18px', '22px']} /> <Avatar src={item.avatar} w={['18px', '22px']} />
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}> <Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
{item.memberUsername} {item.memberName}
</Box> </Box>
</Td> </Td>
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td> <Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
@ -383,7 +385,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
}), }),
undefined, undefined,
t('user.team.Remove Member Confirm Tip', { t('user.team.Remove Member Confirm Tip', {
username: item.memberUsername username: item.memberName
}) })
)() )()
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Box, Button, Flex, Image, useDisclosure, useTheme } from '@chakra-ui/react'; import { Box, Button, Flex, Image, useDisclosure, useTheme } from '@chakra-ui/react';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { feConfigs } from '@/web/common/system/staticData'; import { feConfigs } from '@/web/common/system/staticData';

View File

@ -1,5 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { import {
Button, Button,

View File

@ -1,4 +1,7 @@
import type { DatasetItemType } from '@/types/core/dataset'; import type {
DatasetCollectionItemType,
DatasetItemType
} from '@fastgpt/global/core/dataset/type.d';
export const defaultDatasetDetail: DatasetItemType = { export const defaultDatasetDetail: DatasetItemType = {
_id: '', _id: '',
@ -10,7 +13,7 @@ export const defaultDatasetDetail: DatasetItemType = {
type: 'dataset', type: 'dataset',
avatar: '/icon/logo.svg', avatar: '/icon/logo.svg',
name: '', name: '',
tags: '', tags: [],
permission: 'private', permission: 'private',
isOwner: false, isOwner: false,
canWrite: false, canWrite: false,
@ -22,3 +25,32 @@ export const defaultDatasetDetail: DatasetItemType = {
maxToken: 3000 maxToken: 3000
} }
}; };
export const defaultCollectionDetail: DatasetCollectionItemType = {
_id: '',
userId: '',
teamId: '',
tmbId: '',
datasetId: {
_id: '',
parentId: '',
userId: '',
teamId: '',
tmbId: '',
updateTime: new Date(),
type: 'dataset',
avatar: '/icon/logo.svg',
name: '',
tags: [],
permission: 'private',
vectorModel: 'text-embedding-ada-002'
},
parentId: '',
name: '',
type: 'file',
updateTime: new Date(),
metadata: {},
canWrite: false,
sourceName: '',
sourceId: ''
};

View File

@ -1,108 +0,0 @@
import { getErrText } from '@fastgpt/global/common/error/utils';
import { countPromptTokens } from '@/global/common/tiktoken';
/*
replace {{variable}} to value
*/
export function replaceVariable(text: string, obj: Record<string, string | number>) {
for (const key in obj) {
const val = obj[key];
if (!['string', 'number'].includes(typeof val)) continue;
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), String(val));
}
return text || '';
}
/**
* text split into chunks
* maxLen - one chunk len. max: 3500
* overlapLen - The size of the before and after Text
* maxLen > overlapLen
*/
export const splitText2Chunks = ({ text = '', maxLen }: { text: string; maxLen: number }) => {
const overlapLen = Math.floor(maxLen * 0.15); // Overlap length
const tempMarker = 'SPLIT_HERE_SPLIT_HERE';
const stepReg: Record<number, RegExp> = {
0: /(\n\n)/g,
1: /([\n])/g,
2: /[。]|(?!<[^a-zA-Z])\.\s/g,
3: /([]|!\s|\?\s)/g,
4: /([]|;\s)/g,
5: /([]|,\s)/g
};
const splitTextRecursively = ({ text = '', step }: { text: string; step: number }) => {
if (text.length <= maxLen) {
return [text];
}
const reg = stepReg[step];
if (!reg) {
// use slice-maxLen to split text
const chunks: string[] = [];
let chunk = '';
for (let i = 0; i < text.length; i += maxLen - overlapLen) {
chunk = text.slice(i, i + maxLen);
chunks.push(chunk);
}
return chunks;
}
// split text by delimiters
const splitTexts = text
.replace(reg, `$1${tempMarker}`)
.split(`${tempMarker}`)
.filter((part) => part);
let chunks: string[] = [];
let preChunk = '';
let chunk = '';
for (let i = 0; i < splitTexts.length; i++) {
let text = splitTexts[i];
// chunk over size
if (text.length > maxLen) {
const innerChunks = splitTextRecursively({ text, step: step + 1 });
if (innerChunks.length === 0) continue;
// If the last chunk is too small, it is merged into the next chunk
if (innerChunks[innerChunks.length - 1].length <= maxLen * 0.5) {
text = innerChunks.pop() || '';
chunks = chunks.concat(innerChunks);
} else {
chunks = chunks.concat(innerChunks);
continue;
}
}
chunk += text;
// size over lapLen, push it to next chunk
if (chunk.length > maxLen - overlapLen) {
preChunk += text;
}
if (chunk.length >= maxLen) {
chunks.push(chunk);
chunk = preChunk;
preChunk = '';
}
}
if (chunk && !chunks[chunks.length - 1].endsWith(chunk)) {
chunks.push(chunk);
}
return chunks;
};
try {
const chunks = splitTextRecursively({ text, step: 0 });
const tokens = chunks.reduce((sum, chunk) => sum + countPromptTokens(chunk, 'system'), 0);
return {
chunks,
tokens
};
} catch (err) {
throw new Error(getErrText(err));
}
};

View File

@ -2,7 +2,7 @@ import { DatasetCollectionTypeEnum, DatasetTypeEnum } from '@fastgpt/global/core
import type { RequestPaging } from '@/types'; import type { RequestPaging } from '@/types';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import type { SearchTestItemType } from '@/types/core/dataset'; import type { SearchTestItemType } from '@/types/core/dataset';
import { DatasetChunkItemType, UploadChunkItemType } from '@fastgpt/global/core/dataset/type'; import { UploadChunkItemType } from '@fastgpt/global/core/dataset/type';
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
@ -10,19 +10,11 @@ import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'
export type DatasetUpdateParams = { export type DatasetUpdateParams = {
id: string; id: string;
parentId?: string; parentId?: string;
tags?: string; tags?: string[];
name?: string; name?: string;
avatar?: string; avatar?: string;
permission?: `${PermissionTypeEnum}`; permission?: `${PermissionTypeEnum}`;
}; };
export type CreateDatasetParams = {
parentId?: string;
name: string;
tags: string;
avatar: string;
vectorModel?: string;
type: `${DatasetTypeEnum}`;
};
export type SearchTestProps = { export type SearchTestProps = {
datasetId: string; datasetId: string;
@ -54,20 +46,6 @@ export type UpdateDatasetCollectionParams = {
}; };
/* ==== data ===== */ /* ==== data ===== */
export type SetOneDatasetDataProps = {
id?: string;
collectionId: string;
q?: string; // embedding content
a?: string; // bonus content
};
export type PushDataProps = {
collectionId: string;
data: DatasetChunkItemType[];
mode: `${TrainingModeEnum}`;
prompt?: string;
billId?: string;
};
export type GetDatasetDataListProps = RequestPaging & { export type GetDatasetDataListProps = RequestPaging & {
searchText?: string; searchText?: string;
collectionId: string; collectionId: string;

View File

@ -0,0 +1,35 @@
import { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetDataIndexItemType } from '@fastgpt/global/core/dataset/type';
/* ================= dataset ===================== */
export type CreateDatasetParams = {
parentId?: string;
name: string;
tags: string;
avatar: string;
vectorModel?: string;
type: `${DatasetTypeEnum}`;
};
/* ================= collection ===================== */
/* ================= data ===================== */
export type InsertOneDatasetDataProps = PushDatasetDataChunkProps & {
collectionId: string;
};
export type PushDatasetDataProps = {
collectionId: string;
data: PushDatasetDataChunkProps[];
mode: `${TrainingModeEnum}`;
prompt?: string;
billId?: string;
};
export type UpdateDatasetDataProps = {
id: string;
q?: string; // embedding content
a?: string; // bonus content
indexes: (Omit<DatasetDataIndexItemType, 'dataId'> & {
dataId?: string; // pg data id
})[];
};

View File

@ -1,5 +1,8 @@
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d'; import {
DatasetCollectionSchemaType,
DatasetDataSchemaType
} from '@fastgpt/global/core/dataset/type.d';
/* ================= dataset ===================== */ /* ================= dataset ===================== */
@ -11,7 +14,7 @@ export type DatasetCollectionsListItemType = {
name: string; name: string;
type: DatasetCollectionSchemaType['type']; type: DatasetCollectionSchemaType['type'];
updateTime: Date; updateTime: Date;
dataAmount?: number; dataAmount: number;
trainingAmount: number; trainingAmount: number;
metadata: DatasetCollectionSchemaType['metadata']; metadata: DatasetCollectionSchemaType['metadata'];
canWrite: boolean; canWrite: boolean;
@ -19,7 +22,10 @@ export type DatasetCollectionsListItemType = {
/* ================= data ===================== */ /* ================= data ===================== */
export type DatasetDataListItemType = { export type DatasetDataListItemType = {
id: string; _id: string;
datasetId: string;
collectionId: string;
q: string; // embedding content q: string; // embedding content
a: string; // bonus content a: string; // bonus content
indexes: DatasetDataSchemaType['indexes'];
}; };

View File

@ -10,10 +10,10 @@ import NProgress from 'nprogress'; //nprogress module
import Router from 'next/router'; import Router from 'next/router';
import { clientInitData, feConfigs } from '@/web/common/system/staticData'; import { clientInitData, feConfigs } from '@/web/common/system/staticData';
import { appWithTranslation, useTranslation } from 'next-i18next'; import { appWithTranslation, useTranslation } from 'next-i18next';
import { getLangStore, setLangStore } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d'; import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d';
import { change2DefaultLng, setLngStore } from '@/web/common/utils/i18n';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
import '@/web/styles/reset.scss'; import '@/web/styles/reset.scss';
@ -57,6 +57,7 @@ function App({ Component, pageProps }: AppProps) {
); );
setScripts(scripts || []); setScripts(scripts || []);
})(); })();
// add window error track // add window error track
window.onerror = function (msg, url) { window.onerror = function (msg, url) {
window.umami?.track('windowError', { window.umami?.track('windowError', {
@ -76,23 +77,22 @@ function App({ Component, pageProps }: AppProps) {
}, []); }, []);
useEffect(() => { useEffect(() => {
hiId && localStorage.setItem('inviterId', hiId); // get default language
}, [hiId]); const targetLng = change2DefaultLng(i18n.language);
if (targetLng) {
setLngStore(targetLng);
router.replace(router.asPath, undefined, { locale: targetLng });
}
}, []);
useEffect(() => { useEffect(() => {
const lang = getLangStore() || 'zh'; hiId && localStorage.setItem('inviterId', hiId);
i18n?.changeLanguage?.(lang); }, [hiId]);
setLangStore(lang);
return () => {
setLastRoute(router.asPath);
};
}, [router.asPath]);
return ( return (
<> <>
<Head> <Head>
<title>{feConfigs?.systemTitle || 'AI'}</title> <title>{feConfigs?.systemTitle || process.env.SYSTEM_NAME || 'GPT'}</title>
<meta <meta
name="description" name="description"
content="FastGPT is a knowledge-based question answering system built on the LLM. It offers out-of-the-box data processing and model invocation capabilities. Moreover, it allows for workflow orchestration through Flow visualization, thereby enabling complex question and answer scenarios!" content="FastGPT is a knowledge-based question answering system built on the LLM. It offers out-of-the-box data processing and model invocation capabilities. Moreover, it allows for workflow orchestration through Flow visualization, thereby enabling complex question and answer scenarios!"

View File

@ -2,7 +2,8 @@ import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() { export default function Document() {
return ( return (
<Html lang="en"> <Html>
<title>{process.env.SYSTEM_NAME || 'FastGPT'}</title>
<Head /> <Head />
<body> <body>
<Main /> <Main />

View File

@ -25,8 +25,9 @@ function Error() {
return ( return (
<p> <p>
safari chrome safari chrome
</p> </p>
); );
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import ApiKeyTable from '@/components/support/apikey/Table'; import ApiKeyTable from '@/components/support/apikey/Table';
const ApiKey = () => { const ApiKey = () => {

View File

@ -16,7 +16,7 @@ import dayjs from 'dayjs';
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants'; import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void }) => { const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -30,7 +30,7 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
<ModalBody> <ModalBody>
<Flex alignItems={'center'} pb={4}> <Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box> <Box flex={'0 0 80px'}>:</Box>
<Box>{bill.username}</Box> <Box>{bill.memberName}</Box>
</Flex> </Flex>
<Flex alignItems={'center'} pb={4}> <Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box> <Box flex={'0 0 80px'}>:</Box>

View File

@ -55,7 +55,7 @@ const BillTable = () => {
<Table> <Table>
<Thead> <Thead>
<Tr> <Tr>
<Th>{t('wallet.bill.bill username')}</Th> <Th>{t('user.team.Member Name')}</Th>
<Th>{t('user.Time')}</Th> <Th>{t('user.Time')}</Th>
<Th>{t('user.Source')}</Th> <Th>{t('user.Source')}</Th>
<Th>{t('user.Application Name')}</Th> <Th>{t('user.Application Name')}</Th>
@ -66,7 +66,7 @@ const BillTable = () => {
<Tbody fontSize={'sm'}> <Tbody fontSize={'sm'}>
{bills.map((item) => ( {bills.map((item) => (
<Tr key={item.id}> <Tr key={item.id}>
<Td>{item.username}</Td> <Td>{item.memberName}</Td>
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td> <Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>{BillSourceMap[item.source]}</Td> <Td>{BillSourceMap[item.source]}</Td>
<Td>{t(item.appName) || '-'}</Td> <Td>{t(item.appName) || '-'}</Td>

View File

@ -1,5 +1,14 @@
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef } from 'react';
import { Box, Flex, Button, useDisclosure, useTheme, Divider, Select } from '@chakra-ui/react'; import {
Box,
Flex,
Button,
useDisclosure,
useTheme,
Divider,
Select,
Input
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user'; import { UserUpdateParams } from '@/types/user';
import { useToast } from '@/web/common/hooks/useToast'; import { useToast } from '@/web/common/hooks/useToast';
@ -16,10 +25,11 @@ import Loading from '@/components/Loading';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import { getLangStore, LangEnum, langMap, setLangStore } from '@/web/common/utils/i18n'; import { langMap, setLngStore } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import MySelect from '@/components/Select'; import MySelect from '@/components/Select';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { putUpdateMemberName } from '@/web/support/user/team/api';
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu')); const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
const PayModal = dynamic(() => import('./PayModal'), { const PayModal = dynamic(() => import('./PayModal'), {
@ -63,8 +73,6 @@ const UserInfo = () => {
multiple: false multiple: false
}); });
const [language, setLanguage] = useState<`${LangEnum}`>(getLangStore());
const onclickSave = useCallback( const onclickSave = useCallback(
async (data: UserType) => { async (data: UserType) => {
await updateUserInfo({ await updateUserInfo({
@ -153,6 +161,27 @@ const UserInfo = () => {
ml={[0, 10]} ml={[0, 10]}
mt={[6, 0]} mt={[6, 0]}
> >
{feConfigs.isPlus && (
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Member Name')}:&nbsp;</Box>
<Input
flex={1}
defaultValue={userInfo?.team?.memberName || 'Member'}
title={t('user.Edit name')}
borderColor={'transparent'}
pl={'10px'}
transform={'translateX(-11px)'}
maxLength={20}
onBlur={(e) => {
const val = e.target.value;
if (val === userInfo?.team?.memberName) return;
try {
putUpdateMemberName(val);
} catch (error) {}
}}
/>
</Flex>
)}
<Flex alignItems={'center'} w={['85%', '300px']}> <Flex alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Account')}:&nbsp;</Box> <Box flex={'0 0 80px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box> <Box flex={1}>{userInfo?.username}</Box>
@ -167,17 +196,15 @@ const UserInfo = () => {
<Box flex={'0 0 80px'}>{t('user.Language')}:&nbsp;</Box> <Box flex={'0 0 80px'}>{t('user.Language')}:&nbsp;</Box>
<Box flex={'1 0 0'}> <Box flex={'1 0 0'}>
<MySelect <MySelect
value={language} value={i18n.language}
list={Object.entries(langMap).map(([key, lang]) => ({ list={Object.entries(langMap).map(([key, lang]) => ({
label: lang.label, label: lang.label,
value: key value: key
}))} }))}
onchange={(val: any) => { onchange={(val: any) => {
const lang = val; const lang = val;
setLangStore(lang); setLngStore(lang);
setLanguage(lang); router.replace(router.basePath, router.asPath, { locale: lang });
i18n?.changeLanguage?.(lang);
router.reload();
}} }}
/> />
</Box> </Box>

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react'; import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest'; import { useRequest } from '@/web/common/hooks/useRequest';
import type { UserType } from '@fastgpt/global/support/user/type.d'; import type { UserType } from '@fastgpt/global/support/user/type.d';

View File

@ -5,7 +5,7 @@ import { useToast } from '@/web/common/hooks/useToast';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import Markdown from '@/components/Markdown'; import Markdown from '@/components/Markdown';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { priceMd } from '@/web/common/system/staticData'; import { priceMd } from '@/web/common/system/staticData';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react'; import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest'; import { useRequest } from '@/web/common/hooks/useRequest';
import { updatePasswordByOld } from '@/web/support/user/api'; import { updatePasswordByOld } from '@/web/support/user/api';

View File

@ -12,7 +12,7 @@ import Tabs from '@/components/Tabs';
import UserInfo from './components/Info'; import UserInfo from './components/Info';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { feConfigs } from '@/web/common/system/staticData'; import { feConfigs } from '@/web/common/system/staticData';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import Script from 'next/script'; import Script from 'next/script';
const Promotion = dynamic(() => import('./components/Promotion')); const Promotion = dynamic(() => import('./components/Promotion'));
@ -35,67 +35,64 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo, setUserInfo } = useUserStore(); const { userInfo, setUserInfo } = useUserStore();
const tabList = useMemo( const tabList = [
() => [ {
{ icon: 'meLight',
icon: 'meLight', label: t('user.Personal Information'),
label: t('user.Personal Information'), id: TabEnum.info
id: TabEnum.info },
}, ...(feConfigs?.isPlus
...(feConfigs?.isPlus ? [
? [ {
{ icon: 'billRecordLight',
icon: 'billRecordLight', label: t('user.Usage Record'),
label: t('user.Usage Record'), id: TabEnum.bill
id: TabEnum.bill }
} ]
] : []),
: []), ...(feConfigs?.show_promotion
...(feConfigs?.show_promotion ? [
? [ {
{ icon: 'promotionLight',
icon: 'promotionLight', label: t('user.Promotion Record'),
label: t('user.Promotion Record'), id: TabEnum.promotion
id: TabEnum.promotion }
} ]
] : []),
: []), ...(feConfigs?.show_pay && userInfo?.team.canWrite
...(feConfigs?.show_pay && userInfo?.team.canWrite ? [
? [ {
{ icon: 'payRecordLight',
icon: 'payRecordLight', label: t('user.Recharge Record'),
label: t('user.Recharge Record'), id: TabEnum.pay
id: TabEnum.pay }
} ]
] : []),
: []), ...(userInfo?.team.canWrite
...(userInfo?.team.canWrite ? [
? [ {
{ icon: 'apikey',
icon: 'apikey', label: t('user.apikey.key'),
label: t('user.apikey.key'), id: TabEnum.apikey
id: TabEnum.apikey }
} ]
] : []),
: []), ...(feConfigs.isPlus
...(feConfigs.isPlus ? [
? [ {
{ icon: 'informLight',
icon: 'informLight', label: t('user.Notice'),
label: t('user.Notice'), id: TabEnum.inform
id: TabEnum.inform }
} ]
] : []),
: []),
{ {
icon: 'loginoutLight', icon: 'loginoutLight',
label: t('user.Sign Out'), label: t('user.Sign Out'),
id: TabEnum.loginout id: TabEnum.loginout
} }
], ];
[t, userInfo?.team.canWrite]
);
const { openConfirm, ConfirmModal } = useConfirm({ const { openConfirm, ConfirmModal } = useConfirm({
content: '确认退出登录?' content: '确认退出登录?'

View File

@ -0,0 +1,169 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@/utils/tools';
import { PgClient } from '@fastgpt/service/common/pg';
import {
DatasetDataIndexTypeEnum,
PgDatasetTableName
} from '@fastgpt/global/core/dataset/constant';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { getUserDefaultTeam } from '@fastgpt/service/support/user/team/controller';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
try {
await Promise.allSettled([
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN data_id VARCHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN q DROP NOT NULL;`), // q can null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN a DROP NOT NULL;`), // a can null
PgClient.query(
`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN team_id TYPE VARCHAR(50) USING team_id::VARCHAR(50);`
), // team_id varchar
PgClient.query(
`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN tmb_id TYPE VARCHAR(50) USING tmb_id::VARCHAR(50);`
), // tmb_id varchar
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN team_id SET NOT NULL;`), // team_id not null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN tmb_id SET NOT NULL;`), // tmb_id not null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN dataset_id SET NOT NULL;`), // dataset_id not null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN collection_id SET NOT NULL;`) // collection_id not null
]);
} catch (error) {}
await initPgData();
jsonRes(res, {
data: await init(limit),
message:
'初始化任务已开始,请注意日志进度。可通过 select count(id) from modeldata where data_id is null; 检查是否完全初始化,如果结果为 0 ,则完全初始化。'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
type PgItemType = {
id: string;
q: string;
a: string;
dataset_id: string;
collection_id: string;
team_id: string;
tmb_id: string;
};
async function initPgData() {
const limit = 10;
const { rows } = await PgClient.query<{ user_id: string }>(`
SELECT DISTINCT user_id FROM ${PgDatasetTableName} WHERE team_id='null';
`);
console.log('init pg', rows.length);
let success = 0;
for (let i = 0; i < limit; i++) {
init(i);
}
async function init(index: number): Promise<any> {
const userId = rows[index]?.user_id;
if (!userId) return;
try {
const tmb = await getUserDefaultTeam({ userId });
// update pg
await PgClient.query(
`Update ${PgDatasetTableName} set team_id = '${tmb.teamId}', tmb_id = '${tmb.tmbId}' where user_id = '${userId}' AND team_id='null';`
);
console.log(++success);
init(index + limit);
} catch (error) {
if (error === 'default team not exist') {
return;
}
console.log(error);
await delay(1000);
return init(index);
}
}
}
async function init(limit: number): Promise<any> {
const { rows: idList } = await PgClient.query<{ id: string }>(
`SELECT id FROM ${PgDatasetTableName} WHERE data_id IS NULL`
);
console.log('totalCount', idList.length);
if (idList.length === 0) return;
for (let i = 0; i < limit; i++) {
initData(i);
}
async function initData(index: number): Promise<any> {
const dataId = idList[index]?.id;
if (!dataId) {
console.log('done');
return;
}
// get limit data where data_id is null
const { rows } = await PgClient.query<PgItemType>(
`SELECT id,q,a,dataset_id,collection_id,team_id,tmb_id FROM ${PgDatasetTableName} WHERE id=${dataId};`
);
const data = rows[0];
if (!data) {
console.log('done');
return;
}
let id = '';
try {
// create mongo data and update data_id
const { _id } = await MongoDatasetData.create({
teamId: data.team_id.trim(),
tmbId: data.tmb_id.trim(),
datasetId: data.dataset_id,
collectionId: data.collection_id,
q: data.q,
a: data.a,
indexes: [
{
defaultIndex: !data.a,
type: data.a ? DatasetDataIndexTypeEnum.qa : DatasetDataIndexTypeEnum.chunk,
dataId: data.id,
text: data.q
}
]
});
id = _id;
// update pg data_id
await PgClient.query(
`UPDATE ${PgDatasetTableName} SET data_id='${String(_id)}' WHERE id=${dataId};`
);
console.log(++success);
return initData(index + limit);
} catch (error) {
console.log(error);
console.log(data);
try {
if (id) {
await MongoDatasetData.findByIdAndDelete(id);
}
} catch (error) {}
await delay(500);
return initData(index);
}
}
}

View File

@ -4,21 +4,14 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema'; import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema';
import { import {
createDefaultTeam, createDefaultTeam,
getTeamInfoByTmbId getUserDefaultTeam
} from '@fastgpt/service/support/user/team/controller'; } from '@fastgpt/service/support/user/team/controller';
import { MongoUser } from '@fastgpt/service/support/user/schema'; import { MongoUser } from '@fastgpt/service/support/user/schema';
import { UserModelSchema } from '@fastgpt/global/support/user/type'; import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { delay } from '@/utils/tools'; import { delay } from '@/utils/tools';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import {
DatasetCollectionSchemaType,
DatasetSchemaType,
DatasetTrainingSchemaType
} from '@fastgpt/global/core/dataset/type';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { connectionMongo } from '@fastgpt/service/common/mongo';
import { Types } from 'mongoose';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { PgClient } from '@fastgpt/service/common/pg'; import { PgClient } from '@fastgpt/service/common/pg';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
@ -30,6 +23,7 @@ import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { POST } from '@fastgpt/service/common/api/plusRequest'; import { POST } from '@fastgpt/service/common/api/plusRequest';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getGFSCollection } from '@fastgpt/service/common/file/gridfs/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@ -182,7 +176,7 @@ async function initMongoTeamId(limit: number) {
async function init(userId: string): Promise<any> { async function init(userId: string): Promise<any> {
try { try {
const tmb = await getTeamInfoByTmbId({ userId }); const tmb = await getUserDefaultTeam({ userId });
await schema.updateMany( await schema.updateMany(
{ {
@ -225,7 +219,7 @@ async function initDatasetAndApp() {
} }
async function initCollectionFileTeam(limit: number) { async function initCollectionFileTeam(limit: number) {
/* init user default Team */ /* init user default Team */
const DatasetFile = connectionMongo.connection.db.collection(`dataset.files`); const DatasetFile = getGFSCollection('dataset');
const matchWhere = { const matchWhere = {
$or: [{ 'metadata.teamId': { $exists: false } }, { 'metadata.teamId': null }] $or: [{ 'metadata.teamId': { $exists: false } }, { 'metadata.teamId': null }]
}; };
@ -264,7 +258,7 @@ async function initCollectionFileTeam(limit: number) {
async function init(userId: string): Promise<any> { async function init(userId: string): Promise<any> {
try { try {
const tmb = await getTeamInfoByTmbId({ const tmb = await getUserDefaultTeam({
userId userId
}); });
@ -295,8 +289,8 @@ async function initPgData() {
// add column // add column
try { try {
await Promise.all([ await Promise.all([
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN team_id CHAR(50);`), PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN team_id VARCHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN tmb_id CHAR(50);`), PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN tmb_id VARCHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN user_id DROP NOT NULL;`) PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN user_id DROP NOT NULL;`)
]); ]);
} catch (error) { } catch (error) {
@ -316,7 +310,7 @@ async function initPgData() {
const userId = rows[index]?.user_id; const userId = rows[index]?.user_id;
if (!userId) return; if (!userId) return;
try { try {
const tmb = await getTeamInfoByTmbId({ userId }); const tmb = await getUserDefaultTeam({ userId });
// update pg // update pg
await PgClient.query( await PgClient.query(
`Update ${PgDatasetTableName} set team_id = '${tmb.teamId}', tmb_id = '${tmb.tmbId}' where user_id = '${userId}' AND team_id IS NULL;` `Update ${PgDatasetTableName} set team_id = '${tmb.teamId}', tmb_id = '${tmb.tmbId}' where user_id = '${userId}' AND team_id IS NULL;`

View File

@ -80,8 +80,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
try { try {
await connectToDatabase(); await connectToDatabase();
const { userId, teamId, tmbId } = await authCert({ req, authToken: true }); const { userId, teamId, tmbId } = await authCert({ req, authToken: true });
console.log(req.body);
const { files, bucketName, metadata } = await upload.doUpload(req, res); const { files, bucketName, metadata } = await upload.doUpload(req, res);
const upLoadResults = await Promise.all( const upLoadResults = await Promise.all(

View File

@ -4,15 +4,16 @@ import { connectToDatabase } from '@/service/mongo';
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d'; import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
import { pushQuestionGuideBill } from '@/service/support/wallet/bill/push'; import { pushQuestionGuideBill } from '@/service/support/wallet/bill/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide'; import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCertAndShareId } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
await connectToDatabase(); await connectToDatabase();
const { messages } = req.body as CreateQuestionGuideParams; const { messages, shareId } = req.body as CreateQuestionGuideParams;
const { tmbId, teamId } = await authCert({ const { tmbId, teamId } = await authCertAndShareId({
req, req,
authToken: true authToken: true,
shareId
}); });
const qgModel = global.qgModels[0]; const qgModel = global.qgModels[0];

View File

@ -3,10 +3,11 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { getVectorModel } from '@/service/core/ai/model'; import { getVectorModel } from '@/service/core/ai/model';
import type { DatasetItemType } from '@/types/core/dataset'; import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
/* get all dataset by teamId or tmbId */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
await connectToDatabase(); await connectToDatabase();
@ -20,7 +21,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const data = datasets.map((item) => ({ const data = datasets.map((item) => ({
...item.toJSON(), ...item.toJSON(),
tags: item.tags.join(' '),
vectorModel: getVectorModel(item.vectorModel), vectorModel: getVectorModel(item.vectorModel),
canWrite: String(item.tmbId) === tmbId, canWrite: String(item.tmbId) === tmbId,
isOwner: teamOwner || String(item.tmbId) === tmbId isOwner: teamOwner || String(item.tmbId) === tmbId

View File

@ -30,15 +30,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const collections = await findCollectionAndChild(collectionId, '_id metadata'); const collections = await findCollectionAndChild(collectionId, '_id metadata');
const delIdList = collections.map((item) => item._id); const delIdList = collections.map((item) => item._id);
// delete pg data
await delDataByCollectionId({ collectionIds: delIdList });
// delete training data // delete training data
await MongoDatasetTraining.deleteMany({ await MongoDatasetTraining.deleteMany({
datasetCollectionId: { $in: delIdList }, collectionId: { $in: delIdList },
teamId teamId
}); });
// delete pg data
await delDataByCollectionId({ collectionIds: delIdList });
// delete file // delete file
await Promise.all( await Promise.all(
collections.map((collection) => { collections.map((collection) => {

View File

@ -27,8 +27,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
jsonRes<DatasetCollectionItemType>(res, { jsonRes<DatasetCollectionItemType>(res, {
data: { data: {
...collection, ...collection,
datasetId: collection.datasetId._id, canWrite,
canWrite sourceName: collection?.name,
sourceId: collection?.metadata?.fileId || collection?.metadata?.rawLink
} }
}); });
} catch (err) { } catch (err) {

View File

@ -3,15 +3,15 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema'; import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema';
import { Types } from '@fastgpt/service/common/mongo'; import { Types } from '@fastgpt/service/common/mongo';
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/response'; import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq'; import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
import { PagingData } from '@/types'; import { PagingData } from '@/types';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { countCollectionData } from '@/service/core/dataset/data/utils';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant'; import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { startQueue } from '@/service/utils/tools'; import { startQueue } from '@/service/utils/tools';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { getTeamInfoByTmbId } from '@fastgpt/service/support/user/team/controller'; import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/schema';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
@ -30,7 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// auth dataset and get my role // auth dataset and get my role
const { tmbId } = await authDataset({ req, authToken: true, datasetId, per: 'r' }); const { tmbId } = await authDataset({ req, authToken: true, datasetId, per: 'r' });
const { canWrite } = await getTeamInfoByTmbId({ tmbId }); const { canWrite } = await authUserRole({ req, authToken: true });
const match = { const match = {
datasetId: new Types.ObjectId(datasetId), datasetId: new Types.ObjectId(datasetId),
@ -59,7 +59,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...item, ...item,
dataAmount: 0, dataAmount: 0,
trainingAmount: 0, trainingAmount: 0,
canWrite // admin or owner can write canWrite // admin or team owner can write
})) }))
), ),
total: await MongoDatasetCollection.countDocuments(match) total: await MongoDatasetCollection.countDocuments(match)
@ -67,51 +67,75 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}); });
} }
const collections: DatasetCollectionsListItemType[] = await MongoDatasetCollection.aggregate([ const [collections, total]: [DatasetCollectionsListItemType[], number] = await Promise.all([
{ MongoDatasetCollection.aggregate([
$match: match {
}, $match: match
{ },
$lookup: { {
from: DatasetTrainingCollectionName, $lookup: {
localField: '_id', from: DatasetTrainingCollectionName,
foreignField: 'datasetCollectionId', let: { id: '$_id' },
as: 'trainings_amount' pipeline: [
{
$match: {
$expr: {
$eq: ['$collectionId', '$$id']
}
}
},
{ $project: { _id: 1 } }
],
as: 'trainings'
}
},
{
$lookup: {
from: DatasetDataCollectionName,
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: {
$eq: ['$collectionId', '$$id']
}
}
},
{ $project: { _id: 1 } }
],
as: 'datas'
}
},
// 统计子集合的数量和子训练的数量
{
$project: {
_id: 1,
parentId: 1,
tmbId: 1,
name: 1,
type: 1,
updateTime: 1,
trainingAmount: { $size: '$trainings' },
dataAmount: { $size: '$datas' },
metadata: 1
}
},
{
$sort: { updateTime: -1 }
},
{
$skip: (pageNum - 1) * pageSize
},
{
$limit: pageSize
} }
}, ]),
// 统计子集合的数量和子训练的数量 MongoDatasetCollection.countDocuments(match)
{
$project: {
_id: 1,
parentId: 1,
tmbId: 1,
name: 1,
type: 1,
updateTime: 1,
trainingAmount: { $size: '$trainings_amount' },
metadata: 1
}
},
{
$sort: { updateTime: -1 }
},
{
$skip: (pageNum - 1) * pageSize
},
{
$limit: pageSize
}
]); ]);
const counts = await countCollectionData({
collectionIds: collections.map((item) => item._id),
datasetId
});
const data = await Promise.all( const data = await Promise.all(
collections.map(async (item, i) => ({ collections.map(async (item, i) => ({
...item, ...item,
dataAmount: item.type === DatasetCollectionTypeEnum.folder ? undefined : counts[i],
canWrite: String(item.tmbId) === tmbId || canWrite canWrite: String(item.tmbId) === tmbId || canWrite
})) }))
); );
@ -126,7 +150,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
pageNum, pageNum,
pageSize, pageSize,
data, data,
total: await MongoDatasetCollection.countDocuments(match) total
} }
}); });
} catch (err) { } catch (err) {

View File

@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import type { DatasetPathItemType } from '@/types/core/dataset'; import type { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type.d';
import { getDatasetCollectionPaths } from '@fastgpt/service/core/dataset/collection/utils'; import { getDatasetCollectionPaths } from '@fastgpt/service/core/dataset/collection/utils';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
@ -22,7 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
parentId parentId
}); });
jsonRes<DatasetPathItemType[]>(res, { jsonRes<ParentTreePathItemType[]>(res, {
data: paths data: paths
}); });
} catch (err) { } catch (err) {

View File

@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import type { CreateDatasetParams } from '@/global/core/api/datasetReq.d'; import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
import { createDefaultCollection } from './collection/create'; import { createDefaultCollection } from './collection/create';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';

View File

@ -1,10 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { PgClient } from '@fastgpt/service/common/pg';
import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authDatasetData } from '@/service/support/permission/auth/dataset'; import { authDatasetData } from '@/service/support/permission/auth/dataset';
import { deleteDataByDataId } from '@/service/core/dataset/data/controller';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
@ -20,11 +19,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// 凭证校验 // 凭证校验
await authDatasetData({ req, authToken: true, dataId, per: 'w' }); await authDatasetData({ req, authToken: true, dataId, per: 'w' });
await PgClient.delete(PgDatasetTableName, { await deleteDataByDataId(dataId);
where: [['id', dataId]]
});
jsonRes(res); jsonRes(res, {
data: 'success'
});
} catch (err) { } catch (err) {
console.log(err); console.log(err);
jsonRes(res, { jsonRes(res, {

View File

@ -13,12 +13,9 @@ export type Response = {
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
await connectToDatabase(); await connectToDatabase();
let { dataId } = req.query as { const { dataId } = req.query as {
dataId: string; dataId: string;
}; };
if (!dataId) {
throw new Error('缺少参数');
}
// 凭证校验 // 凭证校验
const { datasetData } = await authDatasetData({ req, authToken: true, dataId, per: 'r' }); const { datasetData } = await authDatasetData({ req, authToken: true, dataId, per: 'r' });

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