mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-21 11:43:56 +00:00
v4.6.2 (#523)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Install dependencies only when needed
|
||||
FROM node:18.15-alpine AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm
|
||||
RUN apk add libc6-compat && npm install -g pnpm
|
||||
WORKDIR /app
|
||||
|
||||
ARG name
|
||||
|
@@ -99,7 +99,7 @@ Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One
|
||||
|
||||
## 接入 FastGPT
|
||||
|
||||
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 模型:
|
||||
修改 config.json 配置文件,在 ChatModels 中加入 chatglm2 模型:
|
||||
|
||||
```json
|
||||
"ChatModels": [
|
||||
@@ -107,10 +107,11 @@ Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One
|
||||
{
|
||||
"model": "chatglm2",
|
||||
"name": "chatglm2",
|
||||
"maxToken": 8000,
|
||||
"price": 0,
|
||||
"quoteMaxToken": 4000,
|
||||
"maxTemperature": 1.2,
|
||||
"maxContext": 4000,
|
||||
"maxResponse": 4000,
|
||||
"quoteMaxToken": 2000,
|
||||
"maxTemperature": 1,
|
||||
"vision": false,
|
||||
"defaultSystemChatPrompt": ""
|
||||
}
|
||||
]
|
||||
|
@@ -21,7 +21,9 @@ weight: 563
|
||||
curl --location --request POST 'https://fastgpt.run/api/support/wallet/bill/createTrainingBill' \
|
||||
--header 'Authorization: Bearer {{apikey}}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw ''
|
||||
--data-raw '{
|
||||
"name": "可选,自定义订单名称,例如:文档训练-fastgpt.docx"
|
||||
}'
|
||||
```
|
||||
|
||||
**响应结果**
|
||||
|
@@ -86,7 +86,7 @@ curl -O https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data
|
||||
|
||||
## 三、启动容器
|
||||
|
||||
修改`docker-compose.yml`中的`OPENAI_BASE_URL`和`CHAT_API_KEY`即可,对应为 API 的地址和 key。
|
||||
修改`docker-compose.yml`中的`OPENAI_BASE_URL`和`CHAT_API_KEY`即可,对应为 API 的地址(别忘记加/v1)和 key。
|
||||
|
||||
```bash
|
||||
# 在 docker-compose.yml 同级目录下执行
|
||||
|
@@ -2,16 +2,15 @@ export type FeConfigsType = {
|
||||
show_emptyChat?: boolean;
|
||||
show_register?: boolean;
|
||||
show_appStore?: boolean;
|
||||
show_contact?: boolean;
|
||||
show_git?: boolean;
|
||||
show_pay?: boolean;
|
||||
show_openai_account?: boolean;
|
||||
show_promotion?: boolean;
|
||||
hide_app_flow?: boolean;
|
||||
concatMd?: string;
|
||||
docUrl?: string;
|
||||
openAPIDocUrl?: string;
|
||||
systemTitle?: string;
|
||||
authorText?: string;
|
||||
googleClientVerKey?: string;
|
||||
isPlus?: boolean;
|
||||
oauth?: {
|
||||
|
8
packages/global/core/ai/model.d.ts
vendored
8
packages/global/core/ai/model.d.ts
vendored
@@ -26,6 +26,14 @@ export type VectorModelItemType = {
|
||||
maxToken: number;
|
||||
};
|
||||
|
||||
export type ReRankModelItemType = {
|
||||
model: string;
|
||||
name: string;
|
||||
price: number;
|
||||
requestUrl?: string;
|
||||
requestAuth?: string;
|
||||
};
|
||||
|
||||
export type AudioSpeechModelType = {
|
||||
model: string;
|
||||
name: string;
|
||||
|
@@ -4,7 +4,8 @@ import type {
|
||||
FunctionModelItemType,
|
||||
VectorModelItemType,
|
||||
AudioSpeechModelType,
|
||||
WhisperModelType
|
||||
WhisperModelType,
|
||||
ReRankModelItemType
|
||||
} from './model.d';
|
||||
|
||||
export const defaultChatModels: ChatModelItemType[] = [
|
||||
@@ -117,6 +118,8 @@ export const defaultVectorModels: VectorModelItemType[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const defaultReRankModels: ReRankModelItemType[] = [];
|
||||
|
||||
export const defaultAudioSpeechModels: AudioSpeechModelType[] = [
|
||||
{
|
||||
model: 'tts-1',
|
||||
|
6
packages/global/core/app/type.d.ts
vendored
6
packages/global/core/app/type.d.ts
vendored
@@ -4,6 +4,7 @@ import { PermissionTypeEnum } from '../../support/permission/constant';
|
||||
import type { AIChatModuleProps, DatasetModuleProps } from '../module/node/type.d';
|
||||
import { VariableInputEnum } from '../module/constants';
|
||||
import { SelectedDatasetType } from '../module/api';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constant';
|
||||
|
||||
export interface AppSchema {
|
||||
_id: string;
|
||||
@@ -18,6 +19,7 @@ export interface AppSchema {
|
||||
updateTime: number;
|
||||
modules: ModuleItemType[];
|
||||
permission: `${PermissionTypeEnum}`;
|
||||
inited?: boolean;
|
||||
}
|
||||
|
||||
export type AppListItemType = {
|
||||
@@ -62,7 +64,7 @@ export type AppSimpleEditFormType = {
|
||||
datasets: SelectedDatasetType;
|
||||
similarity: number;
|
||||
limit: number;
|
||||
rerank: boolean;
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
searchEmptyText: string;
|
||||
};
|
||||
userGuide: {
|
||||
@@ -106,7 +108,7 @@ export type AppSimpleEditConfigTemplateType = {
|
||||
datasets?: boolean;
|
||||
similarity?: boolean;
|
||||
limit?: boolean;
|
||||
rerank?: boolean;
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
searchEmptyText?: boolean;
|
||||
};
|
||||
userGuide?: {
|
||||
|
@@ -5,6 +5,7 @@ import type { FlowNodeInputItemType } from '../module/node/type.d';
|
||||
import { getGuideModule, splitGuideModule } from '../module/utils';
|
||||
import { defaultChatModels } from '../ai/model';
|
||||
import { ModuleItemType } from '../module/type.d';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constant';
|
||||
|
||||
export const getDefaultAppForm = (templateId = 'fastgpt-universal'): AppSimpleEditFormType => {
|
||||
const defaultChatModel = defaultChatModels[0];
|
||||
@@ -25,7 +26,7 @@ export const getDefaultAppForm = (templateId = 'fastgpt-universal'): AppSimpleEd
|
||||
similarity: 0.4,
|
||||
limit: 5,
|
||||
searchEmptyText: '',
|
||||
rerank: false
|
||||
searchMode: DatasetSearchModeEnum.embedding
|
||||
},
|
||||
userGuide: {
|
||||
welcomeText: '',
|
||||
@@ -91,10 +92,9 @@ export const appModules2Form = ({
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetLimit
|
||||
);
|
||||
defaultAppForm.dataset.rerank = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetStartReRank
|
||||
);
|
||||
defaultAppForm.dataset.searchMode =
|
||||
findInputValueByKey(module.inputs, ModuleInputKeyEnum.datasetSearchMode) ||
|
||||
DatasetSearchModeEnum.embedding;
|
||||
|
||||
// empty text
|
||||
const emptyOutputs =
|
||||
|
@@ -86,4 +86,31 @@ export const TrainingTypeMap = {
|
||||
// }
|
||||
};
|
||||
|
||||
export enum DatasetSearchModeEnum {
|
||||
embedding = 'embedding',
|
||||
embeddingReRank = 'embeddingReRank',
|
||||
embFullTextReRank = 'embFullTextReRank'
|
||||
}
|
||||
|
||||
export const DatasetSearchModeMap = {
|
||||
[DatasetSearchModeEnum.embedding]: {
|
||||
icon: 'core/dataset/modeEmbedding',
|
||||
title: 'core.dataset.search.mode.embedding',
|
||||
desc: 'core.dataset.search.mode.embedding desc',
|
||||
value: DatasetSearchModeEnum.embedding
|
||||
},
|
||||
[DatasetSearchModeEnum.embeddingReRank]: {
|
||||
icon: 'core/dataset/modeEmbeddingRerank',
|
||||
title: 'core.dataset.search.mode.embeddingReRank',
|
||||
desc: 'core.dataset.search.mode.embeddingReRank desc',
|
||||
value: DatasetSearchModeEnum.embeddingReRank
|
||||
},
|
||||
[DatasetSearchModeEnum.embFullTextReRank]: {
|
||||
icon: 'core/dataset/modeEmbFTRerank',
|
||||
title: 'core.dataset.search.mode.embFullTextReRank',
|
||||
desc: 'core.dataset.search.mode.embFullTextReRank desc',
|
||||
value: DatasetSearchModeEnum.embFullTextReRank
|
||||
}
|
||||
};
|
||||
|
||||
export const FolderAvatarSrc = '/imgs/files/folder.svg';
|
||||
|
@@ -61,7 +61,8 @@ export enum ModuleInputKeyEnum {
|
||||
datasetSelectList = 'datasets',
|
||||
datasetSimilarity = 'similarity',
|
||||
datasetLimit = 'limit',
|
||||
datasetStartReRank = 'rerank',
|
||||
datasetSearchMode = 'searchMode',
|
||||
datasetParamsModal = 'datasetParamsModal',
|
||||
|
||||
// context extract
|
||||
contextExtractInput = 'content',
|
||||
@@ -98,5 +99,6 @@ export enum ModuleOutputKeyEnum {
|
||||
|
||||
export enum VariableInputEnum {
|
||||
input = 'input',
|
||||
textarea = 'textarea',
|
||||
select = 'select'
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@ export enum FlowNodeInputTypeEnum {
|
||||
selectChatModel = 'selectChatModel',
|
||||
// dataset special input
|
||||
selectDataset = 'selectDataset',
|
||||
selectDatasetParamsModal = 'selectDatasetParamsModal',
|
||||
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
|
@@ -17,8 +17,11 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
flowType: FlowNodeTypeEnum.classifyQuestion,
|
||||
avatar: '/imgs/module/cq.png',
|
||||
name: '问题分类',
|
||||
intro:
|
||||
'根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于 laf 通用问题\n类型3: 关于 laf 代码问题\n类型4: 其他问题',
|
||||
intro: `根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:
|
||||
类型1: 打招呼
|
||||
类型2: 关于商品“使用”问题
|
||||
类型3: 关于商品“购买”问题
|
||||
类型4: 其他问题`,
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
} from '../../constants';
|
||||
import { Input_Template_TFSwitch, Input_Template_UserChatInput } from '../input';
|
||||
import { Output_Template_Finish } from '../output';
|
||||
import { DatasetSearchModeEnum } from '../../../dataset/constant';
|
||||
|
||||
export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.datasetSearchNode,
|
||||
@@ -36,7 +37,7 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetSimilarity,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '最低相关性',
|
||||
value: 0.4,
|
||||
valueType: ModuleDataTypeEnum.number,
|
||||
@@ -52,7 +53,7 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetLimit,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '单次搜索上限',
|
||||
description: '最多取 n 条记录作为本次问题引用',
|
||||
value: 5,
|
||||
@@ -68,13 +69,20 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetStartReRank,
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '结果重排',
|
||||
description: '将召回的结果进行进一步重排,可增加召回率',
|
||||
plusField: true,
|
||||
value: false,
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
key: ModuleInputKeyEnum.datasetSearchMode,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: 'core.dataset.search.Mode',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: DatasetSearchModeEnum.embedding
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetParamsModal,
|
||||
type: FlowNodeInputTypeEnum.selectDatasetParamsModal,
|
||||
label: '',
|
||||
connected: false,
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
|
@@ -18,10 +18,12 @@ export const HistoryModule: FlowModuleTemplateType = {
|
||||
key: ModuleInputKeyEnum.historyMaxAmount,
|
||||
type: FlowNodeInputTypeEnum.numberInput,
|
||||
label: '最长记录数',
|
||||
description:
|
||||
'该记录数不代表模型可接收这么多的历史记录,具体可接收多少历史记录,取决于模型的能力,通常建议不要超过20条。',
|
||||
value: 6,
|
||||
valueType: ModuleDataTypeEnum.number,
|
||||
min: 0,
|
||||
max: 50,
|
||||
max: 100,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
|
@@ -45,7 +45,7 @@ const DatasetDataSchema = new Schema({
|
||||
},
|
||||
fullTextToken: {
|
||||
type: String,
|
||||
required: true
|
||||
default: ''
|
||||
},
|
||||
indexes: {
|
||||
type: [
|
||||
|
@@ -105,14 +105,13 @@ export async function parseHeaderCert({
|
||||
};
|
||||
}
|
||||
// root user
|
||||
async function parseRootKey(rootKey?: string, userId = '') {
|
||||
async function parseRootKey(rootKey?: string) {
|
||||
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
const { cookie, token, apikey, rootkey, userid, authorization } = (req.headers ||
|
||||
const { cookie, token, apikey, rootkey, authorization } = (req.headers ||
|
||||
{}) as ReqHeaderAuthType;
|
||||
|
||||
const { uid, teamId, tmbId, appId, openApiKey, authType } = await (async () => {
|
||||
@@ -129,9 +128,10 @@ export async function parseHeaderCert({
|
||||
};
|
||||
}
|
||||
if (authRoot && rootkey) {
|
||||
await parseRootKey(rootkey);
|
||||
// root user
|
||||
return {
|
||||
uid: await parseRootKey(rootkey, userid),
|
||||
uid: '',
|
||||
teamId: '',
|
||||
tmbId: '',
|
||||
appId: '',
|
||||
|
@@ -113,6 +113,7 @@
|
||||
"maxToken": 3000
|
||||
}
|
||||
],
|
||||
"ReRankModels": [],
|
||||
"AudioSpeechModels": [
|
||||
{
|
||||
"model": "tts-1",
|
||||
|
@@ -170,6 +170,7 @@
|
||||
"Rename Success": "Rename Success",
|
||||
"Request Error": "Request Error",
|
||||
"Require Input": "Required",
|
||||
"Save": "Save",
|
||||
"Save Failed": "Save Failed",
|
||||
"Save Success": "Save Success",
|
||||
"Search": "Search",
|
||||
@@ -216,11 +217,15 @@
|
||||
"Prompt": "Prompt"
|
||||
},
|
||||
"app": {
|
||||
"App params config": "App Config",
|
||||
"Next Step Guide": "Next step guide",
|
||||
"Question Guide Tip": "At the end of the conversation, three leading questions will be asked.",
|
||||
"Save and preview": "Save",
|
||||
"Select TTS": "Select TTS",
|
||||
"Simple Config Tip": "Only basic functions are included. For complex agent functions, use advanced orchestration.",
|
||||
"TTS": "Audio Speech",
|
||||
"TTS Tip": "After this function is enabled, the voice playback function can be used after each conversation. Use of this feature may incur additional charges.",
|
||||
"Welcome Text": "Welcome Text",
|
||||
"create app": "Create App",
|
||||
"setting": "App Setting",
|
||||
"simple": {
|
||||
@@ -270,6 +275,22 @@
|
||||
"Ideal chunk length": "Ideal chunk length",
|
||||
"Ideal chunk length Tips": "Segment by end symbol. We recommend that your document should be properly punctuated to ensure that each complete sentence length does not exceed this value \n Chinese document recommended 400~1000\n English document recommended 600~1200"
|
||||
},
|
||||
"search": {
|
||||
"Empty result response": "Empty Response",
|
||||
"Empty result response Tips": "If you fill in the content, if no suitable content is found, you will directly reply to the content.",
|
||||
"Min Similarity": "Min Similarity",
|
||||
"Min Similarity Tips": "The similarity of different index models is different, please use the search test to select the appropriate value",
|
||||
"Params Setting": "Params Setting",
|
||||
"Top K": "Top K",
|
||||
"mode": {
|
||||
"embFullTextReRank": "Hybrid search ",
|
||||
"embFullTextReRank desc": "Reordering with a mixture of vector search and full-text search results by Rerank usually works best",
|
||||
"embedding": "Vector search",
|
||||
"embedding desc": "Direct vector topk correlation query ",
|
||||
"embeddingReRank": "Enhanced semantic retrieval ",
|
||||
"embeddingReRank desc": "Sort using Rerank after overperforming vector topk queries "
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"Test": "Start",
|
||||
"Test Result": "Results",
|
||||
@@ -327,18 +348,20 @@
|
||||
},
|
||||
"variable": {
|
||||
"add option": "Add Option",
|
||||
"input type": "Text",
|
||||
"key": "Key",
|
||||
"key is required": "variable key is required",
|
||||
"select type": "Select",
|
||||
"text max length": "Max Length",
|
||||
"text type": "Text",
|
||||
"textarea type": "Textarea",
|
||||
"variable key is required": "",
|
||||
"variable name": "Name",
|
||||
"variable name is required": "variable name is required",
|
||||
"variable option is required": "Variable option is required",
|
||||
"variable option is value is required": "Variable option is value is required",
|
||||
"variable options": "Options"
|
||||
}
|
||||
},
|
||||
"variable add option": "Add Option"
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
@@ -412,9 +435,6 @@
|
||||
"deleteDatasetTips": "Are you sure to delete the knowledge base? Data cannot be recovered after deletion, please confirm!",
|
||||
"deleteFolderTips": "Are you sure to delete this folder and all the knowledge bases it contains? Data cannot be recovered after deletion, please confirm!",
|
||||
"import csv tip": "Ensure that the CSV is in UTF-8 format; otherwise, garbled characters will be displayed",
|
||||
"recall": {
|
||||
"rerank": "Rerank"
|
||||
},
|
||||
"test": {
|
||||
"noResult": "Search results are empty"
|
||||
}
|
||||
@@ -674,7 +694,8 @@
|
||||
"bill": {
|
||||
"Audio Speech": "Audio Speech",
|
||||
"Whisper": "Whisper",
|
||||
"bill username": "User"
|
||||
"bill username": "User",
|
||||
"ReRank": "ReRank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -170,6 +170,7 @@
|
||||
"Rename Success": "重命名成功",
|
||||
"Request Error": "请求异常",
|
||||
"Require Input": "必填",
|
||||
"Save": "保存",
|
||||
"Save Failed": "保存失败",
|
||||
"Save Success": "保存成功",
|
||||
"Search": "搜索",
|
||||
@@ -216,11 +217,15 @@
|
||||
"Prompt": "提示词"
|
||||
},
|
||||
"app": {
|
||||
"App params config": "应用配置",
|
||||
"Next Step Guide": "下一步指引",
|
||||
"Question Guide Tip": "对话结束后,会为生成 3 个引导性问题。",
|
||||
"Save and preview": "保存并预览",
|
||||
"Select TTS": "选择语音播放模式",
|
||||
"Simple Config Tip": "仅包含基础功能,复杂 agent 功能请使用高级编排。",
|
||||
"TTS": "语音播报",
|
||||
"TTS Tip": "开启后,每次对话后可使用语音播放功能。使用该功能可能产生额外费用。",
|
||||
"Welcome Text": "对话开场白",
|
||||
"create app": "创建属于你的 AI 应用",
|
||||
"setting": "应用信息设置",
|
||||
"simple": {
|
||||
@@ -270,6 +275,22 @@
|
||||
"Ideal chunk length": "理想分块长度",
|
||||
"Ideal chunk length Tips": "按结束符号进行分段。我们建议您的文档应合理的使用标点符号,以确保每个完整的句子长度不要超过该值\n中文文档建议400~1000\n英文文档建议600~1200"
|
||||
},
|
||||
"search": {
|
||||
"Empty result response": "空搜索回复",
|
||||
"Empty result response Tips": "若填写该内容,没有搜索到合适内容时,将直接回复填写的内容。",
|
||||
"Min Similarity": "最低相似度",
|
||||
"Min Similarity Tips": "不同索引模型的相似度有区别,请通过搜索测试来选择合适的数值",
|
||||
"Params Setting": "搜索参数设置",
|
||||
"Top K": "单次搜索上限",
|
||||
"mode": {
|
||||
"embFullTextReRank": "混合检索",
|
||||
"embFullTextReRank desc": "使用向量检索与全文检索混合结果进行 Rerank 进行重排,通常效果最佳",
|
||||
"embedding": "语义检索",
|
||||
"embedding desc": "直接进行向量 topk 相关性查询",
|
||||
"embeddingReRank": "增强语义检索",
|
||||
"embeddingReRank desc": "超额进行向量 topk 查询后再使用 Rerank 进行排序"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"Test": "测试",
|
||||
"Test Result": "测试结果",
|
||||
@@ -327,18 +348,20 @@
|
||||
},
|
||||
"variable": {
|
||||
"add option": "添加选项",
|
||||
"input type": "文本",
|
||||
"key": "变量 key",
|
||||
"key is required": "",
|
||||
"key is required": "变量key是必须的",
|
||||
"select type": "下拉单选",
|
||||
"text max length": "最大长度",
|
||||
"text type": "文本",
|
||||
"textarea type": "段落",
|
||||
"variable key is required": "变量 key 不能为空",
|
||||
"variable name": "变量名",
|
||||
"variable name is required": "变量名不能为空",
|
||||
"variable option is required": "选项不能全空",
|
||||
"variable option is value is required": "选项内容不能为空",
|
||||
"variable options": "选项"
|
||||
}
|
||||
},
|
||||
"variable add option": "添加选项"
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
@@ -412,9 +435,6 @@
|
||||
"deleteDatasetTips": "确认删除该知识库?删除后数据无法恢复,请确认!",
|
||||
"deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!",
|
||||
"import csv tip": "请确保CSV为UTF-8格式,否则会乱码",
|
||||
"recall": {
|
||||
"rerank": "结果重排"
|
||||
},
|
||||
"test": {
|
||||
"noResult": "搜索结果为空"
|
||||
}
|
||||
@@ -674,7 +694,8 @@
|
||||
"bill": {
|
||||
"Audio Speech": "语音播报",
|
||||
"Whisper": "语音输入",
|
||||
"bill username": "用户"
|
||||
"bill username": "用户",
|
||||
"ReRank": "结果重排"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
"datasets": true,
|
||||
"similarity": false,
|
||||
"limit": false,
|
||||
"rerank": false,
|
||||
"searchMode": "embedding",
|
||||
"searchEmptyText": false
|
||||
},
|
||||
"userGuide": {
|
||||
|
@@ -122,7 +122,7 @@ const QuoteModal = ({
|
||||
<Flex alignItems={'center'} fontSize={'sm'} mt={3} gap={4} color={'myGray.500'}>
|
||||
{isPc && (
|
||||
<MyTooltip label={t('core.dataset.data.id')}>
|
||||
<Flex border={theme.borders.base} px={3} borderRadius={'md'}>
|
||||
<Flex border={theme.borders.base} py={'1px'} px={3} borderRadius={'3px'}>
|
||||
# {item.id}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
|
@@ -116,7 +116,7 @@ const WholeResponseModal = ({
|
||||
if (!activeModule?.historyPreview) return '';
|
||||
return activeModule.historyPreview
|
||||
.map((item, i) => `**${item.obj}**\n${item.value}`)
|
||||
.join('\n---\n');
|
||||
.join('\n\n---\n\n');
|
||||
})()}
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
|
@@ -26,7 +26,8 @@ import {
|
||||
useTheme,
|
||||
BoxProps,
|
||||
FlexProps,
|
||||
Image
|
||||
Image,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { eventBus } from '@/web/common/utils/eventbus';
|
||||
@@ -558,11 +559,23 @@ const ChatBox = (
|
||||
{item.type === VariableInputEnum.input && (
|
||||
<Input
|
||||
isDisabled={variableIsFinish}
|
||||
bg={'myWhite.400'}
|
||||
{...register(item.key, {
|
||||
required: item.required
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{item.type === VariableInputEnum.textarea && (
|
||||
<Textarea
|
||||
isDisabled={variableIsFinish}
|
||||
bg={'myWhite.400'}
|
||||
{...register(item.key, {
|
||||
required: item.required
|
||||
})}
|
||||
rows={5}
|
||||
maxLength={4000}
|
||||
/>
|
||||
)}
|
||||
{item.type === VariableInputEnum.select && (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
|
@@ -3,12 +3,7 @@ import { Button, ModalFooter, ModalBody } from '@chakra-ui/react';
|
||||
import MyModal from '../MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Markdown from '../Markdown';
|
||||
|
||||
const md = `
|
||||
| 交流群 | 小助手 |
|
||||
| ----------------------- | -------------------- |
|
||||
|  |  |
|
||||
`;
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
|
||||
const CommunityModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -20,7 +15,7 @@ const CommunityModal = ({ onClose }: { onClose: () => void }) => {
|
||||
title={t('home.Community')}
|
||||
>
|
||||
<ModalBody textAlign={'center'}>
|
||||
<Markdown source={md} />
|
||||
<Markdown source={feConfigs?.concatMd || ''} />
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
@@ -0,0 +1,17 @@
|
||||
<?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="1701144443685"
|
||||
class="icon" viewBox="0 0 1170 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15096"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="146.25" height="128">
|
||||
<path
|
||||
d="M1024 128a39.862857 39.862857 0 0 1 36.571429 42.788571v682.422858a39.862857 39.862857 0 0 1-36.571429 42.788571H146.285714a39.862857 39.862857 0 0 1-36.571428-42.788571V170.788571A39.862857 39.862857 0 0 1 146.285714 128h877.714286M1024 0H146.285714C65.462857 0 0 76.434286 0 170.788571v682.422858C0 947.565714 65.462857 1024 146.285714 1024h877.714286c80.822857 0 146.285714-76.434286 146.285714-170.788571V170.788571C1170.285714 76.434286 1104.822857 0 1024 0z"
|
||||
p-id="15097"></path>
|
||||
<path
|
||||
d="M310.857143 292.571429A54.857143 54.857143 0 0 0 256 347.428571v329.142858a54.857143 54.857143 0 0 0 109.714286 0v-329.142858A54.857143 54.857143 0 0 0 310.857143 292.571429z"
|
||||
p-id="15098"></path>
|
||||
<path
|
||||
d="M365.714286 292.571429H256a54.857143 54.857143 0 0 0 0 109.714285h109.714286a54.857143 54.857143 0 0 0 0-109.714285zM365.714286 621.714286H256a54.857143 54.857143 0 0 0 0 109.714285h109.714286a54.857143 54.857143 0 0 0 0-109.714285z"
|
||||
p-id="15099"></path>
|
||||
<path
|
||||
d="M556.251429 621.714286a54.857143 54.857143 0 1 0 54.857142 54.857143 54.857143 54.857143 0 0 0-54.857142-54.857143zM746.788571 621.714286a54.857143 54.857143 0 1 0 54.857143 54.857143 54.857143 54.857143 0 0 0-54.857143-54.857143z"
|
||||
p-id="15100"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 17 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M8.89233 4.66667H3.77428C3.42285 4.66666 3.11966 4.66665 2.86995 4.68705C2.60639 4.70859 2.34424 4.75613 2.09199 4.88466C1.71567 5.07641 1.4097 5.38237 1.21796 5.75869C1.08943 6.01095 1.04188 6.27309 1.02035 6.53665C0.999949 6.78636 0.999959 7.08953 0.99997 7.44095V12.559C0.999959 12.9105 0.999949 13.2137 1.02035 13.4634C1.04188 13.7269 1.08943 13.9891 1.21796 14.2413C1.4097 14.6176 1.71567 14.9236 2.09199 15.1154C2.34424 15.2439 2.60639 15.2914 2.86995 15.313C3.11965 15.3334 3.4228 15.3334 3.77421 15.3333H8.89232C9.24372 15.3334 9.54696 15.3334 9.79666 15.313C10.0602 15.2914 10.3224 15.2439 10.5746 15.1154C10.9509 14.9236 11.2569 14.6176 11.4487 14.2413C11.5772 13.9891 11.6247 13.7269 11.6463 13.4634C11.6667 13.2136 11.6667 12.9105 11.6666 12.559V7.44099C11.6667 7.08955 11.6667 6.78637 11.6463 6.53665C11.6247 6.27309 11.5772 6.01095 11.4487 5.75869C11.2569 5.38237 10.9509 5.07641 10.5746 4.88466C10.3224 4.75613 10.0602 4.70859 9.79666 4.68705C9.54695 4.66665 9.24376 4.66666 8.89233 4.66667ZM9.13804 8.80474C9.39839 8.54439 9.39839 8.12228 9.13804 7.86193C8.87769 7.60159 8.45558 7.60159 8.19523 7.86193L5.66664 10.3905L4.80471 9.5286C4.54436 9.26825 4.12225 9.26825 3.8619 9.5286C3.60155 9.78895 3.60155 10.2111 3.8619 10.4714L5.19523 11.8047C5.45558 12.0651 5.87769 12.0651 6.13804 11.8047L9.13804 8.80474Z">
|
||||
</path>
|
||||
<path
|
||||
d="M12.8923 0.666672H7.77427C7.42285 0.666661 7.11966 0.666651 6.86995 0.687053C6.60639 0.708587 6.34424 0.756131 6.09199 0.884661C5.71567 1.07641 5.40971 1.38237 5.21796 1.75869C5.08943 2.01095 5.04188 2.27309 5.02035 2.53665C5.00206 2.76051 5.00018 3.02733 4.99999 3.33336L8.92055 3.33335C9.2463 3.33327 9.59951 3.33319 9.90523 3.35816C10.2512 3.38644 10.7084 3.4564 11.1799 3.69667C11.8071 4.01625 12.3171 4.52618 12.6367 5.15339C12.8769 5.62493 12.9469 6.08208 12.9752 6.42809C13.0001 6.73382 13.0001 7.08702 13 7.41279L13 11.3333C13.306 11.3331 13.5728 11.3313 13.7967 11.313C14.0602 11.2914 14.3224 11.2439 14.5746 11.1154C14.9509 10.9236 15.2569 10.6176 15.4487 10.2413C15.5772 9.98907 15.6247 9.72692 15.6463 9.46336C15.6667 9.21366 15.6666 8.91051 15.6666 8.5591V3.44099C15.6666 3.08959 15.6667 2.78635 15.6463 2.53665C15.6247 2.27309 15.5772 2.01095 15.4487 1.75869C15.2569 1.38237 14.9509 1.07641 14.5746 0.884661C14.3224 0.756131 14.0602 0.708587 13.7967 0.687053C13.5469 0.666651 13.2438 0.666661 12.8923 0.666672Z">
|
||||
</path>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,8 @@
|
||||
<?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="1701144482765"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16956"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
|
||||
<path
|
||||
d="M870.4 709.7856c28.2624 0 51.2 24.4736 51.2 54.7328 0 28.0064-19.7632 51.2-45.2096 54.272L870.4 819.2H153.6c-28.2624 0-51.2-24.4736-51.2-54.6816 0-28.0576 19.7632-51.2 45.2096-54.3744L153.6 709.7856h716.8z m-159.2832-252.4672c28.2624 0 51.2 24.4736 51.2 54.6816 0 28.0576-19.7632 51.2-45.2096 54.3232l-5.9904 0.3584H153.6c-28.2624 0-51.2-24.4736-51.2-54.6816 0-28.0576 19.7632-51.2 45.2096-54.3232L153.6 457.3184h557.5168zM870.4 204.8c28.2624 0 51.2 24.4736 51.2 54.6816 0 28.0576-19.7632 51.2-45.2096 54.3744L870.4 314.2144H153.6c-28.2624 0-51.2-24.4736-51.2-54.7328 0-28.0064 19.7632-51.2 45.2096-54.272L153.6 204.8h716.8z"
|
||||
p-id="16957"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1017 B |
@@ -0,0 +1,12 @@
|
||||
<svg t="1701068093720" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="14465" width="128" height="128">
|
||||
<path
|
||||
d="M413.696 719.36l-98.304 56.832-215.04-124.416v205.312l215.04 124.416L496.64 876.544l-82.944-47.616v-109.568z"
|
||||
fill="#2B85FB" p-id="14466"></path>
|
||||
<path
|
||||
d="M751.616 435.2l-43.008-25.088-179.2 103.424-80.384-45.056 81.92-47.104V311.808l-215.04-124.416-215.04 124.416v248.32l41.984 24.064 173.568 100.352L414.72 627.712v-87.552l79.36 44.032V783.36l215.04 124.416 215.04-124.416v-248.32L751.616 435.2z"
|
||||
fill="#2B85FB" p-id="14467"></path>
|
||||
<path
|
||||
d="M610.304 375.808l98.304-56.832 77.312 44.544V166.912l-215.04-124.416-174.08 100.352 212.992 122.88 0.512 110.08z"
|
||||
fill="#2B85FB" p-id="14468"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 821 B |
@@ -0,0 +1,6 @@
|
||||
<svg t="1701067963240" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6042"
|
||||
width="128" height="128">
|
||||
<path
|
||||
d="M479.971202 0a95.99424 95.99424 0 0 1 92.538447 121.592704l304.365738 213.107214a95.99424 95.99424 0 1 1 35.83785 176.053437l-231.026138 355.690658a95.99424 95.99424 0 1 1-169.269844 70.715757L511.969282 927.944323c0-1.535908 0-3.071816 0.127992-4.543727l-276.143431-128.760274a95.99424 95.99424 0 1 1-129.528229-138.359699l-28.606283-274.031558A95.99424 95.99424 0 1 1 159.862408 216.307022l224.17855-119.224847L383.976961 95.99424A95.99424 95.99424 0 0 1 470.755755 0.447973L479.971202 0zM375.017499 600.731956l-126.264424 98.682079c3.519789 8.447493 5.759654 17.470952 6.783593 26.942384l0.319981 6.975581 280.431174 130.744155a96.634202 96.634202 0 0 1 11.32732-10.815351L469.987801 639.641622a159.15845 159.15845 0 0 1-84.986901-30.910146l-9.983401-7.99952z m467.427954-141.047537l-202.867828 32.254065a160.118393 160.118393 0 0 1-104.953702 138.423694L608.091515 831.950083c6.33562 0 12.543247 0.639962 18.558886 1.791892l229.746215-353.834769a96.378217 96.378217 0 0 1-9.791412-13.055217l-4.095755-7.16757zM163.190209 356.650601c-6.527608 6.33562-13.951163 11.775293-22.01468 16.063036l28.158311 267.695938c13.119213 1.279923 25.470472 5.119693 36.541807 11.199328l130.232187-101.753894a159.350439 159.350439 0 0 1-8.063517-119.864808L163.126212 356.650601zM535.903846 174.069556c-8.127512 5.759654-17.086975 10.303382-26.750395 13.439193l4.735716 136.055837a160.246385 160.246385 0 0 1 117.560946 104.889707l202.547847-32.126073c0.767954-3.83977 1.791892-7.679539 3.071816-11.32732L535.903846 174.069556zM406.375617 157.750535l-215.66706 114.681119a96.634202 96.634202 0 0 1 0.575966 26.750395l168.565886 75.067496c23.038618-26.110433 54.396736-44.66932 89.914605-51.452913l-4.799712-137.399756a96.058237 96.058237 0 0 1-32.062076-20.67076l-6.527609-7.039577z"
|
||||
fill="#0A70B0" p-id="6043"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg t="1701068031255" class="icon" viewBox="0 0 1407 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8879"
|
||||
width="128" height="128">
|
||||
<path
|
||||
d="M1202.775043 15.825987a48.650258 48.650258 0 0 0-68.579279 0 48.06411 48.06411 0 0 0 0 67.993131 607.835146 607.835146 0 0 1 0 855.189468 48.650258 48.650258 0 0 0 0 68.579279 53.339439 53.339439 0 0 0 34.582713 14.067544 48.06411 48.06411 0 0 0 33.996566-14.067544 703.377218 703.377218 0 0 0 0-991.761878zM96.714367 511.706926a603.145965 603.145965 0 0 1 175.844305-428.473955A48.298569 48.298569 0 0 0 205.151689 14.067544 703.377218 703.377218 0 0 0 58.614768 785.437894a689.895821 689.895821 0 0 0 152.398397 224.494562 47.477962 47.477962 0 0 0 33.996566 13.481396 48.650258 48.650258 0 0 0 33.996566-82.646823 599.042931 599.042931 0 0 1-182.29193-429.060103z"
|
||||
fill="#5D78FD" p-id="8880"></path>
|
||||
<path
|
||||
d="M999.967945 218.633085a48.650258 48.650258 0 0 0-69.165426 67.993131 318.864339 318.864339 0 0 1 0 448.989125 48.650258 48.650258 0 0 0 0 68.579278 48.06411 48.06411 0 0 0 33.996565 14.067545 46.305667 46.305667 0 0 0 31.651975-13.481397 410.303377 410.303377 0 0 0 120.746422-293.073841 416.164854 416.164854 0 0 0-117.229536-293.073841z m-616.041213 293.073841a319.450487 319.450487 0 0 1 93.197481-226.839153A48.06411 48.06411 0 1 0 410.303377 218.633085a416.164854 416.164854 0 0 0 0 586.147682 47.477962 47.477962 0 0 0 33.410418 14.067544 48.650258 48.650258 0 0 0 35.168861-14.067544 48.06411 48.06411 0 0 0 0-68.579279 315.347453 315.347453 0 0 1-94.955924-224.494562z"
|
||||
fill="#5D78FD" p-id="8881"></path>
|
||||
<path d="M599.629078 511.706926a103.74814 103.74814 0 1 0 206.910132 0 103.74814 103.74814 0 0 0-206.910132 0z"
|
||||
fill="#FEC743" p-id="8882"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@@ -108,7 +108,13 @@ const iconPaths = {
|
||||
'core/chat/stopSpeechFill': () => import('./icons/core/chat/stopSpeechFill.svg'),
|
||||
'core/chat/stopSpeech': () => import('./icons/core/chat/stopSpeech.svg'),
|
||||
'core/chat/speaking': () => import('./icons/core/chat/speaking.svg'),
|
||||
'core/chat/fileSelect': () => import('./icons/core/chat/fileSelect.svg')
|
||||
'core/chat/fileSelect': () => import('./icons/core/chat/fileSelect.svg'),
|
||||
'core/dataset/modeEmbedding': () => import('./icons/core/dataset/modeEmbedding.svg'),
|
||||
'core/dataset/modeEmbeddingRerank': () => import('./icons/core/dataset/modeEmbeddingRerank.svg'),
|
||||
'core/dataset/modeEmbFTRerank': () => import('./icons/core/dataset/modeEmbFTRerank.svg'),
|
||||
'core/app/variable/input': () => import('./icons/core/app/variable/input.svg'),
|
||||
'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'),
|
||||
'core/app/variable/select': () => import('./icons/core/app/variable/select.svg')
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof iconPaths;
|
||||
|
@@ -47,19 +47,17 @@ const MdImage = ({ src }: { src?: string }) => {
|
||||
/>
|
||||
<Modal isOpen={isOpen} onClose={onClose} isCentered>
|
||||
<ModalOverlay />
|
||||
<ModalContent maxW={'80vw'} maxH={'auto'}>
|
||||
<Box>
|
||||
<Image
|
||||
borderRadius={'md'}
|
||||
src={src}
|
||||
alt={''}
|
||||
w={'auto'}
|
||||
h={'auto'}
|
||||
fallbackSrc={'/imgs/errImg.png'}
|
||||
fallbackStrategy={'onError'}
|
||||
objectFit={'contain'}
|
||||
/>
|
||||
</Box>
|
||||
<ModalContent maxW={'auto'} w="auto" bg={'transparent'}>
|
||||
<Image
|
||||
borderRadius={'md'}
|
||||
src={src}
|
||||
alt={''}
|
||||
w={'100%'}
|
||||
maxH={'80vh'}
|
||||
fallbackSrc={'/imgs/errImg.png'}
|
||||
fallbackStrategy={'onError'}
|
||||
objectFit={'contain'}
|
||||
/>
|
||||
</ModalContent>
|
||||
<ModalCloseButton bg={'myWhite.500'} zIndex={999999} />
|
||||
</Modal>
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
interface Props extends ModalContentProps {
|
||||
export interface MyModalProps extends ModalContentProps {
|
||||
iconSrc?: string;
|
||||
title?: any;
|
||||
isCentered?: boolean;
|
||||
@@ -28,7 +28,7 @@ const MyModal = ({
|
||||
w = 'auto',
|
||||
maxW = ['90vw', '600px'],
|
||||
...props
|
||||
}: Props) => {
|
||||
}: MyModalProps) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
@@ -42,7 +42,7 @@ const MyModal = ({
|
||||
minW={['90vw', '400px']}
|
||||
maxW={maxW}
|
||||
position={'relative'}
|
||||
maxH={['80vh', '85vh']}
|
||||
maxH={'85vh'}
|
||||
{...props}
|
||||
>
|
||||
{!title && onClose && <ModalCloseButton zIndex={1} />}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, useTheme, Grid, type GridProps, theme } from '@chakra-ui/react';
|
||||
import type { StackProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
// @ts-ignore
|
||||
interface Props extends GridProps {
|
||||
@@ -20,6 +20,7 @@ const MyRadio = ({
|
||||
onChange,
|
||||
...props
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Grid gridGap={[3, 5]} fontSize={['sm', 'md']} {...props}>
|
||||
@@ -70,10 +71,10 @@ const MyRadio = ({
|
||||
>
|
||||
{!!item.icon && <MyIcon mr={'14px'} name={item.icon as any} w={iconSize} />}
|
||||
<Box pr={2}>
|
||||
<Box>{item.title}</Box>
|
||||
<Box>{t(item.title)}</Box>
|
||||
{!!item.desc && (
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{item.desc}
|
||||
{t(item.desc)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
@@ -77,7 +77,7 @@ const MySlider = ({
|
||||
h={'18px'}
|
||||
borderRadius={'18px'}
|
||||
fontSize={'xs'}
|
||||
transform={'translate(-50%, -170%)'}
|
||||
transform={'translate(-50%, -155%)'}
|
||||
boxSizing={'border-box'}
|
||||
>
|
||||
{value}
|
||||
|
152
projects/app/src/components/core/module/DatasetParamsModal.tsx
Normal file
152
projects/app/src/components/core/module/DatasetParamsModal.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Box, Button, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MySlider from '@/components/Slider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { reRankModelList } from '@/web/common/system/staticData';
|
||||
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
|
||||
import MyRadio from '@/components/Radio';
|
||||
|
||||
type DatasetParamsProps = {
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
searchEmptyText?: string;
|
||||
};
|
||||
|
||||
const DatasetParamsModal = ({
|
||||
searchEmptyText,
|
||||
limit,
|
||||
similarity,
|
||||
searchMode = DatasetSearchModeEnum.embedding,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
|
||||
defaultValues: {
|
||||
searchEmptyText,
|
||||
limit,
|
||||
similarity,
|
||||
searchMode
|
||||
}
|
||||
});
|
||||
|
||||
const searchModeList = useMemo(() => {
|
||||
const list = Object.values(DatasetSearchModeMap);
|
||||
if (reRankModelList.length > 0) {
|
||||
return list;
|
||||
}
|
||||
return list.slice(0, 1);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/params.svg"
|
||||
title={'搜索参数调整'}
|
||||
minW={['90vw', '500px']}
|
||||
h={['90vh', 'auto']}
|
||||
overflow={'unset'}
|
||||
isCentered={searchEmptyText !== undefined}
|
||||
>
|
||||
<ModalBody flex={['1 0 0', 'auto']} overflow={'auto'}>
|
||||
<MyRadio
|
||||
gridGap={2}
|
||||
gridTemplateColumns={'repeat(1,1fr)'}
|
||||
list={searchModeList}
|
||||
value={getValues('searchMode')}
|
||||
onChange={(e) => {
|
||||
setValue('searchMode', e as `${DatasetSearchModeEnum}`);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
|
||||
{similarity !== undefined && (
|
||||
<Box display={['block', 'flex']} py={8} mt={3}>
|
||||
<Box flex={'0 0 100px'} mb={[8, 0]}>
|
||||
{t('core.dataset.search.Min Similarity')}
|
||||
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '1', value: 1 }
|
||||
]}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={getValues(ModuleInputKeyEnum.datasetSimilarity) || 0.5}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{limit !== undefined && (
|
||||
<Box display={['block', 'flex']} py={8}>
|
||||
<Box flex={'0 0 100px'} mb={[8, 0]}>
|
||||
{t('core.dataset.search.Top K')}
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '1', value: 1 },
|
||||
{ label: '30', value: 30 }
|
||||
]}
|
||||
min={1}
|
||||
max={30}
|
||||
value={getValues(ModuleInputKeyEnum.datasetLimit) || 5}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.datasetLimit, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{searchEmptyText !== undefined && (
|
||||
<Box display={['block', 'flex']} pt={3}>
|
||||
<Box flex={'0 0 100px'} mb={[2, 0]}>
|
||||
{t('core.dataset.search.Empty result response')}
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Textarea
|
||||
rows={5}
|
||||
maxLength={500}
|
||||
placeholder={t('core.dataset.search.Empty result response Tips')}
|
||||
{...register('searchEmptyText')}
|
||||
></Textarea>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
handleSubmit(onSuccess)();
|
||||
}}
|
||||
>
|
||||
{t('common.Done')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatasetParamsModal;
|
@@ -22,7 +22,7 @@ import MySlider from '@/components/Slider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { DatasetSearchModeEnum, DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
@@ -30,9 +30,6 @@ import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/data
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import EmptyTip from '@/components/EmptyTip';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
type DatasetParamsProps = AppSimpleEditFormType['dataset'];
|
||||
|
||||
export const DatasetSelectModal = ({
|
||||
isOpen,
|
||||
@@ -222,127 +219,4 @@ export const DatasetSelectModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const DatasetParamsModal = ({
|
||||
searchEmptyText,
|
||||
limit,
|
||||
similarity,
|
||||
rerank,
|
||||
onClose,
|
||||
onChange
|
||||
}: DatasetParamsProps & { onClose: () => void; onChange: (e: DatasetParamsProps) => void }) => {
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
|
||||
defaultValues: {
|
||||
searchEmptyText,
|
||||
limit,
|
||||
similarity,
|
||||
rerank
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/params.svg"
|
||||
title={'搜索参数调整'}
|
||||
minW={['90vw', '600px']}
|
||||
>
|
||||
<Flex flexDirection={'column'}>
|
||||
<ModalBody>
|
||||
{feConfigs?.isPlus && (
|
||||
<Box display={['block', 'flex']} py={5} pt={[0, 5]}>
|
||||
<Box flex={'0 0 100px'} mb={[8, 0]}>
|
||||
结果重排
|
||||
<MyTooltip label={'将召回的结果进行进一步重排,可增加召回率'} forceShow>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={getValues(ModuleInputKeyEnum.datasetStartReRank)}
|
||||
onChange={(e) => {
|
||||
setValue(ModuleInputKeyEnum.datasetStartReRank, e.target.checked);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box display={['block', 'flex']} py={5} pt={[0, 5]}>
|
||||
<Box flex={'0 0 100px'} mb={[8, 0]}>
|
||||
相似度
|
||||
<MyTooltip
|
||||
label={'不同索引模型的相似度有区别,请通过搜索测试来选择合适的数值'}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '1', value: 1 }
|
||||
]}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={getValues(ModuleInputKeyEnum.datasetSimilarity)}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box display={['block', 'flex']} py={8}>
|
||||
<Box flex={'0 0 100px'} mb={[8, 0]}>
|
||||
单次搜索数量
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '1', value: 1 },
|
||||
{ label: '20', value: 20 }
|
||||
]}
|
||||
min={1}
|
||||
max={20}
|
||||
value={getValues(ModuleInputKeyEnum.datasetLimit)}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.datasetLimit, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display={['block', 'flex']} pt={3}>
|
||||
<Box flex={'0 0 100px'} mb={[2, 0]}>
|
||||
空搜索回复
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Textarea
|
||||
rows={5}
|
||||
maxLength={500}
|
||||
placeholder={`若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文,${feConfigs?.systemTitle} 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。`}
|
||||
{...register('searchEmptyText')}
|
||||
></Textarea>
|
||||
</Box>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
handleSubmit(onChange)();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Flex>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatasetSelectModal;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -54,13 +54,18 @@ const VariableEdit = ({
|
||||
|
||||
const VariableTypeList = [
|
||||
{
|
||||
label: t('core.module.variable.text type'),
|
||||
icon: 'settingLight',
|
||||
label: t('core.module.variable.input type'),
|
||||
icon: 'core/app/variable/input',
|
||||
key: VariableInputEnum.input
|
||||
},
|
||||
{
|
||||
label: t('core.module.variable.textarea type'),
|
||||
icon: 'core/app/variable/textarea',
|
||||
key: VariableInputEnum.textarea
|
||||
},
|
||||
{
|
||||
label: t('core.module.variable.select type'),
|
||||
icon: 'settingLight',
|
||||
icon: 'core/app/variable/select',
|
||||
key: VariableInputEnum.select
|
||||
}
|
||||
];
|
||||
@@ -94,6 +99,13 @@ const VariableEdit = ({
|
||||
}
|
||||
};
|
||||
|
||||
const formatVariables = useMemo(() => {
|
||||
return variables.map((item) => ({
|
||||
...item,
|
||||
icon: VariableTypeList.find((type) => type.key === item.type)?.icon
|
||||
}));
|
||||
}, [variables]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
@@ -114,12 +126,13 @@ const VariableEdit = ({
|
||||
+ {t('common.Add New')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{variables.length > 0 && (
|
||||
{formatVariables.length > 0 && (
|
||||
<Box mt={2} borderRadius={'lg'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th w={'18px !important'} p={0} />
|
||||
<Th>{t('core.module.variable.variable name')}</Th>
|
||||
<Th>{t('core.module.variable.key')}</Th>
|
||||
<Th>{t('common.Require Input')}</Th>
|
||||
@@ -127,9 +140,12 @@ const VariableEdit = ({
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
{formatVariables.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td textAlign={'center'} p={0} pl={3}>
|
||||
<MyIcon name={item.icon as any} w={'14px'} color={'myGray.500'} />
|
||||
</Td>
|
||||
<Td>{item.label}</Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
@@ -190,18 +206,21 @@ const VariableEdit = ({
|
||||
<Box mt={5} mb={2}>
|
||||
{t('core.module.Field Type')}
|
||||
</Box>
|
||||
<Grid gridTemplateColumns={'repeat(2,130px)'} gridGap={4}>
|
||||
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={4}>
|
||||
{VariableTypeList.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
px={4}
|
||||
py={1}
|
||||
px={3}
|
||||
py={3}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
{...(item.key === getValuesEdit('variable.type')
|
||||
? {
|
||||
bg: 'myWhite.600'
|
||||
bg: 'myBlue.100',
|
||||
borderColor: 'myBlue.600',
|
||||
color: 'myBlue.600',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
@@ -214,7 +233,7 @@ const VariableEdit = ({
|
||||
})}
|
||||
>
|
||||
<MyIcon name={item.icon as any} w={'16px'} />
|
||||
<Box ml={3}>{item.label}</Box>
|
||||
<Box ml={2}>{item.label}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
@@ -225,14 +244,14 @@ const VariableEdit = ({
|
||||
{t('core.module.variable.text max length')}
|
||||
</Box>
|
||||
<Box>
|
||||
<NumberInput max={100} min={1} step={1} position={'relative'}>
|
||||
<NumberInput max={500} min={1} step={1} position={'relative'}>
|
||||
<NumberInputField
|
||||
{...registerEdit('variable.maxLen', {
|
||||
min: 1,
|
||||
max: 100,
|
||||
max: 500,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
max={100}
|
||||
max={500}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
@@ -258,16 +277,18 @@ const VariableEdit = ({
|
||||
})}
|
||||
/>
|
||||
</FormControl>
|
||||
<MyIcon
|
||||
ml={3}
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
p={2}
|
||||
borderRadius={'lg'}
|
||||
_hover={{ bg: 'red.100' }}
|
||||
onClick={() => removeEnums(i)}
|
||||
/>
|
||||
{selectEnums.length > 1 && (
|
||||
<MyIcon
|
||||
ml={3}
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
p={2}
|
||||
borderRadius={'lg'}
|
||||
_hover={{ bg: 'red.100' }}
|
||||
onClick={() => removeEnums(i)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import {
|
||||
@@ -36,11 +36,13 @@ import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const SelectAppModal = dynamic(() => import('../../SelectAppModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('../../../DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('../../../DatasetParamsModal'));
|
||||
|
||||
export const Label = React.memo(function Label({
|
||||
moduleId,
|
||||
@@ -232,6 +234,9 @@ const RenderInput = ({
|
||||
{item.type === FlowNodeInputTypeEnum.selectDataset && (
|
||||
<SelectDatasetRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectDatasetParamsModal && (
|
||||
<SelectDatasetParamsRender item={item} inputs={sortInputs} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.custom && CustomComponent[item.key] && (
|
||||
<>{CustomComponent[item.key]({ ...item })}</>
|
||||
)}
|
||||
@@ -251,7 +256,7 @@ type RenderProps = {
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
var NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }: RenderProps) {
|
||||
const NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<NumberInput
|
||||
defaultValue={item.value}
|
||||
@@ -278,7 +283,7 @@ var NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }
|
||||
);
|
||||
});
|
||||
|
||||
var TextInputRender = React.memo(function TextInputRender({ item, moduleId }: RenderProps) {
|
||||
const TextInputRender = React.memo(function TextInputRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Input
|
||||
placeholder={item.placeholder}
|
||||
@@ -298,7 +303,7 @@ var TextInputRender = React.memo(function TextInputRender({ item, moduleId }: Re
|
||||
);
|
||||
});
|
||||
|
||||
var SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderProps) {
|
||||
const SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Switch
|
||||
size={'lg'}
|
||||
@@ -318,7 +323,7 @@ var SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderPr
|
||||
);
|
||||
});
|
||||
|
||||
var TextareaRender = React.memo(function TextareaRender({ item, moduleId }: RenderProps) {
|
||||
const TextareaRender = React.memo(function TextareaRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Textarea
|
||||
rows={5}
|
||||
@@ -340,7 +345,7 @@ var TextareaRender = React.memo(function TextareaRender({ item, moduleId }: Rend
|
||||
);
|
||||
});
|
||||
|
||||
var SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderProps) {
|
||||
const SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
@@ -361,7 +366,7 @@ var SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderPr
|
||||
);
|
||||
});
|
||||
|
||||
var SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderProps) {
|
||||
const SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Box pt={5} pb={4} px={2}>
|
||||
<MySlider
|
||||
@@ -387,7 +392,7 @@ var SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderPr
|
||||
);
|
||||
});
|
||||
|
||||
var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderProps) {
|
||||
const AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderProps) {
|
||||
const { t } = useTranslation();
|
||||
const chatModulesData = useMemo(() => {
|
||||
const obj: Record<string, any> = {};
|
||||
@@ -440,15 +445,15 @@ var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderP
|
||||
);
|
||||
});
|
||||
|
||||
var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
const SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
inputs = [],
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const modelList = chatModelList || [];
|
||||
|
||||
function onChangeModel(e: string) {
|
||||
{
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
@@ -477,8 +482,9 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
value: model.maxResponse / 2
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[inputs, item, modelList, moduleId]
|
||||
);
|
||||
|
||||
const list = modelList.map((item) => {
|
||||
const priceStr = `(${formatPrice(item.price, 1000)}元/1k Tokens)`;
|
||||
@@ -489,9 +495,11 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
};
|
||||
});
|
||||
|
||||
if (!item.value && list.length > 0) {
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!item.value && list.length > 0) {
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
}, [item.value, list, onChangeModel]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
@@ -504,7 +512,10 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
);
|
||||
});
|
||||
|
||||
var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, moduleId }: RenderProps) {
|
||||
const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const theme = useTheme();
|
||||
const { mode } = useFlowProviderStore();
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
@@ -572,7 +583,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
);
|
||||
});
|
||||
|
||||
var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
|
||||
const SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
|
||||
const { filterAppIds } = useFlowProviderStore();
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -622,3 +633,63 @@ var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: Re
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectDatasetParamsRender = React.memo(function SelectDatasetParamsRender({
|
||||
inputs = [],
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const { t } = useTranslation();
|
||||
const [data, setData] = useState({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5
|
||||
});
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
inputs.forEach((input) => {
|
||||
// @ts-ignore
|
||||
if (data[input.key] !== undefined) {
|
||||
setData((state) => ({
|
||||
...state,
|
||||
[input.key]: input.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, [inputs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{t('core.dataset.search.Params Setting')}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<DatasetParamsModal
|
||||
{...data}
|
||||
onClose={onClose}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@@ -82,14 +82,16 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
|
||||
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
|
||||
API 秘钥管理
|
||||
</Box>
|
||||
<Link
|
||||
href={feConfigs.openAPIDocUrl || 'https://doc.fastgpt.run/docs/development/openapi'}
|
||||
target={'_blank'}
|
||||
ml={1}
|
||||
color={'myBlue.600'}
|
||||
>
|
||||
查看文档
|
||||
</Link>
|
||||
{feConfigs.docUrl && (
|
||||
<Link
|
||||
href={feConfigs.openAPIDocUrl || `${feConfigs.docUrl}/docs/development/openapi`}
|
||||
target={'_blank'}
|
||||
ml={1}
|
||||
color={'myBlue.600'}
|
||||
>
|
||||
查看文档
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
<Box fontSize={'sm'} color={'myGray.600'}>
|
||||
{tips}
|
||||
|
@@ -4,7 +4,8 @@ import type {
|
||||
LLMModelItemType,
|
||||
VectorModelItemType,
|
||||
AudioSpeechModels,
|
||||
WhisperModelType
|
||||
WhisperModelType,
|
||||
ReRankModelItemType
|
||||
} from '@fastgpt/global/core/ai/model.d';
|
||||
|
||||
import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d';
|
||||
@@ -19,6 +20,7 @@ export type ConfigFileType = {
|
||||
ExtractModels: FunctionModelItemType[];
|
||||
QGModels: LLMModelItemType[];
|
||||
VectorModels: VectorModelItemType[];
|
||||
ReRankModels: ReRankModelItemType[];
|
||||
AudioSpeechModels: AudioSpeechModelType[];
|
||||
WhisperModel: WhisperModelType;
|
||||
};
|
||||
@@ -29,6 +31,7 @@ export type InitDateResponse = {
|
||||
extractModels: FunctionModelItemType[];
|
||||
vectorModels: VectorModelItemType[];
|
||||
audioSpeechModels: AudioSpeechModels[];
|
||||
reRankModels: ReRankModelItemType[];
|
||||
feConfigs: FeConfigsType;
|
||||
priceMd: string;
|
||||
systemVersion: string;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
|
||||
export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateType = {
|
||||
id: 'fastgpt-universal',
|
||||
@@ -17,7 +18,7 @@ export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateTy
|
||||
datasets: true,
|
||||
similarity: true,
|
||||
limit: true,
|
||||
rerank: true,
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
searchEmptyText: true
|
||||
},
|
||||
userGuide: {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api';
|
||||
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { DatasetSearchModeEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import {
|
||||
DatasetDataIndexItemType,
|
||||
SearchDataResponseItemType
|
||||
@@ -43,7 +43,7 @@ export type SearchTestProps = {
|
||||
datasetId: string;
|
||||
text: string;
|
||||
limit?: number;
|
||||
rerank?: boolean;
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
};
|
||||
export type SearchTestResponse = {
|
||||
list: SearchDataResponseItemType[];
|
||||
|
@@ -90,13 +90,15 @@ function App({ Component, pageProps }: AppProps) {
|
||||
hiId && localStorage.setItem('inviterId', hiId);
|
||||
}, [hiId]);
|
||||
|
||||
const title = feConfigs?.systemTitle || process.env.SYSTEM_NAME || '';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{feConfigs?.systemTitle || process.env.SYSTEM_NAME || ''}</title>
|
||||
<title>{title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="FastGPT 是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!"
|
||||
content={`${title} 是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!`}
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
|
@@ -3,7 +3,6 @@ import { Html, Head, Main, NextScript } from 'next/document';
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<title>{process.env.SYSTEM_NAME || 'FastGPT'}</title>
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
|
@@ -152,6 +152,7 @@ async function init(limit: number): Promise<any> {
|
||||
collectionId: data.collection_id,
|
||||
q: data.q,
|
||||
a: data.a,
|
||||
fullTextToken: '',
|
||||
indexes: [
|
||||
{
|
||||
defaultIndex: !data.a,
|
||||
|
80
projects/app/src/pages/api/admin/initv462-2.ts
Normal file
80
projects/app/src/pages/api/admin/initv462-2.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { delay } from '@/utils/tools';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
|
||||
let success = 0;
|
||||
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { limit = 50 } = req.body as { limit: number };
|
||||
await authCert({ req, authRoot: true });
|
||||
await connectToDatabase();
|
||||
success = 0;
|
||||
|
||||
console.log('total', await MongoApp.countDocuments());
|
||||
|
||||
await initApp(limit);
|
||||
|
||||
jsonRes(res, {
|
||||
message: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function initApp(limit = 50): Promise<any> {
|
||||
try {
|
||||
const apps = await MongoApp.find({ inited: false }).limit(limit);
|
||||
if (apps.length === 0) return;
|
||||
|
||||
const result = await Promise.allSettled(
|
||||
apps.map(async (app) => {
|
||||
// 遍历app的modules,找到 datasetSearch, 如果 rerank=true, searchMode = embFullTextReRank, 否则等于embedding
|
||||
const modules = JSON.parse(JSON.stringify(app.modules)) as ModuleItemType[];
|
||||
modules.forEach((module) => {
|
||||
if (module.flowType === FlowNodeTypeEnum.datasetSearchNode) {
|
||||
module.inputs.forEach((input, i) => {
|
||||
if (input.key === 'rerank') {
|
||||
const val = !!input.value as boolean;
|
||||
module.inputs.splice(i, 1, {
|
||||
key: ModuleInputKeyEnum.datasetSearchMode,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: 'core.dataset.search.Mode',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: val
|
||||
? DatasetSearchModeEnum.embFullTextReRank
|
||||
: DatasetSearchModeEnum.embedding
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
app.modules = modules;
|
||||
app.inited = true;
|
||||
await app.save();
|
||||
})
|
||||
);
|
||||
|
||||
success += result.filter((item) => item.status === 'fulfilled').length;
|
||||
console.log(`success: ${success}`);
|
||||
return initApp(limit);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await delay(1000);
|
||||
return initApp(limit);
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@ import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -378,7 +379,7 @@ function datasetTemplate({
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
value: 0.5,
|
||||
value: 0.4,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '相似度',
|
||||
connected: true
|
||||
@@ -403,13 +404,23 @@ function datasetTemplate({
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'rerank',
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '结果重排',
|
||||
description: '将召回的结果进行进一步重排,可增加召回率',
|
||||
plusField: true,
|
||||
connected: true,
|
||||
value: true
|
||||
key: 'searchMode',
|
||||
type: 'hidden',
|
||||
label: 'core.dataset.search.Mode',
|
||||
valueType: 'string',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: DatasetSearchModeEnum.embFullTextReRank,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'datasetParamsModal',
|
||||
type: 'selectDatasetParamsModal',
|
||||
label: '',
|
||||
connected: false,
|
||||
valueType: 'any',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
|
@@ -312,13 +312,23 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'rerank',
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '结果重排',
|
||||
description: '将召回的结果进行进一步重排,可增加召回率',
|
||||
plusField: true,
|
||||
key: 'searchMode',
|
||||
type: 'hidden',
|
||||
label: 'core.dataset.search.Mode',
|
||||
valueType: 'string',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: formData.dataset.searchMode,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'datasetParamsModal',
|
||||
type: 'selectDatasetParamsModal',
|
||||
label: '',
|
||||
connected: false,
|
||||
value: formData.dataset.rerank
|
||||
valueType: 'any',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
|
@@ -4,7 +4,6 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
/* 获取我的模型 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
|
@@ -67,8 +67,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
'limit.exportKbTime': new Date()
|
||||
});
|
||||
});
|
||||
|
||||
cursor.on('error', (err) => {
|
||||
addLog.error(`export dataset error`, err);
|
||||
res.status(500);
|
||||
res.end();
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500);
|
||||
addLog.error(`export dataset error`, err);
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
|
@@ -15,7 +15,7 @@ import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { datasetId, text, limit = 20, rerank } = req.body as SearchTestProps;
|
||||
const { datasetId, text, limit = 20, searchMode } = req.body as SearchTestProps;
|
||||
|
||||
if (!datasetId || !text) {
|
||||
throw new Error('缺少参数');
|
||||
@@ -40,7 +40,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
model: dataset.vectorModel,
|
||||
limit: Math.min(limit, 50),
|
||||
datasetIds: [datasetId],
|
||||
rerank
|
||||
searchMode
|
||||
});
|
||||
|
||||
// push bill
|
||||
|
@@ -14,7 +14,8 @@ import {
|
||||
defaultQGModels,
|
||||
defaultVectorModels,
|
||||
defaultAudioSpeechModels,
|
||||
defaultWhisperModel
|
||||
defaultWhisperModel,
|
||||
defaultReRankModels
|
||||
} from '@fastgpt/global/core/ai/model';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import { getSimpleTemplatesFromPlus } from '@/service/core/app/utils';
|
||||
@@ -31,11 +32,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
cqModels: global.cqModels,
|
||||
extractModels: global.extractModels,
|
||||
vectorModels: global.vectorModels,
|
||||
audioSpeechModels: global.audioSpeechModels.map((item) => ({
|
||||
reRankModels: global.reRankModels.map((item) => ({
|
||||
...item,
|
||||
baseUrl: undefined,
|
||||
key: undefined
|
||||
requestUrl: undefined,
|
||||
requestAuth: undefined
|
||||
})),
|
||||
audioSpeechModels: global.audioSpeechModels,
|
||||
priceMd: global.priceMd,
|
||||
systemVersion: global.systemVersion || '0.0.0',
|
||||
simpleModeTemplates: global.simpleModeTemplates
|
||||
@@ -50,12 +52,11 @@ const defaultSystemEnv: SystemEnvType = {
|
||||
};
|
||||
const defaultFeConfigs: FeConfigsType = {
|
||||
show_emptyChat: true,
|
||||
show_contact: true,
|
||||
show_git: true,
|
||||
docUrl: 'https://docs.fastgpt.in',
|
||||
show_register: false,
|
||||
docUrl: 'https://doc.fastgpt.in',
|
||||
openAPIDocUrl: 'https://doc.fastgpt.in/docs/development/openapi',
|
||||
systemTitle: 'FastGPT',
|
||||
authorText: 'Made by FastGPT Team.',
|
||||
limit: {
|
||||
exportLimitMinutes: 0
|
||||
},
|
||||
@@ -99,7 +100,14 @@ export function setDefaultData(res?: ConfigFileType) {
|
||||
? { ...defaultSystemEnv, ...res.SystemParams }
|
||||
: defaultSystemEnv;
|
||||
global.feConfigs = res?.FeConfig
|
||||
? { ...defaultFeConfigs, ...res.FeConfig, isPlus: !!res.SystemParams?.pluginBaseUrl }
|
||||
? {
|
||||
concatMd: res?.FeConfig?.show_git
|
||||
? '* 项目开源地址: [FastGPT GitHub](https://github.com/labring/FastGPT)\n* 交流群: '
|
||||
: '',
|
||||
...defaultFeConfigs,
|
||||
...res.FeConfig,
|
||||
isPlus: !!res.SystemParams?.pluginBaseUrl
|
||||
}
|
||||
: defaultFeConfigs;
|
||||
|
||||
global.chatModels = res?.ChatModels || defaultChatModels;
|
||||
@@ -110,6 +118,8 @@ export function setDefaultData(res?: ConfigFileType) {
|
||||
|
||||
global.vectorModels = res?.VectorModels || defaultVectorModels;
|
||||
|
||||
global.reRankModels = res?.ReRankModels || defaultReRankModels;
|
||||
|
||||
global.audioSpeechModels = res?.AudioSpeechModels || defaultAudioSpeechModels;
|
||||
|
||||
global.whisperModel = res?.WhisperModel || defaultWhisperModel;
|
||||
|
@@ -15,7 +15,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
try {
|
||||
let { input, model, billId } = req.body as Props;
|
||||
await connectToDatabase();
|
||||
const { teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
const { teamId, tmbId } = await authCert({ req, authToken: true, authApiKey: true });
|
||||
|
||||
if (!Array.isArray(input) || typeof input !== 'string') {
|
||||
throw new Error('input is nor array or string');
|
||||
|
42
projects/app/src/pages/api/v1/rerank.ts
Normal file
42
projects/app/src/pages/api/v1/rerank.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
import { pushReRankBill } from '@/service/support/wallet/bill/push';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { PostReRankProps } from '@fastgpt/global/core/ai/api';
|
||||
import { reRankRecall } from '@/service/core/ai/rerank';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
let { query, inputs } = req.body as PostReRankProps;
|
||||
await connectToDatabase();
|
||||
const { teamId, tmbId } = await authCert({
|
||||
req,
|
||||
authApiKey: true
|
||||
});
|
||||
await authTeamBalance(teamId);
|
||||
|
||||
// max 150 length
|
||||
inputs = inputs.slice(0, 150);
|
||||
|
||||
const result = await reRankRecall({ query, inputs });
|
||||
|
||||
pushReRankBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
source: 'api'
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: result
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
});
|
@@ -33,7 +33,6 @@ import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { delModelById } from '@/web/core/app/api';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { DatasetParamsModal } from '@/components/core/module/DatasetSelectModal';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
@@ -56,6 +55,7 @@ import VariableEdit from '@/components/core/module/Flow/components/modules/Varia
|
||||
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal'));
|
||||
|
||||
function ConfigForm({
|
||||
@@ -72,7 +72,6 @@ function ConfigForm({
|
||||
const { appDetail, updateAppDetail } = useAppStore();
|
||||
const { loadAllDatasets, allDatasets } = useDatasetStore();
|
||||
const { isPc } = useSystemStore();
|
||||
const [editVariable, setEditVariable] = useState<VariableItemType>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const { register, setValue, getValues, reset, handleSubmit, control } =
|
||||
@@ -197,8 +196,8 @@ function ConfigForm({
|
||||
})}
|
||||
>
|
||||
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
|
||||
应用配置
|
||||
<MyTooltip label={'仅包含基础功能,复杂 agent 功能请使用高级编排。'} forceShow>
|
||||
{t('core.app.App params config')}
|
||||
<MyTooltip label={t('core.app.Simple Config Tip')} forceShow>
|
||||
<QuestionOutlineIcon ml={2} fontSize={'md'} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
@@ -215,7 +214,7 @@ function ConfigForm({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isPc ? '保存并预览' : '保存'}
|
||||
{isPc ? t('core.app.Save and preview') : t('common.Save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -248,7 +247,7 @@ function ConfigForm({
|
||||
<Box {...BoxStyles} mt={2}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Image alt={''} src={'/imgs/module/userGuide.png'} w={'18px'} />
|
||||
<Box mx={2}>对话开场白</Box>
|
||||
<Box mx={2}>{t('core.app.Welcome Text')}</Box>
|
||||
<MyTooltip label={welcomeTextTip} forceShow>
|
||||
<QuestionOutlineIcon />
|
||||
</MyTooltip>
|
||||
@@ -351,7 +350,7 @@ function ConfigForm({
|
||||
</Flex>
|
||||
)}
|
||||
{(selectSimpleTemplate.systemForm.dataset.limit ||
|
||||
selectSimpleTemplate.systemForm.dataset.rerank ||
|
||||
selectSimpleTemplate.systemForm.dataset.searchMode ||
|
||||
selectSimpleTemplate.systemForm.dataset.searchEmptyText ||
|
||||
selectSimpleTemplate.systemForm.dataset.similarity) && (
|
||||
<Flex alignItems={'center'} ml={3} {...BoxBtnStyles} onClick={onOpenKbParams}>
|
||||
@@ -459,7 +458,7 @@ function ConfigForm({
|
||||
<DatasetParamsModal
|
||||
{...getValues('dataset')}
|
||||
onClose={onCloseKbParams}
|
||||
onChange={(e) => {
|
||||
onSuccess={(e) => {
|
||||
setValue('dataset', {
|
||||
...getValues('dataset'),
|
||||
...e
|
||||
|
@@ -66,12 +66,12 @@ const MyApps = () => {
|
||||
);
|
||||
|
||||
/* 加载模型 */
|
||||
useQuery(['loadApps'], () => loadMyApps(true), {
|
||||
const { isFetching } = useQuery(['loadApps'], () => loadMyApps(true), {
|
||||
refetchOnMount: true
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageContainer isLoading={isFetching}>
|
||||
<Flex pt={3} px={5} alignItems={'center'}>
|
||||
<Flex flex={1} alignItems={'center'}>
|
||||
<Image src={'/imgs/module/ai.svg'} alt={''} mr={2} h={'24px'} />
|
||||
|
@@ -20,7 +20,7 @@ const Navbar = () => {
|
||||
const { isOpen: isOpenMenu, onOpen: onOpenMenu, onClose: onCloseMenu } = useDisclosure();
|
||||
const { isPc } = useSystemStore();
|
||||
const menuList = [
|
||||
...(feConfigs?.show_contact
|
||||
...(feConfigs?.concatMd
|
||||
? [
|
||||
{
|
||||
label: t('home.Commercial'),
|
||||
|
@@ -5,7 +5,10 @@ import { useImportStore, SelectorContainer, PreviewFileOrChunk } from './Provide
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const fileExtension = '.csv';
|
||||
const csvTemplate = `index,content\n"被索引的内容","对应的答案。CSV 中请注意内容不能包含双引号,双引号是列分割符号"\n"什么是 laf","laf 是一个云函数开发平台……",""\n"什么是 sealos","Sealos 是以 kubernetes 为内核的云操作系统发行版,可以……"`;
|
||||
const csvTemplate = `index,content
|
||||
"必填内容","可选内容。CSV 中请注意内容不能包含双引号,双引号是列分割符号"
|
||||
"结合人工智能的演进历程,AIGC的发展大致可以分为三个阶段,即:早期萌芽阶段(20世纪50年代至90年代中期)、沉淀积累阶段(20世纪90年代中期至21世纪10年代中期),以及快速发展展阶段(21世纪10年代中期至今)。",""
|
||||
"AIGC发展分为几个阶段?","早期萌芽阶段(20世纪50年代至90年代中期)、沉淀积累阶段(20世纪90年代中期至21世纪10年代中期)、快速发展展阶段(21世纪10年代中期至今)"`;
|
||||
|
||||
const CsvImport = () => {
|
||||
const { t } = useTranslation();
|
||||
|
@@ -1,5 +1,15 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Box, Textarea, Button, Flex, useTheme, Grid, Progress, Switch } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
Textarea,
|
||||
Button,
|
||||
Flex,
|
||||
useTheme,
|
||||
Grid,
|
||||
Progress,
|
||||
Switch,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { useSearchTestStore, SearchTestStoreItemType } from '@/web/core/dataset/store/searchTest';
|
||||
import { getDatasetDataItemById, postSearchText } from '@/web/core/dataset/api';
|
||||
@@ -13,12 +23,14 @@ import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { SearchTestResponse } from '../../../../global/core/dataset/api';
|
||||
import { SearchTestResponse } from '@/global/core/dataset/api';
|
||||
import { DatasetSearchModeEnum, DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
|
||||
|
||||
const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
@@ -30,15 +42,24 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
const [inputText, setInputText] = useState('');
|
||||
const [datasetTestItem, setDatasetTestItem] = useState<SearchTestStoreItemType>();
|
||||
const [editInputData, setEditInputData] = useState<InputDataType & { collectionId: string }>();
|
||||
const [rerank, setRerank] = useState(false);
|
||||
const [searchMode, setSearchMode] = useState<`${DatasetSearchModeEnum}`>(
|
||||
DatasetSearchModeEnum.embedding
|
||||
);
|
||||
const searchModeData = DatasetSearchModeMap[searchMode];
|
||||
|
||||
const kbTestHistory = useMemo(
|
||||
const {
|
||||
isOpen: isOpenSelectMode,
|
||||
onOpen: onOpenSelectMode,
|
||||
onClose: onCloseSelectMode
|
||||
} = useDisclosure();
|
||||
|
||||
const testHistories = useMemo(
|
||||
() => datasetTestList.filter((item) => item.datasetId === datasetId),
|
||||
[datasetId, datasetTestList]
|
||||
);
|
||||
|
||||
const { mutate, isLoading } = useRequest({
|
||||
mutationFn: () => postSearchText({ datasetId, text: inputText.trim(), rerank, limit: 30 }),
|
||||
mutationFn: () => postSearchText({ datasetId, text: inputText.trim(), searchMode, limit: 30 }),
|
||||
onSuccess(res: SearchTestResponse) {
|
||||
if (!res || res.list.length === 0) {
|
||||
return toast({
|
||||
@@ -71,6 +92,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
|
||||
return (
|
||||
<Box h={'100%'} display={['block', 'flex']}>
|
||||
{/* input */}
|
||||
<Box
|
||||
h={['auto', '100%']}
|
||||
display={['block', 'flex']}
|
||||
@@ -86,12 +108,14 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
<MyIcon mr={2} name={'text'} w={'18px'} h={'18px'} color={'myBlue.700'} />
|
||||
{t('core.dataset.test.Test Text')}
|
||||
</Box>
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex alignItems={'center'}>
|
||||
{t('dataset.recall.rerank')}
|
||||
<Switch ml={1} isChecked={rerank} onChange={(e) => setRerank(e.target.checked)} />
|
||||
</Flex>
|
||||
)}
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={searchModeData.icon as any} w={'14px'} />}
|
||||
size={'sm'}
|
||||
onClick={onOpenSelectMode}
|
||||
>
|
||||
{t(searchModeData.title)}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Textarea
|
||||
rows={6}
|
||||
@@ -122,7 +146,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
<Box w={'80px'}>{t('common.Time')}</Box>
|
||||
<Box w={'14px'}></Box>
|
||||
</Flex>
|
||||
{kbTestHistory.map((item) => (
|
||||
{testHistories.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
p={1}
|
||||
@@ -162,6 +186,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* result show */}
|
||||
<Box p={4} h={['auto', '100%']} overflow={'overlay'} flex={1}>
|
||||
{!datasetTestItem?.results || datasetTestItem.results.length === 0 ? (
|
||||
<Flex
|
||||
@@ -307,6 +332,15 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenSelectMode && (
|
||||
<DatasetParamsModal
|
||||
searchMode={searchMode}
|
||||
onClose={onCloseSelectMode}
|
||||
onSuccess={(e) => {
|
||||
setSearchMode(e.searchMode);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -11,7 +11,6 @@ import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
|
||||
|
||||
interface Props {
|
||||
@@ -86,6 +85,8 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
|
||||
: [])
|
||||
];
|
||||
|
||||
const isCommunityVersion = feConfigs?.show_register === false && feConfigs?.show_git;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box fontWeight={'bold'} fontSize={'2xl'} textAlign={'center'}>
|
||||
@@ -94,7 +95,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
|
||||
<form onSubmit={handleSubmit(onclickLogin)}>
|
||||
<FormControl mt={8} isInvalid={!!errors.username}>
|
||||
<Input
|
||||
placeholder="邮箱/手机号/用户名"
|
||||
placeholder={isCommunityVersion ? '使用root用户登录' : '邮箱/手机号/用户名'}
|
||||
size={['md', 'lg']}
|
||||
{...register('username', {
|
||||
required: '邮箱/手机号/用户名不能为空'
|
||||
@@ -108,7 +109,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
|
||||
<Input
|
||||
type={'password'}
|
||||
size={['md', 'lg']}
|
||||
placeholder="密码"
|
||||
placeholder={isCommunityVersion ? 'root密码为你设置的环境变量' : '密码'}
|
||||
{...register('password', {
|
||||
required: '密码不能为空',
|
||||
maxLength: {
|
||||
|
@@ -13,7 +13,6 @@ import { clearToken, setToken } from '@/web/support/user/auth';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import CommunityModal from '@/components/CommunityModal';
|
||||
import Script from 'next/script';
|
||||
import { loginOut } from '@/web/support/user/api';
|
||||
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
|
||||
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
|
||||
|
||||
@@ -114,10 +113,10 @@ const Login = () => {
|
||||
>
|
||||
<DynamicComponent type={pageType} />
|
||||
|
||||
{feConfigs?.show_contact && (
|
||||
{feConfigs?.concatMd && (
|
||||
<Box
|
||||
fontSize={'sm'}
|
||||
color={'myGray.600'}
|
||||
fontWeight={'bold'}
|
||||
color={'myBlue.700'}
|
||||
cursor={'pointer'}
|
||||
position={'absolute'}
|
||||
right={5}
|
||||
|
113
projects/app/src/service/common/api/request.ts
Normal file
113
projects/app/src/service/common/api/request.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
|
||||
interface ConfigType {
|
||||
headers?: { [key: string]: string };
|
||||
hold?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
interface ResponseDataType {
|
||||
code: number;
|
||||
message: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求开始
|
||||
*/
|
||||
function requestStart(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求成功,检查请求头
|
||||
*/
|
||||
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?.code && (data.code < 200 || data.code >= 400)) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应错误
|
||||
*/
|
||||
function responseError(err: any) {
|
||||
if (!err) {
|
||||
return Promise.reject({ message: '未知错误' });
|
||||
}
|
||||
if (typeof err === 'string') {
|
||||
return Promise.reject({ message: err });
|
||||
}
|
||||
|
||||
if (err?.response?.data) {
|
||||
return Promise.reject(err?.response?.data);
|
||||
}
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
/* 创建请求实例 */
|
||||
const instance = axios.create({
|
||||
timeout: 60000, // 超时时间
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'Cache-Control': 'no-cache'
|
||||
}
|
||||
});
|
||||
|
||||
/* 请求拦截 */
|
||||
instance.interceptors.request.use(requestStart, (err) => Promise.reject(err));
|
||||
/* 响应拦截 */
|
||||
instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err));
|
||||
|
||||
export function request(url: string, data: any, config: ConfigType, method: Method): any {
|
||||
/* 去空 */
|
||||
for (const key in data) {
|
||||
if (data[key] === null || data[key] === undefined) {
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return instance
|
||||
.request({
|
||||
baseURL: `http://localhost:${process.env.PORT || 3000}`,
|
||||
url,
|
||||
method,
|
||||
data: ['POST', 'PUT'].includes(method) ? data : null,
|
||||
params: !['POST', 'PUT'].includes(method) ? data : null,
|
||||
...config // 用户自定义配置,可以覆盖前面的配置
|
||||
})
|
||||
.then((res) => checkRes(res.data))
|
||||
.catch((err) => responseError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* api请求方式
|
||||
* @param {String} url
|
||||
* @param {Any} params
|
||||
* @param {Object} config
|
||||
* @returns
|
||||
*/
|
||||
export function GET<T>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, params, config, 'GET');
|
||||
}
|
||||
|
||||
export function POST<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, data, config, 'POST');
|
||||
}
|
||||
|
||||
export function PUT<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, data, config, 'PUT');
|
||||
}
|
||||
|
||||
export function DELETE<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, data, config, 'DELETE');
|
||||
}
|
26
projects/app/src/service/core/ai/rerank.ts
Normal file
26
projects/app/src/service/core/ai/rerank.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { PostReRankProps, PostReRankResponse } from '@fastgpt/global/core/ai/api';
|
||||
import { POST } from '@/service/common/api/request';
|
||||
|
||||
export function reRankRecall({ query, inputs }: PostReRankProps) {
|
||||
const model = global.reRankModels[0];
|
||||
|
||||
if (!model || !model?.requestUrl) {
|
||||
return Promise.reject('no rerank model');
|
||||
}
|
||||
|
||||
let start = Date.now();
|
||||
return POST<PostReRankResponse>(
|
||||
model.requestUrl,
|
||||
{
|
||||
query,
|
||||
inputs
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${model.requestAuth}`
|
||||
}
|
||||
}
|
||||
).finally(() => {
|
||||
console.log('rerank time:', Date.now() - start);
|
||||
});
|
||||
}
|
@@ -72,7 +72,7 @@ export async function insertData2Dataset({
|
||||
collectionId,
|
||||
q,
|
||||
a,
|
||||
fullTextToken: jiebaSplit({ text: q + a }),
|
||||
fullTextToken: jiebaSplit({ text: qaStr }),
|
||||
indexes: indexes.map((item, i) => ({
|
||||
...item,
|
||||
dataId: result[i].insertId
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
|
||||
import { DatasetSearchModeEnum, PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
|
||||
import type {
|
||||
DatasetDataSchemaType,
|
||||
SearchDataResponseItemType
|
||||
@@ -9,9 +9,8 @@ import { delay } from '@/utils/tools';
|
||||
import { PgSearchRawType } from '@fastgpt/global/core/dataset/api';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { POST } from '@fastgpt/service/common/api/plusRequest';
|
||||
import { PostReRankResponse } from '@fastgpt/global/core/ai/api';
|
||||
import { jiebaSplit } from '../utils';
|
||||
import { reRankRecall } from '../../ai/rerank';
|
||||
|
||||
export async function insertData2Pg({
|
||||
mongoDataId,
|
||||
@@ -136,31 +135,60 @@ type SearchProps = {
|
||||
similarity?: number; // min distance
|
||||
limit: number;
|
||||
datasetIds: string[];
|
||||
rerank?: boolean;
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
};
|
||||
export async function searchDatasetData(props: SearchProps) {
|
||||
const { text, similarity = 0, limit, rerank = false } = props;
|
||||
let { text, similarity = 0, limit, searchMode = DatasetSearchModeEnum.embedding } = props;
|
||||
searchMode = global.systemEnv.pluginBaseUrl ? searchMode : DatasetSearchModeEnum.embedding;
|
||||
|
||||
const rerank =
|
||||
searchMode === DatasetSearchModeEnum.embeddingReRank ||
|
||||
searchMode === DatasetSearchModeEnum.embFullTextReRank;
|
||||
|
||||
const { embeddingLimit, fullTextLimit } = (() => {
|
||||
// Increase search range, reduce hnsw loss
|
||||
if (searchMode === DatasetSearchModeEnum.embedding) {
|
||||
return {
|
||||
embeddingLimit: limit * 2,
|
||||
fullTextLimit: 0
|
||||
};
|
||||
}
|
||||
// 50 < 2*limit < value < 100
|
||||
if (searchMode === DatasetSearchModeEnum.embeddingReRank) {
|
||||
return {
|
||||
embeddingLimit: Math.min(100, Math.max(50, limit * 2)),
|
||||
fullTextLimit: 0
|
||||
};
|
||||
}
|
||||
// 50 < 3*limit < embedding < 80
|
||||
// 20 < limit < fullTextLimit < 40
|
||||
return {
|
||||
embeddingLimit: Math.min(80, Math.max(50, limit * 2)),
|
||||
fullTextLimit: Math.min(40, Math.max(20, limit))
|
||||
};
|
||||
})();
|
||||
|
||||
const [{ tokenLen, embeddingRecallResults }, { fullTextRecallResults }] = await Promise.all([
|
||||
embeddingRecall({
|
||||
...props,
|
||||
limit: rerank ? Math.max(50, limit * 3) : limit * 2
|
||||
rerank,
|
||||
limit: embeddingLimit
|
||||
}),
|
||||
fullTextRecall({
|
||||
...props,
|
||||
limit: 40
|
||||
limit: fullTextLimit
|
||||
})
|
||||
]);
|
||||
|
||||
// concat recall result
|
||||
let set = new Set<string>();
|
||||
// concat embedding and fullText recall result
|
||||
let set = new Set<string>(embeddingRecallResults.map((item) => item.id));
|
||||
const concatRecallResults = embeddingRecallResults;
|
||||
for (const item of fullTextRecallResults) {
|
||||
if (!set.has(item.id)) {
|
||||
fullTextRecallResults.forEach((item) => {
|
||||
if (!set.has(item.id) && item.score >= similarity) {
|
||||
concatRecallResults.push(item);
|
||||
set.add(item.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// remove same q and a data
|
||||
set = new Set<string>();
|
||||
@@ -173,32 +201,30 @@ export async function searchDatasetData(props: SearchProps) {
|
||||
|
||||
if (!rerank) {
|
||||
return {
|
||||
searchRes: filterSameDataResults.slice(0, limit),
|
||||
searchRes: filterSameDataResults.filter((item) => item.score >= similarity).slice(0, limit),
|
||||
tokenLen
|
||||
};
|
||||
}
|
||||
|
||||
// ReRank result
|
||||
const reRankResults = await reRankSearchResult({
|
||||
query: text,
|
||||
data: filterSameDataResults
|
||||
const reRankResults = (
|
||||
await reRankSearchResult({
|
||||
query: text,
|
||||
data: filterSameDataResults
|
||||
})
|
||||
).filter((item) => item.score > similarity);
|
||||
|
||||
// (It's possible that rerank failed) concat rerank results and search results
|
||||
set = new Set<string>(reRankResults.map((item) => item.id));
|
||||
embeddingRecallResults.forEach((item) => {
|
||||
if (!set.has(item.id) && item.score >= similarity) {
|
||||
reRankResults.push(item);
|
||||
set.add(item.id);
|
||||
}
|
||||
});
|
||||
|
||||
// similarity filter
|
||||
const filterReRankResults = reRankResults.filter((item) => item.score > similarity);
|
||||
|
||||
// concat rerank and embedding data
|
||||
set = new Set<string>(filterReRankResults.map((item) => item.id));
|
||||
const concatResult = filterReRankResults.concat(
|
||||
filterSameDataResults.filter((item) => {
|
||||
if (set.has(item.id)) return false;
|
||||
set.add(item.id);
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
searchRes: concatResult.slice(0, limit),
|
||||
searchRes: reRankResults.slice(0, limit),
|
||||
tokenLen
|
||||
};
|
||||
}
|
||||
@@ -209,7 +235,7 @@ export async function embeddingRecall({
|
||||
limit,
|
||||
datasetIds = [],
|
||||
rerank = false
|
||||
}: SearchProps) {
|
||||
}: SearchProps & { rerank: boolean }) {
|
||||
const { vectors, tokenLen } = await getVectorsByText({
|
||||
model,
|
||||
input: [text]
|
||||
@@ -282,16 +308,11 @@ export async function embeddingRecall({
|
||||
tokenLen
|
||||
};
|
||||
}
|
||||
export async function fullTextRecall({
|
||||
text,
|
||||
limit,
|
||||
datasetIds = [],
|
||||
rerank = false
|
||||
}: SearchProps): Promise<{
|
||||
export async function fullTextRecall({ text, limit, datasetIds = [] }: SearchProps): Promise<{
|
||||
fullTextRecallResults: SearchDataResponseItemType[];
|
||||
tokenLen: number;
|
||||
}> {
|
||||
if (!rerank) {
|
||||
if (limit === 0) {
|
||||
return {
|
||||
fullTextRecallResults: [],
|
||||
tokenLen: 0
|
||||
@@ -361,22 +382,23 @@ export async function reRankSearchResult({
|
||||
data: SearchDataResponseItemType[];
|
||||
query: string;
|
||||
}): Promise<SearchDataResponseItemType[]> {
|
||||
if (!global.systemEnv.pluginBaseUrl) return data;
|
||||
try {
|
||||
const result = await POST<PostReRankResponse>('/core/ai/retrival/rerank', {
|
||||
const results = await reRankRecall({
|
||||
query,
|
||||
inputs: data.map((item) => ({
|
||||
id: item.id,
|
||||
text: `${item.q}\n${item.a}`.trim()
|
||||
text: `${item.q}\n${item.a}`
|
||||
}))
|
||||
});
|
||||
const mergeResult = result
|
||||
|
||||
// add new score to data
|
||||
const mergeResult = results
|
||||
.map((item) => {
|
||||
const target = data.find((dataItem) => dataItem.id === item.id);
|
||||
if (!target) return null;
|
||||
return {
|
||||
...target,
|
||||
score: item.score ?? target.score
|
||||
score: item.score || 0
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as SearchDataResponseItemType[];
|
||||
@@ -385,7 +407,7 @@ export async function reRankSearchResult({
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
return data;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
// ------------------ search end ------------------
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { cut, extract } from '@node-rs/jieba';
|
||||
import { cut } from '@node-rs/jieba';
|
||||
|
||||
/**
|
||||
* Same value judgment
|
||||
@@ -27,8 +27,10 @@ export async function hasSameValue({
|
||||
export function jiebaSplit({ text }: { text: string }) {
|
||||
const tokens = cut(text, true);
|
||||
|
||||
return tokens
|
||||
.map((item) => item.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s]/g, '').trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
return (
|
||||
tokens
|
||||
.map((item) => item.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s]/g, '').trim())
|
||||
.filter(Boolean)
|
||||
.join(' ') || ''
|
||||
);
|
||||
}
|
||||
|
@@ -6,12 +6,13 @@ import type { ModuleDispatchProps } from '@/types/core/chat/type';
|
||||
import { ModelTypeEnum } from '@/service/core/ai/model';
|
||||
import { searchDatasetData } from '@/service/core/dataset/data/pg';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
|
||||
type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
[ModuleInputKeyEnum.datasetSimilarity]: number;
|
||||
[ModuleInputKeyEnum.datasetLimit]: number;
|
||||
[ModuleInputKeyEnum.datasetStartReRank]: boolean;
|
||||
[ModuleInputKeyEnum.datasetSearchMode]: `${DatasetSearchModeEnum}`;
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
}>;
|
||||
export type DatasetSearchResponse = {
|
||||
@@ -27,7 +28,7 @@ export async function dispatchDatasetSearch(
|
||||
const {
|
||||
teamId,
|
||||
tmbId,
|
||||
inputs: { datasets = [], similarity = 0.4, limit = 5, rerank, userChatInput }
|
||||
inputs: { datasets = [], similarity = 0.4, limit = 5, searchMode, userChatInput }
|
||||
} = props as DatasetSearchProps;
|
||||
|
||||
if (datasets.length === 0) {
|
||||
@@ -47,7 +48,7 @@ export async function dispatchDatasetSearch(
|
||||
similarity,
|
||||
limit,
|
||||
datasetIds: datasets.map((item) => item.datasetId),
|
||||
rerank
|
||||
searchMode
|
||||
});
|
||||
|
||||
return {
|
||||
|
@@ -239,3 +239,35 @@ export function pushWhisperBill({
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export function pushReRankBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
source
|
||||
}: {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
source: `${BillSourceEnum}`;
|
||||
}) {
|
||||
const model = global.reRankModels[0];
|
||||
if (!model) return;
|
||||
|
||||
const total = model.price * PRICE_SCALE;
|
||||
const name = 'wallet.bill.ReRank';
|
||||
|
||||
createBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: name,
|
||||
total,
|
||||
source,
|
||||
list: [
|
||||
{
|
||||
moduleName: name,
|
||||
amount: total,
|
||||
model: model.name,
|
||||
tokenLen: 1
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
2
projects/app/src/types/index.d.ts
vendored
2
projects/app/src/types/index.d.ts
vendored
@@ -3,6 +3,7 @@ import {
|
||||
ChatModelItemType,
|
||||
FunctionModelItemType,
|
||||
LLMModelItemType,
|
||||
ReRankModelItemType,
|
||||
VectorModelItemType,
|
||||
WhisperModelType
|
||||
} from '@fastgpt/global/core/ai/model.d';
|
||||
@@ -30,6 +31,7 @@ declare global {
|
||||
var qgModels: LLMModelItemType[];
|
||||
var audioSpeechModels: AudioSpeechModelType[];
|
||||
var whisperModel: WhisperModelType;
|
||||
var reRankModels: ReRankModelItemType[];
|
||||
|
||||
var priceMd: string;
|
||||
var systemVersion: string;
|
||||
|
@@ -24,7 +24,7 @@ export const readTxtContent = (file: File) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 读取 pdf 内容
|
||||
* read pdf to raw text
|
||||
*/
|
||||
export const readPdfContent = (file: File) =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
@@ -104,7 +104,7 @@ export const readPdfContent = (file: File) =>
|
||||
});
|
||||
|
||||
/**
|
||||
* 读取doc
|
||||
* read docx to markdown
|
||||
*/
|
||||
export const readDocContent = (file: File) =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
@@ -119,34 +119,7 @@ export const readDocContent = (file: File) =>
|
||||
arrayBuffer: target.result as ArrayBuffer
|
||||
});
|
||||
|
||||
let rawText: string = res?.value || '';
|
||||
|
||||
// match base64, upload and replace it
|
||||
const base64Regex = /data:image\/[a-zA-Z]+;base64,([^\)]+)/g;
|
||||
const base64Arr = rawText.match(base64Regex) || [];
|
||||
|
||||
// upload base64 and replace it
|
||||
await Promise.all(
|
||||
base64Arr.map(async (base64) => {
|
||||
try {
|
||||
const str = await compressBase64ImgAndUpload({
|
||||
base64,
|
||||
maxW: 800,
|
||||
maxH: 800,
|
||||
maxSize: 1024 * 1024 * 2
|
||||
});
|
||||
rawText = rawText.replace(base64, str);
|
||||
} catch (error) {
|
||||
rawText = rawText.replace(base64, '');
|
||||
rawText = rawText.replaceAll('![]()', '');
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const trimReg = /\s*(!\[.*\]\(.*\))\s*/g;
|
||||
if (trimReg.test(rawText)) {
|
||||
rawText = rawText.replace(/\s*(!\[.*\]\(.*\))\s*/g, '$1');
|
||||
}
|
||||
const rawText = await formatMarkdown(res?.value);
|
||||
|
||||
resolve(rawText);
|
||||
} catch (error) {
|
||||
@@ -172,7 +145,11 @@ export const readDocContent = (file: File) =>
|
||||
});
|
||||
|
||||
/**
|
||||
* 读取csv
|
||||
* read csv to json
|
||||
* @response {
|
||||
* header: string[],
|
||||
* data: string[][]
|
||||
* }
|
||||
*/
|
||||
export const readCsvContent = async (file: File) => {
|
||||
try {
|
||||
@@ -190,6 +167,48 @@ export const readCsvContent = async (file: File) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* format markdown
|
||||
* 1. upload base64
|
||||
* 2. replace \
|
||||
*/
|
||||
export const formatMarkdown = async (rawText: string = '') => {
|
||||
// match base64, upload and replace it
|
||||
const base64Regex = /data:image\/.*;base64,([^\)]+)/g;
|
||||
const base64Arr = rawText.match(base64Regex) || [];
|
||||
// upload base64 and replace it
|
||||
await Promise.all(
|
||||
base64Arr.map(async (base64) => {
|
||||
try {
|
||||
const str = await compressBase64ImgAndUpload({
|
||||
base64,
|
||||
maxW: 800,
|
||||
maxH: 800,
|
||||
maxSize: 1024 * 1024 * 2
|
||||
});
|
||||
rawText = rawText.replace(base64, str);
|
||||
} catch (error) {
|
||||
rawText = rawText.replace(base64, '');
|
||||
rawText = rawText.replace(/!\[.*\]\(\)/g, '');
|
||||
}
|
||||
})
|
||||
);
|
||||
// Remove white space on both sides of the picture
|
||||
const trimReg = /\s*(!\[.*\]\(.*\))\s*/g;
|
||||
if (trimReg.test(rawText)) {
|
||||
rawText = rawText.replace(/\s*(!\[.*\]\(.*\))\s*/g, '$1');
|
||||
}
|
||||
|
||||
// replace \
|
||||
const reg1 = /\\([-.!`_(){}\[\]])/g;
|
||||
if (reg1.test(rawText)) {
|
||||
rawText = rawText.replace(/\\([`!*()+-_\[\]{}\\.])/g, '$1');
|
||||
}
|
||||
rawText = rawText.replace(/\\\\n/g, '\\n');
|
||||
|
||||
return rawText;
|
||||
};
|
||||
|
||||
/**
|
||||
* file download by text
|
||||
*/
|
||||
|
@@ -9,7 +9,8 @@ import {
|
||||
defaultExtractModels,
|
||||
defaultQGModels,
|
||||
defaultVectorModels,
|
||||
defaultAudioSpeechModels
|
||||
defaultAudioSpeechModels,
|
||||
defaultReRankModels
|
||||
} from '@fastgpt/global/core/ai/model';
|
||||
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
@@ -25,6 +26,7 @@ export let extractModelList = defaultExtractModels;
|
||||
export let qgModelList = defaultQGModels;
|
||||
export let audioSpeechModels = defaultAudioSpeechModels;
|
||||
export let simpleModeTemplates: AppSimpleEditConfigTemplateType[] = [];
|
||||
export let reRankModelList = defaultReRankModels;
|
||||
|
||||
let retryTimes = 3;
|
||||
|
||||
@@ -32,14 +34,16 @@ export const clientInitData = async (): Promise<InitDateResponse> => {
|
||||
try {
|
||||
const res = await getSystemInitData();
|
||||
|
||||
chatModelList = res.chatModels || [];
|
||||
qaModelList = res.qaModels || [];
|
||||
cqModelList = res.cqModels || [];
|
||||
extractModelList = res.extractModels || [];
|
||||
chatModelList = res.chatModels ?? chatModelList;
|
||||
qaModelList = res.qaModels ?? qaModelList;
|
||||
cqModelList = res.cqModels ?? cqModelList;
|
||||
extractModelList = res.extractModels ?? extractModelList;
|
||||
|
||||
vectorModelList = res.vectorModels || [];
|
||||
vectorModelList = res.vectorModels ?? vectorModelList;
|
||||
|
||||
audioSpeechModels = res.audioSpeechModels || [];
|
||||
reRankModelList = res.reRankModels ?? reRankModelList;
|
||||
|
||||
audioSpeechModels = res.audioSpeechModels ?? audioSpeechModels;
|
||||
|
||||
feConfigs = res.feConfigs;
|
||||
priceMd = res.priceMd;
|
||||
|
@@ -23,7 +23,7 @@ const defaultChatData: InitChatResponse = {
|
||||
chatId: '',
|
||||
appId: '',
|
||||
app: {
|
||||
name: 'FastGPT',
|
||||
name: 'Loading',
|
||||
avatar: '/icon/logo.svg',
|
||||
intro: '',
|
||||
canUse: false
|
||||
|
3
python/README.md
Normal file
3
python/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 目录说明
|
||||
|
||||
该目录为 python 辅助代码,非主项目代码,仅供学习使用,未参与实际生产。
|
Reference in New Issue
Block a user