Commit e6d53e3d by Archer Committed by GitHub

4.8.16 test (#3442)

* perf: simple app save

* fix: notify config i18n

* perf: service side props render

* perf: model selector

* update doc
parent 922cb433
......@@ -21,16 +21,16 @@ weight: 708
"lafEnv": "https://laf.dev" // laf环境。 https://laf.run (杭州阿里云) ,或者私有化的laf环境。如果使用 Laf openapi 功能,需要最新版的 laf
},
"systemEnv": {
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"vectorMaxProcess": 15, // 向量处理线程数量
"qaMaxProcess": 15, // 问答拆分线程数量
"tokenWorkers": 50, // Token 计算线程保持数,会持续占用内存,不能设置太大。
"pgHNSWEfSearch": 100 // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。
},
"llmModels": [
{
"provider": "OpenAI", // 模型提供商,主要用于分类展示,目前已经内置提供商包括:https://github.com/labring/FastGPT/blob/main/packages/global/core/ai/provider.ts, pr 提供新的提供商,或直接填写 Other
"model": "gpt-4o-mini", // 模型名(对应OneAPI中渠道的模型名)
"name": "gpt-4o-mini", // 模型别名
"avatar": "/imgs/model/openai.svg", // 模型的logo
"maxContext": 125000, // 最大上下文
"maxResponse": 16000, // 最大回复
"quoteMaxToken": 120000, // 最大引用内容
......@@ -38,7 +38,7 @@ weight: 708
"charsPointsPrice": 0, // n积分/1k token(商业版)
"censor": false, // 是否开启敏感校验(商业版)
"vision": true, // 是否支持图片输入
"datasetProcess": true, // 是否设置为知识库处理模型(QA),务必保证至少有一个为true,否则知识库会报错
"datasetProcess": true, // 是否设置为文本理解模型(QA),务必保证至少有一个为true,否则知识库会报错
"usedInClassify": true, // 是否用于问题分类(务必保证至少有一个为true
"usedInExtractFields": true, // 是否用于内容提取(务必保证至少有一个为true
"usedInToolCall": true, // 是否用于工具调用(务必保证至少有一个为true
......@@ -48,12 +48,13 @@ weight: 708
"customCQPrompt": "", // 自定义文本分类提示词(不支持工具和函数调用的模型
"customExtractPrompt": "", // 自定义内容提取提示词
"defaultSystemChatPrompt": "", // 对话默认携带的系统提示词
"defaultConfig": {} // 请求API时,挟带一些默认配置(比如 GLM4 top_p)
"defaultConfig": {}, // 请求API时,挟带一些默认配置(比如 GLM4 top_p)
"fieldMap": {} // 字段映射(o1 模型需要把 max_tokens 映射为 max_completion_tokens)
},
{
"provider": "OpenAI",
"model": "gpt-4o",
"name": "gpt-4o",
"avatar": "/imgs/model/openai.svg",
"maxContext": 125000,
"maxResponse": 4000,
"quoteMaxToken": 120000,
......@@ -71,14 +72,15 @@ weight: 708
"customCQPrompt": "",
"customExtractPrompt": "",
"defaultSystemChatPrompt": "",
"defaultConfig": {}
"defaultConfig": {},
"fieldMap": {}
},
{
"provider": "OpenAI",
"model": "o1-mini",
"name": "o1-mini",
"avatar": "/imgs/model/openai.svg",
"maxContext": 125000,
"maxResponse": 4000,
"maxResponse": 65000,
"quoteMaxToken": 120000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
......@@ -99,11 +101,11 @@ weight: 708
}
},
{
"provider": "OpenAI",
"model": "o1-preview",
"name": "o1-preview",
"avatar": "/imgs/model/openai.svg",
"maxContext": 125000,
"maxResponse": 4000,
"maxResponse": 32000,
"quoteMaxToken": 120000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
......@@ -126,21 +128,18 @@ weight: 708
],
"vectorModels": [
{
"model": "text-embedding-ada-002", // 模型名(与OneAPI对应)
"name": "Embedding-2", // 模型展示名
"avatar": "/imgs/model/openai.svg", // logo
"charsPointsPrice": 0, // n积分/1k token
"defaultToken": 700, // 默认文本分割时候的 token
"maxToken": 3000, // 最大 token
"weight": 100, // 优先训练权重
"defaultConfig": {}, // 自定义额外参数。例如,如果希望使用 embedding3-large 的话,可以传入 dimensions:1024,来返回1024维度的向量。(目前必须小于1536维度)
"dbConfig": {}, // 存储时的额外参数(非对称向量模型时候需要用到)
"queryConfig": {} // 参训时的额外参数
"provider": "OpenAI",
"model": "text-embedding-3-small",
"name": "text-embedding-3-small",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100
},
{
"provider": "OpenAI",
"model": "text-embedding-3-large",
"name": "text-embedding-3-large",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
......@@ -150,13 +149,16 @@ weight: 708
}
},
{
"model": "text-embedding-3-small",
"name": "text-embedding-3-small",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100
"provider": "OpenAI",
"model": "text-embedding-ada-002", // 模型名(与OneAPI对应)
"name": "Embedding-2", // 模型展示名
"charsPointsPrice": 0, // n积分/1k token
"defaultToken": 700, // 默认文本分割时候的 token
"maxToken": 3000, // 最大 token
"weight": 100, // 优先训练权重
"defaultConfig": {}, // 自定义额外参数。例如,如果希望使用 embedding3-large 的话,可以传入 dimensions:1024,来返回1024维度的向量。(目前必须小于1536维度)
"dbConfig": {}, // 存储时的额外参数(非对称向量模型时候需要用到)
"queryConfig": {} // 参训时的额外参数
}
],
"reRankModels": [],
......@@ -183,25 +185,33 @@ weight: 708
}
```
## 关于模型 logo
## 模型提供商
为了方便模型分类展示,FastGPT 内置了部分模型提供商的名字和 Logo。如果你期望补充提供商,可[提交 Issue](https://github.com/labring/FastGPT/issues),并提供几个信息:
1. 厂商官网地址
2. 厂商 SVG logo,建议是正方形图片。
目前已支持的提供商, 复制 "-" 之前的字符串,作为 provider 的值。
统一放置在项目的`public/imgs/model/xxx`目录中,目前内置了以下几种,如果有需要,可以PR增加。默认logo为 Hugging face 的 logo~
- OpenAI
- Claude
- Gemini
- MistralAI
- Qwen - 通义千问
- Doubao - 豆包
- ChatGLM - 智谱
- DeepSeek - 深度求索
- Moonshot - 月之暗面
- MiniMax
- SparkDesk - 讯飞星火
- Hunyuan - 腾讯混元
- Baichuan - 百川
- Yi - 零一万物
- Ernie - 文心一言
- Ollama
- Other - 其他
- /imgs/model/baichuan.svg - 百川智能
- /imgs/model/chatglm.svg - 智谱清言
- /imgs/model/claude.svg - claude
- /imgs/model/deepseek.svg - deepseek
- /imgs/model/doubao.svg - 火山豆包
- /imgs/model/ernie.svg - 文心一言
- /imgs/model/gemini.svg - gemini
- /imgs/model/huggingface.svg - Hugging face【默认logo】
- /imgs/model/minimax.svg - minimax
- /imgs/model/moonshot.svg - 月之暗面
- /imgs/model/openai.svg - OpenAI GPT
- /imgs/model/qwen.svg - 通义千问
- /imgs/model/sparkDesk.svg - 讯飞星火
- /imgs/model/yi.svg - 零一万物
- /imgs/model/hunyuan.svg - 腾讯混元
## 特殊模型
......
......@@ -7,6 +7,46 @@ toc: true
weight: 808
---
## 更新指南
### 1. 更新镜像:
- 更新 fastgpt 镜像 tag: v4.8.16-beta
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.16-beta
- Sandbox 镜像 tag: v4.8.16-beta
### 2. 更新配置文件
参考最新的[配置文件](/docs/development/configuration/),更新 `config.json` 或 admin 中模型文件配置。给 LLMModel 和 VectorModel 增加 `provider` 字段,以便进行模型分类。例如:
```json
{
"provider": "OpenAI", // 这是新增的
"model": "gpt-4o",
"name": "gpt-4o",
"maxContext": 125000,
"maxResponse": 4000,
"quoteMaxToken": 120000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
"censor": false,
"vision": true,
"datasetProcess": true,
"usedInClassify": true,
"usedInExtractFields": true,
"usedInToolCall": true,
"usedInQueryExtension": true,
"toolChoice": true,
"functionCall": false,
"customCQPrompt": "",
"customExtractPrompt": "",
"defaultSystemChatPrompt": "",
"defaultConfig": {},
"fieldMap": {}
}
```
## 完整更新内容
......@@ -18,12 +58,14 @@ weight: 808
6. 新增 - 商业版支持飞书和语雀知识库导入。[点击查看教程](/docs/guide/knowledge_base/lark_dataset/)
7. 新增 - sandbox 新增 createHmac 加密全局方法。
8. 新增 - 工作流右键支持全部折叠。
9. 优化 - 工作流/简易模式变量初始化代码,去除监听初始化,避免因渲染顺序不一致导致的失败。
10. 优化 - 工作流获取数据类型不一致数据时,增加类型转化,避免 undefined。
11. 修复 - 无法自动切换默认语言。增加分享链接,强制执行一次切换默认语言。
12. 修复 - 数组选择器自动兼容 4.8.13 以前的数据。
13. 修复 - 站点同步知识库,链接同步时未使用选择器。
14. 修复 - 简易模式转工作流,没有把系统配置项转化。
15. 修复 - 插件独立运行,变量初始值未赋上。
16. 修复 - 工作流使用弹窗组件时,关闭弹窗后,有时候会出现页面偏移。
17. 修复 - 插件调试时,日志未保存插件输入参数。
\ No newline at end of file
9. 优化 - 模型选择器。
10. 优化 - SSR 渲染,预判断是移动端还是 pc 端,减少页面抖动。
11. 优化 - 工作流/简易模式变量初始化代码,去除监听初始化,避免因渲染顺序不一致导致的失败。
12. 优化 - 工作流获取数据类型不一致数据时,增加类型转化,避免 undefined。
13. 修复 - 无法自动切换默认语言。增加分享链接,强制执行一次切换默认语言。
14. 修复 - 数组选择器自动兼容 4.8.13 以前的数据。
15. 修复 - 站点同步知识库,链接同步时未使用选择器。
16. 修复 - 简易模式转工作流,没有把系统配置项转化。
17. 修复 - 插件独立运行,变量初始值未赋上。
18. 修复 - 工作流使用弹窗组件时,关闭弹窗后,有时候会出现页面偏移。
19. 修复 - 插件调试时,日志未保存插件输入参数。
\ No newline at end of file
......@@ -2,3 +2,5 @@ export const HUMAN_ICON = `/icon/human.svg`;
export const LOGO_ICON = `/icon/logo.svg`;
export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`;
export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`;
export const isProduction = process.env.NODE_ENV === 'production';
import type { ModelProviderIdType } from './provider';
export type LLMModelItemType = {
provider: ModelProviderIdType;
model: string;
name: string;
avatar?: string;
avatar?: string; // model icon, from provider
maxContext: number;
maxResponse: number;
quoteMaxToken: number;
......@@ -31,6 +34,7 @@ export type LLMModelItemType = {
};
export type VectorModelItemType = {
provider: ModelProviderIdType;
model: string; // model name
name: string; // show name
avatar?: string;
......
import type { LLMModelItemType, VectorModelItemType } from './model.d';
import { getModelProvider, ModelProviderIdType } from './provider';
export const defaultQAModels: LLMModelItemType[] = [
{
provider: 'OpenAI',
model: 'gpt-4o-mini',
name: 'gpt-4o-mini',
maxContext: 16000,
......@@ -23,6 +25,7 @@ export const defaultQAModels: LLMModelItemType[] = [
export const defaultVectorModels: VectorModelItemType[] = [
{
provider: 'OpenAI',
model: 'text-embedding-3-small',
name: 'Embedding-2',
charsPointsPrice: 0,
......@@ -31,3 +34,15 @@ export const defaultVectorModels: VectorModelItemType[] = [
weight: 100
}
];
export const getModelFromList = (
modelList: { provider: ModelProviderIdType; name: string; model: string }[],
model: string
) => {
const modelData = modelList.find((item) => item.model === model) ?? modelList[0];
const provider = getModelProvider(modelData.provider);
return {
...modelData,
avatar: provider.avatar
};
};
import { i18nT } from '../../../web/i18n/utils';
export type ModelProviderIdType =
| 'OpenAI'
| 'Claude'
| 'Gemini'
| 'MistralAI'
| 'Qwen'
| 'Doubao'
| 'ChatGLM'
| 'DeepSeek'
| 'Moonshot'
| 'MiniMax'
| 'SparkDesk'
| 'Hunyuan'
| 'Baichuan'
| 'Yi'
| 'Ernie'
| 'Ollama'
| 'Other';
export type ModelProviderType = {
id: ModelProviderIdType;
name: string;
avatar: string;
};
export const ModelProviderList: ModelProviderType[] = [
{
id: 'OpenAI',
name: 'OpenAI',
avatar: 'model/openai'
},
{
id: 'Claude',
name: 'Claude',
avatar: 'model/claude'
},
{
id: 'Gemini',
name: 'Gemini',
avatar: 'model/gemini'
},
{
id: 'MistralAI',
name: 'MistralAI',
avatar: 'model/huggingface'
},
{
id: 'Qwen',
name: i18nT('common:model_qwen'),
avatar: 'model/qwen'
},
{
id: 'Doubao',
name: i18nT('common:model_doubao'),
avatar: 'model/doubao'
},
{
id: 'ChatGLM',
name: i18nT('common:model_chatglm'),
avatar: 'model/chatglm'
},
{
id: 'DeepSeek',
name: 'DeepSeek',
avatar: 'model/deepseek'
},
{
id: 'Moonshot',
name: i18nT('common:model_moonshot'),
avatar: 'model/moonshot'
},
{
id: 'MiniMax',
name: 'MiniMax',
avatar: 'model/minimax'
},
{
id: 'SparkDesk',
name: i18nT('common:model_sparkdesk'),
avatar: 'model/sparkDesk'
},
{
id: 'Hunyuan',
name: i18nT('common:model_hunyuan'),
avatar: 'model/hunyuan'
},
{
id: 'Baichuan',
name: i18nT('common:model_baichuan'),
avatar: 'model/baichuan'
},
{
id: 'Yi',
name: i18nT('common:model_yi'),
avatar: 'model/yi'
},
{
id: 'Ernie',
name: i18nT('common:model_ernie'),
avatar: 'model/ernie'
},
{
id: 'Ollama',
name: 'Ollama',
avatar: 'model/ollama'
},
{
id: 'Other',
name: i18nT('common:model_other'),
avatar: 'model/huggingface'
}
];
export const ModelProviderMap = Object.fromEntries(
ModelProviderList.map((item) => [item.id, item])
);
export const getModelProvider = (provider: ModelProviderIdType) => {
return ModelProviderMap[provider] ?? ModelProviderMap.Other;
};
import path from 'path';
import { isProduction } from '@fastgpt/global/common/system/constants';
export const tmpFileDirPath =
process.env.NODE_ENV === 'production' ? '/app/tmp' : path.join(process.cwd(), 'tmp');
export const tmpFileDirPath = isProduction ? '/app/tmp' : path.join(process.cwd(), 'tmp');
export const previewMaxCharCount = 3000;
import { isProduction } from '../system/constants';
import { isProduction } from '@fastgpt/global/common/system/constants';
import fs from 'fs';
import path from 'path';
......
export const FastGPTProUrl = process.env.PRO_URL ? `${process.env.PRO_URL}/api` : '';
export const isProduction = process.env.NODE_ENV === 'production';
import { I18nNsType } from '../../types/i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export const serviceSideProps = async (content: any, ns: I18nNsType = []) => {
const lang = content.req?.cookies?.NEXT_LOCALE || content.locale;
const extraLng = content.req?.cookies?.NEXT_LOCALE ? undefined : content.locales;
// Device size
const deviceSize = content.req?.cookies?.NEXT_DEVICE_SIZE || 'pc';
return {
...(await serverSideTranslations(lang, ['common', ...ns], null, extraLng)),
deviceSize
};
};
......@@ -29,6 +29,7 @@ export const iconPaths = {
'common/courseLight': () => import('./icons/common/courseLight.svg'),
'common/customTitleLight': () => import('./icons/common/customTitleLight.svg'),
'common/data': () => import('./icons/common/data.svg'),
'common/dingtalkFill': () => import('./icons/common/dingtalkFill.svg'),
'common/editor/resizer': () => import('./icons/common/editor/resizer.svg'),
'common/errorFill': () => import('./icons/common/errorFill.svg'),
'common/file/move': () => import('./icons/common/file/move.svg'),
......@@ -91,7 +92,6 @@ export const iconPaths = {
'common/voiceLight': () => import('./icons/common/voiceLight.svg'),
'common/warn': () => import('./icons/common/warn.svg'),
'common/wechatFill': () => import('./icons/common/wechatFill.svg'),
'common/dingtalkFill': () => import('./icons/common/dingtalkFill.svg'),
configmap: () => import('./icons/configmap.svg'),
copy: () => import('./icons/copy.svg'),
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
......@@ -256,7 +256,6 @@ export const iconPaths = {
'core/workflow/template/formInput': () => import('./icons/core/workflow/template/formInput.svg'),
'core/workflow/template/getTime': () => import('./icons/core/workflow/template/getTime.svg'),
'core/workflow/template/google': () => import('./icons/core/workflow/template/google.svg'),
'core/workflow/template/searxng': () => import('./icons/core/workflow/template/searxng.svg'),
'core/workflow/template/httpRequest': () =>
import('./icons/core/workflow/template/httpRequest.svg'),
'core/workflow/template/ifelse': () => import('./icons/core/workflow/template/ifelse.svg'),
......@@ -275,6 +274,7 @@ export const iconPaths = {
'core/workflow/template/readFiles': () => import('./icons/core/workflow/template/readFiles.svg'),
'core/workflow/template/reply': () => import('./icons/core/workflow/template/reply.svg'),
'core/workflow/template/runApp': () => import('./icons/core/workflow/template/runApp.svg'),
'core/workflow/template/searxng': () => import('./icons/core/workflow/template/searxng.svg'),
'core/workflow/template/stopTool': () => import('./icons/core/workflow/template/stopTool.svg'),
'core/workflow/template/systemConfig': () =>
import('./icons/core/workflow/template/systemConfig.svg'),
......@@ -341,6 +341,22 @@ export const iconPaths = {
'modal/selectSource': () => import('./icons/modal/selectSource.svg'),
'modal/setting': () => import('./icons/modal/setting.svg'),
'modal/teamPlans': () => import('./icons/modal/teamPlans.svg'),
'model/baichuan': () => import('./icons/model/baichuan.svg'),
'model/chatglm': () => import('./icons/model/chatglm.svg'),
'model/claude': () => import('./icons/model/claude.svg'),
'model/deepseek': () => import('./icons/model/deepseek.svg'),
'model/doubao': () => import('./icons/model/doubao.svg'),
'model/ernie': () => import('./icons/model/ernie.svg'),
'model/gemini': () => import('./icons/model/gemini.svg'),
'model/huggingface': () => import('./icons/model/huggingface.svg'),
'model/hunyuan': () => import('./icons/model/hunyuan.svg'),
'model/minimax': () => import('./icons/model/minimax.svg'),
'model/moonshot': () => import('./icons/model/moonshot.svg'),
'model/ollama': () => import('./icons/model/ollama.svg'),
'model/openai': () => import('./icons/model/openai.svg'),
'model/qwen': () => import('./icons/model/qwen.svg'),
'model/sparkDesk': () => import('./icons/model/sparkDesk.svg'),
'model/yi': () => import('./icons/model/yi.svg'),
more: () => import('./icons/more.svg'),
moreLine: () => import('./icons/moreLine.svg'),
out: () => import('./icons/out.svg'),
......
<svg t="1734679654271" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7734" width="64" height="64"><path d="M604.814264 81.402675a1319.065676 1319.065676 0 0 1 36.517088 237.361069c-88.649795-11.49071-173.856333 0.427554-255.619614 35.756315a427.093207 427.093207 0 0 0-59.340267 34.23477 450.161355 450.161355 0 0 0-66.947994 49.450223C170.040297 521.839834 103.600499 620.994377 60.10104 735.667162c-0.715126-140.51471 0.045646-281.003554 2.282318-421.468054C76.803043 215.078039 122.195305 133.675364 198.561664 69.991085c95.178746-71.853456 199.150502-87.575584 311.916791-47.167905 35.223774 13.789765 66.669551 33.315756 94.335809 58.579495z" fill="#A569FF" p-id="7735"></path><path d="M604.814264 81.402675a56768.782835 56768.782835 0 0 0 346.912333 352.998514c-94.145617-61.14482-197.102502-99.183453-308.8737-114.115899-1.014871 0-1.521545-0.506675-1.521545-1.521546a1319.065676 1319.065676 0 0 0-36.517088-237.361069z" fill="#2038FB" p-id="7736"></path><path d="M641.331352 318.763744c0 1.014871 0.506675 1.521545 1.521545 1.521546 7.105617 102.384785 3.301753 204.328321-11.411589 305.830609-13.789765 84.228184-40.416808 163.855215-79.88113 238.882615a499.523328 499.523328 0 0 1-103.465081 35.756315c-131.312404 17.313664-206.122223-39.744285-224.427935-171.173848-8.108315-103.991536 14.20819-200.863762 66.947994-290.615156a679.070241 679.070241 0 0 1 35.756315-50.210996 427.093207 427.093207 0 0 1 59.340267-34.23477c81.763281-35.328761 166.969819-47.247025 255.619614-35.756315z" fill="#FEFEFE" p-id="7737"></path><path d="M642.852897 320.28529c111.771198 14.932446 214.728083 52.971079 308.8737 114.115899 1.240059 2.034306 3.015703 3.555851 5.325409 4.564636 6.883471 14.958312 10.18066 30.681961 9.890045 47.167905-3.882984 72.767905-26.95874 138.70255-69.230313 197.800891-44.500636 59.716089-96.993949 111.196053-157.47994 154.43685-86.741777 63.043709-180.570912 113.508802-281.485884 151.393759 36.800095-37.296119 67.739198-78.886039 92.814264-124.766716 39.464321-75.0274 66.091364-154.654431 79.88113-238.882615 14.713343-101.502288 18.517207-203.445825 11.411589-305.830609z" fill="#37E0BE" p-id="7738"></path><path d="M326.371471 388.754829a679.070241 679.070241 0 0 0-35.756315 50.210996c-52.739804 89.751394-75.056309 186.62362-66.947994 290.615156 18.305712 131.429563 93.11553 188.487513 224.427935 171.173848a499.523328 499.523328 0 0 0 103.465081-35.756315c-25.075067 45.880678-56.014169 87.470597-92.814264 124.766716-39.120452 18.194639-80.202175 29.099554-123.245171 32.713225-87.99553 4.042746-161.790478-26.134062-221.384844-90.531947-45.209676-57.563103-63.215643-122.989551-54.014859-196.279346 43.499459-114.672785 109.939257-213.827328 199.322437-297.46211a450.161355 450.161355 0 0 1 66.947994-49.450223z" fill="#1F37FB" p-id="7739"></path></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="181px" height="256px" viewBox="0 0 181 256" enable-background="new 0 0 181 256" xml:space="preserve"> <image id="image0" width="181" height="256" x="0" y="0"
xlink:href="data:image/png;base64,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
MDYrMDA6MDAeYvBBAAAAAElFTkSuQmCC" />
</svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1734679619961" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4292" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M955 607.2L843.6 412.1l47.8-91.6c6.6-8.7 8.3-21.5 5.6-31.8L835.7 178c-4.7-7.3-12.8-11.8-21.5-12H586.3l-51-89.2c-3.6-7-10.5-11.8-18.3-12.7H396.7c-8.9 0.2-15.2 6.7-19.6 14.4l-1.9 3.1-113.8 195.1H152.2c-9-0.2-17.5 4.3-22.3 12L67 400.1c-4 8-4 17.5 0 25.5l113.1 196.7-51 89.2c-4 8-4 17.4 0 25.5l58.1 102c4.7 7.8 13.2 12.7 22.3 12.7h227.8l54.9 95.5c4.2 7.2 11.6 11.9 19.9 12.8h129c8.9-0.2 17.1-5 21.5-12.7L775 750.5h100.4c8.9-0.8 16.9-5.8 21.5-13.5L955 634.3c5.4-8.3 5.4-18.9 0-27.1zM814.1 620L756 512.5 517 933.7l-65.3-107.5H212.8l57.4-104.3H392L153 302.2h125L396.7 90.3l59.7 104.3-61.3 107.5H873l-60.5 106.8 120.3 211H814.1z" fill="#605BEC" p-id="4293"></path><path d="M511.4 660.5l149-238.9H361.7l149.7 238.9z m0 0" fill="#605BEC" p-id="4294"></path></svg>
\ No newline at end of file
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="300" cy="300" r="300" fill="white" />
<rect x="409.733" y="340.032" width="42.3862" height="151.648" rx="21.1931" fill="#003425" />
<path fill-rule="evenodd" clip-rule="evenodd"
......
......@@ -8,6 +8,7 @@ import MyImage from './MyImage';
const MyPhotoView = ({ ...props }: ImageProps) => {
const { isPc } = useSystem();
return (
<PhotoProvider
maskOpacity={0.6}
......
import React, { useRef, useCallback, useState, useMemo } from 'react';
import { Button, useDisclosure, Box, Flex, useOutsideClick, Checkbox } from '@chakra-ui/react';
import React, { useRef, useCallback, useState, useMemo, useEffect } from 'react';
import {
Button,
useDisclosure,
Box,
Flex,
useOutsideClick,
Checkbox,
css,
Menu,
MenuButton,
MenuList
} from '@chakra-ui/react';
import { ListItemType, MultipleArraySelectProps, MultipleSelectProps } from './type';
import EmptyTip from '../EmptyTip';
import { useTranslation } from 'next-i18next';
......@@ -14,18 +25,31 @@ export const MultipleRowSelect = ({
maxH = 300,
onSelect,
popDirection = 'bottom',
styles,
ButtonProps,
changeOnEverySelect = false
}: MultipleSelectProps) => {
const { t } = useTranslation();
const ref = useRef<HTMLDivElement>(null);
const ButtonRef = useRef<HTMLButtonElement>(null);
const { isOpen, onOpen, onClose } = useDisclosure();
const [cloneValue, setCloneValue] = useState(value);
useOutsideClick({
ref: ref,
handler: onClose
});
const MenuRef = useRef<(HTMLDivElement | null)[]>([]);
const SelectedItemRef = useRef<(HTMLDivElement | null)[]>([]);
useEffect(() => {
if (isOpen) {
for (let i = 0; i < MenuRef.current.length; i++) {
const menu = MenuRef.current[i];
const selectedItem = SelectedItemRef.current[i];
if (menu && selectedItem) {
menu.scrollTop = selectedItem.offsetTop - menu.offsetTop - 100;
}
}
}
}, [isOpen]);
const minWidth = `${MenuRef.current?.[0]?.offsetWidth || 0}px`;
const RenderList = useCallback(
({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => {
......@@ -37,11 +61,14 @@ export const MultipleRowSelect = ({
return (
<>
<Box
ref={(ref) => {
MenuRef.current[index] = ref;
}}
className="nowheel"
flex={'1 0 auto'}
// width={0}
px={2}
borderLeft={index !== 0 ? 'base' : 'none'}
minW={index !== 0 ? minWidth : 'auto'}
maxH={`${maxH}px`}
overflowY={'auto'}
whiteSpace={'nowrap'}
......@@ -49,13 +76,17 @@ export const MultipleRowSelect = ({
{list.map((item) => (
<Flex
key={item.value}
ref={(ref) => {
if (item.value === selectedValue) {
SelectedItemRef.current[index] = ref;
}
}}
py={2}
cursor={'pointer'}
px={2}
borderRadius={'md'}
_hover={{
bg: 'primary.50',
color: 'primary.600'
bg: 'primary.50'
}}
onClick={() => {
const newValue = [...cloneValue];
......@@ -81,6 +112,7 @@ export const MultipleRowSelect = ({
}}
{...(item.value === selectedValue
? {
bg: 'primary.50',
color: 'primary.600'
}
: {})}
......@@ -109,60 +141,73 @@ export const MultipleRowSelect = ({
}, [value, onOpen]);
return (
<Box ref={ref} position={'relative'}>
<Button
justifyContent={'space-between'}
width={'100%'}
variant={'whitePrimaryOutline'}
size={'lg'}
fontSize={'sm'}
px={3}
outline={'none'}
rightIcon={<MyIcon name={'core/chat/chevronDown'} w="1rem" color={'myGray.500'} />}
_active={{
transform: 'none'
}}
{...(isOpen
? {
borderColor: 'primary.600',
color: 'primary.700',
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
}
: {
borderColor: 'myGray.200',
boxShadow: 'none'
})}
{...styles}
onClick={() => (isOpen ? onClose() : onOpenSelect())}
<Box
css={css({
'& div': {
width: 'auto !important'
}
})}
>
<Menu
autoSelect={false}
isOpen={isOpen}
onOpen={onOpenSelect}
onClose={onClose}
strategy={'fixed'}
matchWidth
>
<Box>{label ?? placeholder}</Box>
</Button>
{isOpen && (
<Box
position={'absolute'}
{...(popDirection === 'top'
<MenuButton
as={Button}
ref={ButtonRef}
width={'100%'}
px={3}
rightIcon={<MyIcon name={'core/chat/chevronDown'} w={4} color={'myGray.500'} />}
variant={'whitePrimaryOutline'}
size={'lg'}
fontSize={'sm'}
textAlign={'left'}
_active={{
transform: 'none'
}}
{...(isOpen
? {
transform: 'translateY(-105%)',
top: '0'
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)',
borderColor: 'primary.600',
color: 'primary.700'
}
: {
transform: 'translateY(105%)',
bottom: '0'
})}
py={2}
bg={'white'}
: {})}
{...ButtonProps}
>
<Box>{label ?? placeholder}</Box>
</MenuButton>
<MenuList
className={ButtonProps?.className}
minW={(() => {
const w = ButtonRef.current?.clientWidth;
if (w) {
return `${w}px !important`;
}
const width = ButtonProps?.width;
return Array.isArray(width)
? width.map((item) => `${item} !important`)
: `${width} !important`;
})()}
w={'auto'}
px={'6px'}
py={'6px'}
border={'1px solid #fff'}
boxShadow={'5'}
borderRadius={'md'}
zIndex={1}
minW={'100%'}
w={'max-content'}
boxShadow={
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
}
zIndex={99}
maxH={'40vh'}
overflowY={'auto'}
display={'flex'}
>
<Flex>
<RenderList list={list} index={0} />
</Flex>
</Box>
)}
<RenderList list={list} index={0} />
</MenuList>
</Menu>
</Box>
);
};
......@@ -176,7 +221,7 @@ export const MultipleRowArraySelect = ({
maxH = 300,
onSelect,
popDirection = 'bottom',
styles
ButtonProps
}: MultipleArraySelectProps) => {
const { t } = useTranslation();
const ref = useRef<HTMLDivElement>(null);
......@@ -318,7 +363,7 @@ export const MultipleRowArraySelect = ({
borderColor: 'myGray.200',
boxShadow: 'none'
})}
{...styles}
{...ButtonProps}
onClick={() => (isOpen ? onClose() : onOpenSelect())}
className="nowheel"
>
......@@ -343,7 +388,7 @@ export const MultipleRowArraySelect = ({
border={'1px solid #fff'}
boxShadow={'5'}
borderRadius={'md'}
zIndex={1}
zIndex={1000}
minW={'100%'}
w={'max-content'}
>
......
import { ButtonProps } from '@chakra-ui/react';
type ListItemType = {
alias?: string;
label: string | React.ReactNode;
......@@ -12,9 +14,9 @@ export type MultipleSelectProps = {
emptyTip?: string;
maxH?: number;
onSelect: (val: any[]) => void;
styles?: ButtonProps;
popDirection?: 'top' | 'bottom';
changeOnEverySelect?: boolean;
ButtonProps?: ButtonProps;
};
export type MultipleArraySelectProps = Omit<MultipleSelectProps, 'value'> & {
value?: any[][];
......
import React, { ReactNode, useMemo } from 'react';
import { createContext } from 'use-context-selector';
import { useMediaQuery } from '@chakra-ui/react';
import Cookies from 'js-cookie';
import { useEffect } from 'react';
const CookieKey = 'NEXT_DEVICE_SIZE';
const setSize = (value: string) => {
Cookies.set(CookieKey, value, { expires: 30 });
localStorage.setItem(CookieKey, value);
};
type useSystemContextType = {
isPc: boolean;
};
export const useSystemStoreContext = createContext<useSystemContextType>({
isPc: true
});
const SystemStoreContextProvider = ({
children,
device
}: {
children: ReactNode;
device?: 'pc' | 'mobile';
}) => {
const [isPc] = useMediaQuery('(min-width: 900px)');
useEffect(() => {
setSize(isPc ? 'pc' : 'mobile');
}, [isPc]);
const contextValue = useMemo(
() => ({
isPc: device ? device === 'pc' : isPc
}),
[device, isPc]
);
return (
<useSystemStoreContext.Provider value={contextValue}>{children}</useSystemStoreContext.Provider>
);
};
export default SystemStoreContextProvider;
import { useTranslation } from 'next-i18next';
import { useEffect } from 'react';
import { isProduction } from '@fastgpt/global/common/system/constants';
export const useBeforeunload = (props?: { callback?: () => any; tip?: string }) => {
const { t } = useTranslation();
......@@ -7,16 +8,15 @@ export const useBeforeunload = (props?: { callback?: () => any; tip?: string })
const { tip = t('common:common.Confirm to leave the page'), callback } = props || {};
useEffect(() => {
const listen =
process.env.NODE_ENV === 'production'
? (e: any) => {
e.preventDefault();
e.returnValue = tip;
callback?.();
}
: () => {
callback?.();
};
const listen = isProduction
? (e: any) => {
e.preventDefault();
e.returnValue = tip;
callback?.();
}
: () => {
callback?.();
};
window.addEventListener('beforeunload', listen);
return () => {
......
import { useMediaQuery } from '@chakra-ui/react';
import { useSystemStoreContext } from '../context/useSystem';
import { useContextSelector } from 'use-context-selector';
export const useSystem = () => {
const [isPc] = useMediaQuery('(min-width: 900px)');
const isPc = useContextSelector(useSystemStoreContext, (state) => state.isPc);
return { isPc };
};
......@@ -77,7 +77,7 @@
"usage_balance_notice": "Due to the system upgrade, the original \"automatic renewal and deduction from balance\" mode has been cancelled, and the balance recharge entrance has been closed. \nYour balance can be used to purchase points",
"user_account": "Username",
"user_team_team_name": "Team",
"verification_code_required": "Verification code cannot be empty",
"verification_code": "Verification code",
"you_can_convert": "you can redeem",
"yuan": "Yuan"
}
......@@ -906,6 +906,16 @@
"item_name": "Field Name",
"just_now": "just",
"key_repetition": "Key Repetition",
"model_baichuan": "Baichuan",
"model_chatglm": "ChatGLM",
"model_doubao": "Doubao",
"model_ernie": "Ernie",
"model_hunyuan": "Hunyuan",
"model_moonshot": "Moonshot",
"model_other": "Other",
"model_qwen": "Qwen",
"model_sparkdesk": "SprkDesk",
"model_yi": "Yi",
"move.confirm": "Confirm move",
"navbar.Account": "Account",
"navbar.Chat": "Chat",
......
......@@ -77,7 +77,7 @@
"usage_balance_notice": "由于系统升级,原“自动续费从余额扣款”模式取消,余额充值入口关闭。您的余额可用于购买积分",
"user_account": "账号",
"user_team_team_name": "团队名",
"verification_code_required": "验证码不能为空",
"verification_code": "验证码",
"you_can_convert": "您可以兑换",
"yuan": "元"
}
......@@ -905,6 +905,16 @@
"item_name": "字段名",
"just_now": "刚刚",
"key_repetition": "key 重复",
"model_baichuan": "百川智能",
"model_chatglm": "ChatGLM",
"model_doubao": "豆包",
"model_ernie": "文心一言",
"model_hunyuan": "腾讯混元",
"model_moonshot": "月之暗面",
"model_other": "自定义",
"model_qwen": "阿里千问",
"model_sparkdesk": "讯飞星火",
"model_yi": "零一万物",
"move.confirm": "确认移动",
"navbar.Account": "账号",
"navbar.Chat": "聊天",
......
......@@ -77,7 +77,7 @@
"usage_balance_notice": "由於系統升級,原「自動續費從餘額扣款」模式取消,餘額儲值入口關閉。\n您的餘額可用於購買積分",
"user_account": "帳號",
"user_team_team_name": "團隊名",
"verification_code_required": "驗證碼不能為空",
"verification_code": "驗證碼",
"you_can_convert": "您可以兌換",
"yuan": "元"
}
......@@ -907,6 +907,16 @@
"item_name": "欄位名稱",
"just_now": "剛剛",
"key_repetition": "鍵值重複",
"model_baichuan": "百川智能",
"model_chatglm": "ChatGLM",
"model_doubao": "豆包",
"model_ernie": "文心一言",
"model_hunyuan": "騰訊混元",
"model_moonshot": "月之暗面",
"model_other": "自訂",
"model_qwen": "阿里千問",
"model_sparkdesk": "訊飛星火",
"model_yi": "零一萬物",
"move.confirm": "確認移動",
"navbar.Account": "帳戶",
"navbar.Chat": "對話",
......
......@@ -11,9 +11,9 @@
},
"llmModels": [
{
"provider": "OpenAI", // 模型提供商,主要用于分类展示,目前已经内置提供商包括:https://github.com/labring/FastGPT/blob/main/packages/global/core/ai/provider.ts, pr 提供新的提供商,或直接填写 Other
"model": "gpt-4o-mini", // 模型名(对应OneAPI中渠道的模型名)
"name": "gpt-4o-mini", // 模型别名
"avatar": "/imgs/model/openai.svg", // 模型的logo
"maxContext": 125000, // 最大上下文
"maxResponse": 16000, // 最大回复
"quoteMaxToken": 120000, // 最大引用内容
......@@ -35,9 +35,9 @@
"fieldMap": {} // 字段映射(o1 模型需要把 max_tokens 映射为 max_completion_tokens)
},
{
"provider": "OpenAI",
"model": "gpt-4o",
"name": "gpt-4o",
"avatar": "/imgs/model/openai.svg",
"maxContext": 125000,
"maxResponse": 4000,
"quoteMaxToken": 120000,
......@@ -59,9 +59,9 @@
"fieldMap": {}
},
{
"provider": "OpenAI",
"model": "o1-mini",
"name": "o1-mini",
"avatar": "/imgs/model/openai.svg",
"maxContext": 125000,
"maxResponse": 65000,
"quoteMaxToken": 120000,
......@@ -84,9 +84,9 @@
}
},
{
"provider": "OpenAI",
"model": "o1-preview",
"name": "o1-preview",
"avatar": "/imgs/model/openai.svg",
"maxContext": 125000,
"maxResponse": 32000,
"quoteMaxToken": 120000,
......@@ -111,21 +111,18 @@
],
"vectorModels": [
{
"model": "text-embedding-ada-002", // 模型名(与OneAPI对应)
"name": "Embedding-2", // 模型展示名
"avatar": "/imgs/model/openai.svg", // logo
"charsPointsPrice": 0, // n积分/1k token
"defaultToken": 700, // 默认文本分割时候的 token
"maxToken": 3000, // 最大 token
"weight": 100, // 优先训练权重
"defaultConfig": {}, // 自定义额外参数。例如,如果希望使用 embedding3-large 的话,可以传入 dimensions:1024,来返回1024维度的向量。(目前必须小于1536维度)
"dbConfig": {}, // 存储时的额外参数(非对称向量模型时候需要用到)
"queryConfig": {} // 参训时的额外参数
"provider": "OpenAI",
"model": "text-embedding-3-small",
"name": "text-embedding-3-small",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100
},
{
"provider": "OpenAI",
"model": "text-embedding-3-large",
"name": "text-embedding-3-large",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
......@@ -135,13 +132,16 @@
}
},
{
"model": "text-embedding-3-small",
"name": "text-embedding-3-small",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100
"provider": "OpenAI",
"model": "text-embedding-ada-002", // 模型名(与OneAPI对应)
"name": "Embedding-2", // 模型展示名
"charsPointsPrice": 0, // n积分/1k token
"defaultToken": 700, // 默认文本分割时候的 token
"maxToken": 3000, // 最大 token
"weight": 100, // 优先训练权重
"defaultConfig": {}, // 自定义额外参数。例如,如果希望使用 embedding3-large 的话,可以传入 dimensions:1024,来返回1024维度的向量。(目前必须小于1536维度)
"dbConfig": {}, // 存储时的额外参数(非对称向量模型时候需要用到)
"queryConfig": {} // 参训时的额外参数
}
],
"reRankModels": [],
......
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
import MySelect, { SelectProps } from '@fastgpt/web/components/common/MySelect';
import { HUGGING_FACE_ICON, LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { Box, Flex, HStack, useDisclosure } from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import dynamic from 'next/dynamic';
import { ModelProviderList } from '@fastgpt/global/core/ai/provider';
import MultipleRowSelect from '@fastgpt/web/components/common/MySelect/MultipleRowSelect';
import { getModelFromList } from '@fastgpt/global/core/ai/model';
const AiPointsModal = dynamic(() =>
import('@/pages/price/components/Points').then((mod) => mod.AiPointsModal)
......@@ -18,10 +20,9 @@ type Props = SelectProps & {
disableTip?: string;
};
const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
const { t } = useTranslation();
const { feConfigs, llmModelList, vectorModelList } = useSystemStore();
const router = useRouter();
const {
isOpen: isOpenAiPointsModal,
......@@ -40,9 +41,7 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
}, [props.size]);
const avatarList = list.map((item) => {
const modelData =
llmModelList.find((model) => model.model === item.value) ||
vectorModelList.find((model) => model.model === item.value);
const modelData = getModelFromList([...llmModelList, ...vectorModelList], item.value);
return {
value: item.value,
......@@ -55,14 +54,14 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
fallbackSrc={HUGGING_FACE_ICON}
w={avatarSize}
/>
<Box>{item.label}</Box>
<Box>{modelData.name}</Box>
</Flex>
)
};
});
const expandList = useMemo(() => {
return feConfigs.show_pay
return feConfigs?.show_pay
? avatarList.concat({
label: (
<Flex alignItems={'center'}>
......@@ -108,5 +107,121 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
</Box>
);
};
const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
const { t } = useTranslation();
const { feConfigs, llmModelList, vectorModelList } = useSystemStore();
const [value, setValue] = useState<string[]>([]);
const {
isOpen: isOpenAiPointsModal,
onClose: onCloseAiPointsModal,
onOpen: onOpenAiPointsModal
} = useDisclosure();
const avatarSize = useMemo(() => {
const size = {
sm: '1rem',
md: '1.2rem',
lg: '1.4rem'
};
//@ts-ignore
return props.size ? size[props.size] : size['md'];
}, [props.size]);
const selectorList = useMemo(() => {
const renderList = ModelProviderList.map<{
label: React.JSX.Element;
value: string;
children: { label: string | React.ReactNode; value: string }[];
}>((provider) => ({
label: (
<Flex alignItems={'center'} py={1}>
<Avatar
borderRadius={'0'}
mr={2}
src={provider?.avatar || HUGGING_FACE_ICON}
fallbackSrc={HUGGING_FACE_ICON}
w={avatarSize}
/>
<Box>{t(provider.name as any)}</Box>
</Flex>
),
value: provider.id,
children: []
}));
for (const item of list) {
const modelData = getModelFromList([...llmModelList, ...vectorModelList], item.value);
const provider =
renderList.find((item) => item.value === (modelData?.provider || 'Other')) ??
renderList[renderList.length - 1];
provider.children.push({
label: modelData.name,
value: modelData.model
});
}
return renderList.filter((item) => item.children.length > 0);
}, [avatarSize, list, llmModelList, t, vectorModelList]);
const onSelect = useCallback(
(e: string[]) => {
return onchange?.(e[1]);
},
[onchange]
);
const SelectedModel = useMemo(() => {
const modelData = getModelFromList([...llmModelList, ...vectorModelList], props.value);
setValue([modelData.provider, props.value]);
return (
<HStack spacing={1}>
<Avatar
borderRadius={'0'}
mr={2}
src={modelData?.avatar}
fallbackSrc={HUGGING_FACE_ICON}
w={avatarSize}
/>
<Box>{modelData?.name}</Box>
</HStack>
);
}, [avatarSize, llmModelList, props.value, vectorModelList]);
return (
<Box
css={{
span: {
display: 'block'
}
}}
>
<MyTooltip label={disableTip}>
<MultipleRowSelect
label={SelectedModel}
list={selectorList}
onSelect={onSelect}
value={value}
ButtonProps={{
isDisabled: !!disableTip
}}
/>
</MyTooltip>
{isOpenAiPointsModal && <AiPointsModal onClose={onCloseAiPointsModal} />}
</Box>
);
};
const AIModelSelector = (props: Props) => {
return props.list.length > 10 ? (
<MultipleRowSelector {...props} />
) : (
<OneRowSelector {...props} />
);
};
export default AIModelSelector;
import React from 'react';
import React, { useMemo } from 'react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { LLMModelTypeEnum, llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
import { Box, Button, css, useDisclosure } from '@chakra-ui/react';
......@@ -10,6 +10,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useMount } from 'ahooks';
import { getModelFromList } from '@fastgpt/global/core/ai/model';
type Props = {
llmModelType?: `${LLMModelTypeEnum}`;
......@@ -29,15 +30,19 @@ const SettingLLMModel = ({
const model = defaultData.model;
const modelList = llmModelList.filter((model) => {
if (!llmModelType) return true;
const filterField = llmModelTypeFilterMap[llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!model[filterField];
});
const modelList = useMemo(
() =>
llmModelList.filter((modelData) => {
if (!llmModelType) return true;
const filterField = llmModelTypeFilterMap[llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!modelData[filterField];
}),
[llmModelList, llmModelType]
);
const selectedModel = modelList.find((item) => item.model === model) || modelList[0];
const selectedModel = useMemo(() => getModelFromList(modelList, model), [modelList, model]);
const {
isOpen: isOpenAIChatSetting,
......
......@@ -14,6 +14,7 @@ import NextHead from '@/components/common/NextHead';
import { ReactElement, useEffect } from 'react';
import { NextPage } from 'next';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import SystemStoreContextProvider from '@fastgpt/web/context/useSystem';
type NextPageWithLayout = NextPage & {
setLayout?: (page: ReactElement) => JSX.Element;
......@@ -55,11 +56,13 @@ function App({ Component, pageProps }: AppPropsWithLayout) {
{scripts?.map((item, i) => <Script key={i} strategy="lazyOnload" {...item}></Script>)}
<QueryClientContext>
<I18nContextProvider>
<ChakraUIContext>
<Layout>{setLayout(<Component {...pageProps} />)}</Layout>
</ChakraUIContext>
</I18nContextProvider>
<SystemStoreContextProvider device={pageProps.deviceSize}>
<I18nContextProvider>
<ChakraUIContext>
<Layout>{setLayout(<Component {...pageProps} />)}</Layout>
</ChakraUIContext>
</I18nContextProvider>
</SystemStoreContextProvider>
</QueryClientContext>
</>
);
......
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { Box } from '@chakra-ui/react';
import { TrackEventName } from '@/web/common/system/constants';
......
......@@ -3,7 +3,7 @@ import ApiKeyTable from '@/components/support/apikey/Table';
import { useTranslation } from 'next-i18next';
import { Box } from '@chakra-ui/react';
import AccountContainer, { TabEnum } from './components/AccountContainer';
import { serviceSideProps } from '../../web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
const ApiKey = () => {
const { t } = useTranslation();
......
......@@ -6,7 +6,7 @@ import { useTranslation } from 'next-i18next';
import ApplyInvoiceModal from './components/ApplyInvoiceModal';
import { useRouter } from 'next/router';
import AccountContainer, { TabEnum } from '../components/AccountContainer';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
export enum InvoiceTabEnum {
bill = 'bill',
......
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useRef } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
......@@ -42,7 +42,7 @@ const AccountContainer = ({
return router.pathname.split('/').pop() as TabEnum;
}, [router.pathname]);
const tabList = [
const tabList = useRef([
{
icon: 'support/user/userLight',
label: t('account:personal_information'),
......@@ -108,7 +108,7 @@ const AccountContainer = ({
label: t('account:logout'),
value: TabEnum.loginout
}
];
]);
const { openConfirm, ConfirmModal } = useConfirm({
content: t('account:confirm_logout')
......@@ -129,57 +129,54 @@ const AccountContainer = ({
);
return (
<>
<Script src={getWebReqUrl('/js/qrcode.min.js')} strategy="lazyOnload"></Script>
<PageContainer isLoading={isLoading}>
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
{isPc ? (
<Flex
flexDirection={'column'}
p={4}
h={'100%'}
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<SideTabs<TabEnum>
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList}
value={currentTab}
onChange={setCurrentTab}
/>
<Flex alignItems={'center'}>
<Box w={'8px'} h={'8px'} borderRadius={'50%'} bg={'#67c13b'} />
<Box fontSize={'md'} ml={2}>
V{systemVersion}
</Box>
</Flex>
<PageContainer isLoading={isLoading}>
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
{isPc ? (
<Flex
flexDirection={'column'}
p={4}
h={'100%'}
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<SideTabs<TabEnum>
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList.current}
value={currentTab}
onChange={setCurrentTab}
/>
<Flex alignItems={'center'}>
<Box w={'8px'} h={'8px'} borderRadius={'50%'} bg={'#67c13b'} />
<Box fontSize={'md'} ml={2}>
V{systemVersion}
</Box>
</Flex>
) : (
<Box mb={3}>
<LightRowTabs<TabEnum>
m={'auto'}
w={'100%'}
size={isPc ? 'md' : 'sm'}
list={tabList.map((item) => ({
value: item.value,
label: item.label
}))}
value={currentTab}
onChange={setCurrentTab}
/>
</Box>
)}
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]} overflow={'auto'}>
{children}
</Flex>
) : (
<Box mb={3}>
<LightRowTabs<TabEnum>
m={'auto'}
w={'100%'}
size={isPc ? 'md' : 'sm'}
list={tabList.current.map((item) => ({
value: item.value,
label: item.label
}))}
value={currentTab}
onChange={setCurrentTab}
/>
</Box>
</Flex>
<ConfirmModal />
</PageContainer>
</>
)}
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]} overflow={'auto'}>
{children}
</Box>
</Flex>
<ConfirmModal />
</PageContainer>
);
};
......
......@@ -10,7 +10,7 @@ import { UserUpdateParams } from '@/types/user';
import TimezoneSelect from '@fastgpt/web/components/common/MySelect/TimezoneSelect';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import AccountContainer from './components/AccountContainer';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
const Individuation = () => {
const { t } = useTranslation();
......
import React, { useCallback } from 'react';
import {
ModalBody,
Box,
Flex,
Input,
ModalFooter,
Button,
HStack,
useDisclosure
} from '@chakra-ui/react';
import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button, HStack } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
......@@ -89,7 +80,7 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
></Input>
</Flex>
<Flex mt="6" alignItems="center" position={'relative'}>
<Box flex={'0 0 70px'}>{t('account_info:verification_code_required')}</Box>
<Box flex={'0 0 70px'}>{t('account_info:verification_code')}</Box>
<Input
flex={1}
bg={'myGray.50'}
......
......@@ -44,7 +44,7 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import AccountContainer from '../components/AccountContainer';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useRouter } from 'next/router';
import TeamSelector from '../components/TeamSelector';
......
......@@ -8,7 +8,7 @@ import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useTranslation } from 'next-i18next';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import AccountContainer, { TabEnum } from './components/AccountContainer';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
const InformTable = () => {
const { t } = useTranslation();
......
......@@ -26,7 +26,7 @@ import { useLoading } from '@fastgpt/web/hooks/useLoading';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import AccountContainer, { TabEnum } from './components/AccountContainer';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
const Promotion = () => {
const { t } = useTranslation();
......
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import AccountContainer from '../components/AccountContainer';
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
import Icon from '@fastgpt/web/components/common/Icon';
......
......@@ -31,7 +31,7 @@ import { formatNumber } from '@fastgpt/global/common/math/tools';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import AccountContainer, { TabEnum } from '../components/AccountContainer';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
const UsageDetail = dynamic(() => import('./UsageDetail'));
......
......@@ -19,7 +19,7 @@ import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import SaveButton from '../Workflow/components/SaveButton';
import { useBoolean, useDebounceEffect } from 'ahooks';
import { useBoolean, useDebounceEffect, useLockFn } from 'ahooks';
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
import {
compareSimpleAppSnapshot,
......@@ -29,6 +29,7 @@ import {
import PublishHistories from '../PublishHistoriesSlider';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import { isProduction } from '@fastgpt/global/common/system/constants';
const Header = ({
forbiddenSaveSnapshot,
......@@ -152,15 +153,22 @@ const Header = ({
{ wait: 500 }
);
const onLeaveAutoSave = useCallback(() => {
const onLeaveAutoSave = useLockFn(async () => {
if (isSaved) return;
try {
console.log('Leave auto save');
onClickSave({ isPublish: false, versionName: t('app:auto_save') });
return onClickSave({ isPublish: false, versionName: t('app:auto_save') });
} catch (error) {
console.error(error);
}
}, [isSaved, onClickSave, t]);
});
useEffect(() => {
return () => {
if (isProduction) {
onLeaveAutoSave();
}
};
}, []);
useBeforeunload({
tip: t('common:core.common.tip.leave page'),
callback: onLeaveAutoSave
......
......@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import { Box } from '@chakra-ui/react';
import dynamic from 'next/dynamic';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import NextHead from '@/components/common/NextHead';
import { useContextSelector } from 'use-context-selector';
import AppContextProvider, { AppContext } from './components/context';
......
import React, { useMemo, useState } from 'react';
import { Box, Flex, Button, useDisclosure, Input, InputGroup } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
......
......@@ -15,7 +15,7 @@ import ChatHistorySlider from './components/ChatHistorySlider';
import SliderApps from './components/SliderApps';
import ChatHeader from './components/ChatHeader';
import { useUserStore } from '@/web/support/user/useUserStore';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getMyApps } from '@/web/core/app/api';
......
......@@ -11,7 +11,7 @@ import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type
import PageContainer from '@/components/PageContainer';
import ChatHeader from './components/ChatHeader';
import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useTranslation } from 'next-i18next';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
......
......@@ -8,7 +8,7 @@ import PageContainer from '@/components/PageContainer';
import { getMyTokensApps } from '@/web/core/chat/api';
import ChatHistorySlider from './components/ChatHistorySlider';
import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useTranslation } from 'next-i18next';
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
......
......@@ -5,7 +5,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import dynamic from 'next/dynamic';
import PageContainer from '@/components/PageContainer';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useTranslation } from 'next-i18next';
import MetaDataCard from './components/MetaDataCard';
import NavBar from './components/NavBar';
......
......@@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import { Box, Flex, Button, InputGroup, InputLeftElement, Input } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import ParentPaths from '@/components/common/folder/Path';
import List from './component/List';
import { DatasetsContext } from './context';
......
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import React, { useEffect } from 'react';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { useRouter } from 'next/router';
......
......@@ -32,8 +32,10 @@ const WechatForm = ({ setPageType, loginSuccess }: Props) => {
useQuery(['getWXLoginResult', wechatInfo?.code], () => getWXLoginResult(wechatInfo?.code || ''), {
refetchInterval: 3 * 1000,
enabled: !!wechatInfo?.code,
onSuccess(data: ResLogin) {
loginSuccess(data);
onSuccess(data: ResLogin | undefined) {
if (data) {
loginSuccess(data);
}
}
});
......
......@@ -6,7 +6,7 @@ import { clearToken, setToken } from '@/web/support/user/auth';
import { postFastLogin } from '@/web/support/user/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
const FastLogin = ({
......
......@@ -18,7 +18,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import LoginForm from './components/LoginForm/LoginForm';
import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { clearToken, setToken } from '@/web/support/user/auth';
import Script from 'next/script';
import Loading from '@fastgpt/web/components/common/MyLoading';
......
......@@ -7,7 +7,7 @@ import { clearToken, setToken } from '@/web/support/user/auth';
import { oauthLogin } from '@/web/support/user/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import { OAuthEnum } from '@fastgpt/global/support/user/constant';
......
......@@ -4,7 +4,7 @@ import { ChevronRightIcon } from '@chakra-ui/icons';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { useTranslation } from 'next-i18next';
import { getDocPath } from '@/web/common/system/doc';
......
import React from 'react';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { Box, Flex } from '@chakra-ui/react';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getTeamPlanStatus } from '@/web/support/user/team/api';
......
import { serviceSideProps } from '@/web/common/utils/i18n';
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import { getPluginGroups, getSystemPlugTemplates } from '@/web/core/app/api/plugin';
import { Box, Flex, Grid, useDisclosure } from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
......
......@@ -5,7 +5,8 @@ import type { FastGPTConfigFileType } from '@fastgpt/global/common/system/types/
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { getFastGPTConfigFromDB } from '@fastgpt/service/common/system/config/controller';
import { PluginTemplateType } from '@fastgpt/global/core/plugin/type';
import { FastGPTProUrl, isProduction } from '@fastgpt/service/common/system/constants';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
import { isProduction } from '@fastgpt/global/common/system/constants';
import { initFastGPTConfig } from '@fastgpt/service/common/system/tools';
import json5 from 'json5';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
......
import { FastGPTProUrl, isProduction } from '@fastgpt/service/common/system/constants';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
import { isProduction } from '@fastgpt/global/common/system/constants';
import { cloneDeep } from 'lodash';
import { getCommunityCb, getCommunityPlugins } from '@fastgpt/plugins/register';
import { GET, POST } from '@fastgpt/service/common/api/plusRequest';
......
import { isProduction } from '@fastgpt/service/common/system/constants';
import { isProduction } from '@fastgpt/global/common/system/constants';
import { readdirSync, readFileSync } from 'fs';
import path from 'path';
......
......@@ -2,7 +2,7 @@ import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useCallback } from 'react';
import { hasHttps } from '@fastgpt/web/common/system/utils';
import { isProduction } from '@fastgpt/service/common/system/constants';
import { isProduction } from '@fastgpt/global/common/system/constants';
/**
* copy text data
......
......@@ -20,10 +20,3 @@ export const langMap = {
avatar: 'common/language/China'
}
};
export const serviceSideProps = (content: any, ns: I18nNsType = []) => {
const lang = content.req?.cookies?.NEXT_LOCALE || content.locale;
const extraLng = content.req?.cookies?.NEXT_LOCALE ? undefined : content.locales;
return serverSideTranslations(lang, ['common', ...ns], null, extraLng);
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment