Compare commits
14 Commits
v4.8.22
...
v4.8.23-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb0eb49196 | ||
|
|
27ebd2e8cf | ||
|
|
81a06718d8 | ||
|
|
3c382d1240 | ||
|
|
747bb303ec | ||
|
|
cf9c8e9f6a | ||
|
|
5d5bee9e41 | ||
|
|
4f0dd96699 | ||
|
|
fb6dbaf2d6 | ||
|
|
ffc1520f4c | ||
|
|
255764400f | ||
|
|
3bfe802c48 | ||
|
|
2bf17dbb87 | ||
|
|
8d766372fe |
@@ -23,7 +23,7 @@ weight: 744
|
||||
|
||||
{{% alert icon=" " context="info" %}}
|
||||
- [SiliconCloud(硅基流动)](https://cloud.siliconflow.cn/i/TR9Ym0c4): 提供开源模型调用的平台。
|
||||
- [Sealos AIProxy](https://hzh.sealos.run/?openapp=system-aiproxy): 提供国内各家模型代理,无需逐一申请 api。
|
||||
- [Sealos AIProxy](https://cloud.sealos.run/?uid=fnWRt09fZP&openapp=system-aiproxy): 提供国内各家模型代理,无需逐一申请 api。
|
||||
{{% /alert %}}
|
||||
|
||||
在 OneAPI 配置好模型后,你就可以打开 FastGPT 页面,启用对应模型了。
|
||||
@@ -467,4 +467,4 @@ OneAPI 的语言识别接口,无法正确的识别其他模型(会始终识
|
||||
"charsPointsPrice": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.22(进行中)'
|
||||
title: 'V4.8.22(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.22 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -13,8 +13,8 @@ weight: 802
|
||||
|
||||
### 2. 更新镜像:
|
||||
|
||||
- 更新 fastgpt 镜像 tag: v4.8.22-alpha
|
||||
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.22-alpha
|
||||
- 更新 fastgpt 镜像 tag: v4.8.22
|
||||
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.22
|
||||
- Sandbox 镜像无需更新
|
||||
|
||||
### 3. 运行升级脚本
|
||||
|
||||
30
docSite/content/zh-cn/docs/development/upgrading/4823.md
Normal file
30
docSite/content/zh-cn/docs/development/upgrading/4823.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: 'V4.8.23(进行中)'
|
||||
description: 'FastGPT V4.8.23 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 802
|
||||
---
|
||||
|
||||
|
||||
## 🚀 新增内容
|
||||
|
||||
1. 增加默认“知识库文本理解模型”配置
|
||||
2. AI proxy V1版,可替换 OneAPI使用,同时提供完整模型调用日志,便于排查问题。
|
||||
|
||||
## ⚙️ 优化
|
||||
|
||||
1. 模型配置表单,增加必填项校验。
|
||||
2. 集合列表数据统计方式,提高大数据量统计性能。
|
||||
3. 优化数学公式,转义 Latex 格式成 Markdown 格式。
|
||||
4. 解析文档图片,图片太大时,自动忽略。
|
||||
5. 时间选择器,当天开始时间自动设0,结束设置设 23:59:59,避免 UI 与实际逻辑偏差。
|
||||
6. 升级 mongoose 库版本依赖。
|
||||
|
||||
## 🐛 修复
|
||||
|
||||
1. 标签过滤时,子文件夹未成功过滤。
|
||||
2. 暂时移除 md 阅读优化,避免链接分割错误。
|
||||
3. 离开团队时,未刷新成员列表。
|
||||
4. PPTX 编码错误,导致解析失败。
|
||||
@@ -7,11 +7,11 @@ toc: true
|
||||
weight: 102
|
||||
---
|
||||
|
||||
更多使用技巧,[查看视屏教程](https://www.bilibili.com/video/BV1sH4y1T7s9)
|
||||
更多使用技巧,[查看视频教程](https://www.bilibili.com/video/BV1sH4y1T7s9)
|
||||
|
||||
## 知识库
|
||||
|
||||
开始前,请准备一份测试电子文档,WORD,PDF,TXT,excel,markdown 都可以,比如公司休假制度,不涉密的销售说辞,产品知识等等。
|
||||
开始前,请准备一份测试电子文档,WORD、PDF、TXT、excel、markdown 都可以,比如公司休假制度、不涉密的销售说辞、产品知识等等。
|
||||
|
||||
这里使用 FastGPT 中文 README 文件为例。
|
||||
|
||||
@@ -31,7 +31,7 @@ weight: 102
|
||||
|
||||

|
||||
|
||||
点击上传后我们需要等待数据处理完成,等到我们上传的文件状态为可用。
|
||||
点击上传后我们需要等待数据处理完成,直到我们上传的文件状态为可用。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -114,15 +114,15 @@ services:
|
||||
# fastgpt
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.21-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.21-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.22 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.22 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.21-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.21-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.22 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.22 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
@@ -133,7 +133,7 @@ services:
|
||||
- sandbox
|
||||
restart: always
|
||||
environment:
|
||||
# 前端访问地址: http://localhost:3000
|
||||
# 前端外部可访问的地址,用于自动补全文件资源路径。例如 https:fastgpt.cn,不能填 localhost。这个值可以不填,不填则发给模型的图片会是一个相对路径,而不是全路径,模型可能伪造Host。
|
||||
- FE_DOMAIN=
|
||||
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
||||
- DEFAULT_ROOT_PSW=1234
|
||||
|
||||
@@ -72,15 +72,15 @@ services:
|
||||
# fastgpt
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.21-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.21-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.22 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.22 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.21-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.21-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.22 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.22 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
@@ -91,7 +91,7 @@ services:
|
||||
- sandbox
|
||||
restart: always
|
||||
environment:
|
||||
# 前端访问地址: http://localhost:3000
|
||||
# 前端外部可访问的地址,用于自动补全文件资源路径。例如 https:fastgpt.cn,不能填 localhost。这个值可以不填,不填则发给模型的图片会是一个相对路径,而不是全路径,模型可能伪造Host。
|
||||
- FE_DOMAIN=
|
||||
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
||||
- DEFAULT_ROOT_PSW=1234
|
||||
|
||||
@@ -53,15 +53,15 @@ services:
|
||||
wait $$!
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.21-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.21-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.22 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.22 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.21-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.21-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.22 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.22 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
@@ -71,7 +71,7 @@ services:
|
||||
- sandbox
|
||||
restart: always
|
||||
environment:
|
||||
# 前端访问地址: http://localhost:3000
|
||||
# 前端外部可访问的地址,用于自动补全文件资源路径。例如 https:fastgpt.cn,不能填 localhost。这个值可以不填,不填则发给模型的图片会是一个相对路径,而不是全路径,模型可能伪造Host。
|
||||
- FE_DOMAIN=
|
||||
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
||||
- DEFAULT_ROOT_PSW=1234
|
||||
|
||||
@@ -7,12 +7,14 @@ import { i18nT } from '../../../web/i18n/utils';
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export const formatTime2YMDHMW = (time?: Date) => dayjs(time).format('YYYY-MM-DD HH:mm:ss dddd');
|
||||
export const formatTime2YMDHMS = (time?: Date) =>
|
||||
export const formatTime2YMDHMW = (time?: Date | number) =>
|
||||
dayjs(time).format('YYYY-MM-DD HH:mm:ss dddd');
|
||||
export const formatTime2YMDHMS = (time?: Date | number) =>
|
||||
time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '';
|
||||
export const formatTime2YMDHM = (time?: Date) =>
|
||||
export const formatTime2YMDHM = (time?: Date | number) =>
|
||||
time ? dayjs(time).format('YYYY-MM-DD HH:mm') : '';
|
||||
export const formatTime2YMD = (time?: Date) => (time ? dayjs(time).format('YYYY-MM-DD') : '');
|
||||
export const formatTime2YMD = (time?: Date | number) =>
|
||||
time ? dayjs(time).format('YYYY-MM-DD') : '';
|
||||
export const formatTime2HM = (time: Date = new Date()) => dayjs(time).format('HH:mm');
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,7 @@ export type FastGPTConfigFileType = {
|
||||
};
|
||||
|
||||
export type FastGPTFeConfigsType = {
|
||||
show_workorder?: boolean;
|
||||
show_emptyChat?: boolean;
|
||||
register_method?: ['email' | 'phone' | 'sync'];
|
||||
login_method?: ['email' | 'phone']; // Attention: login method is diffrent with oauth
|
||||
@@ -53,6 +54,7 @@ export type FastGPTFeConfigsType = {
|
||||
show_promotion?: boolean;
|
||||
show_team_chat?: boolean;
|
||||
show_compliance_copywriting?: boolean;
|
||||
show_aiproxy?: boolean;
|
||||
concatMd?: string;
|
||||
|
||||
docUrl?: string;
|
||||
|
||||
2
packages/global/core/ai/model.d.ts
vendored
2
packages/global/core/ai/model.d.ts
vendored
@@ -17,6 +17,8 @@ type BaseModelItemType = {
|
||||
isActive?: boolean;
|
||||
isCustom?: boolean;
|
||||
isDefault?: boolean;
|
||||
isDefaultDatasetTextModel?: boolean;
|
||||
isDefaultDatasetImageModel?: boolean;
|
||||
|
||||
// If has requestUrl, it will request the model directly
|
||||
requestUrl?: string;
|
||||
|
||||
1
packages/global/core/dataset/type.d.ts
vendored
1
packages/global/core/dataset/type.d.ts
vendored
@@ -192,6 +192,7 @@ export type DatasetCollectionItemType = CollectionWithDatasetType & {
|
||||
sourceId?: string;
|
||||
file?: DatasetFileSchema;
|
||||
permission: DatasetPermission;
|
||||
indexAmount: number;
|
||||
};
|
||||
|
||||
/* ================= data ===================== */
|
||||
|
||||
1
packages/global/support/user/type.d.ts
vendored
1
packages/global/support/user/type.d.ts
vendored
@@ -27,7 +27,6 @@ export type UserType = {
|
||||
timezone: string;
|
||||
promotionRate: UserModelSchema['promotionRate'];
|
||||
team: TeamTmbItemType;
|
||||
standardInfo?: standardInfoType;
|
||||
notificationAccount?: string;
|
||||
permission: TeamPermission;
|
||||
contact?: string;
|
||||
|
||||
@@ -18,10 +18,10 @@ export function getGFSCollection(bucket: `${BucketNameEnum}`) {
|
||||
MongoDatasetFileSchema;
|
||||
MongoChatFileSchema;
|
||||
|
||||
return connectionMongo.connection.db.collection(`${bucket}.files`);
|
||||
return connectionMongo.connection.db!.collection(`${bucket}.files`);
|
||||
}
|
||||
export function getGridBucket(bucket: `${BucketNameEnum}`) {
|
||||
return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db, {
|
||||
return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db!, {
|
||||
bucketName: bucket,
|
||||
// @ts-ignore
|
||||
readPreference: ReadPreference.SECONDARY_PREFERRED // Read from secondary node
|
||||
|
||||
@@ -111,15 +111,21 @@ export const readRawContentByFileBuffer = async ({
|
||||
// markdown data format
|
||||
if (imageList) {
|
||||
await batchRun(imageList, async (item) => {
|
||||
const src = await uploadMongoImg({
|
||||
base64Img: `data:${item.mime};base64,${item.base64}`,
|
||||
teamId,
|
||||
// expiredTime: addHours(new Date(), 1),
|
||||
metadata: {
|
||||
...metadata,
|
||||
mime: item.mime
|
||||
const src = await (async () => {
|
||||
try {
|
||||
return await uploadMongoImg({
|
||||
base64Img: `data:${item.mime};base64,${item.base64}`,
|
||||
teamId,
|
||||
// expiredTime: addHours(new Date(), 1),
|
||||
metadata: {
|
||||
...metadata,
|
||||
mime: item.mime
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
})();
|
||||
rawText = rawText.replace(item.uuid, src);
|
||||
if (formatText) {
|
||||
formatText = formatText.replace(item.uuid, src);
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export const FastGPTProUrl = process.env.PRO_URL ? `${process.env.PRO_URL}/api` : '';
|
||||
export const isFastGPTMainService = !!process.env.PRO_URL;
|
||||
// @ts-ignore
|
||||
export const isFastGPTProService = () => !!global.systemConfig;
|
||||
|
||||
@@ -21,6 +21,7 @@ export const recallFromVectorStore = Vector.embRecall;
|
||||
export const getVectorDataByTime = Vector.getVectorDataByTime;
|
||||
export const getVectorCountByTeamId = Vector.getVectorCountByTeamId;
|
||||
export const getVectorCountByDatasetId = Vector.getVectorCountByDatasetId;
|
||||
export const getVectorCountByCollectionId = Vector.getVectorCountByCollectionId;
|
||||
|
||||
export const insertDatasetDataVector = async ({
|
||||
model,
|
||||
|
||||
@@ -321,6 +321,23 @@ export class MilvusCtrl {
|
||||
|
||||
return total;
|
||||
};
|
||||
getVectorCountByCollectionId = async (
|
||||
teamId: string,
|
||||
datasetId: string,
|
||||
collectionId: string
|
||||
) => {
|
||||
const client = await this.getClient();
|
||||
|
||||
const result = await client.query({
|
||||
collection_name: DatasetVectorTableName,
|
||||
output_fields: ['count(*)'],
|
||||
filter: `(teamId == "${String(teamId)}") and (datasetId == "${String(datasetId)}") and (collectionId == "${String(collectionId)}")`
|
||||
});
|
||||
|
||||
const total = result.data?.[0]?.['count(*)'] as number;
|
||||
|
||||
return total;
|
||||
};
|
||||
|
||||
getVectorDataByTime = async (start: Date, end: Date) => {
|
||||
const client = await this.getClient();
|
||||
|
||||
@@ -240,6 +240,23 @@ export class PgVectorCtrl {
|
||||
where: [['team_id', String(teamId)], 'and', ['dataset_id', String(datasetId)]]
|
||||
});
|
||||
|
||||
return total;
|
||||
};
|
||||
getVectorCountByCollectionId = async (
|
||||
teamId: string,
|
||||
datasetId: string,
|
||||
collectionId: string
|
||||
) => {
|
||||
const total = await PgClient.count(DatasetVectorTableName, {
|
||||
where: [
|
||||
['team_id', String(teamId)],
|
||||
'and',
|
||||
['dataset_id', String(datasetId)],
|
||||
'and',
|
||||
['collection_id', String(collectionId)]
|
||||
]
|
||||
});
|
||||
|
||||
return total;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,14 +11,17 @@ import { i18nT } from '../../../web/i18n/utils';
|
||||
import { OpenaiAccountType } from '@fastgpt/global/support/user/team/type';
|
||||
import { getLLMModel } from './model';
|
||||
|
||||
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
||||
const aiProxyBaseUrl = process.env.AIPROXY_API_ENDPOINT
|
||||
? `${process.env.AIPROXY_API_ENDPOINT}/v1`
|
||||
: undefined;
|
||||
const openaiBaseUrl = aiProxyBaseUrl || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
||||
const openaiBaseKey = process.env.AIPROXY_API_TOKEN || process.env.CHAT_API_KEY || '';
|
||||
|
||||
export const getAIApi = (props?: { userKey?: OpenaiAccountType; timeout?: number }) => {
|
||||
const { userKey, timeout } = props || {};
|
||||
|
||||
const baseUrl = userKey?.baseUrl || global?.systemEnv?.oneapiUrl || openaiBaseUrl;
|
||||
const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || process.env.CHAT_API_KEY || '';
|
||||
|
||||
const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || openaiBaseKey;
|
||||
return new OpenAI({
|
||||
baseURL: baseUrl,
|
||||
apiKey,
|
||||
@@ -72,6 +75,7 @@ export const createChatCompletion = async ({
|
||||
userKey,
|
||||
timeout: formatTimeout
|
||||
});
|
||||
|
||||
const response = await ai.chat.completions.create(body, {
|
||||
...options,
|
||||
...(modelConstantsData.requestUrl ? { path: modelConstantsData.requestUrl } : {}),
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
{
|
||||
"provider": "Claude",
|
||||
"list": [
|
||||
{
|
||||
"model": "claude-3-7-sonnet-20250219",
|
||||
"name": "claude-3-7-sonnet-20250219",
|
||||
"maxContext": 200000,
|
||||
"maxResponse": 8000,
|
||||
"quoteMaxToken": 100000,
|
||||
"maxTemperature": 1,
|
||||
"showTopP": true,
|
||||
"showStopSign": true,
|
||||
"vision": true,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"defaultSystemChatPrompt": "",
|
||||
"datasetProcess": true,
|
||||
"usedInClassify": true,
|
||||
"customCQPrompt": "",
|
||||
"usedInExtractFields": true,
|
||||
"usedInQueryExtension": true,
|
||||
"customExtractPrompt": "",
|
||||
"usedInToolCall": true,
|
||||
"defaultConfig": {},
|
||||
"fieldMap": {},
|
||||
"type": "llm"
|
||||
},
|
||||
{
|
||||
"model": "claude-3-5-haiku-20241022",
|
||||
"name": "claude-3-5-haiku-20241022",
|
||||
@@ -10,7 +34,7 @@
|
||||
"maxTemperature": 1,
|
||||
"showTopP": true,
|
||||
"showStopSign": true,
|
||||
"vision": false,
|
||||
"vision": true,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
"defaultSystemChatPrompt": "",
|
||||
@@ -98,4 +122,4 @@
|
||||
"type": "llm"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,29 @@
|
||||
{
|
||||
"provider": "Grok",
|
||||
"list": []
|
||||
}
|
||||
"list": [
|
||||
{
|
||||
"model": "grok-3",
|
||||
"name": "grok-3",
|
||||
"maxContext": 128000,
|
||||
"maxResponse": 8000,
|
||||
"quoteMaxToken": 128000,
|
||||
"maxTemperature": 1,
|
||||
"showTopP": true,
|
||||
"showStopSign": true,
|
||||
"vision": false,
|
||||
"toolChoice": false,
|
||||
"functionCall": false,
|
||||
"defaultSystemChatPrompt": "",
|
||||
"datasetProcess": true,
|
||||
"usedInClassify": true,
|
||||
"customCQPrompt": "",
|
||||
"usedInExtractFields": true,
|
||||
"usedInQueryExtension": true,
|
||||
"customExtractPrompt": "",
|
||||
"usedInToolCall": true,
|
||||
"defaultConfig": {},
|
||||
"fieldMap": {},
|
||||
"type": "llm"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ export const loadSystemModels = async (init = false) => {
|
||||
if (model.isDefault) {
|
||||
global.systemDefaultModel.llm = model;
|
||||
}
|
||||
if (model.isDefaultDatasetTextModel) {
|
||||
global.systemDefaultModel.datasetTextLLM = model;
|
||||
}
|
||||
if (model.isDefaultDatasetImageModel) {
|
||||
global.systemDefaultModel.datasetImageLLM = model;
|
||||
}
|
||||
} else if (model.type === ModelTypeEnum.embedding) {
|
||||
global.embeddingModelMap.set(model.model, model);
|
||||
global.embeddingModelMap.set(model.name, model);
|
||||
@@ -134,6 +140,16 @@ export const loadSystemModels = async (init = false) => {
|
||||
if (!global.systemDefaultModel.llm) {
|
||||
global.systemDefaultModel.llm = Array.from(global.llmModelMap.values())[0];
|
||||
}
|
||||
if (!global.systemDefaultModel.datasetTextLLM) {
|
||||
global.systemDefaultModel.datasetTextLLM = Array.from(global.llmModelMap.values()).find(
|
||||
(item) => item.datasetProcess
|
||||
);
|
||||
}
|
||||
if (!global.systemDefaultModel.datasetImageLLM) {
|
||||
global.systemDefaultModel.datasetImageLLM = Array.from(global.llmModelMap.values()).find(
|
||||
(item) => item.vision
|
||||
);
|
||||
}
|
||||
if (!global.systemDefaultModel.embedding) {
|
||||
global.systemDefaultModel.embedding = Array.from(global.embeddingModelMap.values())[0];
|
||||
}
|
||||
|
||||
3
packages/service/core/ai/type.d.ts
vendored
3
packages/service/core/ai/type.d.ts
vendored
@@ -22,6 +22,9 @@ export type SystemModelItemType =
|
||||
|
||||
export type SystemDefaultModelType = {
|
||||
[ModelTypeEnum.llm]?: LLMModelItemType;
|
||||
datasetTextLLM?: LLMModelItemType;
|
||||
datasetImageLLM?: LLMModelItemType;
|
||||
|
||||
[ModelTypeEnum.embedding]?: EmbeddingModelItemType;
|
||||
[ModelTypeEnum.tts]?: TTSModelType;
|
||||
[ModelTypeEnum.stt]?: STTModelType;
|
||||
|
||||
@@ -200,6 +200,7 @@ export async function searchDatasetData(
|
||||
forbidCollectionIdList: collections.map((item) => String(item._id))
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
Collection metadata filter
|
||||
标签过滤:
|
||||
@@ -207,6 +208,63 @@ export async function searchDatasetData(
|
||||
2. and 标签和 null 不能共存,否则返回空数组
|
||||
*/
|
||||
const filterCollectionByMetadata = async (): Promise<string[] | undefined> => {
|
||||
const getAllCollectionIds = async ({
|
||||
parentCollectionIds
|
||||
}: {
|
||||
parentCollectionIds?: string[];
|
||||
}): Promise<string[] | undefined> => {
|
||||
if (!parentCollectionIds) return;
|
||||
if (parentCollectionIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const collections = await MongoDatasetCollection.find(
|
||||
{
|
||||
teamId,
|
||||
datasetId: { $in: datasetIds },
|
||||
_id: { $in: parentCollectionIds }
|
||||
},
|
||||
'_id type',
|
||||
{
|
||||
...readFromSecondary
|
||||
}
|
||||
).lean();
|
||||
|
||||
const resultIds = new Set<string>();
|
||||
collections.forEach((item) => {
|
||||
if (item.type !== 'folder') {
|
||||
resultIds.add(String(item._id));
|
||||
}
|
||||
});
|
||||
|
||||
const folderIds = collections
|
||||
.filter((item) => item.type === 'folder')
|
||||
.map((item) => String(item._id));
|
||||
|
||||
// Get all child collection ids
|
||||
if (folderIds.length) {
|
||||
const childCollections = await MongoDatasetCollection.find(
|
||||
{
|
||||
teamId,
|
||||
datasetId: { $in: datasetIds },
|
||||
parentId: { $in: folderIds }
|
||||
},
|
||||
'_id type',
|
||||
{
|
||||
...readFromSecondary
|
||||
}
|
||||
).lean();
|
||||
|
||||
const childIds = await getAllCollectionIds({
|
||||
parentCollectionIds: childCollections.map((item) => String(item._id))
|
||||
});
|
||||
|
||||
childIds?.forEach((id) => resultIds.add(id));
|
||||
}
|
||||
|
||||
return Array.from(resultIds);
|
||||
};
|
||||
|
||||
if (!collectionFilterMatch || !global.feConfigs.isPlus) return;
|
||||
|
||||
let tagCollectionIdList: string[] | undefined = undefined;
|
||||
@@ -326,13 +384,19 @@ export async function searchDatasetData(
|
||||
}
|
||||
|
||||
// Concat tag and time
|
||||
if (tagCollectionIdList && createTimeCollectionIdList) {
|
||||
return tagCollectionIdList.filter((id) => createTimeCollectionIdList!.includes(id));
|
||||
} else if (tagCollectionIdList) {
|
||||
return tagCollectionIdList;
|
||||
} else if (createTimeCollectionIdList) {
|
||||
return createTimeCollectionIdList;
|
||||
}
|
||||
const collectionIds = (() => {
|
||||
if (tagCollectionIdList && createTimeCollectionIdList) {
|
||||
return tagCollectionIdList.filter((id) =>
|
||||
(createTimeCollectionIdList as string[]).includes(id)
|
||||
);
|
||||
}
|
||||
|
||||
return tagCollectionIdList || createTimeCollectionIdList;
|
||||
})();
|
||||
|
||||
return await getAllCollectionIds({
|
||||
parentCollectionIds: collectionIds
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
const embeddingRecall = async ({
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mammoth": "^1.6.0",
|
||||
"mongoose": "^7.0.2",
|
||||
"mongoose": "^8.10.1",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"next": "14.2.5",
|
||||
"nextjs-cors": "^2.2.0",
|
||||
|
||||
@@ -178,7 +178,7 @@ export const getClbsAndGroupsWithInfo = async ({
|
||||
]);
|
||||
|
||||
export const delResourcePermissionById = (id: string) => {
|
||||
return MongoResourcePermission.findByIdAndRemove(id);
|
||||
return MongoResourcePermission.findByIdAndDelete(id);
|
||||
};
|
||||
export const delResourcePermission = ({
|
||||
session,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AppDetailType } from '@fastgpt/global/core/app/type';
|
||||
import { OutlinkAppType, OutLinkSchema } from '@fastgpt/global/support/outLink/type';
|
||||
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { MongoOutLink } from '../../outLink/schema';
|
||||
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
|
||||
@@ -54,15 +54,11 @@ export async function authOutLinkCrud({
|
||||
}
|
||||
|
||||
/* outLink exist and it app exist */
|
||||
export async function authOutLinkValid<T extends OutlinkAppType = undefined>({
|
||||
shareId
|
||||
}: {
|
||||
shareId?: string;
|
||||
}) {
|
||||
export async function authOutLinkValid({ shareId }: { shareId?: string }) {
|
||||
if (!shareId) {
|
||||
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
|
||||
}
|
||||
const outLinkConfig = (await MongoOutLink.findOne({ shareId }).lean()) as OutLinkSchema<T>;
|
||||
const outLinkConfig = await MongoOutLink.findOne({ shareId }).lean();
|
||||
|
||||
if (!outLinkConfig) {
|
||||
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
|
||||
|
||||
@@ -64,7 +64,7 @@ export const checkTeamDatasetLimit = async (teamId: string) => {
|
||||
export const checkTeamAppLimit = async (teamId: string, amount = 1) => {
|
||||
const [{ standardConstants }, appCount] = await Promise.all([
|
||||
getTeamStandPlan({ teamId }),
|
||||
MongoApp.count({
|
||||
MongoApp.countDocuments({
|
||||
teamId,
|
||||
type: { $in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin] }
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/use
|
||||
import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupSchema';
|
||||
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { getAIApi, openaiBaseUrl } from '../../../core/ai/config';
|
||||
import { getAIApi } from '../../../core/ai/config';
|
||||
import { createRootOrg } from '../../permission/org/controllers';
|
||||
import { refreshSourceAvatar } from '../../../common/file/image/controller';
|
||||
|
||||
@@ -152,7 +152,7 @@ export async function updateTeam({
|
||||
// auth openai key
|
||||
if (openaiAccount?.key) {
|
||||
console.log('auth user openai key', openaiAccount?.key);
|
||||
const baseUrl = openaiAccount?.baseUrl || openaiBaseUrl;
|
||||
const baseUrl = openaiAccount?.baseUrl || 'https://api.openai.com/v1';
|
||||
openaiAccount.baseUrl = baseUrl;
|
||||
|
||||
const ai = getAIApi({
|
||||
|
||||
@@ -8,12 +8,12 @@ import { i18nT } from '../../../../web/i18n/utils';
|
||||
import { pushConcatBillTask, pushReduceTeamAiPointsTask } from './utils';
|
||||
|
||||
import { POST } from '../../../common/api/plusRequest';
|
||||
import { FastGPTProUrl } from '../../../common/system/constants';
|
||||
import { isFastGPTMainService } from '../../../common/system/constants';
|
||||
|
||||
export async function createUsage(data: CreateUsageProps) {
|
||||
try {
|
||||
// In FastGPT server
|
||||
if (FastGPTProUrl) {
|
||||
if (isFastGPTMainService) {
|
||||
await POST('/support/wallet/usage/createUsage', data);
|
||||
} else if (global.reduceAiPointsQueue) {
|
||||
// In FastGPT pro server
|
||||
@@ -31,7 +31,7 @@ export async function createUsage(data: CreateUsageProps) {
|
||||
export async function concatUsage(data: ConcatUsageProps) {
|
||||
try {
|
||||
// In FastGPT server
|
||||
if (FastGPTProUrl) {
|
||||
if (isFastGPTMainService) {
|
||||
await POST('/support/wallet/usage/concatUsage', data);
|
||||
} else if (global.reduceAiPointsQueue) {
|
||||
const {
|
||||
|
||||
@@ -45,11 +45,11 @@ const parsePowerPoint = async ({
|
||||
|
||||
// Returning an array of all the xml contents read using fs.readFileSync
|
||||
const xmlContentArray = await Promise.all(
|
||||
files.map((file) => {
|
||||
files.map(async (file) => {
|
||||
try {
|
||||
return fs.promises.readFile(`${decompressPath}/${file.path}`, encoding);
|
||||
return await fs.promises.readFile(`${decompressPath}/${file.path}`, encoding);
|
||||
} catch (err) {
|
||||
return fs.promises.readFile(`${decompressPath}/${file.path}`, 'utf-8');
|
||||
return await fs.promises.readFile(`${decompressPath}/${file.path}`, 'utf-8');
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -100,6 +100,13 @@ const DateRangePicker = ({
|
||||
if (date?.to === undefined) {
|
||||
date.to = date.from;
|
||||
}
|
||||
|
||||
if (date?.from) {
|
||||
date.from = new Date(date.from.setHours(0, 0, 0, 0));
|
||||
}
|
||||
if (date?.to) {
|
||||
date.to = new Date(date.to.setHours(23, 59, 59, 999));
|
||||
}
|
||||
setRange(date);
|
||||
onChange?.(date);
|
||||
}}
|
||||
|
||||
@@ -6,6 +6,7 @@ export const iconPaths = {
|
||||
chatSend: () => import('./icons/chatSend.svg'),
|
||||
check: () => import('./icons/check.svg'),
|
||||
checkCircle: () => import('./icons/checkCircle.svg'),
|
||||
close: () => import('./icons/close.svg'),
|
||||
closeSolid: () => import('./icons/closeSolid.svg'),
|
||||
code: () => import('./icons/code.svg'),
|
||||
collectionLight: () => import('./icons/collectionLight.svg'),
|
||||
@@ -32,8 +33,10 @@ export const iconPaths = {
|
||||
'common/customTitleLight': () => import('./icons/common/customTitleLight.svg'),
|
||||
'common/data': () => import('./icons/common/data.svg'),
|
||||
'common/dingtalkFill': () => import('./icons/common/dingtalkFill.svg'),
|
||||
'common/disable': () => import('./icons/common/disable.svg'),
|
||||
'common/downArrowFill': () => import('./icons/common/downArrowFill.svg'),
|
||||
'common/editor/resizer': () => import('./icons/common/editor/resizer.svg'),
|
||||
'common/enable': () => import('./icons/common/enable.svg'),
|
||||
'common/errorFill': () => import('./icons/common/errorFill.svg'),
|
||||
'common/file/move': () => import('./icons/common/file/move.svg'),
|
||||
'common/folderFill': () => import('./icons/common/folderFill.svg'),
|
||||
@@ -161,6 +164,7 @@ export const iconPaths = {
|
||||
'core/chat/chatLight': () => import('./icons/core/chat/chatLight.svg'),
|
||||
'core/chat/chatModelTag': () => import('./icons/core/chat/chatModelTag.svg'),
|
||||
'core/chat/chevronDown': () => import('./icons/core/chat/chevronDown.svg'),
|
||||
'core/chat/chevronLeft': () => import('./icons/core/chat/chevronLeft.svg'),
|
||||
'core/chat/chevronRight': () => import('./icons/core/chat/chevronRight.svg'),
|
||||
'core/chat/chevronSelector': () => import('./icons/core/chat/chevronSelector.svg'),
|
||||
'core/chat/chevronUp': () => import('./icons/core/chat/chevronUp.svg'),
|
||||
@@ -329,6 +333,7 @@ export const iconPaths = {
|
||||
edit: () => import('./icons/edit.svg'),
|
||||
empty: () => import('./icons/empty.svg'),
|
||||
export: () => import('./icons/export.svg'),
|
||||
feedback: () => import('./icons/feedback.svg'),
|
||||
'file/csv': () => import('./icons/file/csv.svg'),
|
||||
'file/fill/csv': () => import('./icons/file/fill/csv.svg'),
|
||||
'file/fill/doc': () => import('./icons/file/fill/doc.svg'),
|
||||
|
||||
3
packages/web/components/common/Icon/icons/close.svg
Normal file
3
packages/web/components/common/Icon/icons/close.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.65613 2.84383C9.85139 3.0391 9.85139 3.35568 9.65613 3.55094L7.20708 5.99999L9.65617 8.44908C9.85143 8.64434 9.85143 8.96093 9.65617 9.15619C9.46091 9.35145 9.14433 9.35145 8.94906 9.15619L6.49997 6.7071L4.05088 9.15619C3.85562 9.35145 3.53904 9.35145 3.34377 9.15619C3.14851 8.96093 3.14851 8.64434 3.34377 8.44908L5.79286 5.99999L3.34382 3.55094C3.14855 3.35568 3.14855 3.0391 3.34382 2.84383C3.53908 2.64857 3.85566 2.64857 4.05092 2.84383L6.49997 5.29288L8.94902 2.84383C9.14428 2.64857 9.46087 2.64857 9.65613 2.84383Z" fill="#92A5C9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 675 B |
@@ -0,0 +1 @@
|
||||
<svg t="1740494996853" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2899" width="64" height="64"><path d="M512 953.6a441.6 441.6 0 1 1 0-883.2 441.6 441.6 0 0 1 0 883.2z m0-64a377.6 377.6 0 1 0 0-755.2 377.6 377.6 0 0 0 0 755.2z" p-id="2900"></path><path d="M182.1696 227.4304l45.2608-45.2608 614.4 614.4-45.2608 45.2608z" p-id="2901"></path></svg>
|
||||
|
After Width: | Height: | Size: 397 B |
@@ -0,0 +1 @@
|
||||
<svg t="1740495050372" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4745" width="64" height="64"><path d="M510.2 959.7c-246.9-1-447-202.6-446-449.5s202.6-447 449.5-446 447 202.6 446 449.5-202.6 447-449.5 446z m3.3-833.7c-212.8-0.8-386.7 171.7-387.5 384.5S297.7 897.2 510.5 898 897.2 726.3 898 513.5 726.3 126.8 513.5 126z" p-id="4746"></path><path d="M465.8 712.3L291.1 537.6l43.7-43.7 131 131 262-262 43.7 43.7z" p-id="4747"></path></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4714 3.52864C10.7317 3.78899 10.7317 4.2111 10.4714 4.47145L6.94277 8.00004L10.4714 11.5286C10.7317 11.789 10.7317 12.2111 10.4714 12.4714C10.211 12.7318 9.7889 12.7318 9.52855 12.4714L5.52855 8.47144C5.26821 8.21109 5.26821 7.78898 5.52855 7.52864L9.52855 3.52864C9.7889 3.26829 10.211 3.26829 10.4714 3.52864Z" fill="#92A5C9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 464 B |
29
packages/web/components/common/Icon/icons/feedback.svg
Normal file
29
packages/web/components/common/Icon/icons/feedback.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<svg viewBox="0 0 29 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.49993 10.1261C8.49993 9.48174 9.02226 8.9594 9.6666 8.9594H14.3333C14.9776 8.9594 15.4999 9.48174 15.4999 10.1261C15.4999 10.7704 14.9776 11.2927 14.3333 11.2927H9.6666C9.02226 11.2927 8.49993 10.7704 8.49993 10.1261Z" fill="url(#paint0_linear_17422_2138)"/>
|
||||
<path d="M8.49993 14.7927C8.49993 14.1484 9.02226 13.6261 9.6666 13.6261H18.4041C19.0485 13.6261 19.5708 14.1484 19.5708 14.7927C19.5708 15.4371 19.0485 15.9594 18.4041 15.9594H9.6666C9.02226 15.9594 8.49993 15.4371 8.49993 14.7927Z" fill="url(#paint1_linear_17422_2138)"/>
|
||||
<path d="M17.6859 9.1641C17.3948 9.37757 17.2057 9.72212 17.2057 10.1108V11.2504C17.2057 11.2738 17.2247 11.2927 17.248 11.2927H18.3745C18.7286 11.2927 19.0462 11.1358 19.262 10.8878L25.8249 4.32494C26.2805 3.86933 26.2805 3.13063 25.8249 2.67502C25.3693 2.21941 24.6306 2.21941 24.175 2.67502L17.6859 9.1641Z" fill="url(#paint2_linear_17422_2138)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5122 3.45903C17.5122 2.81469 16.9899 2.29236 16.3456 2.29236H7.34644C5.41344 2.29236 3.84644 3.85936 3.84644 5.79236V22.1257C3.84644 24.0587 5.41344 25.6257 7.34644 25.6257H21.6535C23.5865 25.6257 25.1535 24.0587 25.1535 22.1257V12.1788C25.1535 11.5344 24.6311 11.0121 23.9868 11.0121C23.3425 11.0121 22.8201 11.5345 22.8201 12.1788V22.1257C22.8201 22.77 22.2978 23.2924 21.6535 23.2924H7.34644C6.7021 23.2924 6.17977 22.77 6.17977 22.1257V5.79236C6.17977 5.14803 6.7021 4.62569 7.34644 4.62569H16.3456C16.9899 4.62569 17.5122 4.10336 17.5122 3.45903Z" fill="url(#paint3_linear_17422_2138)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17422_2138" x1="15.0065" y1="3.04993" x2="15.0065" y2="24.8681" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#499DFF"/>
|
||||
<stop offset="0.432292" stop-color="#2770FF"/>
|
||||
<stop offset="1" stop-color="#6E80FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17422_2138" x1="15.0065" y1="3.04993" x2="15.0065" y2="24.8681" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#499DFF"/>
|
||||
<stop offset="0.432292" stop-color="#2770FF"/>
|
||||
<stop offset="1" stop-color="#6E80FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_17422_2138" x1="15.0065" y1="3.04993" x2="15.0065" y2="24.8681" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#499DFF"/>
|
||||
<stop offset="0.432292" stop-color="#2770FF"/>
|
||||
<stop offset="1" stop-color="#6E80FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_17422_2138" x1="15.0065" y1="3.04993" x2="15.0065" y2="24.8681" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#499DFF"/>
|
||||
<stop offset="0.432292" stop-color="#2770FF"/>
|
||||
<stop offset="1" stop-color="#6E80FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -10,8 +10,9 @@ import React from 'react';
|
||||
import MyIcon from '../../Icon';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
|
||||
type Props = Omit<NumberInputProps, 'onChange'> & {
|
||||
type Props = Omit<NumberInputProps, 'onChange' | 'onBlur'> & {
|
||||
onChange?: (e?: number) => any;
|
||||
onBlur?: (e?: number) => any;
|
||||
placeholder?: string;
|
||||
register?: UseFormRegister<any>;
|
||||
name?: string;
|
||||
@@ -19,11 +20,21 @@ type Props = Omit<NumberInputProps, 'onChange'> & {
|
||||
};
|
||||
|
||||
const MyNumberInput = (props: Props) => {
|
||||
const { register, name, onChange, placeholder, bg, ...restProps } = props;
|
||||
const { register, name, onChange, onBlur, placeholder, bg, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<NumberInput
|
||||
{...restProps}
|
||||
onBlur={(e) => {
|
||||
if (!onBlur) return;
|
||||
const numE = Number(e.target.value);
|
||||
if (isNaN(numE)) {
|
||||
// @ts-ignore
|
||||
onBlur('');
|
||||
} else {
|
||||
onBlur(numE);
|
||||
}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (!onChange) return;
|
||||
const numE = Number(e);
|
||||
@@ -38,6 +49,8 @@ const MyNumberInput = (props: Props) => {
|
||||
<NumberInputField
|
||||
bg={bg}
|
||||
placeholder={placeholder}
|
||||
h={restProps.h}
|
||||
defaultValue={restProps.defaultValue}
|
||||
{...(register && name
|
||||
? register(name, {
|
||||
required: props.isRequired,
|
||||
|
||||
@@ -98,7 +98,6 @@ const MultipleSelect = <T = any,>({
|
||||
return (
|
||||
<MenuItem
|
||||
key={i}
|
||||
{...menuItemStyles}
|
||||
{...(isSelected
|
||||
? {
|
||||
color: 'primary.600'
|
||||
@@ -114,6 +113,7 @@ const MultipleSelect = <T = any,>({
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
{...menuItemStyles}
|
||||
>
|
||||
<Checkbox isChecked={isSelected} />
|
||||
{item.icon && <MyAvatar src={item.icon} w={'1rem'} borderRadius={'0'} />}
|
||||
@@ -204,6 +204,7 @@ const MultipleSelect = <T = any,>({
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
/>
|
||||
@@ -230,7 +231,6 @@ const MultipleSelect = <T = any,>({
|
||||
overflowY={'auto'}
|
||||
>
|
||||
<MenuItem
|
||||
{...menuItemStyles}
|
||||
color={isSelectAll ? 'primary.600' : 'myGray.900'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -241,6 +241,7 @@ const MultipleSelect = <T = any,>({
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
mb={1}
|
||||
{...menuItemStyles}
|
||||
>
|
||||
<Checkbox isChecked={isSelectAll} />
|
||||
<Box flex={'1 0 0'}>{t('common:common.All')}</Box>
|
||||
|
||||
@@ -4,7 +4,8 @@ import React, {
|
||||
useMemo,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
ForwardedRef
|
||||
ForwardedRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import {
|
||||
Menu,
|
||||
@@ -15,7 +16,8 @@ import {
|
||||
MenuButton,
|
||||
Box,
|
||||
css,
|
||||
Flex
|
||||
Flex,
|
||||
Input
|
||||
} from '@chakra-ui/react';
|
||||
import type { ButtonProps, MenuItemProps } from '@chakra-ui/react';
|
||||
import MyIcon from '../Icon';
|
||||
@@ -33,8 +35,10 @@ import { useScrollPagination } from '../../../hooks/useScrollPagination';
|
||||
export type SelectProps<T = any> = ButtonProps & {
|
||||
value?: T;
|
||||
placeholder?: string;
|
||||
isSearch?: boolean;
|
||||
list: {
|
||||
alias?: string;
|
||||
icon?: string;
|
||||
label: string | React.ReactNode;
|
||||
description?: string;
|
||||
value: T;
|
||||
@@ -49,6 +53,7 @@ const MySelect = <T = any,>(
|
||||
{
|
||||
placeholder,
|
||||
value,
|
||||
isSearch = false,
|
||||
width = '100%',
|
||||
list = [],
|
||||
onchange,
|
||||
@@ -63,6 +68,7 @@ const MySelect = <T = any,>(
|
||||
const ButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const MenuListRef = useRef<HTMLDivElement>(null);
|
||||
const SelectedItemRef = useRef<HTMLDivElement>(null);
|
||||
const SearchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const menuItemStyles: MenuItemProps = {
|
||||
borderRadius: 'sm',
|
||||
@@ -79,6 +85,18 @@ const MySelect = <T = any,>(
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]);
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const filterList = useMemo(() => {
|
||||
if (!isSearch || !search) {
|
||||
return list;
|
||||
}
|
||||
return list.filter((item) => {
|
||||
const text = `${item.label?.toString()}${item.alias}${item.value}`;
|
||||
const regx = new RegExp(search, 'gi');
|
||||
return regx.test(text);
|
||||
});
|
||||
}, [list, search, isSearch]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus() {
|
||||
onOpen();
|
||||
@@ -90,17 +108,19 @@ const MySelect = <T = any,>(
|
||||
const menu = MenuListRef.current;
|
||||
const selectedItem = SelectedItemRef.current;
|
||||
menu.scrollTop = selectedItem.offsetTop - menu.offsetTop - 100;
|
||||
|
||||
if (isSearch) {
|
||||
setSearch('');
|
||||
}
|
||||
}
|
||||
}, [isOpen]);
|
||||
}, [isSearch, isOpen]);
|
||||
|
||||
const { runAsync: onChange, loading } = useRequest2((val: T) => onchange?.(val));
|
||||
|
||||
const isSelecting = loading || isLoading;
|
||||
|
||||
const ListRender = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{list.map((item, i) => (
|
||||
{filterList.map((item, i) => (
|
||||
<Box key={i}>
|
||||
<MenuItem
|
||||
{...menuItemStyles}
|
||||
@@ -123,7 +143,10 @@ const MySelect = <T = any,>(
|
||||
fontSize={'sm'}
|
||||
display={'block'}
|
||||
>
|
||||
<Box>{item.label}</Box>
|
||||
<Flex alignItems={'center'}>
|
||||
{item.icon && <MyIcon mr={2} name={item.icon as any} w={'1rem'} />}
|
||||
{item.label}
|
||||
</Flex>
|
||||
{item.description && (
|
||||
<Box color={'myGray.500'} fontSize={'xs'}>
|
||||
{item.description}
|
||||
@@ -135,7 +158,9 @@ const MySelect = <T = any,>(
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}, [list, value]);
|
||||
}, [filterList, value]);
|
||||
|
||||
const isSelecting = loading || isLoading;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -176,8 +201,33 @@ const MySelect = <T = any,>(
|
||||
{...props}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
{isSelecting && <MyIcon mr={2} name={'common/loading'} w={'16px'} />}
|
||||
{selectItem?.alias || selectItem?.label || placeholder}
|
||||
{isSelecting && <MyIcon mr={2} name={'common/loading'} w={'1rem'} />}
|
||||
{isSearch && isOpen ? (
|
||||
<Input
|
||||
ref={SearchInputRef}
|
||||
autoFocus
|
||||
variant={'unstyled'}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={
|
||||
selectItem?.alias ||
|
||||
(typeof selectItem?.label === 'string' ? selectItem?.label : placeholder)
|
||||
}
|
||||
size={'sm'}
|
||||
w={'100%'}
|
||||
color={'myGray.700'}
|
||||
onBlur={() => {
|
||||
setTimeout(() => {
|
||||
SearchInputRef?.current?.focus();
|
||||
}, 0);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{selectItem?.icon && <MyIcon mr={2} name={selectItem.icon as any} w={'1rem'} />}
|
||||
{selectItem?.alias || selectItem?.label || placeholder}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</MenuButton>
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ export function useScrollPagination<
|
||||
const offset = init ? 0 : data.length;
|
||||
|
||||
setTrue();
|
||||
|
||||
console.log(offset);
|
||||
try {
|
||||
const res = await api({
|
||||
offset,
|
||||
|
||||
46
packages/web/i18n/en/account_model.json
Normal file
46
packages/web/i18n/en/account_model.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"api_key": "API key",
|
||||
"azure": "Azure",
|
||||
"base_url": "Base url",
|
||||
"channel_name": "Channel",
|
||||
"channel_priority": "Priority",
|
||||
"channel_priority_tip": "The higher the priority channel, the easier it is to be requested",
|
||||
"channel_status": "state",
|
||||
"channel_status_auto_disabled": "Automatically disable",
|
||||
"channel_status_disabled": "Disabled",
|
||||
"channel_status_enabled": "Enable",
|
||||
"channel_status_unknown": "unknown",
|
||||
"channel_type": "Manufacturer",
|
||||
"clear_model": "Clear the model",
|
||||
"copy_model_id_success": "Copyed model id",
|
||||
"create_channel": "Added channels",
|
||||
"default_url": "Default address",
|
||||
"detail": "Detail",
|
||||
"duration": "Duration",
|
||||
"edit": "edit",
|
||||
"edit_channel": "Channel configuration",
|
||||
"enable_channel": "Enable",
|
||||
"forbid_channel": "Disabled",
|
||||
"key_type": "API key format:",
|
||||
"log": "Call log",
|
||||
"log_detail": "Log details",
|
||||
"log_status": "Status",
|
||||
"mapping": "Model Mapping",
|
||||
"mapping_tip": "A valid Json is required. \nThe model can be mapped when sending a request to the actual address. \nFor example:\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\nWhen FastGPT requests the gpt-4o model, the gpt-4o-test model is sent to the actual address, instead of gpt-4o.",
|
||||
"model": "Model",
|
||||
"model_name": "Model name",
|
||||
"model_test": "Model testing",
|
||||
"model_tokens": "Input/Output tokens",
|
||||
"request_at": "Request time",
|
||||
"request_duration": "Request duration: {{duration}}s",
|
||||
"running_test": "In testing",
|
||||
"search_model": "Search for models",
|
||||
"select_channel": "Select a channel name",
|
||||
"select_model": "Select a model",
|
||||
"select_model_placeholder": "Select the model available under this channel",
|
||||
"select_provider_placeholder": "Search for manufacturers",
|
||||
"selected_model_empty": "Choose at least one model",
|
||||
"start_test": "Start testing {{num}} models",
|
||||
"test_failed": "There are {{num}} models that report errors",
|
||||
"waiting_test": "Waiting for testing"
|
||||
}
|
||||
@@ -125,7 +125,6 @@
|
||||
"common.Copy Successful": "Copied Successfully",
|
||||
"common.Copy_failed": "Copy Failed, Please Copy Manually",
|
||||
"common.Create Failed": "Creation Failed",
|
||||
"common.Create New": "Create",
|
||||
"common.Create Success": "Created Successfully",
|
||||
"common.Create Time": "Creation Time",
|
||||
"common.Creating": "Creating",
|
||||
@@ -547,7 +546,6 @@
|
||||
"core.dataset.data.Main Content": "Main Content",
|
||||
"core.dataset.data.Search data placeholder": "Search Related Data",
|
||||
"core.dataset.data.Too Long": "Total Length Exceeded",
|
||||
"core.dataset.data.Total Amount": "{{total}} Groups",
|
||||
"core.dataset.data.group": "Group",
|
||||
"core.dataset.data.unit": "Items",
|
||||
"core.dataset.embedding model tip": "The index model can convert natural language into vectors for semantic search.\nNote that different index models cannot be used together. Once an index model is selected, it cannot be changed.",
|
||||
@@ -860,7 +858,6 @@
|
||||
"dataset.collections.Collection Embedding": "{{total}} Indexes",
|
||||
"dataset.collections.Confirm to delete the folder": "Confirm to Delete This Folder and All Its Contents?",
|
||||
"dataset.collections.Create And Import": "Create/Import",
|
||||
"dataset.collections.Data Amount": "Total Data",
|
||||
"dataset.collections.Select Collection": "Select File",
|
||||
"dataset.collections.Select One Collection To Store": "Select a File to Store",
|
||||
"dataset.data.Can not edit": "No Edit Permission",
|
||||
@@ -876,6 +873,7 @@
|
||||
"dataset.dataset_name": "Dataset Name",
|
||||
"dataset.deleteFolderTips": "Confirm to Delete This Folder and All Its Contained Datasets? Data Cannot Be Recovered After Deletion, Please Confirm!",
|
||||
"dataset.test.noResult": "No Search Results",
|
||||
"dataset_text_model_tip": "Used for text processing in the knowledge base preprocessing stage, such as automatic supplementary indexing, Q&A pair extraction.",
|
||||
"deep_rag_search": "In-depth search",
|
||||
"delete_api": "Are you sure you want to delete this API key? \nAfter deletion, the key will become invalid immediately and the corresponding conversation log will not be deleted. Please confirm!",
|
||||
"embedding_model_not_config": "No index model is detected",
|
||||
@@ -1013,6 +1011,7 @@
|
||||
"plugin.go to laf": "Go to Write",
|
||||
"plugin.path": "Path",
|
||||
"prompt_input_placeholder": "Please enter the prompt word",
|
||||
"question_feedback": "Work order",
|
||||
"read_quote": "View citations",
|
||||
"required": "Required",
|
||||
"resume_failed": "Resume Failed",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"close_auto_sync": "Are you sure you want to turn off automatic sync?",
|
||||
"collection.Create update time": "Creation/Update Time",
|
||||
"collection.Training type": "Training",
|
||||
"collection_data_count": "Data amount",
|
||||
"collection_not_support_retraining": "This collection type does not support retuning parameters",
|
||||
"collection_not_support_sync": "This collection does not support synchronization",
|
||||
"collection_sync": "Sync data",
|
||||
@@ -20,6 +21,7 @@
|
||||
"custom_data_process_params": "Custom",
|
||||
"custom_data_process_params_desc": "Customize data processing rules",
|
||||
"data.ideal_chunk_length": "ideal block length",
|
||||
"data_amount": "{{dataAmount}} Datas, {{indexAmount}} Indexes",
|
||||
"data_process_params": "Params",
|
||||
"data_process_setting": "Processing config",
|
||||
"dataset.Unsupported operation": "dataset.Unsupported operation",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"add_default_model": "添加预设模型",
|
||||
"api_key": "API 密钥",
|
||||
"bills_and_invoices": "账单与发票",
|
||||
"channel": "渠道",
|
||||
"channel": "模型渠道",
|
||||
"config_model": "模型配置",
|
||||
"confirm_logout": "确认退出登录?",
|
||||
"create_channel": "新增渠道",
|
||||
|
||||
46
packages/web/i18n/zh-CN/account_model.json
Normal file
46
packages/web/i18n/zh-CN/account_model.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"api_key": "API 密钥",
|
||||
"azure": "微软 Azure",
|
||||
"base_url": "代理地址",
|
||||
"channel_name": "渠道名",
|
||||
"channel_priority": "优先级",
|
||||
"channel_priority_tip": "优先级越高的渠道,越容易被请求到",
|
||||
"channel_status": "状态",
|
||||
"channel_status_auto_disabled": "自动禁用",
|
||||
"channel_status_disabled": "禁用",
|
||||
"channel_status_enabled": "启用",
|
||||
"channel_status_unknown": "未知",
|
||||
"channel_type": "厂商",
|
||||
"clear_model": "清空模型",
|
||||
"copy_model_id_success": "已复制模型id",
|
||||
"create_channel": "新增渠道",
|
||||
"default_url": "默认地址",
|
||||
"detail": "详情",
|
||||
"duration": "耗时",
|
||||
"edit": "编辑",
|
||||
"edit_channel": "渠道配置",
|
||||
"enable_channel": "启用",
|
||||
"forbid_channel": "禁用",
|
||||
"key_type": "API key 格式: ",
|
||||
"log": "调用日志",
|
||||
"log_detail": "日志详情",
|
||||
"log_status": "状态",
|
||||
"mapping": "模型映射",
|
||||
"mapping_tip": "需填写一个有效 Json。可在向实际地址发送请求时,对模型进行映射。例如:\n{\n \"gpt-4o\": \"gpt-4o-test\"\n}\n当 FastGPT 请求 gpt-4o 模型时,会向实际地址发送 gpt-4o-test 的模型,而不是 gpt-4o。",
|
||||
"model": "模型",
|
||||
"model_name": "模型名",
|
||||
"model_test": "模型测试",
|
||||
"model_tokens": "输入/输出 Tokens",
|
||||
"request_at": "请求时间",
|
||||
"request_duration": "请求时长: {{duration}}s",
|
||||
"running_test": "测试中",
|
||||
"search_model": "搜索模型",
|
||||
"select_channel": "选择渠道名",
|
||||
"select_model": "选择模型",
|
||||
"select_model_placeholder": "选择该渠道下可用的模型",
|
||||
"select_provider_placeholder": "搜索厂商",
|
||||
"selected_model_empty": "至少选择一个模型",
|
||||
"start_test": "开始测试{{num}}个模型",
|
||||
"test_failed": "有{{num}}个模型报错",
|
||||
"waiting_test": "等待测试"
|
||||
}
|
||||
@@ -129,7 +129,6 @@
|
||||
"common.Copy Successful": "复制成功",
|
||||
"common.Copy_failed": "复制失败,请手动复制",
|
||||
"common.Create Failed": "创建异常",
|
||||
"common.Create New": "新建",
|
||||
"common.Create Success": "创建成功",
|
||||
"common.Create Time": "创建时间",
|
||||
"common.Creating": "创建中",
|
||||
@@ -550,7 +549,6 @@
|
||||
"core.dataset.data.Main Content": "主要内容",
|
||||
"core.dataset.data.Search data placeholder": "搜索相关数据",
|
||||
"core.dataset.data.Too Long": "总长度超长了",
|
||||
"core.dataset.data.Total Amount": "{{total}} 组",
|
||||
"core.dataset.data.group": "组",
|
||||
"core.dataset.data.unit": "条",
|
||||
"core.dataset.embedding model tip": "索引模型可以将自然语言转成向量,用于进行语义检索。\n注意,不同索引模型无法一起使用,选择完索引模型后将无法修改。",
|
||||
@@ -863,7 +861,6 @@
|
||||
"dataset.collections.Collection Embedding": "{{total}} 组索引中",
|
||||
"dataset.collections.Confirm to delete the folder": "确认删除该文件夹及里面所有内容?",
|
||||
"dataset.collections.Create And Import": "新建/导入",
|
||||
"dataset.collections.Data Amount": "数据总量",
|
||||
"dataset.collections.Select Collection": "选择文件",
|
||||
"dataset.collections.Select One Collection To Store": "选择一个文件进行存储",
|
||||
"dataset.data.Can not edit": "无编辑权限",
|
||||
@@ -879,6 +876,7 @@
|
||||
"dataset.dataset_name": "知识库名称",
|
||||
"dataset.deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!",
|
||||
"dataset.test.noResult": "搜索结果为空",
|
||||
"dataset_text_model_tip": "用于知识库预处理阶段的文本处理,例如自动补充索引、问答对提取。",
|
||||
"deep_rag_search": "深度搜索",
|
||||
"delete_api": "确认删除该API密钥?删除后该密钥立即失效,对应的对话日志不会删除,请确认!",
|
||||
"embedding_model_not_config": "检测到没有可用的索引模型",
|
||||
@@ -944,9 +942,9 @@
|
||||
"model_moka": "Moka-AI",
|
||||
"model_moonshot": "月之暗面",
|
||||
"model_other": "其他",
|
||||
"model_ppio": "PPIO 派欧云",
|
||||
"model_qwen": "阿里千问",
|
||||
"model_siliconflow": "硅基流动",
|
||||
"model_ppio": "PPIO 派欧云",
|
||||
"model_sparkdesk": "讯飞星火",
|
||||
"model_stepfun": "阶跃星辰",
|
||||
"model_yi": "零一万物",
|
||||
@@ -1016,6 +1014,7 @@
|
||||
"plugin.go to laf": "去编写",
|
||||
"plugin.path": "路径",
|
||||
"prompt_input_placeholder": "请输入提示词",
|
||||
"question_feedback": "工单咨询",
|
||||
"read_quote": "查看引用",
|
||||
"required": "必须",
|
||||
"resume_failed": "恢复失败",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"close_auto_sync": "确认关闭自动同步功能?",
|
||||
"collection.Create update time": "创建/更新时间",
|
||||
"collection.Training type": "训练模式",
|
||||
"collection_data_count": "数据量",
|
||||
"collection_not_support_retraining": "该集合类型不支持重新调整参数",
|
||||
"collection_not_support_sync": "该集合不支持同步",
|
||||
"collection_sync": "立即同步",
|
||||
@@ -20,6 +21,7 @@
|
||||
"custom_data_process_params": "自定义",
|
||||
"custom_data_process_params_desc": "自定义设置数据处理规则",
|
||||
"data.ideal_chunk_length": "理想分块长度",
|
||||
"data_amount": "{{dataAmount}} 组数据, {{indexAmount}} 组索引",
|
||||
"data_process_params": "处理参数",
|
||||
"data_process_setting": "数据处理配置",
|
||||
"dataset.Unsupported operation": "操作不支持",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"add_default_model": "新增預設模型",
|
||||
"api_key": "API 金鑰",
|
||||
"bills_and_invoices": "帳單與發票",
|
||||
"channel": "頻道",
|
||||
"channel": "模型渠道",
|
||||
"config_model": "模型配置",
|
||||
"confirm_logout": "確認登出登入?",
|
||||
"create_channel": "新增頻道",
|
||||
|
||||
44
packages/web/i18n/zh-Hant/account_model.json
Normal file
44
packages/web/i18n/zh-Hant/account_model.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"api_key": "API 密鑰",
|
||||
"azure": "Azure",
|
||||
"base_url": "代理地址",
|
||||
"channel_name": "渠道名",
|
||||
"channel_priority": "優先級",
|
||||
"channel_priority_tip": "優先級越高的渠道,越容易被請求到",
|
||||
"channel_status": "狀態",
|
||||
"channel_status_auto_disabled": "自動禁用",
|
||||
"channel_status_disabled": "禁用",
|
||||
"channel_status_enabled": "啟用",
|
||||
"channel_status_unknown": "未知",
|
||||
"channel_type": "廠商",
|
||||
"clear_model": "清空模型",
|
||||
"copy_model_id_success": "已復制模型id",
|
||||
"create_channel": "新增渠道",
|
||||
"default_url": "默認地址",
|
||||
"detail": "詳情",
|
||||
"edit_channel": "渠道配置",
|
||||
"enable_channel": "啟用",
|
||||
"forbid_channel": "禁用",
|
||||
"key_type": "API key 格式:",
|
||||
"log": "調用日誌",
|
||||
"log_detail": "日誌詳情",
|
||||
"log_status": "狀態",
|
||||
"mapping": "模型映射",
|
||||
"mapping_tip": "需填寫一個有效 Json。\n可在向實際地址發送請求時,對模型進行映射。\n例如:\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\n當 FastGPT 請求 gpt-4o 模型時,會向實際地址發送 gpt-4o-test 的模型,而不是 gpt-4o。",
|
||||
"model": "模型",
|
||||
"model_name": "模型名",
|
||||
"model_test": "模型測試",
|
||||
"model_tokens": "輸入/輸出 Tokens",
|
||||
"request_at": "請求時間",
|
||||
"request_duration": "請求時長: {{duration}}s",
|
||||
"running_test": "測試中",
|
||||
"search_model": "搜索模型",
|
||||
"select_channel": "選擇渠道名",
|
||||
"select_model": "選擇模型",
|
||||
"select_model_placeholder": "選擇該渠道下可用的模型",
|
||||
"select_provider_placeholder": "搜索廠商",
|
||||
"selected_model_empty": "至少選擇一個模型",
|
||||
"start_test": "開始測試{{num}}個模型",
|
||||
"test_failed": "有{{num}}個模型報錯",
|
||||
"waiting_test": "等待測試"
|
||||
}
|
||||
@@ -124,7 +124,6 @@
|
||||
"common.Copy Successful": "複製成功",
|
||||
"common.Copy_failed": "複製失敗,請手動複製",
|
||||
"common.Create Failed": "建立失敗",
|
||||
"common.Create New": "建立新項目",
|
||||
"common.Create Success": "建立成功",
|
||||
"common.Create Time": "建立時間",
|
||||
"common.Creating": "建立中",
|
||||
@@ -546,7 +545,6 @@
|
||||
"core.dataset.data.Main Content": "主要內容",
|
||||
"core.dataset.data.Search data placeholder": "搜尋相關資料",
|
||||
"core.dataset.data.Too Long": "總長度超出上限",
|
||||
"core.dataset.data.Total Amount": "{{total}} 組",
|
||||
"core.dataset.data.group": "組",
|
||||
"core.dataset.data.unit": "筆",
|
||||
"core.dataset.embedding model tip": "索引模型可以將自然語言轉換成向量,用於進行語意搜尋。\n注意,不同索引模型無法一起使用。選擇索引模型後就無法修改。",
|
||||
@@ -860,7 +858,6 @@
|
||||
"dataset.collections.Collection Embedding": "{{total}} 個索引",
|
||||
"dataset.collections.Confirm to delete the folder": "確認刪除此資料夾及其所有內容?",
|
||||
"dataset.collections.Create And Import": "建立或匯入",
|
||||
"dataset.collections.Data Amount": "資料總量",
|
||||
"dataset.collections.Select Collection": "選擇檔案",
|
||||
"dataset.collections.Select One Collection To Store": "選擇一個檔案進行儲存",
|
||||
"dataset.data.Can not edit": "無編輯權限",
|
||||
@@ -876,6 +873,7 @@
|
||||
"dataset.dataset_name": "知識庫名稱",
|
||||
"dataset.deleteFolderTips": "確認刪除此資料夾及其包含的所有知識庫?刪除後資料無法復原,請確認!",
|
||||
"dataset.test.noResult": "搜尋結果為空",
|
||||
"dataset_text_model_tip": "用於知識庫預處理階段的文本處理,例如自動補充索引、問答對提取。",
|
||||
"deep_rag_search": "深度搜索",
|
||||
"delete_api": "確認刪除此 API 金鑰?\n刪除後該金鑰將立即失效,對應的對話記錄不會被刪除,請確認!",
|
||||
"embedding_model_not_config": "檢測到沒有可用的索引模型",
|
||||
@@ -1012,6 +1010,7 @@
|
||||
"plugin.go to laf": "前往編寫",
|
||||
"plugin.path": "路徑",
|
||||
"prompt_input_placeholder": "請輸入提示詞",
|
||||
"question_feedback": "工單諮詢",
|
||||
"read_quote": "查看引用",
|
||||
"required": "必填",
|
||||
"resume_failed": "恢復失敗",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"close_auto_sync": "確認關閉自動同步功能?",
|
||||
"collection.Create update time": "建立/更新時間",
|
||||
"collection.Training type": "分段模式",
|
||||
"collection_data_count": "數據量",
|
||||
"collection_not_support_retraining": "此集合類型不支援重新調整參數",
|
||||
"collection_not_support_sync": "該集合不支援同步",
|
||||
"collection_sync": "立即同步",
|
||||
@@ -20,6 +21,7 @@
|
||||
"custom_data_process_params": "自訂",
|
||||
"custom_data_process_params_desc": "自訂資料處理規則",
|
||||
"data.ideal_chunk_length": "理想分塊長度",
|
||||
"data_amount": "{{dataAmount}} 組數據, {{indexAmount}} 組索引",
|
||||
"data_process_params": "處理參數",
|
||||
"data_process_setting": "資料處理設定",
|
||||
"dataset.Unsupported operation": "操作不支持",
|
||||
|
||||
5
packages/web/types/i18next.d.ts
vendored
5
packages/web/types/i18next.d.ts
vendored
@@ -18,6 +18,7 @@ import workflow from '../i18n/zh-CN/workflow.json';
|
||||
import user from '../i18n/zh-CN/user.json';
|
||||
import chat from '../i18n/zh-CN/chat.json';
|
||||
import login from '../i18n/zh-CN/login.json';
|
||||
import account_model from '../i18n/zh-CN/account_model.json';
|
||||
|
||||
export interface I18nNamespaces {
|
||||
common: typeof common;
|
||||
@@ -39,6 +40,7 @@ export interface I18nNamespaces {
|
||||
account: typeof account;
|
||||
account_team: typeof account_team;
|
||||
account_thirdParty: typeof account_thirdParty;
|
||||
account_model: typeof account_model;
|
||||
}
|
||||
|
||||
export type I18nNsType = (keyof I18nNamespaces)[];
|
||||
@@ -73,7 +75,8 @@ declare module 'i18next' {
|
||||
'account_promotion',
|
||||
'account_thirdParty',
|
||||
'account',
|
||||
'account_team'
|
||||
'account_team',
|
||||
'account_model'
|
||||
];
|
||||
resources: I18nNamespaces;
|
||||
}
|
||||
|
||||
94
pnpm-lock.yaml
generated
94
pnpm-lock.yaml
generated
@@ -206,8 +206,8 @@ importers:
|
||||
specifier: ^1.6.0
|
||||
version: 1.8.0
|
||||
mongoose:
|
||||
specifier: ^7.0.2
|
||||
version: 7.8.2
|
||||
specifier: ^8.10.1
|
||||
version: 8.10.2(socks@2.8.3)
|
||||
multer:
|
||||
specifier: 1.4.5-lts.1
|
||||
version: 1.4.5-lts.1
|
||||
@@ -603,7 +603,7 @@ importers:
|
||||
version: 9.0.3
|
||||
'@shelf/jest-mongodb':
|
||||
specifier: ^4.3.2
|
||||
version: 4.3.2(jest-environment-node@29.7.0)(mongodb@6.9.0(socks@2.8.3))
|
||||
version: 4.3.2(jest-environment-node@29.7.0)(mongodb@6.13.1(socks@2.8.3))
|
||||
'@svgr/webpack':
|
||||
specifier: ^6.5.1
|
||||
version: 6.5.1
|
||||
@@ -645,7 +645,7 @@ importers:
|
||||
version: 14.2.3(eslint@8.56.0)(typescript@5.5.3)
|
||||
mockingoose:
|
||||
specifier: ^2.16.2
|
||||
version: 2.16.2(mongoose@7.8.2)
|
||||
version: 2.16.2(mongoose@8.10.2(socks@2.8.3))
|
||||
mongodb-memory-server:
|
||||
specifier: ^10.0.0
|
||||
version: 10.1.0(socks@2.8.3)
|
||||
@@ -4204,9 +4204,14 @@ packages:
|
||||
resolution: {integrity: sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==}
|
||||
engines: {node: '>=14.20.1'}
|
||||
|
||||
bson@6.10.3:
|
||||
resolution: {integrity: sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
|
||||
bson@6.8.0:
|
||||
resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
deprecated: a critical bug affecting only useBigInt64=true deserialization usage is fixed in bson@6.10.3
|
||||
|
||||
buffer-alloc-unsafe@1.1.0:
|
||||
resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==}
|
||||
@@ -6507,8 +6512,8 @@ packages:
|
||||
jws@4.0.0:
|
||||
resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==}
|
||||
|
||||
kareem@2.5.1:
|
||||
resolution: {integrity: sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==}
|
||||
kareem@2.6.3:
|
||||
resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
katex@0.16.11:
|
||||
@@ -7159,6 +7164,33 @@ packages:
|
||||
snappy:
|
||||
optional: true
|
||||
|
||||
mongodb@6.13.1:
|
||||
resolution: {integrity: sha512-gdq40tX8StmhP6akMp1pPoEVv+9jTYFSrga/g23JxajPAQhH39ysZrHGzQCSd9PEOnuEQEdjIWqxO7ZSwC0w7Q==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
peerDependencies:
|
||||
'@aws-sdk/credential-providers': ^3.632.0
|
||||
'@mongodb-js/zstd': ^1.1.0 || ^2.0.0
|
||||
gcp-metadata: ^5.2.0
|
||||
kerberos: ^2.0.1
|
||||
mongodb-client-encryption: '>=6.0.0 <7'
|
||||
snappy: ^7.2.2
|
||||
socks: ^2.7.1
|
||||
peerDependenciesMeta:
|
||||
'@aws-sdk/credential-providers':
|
||||
optional: true
|
||||
'@mongodb-js/zstd':
|
||||
optional: true
|
||||
gcp-metadata:
|
||||
optional: true
|
||||
kerberos:
|
||||
optional: true
|
||||
mongodb-client-encryption:
|
||||
optional: true
|
||||
snappy:
|
||||
optional: true
|
||||
socks:
|
||||
optional: true
|
||||
|
||||
mongodb@6.9.0:
|
||||
resolution: {integrity: sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
@@ -7186,9 +7218,9 @@ packages:
|
||||
socks:
|
||||
optional: true
|
||||
|
||||
mongoose@7.8.2:
|
||||
resolution: {integrity: sha512-/KDcZL84gg8hnmOHRRPK49WtxH3Xsph38c7YqvYPdxEB2OsDAXvwAknGxyEC0F2P3RJCqFOp+523iFCa0p3dfw==}
|
||||
engines: {node: '>=14.20.1'}
|
||||
mongoose@8.10.2:
|
||||
resolution: {integrity: sha512-DvqfK1s/JLwP39ogXULC8ygNDdmDber5ZbxZzELYtkzl9VGJ3K5T2MCLdpTs9I9J6DnkDyIHJwt7IOyMxh/Adw==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
|
||||
mpath@0.9.0:
|
||||
resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==}
|
||||
@@ -8348,8 +8380,8 @@ packages:
|
||||
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
sift@16.0.1:
|
||||
resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==}
|
||||
sift@17.1.3:
|
||||
resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
@@ -12469,11 +12501,11 @@ snapshots:
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1': {}
|
||||
|
||||
'@shelf/jest-mongodb@4.3.2(jest-environment-node@29.7.0)(mongodb@6.9.0(socks@2.8.3))':
|
||||
'@shelf/jest-mongodb@4.3.2(jest-environment-node@29.7.0)(mongodb@6.13.1(socks@2.8.3))':
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
jest-environment-node: 29.7.0
|
||||
mongodb: 6.9.0(socks@2.8.3)
|
||||
mongodb: 6.13.1(socks@2.8.3)
|
||||
mongodb-memory-server: 9.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
@@ -13772,6 +13804,8 @@ snapshots:
|
||||
|
||||
bson@5.5.1: {}
|
||||
|
||||
bson@6.10.3: {}
|
||||
|
||||
bson@6.8.0: {}
|
||||
|
||||
buffer-alloc-unsafe@1.1.0: {}
|
||||
@@ -14913,7 +14947,7 @@ snapshots:
|
||||
eslint: 8.56.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
||||
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.56.0)
|
||||
eslint-plugin-react: 7.34.4(eslint@8.56.0)
|
||||
eslint-plugin-react-hooks: 4.6.2(eslint@8.56.0)
|
||||
@@ -14937,7 +14971,7 @@ snapshots:
|
||||
enhanced-resolve: 5.17.0
|
||||
eslint: 8.56.0
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.7.5
|
||||
is-core-module: 2.14.0
|
||||
@@ -14959,7 +14993,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0):
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
array.prototype.findlastindex: 1.2.5
|
||||
@@ -16637,7 +16671,7 @@ snapshots:
|
||||
jwa: 2.0.0
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
kareem@2.5.1: {}
|
||||
kareem@2.6.3: {}
|
||||
|
||||
katex@0.16.11:
|
||||
dependencies:
|
||||
@@ -17564,9 +17598,9 @@ snapshots:
|
||||
dependencies:
|
||||
obliterator: 2.0.4
|
||||
|
||||
mockingoose@2.16.2(mongoose@7.8.2):
|
||||
mockingoose@2.16.2(mongoose@8.10.2(socks@2.8.3)):
|
||||
dependencies:
|
||||
mongoose: 7.8.2
|
||||
mongoose: 8.10.2(socks@2.8.3)
|
||||
|
||||
monaco-editor@0.50.0: {}
|
||||
|
||||
@@ -17660,6 +17694,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@mongodb-js/saslprep': 1.1.9
|
||||
|
||||
mongodb@6.13.1(socks@2.8.3):
|
||||
dependencies:
|
||||
'@mongodb-js/saslprep': 1.1.9
|
||||
bson: 6.10.3
|
||||
mongodb-connection-string-url: 3.0.1
|
||||
optionalDependencies:
|
||||
socks: 2.8.3
|
||||
|
||||
mongodb@6.9.0(socks@2.8.3):
|
||||
dependencies:
|
||||
'@mongodb-js/saslprep': 1.1.9
|
||||
@@ -17668,21 +17710,23 @@ snapshots:
|
||||
optionalDependencies:
|
||||
socks: 2.8.3
|
||||
|
||||
mongoose@7.8.2:
|
||||
mongoose@8.10.2(socks@2.8.3):
|
||||
dependencies:
|
||||
bson: 5.5.1
|
||||
kareem: 2.5.1
|
||||
mongodb: 5.9.2
|
||||
bson: 6.10.3
|
||||
kareem: 2.6.3
|
||||
mongodb: 6.13.1(socks@2.8.3)
|
||||
mpath: 0.9.0
|
||||
mquery: 5.0.0
|
||||
ms: 2.1.3
|
||||
sift: 16.0.1
|
||||
sift: 17.1.3
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@mongodb-js/zstd'
|
||||
- gcp-metadata
|
||||
- kerberos
|
||||
- mongodb-client-encryption
|
||||
- snappy
|
||||
- socks
|
||||
- supports-color
|
||||
|
||||
mpath@0.9.0: {}
|
||||
@@ -19015,7 +19059,7 @@ snapshots:
|
||||
get-intrinsic: 1.2.4
|
||||
object-inspect: 1.13.2
|
||||
|
||||
sift@16.0.1: {}
|
||||
sift@17.1.3: {}
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ ROOT_KEY=fdafasd
|
||||
OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
# OpenAI API Key
|
||||
CHAT_API_KEY=sk-xxxx
|
||||
# ai proxy api
|
||||
AIPROXY_API_ENDPOINT=https://xxx.come
|
||||
AIPROXY_API_TOKEN=xxxxx
|
||||
|
||||
# 强制将图片转成 base64 传递给模型
|
||||
MULTIPLE_DATA_TO_BASE64=true
|
||||
|
||||
|
||||
@@ -105,22 +105,6 @@ function getWorkerConfig() {
|
||||
.isDirectory();
|
||||
});
|
||||
|
||||
/*
|
||||
{
|
||||
'worker/htmlStr2Md': path.resolve(
|
||||
process.cwd(),
|
||||
'../../packages/service/worker/htmlStr2Md/index.ts'
|
||||
),
|
||||
'worker/countGptMessagesTokens': path.resolve(
|
||||
process.cwd(),
|
||||
'../../packages/service/worker/countGptMessagesTokens/index.ts'
|
||||
),
|
||||
'worker/readFile': path.resolve(
|
||||
process.cwd(),
|
||||
'../../packages/service/worker/readFile/index.ts'
|
||||
)
|
||||
}
|
||||
*/
|
||||
const workerConfig = folderList.reduce((acc, item) => {
|
||||
acc[`worker/${item}`] = path.resolve(
|
||||
process.cwd(),
|
||||
|
||||
143
projects/app/src/components/Layout/WorkorderButton.tsx
Normal file
143
projects/app/src/components/Layout/WorkorderButton.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getWorkorderURL } from '@/web/common/workorder/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { StandardSubLevelEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useToggle } from 'ahooks';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const WorkOrderShowRouter: { [key: string]: boolean } = {
|
||||
'/app/list': true,
|
||||
'/dataset/list': true,
|
||||
'/toolkit': true
|
||||
};
|
||||
|
||||
function WorkorderButton() {
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useToggle(true);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { feConfigs, subPlans } = useSystemStore();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { runAsync: onFeedback } = useRequest2(getWorkorderURL, {
|
||||
manual: true,
|
||||
onSuccess(data) {
|
||||
if (data) {
|
||||
window.open(data.redirectUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const showWorkorder = WorkOrderShowRouter[router.pathname];
|
||||
|
||||
const isPlanUser = useMemo(() => {
|
||||
if (!teamPlanStatus) return false;
|
||||
if (teamPlanStatus.standard?.currentSubLevel !== StandardSubLevelEnum.free) return true;
|
||||
if (teamPlanStatus.datasetMaxSize !== subPlans?.standard?.free?.maxDatasetSize) return true;
|
||||
if (teamPlanStatus.totalPoints !== subPlans?.standard?.free?.totalPoints) return true;
|
||||
return false;
|
||||
}, [
|
||||
subPlans?.standard?.free?.maxDatasetSize,
|
||||
subPlans?.standard?.free?.totalPoints,
|
||||
teamPlanStatus
|
||||
]);
|
||||
|
||||
return showWorkorder && feConfigs?.show_workorder && isPlanUser && isPc ? (
|
||||
<>
|
||||
{open ? (
|
||||
<Flex
|
||||
position="fixed"
|
||||
bottom="10%"
|
||||
right="0"
|
||||
height="56px"
|
||||
width="56px"
|
||||
zIndex={100}
|
||||
boxShadow="0px 12px 32px -4px #00175633"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
direction="column"
|
||||
borderTopLeftRadius="8px"
|
||||
borderBottomLeftRadius="8px"
|
||||
border={'1px'}
|
||||
borderColor={'#DFE6F2'}
|
||||
>
|
||||
<Box
|
||||
zIndex={10}
|
||||
width="1rem"
|
||||
height="1rem"
|
||||
position="absolute"
|
||||
left="-6px"
|
||||
top="-6px"
|
||||
borderRadius="full"
|
||||
background="white"
|
||||
border="1px"
|
||||
borderColor={'myGray.100'}
|
||||
bgColor="myGray.25"
|
||||
_hover={{
|
||||
cursor: 'pointer',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
onClick={() => setOpen.set(false)}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Box>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
direction="column"
|
||||
bgColor="myGray.25"
|
||||
_hover={{
|
||||
cursor: 'pointer',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
width="100%"
|
||||
height="100%"
|
||||
borderTopLeftRadius="8px"
|
||||
borderBottomLeftRadius="8px"
|
||||
onClick={onFeedback}
|
||||
>
|
||||
<Icon name="feedback" width="24px" height="24px" />
|
||||
<Box fontSize="xs" fontWeight="500">
|
||||
{t('common:question_feedback')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
position="fixed"
|
||||
bottom="10%"
|
||||
right="0"
|
||||
height="44px"
|
||||
width="19px"
|
||||
bgColor="myGray.25"
|
||||
borderTopLeftRadius="8px"
|
||||
borderBottomLeftRadius="8px"
|
||||
border={'1px'}
|
||||
borderColor={'#DFE6F2'}
|
||||
zIndex={100}
|
||||
boxShadow="0px 12px 32px -4px #00175633"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
direction="column"
|
||||
_hover={{
|
||||
cursor: 'pointer',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
onClick={() => setOpen.set(true)}
|
||||
>
|
||||
<Icon name="core/chat/chevronLeft" width="16px" height="16px" />
|
||||
</Flex>
|
||||
)}
|
||||
</>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default WorkorderButton;
|
||||
@@ -14,6 +14,7 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useDebounceEffect, useMount } from 'ahooks';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import WorkorderButton from './WorkorderButton';
|
||||
|
||||
const Navbar = dynamic(() => import('./navbar'));
|
||||
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
||||
@@ -51,12 +52,12 @@ export const navbarWidth = '64px';
|
||||
const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { Loading } = useLoading();
|
||||
const { loading, feConfigs, notSufficientModalType, llmModelList, embeddingModelList } =
|
||||
useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { userInfo, isUpdateNotification, setIsUpdateNotification } = useUserStore();
|
||||
const { userInfo, teamPlanStatus, isUpdateNotification, setIsUpdateNotification } =
|
||||
useUserStore();
|
||||
const { setUserDefaultLng } = useI18nLng();
|
||||
|
||||
const isChatPage = useMemo(
|
||||
@@ -86,6 +87,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
});
|
||||
|
||||
// Check model invalid
|
||||
const { toast } = useToast();
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
if (userInfo?.username === 'root') {
|
||||
@@ -94,13 +96,13 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
status: 'warning',
|
||||
title: t('common:llm_model_not_config')
|
||||
});
|
||||
router.push('/account/model');
|
||||
router.pathname !== '/account/model' && router.push('/account/model');
|
||||
} else if (embeddingModelList.length === 0) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:embedding_model_not_config')
|
||||
});
|
||||
router.push('/account/model');
|
||||
router.pathname !== '/account/model' && router.push('/account/model');
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -159,6 +161,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
{!!userInfo && importantInforms.length > 0 && (
|
||||
<ImportantInform informs={importantInforms} refetch={refetchUnRead} />
|
||||
)}
|
||||
<WorkorderButton />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import styles from './index.module.scss';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { CodeClassNameEnum } from './utils';
|
||||
import { CodeClassNameEnum, mdTextFormat } from './utils';
|
||||
|
||||
const CodeLight = dynamic(() => import('./codeBlock/CodeLight'), { ssr: false });
|
||||
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false });
|
||||
@@ -54,36 +54,7 @@ const MarkdownRender = ({ source = '', showAnimation, isDisabled, forbidZhFormat
|
||||
|
||||
const formatSource = useMemo(() => {
|
||||
if (showAnimation || forbidZhFormat) return source;
|
||||
|
||||
// 保护 URL 格式:https://, http://, /api/xxx
|
||||
const urlPlaceholders: string[] = [];
|
||||
const textWithProtectedUrls = source.replace(
|
||||
/https?:\/\/(?:(?:[\w-]+\.)+[a-zA-Z]{2,6}|localhost)(?::\d{2,5})?(?:\/[\w\-./?%&=@]*)?/g,
|
||||
(match) => {
|
||||
urlPlaceholders.push(match);
|
||||
return `__URL_${urlPlaceholders.length - 1}__ `;
|
||||
}
|
||||
);
|
||||
|
||||
// 处理中文与英文数字之间的分词
|
||||
const textWithSpaces = textWithProtectedUrls
|
||||
.replace(
|
||||
/([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g,
|
||||
'$1$3 $2$4'
|
||||
)
|
||||
// 处理引用标记
|
||||
.replace(/\n*(\[QUOTE SIGN\]\(.*\))/g, '$1')
|
||||
// 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE)
|
||||
.replace(/\[quote:?\s*([a-f0-9]{24})\](?!\()/gi, '[$1](QUOTE)')
|
||||
.replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](QUOTE)');
|
||||
|
||||
// 还原 URL
|
||||
const finalText = textWithSpaces.replace(
|
||||
/__URL_(\d+)__/g,
|
||||
(_, index) => `${urlPlaceholders[parseInt(index)]}`
|
||||
);
|
||||
|
||||
return finalText;
|
||||
return mdTextFormat(source);
|
||||
}, [forbidZhFormat, showAnimation, source]);
|
||||
|
||||
const urlTransform = useCallback((val: string) => {
|
||||
|
||||
@@ -12,70 +12,34 @@ export enum CodeClassNameEnum {
|
||||
audio = 'audio'
|
||||
}
|
||||
|
||||
function htmlTableToLatex(html: string) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const table = doc.querySelector('table');
|
||||
|
||||
if (!table) return '';
|
||||
|
||||
let latex = '\\begin{tabular}{';
|
||||
|
||||
// 获取列数
|
||||
const columns = table.querySelectorAll('tr:first-child th, tr:first-child td').length;
|
||||
latex += '|' + 'c|'.repeat(columns) + '}\n\\hline\n';
|
||||
|
||||
// 创建一个二维数组来跟踪单元格合并情况
|
||||
const cellTracker = Array.from({ length: table.rows.length }, () => Array(columns).fill(false));
|
||||
|
||||
// 遍历行
|
||||
table.querySelectorAll('tr').forEach((row, rowIndex) => {
|
||||
const cells = row.querySelectorAll('th, td');
|
||||
let cellTexts: string[] = [];
|
||||
let colIndex = 0;
|
||||
|
||||
cells.forEach((cell) => {
|
||||
// 跳过已经被合并的单元格
|
||||
while (cellTracker[rowIndex][colIndex]) {
|
||||
colIndex++;
|
||||
export const mdTextFormat = (text: string) => {
|
||||
// NextChat function - Format latex to $$
|
||||
const escapeBrackets = (text: string) => {
|
||||
const pattern = /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g;
|
||||
return text.replace(pattern, (match, codeBlock, squareBracket, roundBracket) => {
|
||||
if (codeBlock) {
|
||||
return codeBlock;
|
||||
} else if (squareBracket) {
|
||||
return `$$${squareBracket}$$`;
|
||||
} else if (roundBracket) {
|
||||
return `$${roundBracket}$`;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const rowspan = parseInt(cell.getAttribute('rowspan') || 1, 10);
|
||||
// @ts-ignore
|
||||
const colspan = parseInt(cell.getAttribute('colspan') || 1, 10);
|
||||
|
||||
// 添加单元格内容
|
||||
let cellText = cell.textContent?.trim() || '';
|
||||
if (colspan > 1) {
|
||||
cellText = `\\multicolumn{${colspan}}{|c|}{${cellText}}`;
|
||||
}
|
||||
if (rowspan > 1) {
|
||||
cellText = `\\multirow{${rowspan}}{*}{${cellText}}`;
|
||||
}
|
||||
cellTexts.push(cellText);
|
||||
|
||||
// 标记合并的单元格
|
||||
for (let i = 0; i < rowspan; i++) {
|
||||
for (let j = 0; j < colspan; j++) {
|
||||
cellTracker[rowIndex + i][colIndex + j] = true;
|
||||
}
|
||||
}
|
||||
|
||||
colIndex += colspan;
|
||||
return match;
|
||||
});
|
||||
};
|
||||
// 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE)
|
||||
const formatQuote = (text: string) => {
|
||||
return (
|
||||
text
|
||||
// .replace(
|
||||
// /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g,
|
||||
// '$1$3 $2$4'
|
||||
// )
|
||||
// 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE)
|
||||
.replace(/\[quote:?\s*([a-f0-9]{24})\](?!\()/gi, '[$1](QUOTE)')
|
||||
.replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](QUOTE)')
|
||||
);
|
||||
};
|
||||
|
||||
latex += cellTexts.join(' & ') + ' \\\\\n\\hline\n';
|
||||
});
|
||||
|
||||
latex += '\\end{tabular}';
|
||||
|
||||
return `\`\`\`${CodeClassNameEnum.latex}
|
||||
${latex}
|
||||
\`\`\``;
|
||||
}
|
||||
|
||||
export function convertHtmlTablesToLatex(input: string) {
|
||||
const tableRegex = /<table[\s\S]*?<\/table>/gi;
|
||||
return input.replace(tableRegex, (match) => htmlTableToLatex(match));
|
||||
}
|
||||
return formatQuote(escapeBrackets(text));
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { getWebDefaultModel } from '@/web/common/system/utils';
|
||||
import { getWebDefaultLLMModel } from '@/web/common/system/utils';
|
||||
|
||||
type Props = {
|
||||
llmModelType?: `${LLMModelTypeEnum}`;
|
||||
@@ -40,7 +40,7 @@ const SettingLLMModel = ({
|
||||
[llmModelList, llmModelType]
|
||||
);
|
||||
const defaultModel = useMemo(() => {
|
||||
return getWebDefaultModel(modelList).model;
|
||||
return getWebDefaultLLMModel(modelList).model;
|
||||
}, [modelList]);
|
||||
|
||||
// Set default model
|
||||
|
||||
128
projects/app/src/global/aiproxy/constants.ts
Normal file
128
projects/app/src/global/aiproxy/constants.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { ModelProviderIdType } from '@fastgpt/global/core/ai/provider';
|
||||
import { ChannelInfoType } from './type';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
export enum ChannelStatusEnum {
|
||||
ChannelStatusUnknown = 0,
|
||||
ChannelStatusEnabled = 1,
|
||||
ChannelStatusDisabled = 2,
|
||||
ChannelStatusAutoDisabled = 3
|
||||
}
|
||||
export const ChannelStautsMap = {
|
||||
[ChannelStatusEnum.ChannelStatusUnknown]: {
|
||||
label: i18nT('account_model:channel_status_unknown'),
|
||||
colorSchema: 'gray'
|
||||
},
|
||||
[ChannelStatusEnum.ChannelStatusEnabled]: {
|
||||
label: i18nT('account_model:channel_status_enabled'),
|
||||
colorSchema: 'green'
|
||||
},
|
||||
[ChannelStatusEnum.ChannelStatusDisabled]: {
|
||||
label: i18nT('account_model:channel_status_disabled'),
|
||||
colorSchema: 'red'
|
||||
},
|
||||
[ChannelStatusEnum.ChannelStatusAutoDisabled]: {
|
||||
label: i18nT('account_model:channel_status_auto_disabled'),
|
||||
colorSchema: 'gray'
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultChannel: ChannelInfoType = {
|
||||
id: 0,
|
||||
status: ChannelStatusEnum.ChannelStatusEnabled,
|
||||
type: 1,
|
||||
created_at: 0,
|
||||
models: [],
|
||||
model_mapping: {},
|
||||
key: '',
|
||||
name: '',
|
||||
base_url: '',
|
||||
priority: 0
|
||||
};
|
||||
|
||||
export const aiproxyIdMap: Record<number, { label: string; provider: ModelProviderIdType }> = {
|
||||
1: {
|
||||
label: 'OpenAI',
|
||||
provider: 'OpenAI'
|
||||
},
|
||||
3: {
|
||||
label: i18nT('account_model:azure'),
|
||||
provider: 'OpenAI'
|
||||
},
|
||||
14: {
|
||||
label: 'Anthropic',
|
||||
provider: 'Claude'
|
||||
},
|
||||
12: {
|
||||
label: 'Google Gemini(OpenAI)',
|
||||
provider: 'Gemini'
|
||||
},
|
||||
24: {
|
||||
label: 'Google Gemini',
|
||||
provider: 'Gemini'
|
||||
},
|
||||
28: {
|
||||
label: 'Mistral AI',
|
||||
provider: 'MistralAI'
|
||||
},
|
||||
29: {
|
||||
label: 'Groq',
|
||||
provider: 'Groq'
|
||||
},
|
||||
17: {
|
||||
label: '阿里云',
|
||||
provider: 'Qwen'
|
||||
},
|
||||
40: {
|
||||
label: '豆包',
|
||||
provider: 'Doubao'
|
||||
},
|
||||
36: {
|
||||
label: 'DeepSeek AI',
|
||||
provider: 'DeepSeek'
|
||||
},
|
||||
13: {
|
||||
label: '百度智能云 V2',
|
||||
provider: 'Ernie'
|
||||
},
|
||||
15: {
|
||||
label: '百度智能云',
|
||||
provider: 'Ernie'
|
||||
},
|
||||
16: {
|
||||
label: '智谱 AI',
|
||||
provider: 'ChatGLM'
|
||||
},
|
||||
18: {
|
||||
label: '讯飞星火',
|
||||
provider: 'SparkDesk'
|
||||
},
|
||||
25: {
|
||||
label: '月之暗面',
|
||||
provider: 'Moonshot'
|
||||
},
|
||||
26: {
|
||||
label: '百川智能',
|
||||
provider: 'Baichuan'
|
||||
},
|
||||
27: {
|
||||
label: 'MiniMax',
|
||||
provider: 'MiniMax'
|
||||
},
|
||||
31: {
|
||||
label: '零一万物',
|
||||
provider: 'Yi'
|
||||
},
|
||||
32: {
|
||||
label: '阶跃星辰',
|
||||
provider: 'StepFun'
|
||||
},
|
||||
43: {
|
||||
label: 'SiliconFlow',
|
||||
provider: 'Siliconflow'
|
||||
},
|
||||
30: {
|
||||
label: 'Ollama',
|
||||
provider: 'Ollama'
|
||||
}
|
||||
};
|
||||
47
projects/app/src/global/aiproxy/type.d.ts
vendored
Normal file
47
projects/app/src/global/aiproxy/type.d.ts
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ChannelStatusEnum } from './constants';
|
||||
|
||||
export type ChannelInfoType = {
|
||||
model_mapping: Record<string, any>;
|
||||
key: string;
|
||||
name: string;
|
||||
base_url: string;
|
||||
models: any[];
|
||||
id: number;
|
||||
status: ChannelStatusEnum;
|
||||
type: number;
|
||||
created_at: number;
|
||||
priority: number;
|
||||
};
|
||||
|
||||
// Channel api
|
||||
export type ChannelListQueryType = {
|
||||
page: number;
|
||||
perPage: number;
|
||||
};
|
||||
export type ChannelListResponseType = ChannelInfoType[];
|
||||
|
||||
export type CreateChannelProps = {
|
||||
type: number;
|
||||
model_mapping: Record<string, any>;
|
||||
key?: string;
|
||||
name: string;
|
||||
base_url: string;
|
||||
models: string[];
|
||||
};
|
||||
|
||||
// Log
|
||||
export type ChannelLogListItemType = {
|
||||
token_name: string;
|
||||
model: string;
|
||||
request_id: string;
|
||||
id: number;
|
||||
channel: number;
|
||||
mode: number;
|
||||
created_at: number;
|
||||
request_at: number;
|
||||
code: number;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
endpoint: string;
|
||||
content?: string;
|
||||
};
|
||||
722
projects/app/src/pageComponents/account/model/AddModelBox.tsx
Normal file
722
projects/app/src/pageComponents/account/model/AddModelBox.tsx
Normal file
@@ -0,0 +1,722 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
HStack,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
Switch,
|
||||
ModalBody,
|
||||
Input,
|
||||
ModalFooter,
|
||||
Button,
|
||||
ButtonProps
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ModelProviderList,
|
||||
ModelProviderIdType,
|
||||
getModelProvider
|
||||
} from '@fastgpt/global/core/ai/provider';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { ModelTypeEnum } from '@fastgpt/global/core/ai/model';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getSystemModelDefaultConfig, putSystemModel } from '@/web/core/ai/config';
|
||||
import { SystemModelItemType } from '@fastgpt/service/core/ai/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { Prompt_CQJson, Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
|
||||
export const AddModelButton = ({
|
||||
onCreate,
|
||||
...props
|
||||
}: { onCreate: (type: ModelTypeEnum) => void } & ButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyMenu
|
||||
trigger="hover"
|
||||
size="sm"
|
||||
Button={<Button {...props}>{t('account:create_model')}</Button>}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
label: t('common:model.type.chat'),
|
||||
onClick: () => onCreate(ModelTypeEnum.llm)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.embedding'),
|
||||
onClick: () => onCreate(ModelTypeEnum.embedding)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.tts'),
|
||||
onClick: () => onCreate(ModelTypeEnum.tts)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.stt'),
|
||||
onClick: () => onCreate(ModelTypeEnum.stt)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.reRank'),
|
||||
onClick: () => onCreate(ModelTypeEnum.rerank)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const InputStyles = {
|
||||
maxW: '300px',
|
||||
bg: 'myGray.50',
|
||||
w: '100%',
|
||||
rows: 3
|
||||
};
|
||||
export const ModelEditModal = ({
|
||||
modelData,
|
||||
onSuccess,
|
||||
onClose
|
||||
}: {
|
||||
modelData: SystemModelItemType;
|
||||
onSuccess: () => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch, reset } =
|
||||
useForm<SystemModelItemType>({
|
||||
defaultValues: modelData
|
||||
});
|
||||
|
||||
const isCustom = !!modelData.isCustom;
|
||||
const isLLMModel = modelData?.type === ModelTypeEnum.llm;
|
||||
const isEmbeddingModel = modelData?.type === ModelTypeEnum.embedding;
|
||||
const isTTSModel = modelData?.type === ModelTypeEnum.tts;
|
||||
const isSTTModel = modelData?.type === ModelTypeEnum.stt;
|
||||
const isRerankModel = modelData?.type === ModelTypeEnum.rerank;
|
||||
|
||||
const provider = watch('provider');
|
||||
const providerData = useMemo(() => getModelProvider(provider), [provider]);
|
||||
|
||||
const providerList = useRef<{ label: any; value: ModelProviderIdType }[]>(
|
||||
ModelProviderList.map((item) => ({
|
||||
label: (
|
||||
<HStack>
|
||||
<Avatar src={item.avatar} w={'1rem'} />
|
||||
<Box>{t(item.name as any)}</Box>
|
||||
</HStack>
|
||||
),
|
||||
value: item.id
|
||||
}))
|
||||
);
|
||||
|
||||
const priceUnit = useMemo(() => {
|
||||
if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens';
|
||||
if (isTTSModel) return `/ 1k ${t('common:unit.character')}`;
|
||||
if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`;
|
||||
return '';
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]);
|
||||
|
||||
const { runAsync: updateModel, loading: updatingModel } = useRequest2(
|
||||
async (data: SystemModelItemType) => {
|
||||
return putSystemModel({
|
||||
model: data.model,
|
||||
metadata: data
|
||||
}).then(onSuccess);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
}
|
||||
);
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2(
|
||||
getSystemModelDefaultConfig,
|
||||
{
|
||||
onSuccess(res) {
|
||||
reset({
|
||||
...getValues(),
|
||||
...res
|
||||
});
|
||||
setTimeout(() => {
|
||||
setKey((prev) => prev + 1);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'modal/edit'}
|
||||
title={t('account:model.edit_model')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '80vw']}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex gap={4} key={key}>
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.model_id')}</Box>
|
||||
<QuestionTip label={t('account:model.model_id_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
{isCustom ? (
|
||||
<Input {...register('model', { required: true })} {...InputStyles} />
|
||||
) : (
|
||||
modelData?.model
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:model.provider')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MySelect
|
||||
value={provider}
|
||||
onchange={(value) => setValue('provider', value)}
|
||||
list={providerList.current}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.alias')}</Box>
|
||||
<QuestionTip label={t('account:model.alias_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('name', { required: true })} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
{priceUnit && feConfigs?.isPlus && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.charsPointsPrice')}</Box>
|
||||
<QuestionTip label={t('account:model.charsPointsPrice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'charsPointsPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.input_price')}</Box>
|
||||
<QuestionTip label={t('account:model.input_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'inputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.output_price')}</Box>
|
||||
<QuestionTip label={t('account:model.output_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'outputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxContext"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_quote')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="quoteMaxToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.chat.response.module maxToken')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput register={register} name="maxResponse" {...InputStyles} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_temperature')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
name="maxTemperature"
|
||||
step={0.1}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_top_p')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showTopP')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_stop_sign')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showStopSign')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.response_format')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('responseFormatList'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('responseFormatList', []);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('responseFormatList', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isEmbeddingModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.normalization')}</Box>
|
||||
<QuestionTip label={t('account:model.normalization_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('normalization')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_token')}</Box>
|
||||
<QuestionTip label={t('account:model.default_token_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="defaultToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.defaultConfig')}</Box>
|
||||
<QuestionTip label={t('account:model.defaultConfig_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isTTSModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.voices')}</Box>
|
||||
<QuestionTip label={t('account:model.voices_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('voices'), null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setValue('voices', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_url')}</Box>
|
||||
<QuestionTip label={t('account:model.request_url_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestUrl')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_auth')}</Box>
|
||||
<QuestionTip label={t('account:model.request_auth_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestAuth')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{isLLMModel && (
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.tool_choice')}</Box>
|
||||
<QuestionTip label={t('account:model.tool_choice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('toolChoice')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.function_call')}</Box>
|
||||
<QuestionTip label={t('account:model.function_call_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('functionCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.vision')}</Box>
|
||||
<QuestionTip label={t('account:model.vision_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('vision')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.reasoning')}</Box>
|
||||
<QuestionTip label={t('account:model.reasoning_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('reasoning')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{feConfigs?.isPlus && (
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.censor')}</Box>
|
||||
<QuestionTip label={t('account:model.censor_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('censor')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>{t('account:model.dataset_process')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('datasetProcess')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_classify')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInClassify')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_extract_fields')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInExtractFields')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_tool_call')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInToolCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_system_chat_prompt')}</Box>
|
||||
<QuestionTip label={t('account:model.default_system_chat_prompt_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('defaultSystemChatPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_cq_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_cq_prompt_tip', { prompt: Prompt_CQJson })}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customCQPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_extract_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_extract_prompt_tip', {
|
||||
prompt: Prompt_ExtractJson
|
||||
})}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customExtractPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_config')}</Box>
|
||||
<QuestionTip label={t('account:model.default_config_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!modelData.isCustom && (
|
||||
<Button
|
||||
isLoading={loadingDefaultConfig}
|
||||
variant={'whiteBase'}
|
||||
mr={4}
|
||||
onClick={() => loadDefaultConfig(modelData.model)}
|
||||
>
|
||||
{t('account:reset_default')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={'whiteBase'} mr={4} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={updatingModel} onClick={handleSubmit(updateModel)}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Dom() {
|
||||
return <></>;
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
import { aiproxyIdMap } from '@/global/aiproxy/constants';
|
||||
import { ChannelInfoType } from '@/global/aiproxy/type';
|
||||
import {
|
||||
Box,
|
||||
BoxProps,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
MenuItemProps,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
useDisclosure,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
HStack,
|
||||
useOutsideClick
|
||||
} from '@chakra-ui/react';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { AddModelButton } from '../AddModelBox';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { SystemModelItemType } from '@fastgpt/service/core/ai/type';
|
||||
import { ModelTypeEnum } from '@fastgpt/global/core/ai/model';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getSystemModelList } from '@/web/core/ai/config';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyAvatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import { getChannelProviders, postCreateChannel, putChannel } from '@/web/core/ai/channel';
|
||||
import CopyBox from '@fastgpt/web/components/common/String/CopyBox';
|
||||
|
||||
const ModelEditModal = dynamic(() => import('../AddModelBox').then((mod) => mod.ModelEditModal));
|
||||
|
||||
const LabelStyles: BoxProps = {
|
||||
fontSize: 'sm',
|
||||
color: 'myGray.900',
|
||||
flex: '0 0 70px'
|
||||
};
|
||||
const EditChannelModal = ({
|
||||
defaultConfig,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultConfig: ChannelInfoType;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { defaultModels } = useSystemStore();
|
||||
const isEdit = defaultConfig.id !== 0;
|
||||
|
||||
const { register, handleSubmit, watch, setValue } = useForm({
|
||||
defaultValues: defaultConfig
|
||||
});
|
||||
|
||||
const providerType = watch('type');
|
||||
const { data: providerList = [] } = useRequest2(
|
||||
() =>
|
||||
getChannelProviders().then((res) => {
|
||||
return Object.entries(res)
|
||||
.map(([key, value]) => {
|
||||
const mapData = aiproxyIdMap[key as any] ?? {
|
||||
label: value.name,
|
||||
provider: 'Other'
|
||||
};
|
||||
const provider = getModelProvider(mapData.provider);
|
||||
return {
|
||||
order: provider.order,
|
||||
defaultBaseUrl: value.defaultBaseUrl,
|
||||
keyHelp: value.keyHelp,
|
||||
icon: provider.avatar,
|
||||
label: t(mapData.label as any),
|
||||
value: Number(key)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
const selectedProvider = useMemo(() => {
|
||||
const res = providerList.find((item) => item.value === providerType);
|
||||
return res;
|
||||
}, [providerList, providerType]);
|
||||
|
||||
const [editModelData, setEditModelData] = useState<SystemModelItemType>();
|
||||
const onCreateModel = (type: ModelTypeEnum) => {
|
||||
const defaultModel = defaultModels[type];
|
||||
|
||||
setEditModelData({
|
||||
...defaultModel,
|
||||
model: '',
|
||||
name: '',
|
||||
charsPointsPrice: 0,
|
||||
inputPrice: undefined,
|
||||
outputPrice: undefined,
|
||||
|
||||
isCustom: true,
|
||||
isActive: true,
|
||||
// @ts-ignore
|
||||
type
|
||||
});
|
||||
};
|
||||
|
||||
const models = watch('models');
|
||||
const {
|
||||
data: systemModelList = [],
|
||||
runAsync: refreshSystemModelList,
|
||||
loading: loadingModels
|
||||
} = useRequest2(getSystemModelList, {
|
||||
manual: false
|
||||
});
|
||||
const modelList = useMemo(() => {
|
||||
const currentProvider = aiproxyIdMap[providerType]?.provider;
|
||||
return systemModelList
|
||||
.map((item) => {
|
||||
const provider = getModelProvider(item.provider);
|
||||
|
||||
return {
|
||||
provider: item.provider,
|
||||
icon: provider.avatar,
|
||||
label: item.model,
|
||||
value: item.model
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// sort by provider, same provider first
|
||||
if (a.provider === currentProvider && b.provider !== currentProvider) return -1;
|
||||
if (a.provider !== currentProvider && b.provider === currentProvider) return 1;
|
||||
return 0;
|
||||
});
|
||||
}, [providerType, systemModelList]);
|
||||
|
||||
const modelMapping = watch('model_mapping');
|
||||
|
||||
const { runAsync: onSubmit, loading: loadingCreate } = useRequest2(
|
||||
(data: ChannelInfoType) => {
|
||||
if (data.models.length === 0) {
|
||||
return Promise.reject(t('account_model:selected_model_empty'));
|
||||
}
|
||||
return isEdit ? putChannel(data) : postCreateChannel(data);
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
successToast: isEdit ? t('common:common.Update Success') : t('common:common.Create Success'),
|
||||
manual: true
|
||||
}
|
||||
);
|
||||
|
||||
const isLoading = loadingModels || loadingCreate;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
isLoading={isLoading}
|
||||
iconSrc={'modal/setting'}
|
||||
title={t('account_model:edit_channel')}
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '800px']}
|
||||
>
|
||||
<ModalBody>
|
||||
{/* Chnnel name */}
|
||||
<Box>
|
||||
<FormLabel required {...LabelStyles}>
|
||||
{t('account_model:channel_name')}
|
||||
</FormLabel>
|
||||
<Input mt={1} {...register('name', { required: true })} />
|
||||
</Box>
|
||||
{/* Provider */}
|
||||
<Box alignItems={'center'} mt={4}>
|
||||
<FormLabel required {...LabelStyles}>
|
||||
{t('account_model:channel_type')}
|
||||
</FormLabel>
|
||||
<Box mt={1}>
|
||||
<MySelect
|
||||
list={providerList}
|
||||
placeholder={t('account_model:select_provider_placeholder')}
|
||||
value={providerType}
|
||||
isSearch
|
||||
onchange={(val) => {
|
||||
setValue('type', val);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* Model */}
|
||||
<Box mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel required flex={'1 0 0'}>
|
||||
{t('account_model:model')}({models.length})
|
||||
</FormLabel>
|
||||
|
||||
<AddModelButton onCreate={onCreateModel} size={'sm'} variant={'outline'} />
|
||||
<Button ml={2} size={'sm'} variant={'outline'} onClick={() => setValue('models', [])}>
|
||||
{t('account_model:clear_model')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box mt={2}>
|
||||
<MultipleSelect
|
||||
value={models}
|
||||
list={modelList}
|
||||
onSelect={(val) => {
|
||||
setValue('models', val);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* Mapping */}
|
||||
<Box mt={4}>
|
||||
<HStack>
|
||||
<FormLabel>{t('account_model:mapping')}</FormLabel>
|
||||
<QuestionTip label={t('account_model:mapping_tip')} />
|
||||
</HStack>
|
||||
<Box mt={2}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(modelMapping, null, 2)}
|
||||
onChange={(val) => {
|
||||
if (!val) {
|
||||
setValue('model_mapping', {});
|
||||
} else {
|
||||
try {
|
||||
setValue('model_mapping', JSON.parse(val));
|
||||
} catch (error) {}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* url and key */}
|
||||
<Box mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('account_model:base_url')}</FormLabel>
|
||||
{selectedProvider && (
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
<Box>{'('}</Box>
|
||||
<Box mr={1}>{t('account_model:default_url')}:</Box>
|
||||
<CopyBox value={selectedProvider?.defaultBaseUrl || ''}>
|
||||
{selectedProvider?.defaultBaseUrl || ''}
|
||||
</CopyBox>
|
||||
<Box>{')'}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<Input
|
||||
mt={1}
|
||||
{...register('base_url')}
|
||||
placeholder={selectedProvider?.defaultBaseUrl || 'https://api.openai.com/v1'}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('account_model:api_key')}</FormLabel>
|
||||
{selectedProvider?.keyHelp && (
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
<Box>{'('}</Box>
|
||||
<Box mr={1}>{t('account_model:key_type')}</Box>
|
||||
<Box>{selectedProvider.keyHelp}</Box>
|
||||
<Box>{')'}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<Input
|
||||
mt={1}
|
||||
{...register('key')}
|
||||
placeholder={selectedProvider?.keyHelp || 'sk-1234567890'}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'outline'} onClick={onClose} mr={4}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button variant={'primary'} onClick={handleSubmit(onSubmit)}>
|
||||
{isEdit ? t('common:common.Update') : t('common:new_create')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
{!!editModelData && (
|
||||
<ModelEditModal
|
||||
modelData={editModelData}
|
||||
onSuccess={refreshSystemModelList}
|
||||
onClose={() => setEditModelData(undefined)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default EditChannelModal;
|
||||
|
||||
type SelectProps = {
|
||||
list: {
|
||||
icon?: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
value: string[];
|
||||
onSelect: (val: string[]) => void;
|
||||
};
|
||||
const menuItemStyles: MenuItemProps = {
|
||||
borderRadius: 'sm',
|
||||
py: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
_hover: {
|
||||
backgroundColor: 'myGray.100'
|
||||
},
|
||||
_notLast: {
|
||||
mb: 0.5
|
||||
}
|
||||
};
|
||||
const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const BoxRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const onclickItem = useCallback(
|
||||
(val: string) => {
|
||||
if (value.includes(val)) {
|
||||
onSelect(value.filter((i) => i !== val));
|
||||
} else {
|
||||
onSelect([...value, val]);
|
||||
BoxRef.current?.scrollTo({
|
||||
top: BoxRef.current.scrollHeight
|
||||
});
|
||||
}
|
||||
},
|
||||
[value, onSelect]
|
||||
);
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filterUnSelected = useMemo(() => {
|
||||
return list
|
||||
.filter((item) => !value.includes(item.value))
|
||||
.filter((item) => {
|
||||
if (!search) return true;
|
||||
const regx = new RegExp(search, 'i');
|
||||
return regx.test(item.label);
|
||||
});
|
||||
}, [list, value, search]);
|
||||
|
||||
useOutsideClick({
|
||||
ref,
|
||||
handler: () => {
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<Menu autoSelect={false} isOpen={isOpen} strategy={'fixed'} matchWidth closeOnSelect={false}>
|
||||
<Box
|
||||
position={'relative'}
|
||||
py={2}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
userSelect={'none'}
|
||||
cursor={'pointer'}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
_hover={{
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
{...(isOpen
|
||||
? {
|
||||
boxShadow: '0px 0px 4px #A8DBFF',
|
||||
borderColor: 'primary.500',
|
||||
onClick: onClose
|
||||
}
|
||||
: {
|
||||
onClick: () => {
|
||||
onOpen();
|
||||
setSearch('');
|
||||
}
|
||||
})}
|
||||
>
|
||||
<MenuButton zIndex={0} position={'absolute'} bottom={0} left={0} right={0} top={0} />
|
||||
<Flex
|
||||
ref={BoxRef}
|
||||
position={'relative'}
|
||||
alignItems={value.length === 0 ? 'center' : 'flex-start'}
|
||||
gap={2}
|
||||
px={2}
|
||||
pb={0}
|
||||
overflowY={'auto'}
|
||||
maxH={'200px'}
|
||||
>
|
||||
{value.length === 0 ? (
|
||||
<Box flex={'1 0 0'} color={'myGray.500'} fontSize={'xs'}>
|
||||
{t('account_model:select_model_placeholder')}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex flex={'1 0 0'} alignItems={'center'} gap={2} flexWrap={'wrap'}>
|
||||
{value.map((item) => (
|
||||
<MyTag
|
||||
key={item}
|
||||
type="borderSolid"
|
||||
colorSchema="gray"
|
||||
bg={'myGray.150'}
|
||||
color={'myGray.900'}
|
||||
_hover={{
|
||||
bg: 'myGray.250'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyData(item, t('account_model:copy_model_id_success'));
|
||||
}}
|
||||
>
|
||||
<Box>{item}</Box>
|
||||
<MyIcon
|
||||
ml={0.5}
|
||||
name={'common/closeLight'}
|
||||
w={'14px'}
|
||||
h={'14px'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onclickItem(item);
|
||||
}}
|
||||
/>
|
||||
</MyTag>
|
||||
))}
|
||||
{isOpen && (
|
||||
<Input
|
||||
key={'search'}
|
||||
variant={'unstyled'}
|
||||
w={'150px'}
|
||||
h={'24px'}
|
||||
autoFocus
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t('account_model:search_model')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
<MyIcon name={'core/chat/chevronDown'} color={'myGray.600'} w={4} h={4} />
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<MenuList
|
||||
px={'6px'}
|
||||
py={'6px'}
|
||||
border={'1px solid #fff'}
|
||||
boxShadow={
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10);'
|
||||
}
|
||||
zIndex={99}
|
||||
maxH={'40vh'}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{filterUnSelected.map((item, i) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={i}
|
||||
color={'myGray.900'}
|
||||
onClick={(e) => {
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
{...menuItemStyles}
|
||||
>
|
||||
{item.icon && <MyAvatar src={item.icon} w={'1rem'} borderRadius={'0'} />}
|
||||
<Box flex={'1 0 0'}>{item.label}</Box>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
import { getSystemModelList, getTestModel } from '@/web/core/ai/config';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { batchRun } from '@fastgpt/global/common/fn/utils';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
type ModelTestItem = {
|
||||
label: React.ReactNode;
|
||||
model: string;
|
||||
status: 'waiting' | 'running' | 'success' | 'error';
|
||||
message?: string;
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [testModelList, setTestModelList] = useState<ModelTestItem[]>([]);
|
||||
|
||||
const statusMap = useRef({
|
||||
waiting: {
|
||||
label: t('account_model:waiting_test'),
|
||||
colorSchema: 'gray'
|
||||
},
|
||||
running: {
|
||||
label: t('account_model:running_test'),
|
||||
colorSchema: 'blue'
|
||||
},
|
||||
success: {
|
||||
label: t('common:common.Success'),
|
||||
colorSchema: 'green'
|
||||
},
|
||||
error: {
|
||||
label: t('common:common.failed'),
|
||||
colorSchema: 'red'
|
||||
}
|
||||
});
|
||||
const { loading: loadingModels } = useRequest2(getSystemModelList, {
|
||||
manual: false,
|
||||
refreshDeps: [models],
|
||||
onSuccess(res) {
|
||||
const list = models
|
||||
.map((model) => {
|
||||
const modelData = res.find((item) => item.model === model);
|
||||
if (!modelData) return null;
|
||||
const provider = getModelProvider(modelData.provider);
|
||||
|
||||
return {
|
||||
label: (
|
||||
<HStack>
|
||||
<MyIcon name={provider.avatar as any} w={'1rem'} />
|
||||
<Box>{t(modelData.name as any)}</Box>
|
||||
</HStack>
|
||||
),
|
||||
model: modelData.model,
|
||||
status: 'waiting'
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as ModelTestItem[];
|
||||
setTestModelList(list);
|
||||
}
|
||||
});
|
||||
|
||||
const { runAsync: onStartTest, loading: isTesting } = useRequest2(
|
||||
async () => {
|
||||
{
|
||||
let errorNum = 0;
|
||||
const testModel = async (model: string) => {
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'running', message: '' } : item
|
||||
)
|
||||
);
|
||||
const start = Date.now();
|
||||
try {
|
||||
await getTestModel(model);
|
||||
const duration = Date.now() - start;
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model
|
||||
? { ...item, status: 'success', duration: duration / 1000 }
|
||||
: item
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model
|
||||
? { ...item, status: 'error', message: getErrText(error) }
|
||||
: item
|
||||
)
|
||||
);
|
||||
errorNum++;
|
||||
}
|
||||
};
|
||||
|
||||
await batchRun(
|
||||
testModelList.map((item) => item.model),
|
||||
testModel,
|
||||
5
|
||||
);
|
||||
|
||||
if (errorNum > 0) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('account_model:test_failed', { num: errorNum })
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
refreshDeps: [testModelList]
|
||||
}
|
||||
);
|
||||
|
||||
console.log(testModelList);
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'core/chat/sendLight'}
|
||||
isLoading={loadingModels}
|
||||
title={t('account_model:model_test')}
|
||||
w={'600px'}
|
||||
isOpen
|
||||
>
|
||||
<ModalBody>
|
||||
<TableContainer h={'100%'} overflowY={'auto'} fontSize={'sm'} maxH={'60vh'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('account_model:model')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{testModelList.map((item) => {
|
||||
const data = statusMap.current[item.status];
|
||||
return (
|
||||
<Tr key={item.model}>
|
||||
<Td>{item.label}</Td>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTag mr={1} type="borderSolid" colorSchema={data.colorSchema as any}>
|
||||
{data.label}
|
||||
</MyTag>
|
||||
{item.message && <QuestionTip label={item.message} />}
|
||||
{item.status === 'success' && item.duration && (
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('account_model:request_duration', {
|
||||
duration: item.duration.toFixed(2)
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={4} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isTesting} variant={'primary'} onClick={onStartTest}>
|
||||
{t('account_model:start_test', { num: testModelList.length })}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelTest;
|
||||
230
projects/app/src/pageComponents/account/model/Channel/index.tsx
Normal file
230
projects/app/src/pageComponents/account/model/Channel/index.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
import { deleteChannel, getChannelList, putChannel, putChannelStatus } from '@/web/core/ai/channel';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { ChannelInfoType } from '@/global/aiproxy/type';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import {
|
||||
aiproxyIdMap,
|
||||
ChannelStatusEnum,
|
||||
ChannelStautsMap,
|
||||
defaultChannel
|
||||
} from '@/global/aiproxy/constants';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import dynamic from 'next/dynamic';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const EditChannelModal = dynamic(() => import('./EditChannelModal'), { ssr: false });
|
||||
const ModelTest = dynamic(() => import('./ModelTest'), { ssr: false });
|
||||
|
||||
const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const isRoot = userInfo?.username === 'root';
|
||||
|
||||
const {
|
||||
data: channelList = [],
|
||||
runAsync: refreshChannelList,
|
||||
loading: loadingChannelList
|
||||
} = useRequest2(getChannelList, {
|
||||
manual: false
|
||||
});
|
||||
|
||||
const [editChannel, setEditChannel] = useState<ChannelInfoType>();
|
||||
|
||||
const { runAsync: updateChannel, loading: loadingUpdateChannel } = useRequest2(putChannel, {
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
refreshChannelList();
|
||||
}
|
||||
});
|
||||
const { runAsync: updateChannelStatus, loading: loadingUpdateChannelStatus } = useRequest2(
|
||||
putChannelStatus,
|
||||
{
|
||||
onSuccess: () => {
|
||||
refreshChannelList();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { runAsync: onDeleteChannel, loading: loadingDeleteChannel } = useRequest2(deleteChannel, {
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
refreshChannelList();
|
||||
}
|
||||
});
|
||||
|
||||
const [testModels, setTestModels] = useState<string[]>();
|
||||
|
||||
const isLoading =
|
||||
loadingChannelList ||
|
||||
loadingUpdateChannel ||
|
||||
loadingDeleteChannel ||
|
||||
loadingUpdateChannelStatus;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRoot && (
|
||||
<Flex alignItems={'center'}>
|
||||
{Tab}
|
||||
<Box flex={1} />
|
||||
<Button variant={'whiteBase'} mr={2} onClick={() => setEditChannel(defaultChannel)}>
|
||||
{t('account_model:create_channel')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<MyBox flex={'1 0 0'} h={0} isLoading={isLoading}>
|
||||
<TableContainer h={'100%'} overflowY={'auto'} fontSize={'sm'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>ID</Th>
|
||||
<Th>{t('account_model:channel_name')}</Th>
|
||||
<Th>{t('account_model:channel_type')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
<Th>
|
||||
{t('account_model:channel_priority')}
|
||||
<QuestionTip label={t('account_model:channel_priority_tip')} />
|
||||
</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{channelList.map((item) => {
|
||||
const providerData = aiproxyIdMap[item.type];
|
||||
const provider = getModelProvider(providerData?.provider);
|
||||
|
||||
return (
|
||||
<Tr key={item.id} _hover={{ bg: 'myGray.100' }}>
|
||||
<Td>{item.id}</Td>
|
||||
<Td>{item.name}</Td>
|
||||
<Td>
|
||||
{providerData ? (
|
||||
<HStack>
|
||||
<MyIcon name={provider?.avatar as any} w={'1rem'} />
|
||||
<Box>{t(providerData?.label as any)}</Box>
|
||||
</HStack>
|
||||
) : (
|
||||
'Invalid provider'
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<MyTag
|
||||
colorSchema={ChannelStautsMap[item.status]?.colorSchema as any}
|
||||
type="borderFill"
|
||||
>
|
||||
{t(ChannelStautsMap[item.status]?.label as any) ||
|
||||
t('account_model:channel_status_unknown')}
|
||||
</MyTag>
|
||||
</Td>
|
||||
<Td>
|
||||
<MyNumberInput
|
||||
defaultValue={item.priority || 0}
|
||||
min={0}
|
||||
max={100}
|
||||
h={'32px'}
|
||||
w={'80px'}
|
||||
onBlur={(e) => {
|
||||
const val = (() => {
|
||||
if (!e) return 0;
|
||||
return e;
|
||||
})();
|
||||
updateChannel({
|
||||
...item,
|
||||
priority: val
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<MyMenu
|
||||
menuList={[
|
||||
{
|
||||
label: '',
|
||||
children: [
|
||||
{
|
||||
icon: 'core/chat/sendLight',
|
||||
label: t('account_model:model_test'),
|
||||
onClick: () => setTestModels(item.models)
|
||||
},
|
||||
...(item.status === ChannelStatusEnum.ChannelStatusEnabled
|
||||
? [
|
||||
{
|
||||
icon: 'common/disable',
|
||||
label: t('account_model:forbid_channel'),
|
||||
onClick: () =>
|
||||
updateChannelStatus(
|
||||
item.id,
|
||||
ChannelStatusEnum.ChannelStatusDisabled
|
||||
)
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
icon: 'common/enable',
|
||||
label: t('account_model:enable_channel'),
|
||||
onClick: () =>
|
||||
updateChannelStatus(
|
||||
item.id,
|
||||
ChannelStatusEnum.ChannelStatusEnabled
|
||||
)
|
||||
}
|
||||
]),
|
||||
{
|
||||
icon: 'common/settingLight',
|
||||
label: t('account_model:edit'),
|
||||
onClick: () => setEditChannel(item)
|
||||
},
|
||||
{
|
||||
type: 'danger',
|
||||
icon: 'delete',
|
||||
label: t('common:common.Delete'),
|
||||
onClick: () => onDeleteChannel(item.id)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
Button={<MyIconButton icon={'more'} />}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</MyBox>
|
||||
|
||||
{!!editChannel && (
|
||||
<EditChannelModal
|
||||
defaultConfig={editChannel}
|
||||
onClose={() => setEditChannel(undefined)}
|
||||
onSuccess={refreshChannelList}
|
||||
/>
|
||||
)}
|
||||
{!!testModels && <ModelTest models={testModels} onClose={() => setTestModels(undefined)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelTable;
|
||||
406
projects/app/src/pageComponents/account/model/Log/index.tsx
Normal file
406
projects/app/src/pageComponents/account/model/Log/index.tsx
Normal file
@@ -0,0 +1,406 @@
|
||||
import { getChannelList, getChannelLog, getLogDetail } from '@/web/core/ai/channel';
|
||||
import { getSystemModelList } from '@/web/core/ai/config';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
HStack,
|
||||
ModalBody,
|
||||
Grid,
|
||||
GridItem,
|
||||
BoxProps
|
||||
} from '@chakra-ui/react';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { addDays } from 'date-fns';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
type LogDetailType = {
|
||||
id: number;
|
||||
request_id: string;
|
||||
channelName: string | number;
|
||||
model: React.JSX.Element;
|
||||
duration: number;
|
||||
request_at: string;
|
||||
code: number;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
endpoint: string;
|
||||
|
||||
content?: string;
|
||||
request_body?: string;
|
||||
response_body?: string;
|
||||
};
|
||||
const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const isRoot = userInfo?.username === 'root';
|
||||
const [filterProps, setFilterProps] = useState<{
|
||||
channelId?: string;
|
||||
model?: string;
|
||||
code_type: 'all' | 'success' | 'error';
|
||||
dateRange: DateRangeType;
|
||||
}>({
|
||||
code_type: 'all',
|
||||
dateRange: {
|
||||
from: (() => {
|
||||
const today = addDays(new Date(), -1);
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return today;
|
||||
})(),
|
||||
to: (() => {
|
||||
const today = new Date();
|
||||
today.setHours(23, 59, 59, 999);
|
||||
return today;
|
||||
})()
|
||||
}
|
||||
});
|
||||
|
||||
const { data: channelList = [] } = useRequest2(
|
||||
async () => {
|
||||
const res = await getChannelList().then((res) =>
|
||||
res.map((item) => ({
|
||||
label: item.name,
|
||||
value: `${item.id}`
|
||||
}))
|
||||
);
|
||||
return [
|
||||
{
|
||||
label: t('common:common.All'),
|
||||
value: ''
|
||||
},
|
||||
...res
|
||||
];
|
||||
},
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { data: systemModelList = [] } = useRequest2(getSystemModelList, {
|
||||
manual: false
|
||||
});
|
||||
const modelList = useMemo(() => {
|
||||
const res = systemModelList
|
||||
.map((item) => {
|
||||
const provider = getModelProvider(item.provider);
|
||||
|
||||
return {
|
||||
order: provider.order,
|
||||
icon: provider.avatar,
|
||||
label: item.model,
|
||||
value: item.model
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
return [
|
||||
{
|
||||
label: t('common:common.All'),
|
||||
value: ''
|
||||
},
|
||||
...res
|
||||
];
|
||||
}, [systemModelList]);
|
||||
|
||||
const { data, isLoading, ScrollData } = useScrollPagination(getChannelLog, {
|
||||
pageSize: 20,
|
||||
refreshDeps: [filterProps],
|
||||
params: {
|
||||
channel: filterProps.channelId,
|
||||
model_name: filterProps.model,
|
||||
code_type: filterProps.code_type,
|
||||
start_timestamp: filterProps.dateRange.from?.getTime() || 0,
|
||||
end_timestamp: filterProps.dateRange.to?.getTime() || 0
|
||||
}
|
||||
});
|
||||
|
||||
const formatData = useMemo<LogDetailType[]>(() => {
|
||||
return data.map((item) => {
|
||||
const duration = item.created_at - item.request_at;
|
||||
const durationSecond = duration / 1000;
|
||||
|
||||
const channelName = channelList.find((channel) => channel.value === `${item.channel}`)?.label;
|
||||
|
||||
const model = systemModelList.find((model) => model.model === item.model);
|
||||
const provider = getModelProvider(model?.provider);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
channelName: channelName || item.channel,
|
||||
model: (
|
||||
<HStack>
|
||||
<MyIcon name={provider?.avatar as any} w={'1rem'} />
|
||||
<Box>{model?.model}</Box>
|
||||
</HStack>
|
||||
),
|
||||
duration: durationSecond,
|
||||
request_at: formatTime2YMDHMS(item.request_at),
|
||||
code: item.code,
|
||||
prompt_tokens: item.prompt_tokens,
|
||||
completion_tokens: item.completion_tokens,
|
||||
request_id: item.request_id,
|
||||
endpoint: item.endpoint,
|
||||
content: item.content
|
||||
};
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
const [logDetail, setLogDetail] = useState<LogDetailType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRoot && (
|
||||
<Flex alignItems={'center'}>
|
||||
{Tab}
|
||||
<Box flex={1} />
|
||||
</Flex>
|
||||
)}
|
||||
<HStack spacing={4}>
|
||||
<HStack>
|
||||
<FormLabel>{t('common:user.Time')}</FormLabel>
|
||||
<Box>
|
||||
<DateRangePicker
|
||||
defaultDate={filterProps.dateRange}
|
||||
dateRange={filterProps.dateRange}
|
||||
position="bottom"
|
||||
onSuccess={(e) => setFilterProps({ ...filterProps, dateRange: e })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
<HStack flex={'0 0 200px'}>
|
||||
<FormLabel>{t('account_model:channel_name')}</FormLabel>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect<string>
|
||||
bg={'myGray.50'}
|
||||
isSearch
|
||||
list={channelList}
|
||||
placeholder={t('account_model:select_channel')}
|
||||
value={filterProps.channelId}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, channelId: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
<HStack flex={'0 0 200px'}>
|
||||
<FormLabel>{t('account_model:model_name')}</FormLabel>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect<string>
|
||||
bg={'myGray.50'}
|
||||
isSearch
|
||||
list={modelList}
|
||||
placeholder={t('account_model:select_model')}
|
||||
value={filterProps.model}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, model: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
<HStack flex={'0 0 200px'}>
|
||||
<FormLabel>{t('account_model:log_status')}</FormLabel>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect<'all' | 'success' | 'error'>
|
||||
bg={'myGray.50'}
|
||||
list={[
|
||||
{ label: t('common:common.All'), value: 'all' },
|
||||
{ label: t('common:common.Success'), value: 'success' },
|
||||
{ label: t('common:common.failed'), value: 'error' }
|
||||
]}
|
||||
value={filterProps.code_type}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, code_type: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<MyBox flex={'1 0 0'} h={0} isLoading={isLoading}>
|
||||
<ScrollData h={'100%'}>
|
||||
<TableContainer fontSize={'sm'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('account_model:channel_name')}</Th>
|
||||
<Th>{t('account_model:model')}</Th>
|
||||
<Th>{t('account_model:model_tokens')}</Th>
|
||||
<Th>{t('account_model:duration')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
<Th>{t('account_model:request_at')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{formatData.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.channelName}</Td>
|
||||
<Td>{item.model}</Td>
|
||||
<Td>
|
||||
{item.prompt_tokens} / {item.completion_tokens}
|
||||
</Td>
|
||||
<Td color={item.duration > 10 ? 'red.600' : ''}>{item.duration.toFixed(2)}s</Td>
|
||||
<Td color={item.code === 200 ? 'green.600' : 'red.600'}>
|
||||
{item.code}
|
||||
{item.content && <QuestionTip label={item.content} />}
|
||||
</Td>
|
||||
<Td>{item.request_at}</Td>
|
||||
<Td>
|
||||
<Button
|
||||
leftIcon={<MyIcon name={'menu'} w={'1rem'} />}
|
||||
size={'sm'}
|
||||
variant={'outline'}
|
||||
onClick={() => setLogDetail(item)}
|
||||
>
|
||||
{t('account_model:detail')}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ScrollData>
|
||||
</MyBox>
|
||||
|
||||
{!!logDetail && <LogDetail data={logDetail} onClose={() => setLogDetail(undefined)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelLog;
|
||||
|
||||
const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { data: detailData } = useRequest2(
|
||||
async () => {
|
||||
if (data.code === 200) return data;
|
||||
const res = await getLogDetail(data.id);
|
||||
return {
|
||||
...res,
|
||||
...data
|
||||
};
|
||||
},
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const Title = useCallback(({ children, ...props }: { children: React.ReactNode } & BoxProps) => {
|
||||
return (
|
||||
<Box
|
||||
bg={'myGray.50'}
|
||||
color="myGray.900 "
|
||||
borderRight={'base'}
|
||||
p={3}
|
||||
flex={'0 0 100px'}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}, []);
|
||||
const Container = useCallback(
|
||||
({ children, ...props }: { children: React.ReactNode } & BoxProps) => {
|
||||
return (
|
||||
<Box p={3} flex={1} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="support/bill/payRecordLight"
|
||||
title={t('account_model:log_detail')}
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '800px']}
|
||||
w={'100%'}
|
||||
>
|
||||
{detailData && (
|
||||
<ModalBody>
|
||||
{/* 基本信息表格 */}
|
||||
<Grid
|
||||
templateColumns="repeat(2, 1fr)"
|
||||
gap={0}
|
||||
borderWidth="1px"
|
||||
borderRadius="md"
|
||||
fontSize={'sm'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
{/* 第一行 */}
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>RequestID</Title>
|
||||
<Container>{detailData?.request_id}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:channel_status')}</Title>
|
||||
<Container color={detailData.code === 200 ? 'green.600' : 'red.600'}>
|
||||
{detailData?.code}
|
||||
</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>Endpoint</Title>
|
||||
<Container>{detailData?.endpoint}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:channel_name')}</Title>
|
||||
<Container>{detailData?.channelName}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:request_at')}</Title>
|
||||
<Container>{detailData?.request_at}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:duration')}</Title>
|
||||
<Container>{detailData?.duration.toFixed(2)}s</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:model')}</Title>
|
||||
<Container>{detailData?.model}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title flex={'0 0 150px'}>{t('account_model:model_tokens')}</Title>
|
||||
<Container>
|
||||
{detailData?.prompt_tokens} / {detailData?.completion_tokens}
|
||||
</Container>
|
||||
</GridItem>
|
||||
{detailData?.content && (
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px" colSpan={2}>
|
||||
<Title>Content</Title>
|
||||
<Container>{detailData?.content}</Container>
|
||||
</GridItem>
|
||||
)}
|
||||
{detailData?.request_body && (
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px" colSpan={2}>
|
||||
<Title>Request Body</Title>
|
||||
<Container userSelect={'all'}>{detailData?.request_body}</Container>
|
||||
</GridItem>
|
||||
)}
|
||||
{detailData?.response_body && (
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px" colSpan={2}>
|
||||
<Title>Response Body</Title>
|
||||
<Container>{detailData?.response_body}</Container>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
@@ -33,7 +33,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
deleteSystemModel,
|
||||
getModelConfigJson,
|
||||
getSystemModelDefaultConfig,
|
||||
getSystemModelDetail,
|
||||
getSystemModelList,
|
||||
getTestModel,
|
||||
@@ -44,23 +43,20 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { SystemModelItemType } from '@fastgpt/service/core/ai/type';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import { clientInitData } from '@/web/common/system/staticData';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { putUpdateWithJson } from '@/web/core/ai/config';
|
||||
import CopyBox from '@fastgpt/web/components/common/String/CopyBox';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { useRefresh } from '../../../../../../packages/web/hooks/useRefresh';
|
||||
import { Prompt_CQJson, Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
import { AddModelButton } from './AddModelBox';
|
||||
|
||||
const MyModal = dynamic(() => import('@fastgpt/web/components/common/MyModal'));
|
||||
const ModelEditModal = dynamic(() => import('./AddModelBox').then((mod) => mod.ModelEditModal));
|
||||
|
||||
const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -270,6 +266,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onCreateModel = (type: ModelTypeEnum) => {
|
||||
const defaultModel = defaultModels[type];
|
||||
|
||||
@@ -315,37 +312,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
<Button variant={'whiteBase'} mr={2} onClick={onOpenJsonConfig}>
|
||||
{t('account:model.json_config')}
|
||||
</Button>
|
||||
<MyMenu
|
||||
trigger="hover"
|
||||
size="sm"
|
||||
Button={<Button>{t('account:create_model')}</Button>}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
label: t('common:model.type.chat'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.llm)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.embedding'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.embedding)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.tts'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.tts)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.stt'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.stt)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.reRank'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.rerank)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<AddModelButton onCreate={onCreateModel} />
|
||||
</Flex>
|
||||
)}
|
||||
<MyBox flex={'1 0 0'} isLoading={isLoading}>
|
||||
@@ -511,628 +478,6 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const InputStyles = {
|
||||
maxW: '300px',
|
||||
bg: 'myGray.50',
|
||||
w: '100%',
|
||||
rows: 3
|
||||
};
|
||||
const ModelEditModal = ({
|
||||
modelData,
|
||||
onSuccess,
|
||||
onClose
|
||||
}: {
|
||||
modelData: SystemModelItemType;
|
||||
onSuccess: () => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch, reset } =
|
||||
useForm<SystemModelItemType>({
|
||||
defaultValues: modelData
|
||||
});
|
||||
|
||||
const isCustom = !!modelData.isCustom;
|
||||
const isLLMModel = modelData?.type === ModelTypeEnum.llm;
|
||||
const isEmbeddingModel = modelData?.type === ModelTypeEnum.embedding;
|
||||
const isTTSModel = modelData?.type === ModelTypeEnum.tts;
|
||||
const isSTTModel = modelData?.type === ModelTypeEnum.stt;
|
||||
const isRerankModel = modelData?.type === ModelTypeEnum.rerank;
|
||||
|
||||
const provider = watch('provider');
|
||||
const providerData = useMemo(() => getModelProvider(provider), [provider]);
|
||||
|
||||
const providerList = useRef<{ label: any; value: ModelProviderIdType }[]>(
|
||||
ModelProviderList.map((item) => ({
|
||||
label: (
|
||||
<HStack>
|
||||
<Avatar src={item.avatar} w={'1rem'} />
|
||||
<Box>{t(item.name as any)}</Box>
|
||||
</HStack>
|
||||
),
|
||||
value: item.id
|
||||
}))
|
||||
);
|
||||
|
||||
const priceUnit = useMemo(() => {
|
||||
if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens';
|
||||
if (isTTSModel) return `/ 1k ${t('common:unit.character')}`;
|
||||
if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`;
|
||||
return '';
|
||||
return '';
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]);
|
||||
|
||||
const { runAsync: updateModel, loading: updatingModel } = useRequest2(
|
||||
async (data: SystemModelItemType) => {
|
||||
return putSystemModel({
|
||||
model: data.model,
|
||||
metadata: data
|
||||
}).then(onSuccess);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
}
|
||||
);
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2(
|
||||
getSystemModelDefaultConfig,
|
||||
{
|
||||
onSuccess(res) {
|
||||
reset({
|
||||
...getValues(),
|
||||
...res
|
||||
});
|
||||
setTimeout(() => {
|
||||
setKey((prev) => prev + 1);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'modal/edit'}
|
||||
title={t('account:model.edit_model')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '80vw']}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex gap={4} key={key}>
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.model_id')}</Box>
|
||||
<QuestionTip label={t('account:model.model_id_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
{isCustom ? (
|
||||
<Input {...register('model', { required: true })} {...InputStyles} />
|
||||
) : (
|
||||
modelData?.model
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:model.provider')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MySelect
|
||||
value={provider}
|
||||
onchange={(value) => setValue('provider', value)}
|
||||
list={providerList.current}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.alias')}</Box>
|
||||
<QuestionTip label={t('account:model.alias_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('name', { required: true })} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
{priceUnit && feConfigs?.isPlus && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.charsPointsPrice')}</Box>
|
||||
<QuestionTip label={t('account:model.charsPointsPrice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'charsPointsPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.input_price')}</Box>
|
||||
<QuestionTip label={t('account:model.input_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'inputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.output_price')}</Box>
|
||||
<QuestionTip label={t('account:model.output_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'outputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput register={register} name="maxContext" {...InputStyles} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_quote')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
name="quoteMaxToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.chat.response.module maxToken')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput register={register} name="maxResponse" {...InputStyles} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_temperature')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
name="maxTemperature"
|
||||
step={0.1}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_top_p')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showTopP')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_stop_sign')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showStopSign')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.response_format')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('responseFormatList'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('responseFormatList', []);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('responseFormatList', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isEmbeddingModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.normalization')}</Box>
|
||||
<QuestionTip label={t('account:model.normalization_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('normalization')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_token')}</Box>
|
||||
<QuestionTip label={t('account:model.default_token_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput register={register} name="defaultToken" {...InputStyles} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput register={register} name="maxToken" {...InputStyles} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.defaultConfig')}</Box>
|
||||
<QuestionTip label={t('account:model.defaultConfig_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isTTSModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.voices')}</Box>
|
||||
<QuestionTip label={t('account:model.voices_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('voices'), null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setValue('voices', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_url')}</Box>
|
||||
<QuestionTip label={t('account:model.request_url_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestUrl')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_auth')}</Box>
|
||||
<QuestionTip label={t('account:model.request_auth_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestAuth')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{isLLMModel && (
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.tool_choice')}</Box>
|
||||
<QuestionTip label={t('account:model.tool_choice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('toolChoice')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.function_call')}</Box>
|
||||
<QuestionTip label={t('account:model.function_call_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('functionCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.vision')}</Box>
|
||||
<QuestionTip label={t('account:model.vision_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('vision')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.reasoning')}</Box>
|
||||
<QuestionTip label={t('account:model.reasoning_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('reasoning')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{feConfigs?.isPlus && (
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.censor')}</Box>
|
||||
<QuestionTip label={t('account:model.censor_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('censor')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>{t('account:model.dataset_process')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('datasetProcess')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_classify')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInClassify')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_extract_fields')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInExtractFields')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_tool_call')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInToolCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_system_chat_prompt')}</Box>
|
||||
<QuestionTip label={t('account:model.default_system_chat_prompt_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('defaultSystemChatPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_cq_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_cq_prompt_tip', { prompt: Prompt_CQJson })}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customCQPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_extract_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_extract_prompt_tip', {
|
||||
prompt: Prompt_ExtractJson
|
||||
})}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customExtractPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_config')}</Box>
|
||||
<QuestionTip label={t('account:model.default_config_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!modelData.isCustom && (
|
||||
<Button
|
||||
isLoading={loadingDefaultConfig}
|
||||
variant={'whiteBase'}
|
||||
mr={4}
|
||||
onClick={() => loadDefaultConfig(modelData.model)}
|
||||
>
|
||||
{t('account:reset_default')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={'whiteBase'} mr={4} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={updatingModel} onClick={handleSubmit(updateModel)}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
const JsonConfigModal = ({
|
||||
onClose,
|
||||
onSuccess
|
||||
@@ -1214,6 +559,7 @@ const DefaultModelModal = ({
|
||||
const {
|
||||
defaultModels,
|
||||
llmModelList,
|
||||
datasetModelList,
|
||||
embeddingModelList,
|
||||
ttsModelList,
|
||||
sttModelList,
|
||||
@@ -1334,6 +680,29 @@ const DefaultModelModal = ({
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<MyDivider />
|
||||
<Box>
|
||||
<Flex {...labelStyles} alignItems={'center'}>
|
||||
<Box mr={0.5}>{t('common:core.ai.model.Dataset Agent Model')}</Box>
|
||||
<QuestionTip label={t('common:dataset_text_model_tip')} />
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<AIModelSelector
|
||||
bg="myGray.50"
|
||||
value={defaultData.datasetTextLLM?.model}
|
||||
list={datasetModelList.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
datasetTextLLM: datasetModelList.find((item) => item.model === e)
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={4} onClick={onClose}>
|
||||
@@ -1347,7 +716,9 @@ const DefaultModelModal = ({
|
||||
[ModelTypeEnum.embedding]: defaultData.embedding?.model,
|
||||
[ModelTypeEnum.tts]: defaultData.tts?.model,
|
||||
[ModelTypeEnum.stt]: defaultData.stt?.model,
|
||||
[ModelTypeEnum.rerank]: defaultData.rerank?.model
|
||||
[ModelTypeEnum.rerank]: defaultData.rerank?.model,
|
||||
datasetTextLLM: defaultData.datasetTextLLM?.model,
|
||||
datasetImageLLM: defaultData.datasetImageLLM?.model
|
||||
})
|
||||
}
|
||||
>
|
||||
|
||||
@@ -101,6 +101,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
{
|
||||
onSuccess() {
|
||||
refetchTeams();
|
||||
refetchMembers();
|
||||
},
|
||||
errorToast: t('account_team:user_team_leave_team_failed')
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
total: memberTotal,
|
||||
ScrollData: MemberScrollData
|
||||
} = useScrollPagination(getTeamMembers, {
|
||||
pageSize: 20,
|
||||
pageSize: 1000,
|
||||
params: {
|
||||
withLeaved: true
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context';
|
||||
import { getWebDefaultModel } from '@/web/common/system/utils';
|
||||
import { getWebDefaultLLMModel } from '@/web/common/system/utils';
|
||||
|
||||
const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
const { llmModelList } = useSystemStore();
|
||||
@@ -23,7 +23,7 @@ const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
[llmModelList, item.llmModelType]
|
||||
);
|
||||
const defaultModel = useMemo(() => {
|
||||
return getWebDefaultModel(modelList).model;
|
||||
return getWebDefaultLLMModel(modelList).model;
|
||||
}, [modelList]);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import {
|
||||
delDatasetCollectionTag,
|
||||
getDatasetCollectionTags,
|
||||
getScrollCollectionList,
|
||||
getDatasetCollections,
|
||||
getTagUsage,
|
||||
postAddTagsToCollections,
|
||||
updateDatasetCollectionTag
|
||||
@@ -146,7 +146,7 @@ const TagManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
scrollDataList: collectionsList,
|
||||
ScrollList: ScrollListCollections,
|
||||
isLoading: collectionsListLoading
|
||||
} = useVirtualScrollPagination(getScrollCollectionList, {
|
||||
} = useVirtualScrollPagination(getDatasetCollections, {
|
||||
refreshDeps: [searchText],
|
||||
// debounceWait: 300,
|
||||
|
||||
@@ -156,6 +156,7 @@ const TagManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
pageSize: 30,
|
||||
defaultParams: {
|
||||
datasetId: datasetDetail._id,
|
||||
simple: true,
|
||||
searchText
|
||||
}
|
||||
});
|
||||
|
||||
@@ -195,7 +195,7 @@ const CollectionCard = () => {
|
||||
<Tr>
|
||||
<Th py={4}>{t('common:common.Name')}</Th>
|
||||
<Th py={4}>{t('dataset:collection.Training type')}</Th>
|
||||
<Th py={4}>{t('common:dataset.collections.Data Amount')}</Th>
|
||||
<Th py={4}>{t('dataset:collection_data_count')}</Th>
|
||||
<Th py={4}>{t('dataset:collection.Create update time')}</Th>
|
||||
<Th py={4}>{t('common:common.Status')}</Th>
|
||||
<Th py={4}>{t('dataset:Enable')}</Th>
|
||||
|
||||
@@ -29,10 +29,8 @@ import Markdown from '@/components/Markdown';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { TabEnum } from './NavBar';
|
||||
import {
|
||||
DatasetCollectionTypeEnum,
|
||||
ImportDataSourceEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const DataCard = () => {
|
||||
const theme = useTheme();
|
||||
@@ -76,19 +74,17 @@ const DataCard = () => {
|
||||
const [editDataId, setEditDataId] = useState<string>();
|
||||
|
||||
// get file info
|
||||
const { data: collection } = useQuery(
|
||||
['getDatasetCollectionById', collectionId],
|
||||
() => getDatasetCollectionById(collectionId),
|
||||
{
|
||||
onError: () => {
|
||||
router.replace({
|
||||
query: {
|
||||
datasetId
|
||||
}
|
||||
});
|
||||
}
|
||||
const { data: collection } = useRequest2(() => getDatasetCollectionById(collectionId), {
|
||||
refreshDeps: [collectionId],
|
||||
manual: false,
|
||||
onError: () => {
|
||||
router.replace({
|
||||
query: {
|
||||
datasetId
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const canWrite = useMemo(() => datasetDetail.permission.hasWritePer, [datasetDetail]);
|
||||
|
||||
@@ -182,7 +178,10 @@ const DataCard = () => {
|
||||
<Flex align={'center'} color={'myGray.500'}>
|
||||
<MyIcon name="common/list" mr={2} w={'18px'} />
|
||||
<Box as={'span'} fontSize={['sm', '14px']} fontWeight={'500'}>
|
||||
{t('common:core.dataset.data.Total Amount', { total })}
|
||||
{t('dataset:data_amount', {
|
||||
dataAmount: total,
|
||||
indexAmount: collection?.indexAmount ?? '-'
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box flex={1} mr={1} />
|
||||
|
||||
@@ -164,12 +164,12 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
</Flex>
|
||||
|
||||
<Box mt={5} w={'100%'}>
|
||||
<Flex alignItems={'center'} fontSize={'mini'}>
|
||||
<FormLabel fontWeight={'500'} flex={'1 0 0'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel fontWeight={'500'} flex={'1 0 0'} fontSize={'mini'}>
|
||||
{t('common:core.ai.model.Vector Model')}
|
||||
</FormLabel>
|
||||
<MyTooltip label={t('dataset:vector_model_max_tokens_tip')}>
|
||||
<Box>
|
||||
<Box fontSize={'mini'}>
|
||||
{t('dataset:chunk_max_tokens')}: {vectorModel.maxToken}
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
|
||||
@@ -21,7 +21,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { datasetTypeCourseMap } from '@/web/core/dataset/constants';
|
||||
import ApiDatasetForm from '../ApiDatasetForm';
|
||||
import { getWebDefaultModel } from '@/web/common/system/utils';
|
||||
import { getWebDefaultEmbeddingModel, getWebDefaultLLMModel } from '@/web/common/system/utils';
|
||||
|
||||
export type CreateDatasetType =
|
||||
| DatasetTypeEnum.dataset
|
||||
@@ -40,7 +40,6 @@ const CreateModal = ({
|
||||
type: CreateDatasetType;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { defaultModels, embeddingModelList, datasetModelList } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
@@ -79,8 +78,10 @@ const CreateModal = ({
|
||||
avatar: datasetTypeMap[type].icon,
|
||||
name: '',
|
||||
intro: '',
|
||||
vectorModel: defaultModels.embedding?.model,
|
||||
agentModel: getWebDefaultModel(datasetModelList)?.model
|
||||
vectorModel:
|
||||
defaultModels.embedding?.model || getWebDefaultEmbeddingModel(embeddingModelList)?.model,
|
||||
agentModel:
|
||||
defaultModels.datasetTextLLM?.model || getWebDefaultLLMModel(datasetModelList)?.model
|
||||
}
|
||||
});
|
||||
const { register, setValue, handleSubmit, watch } = form;
|
||||
|
||||
@@ -42,6 +42,8 @@ import AccountContainer from '@/pageComponents/account/AccountContainer';
|
||||
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
|
||||
import { useRouter } from 'next/router';
|
||||
import TeamSelector from '@/pageComponents/account/TeamSelector';
|
||||
import { getWorkorderURL } from '@/web/common/workorder/api';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const StandDetailModal = dynamic(
|
||||
() => import('@/pageComponents/account/info/standardDetailModal'),
|
||||
@@ -232,11 +234,12 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
borderColor={'transparent'}
|
||||
transform={'translateX(-11px)'}
|
||||
maxLength={20}
|
||||
onBlur={(e) => {
|
||||
onBlur={async (e) => {
|
||||
const val = e.target.value;
|
||||
if (val === userInfo?.team?.memberName) return;
|
||||
try {
|
||||
putUpdateMemberName(val);
|
||||
await putUpdateMemberName(val);
|
||||
initUserInfo();
|
||||
} catch (error) {}
|
||||
}}
|
||||
/>
|
||||
@@ -580,9 +583,19 @@ const ButtonStyles = {
|
||||
};
|
||||
const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { runAsync: onFeedback } = useRequest2(getWorkorderURL, {
|
||||
manual: true,
|
||||
onSuccess(data) {
|
||||
if (data) {
|
||||
window.open(data.redirectUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Grid gridGap={4} mt={3}>
|
||||
@@ -619,6 +632,16 @@ const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{feConfigs?.show_workorder &&
|
||||
teamPlanStatus &&
|
||||
teamPlanStatus.standard?.currentSubLevel !== StandardSubLevelEnum.free && (
|
||||
<Flex onClick={onFeedback} {...ButtonStyles}>
|
||||
<MyIcon name={'feedback'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('common:question_feedback')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -7,13 +7,17 @@ import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const ModelConfigTable = dynamic(() => import('@/pageComponents/account/model/ModelConfigTable'));
|
||||
const ChannelTable = dynamic(() => import('@/pageComponents/account/model/Channel'));
|
||||
const ChannelLog = dynamic(() => import('@/pageComponents/account/model/Log'));
|
||||
|
||||
type TabType = 'model' | 'config' | 'channel';
|
||||
type TabType = 'model' | 'config' | 'channel' | 'channel_log';
|
||||
|
||||
const ModelProvider = () => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const [tab, setTab] = useState<TabType>('model');
|
||||
|
||||
@@ -22,21 +26,29 @@ const ModelProvider = () => {
|
||||
<FillRowTabs<TabType>
|
||||
list={[
|
||||
{ label: t('account:active_model'), value: 'model' },
|
||||
{ label: t('account:config_model'), value: 'config' }
|
||||
// { label: t('account:channel'), value: 'channel' }
|
||||
{ label: t('account:config_model'), value: 'config' },
|
||||
// @ts-ignore
|
||||
...(feConfigs?.show_aiproxy
|
||||
? [
|
||||
{ label: t('account:channel'), value: 'channel' },
|
||||
{ label: t('account_model:log'), value: 'channel_log' }
|
||||
]
|
||||
: [])
|
||||
]}
|
||||
value={tab}
|
||||
py={1}
|
||||
onChange={setTab}
|
||||
/>
|
||||
);
|
||||
}, [t, tab]);
|
||||
}, [feConfigs.show_aiproxy, t, tab]);
|
||||
|
||||
return (
|
||||
<AccountContainer>
|
||||
<Flex h={'100%'} flexDirection={'column'} gap={4} py={4} px={6}>
|
||||
{tab === 'model' && <ValidModelTable Tab={Tab} />}
|
||||
{tab === 'config' && <ModelConfigTable Tab={Tab} />}
|
||||
{tab === 'channel' && <ChannelTable Tab={Tab} />}
|
||||
{tab === 'channel_log' && <ChannelLog Tab={Tab} />}
|
||||
</Flex>
|
||||
</AccountContainer>
|
||||
);
|
||||
@@ -45,7 +57,7 @@ const ModelProvider = () => {
|
||||
export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
...(await serviceSideProps(content, ['account']))
|
||||
...(await serviceSideProps(content, ['account', 'account_model']))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
// 重命名 dataset.trainigns -> dataset_trainings
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'dataset.trainings' })
|
||||
.toArray();
|
||||
@@ -31,6 +36,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'dataset.collections' })
|
||||
.toArray();
|
||||
@@ -52,6 +62,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'dataset.datas' })
|
||||
.toArray();
|
||||
@@ -73,6 +88,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'app.versions' })
|
||||
.toArray();
|
||||
@@ -94,6 +114,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'buffer.rawtexts' })
|
||||
.toArray();
|
||||
@@ -115,6 +140,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'buffer.tts' })
|
||||
.toArray();
|
||||
@@ -134,6 +164,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'team.members' })
|
||||
.toArray();
|
||||
@@ -155,6 +190,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'team.tags' })
|
||||
.toArray();
|
||||
@@ -174,6 +214,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connectionMongo.connection.db) {
|
||||
return jsonRes(res, {
|
||||
message: '数据库连接失败'
|
||||
});
|
||||
}
|
||||
const collections = await connectionMongo.connection.db
|
||||
.listCollections({ name: 'team.subscriptions' })
|
||||
.toArray();
|
||||
|
||||
72
projects/app/src/pages/api/aiproxy/[...path].ts
Normal file
72
projects/app/src/pages/api/aiproxy/[...path].ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { request } from 'https';
|
||||
import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth';
|
||||
|
||||
const baseUrl = process.env.AIPROXY_API_ENDPOINT;
|
||||
const token = process.env.AIPROXY_API_TOKEN;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await authSystemAdmin({ req });
|
||||
|
||||
if (!baseUrl || !token) {
|
||||
throw new Error('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set');
|
||||
}
|
||||
|
||||
const { path = [], ...query } = req.query as any;
|
||||
|
||||
if (!path.length) {
|
||||
throw new Error('url is empty');
|
||||
}
|
||||
|
||||
const queryStr = new URLSearchParams(query).toString();
|
||||
const requestPath = queryStr
|
||||
? `/${path?.join('/')}?${new URLSearchParams(query).toString()}`
|
||||
: `/${path?.join('/')}`;
|
||||
|
||||
const parsedUrl = new URL(baseUrl);
|
||||
delete req.headers?.cookie;
|
||||
delete req.headers?.host;
|
||||
delete req.headers?.origin;
|
||||
|
||||
const requestResult = request({
|
||||
protocol: parsedUrl.protocol,
|
||||
hostname: parsedUrl.hostname,
|
||||
port: parsedUrl.port,
|
||||
path: requestPath,
|
||||
method: req.method,
|
||||
headers: {
|
||||
...req.headers,
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
req.pipe(requestResult);
|
||||
|
||||
requestResult.on('response', (response) => {
|
||||
Object.keys(response.headers).forEach((key) => {
|
||||
// @ts-ignore
|
||||
res.setHeader(key, response.headers[key]);
|
||||
});
|
||||
response.statusCode && res.writeHead(response.statusCode);
|
||||
response.pipe(res);
|
||||
});
|
||||
requestResult.on('error', (e) => {
|
||||
res.send(e);
|
||||
res.end();
|
||||
});
|
||||
} catch (error) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false
|
||||
}
|
||||
};
|
||||
33
projects/app/src/pages/api/aiproxy/api/createChannel.ts
Normal file
33
projects/app/src/pages/api/aiproxy/api/createChannel.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth';
|
||||
import axios from 'axios';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
const baseUrl = process.env.AIPROXY_API_ENDPOINT;
|
||||
const token = process.env.AIPROXY_API_TOKEN;
|
||||
|
||||
async function handler(req: ApiRequestProps, res: ApiResponseType<any>) {
|
||||
try {
|
||||
await authSystemAdmin({ req });
|
||||
|
||||
if (!baseUrl || !token) {
|
||||
return Promise.reject('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set');
|
||||
}
|
||||
|
||||
const { data } = await axios.post(`${baseUrl}/api/channel/`, req.body, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
res.json(data);
|
||||
} catch (error) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: getErrText(error),
|
||||
data: error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default handler;
|
||||
@@ -60,6 +60,7 @@ const testLLMModel = async (model: LLMModelItemType) => {
|
||||
const ai = getAIApi({
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
const requestBody = llmCompletionsBodyFormat(
|
||||
{
|
||||
model: model.model,
|
||||
|
||||
@@ -15,6 +15,8 @@ export type updateDefaultBody = {
|
||||
[ModelTypeEnum.tts]?: string;
|
||||
[ModelTypeEnum.stt]?: string;
|
||||
[ModelTypeEnum.rerank]?: string;
|
||||
datasetTextLLM?: string;
|
||||
datasetImageLLM?: string;
|
||||
};
|
||||
|
||||
export type updateDefaultResponse = {};
|
||||
@@ -25,10 +27,21 @@ async function handler(
|
||||
): Promise<updateDefaultResponse> {
|
||||
await authSystemAdmin({ req });
|
||||
|
||||
const { llm, embedding, tts, stt, rerank } = req.body;
|
||||
const { llm, embedding, tts, stt, rerank, datasetTextLLM, datasetImageLLM } = req.body;
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoSystemModel.updateMany({}, { $unset: { 'metadata.isDefault': 1 } }, { session });
|
||||
// Remove all default flags
|
||||
await MongoSystemModel.updateMany(
|
||||
{},
|
||||
{
|
||||
$unset: {
|
||||
'metadata.isDefault': 1,
|
||||
'metadata.isDefaultDatasetTextModel': 1,
|
||||
'metadata.isDefaultDatasetImageModel': 1
|
||||
}
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (llm) {
|
||||
await MongoSystemModel.updateOne(
|
||||
@@ -37,6 +50,20 @@ async function handler(
|
||||
{ session }
|
||||
);
|
||||
}
|
||||
if (datasetTextLLM) {
|
||||
await MongoSystemModel.updateOne(
|
||||
{ model: datasetTextLLM },
|
||||
{ $set: { 'metadata.isDefaultDatasetTextModel': true } },
|
||||
{ session }
|
||||
);
|
||||
}
|
||||
if (datasetImageLLM) {
|
||||
await MongoSystemModel.updateOne(
|
||||
{ model: datasetImageLLM },
|
||||
{ $set: { 'metadata.isDefaultDatasetImageModel': true } },
|
||||
{ session }
|
||||
);
|
||||
}
|
||||
if (embedding) {
|
||||
await MongoSystemModel.updateOne(
|
||||
{ model: embedding },
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { DatasetCollectionItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { collectionTagsToTagLabel } from '@fastgpt/service/core/dataset/collection/utils';
|
||||
import { getVectorCountByCollectionId } from '@fastgpt/service/common/vectorStore/controller';
|
||||
|
||||
async function handler(req: NextApiRequest): Promise<DatasetCollectionItemType> {
|
||||
const { id } = req.query as { id: string };
|
||||
@@ -29,12 +30,16 @@ async function handler(req: NextApiRequest): Promise<DatasetCollectionItemType>
|
||||
});
|
||||
|
||||
// get file
|
||||
const file = collection?.fileId
|
||||
? await getFileById({ bucketName: BucketNameEnum.dataset, fileId: collection.fileId })
|
||||
: undefined;
|
||||
const [file, indexAmount] = await Promise.all([
|
||||
collection?.fileId
|
||||
? await getFileById({ bucketName: BucketNameEnum.dataset, fileId: collection.fileId })
|
||||
: undefined,
|
||||
getVectorCountByCollectionId(collection.teamId, collection.datasetId, collection._id)
|
||||
]);
|
||||
|
||||
return {
|
||||
...collection,
|
||||
indexAmount: indexAmount ?? 0,
|
||||
...getCollectionSourceData(collection),
|
||||
tags: await collectionTagsToTagLabel({
|
||||
datasetId: collection.datasetId,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
|
||||
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
@@ -14,6 +12,8 @@ import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
|
||||
import { collectionTagsToTagLabel } from '@fastgpt/service/core/dataset/collection/utils';
|
||||
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
|
||||
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest
|
||||
@@ -77,6 +77,8 @@ async function handler(
|
||||
.sort({
|
||||
updateTime: -1
|
||||
})
|
||||
.skip(offset)
|
||||
.limit(pageSize)
|
||||
.lean();
|
||||
|
||||
return {
|
||||
@@ -88,6 +90,7 @@ async function handler(
|
||||
tags: item.tags
|
||||
}),
|
||||
dataAmount: 0,
|
||||
indexAmount: 0,
|
||||
trainingAmount: 0,
|
||||
permission
|
||||
}))
|
||||
@@ -96,75 +99,62 @@ async function handler(
|
||||
};
|
||||
}
|
||||
|
||||
const [collections, total]: [DatasetCollectionsListItemType[], number] = await Promise.all([
|
||||
MongoDatasetCollection.aggregate([
|
||||
{
|
||||
$match: match
|
||||
},
|
||||
{
|
||||
$sort: { updateTime: -1 }
|
||||
},
|
||||
{
|
||||
$skip: offset
|
||||
},
|
||||
{
|
||||
$limit: pageSize
|
||||
},
|
||||
// count training data
|
||||
{
|
||||
$lookup: {
|
||||
from: DatasetTrainingCollectionName,
|
||||
let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [{ $eq: ['$teamId', '$$team_id'] }, { $eq: ['$collectionId', '$$id'] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $count: 'count' }
|
||||
],
|
||||
as: 'trainingCount'
|
||||
}
|
||||
},
|
||||
// count collection total data
|
||||
{
|
||||
$lookup: {
|
||||
from: DatasetDataCollectionName,
|
||||
let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [
|
||||
{ $eq: ['$teamId', '$$team_id'] },
|
||||
{ $eq: ['$datasetId', '$$dataset_id'] },
|
||||
{ $eq: ['$collectionId', '$$id'] }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $count: 'count' }
|
||||
],
|
||||
as: 'dataCount'
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
...selectField,
|
||||
dataAmount: {
|
||||
$ifNull: [{ $arrayElemAt: ['$dataCount.count', 0] }, 0]
|
||||
},
|
||||
trainingAmount: {
|
||||
$ifNull: [{ $arrayElemAt: ['$trainingCount.count', 0] }, 0]
|
||||
const [collections, total]: [DatasetCollectionSchemaType[], number] = await Promise.all([
|
||||
MongoDatasetCollection.find(match, undefined, { ...readFromSecondary })
|
||||
.select(selectField)
|
||||
.sort({ updateTime: -1 })
|
||||
.skip(offset)
|
||||
.limit(pageSize)
|
||||
.lean(),
|
||||
MongoDatasetCollection.countDocuments(match, { ...readFromSecondary })
|
||||
]);
|
||||
const collectionIds = collections.map((item) => item._id);
|
||||
|
||||
// Compute data amount
|
||||
const [trainingAmount, dataAmount]: [
|
||||
{ _id: string; count: number }[],
|
||||
{ _id: string; count: number }[]
|
||||
] = await Promise.all([
|
||||
MongoDatasetCollection.aggregate(
|
||||
[
|
||||
{
|
||||
$match: {
|
||||
teamId: match.teamId,
|
||||
datasetId: match.datasetId,
|
||||
collectionId: { $in: collectionIds }
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$collectionId',
|
||||
count: { $sum: 1 }
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
...readFromSecondary
|
||||
}
|
||||
]),
|
||||
MongoDatasetCollection.countDocuments(match, {
|
||||
...readFromSecondary
|
||||
})
|
||||
),
|
||||
MongoDatasetData.aggregate(
|
||||
[
|
||||
{
|
||||
$match: {
|
||||
teamId: match.teamId,
|
||||
datasetId: match.datasetId,
|
||||
collectionId: { $in: collectionIds }
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$collectionId',
|
||||
count: { $sum: 1 }
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
...readFromSecondary
|
||||
}
|
||||
)
|
||||
]);
|
||||
|
||||
const list = await Promise.all(
|
||||
@@ -174,11 +164,14 @@ async function handler(
|
||||
datasetId,
|
||||
tags: item.tags
|
||||
}),
|
||||
trainingAmount:
|
||||
trainingAmount.find((amount) => String(amount._id) === String(item._id))?.count || 0,
|
||||
dataAmount: dataAmount.find((amount) => String(amount._id) === String(item._id))?.count || 0,
|
||||
permission
|
||||
}))
|
||||
);
|
||||
|
||||
if (list.find((item) => item.trainingAmount > 0)) {
|
||||
if (list.some((item) => item.trainingAmount > 0)) {
|
||||
startTrainingQueue();
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ async function handler(
|
||||
...item,
|
||||
dataAmount: 0,
|
||||
trainingAmount: 0,
|
||||
indexAmount: 0,
|
||||
permission
|
||||
}))
|
||||
),
|
||||
|
||||
@@ -16,7 +16,7 @@ async function handler(
|
||||
): Promise<OutLinkDeleteResponse> {
|
||||
const { id } = req.query;
|
||||
await authOutLinkCrud({ req, outLinkId: id, authToken: true, per: OwnerPermissionVal });
|
||||
await MongoOutLink.findByIdAndRemove(id);
|
||||
await MongoOutLink.findByIdAndDelete(id);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ const MyApps = () => {
|
||||
size="md"
|
||||
Button={
|
||||
<Button variant={'primary'} leftIcon={<AddIcon />}>
|
||||
<Box>{t('common:common.Create New')}</Box>
|
||||
<Box>{t('common:new_create')}</Box>
|
||||
</Button>
|
||||
}
|
||||
menuList={[
|
||||
|
||||
@@ -147,7 +147,7 @@ const Dataset = () => {
|
||||
<Button variant={'primary'} px="0">
|
||||
<Flex alignItems={'center'} px={5}>
|
||||
<AddIcon mr={2} />
|
||||
<Box>{t('common:common.Create New')}</Box>
|
||||
<Box>{t('common:new_create')}</Box>
|
||||
</Flex>
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -83,7 +83,8 @@ export async function initSystemConfig() {
|
||||
...fileRes?.feConfigs,
|
||||
...defaultFeConfigs,
|
||||
...(dbConfig.feConfigs || {}),
|
||||
isPlus: !!FastGPTProUrl
|
||||
isPlus: !!FastGPTProUrl,
|
||||
show_aiproxy: !!process.env.AIPROXY_API_ENDPOINT
|
||||
},
|
||||
systemEnv: {
|
||||
...fileRes.systemEnv,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { EmbeddingModelItemType, LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { useSystemStore } from './useSystemStore';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
|
||||
@@ -49,7 +49,7 @@ export const getWebLLMModel = (model?: string) => {
|
||||
|
||||
return list.find((item) => item.model === model || item.name === model) ?? defaultModels.llm!;
|
||||
};
|
||||
export const getWebDefaultModel = (llmList: LLMModelItemType[] = []) => {
|
||||
export const getWebDefaultLLMModel = (llmList: LLMModelItemType[] = []) => {
|
||||
const list = llmList.length > 0 ? llmList : useSystemStore.getState().llmModelList;
|
||||
const defaultModels = useSystemStore.getState().defaultModels;
|
||||
|
||||
@@ -57,3 +57,13 @@ export const getWebDefaultModel = (llmList: LLMModelItemType[] = []) => {
|
||||
? defaultModels.llm
|
||||
: list[0];
|
||||
};
|
||||
export const getWebDefaultEmbeddingModel = (embeddingList: EmbeddingModelItemType[] = []) => {
|
||||
const list =
|
||||
embeddingList.length > 0 ? embeddingList : useSystemStore.getState().embeddingModelList;
|
||||
const defaultModels = useSystemStore.getState().defaultModels;
|
||||
|
||||
return defaultModels.embedding &&
|
||||
list.find((item) => item.model === defaultModels.embedding?.model)
|
||||
? defaultModels.embedding
|
||||
: list[0];
|
||||
};
|
||||
|
||||
5
projects/app/src/web/common/workorder/api.ts
Normal file
5
projects/app/src/web/common/workorder/api.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { GET } from '@/web/common/api/request';
|
||||
export const getWorkorderURL = () =>
|
||||
GET<{
|
||||
redirectUrl: string;
|
||||
}>('/proApi/common/workorder/create');
|
||||
183
projects/app/src/web/core/ai/channel.ts
Normal file
183
projects/app/src/web/core/ai/channel.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import axios, { Method, AxiosResponse } from 'axios';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import {
|
||||
ChannelInfoType,
|
||||
ChannelListResponseType,
|
||||
ChannelLogListItemType,
|
||||
CreateChannelProps
|
||||
} from '@/global/aiproxy/type';
|
||||
import { ChannelStatusEnum } from '@/global/aiproxy/constants';
|
||||
|
||||
interface ResponseDataType {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求成功,检查请求头
|
||||
*/
|
||||
function responseSuccess(response: AxiosResponse<ResponseDataType>) {
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* 响应数据检查
|
||||
*/
|
||||
function checkRes(data: ResponseDataType) {
|
||||
if (data === undefined) {
|
||||
console.log('error->', data, 'data is empty');
|
||||
return Promise.reject('服务器异常');
|
||||
} else if (!data.success) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应错误
|
||||
*/
|
||||
function responseError(err: any) {
|
||||
console.log('error->', '请求错误', err);
|
||||
const data = err?.response?.data || err;
|
||||
|
||||
if (!err) {
|
||||
return Promise.reject({ message: '未知错误' });
|
||||
}
|
||||
if (typeof err === 'string') {
|
||||
return Promise.reject({ message: err });
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
|
||||
return Promise.reject(data);
|
||||
}
|
||||
|
||||
/* 创建请求实例 */
|
||||
const instance = axios.create({
|
||||
timeout: 60000, // 超时时间
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
/* 响应拦截 */
|
||||
instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err));
|
||||
|
||||
function request(url: string, data: any, method: Method): any {
|
||||
/* 去空 */
|
||||
for (const key in data) {
|
||||
if (data[key] === undefined) {
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return instance
|
||||
.request({
|
||||
baseURL: getWebReqUrl('/api/aiproxy/api'),
|
||||
url,
|
||||
method,
|
||||
data: ['POST', 'PUT'].includes(method) ? data : undefined,
|
||||
params: !['POST', 'PUT'].includes(method) ? data : undefined
|
||||
})
|
||||
.then((res) => checkRes(res.data))
|
||||
.catch((err) => responseError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* api请求方式
|
||||
* @param {String} url
|
||||
* @param {Any} params
|
||||
* @param {Object} config
|
||||
* @returns
|
||||
*/
|
||||
export function GET<T = undefined>(url: string, params = {}): Promise<T> {
|
||||
return request(url, params, 'GET');
|
||||
}
|
||||
export function POST<T = undefined>(url: string, data = {}): Promise<T> {
|
||||
return request(url, data, 'POST');
|
||||
}
|
||||
export function PUT<T = undefined>(url: string, data = {}): Promise<T> {
|
||||
return request(url, data, 'PUT');
|
||||
}
|
||||
export function DELETE<T = undefined>(url: string, data = {}): Promise<T> {
|
||||
return request(url, data, 'DELETE');
|
||||
}
|
||||
|
||||
// ====== API ======
|
||||
export const getChannelList = () =>
|
||||
GET<ChannelListResponseType>('/channels/all', {
|
||||
page: 1,
|
||||
perPage: 10
|
||||
});
|
||||
|
||||
export const getChannelProviders = () =>
|
||||
GET<
|
||||
Record<
|
||||
number,
|
||||
{
|
||||
defaultBaseUrl: string;
|
||||
keyHelp: string;
|
||||
name: string;
|
||||
}
|
||||
>
|
||||
>('/channels/type_metas');
|
||||
|
||||
export const postCreateChannel = (data: CreateChannelProps) =>
|
||||
POST(`/createChannel`, {
|
||||
type: data.type,
|
||||
name: data.name,
|
||||
base_url: data.base_url,
|
||||
models: data.models,
|
||||
model_mapping: data.model_mapping,
|
||||
key: data.key
|
||||
});
|
||||
|
||||
export const putChannelStatus = (id: number, status: ChannelStatusEnum) =>
|
||||
POST(`/channel/${id}/status`, {
|
||||
status
|
||||
});
|
||||
export const putChannel = (data: ChannelInfoType) =>
|
||||
PUT(`/channel/${data.id}`, {
|
||||
type: data.type,
|
||||
name: data.name,
|
||||
base_url: data.base_url,
|
||||
models: data.models,
|
||||
model_mapping: data.model_mapping,
|
||||
key: data.key,
|
||||
status: data.status,
|
||||
priority: data.priority
|
||||
});
|
||||
|
||||
export const deleteChannel = (id: number) => DELETE(`/channel/${id}`);
|
||||
|
||||
export const getChannelLog = (params: {
|
||||
channel?: string;
|
||||
model_name?: string;
|
||||
status?: 'all' | 'success' | 'error';
|
||||
start_timestamp: number;
|
||||
end_timestamp: number;
|
||||
offset: number;
|
||||
pageSize: number;
|
||||
}) =>
|
||||
GET<{
|
||||
logs: ChannelLogListItemType[];
|
||||
total: number;
|
||||
}>(`/logs/search`, {
|
||||
...params,
|
||||
p: Math.floor(params.offset / params.pageSize) + 1,
|
||||
per_page: params.pageSize,
|
||||
offset: undefined,
|
||||
pageSize: undefined
|
||||
}).then((res) => {
|
||||
return {
|
||||
list: res.logs,
|
||||
total: res.total
|
||||
};
|
||||
});
|
||||
|
||||
export const getLogDetail = (id: number) =>
|
||||
GET<{
|
||||
request_body: string;
|
||||
response_body: string;
|
||||
}>(`/logs/detail/${id}`);
|
||||
@@ -56,7 +56,6 @@ import type {
|
||||
import type { UpdateDatasetDataProps } from '@fastgpt/global/core/dataset/controller';
|
||||
import type { DatasetFolderCreateBody } from '@/pages/api/core/dataset/folder/create';
|
||||
import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import type { GetScrollCollectionsProps } from '@/pages/api/core/dataset/collection/scrollList';
|
||||
import type {
|
||||
GetApiDatasetFileListProps,
|
||||
GetApiDatasetFileListResponse
|
||||
@@ -173,11 +172,6 @@ export const getTagUsage = (datasetId: string) =>
|
||||
GET<TagUsageType[]>(`/proApi/core/dataset/tag/tagUsage?datasetId=${datasetId}`);
|
||||
export const getAllTags = (datasetId: string) =>
|
||||
GET<{ list: DatasetTagType[] }>(`/proApi/core/dataset/tag/getAllTags?datasetId=${datasetId}`);
|
||||
export const getScrollCollectionList = (data: GetScrollCollectionsProps) =>
|
||||
POST<PaginationResponse<DatasetCollectionsListItemType>>(
|
||||
`/core/dataset/collection/scrollList`,
|
||||
data
|
||||
);
|
||||
|
||||
/* =============================== data ==================================== */
|
||||
/* get dataset list */
|
||||
|
||||
@@ -59,7 +59,8 @@ export const defaultCollectionDetail: DatasetCollectionItemType = {
|
||||
createTime: new Date(),
|
||||
trainingType: TrainingModeEnum.chunk,
|
||||
chunkSize: 0,
|
||||
permission: new DatasetPermission()
|
||||
permission: new DatasetPermission(),
|
||||
indexAmount: 0
|
||||
};
|
||||
|
||||
export enum ImportProcessWayEnum {
|
||||
|
||||
@@ -81,13 +81,14 @@ export PROCESSES_PER_GPU="1"
|
||||
```
|
||||
2. 运行容器
|
||||
```bash
|
||||
sudo docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 model_pdf
|
||||
sudo docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 -e PROCESSES_PER_GPU="2" model_pdf
|
||||
```
|
||||
## 快速构建镜像
|
||||
```dockerfile
|
||||
docker pull crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest
|
||||
docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest
|
||||
docker run --gpus all -itd -p 7231:7231 --name model_pdf_v1 -e PROCESSES_PER_GPU="2" crpi-h3snc261q1dosroc.cn-hangzhou.personal.cr.aliyuncs.com/marker11/marker_images:latest
|
||||
```
|
||||
*注意*:参数PROCESSES_PER_GPU设置每张显卡上文件处理的并行数量,24G的显卡可以设置为2。在多显卡的环境中会自动切换显卡来运行多文件的并行处理。
|
||||
# 访问示例
|
||||
|
||||
用Post方法访问端口为 `7321 ` 的 `v1/parse/file` 服务
|
||||
|
||||
Reference in New Issue
Block a user