feat: overview setting
This commit is contained in:
parent
b7b20a353f
commit
fdcf53ea38
@ -650,9 +650,9 @@ const ChatBox = (
|
|||||||
<Box
|
<Box
|
||||||
py={'18px'}
|
py={'18px'}
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
boxShadow={`0 0 10px rgba(0,0,0,0.1)`}
|
boxShadow={`0 0 10px rgba(0,0,0,0.2)`}
|
||||||
borderTop={['1px solid', 0]}
|
borderTop={['1px solid', 0]}
|
||||||
borderTopColor={'gray.200'}
|
borderTopColor={'myGray.200'}
|
||||||
borderRadius={['none', 'md']}
|
borderRadius={['none', 'md']}
|
||||||
backgroundColor={'white'}
|
backgroundColor={'white'}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -9,6 +9,14 @@ import {
|
|||||||
} from './inputTemplate';
|
} from './inputTemplate';
|
||||||
import { rawSearchKey } from '../chat';
|
import { rawSearchKey } from '../chat';
|
||||||
|
|
||||||
|
export const ChatModelSystemTip =
|
||||||
|
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
|
||||||
|
export const ChatModelLimitTip =
|
||||||
|
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"';
|
||||||
|
export const userGuideTip = '可以添加特殊的对话前后引导模块,更好的让用户进行对话';
|
||||||
|
export const welcomeTextTip =
|
||||||
|
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
|
||||||
|
|
||||||
export const VariableModule: AppModuleTemplateItemType = {
|
export const VariableModule: AppModuleTemplateItemType = {
|
||||||
logo: '/imgs/module/variable.png',
|
logo: '/imgs/module/variable.png',
|
||||||
name: '全局变量',
|
name: '全局变量',
|
||||||
@ -30,7 +38,7 @@ export const VariableModule: AppModuleTemplateItemType = {
|
|||||||
export const UserGuideModule: AppModuleTemplateItemType = {
|
export const UserGuideModule: AppModuleTemplateItemType = {
|
||||||
logo: '/imgs/module/userGuide.png',
|
logo: '/imgs/module/userGuide.png',
|
||||||
name: '用户引导',
|
name: '用户引导',
|
||||||
intro: '可以添加特殊的对话前后引导模块,更好的让用户进行对话',
|
intro: userGuideTip,
|
||||||
type: AppModuleItemTypeEnum.userGuide,
|
type: AppModuleItemTypeEnum.userGuide,
|
||||||
flowType: FlowModuleTypeEnum.userGuide,
|
flowType: FlowModuleTypeEnum.userGuide,
|
||||||
inputs: [
|
inputs: [
|
||||||
@ -77,7 +85,7 @@ export const HistoryModule: AppModuleTemplateItemType = {
|
|||||||
key: 'maxContext',
|
key: 'maxContext',
|
||||||
type: FlowInputItemTypeEnum.numberInput,
|
type: FlowInputItemTypeEnum.numberInput,
|
||||||
label: '最长记录数',
|
label: '最长记录数',
|
||||||
value: 4,
|
value: 6,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 50
|
max: 50
|
||||||
},
|
},
|
||||||
@ -146,20 +154,16 @@ export const ChatModule: AppModuleTemplateItemType = {
|
|||||||
key: 'systemPrompt',
|
key: 'systemPrompt',
|
||||||
type: FlowInputItemTypeEnum.textarea,
|
type: FlowInputItemTypeEnum.textarea,
|
||||||
label: '系统提示词',
|
label: '系统提示词',
|
||||||
description:
|
description: ChatModelSystemTip,
|
||||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
|
placeholder: ChatModelSystemTip,
|
||||||
placeholder:
|
|
||||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
|
|
||||||
value: ''
|
value: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'limitPrompt',
|
key: 'limitPrompt',
|
||||||
type: FlowInputItemTypeEnum.textarea,
|
type: FlowInputItemTypeEnum.textarea,
|
||||||
label: '限定词',
|
label: '限定词',
|
||||||
description:
|
description: ChatModelLimitTip,
|
||||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
placeholder: ChatModelLimitTip,
|
||||||
placeholder:
|
|
||||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
|
||||||
value: ''
|
value: ''
|
||||||
},
|
},
|
||||||
// Input_Template_TFSwitch,
|
// Input_Template_TFSwitch,
|
||||||
@ -191,7 +195,7 @@ export const KBSearchModule: AppModuleTemplateItemType = {
|
|||||||
url: '/app/modules/kb/search',
|
url: '/app/modules/kb/search',
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
key: 'kb_ids',
|
key: 'kbList',
|
||||||
type: FlowInputItemTypeEnum.custom,
|
type: FlowInputItemTypeEnum.custom,
|
||||||
label: '关联的知识库',
|
label: '关联的知识库',
|
||||||
value: [],
|
value: [],
|
||||||
@ -548,7 +552,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
key: 'maxContext',
|
key: 'maxContext',
|
||||||
type: 'numberInput',
|
type: 'numberInput',
|
||||||
label: '最长记录数',
|
label: '最长记录数',
|
||||||
value: 4,
|
value: 10,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 50,
|
max: 50,
|
||||||
connected: false
|
connected: false
|
||||||
@ -627,7 +631,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
key: 'maxContext',
|
key: 'maxContext',
|
||||||
type: 'numberInput',
|
type: 'numberInput',
|
||||||
label: '最长记录数',
|
label: '最长记录数',
|
||||||
value: 4,
|
value: 10,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 50,
|
max: 50,
|
||||||
connected: false
|
connected: false
|
||||||
@ -788,7 +792,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
...KBSearchModule,
|
...KBSearchModule,
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
key: 'kb_ids',
|
key: 'kbList',
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
label: '关联的知识库',
|
label: '关联的知识库',
|
||||||
value: [],
|
value: [],
|
||||||
@ -1100,7 +1104,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
key: 'maxContext',
|
key: 'maxContext',
|
||||||
type: 'numberInput',
|
type: 'numberInput',
|
||||||
label: '最长记录数',
|
label: '最长记录数',
|
||||||
value: 4,
|
value: 10,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 50,
|
max: 50,
|
||||||
connected: false
|
connected: false
|
||||||
@ -1239,7 +1243,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
key: 'maxContext',
|
key: 'maxContext',
|
||||||
type: 'numberInput',
|
type: 'numberInput',
|
||||||
label: '最长记录数',
|
label: '最长记录数',
|
||||||
value: 4,
|
value: 10,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 50,
|
max: 50,
|
||||||
connected: false
|
connected: false
|
||||||
@ -1400,7 +1404,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
|||||||
...KBSearchModule,
|
...KBSearchModule,
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
key: 'kb_ids',
|
key: 'kbList',
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
label: '关联的知识库',
|
label: '关联的知识库',
|
||||||
value: [],
|
value: [],
|
||||||
|
|||||||
@ -163,6 +163,9 @@ const Textarea: ComponentStyleConfig = {
|
|||||||
border: '1px solid',
|
border: '1px solid',
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
borderColor: 'myGray.200',
|
borderColor: 'myGray.200',
|
||||||
|
_hover: {
|
||||||
|
borderColor: ''
|
||||||
|
},
|
||||||
_focus: {
|
_focus: {
|
||||||
borderColor: 'myBlue.600',
|
borderColor: 'myBlue.600',
|
||||||
boxShadow: '0px 0px 4px #A8DBFF',
|
boxShadow: '0px 0px 4px #A8DBFF',
|
||||||
|
|||||||
@ -52,6 +52,49 @@ const chatTemplate = ({
|
|||||||
},
|
},
|
||||||
moduleId: '7z5g5h'
|
moduleId: '7z5g5h'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
logo: '/imgs/module/history.png',
|
||||||
|
name: '聊天记录',
|
||||||
|
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||||
|
type: 'initInput',
|
||||||
|
flowType: 'historyNode',
|
||||||
|
url: '/app/modules/init/history',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'maxContext',
|
||||||
|
type: 'numberInput',
|
||||||
|
label: '最长记录数',
|
||||||
|
value: 4,
|
||||||
|
min: 0,
|
||||||
|
max: 50,
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
type: 'hidden',
|
||||||
|
label: '聊天记录',
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
label: '聊天记录',
|
||||||
|
type: 'source',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
moduleId: '7pacf0',
|
||||||
|
key: 'history'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 452.5466249541586,
|
||||||
|
y: 1276.3930310334215
|
||||||
|
},
|
||||||
|
moduleId: 'xj0c9p'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
logo: '/imgs/module/AI.png',
|
logo: '/imgs/module/AI.png',
|
||||||
name: 'AI 对话',
|
name: 'AI 对话',
|
||||||
@ -176,49 +219,6 @@ const chatTemplate = ({
|
|||||||
y: 890.014595014464
|
y: 890.014595014464
|
||||||
},
|
},
|
||||||
moduleId: '7pacf0'
|
moduleId: '7pacf0'
|
||||||
},
|
|
||||||
{
|
|
||||||
logo: '/imgs/module/history.png',
|
|
||||||
name: '聊天记录',
|
|
||||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
|
||||||
type: 'initInput',
|
|
||||||
flowType: 'historyNode',
|
|
||||||
url: '/app/modules/init/history',
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
key: 'maxContext',
|
|
||||||
type: 'numberInput',
|
|
||||||
label: '最长记录数',
|
|
||||||
value: 4,
|
|
||||||
min: 0,
|
|
||||||
max: 50,
|
|
||||||
connected: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'history',
|
|
||||||
type: 'hidden',
|
|
||||||
label: '聊天记录',
|
|
||||||
connected: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
key: 'history',
|
|
||||||
label: '聊天记录',
|
|
||||||
type: 'source',
|
|
||||||
targets: [
|
|
||||||
{
|
|
||||||
moduleId: '7pacf0',
|
|
||||||
key: 'history'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
position: {
|
|
||||||
x: 452.5466249541586,
|
|
||||||
y: 1276.3930310334215
|
|
||||||
},
|
|
||||||
moduleId: 'xj0c9p'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@ -228,7 +228,7 @@ const kbTemplate = ({
|
|||||||
maxToken,
|
maxToken,
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
limitPrompt,
|
limitPrompt,
|
||||||
kbs = [],
|
kbList = [],
|
||||||
searchSimilarity,
|
searchSimilarity,
|
||||||
searchLimit,
|
searchLimit,
|
||||||
searchEmptyText
|
searchEmptyText
|
||||||
@ -238,7 +238,7 @@ const kbTemplate = ({
|
|||||||
maxToken: number;
|
maxToken: number;
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
limitPrompt: string;
|
limitPrompt: string;
|
||||||
kbs: string[];
|
kbList: { kbId: string }[];
|
||||||
searchSimilarity: number;
|
searchSimilarity: number;
|
||||||
searchLimit: number;
|
searchLimit: number;
|
||||||
searchEmptyText: string;
|
searchEmptyText: string;
|
||||||
@ -446,10 +446,10 @@ const kbTemplate = ({
|
|||||||
url: '/app/modules/kb/search',
|
url: '/app/modules/kb/search',
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
key: 'kb_ids',
|
key: 'kbList',
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
label: '关联的知识库',
|
label: '关联的知识库',
|
||||||
value: kbs,
|
value: kbList,
|
||||||
list: [],
|
list: [],
|
||||||
connected: false
|
connected: false
|
||||||
},
|
},
|
||||||
@ -588,54 +588,24 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
const { limit = 1000 } = req.body as { limit: number };
|
const { limit = 1000 } = req.body as { limit: number };
|
||||||
|
let skip = 0;
|
||||||
|
const total = await App.countDocuments();
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
console.log(total);
|
||||||
|
|
||||||
// 遍历所有的 app
|
for (let i = 0; i < total; i += limit) {
|
||||||
const apps = await App.find(
|
const skipVal = skip;
|
||||||
{
|
skip += limit;
|
||||||
chat: { $ne: null },
|
promise = promise
|
||||||
modules: { $exists: false }
|
.then(() => init(limit, skipVal))
|
||||||
// userId: '63f9a14228d2a688d8dc9e1b'
|
.then(() => {
|
||||||
},
|
console.log(skipVal);
|
||||||
'_id chat'
|
|
||||||
).limit(limit);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
apps.map(async (app) => {
|
|
||||||
if (!app.chat) return app;
|
|
||||||
const modules = (() => {
|
|
||||||
if (app.chat.relatedKbs.length === 0) {
|
|
||||||
return chatTemplate({
|
|
||||||
model: app.chat.chatModel,
|
|
||||||
temperature: app.chat.temperature,
|
|
||||||
maxToken: app.chat.maxToken,
|
|
||||||
systemPrompt: app.chat.systemPrompt,
|
|
||||||
limitPrompt: app.chat.limitPrompt
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return kbTemplate({
|
|
||||||
model: app.chat.chatModel,
|
|
||||||
temperature: app.chat.temperature,
|
|
||||||
maxToken: app.chat.maxToken,
|
|
||||||
systemPrompt: app.chat.systemPrompt,
|
|
||||||
limitPrompt: app.chat.limitPrompt,
|
|
||||||
kbs: app.chat.relatedKbs,
|
|
||||||
searchEmptyText: app.chat.searchEmptyText,
|
|
||||||
searchLimit: app.chat.searchLimit,
|
|
||||||
searchSimilarity: app.chat.searchSimilarity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
await App.findByIdAndUpdate(app.id, {
|
|
||||||
modules
|
|
||||||
});
|
});
|
||||||
return modules;
|
}
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
jsonRes(res, {
|
await promise;
|
||||||
data: apps.length
|
|
||||||
});
|
jsonRes(res, {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
code: 500,
|
code: 500,
|
||||||
@ -643,3 +613,51 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function init(limit: number, skip: number) {
|
||||||
|
// 遍历 app
|
||||||
|
const apps = await App.find(
|
||||||
|
{
|
||||||
|
chat: { $ne: null }
|
||||||
|
// modules: { $exists: false },
|
||||||
|
// userId: '63f9a14228d2a688d8dc9e1b'
|
||||||
|
},
|
||||||
|
'_id chat'
|
||||||
|
)
|
||||||
|
.limit(limit)
|
||||||
|
.skip(skip);
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
apps.map(async (app) => {
|
||||||
|
if (!app.chat) return app;
|
||||||
|
const modules = (() => {
|
||||||
|
if (app.chat.relatedKbs.length === 0) {
|
||||||
|
return chatTemplate({
|
||||||
|
model: app.chat.chatModel,
|
||||||
|
temperature: app.chat.temperature,
|
||||||
|
maxToken: app.chat.maxToken,
|
||||||
|
systemPrompt: app.chat.systemPrompt,
|
||||||
|
limitPrompt: app.chat.limitPrompt
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return kbTemplate({
|
||||||
|
model: app.chat.chatModel,
|
||||||
|
temperature: app.chat.temperature,
|
||||||
|
maxToken: app.chat.maxToken,
|
||||||
|
systemPrompt: app.chat.systemPrompt,
|
||||||
|
limitPrompt: app.chat.limitPrompt,
|
||||||
|
kbList: app.chat.relatedKbs.map((id) => ({ kbId: id })),
|
||||||
|
searchEmptyText: app.chat.searchEmptyText,
|
||||||
|
searchLimit: app.chat.searchLimit,
|
||||||
|
searchSimilarity: app.chat.searchSimilarity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
await App.findByIdAndUpdate(app.id, {
|
||||||
|
modules
|
||||||
|
});
|
||||||
|
return modules;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { getVector } from '@/pages/api/openapi/plugin/vector';
|
|||||||
import { countModelPrice, pushTaskBillListItem } from '@/service/events/pushBill';
|
import { countModelPrice, pushTaskBillListItem } from '@/service/events/pushBill';
|
||||||
import { getModel } from '@/service/utils/data';
|
import { getModel } from '@/service/utils/data';
|
||||||
import { authUser } from '@/service/utils/auth';
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
import type { SelectedKbType } from '@/types/plugin';
|
||||||
|
|
||||||
export type QuoteItemType = {
|
export type QuoteItemType = {
|
||||||
kb_id: string;
|
kb_id: string;
|
||||||
@ -18,7 +19,7 @@ export type QuoteItemType = {
|
|||||||
source?: string;
|
source?: string;
|
||||||
};
|
};
|
||||||
type Props = {
|
type Props = {
|
||||||
kb_ids: string[];
|
kbList: SelectedKbType;
|
||||||
history: ChatItemType[];
|
history: ChatItemType[];
|
||||||
similarity: number;
|
similarity: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
@ -37,19 +38,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
try {
|
try {
|
||||||
await authUser({ req, authRoot: true });
|
await authUser({ req, authRoot: true });
|
||||||
|
|
||||||
const { kb_ids = [], userChatInput } = req.body as Props;
|
const { kbList = [], userChatInput } = req.body as Props;
|
||||||
|
|
||||||
if (!userChatInput) {
|
if (!userChatInput) {
|
||||||
throw new Error('用户输入为空');
|
throw new Error('用户输入为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(kb_ids) || kb_ids.length === 0) {
|
if (!Array.isArray(kbList) || kbList.length === 0) {
|
||||||
throw new Error('没有选择知识库');
|
throw new Error('没有选择知识库');
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await kbSearch({
|
const result = await kbSearch({
|
||||||
...req.body,
|
...req.body,
|
||||||
kb_ids,
|
kbList,
|
||||||
userChatInput
|
userChatInput
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function kbSearch({
|
export async function kbSearch({
|
||||||
kb_ids = [],
|
kbList = [],
|
||||||
history = [],
|
history = [],
|
||||||
similarity = 0.8,
|
similarity = 0.8,
|
||||||
limit = 5,
|
limit = 5,
|
||||||
@ -74,12 +75,9 @@ export async function kbSearch({
|
|||||||
userChatInput,
|
userChatInput,
|
||||||
billId
|
billId
|
||||||
}: Props): Promise<Response> {
|
}: Props): Promise<Response> {
|
||||||
if (kb_ids.length === 0)
|
if (kbList.length === 0) {
|
||||||
return {
|
return Promise.reject('没有选择知识库');
|
||||||
isEmpty: true,
|
}
|
||||||
rawSearch: [],
|
|
||||||
quotePrompt: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
// get vector
|
// get vector
|
||||||
const vectorModel = global.vectorModels[0].model;
|
const vectorModel = global.vectorModels[0].model;
|
||||||
@ -93,8 +91,8 @@ export async function kbSearch({
|
|||||||
PgClient.query(
|
PgClient.query(
|
||||||
`BEGIN;
|
`BEGIN;
|
||||||
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
|
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
|
||||||
select kb_id,id,q,a,source from modelData where kb_id IN (${kb_ids
|
select kb_id,id,q,a,source from modelData where kb_id IN (${kbList
|
||||||
.map((item) => `'${item}'`)
|
.map((item) => `'${item.kbId}'`)
|
||||||
.join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${
|
.join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${
|
||||||
vectors[0]
|
vectors[0]
|
||||||
}]' limit ${limit};
|
}]' limit ${limit};
|
||||||
|
|||||||
527
client/src/pages/app/detail/components/BasicEdit/index.tsx
Normal file
527
client/src/pages/app/detail/components/BasicEdit/index.tsx
Normal file
@ -0,0 +1,527 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Grid,
|
||||||
|
BoxProps,
|
||||||
|
Textarea,
|
||||||
|
useTheme,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
TableContainer,
|
||||||
|
useDisclosure,
|
||||||
|
Button,
|
||||||
|
IconButton
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||||
|
import { useForm, useFieldArray } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
appModules2Form,
|
||||||
|
getDefaultAppForm,
|
||||||
|
appForm2Modules,
|
||||||
|
type EditFormType
|
||||||
|
} from '@/utils/app';
|
||||||
|
import { chatModelList } from '@/store/static';
|
||||||
|
import { formatPrice } from '@/utils/user';
|
||||||
|
import {
|
||||||
|
ChatModelSystemTip,
|
||||||
|
ChatModelLimitTip,
|
||||||
|
welcomeTextTip
|
||||||
|
} from '@/constants/flow/ModuleTemplate';
|
||||||
|
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||||
|
import { useRequest } from '@/hooks/useRequest';
|
||||||
|
import { useConfirm } from '@/hooks/useConfirm';
|
||||||
|
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||||
|
import { streamFetch } from '@/api/fetch';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import MySelect from '@/components/Select';
|
||||||
|
import MySlider from '@/components/Slider';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
import ChatBox, {
|
||||||
|
getSpecialModule,
|
||||||
|
type ComponentRef,
|
||||||
|
type StartChatFnProps
|
||||||
|
} from '@/components/ChatBox';
|
||||||
|
import { addVariable } from '../VariableEditModal';
|
||||||
|
import { KBSelectModal, KbParamsModal } from '../KBSelectModal';
|
||||||
|
|
||||||
|
const VariableEditModal = dynamic(() => import('../VariableEditModal'));
|
||||||
|
|
||||||
|
const Settings = ({ appId }: { appId: string }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { appDetail, updateAppDetail, loadKbList, myKbList } = useUserStore();
|
||||||
|
|
||||||
|
const [editVariable, setEditVariable] = useState<VariableItemType>();
|
||||||
|
|
||||||
|
useQuery(['initkb', appId], () => loadKbList());
|
||||||
|
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
||||||
|
const { openConfirm, ConfirmChild } = useConfirm({
|
||||||
|
title: '警告',
|
||||||
|
content: '保存后将会覆盖高级编排配置,请确保该应用未使用高级编排功能。'
|
||||||
|
});
|
||||||
|
const { register, setValue, getValues, reset, handleSubmit, control } = useForm<EditFormType>({
|
||||||
|
defaultValues: getDefaultAppForm()
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
fields: variables,
|
||||||
|
append: appendVariable,
|
||||||
|
remove: removeVariable,
|
||||||
|
replace: replaceVariables
|
||||||
|
} = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: 'variables'
|
||||||
|
});
|
||||||
|
const { fields: kbList, replace: replaceKbList } = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: 'kb.list'
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen: isOpenKbSelect,
|
||||||
|
onOpen: onOpenKbSelect,
|
||||||
|
onClose: onCloseKbSelect
|
||||||
|
} = useDisclosure();
|
||||||
|
const {
|
||||||
|
isOpen: isOpenKbParams,
|
||||||
|
onOpen: onOpenKbParams,
|
||||||
|
onClose: onCloseKbParams
|
||||||
|
} = useDisclosure();
|
||||||
|
|
||||||
|
const chatModelSelectList = useMemo(() => {
|
||||||
|
return chatModelList.map((item) => ({
|
||||||
|
value: item.model,
|
||||||
|
label: `${item.name} (${formatPrice(item.price, 1000)} 元/1k tokens)`
|
||||||
|
}));
|
||||||
|
}, [refresh]);
|
||||||
|
const tokenLimit = useMemo(() => {
|
||||||
|
return (
|
||||||
|
chatModelList.find((item) => item.model === getValues('chatModel.model'))?.contextMaxToken ||
|
||||||
|
4000
|
||||||
|
);
|
||||||
|
}, [getValues, refresh]);
|
||||||
|
const selectedKbList = useMemo(
|
||||||
|
() => myKbList.filter((item) => kbList.find((kb) => kb.kbId === item._id)),
|
||||||
|
[myKbList, kbList]
|
||||||
|
);
|
||||||
|
|
||||||
|
const appModule2Form = useCallback(() => {
|
||||||
|
const formVal = appModules2Form(appDetail.modules);
|
||||||
|
reset(formVal);
|
||||||
|
setRefresh((state) => !state);
|
||||||
|
}, [appDetail.modules, reset]);
|
||||||
|
|
||||||
|
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
|
||||||
|
mutationFn: async (data: EditFormType) => {
|
||||||
|
const modules = appForm2Modules(data);
|
||||||
|
|
||||||
|
await updateAppDetail(appDetail._id, {
|
||||||
|
modules
|
||||||
|
});
|
||||||
|
},
|
||||||
|
successToast: '保存成功',
|
||||||
|
errorToast: '保存出现异常'
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
appModule2Form();
|
||||||
|
}, [appModule2Form]);
|
||||||
|
|
||||||
|
const BoxStyles: BoxProps = {
|
||||||
|
bg: 'myWhite.300',
|
||||||
|
px: 4,
|
||||||
|
py: 3,
|
||||||
|
borderRadius: 'lg',
|
||||||
|
border: theme.borders.base
|
||||||
|
};
|
||||||
|
const BoxBtnStyles: BoxProps = {
|
||||||
|
cursor: 'pointer',
|
||||||
|
px: 3,
|
||||||
|
py: '2px',
|
||||||
|
borderRadius: 'md',
|
||||||
|
_hover: {
|
||||||
|
bg: 'myGray.200'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
flexDirection={'column'}
|
||||||
|
h={'100%'}
|
||||||
|
borderRight={'1.5px solid'}
|
||||||
|
borderColor={'myGray.200'}
|
||||||
|
pt={4}
|
||||||
|
>
|
||||||
|
<Flex overflowY={'auto'} pr={4} justifyContent={'space-between'}>
|
||||||
|
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
|
||||||
|
应用配置
|
||||||
|
<MyTooltip label={'仅包含基础功能,复杂 agent 功能请使用高级编排。'}>
|
||||||
|
<QuestionOutlineIcon ml={2} fontSize={'md'} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
isLoading={isSaving}
|
||||||
|
fontSize={'sm'}
|
||||||
|
onClick={openConfirm(handleSubmit((data) => onSubmitSave(data)))}
|
||||||
|
>
|
||||||
|
保存并预览
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Box flex={'1 0 0'} my={4} pr={4} overflowY={'auto'}>
|
||||||
|
{/* variable */}
|
||||||
|
<Box {...BoxStyles}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Avatar src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
|
||||||
|
<Box ml={2} flex={1}>
|
||||||
|
变量
|
||||||
|
</Box>
|
||||||
|
<Flex {...BoxBtnStyles} onClick={() => setEditVariable(addVariable())}>
|
||||||
|
+ 新增
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Box
|
||||||
|
mt={2}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
overflow={'hidden'}
|
||||||
|
borderWidth={'1px'}
|
||||||
|
borderBottom="none"
|
||||||
|
>
|
||||||
|
<TableContainer>
|
||||||
|
<Table bg={'white'}>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>变量名</Th>
|
||||||
|
<Th>变量 key</Th>
|
||||||
|
<Th>必填</Th>
|
||||||
|
<Th></Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{variables.map((item, index) => (
|
||||||
|
<Tr key={item.id}>
|
||||||
|
<Td>{item.label} </Td>
|
||||||
|
<Td>{item.key}</Td>
|
||||||
|
<Td>{item.required ? '✔' : ''}</Td>
|
||||||
|
<Td>
|
||||||
|
<MyIcon
|
||||||
|
mr={3}
|
||||||
|
name={'settingLight'}
|
||||||
|
w={'16px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => setEditVariable(item)}
|
||||||
|
/>
|
||||||
|
<MyIcon
|
||||||
|
name={'delete'}
|
||||||
|
w={'16px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => removeVariable(index)}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box mt={5} {...BoxStyles}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
|
||||||
|
<Box ml={2}>AI 配置</Box>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex alignItems={'center'} mt={5}>
|
||||||
|
<Box w={['60px', '100px']} flexShrink={0}>
|
||||||
|
对话模型
|
||||||
|
</Box>
|
||||||
|
<MySelect
|
||||||
|
width={['100%', '300px']}
|
||||||
|
value={getValues('chatModel.model')}
|
||||||
|
list={chatModelSelectList}
|
||||||
|
onchange={(val: any) => {
|
||||||
|
setValue('chatModel.model', val);
|
||||||
|
const maxToken =
|
||||||
|
chatModelList.find((item) => item.model === getValues('chatModel.model'))
|
||||||
|
?.contextMaxToken || 4000;
|
||||||
|
const token = maxToken / 2;
|
||||||
|
setValue('chatModel.maxToken', token);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} my={10}>
|
||||||
|
<Box w={['60px', '100px']} flexShrink={0}>
|
||||||
|
温度
|
||||||
|
</Box>
|
||||||
|
<Box flex={1} ml={'10px'}>
|
||||||
|
<MySlider
|
||||||
|
markList={[
|
||||||
|
{ label: '严谨', value: 0 },
|
||||||
|
{ label: '发散', value: 10 }
|
||||||
|
]}
|
||||||
|
width={'95%'}
|
||||||
|
min={0}
|
||||||
|
max={10}
|
||||||
|
value={getValues('chatModel.temperature')}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue('chatModel.temperature', e);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mt={12} mb={10}>
|
||||||
|
<Box w={['60px', '100px']} flexShrink={0}>
|
||||||
|
回复上限
|
||||||
|
</Box>
|
||||||
|
<Box flex={1} ml={'10px'}>
|
||||||
|
<MySlider
|
||||||
|
markList={[
|
||||||
|
{ label: '100', value: 100 },
|
||||||
|
{ label: `${tokenLimit}`, value: tokenLimit }
|
||||||
|
]}
|
||||||
|
width={'95%'}
|
||||||
|
min={100}
|
||||||
|
max={tokenLimit}
|
||||||
|
step={50}
|
||||||
|
value={getValues('chatModel.maxToken')}
|
||||||
|
onChange={(val) => {
|
||||||
|
setValue('chatModel.maxToken', val);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={10} alignItems={'flex-start'}>
|
||||||
|
<Box w={['60px', '100px']} flexShrink={0}>
|
||||||
|
提示词
|
||||||
|
<MyTooltip label={ChatModelSystemTip}>
|
||||||
|
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Box>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
placeholder={ChatModelSystemTip}
|
||||||
|
borderColor={'myGray.100'}
|
||||||
|
{...register('chatModel.systemPrompt')}
|
||||||
|
></Textarea>
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={5} alignItems={'flex-start'}>
|
||||||
|
<Box w={['60px', '100px']} flexShrink={0}>
|
||||||
|
限定词
|
||||||
|
<MyTooltip label={ChatModelLimitTip}>
|
||||||
|
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Box>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
placeholder={ChatModelLimitTip}
|
||||||
|
borderColor={'myGray.100'}
|
||||||
|
{...register('chatModel.limitPrompt')}
|
||||||
|
></Textarea>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* kb */}
|
||||||
|
<Box mt={5} {...BoxStyles}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Flex alignItems={'center'} flex={1}>
|
||||||
|
<Avatar src={'/imgs/module/db.png'} w={'18px'} />
|
||||||
|
<Box ml={2}>知识库</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mr={3} {...BoxBtnStyles} onClick={onOpenKbSelect}>
|
||||||
|
<SmallAddIcon />
|
||||||
|
选择
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbParams}>
|
||||||
|
<MyIcon name={'edit'} w={'14px'} mr={1} />
|
||||||
|
参数
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
|
||||||
|
相似度: {getValues('kb.searchSimilarity')}, 单次搜索数量: {getValues('kb.searchLimit')},
|
||||||
|
空搜索时拒绝回复: {getValues('kb.searchEmptyText') !== '' ? 'true' : 'false'}
|
||||||
|
</Flex>
|
||||||
|
<Grid templateColumns={['1fr', 'repeat(2,1fr)']} my={2} gridGap={[2, 4]}>
|
||||||
|
{selectedKbList.map((item) => (
|
||||||
|
<Flex
|
||||||
|
key={item._id}
|
||||||
|
alignItems={'center'}
|
||||||
|
p={2}
|
||||||
|
bg={'white'}
|
||||||
|
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
border={theme.borders.base}
|
||||||
|
>
|
||||||
|
<Avatar src={item.avatar} w={'18px'} mr={1} />
|
||||||
|
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* welcome */}
|
||||||
|
<Box mt={5} {...BoxStyles}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
|
||||||
|
<Box mx={2}>对话开场白</Box>
|
||||||
|
<MyTooltip label={welcomeTextTip}>
|
||||||
|
<QuestionOutlineIcon />
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Textarea
|
||||||
|
mt={2}
|
||||||
|
rows={5}
|
||||||
|
placeholder={welcomeTextTip}
|
||||||
|
borderColor={'myGray.100'}
|
||||||
|
{...register('guide.welcome.text')}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<ConfirmChild />
|
||||||
|
{editVariable && (
|
||||||
|
<VariableEditModal
|
||||||
|
defaultVariable={editVariable}
|
||||||
|
onClose={() => setEditVariable(undefined)}
|
||||||
|
onSubmit={({ variable }) => {
|
||||||
|
const record = variables.find((item) => item.id === variable.id);
|
||||||
|
if (record) {
|
||||||
|
replaceVariables(
|
||||||
|
variables.map((item) => (item.id === variable.id ? variable : item))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
appendVariable(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditVariable(undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isOpenKbSelect && (
|
||||||
|
<KBSelectModal
|
||||||
|
kbList={myKbList}
|
||||||
|
activeKbs={selectedKbList.map((item) => ({ kbId: item._id }))}
|
||||||
|
onClose={onCloseKbSelect}
|
||||||
|
onChange={replaceKbList}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isOpenKbParams && (
|
||||||
|
<KbParamsModal
|
||||||
|
searchEmptyText={getValues('kb.searchEmptyText')}
|
||||||
|
searchLimit={getValues('kb.searchLimit')}
|
||||||
|
searchSimilarity={getValues('kb.searchSimilarity')}
|
||||||
|
onClose={onCloseKbParams}
|
||||||
|
onChange={({ searchEmptyText, searchLimit, searchSimilarity }) => {
|
||||||
|
setValue('kb.searchEmptyText', searchEmptyText);
|
||||||
|
setValue('kb.searchLimit', searchLimit);
|
||||||
|
setValue('kb.searchSimilarity', searchSimilarity);
|
||||||
|
setRefresh((state) => !state);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChatTest = ({ appId }: { appId: string }) => {
|
||||||
|
const { appDetail } = useUserStore();
|
||||||
|
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||||
|
const [modules, setModules] = useState<AppModuleItemType[]>([]);
|
||||||
|
|
||||||
|
const startChat = useCallback(
|
||||||
|
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||||
|
const historyMaxLen =
|
||||||
|
modules
|
||||||
|
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
|
||||||
|
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0;
|
||||||
|
const history = messages.slice(-historyMaxLen - 2, -2);
|
||||||
|
|
||||||
|
// 流请求,获取数据
|
||||||
|
const { responseText, rawSearch } = await streamFetch({
|
||||||
|
url: '/api/chat/chatTest',
|
||||||
|
data: {
|
||||||
|
history,
|
||||||
|
prompt: messages[messages.length - 2].content,
|
||||||
|
modules,
|
||||||
|
variables,
|
||||||
|
appId,
|
||||||
|
appName: `调试-${appDetail.name}`
|
||||||
|
},
|
||||||
|
onMessage: generatingMessage,
|
||||||
|
abortSignal: controller
|
||||||
|
});
|
||||||
|
|
||||||
|
return { responseText, rawSearch };
|
||||||
|
},
|
||||||
|
[modules, appId, appDetail.name]
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetChatBox = useCallback(() => {
|
||||||
|
ChatBoxRef.current?.resetHistory([]);
|
||||||
|
ChatBoxRef.current?.resetVariables();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const formVal = appModules2Form(appDetail.modules);
|
||||||
|
setModules(appForm2Modules(formVal));
|
||||||
|
resetChatBox();
|
||||||
|
}, [appDetail, resetChatBox]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDirection={'column'} h={'100%'} pl={4} py={4}>
|
||||||
|
<Flex>
|
||||||
|
<Box fontSize={['md', 'xl']} fontWeight={'bold'} flex={1}>
|
||||||
|
调试预览
|
||||||
|
</Box>
|
||||||
|
<MyTooltip label={'重置'}>
|
||||||
|
<IconButton
|
||||||
|
className="chat"
|
||||||
|
size={'sm'}
|
||||||
|
icon={<MyIcon name={'clearLight'} w={'14px'} />}
|
||||||
|
variant={'base'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
aria-label={'delete'}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
resetChatBox();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Box flex={1}>
|
||||||
|
<ChatBox
|
||||||
|
ref={ChatBoxRef}
|
||||||
|
appAvatar={appDetail.avatar}
|
||||||
|
{...getSpecialModule(modules)}
|
||||||
|
onStartChat={startChat}
|
||||||
|
onDelMessage={() => {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BasicEdit = ({ appId }: { appId: string }) => {
|
||||||
|
return (
|
||||||
|
<Grid gridTemplateColumns={['1fr', '550px 1fr']} h={'100%'}>
|
||||||
|
<Settings appId={appId} />
|
||||||
|
<ChatTest appId={appId} />
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasicEdit;
|
||||||
@ -192,7 +192,7 @@ const TokenUsage = ({ appId }: { appId: string }) => {
|
|||||||
}, [screenWidth]);
|
}, [screenWidth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={Dom} w={'100%'} flex={'1 0 0'} h={'100%'} position={'relative'}>
|
<Box ref={Dom} w={'100%'} flex={'1 0 0'} h={'100%'} minH={'150px'} position={'relative'}>
|
||||||
<Loading fixed={false} />
|
<Loading fixed={false} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,12 +1,73 @@
|
|||||||
import React from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { NodeProps } from 'reactflow';
|
import { NodeProps } from 'reactflow';
|
||||||
import NodeCard from '../modules/NodeCard';
|
|
||||||
import { FlowModuleItemType } from '@/types/flow';
|
import { FlowModuleItemType } from '@/types/flow';
|
||||||
|
import { Flex, Box, Button, useTheme, useDisclosure, Grid } from '@chakra-ui/react';
|
||||||
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import NodeCard from '../modules/NodeCard';
|
||||||
import Divider from '../modules/Divider';
|
import Divider from '../modules/Divider';
|
||||||
import Container from '../modules/Container';
|
import Container from '../modules/Container';
|
||||||
import RenderInput from '../render/RenderInput';
|
import RenderInput from '../render/RenderInput';
|
||||||
import RenderOutput from '../render/RenderOutput';
|
import RenderOutput from '../render/RenderOutput';
|
||||||
import KBSelect from '../Plugins/KBSelect';
|
import { KBSelectModal } from '../../../KBSelectModal';
|
||||||
|
import type { SelectedKbType } from '@/types/plugin';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
|
||||||
|
const KBSelect = ({
|
||||||
|
activeKbs = [],
|
||||||
|
onChange
|
||||||
|
}: {
|
||||||
|
activeKbs: SelectedKbType;
|
||||||
|
onChange: (e: SelectedKbType) => void;
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { myKbList, loadKbList } = useUserStore();
|
||||||
|
const {
|
||||||
|
isOpen: isOpenKbSelect,
|
||||||
|
onOpen: onOpenKbSelect,
|
||||||
|
onClose: onCloseKbSelect
|
||||||
|
} = useDisclosure();
|
||||||
|
|
||||||
|
const showKbList = useMemo(
|
||||||
|
() => myKbList.filter((item) => activeKbs.find((kb) => kb.kbId === item._id)),
|
||||||
|
[myKbList, activeKbs]
|
||||||
|
);
|
||||||
|
|
||||||
|
useQuery(['initkb'], loadKbList);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid gridTemplateColumns={'1fr 1fr'} gridGap={4}>
|
||||||
|
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||||
|
选择知识库
|
||||||
|
</Button>
|
||||||
|
{showKbList.map((item) => (
|
||||||
|
<Flex
|
||||||
|
key={item._id}
|
||||||
|
alignItems={'center'}
|
||||||
|
h={'36px'}
|
||||||
|
border={theme.borders.base}
|
||||||
|
px={2}
|
||||||
|
borderRadius={'md'}
|
||||||
|
>
|
||||||
|
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
||||||
|
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
{isOpenKbSelect && (
|
||||||
|
<KBSelectModal
|
||||||
|
kbList={myKbList}
|
||||||
|
activeKbs={activeKbs}
|
||||||
|
onChange={onChange}
|
||||||
|
onClose={onCloseKbSelect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const NodeKbSearch = ({
|
const NodeKbSearch = ({
|
||||||
data: { moduleId, inputs, outputs, onChangeNode, ...props }
|
data: { moduleId, inputs, outputs, onChangeNode, ...props }
|
||||||
@ -20,9 +81,9 @@ const NodeKbSearch = ({
|
|||||||
onChangeNode={onChangeNode}
|
onChangeNode={onChangeNode}
|
||||||
flowInputList={inputs}
|
flowInputList={inputs}
|
||||||
CustomComponent={{
|
CustomComponent={{
|
||||||
kb_ids: ({ key, value }) => (
|
kbList: ({ key, value }) => (
|
||||||
<KBSelect
|
<KBSelect
|
||||||
relatedKbs={value}
|
activeKbs={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChangeNode({
|
onChangeNode({
|
||||||
moduleId,
|
moduleId,
|
||||||
|
|||||||
@ -7,12 +7,8 @@ import { FlowModuleItemType } from '@/types/flow';
|
|||||||
import Container from '../modules/Container';
|
import Container from '../modules/Container';
|
||||||
import { SystemInputEnum } from '@/constants/app';
|
import { SystemInputEnum } from '@/constants/app';
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import { customAlphabet } from 'nanoid';
|
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
|
||||||
import MyTooltip from '@/components/MyTooltip';
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
import { welcomeTextTip } from '@/constants/flow/ModuleTemplate';
|
||||||
const welcomePlaceholder =
|
|
||||||
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
|
|
||||||
|
|
||||||
const NodeUserGuide = ({
|
const NodeUserGuide = ({
|
||||||
data: { inputs, outputs, onChangeNode, ...props }
|
data: { inputs, outputs, onChangeNode, ...props }
|
||||||
@ -30,7 +26,7 @@ const NodeUserGuide = ({
|
|||||||
<Flex mb={1} alignItems={'center'}>
|
<Flex mb={1} alignItems={'center'}>
|
||||||
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
|
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
|
||||||
<Box>开场白</Box>
|
<Box>开场白</Box>
|
||||||
<MyTooltip label={welcomePlaceholder}>
|
<MyTooltip label={welcomeTextTip}>
|
||||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -40,7 +36,7 @@ const NodeUserGuide = ({
|
|||||||
resize={'both'}
|
resize={'both'}
|
||||||
defaultValue={welcomeText}
|
defaultValue={welcomeText}
|
||||||
bg={'myWhite.500'}
|
bg={'myWhite.500'}
|
||||||
placeholder={welcomePlaceholder}
|
placeholder={welcomeTextTip}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChangeNode({
|
onChangeNode({
|
||||||
moduleId: props.moduleId,
|
moduleId: props.moduleId,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ const VariableTypeList = [
|
|||||||
{ label: '文本', icon: 'settingLight', key: VariableInputEnum.input },
|
{ label: '文本', icon: 'settingLight', key: VariableInputEnum.input },
|
||||||
{ label: '下拉单选', icon: 'settingLight', key: VariableInputEnum.select }
|
{ label: '下拉单选', icon: 'settingLight', key: VariableInputEnum.select }
|
||||||
];
|
];
|
||||||
const defaultVariable: VariableItemType = {
|
export const defaultVariable: VariableItemType = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
key: 'key',
|
key: 'key',
|
||||||
label: 'label',
|
label: 'label',
|
||||||
@ -66,10 +66,6 @@ const NodeUserGuide = ({
|
|||||||
?.value as VariableItemType[]) || [],
|
?.value as VariableItemType[]) || [],
|
||||||
[inputs]
|
[inputs]
|
||||||
);
|
);
|
||||||
const welcomeText = useMemo(
|
|
||||||
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
|
|
||||||
[inputs]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||||
|
|||||||
@ -1,143 +0,0 @@
|
|||||||
import React, { useState, useEffect, useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
Flex,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalBody,
|
|
||||||
ModalHeader,
|
|
||||||
ModalFooter,
|
|
||||||
ModalCloseButton,
|
|
||||||
useTheme,
|
|
||||||
useDisclosure,
|
|
||||||
Grid
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { useUserStore } from '@/store/user';
|
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
|
|
||||||
const KBSelect = ({
|
|
||||||
relatedKbs = [],
|
|
||||||
onChange
|
|
||||||
}: {
|
|
||||||
relatedKbs: string[];
|
|
||||||
onChange: (e: string[]) => void;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { myKbList, loadKbList } = useUserStore();
|
|
||||||
const [selectedIdList, setSelectedIdList] = useState<string[]>(relatedKbs);
|
|
||||||
const {
|
|
||||||
isOpen: isOpenKbSelect,
|
|
||||||
onOpen: onOpenKbSelect,
|
|
||||||
onClose: onCloseKbSelect
|
|
||||||
} = useDisclosure();
|
|
||||||
|
|
||||||
const showKbList = useMemo(
|
|
||||||
() => myKbList.filter((item) => relatedKbs.includes(item._id)),
|
|
||||||
[myKbList, relatedKbs]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadKbList();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Grid gridTemplateColumns={'1fr 1fr'} gridGap={4}>
|
|
||||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
|
||||||
选择知识库
|
|
||||||
</Button>
|
|
||||||
{showKbList.map((item) => (
|
|
||||||
<Flex
|
|
||||||
key={item._id}
|
|
||||||
alignItems={'center'}
|
|
||||||
h={'36px'}
|
|
||||||
border={theme.borders.base}
|
|
||||||
px={2}
|
|
||||||
borderRadius={'md'}
|
|
||||||
>
|
|
||||||
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
|
||||||
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
|
|
||||||
{item.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
<Modal isOpen={isOpenKbSelect} onClose={onCloseKbSelect}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent
|
|
||||||
display={'flex'}
|
|
||||||
flexDirection={'column'}
|
|
||||||
w={'800px'}
|
|
||||||
maxW={'90vw'}
|
|
||||||
h={['90vh', 'auto']}
|
|
||||||
>
|
|
||||||
<ModalHeader>关联的知识库({selectedIdList.length})</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody
|
|
||||||
flex={['1 0 0', '0 0 auto']}
|
|
||||||
maxH={'80vh'}
|
|
||||||
overflowY={'auto'}
|
|
||||||
display={'grid'}
|
|
||||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
|
|
||||||
gridGap={3}
|
|
||||||
>
|
|
||||||
{myKbList.map((item) => (
|
|
||||||
<Card
|
|
||||||
key={item._id}
|
|
||||||
p={3}
|
|
||||||
border={theme.borders.base}
|
|
||||||
boxShadow={'sm'}
|
|
||||||
h={'80px'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
order={relatedKbs.includes(item._id) ? 0 : 1}
|
|
||||||
_hover={{
|
|
||||||
boxShadow: 'md'
|
|
||||||
}}
|
|
||||||
{...(selectedIdList.includes(item._id)
|
|
||||||
? {
|
|
||||||
bg: 'myBlue.300'
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
onClick={() => {
|
|
||||||
let ids = [...selectedIdList];
|
|
||||||
if (!selectedIdList.includes(item._id)) {
|
|
||||||
ids = ids.concat(item._id);
|
|
||||||
} else {
|
|
||||||
const i = ids.findIndex((id) => id === item._id);
|
|
||||||
ids.splice(i, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ids = ids.filter((id) => myKbList.find((item) => item._id === id));
|
|
||||||
setSelectedIdList(ids);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'} h={'38px'}>
|
|
||||||
<Avatar src={item.avatar} w={['24px', '28px', '32px']}></Avatar>
|
|
||||||
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
|
|
||||||
{item.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
onCloseKbSelect();
|
|
||||||
onChange(selectedIdList);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
完成
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KBSelect;
|
|
||||||
@ -17,10 +17,11 @@ import {
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { AppSchema } from '@/types/mongoSchema';
|
import { AppSchema } from '@/types/mongoSchema';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { delModelById, putAppById } from '@/api/app';
|
|
||||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
import { useSelectFile } from '@/hooks/useSelectFile';
|
||||||
import { compressImg } from '@/utils/file';
|
import { compressImg } from '@/utils/file';
|
||||||
import { getErrText } from '@/utils/tools';
|
import { getErrText } from '@/utils/tools';
|
||||||
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { useRequest } from '@/hooks/useRequest';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
|
|
||||||
const InfoModal = ({
|
const InfoModal = ({
|
||||||
@ -30,9 +31,11 @@ const InfoModal = ({
|
|||||||
}: {
|
}: {
|
||||||
defaultApp: AppSchema;
|
defaultApp: AppSchema;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { updateAppDetail } = useUserStore();
|
||||||
|
|
||||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
fileType: '.jpg,.png',
|
fileType: '.jpg,.png',
|
||||||
multiple: false
|
multiple: false
|
||||||
@ -47,31 +50,30 @@ const InfoModal = ({
|
|||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues: defaultApp
|
defaultValues: defaultApp
|
||||||
});
|
});
|
||||||
const [btnLoading, setBtnLoading] = useState(false);
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
||||||
// 提交保存模型修改
|
// 提交保存模型修改
|
||||||
const saveSubmitSuccess = useCallback(
|
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
|
||||||
async (data: AppSchema) => {
|
mutationFn: async (data: AppSchema) => {
|
||||||
setBtnLoading(true);
|
await updateAppDetail(data._id, {
|
||||||
try {
|
name: data.name,
|
||||||
await putAppById(data._id, {
|
avatar: data.avatar,
|
||||||
name: data.name,
|
intro: data.intro,
|
||||||
avatar: data.avatar,
|
chat: data.chat,
|
||||||
intro: data.intro,
|
share: data.share
|
||||||
chat: data.chat,
|
});
|
||||||
share: data.share
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '更新失败',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setBtnLoading(false);
|
|
||||||
},
|
},
|
||||||
[toast]
|
onSuccess() {
|
||||||
);
|
onSuccess && onSuccess();
|
||||||
|
onClose();
|
||||||
|
toast({
|
||||||
|
title: '更新成功',
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
errorToast: '更新失败'
|
||||||
|
});
|
||||||
|
|
||||||
// 提交保存表单失败
|
// 提交保存表单失败
|
||||||
const saveSubmitError = useCallback(() => {
|
const saveSubmitError = useCallback(() => {
|
||||||
// deep search message
|
// deep search message
|
||||||
@ -91,7 +93,7 @@ const InfoModal = ({
|
|||||||
}, [errors, toast]);
|
}, [errors, toast]);
|
||||||
|
|
||||||
const saveUpdateModel = useCallback(
|
const saveUpdateModel = useCallback(
|
||||||
() => handleSubmit(saveSubmitSuccess, saveSubmitError)(),
|
() => handleSubmit((data) => saveSubmitSuccess(data), saveSubmitError)(),
|
||||||
[handleSubmit, saveSubmitError, saveSubmitSuccess]
|
[handleSubmit, saveSubmitError, saveSubmitSuccess]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -165,22 +167,7 @@ const InfoModal = ({
|
|||||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button isLoading={btnLoading} onClick={saveUpdateModel}>
|
||||||
isLoading={btnLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await saveUpdateModel();
|
|
||||||
onSuccess();
|
|
||||||
onClose();
|
|
||||||
toast({
|
|
||||||
title: '更新成功',
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
|||||||
214
client/src/pages/app/detail/components/KBSelectModal.tsx
Normal file
214
client/src/pages/app/detail/components/KBSelectModal.tsx
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Flex,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalBody,
|
||||||
|
ModalHeader,
|
||||||
|
ModalFooter,
|
||||||
|
ModalCloseButton,
|
||||||
|
useTheme,
|
||||||
|
Textarea
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { KbListItemType } from '@/types/plugin';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
|
import type { SelectedKbType } from '@/types/plugin';
|
||||||
|
import MySlider from '@/components/Slider';
|
||||||
|
|
||||||
|
export type KbParamsType = {
|
||||||
|
searchSimilarity: number;
|
||||||
|
searchLimit: number;
|
||||||
|
searchEmptyText: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KBSelectModal = ({
|
||||||
|
kbList,
|
||||||
|
activeKbs = [],
|
||||||
|
onChange,
|
||||||
|
onClose
|
||||||
|
}: {
|
||||||
|
kbList: KbListItemType[];
|
||||||
|
activeKbs: SelectedKbType;
|
||||||
|
onChange: (e: SelectedKbType) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [selectedKbList, setSelectedKbList] = useState<SelectedKbType>(activeKbs);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'column'}
|
||||||
|
w={'800px'}
|
||||||
|
maxW={'90vw'}
|
||||||
|
h={['90vh', 'auto']}
|
||||||
|
>
|
||||||
|
<ModalHeader>关联的知识库({selectedKbList.length})</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody
|
||||||
|
flex={['1 0 0', '0 0 auto']}
|
||||||
|
maxH={'80vh'}
|
||||||
|
overflowY={'auto'}
|
||||||
|
display={'grid'}
|
||||||
|
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
|
||||||
|
gridGap={3}
|
||||||
|
>
|
||||||
|
{kbList.map((item) =>
|
||||||
|
(() => {
|
||||||
|
const selected = !!selectedKbList.find((kb) => kb.kbId === item._id);
|
||||||
|
const active = !!activeKbs.find((kb) => kb.kbId === item._id);
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={item._id}
|
||||||
|
p={3}
|
||||||
|
border={theme.borders.base}
|
||||||
|
boxShadow={'sm'}
|
||||||
|
h={'80px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
order={active ? 0 : 1}
|
||||||
|
_hover={{
|
||||||
|
boxShadow: 'md'
|
||||||
|
}}
|
||||||
|
{...(selected
|
||||||
|
? {
|
||||||
|
bg: 'myBlue.300'
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
onClick={() => {
|
||||||
|
if (selected) {
|
||||||
|
setSelectedKbList((state) => state.filter((kb) => kb.kbId !== item._id));
|
||||||
|
} else {
|
||||||
|
setSelectedKbList((state) => [...state, { kbId: item._id }]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} h={'38px'}>
|
||||||
|
<Avatar src={item.avatar} w={['24px', '28px', '32px']}></Avatar>
|
||||||
|
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
onChange(selectedKbList);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
完成
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KbParamsModal = ({
|
||||||
|
searchEmptyText,
|
||||||
|
searchLimit,
|
||||||
|
searchSimilarity,
|
||||||
|
onClose,
|
||||||
|
onChange
|
||||||
|
}: KbParamsType & { onClose: () => void; onChange: (e: KbParamsType) => void }) => {
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const { register, setValue, getValues, handleSubmit } = useForm<KbParamsType>({
|
||||||
|
defaultValues: {
|
||||||
|
searchEmptyText,
|
||||||
|
searchLimit,
|
||||||
|
searchSimilarity
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent display={'flex'} flexDirection={'column'} w={'600px'} maxW={'90vw'}>
|
||||||
|
<ModalHeader>搜索参数调整</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex pt={3} pb={5}>
|
||||||
|
<Box flex={'0 0 100px'}>
|
||||||
|
相似度
|
||||||
|
<MyTooltip label={'高相似度推荐0.8及以上。'}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Box>
|
||||||
|
<MySlider
|
||||||
|
markList={[
|
||||||
|
{ label: '0', value: 0 },
|
||||||
|
{ label: '1', value: 1 }
|
||||||
|
]}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
value={getValues('searchSimilarity')}
|
||||||
|
onChange={(val) => {
|
||||||
|
setValue('searchSimilarity', val);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex py={8}>
|
||||||
|
<Box flex={'0 0 100px'}>单次搜索数量</Box>
|
||||||
|
<Box flex={1}>
|
||||||
|
<MySlider
|
||||||
|
markList={[
|
||||||
|
{ label: '1', value: 1 },
|
||||||
|
{ label: '20', value: 20 }
|
||||||
|
]}
|
||||||
|
min={1}
|
||||||
|
max={20}
|
||||||
|
value={getValues('searchLimit')}
|
||||||
|
onChange={(val) => {
|
||||||
|
setValue('searchLimit', val);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex pt={3}>
|
||||||
|
<Box flex={'0 0 100px'}>空搜索回复</Box>
|
||||||
|
<Box flex={1}>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
maxLength={500}
|
||||||
|
placeholder={
|
||||||
|
'若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文,FastGpt 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。'
|
||||||
|
}
|
||||||
|
{...register('searchEmptyText')}
|
||||||
|
></Textarea>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
handleSubmit(onChange)();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
完成
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -7,12 +7,13 @@ import { useToast } from '@/hooks/useToast';
|
|||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
import { delModelById } from '@/api/app';
|
import { delModelById } from '@/api/app';
|
||||||
import { useConfirm } from '@/hooks/useConfirm';
|
import { useConfirm } from '@/hooks/useConfirm';
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { AppSchema } from '@/types/mongoSchema';
|
import { AppSchema } from '@/types/mongoSchema';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import TotalUsage from './Charts/TotalUsage';
|
import TotalUsage from './Charts/TotalUsage';
|
||||||
|
import BasicEdit from './BasicEdit';
|
||||||
|
|
||||||
const InfoModal = dynamic(() => import('./InfoModal'));
|
const InfoModal = dynamic(() => import('./InfoModal'));
|
||||||
|
|
||||||
@ -54,98 +55,100 @@ const OverView = ({ appId }: { appId: string }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h={'100%'} flexDirection={'column'} position={'relative'}>
|
<Flex h={'100%'} flexDirection={'column'} position={'relative'}>
|
||||||
<Box w={'100%'} pt={[0, 7]} px={[3, 5, 8]}>
|
<Grid
|
||||||
<Grid gridTemplateColumns={['1fr', 'repeat(2,1fr)']} gridGap={[2, 4, 6]}>
|
gridTemplateColumns={['1fr', 'repeat(2,1fr)']}
|
||||||
<Box>
|
gridGap={[2, 4, 6]}
|
||||||
<Box mb={2} fontSize={['md', 'xl']}>
|
pt={[0, 7]}
|
||||||
基本信息
|
px={[3, 5, 8]}
|
||||||
</Box>
|
>
|
||||||
<Box
|
<Box>
|
||||||
border={theme.borders.base}
|
<Box mb={2} fontSize={['md', 'xl']}>
|
||||||
borderRadius={'lg'}
|
基本信息
|
||||||
px={5}
|
|
||||||
py={4}
|
|
||||||
bg={'myBlue.100'}
|
|
||||||
position={'relative'}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'} py={2}>
|
|
||||||
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
|
|
||||||
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
|
|
||||||
{appDetail.name}
|
|
||||||
</Box>
|
|
||||||
<IconButton
|
|
||||||
className="delete"
|
|
||||||
position={'absolute'}
|
|
||||||
top={4}
|
|
||||||
right={4}
|
|
||||||
size={'sm'}
|
|
||||||
icon={<MyIcon name={'delete'} w={'14px'} />}
|
|
||||||
variant={'base'}
|
|
||||||
borderRadius={'md'}
|
|
||||||
aria-label={'delete'}
|
|
||||||
_hover={{
|
|
||||||
bg: 'myGray.100',
|
|
||||||
color: 'red.600'
|
|
||||||
}}
|
|
||||||
onClick={openConfirm(handleDelModel)}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Box className={'textEllipsis3'} py={3} wordBreak={'break-all'} color={'myGray.600'}>
|
|
||||||
{appDetail.intro || '快来给应用一个介绍~'}
|
|
||||||
</Box>
|
|
||||||
<Flex>
|
|
||||||
<Button
|
|
||||||
size={['sm', 'md']}
|
|
||||||
variant={'base'}
|
|
||||||
leftIcon={<MyIcon name={'chatLight'} w={'16px'} />}
|
|
||||||
onClick={() => router.push(`/chat?appId=${appId}`)}
|
|
||||||
>
|
|
||||||
对话
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
mx={3}
|
|
||||||
size={['sm', 'md']}
|
|
||||||
variant={'base'}
|
|
||||||
leftIcon={<MyIcon name={'shareLight'} w={'16px'} />}
|
|
||||||
onClick={() => {
|
|
||||||
router.replace({
|
|
||||||
query: {
|
|
||||||
appId,
|
|
||||||
currentTab: 'share'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
分享
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size={['sm', 'md']}
|
|
||||||
variant={'base'}
|
|
||||||
leftIcon={<MyIcon name={'settingLight'} w={'16px'} />}
|
|
||||||
onClick={() => setSettingAppInfo(appDetail)}
|
|
||||||
>
|
|
||||||
设置
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Flex flexDirection={'column'}>
|
<Box
|
||||||
<Box mb={2} fontSize={['md', 'xl']}>
|
border={theme.borders.base}
|
||||||
近 14 日消费
|
borderRadius={'lg'}
|
||||||
|
px={5}
|
||||||
|
py={4}
|
||||||
|
bg={'myBlue.100'}
|
||||||
|
position={'relative'}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} py={2}>
|
||||||
|
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
|
||||||
|
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
|
||||||
|
{appDetail.name}
|
||||||
|
</Box>
|
||||||
|
<IconButton
|
||||||
|
className="delete"
|
||||||
|
position={'absolute'}
|
||||||
|
top={4}
|
||||||
|
right={4}
|
||||||
|
size={'sm'}
|
||||||
|
icon={<MyIcon name={'delete'} w={'14px'} />}
|
||||||
|
variant={'base'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
aria-label={'delete'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'myGray.100',
|
||||||
|
color: 'red.600'
|
||||||
|
}}
|
||||||
|
onClick={openConfirm(handleDelModel)}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Box className={'textEllipsis3'} py={3} wordBreak={'break-all'} color={'myGray.600'}>
|
||||||
|
{appDetail.intro || '快来给应用一个介绍~'}
|
||||||
</Box>
|
</Box>
|
||||||
<TotalUsage appId={appId} />
|
<Flex>
|
||||||
</Flex>
|
<Button
|
||||||
</Grid>
|
size={['sm', 'md']}
|
||||||
|
variant={'base'}
|
||||||
|
leftIcon={<MyIcon name={'chatLight'} w={'16px'} />}
|
||||||
|
onClick={() => router.push(`/chat?appId=${appId}`)}
|
||||||
|
>
|
||||||
|
对话
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mx={3}
|
||||||
|
size={['sm', 'md']}
|
||||||
|
variant={'base'}
|
||||||
|
leftIcon={<MyIcon name={'shareLight'} w={'16px'} />}
|
||||||
|
onClick={() => {
|
||||||
|
router.replace({
|
||||||
|
query: {
|
||||||
|
appId,
|
||||||
|
currentTab: 'share'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
分享
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={['sm', 'md']}
|
||||||
|
variant={'base'}
|
||||||
|
leftIcon={<MyIcon name={'settingLight'} w={'16px'} />}
|
||||||
|
onClick={() => setSettingAppInfo(appDetail)}
|
||||||
|
>
|
||||||
|
设置
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Flex flexDirection={'column'}>
|
||||||
|
<Box mb={2} fontSize={['md', 'xl']}>
|
||||||
|
近 14 日消费
|
||||||
|
</Box>
|
||||||
|
<TotalUsage appId={appId} />
|
||||||
|
</Flex>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Box flex={'1 0 0'} h={0} mt={4} borderTop={theme.borders.base} px={[3, 5, 8]}>
|
||||||
|
<BasicEdit appId={appId} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{settingAppInfo && (
|
{settingAppInfo && (
|
||||||
<InfoModal
|
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
|
||||||
defaultApp={settingAppInfo}
|
|
||||||
onClose={() => setSettingAppInfo(undefined)}
|
|
||||||
onSuccess={refetch}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ConfirmChild />
|
<ConfirmChild />
|
||||||
<Loading fixed={false} />
|
<Loading fixed={false} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
214
client/src/pages/app/detail/components/VariableEditModal.tsx
Normal file
214
client/src/pages/app/detail/components/VariableEditModal.tsx
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalFooter,
|
||||||
|
ModalBody,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
Flex,
|
||||||
|
Switch,
|
||||||
|
Input,
|
||||||
|
Grid,
|
||||||
|
FormControl,
|
||||||
|
useTheme
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||||
|
import { VariableInputEnum } from '@/constants/app';
|
||||||
|
import type { VariableItemType } from '@/types/app';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useFieldArray } from 'react-hook-form';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||||
|
|
||||||
|
const VariableTypeList = [
|
||||||
|
{ label: '文本', icon: 'settingLight', key: VariableInputEnum.input },
|
||||||
|
{ label: '下拉单选', icon: 'settingLight', key: VariableInputEnum.select }
|
||||||
|
];
|
||||||
|
|
||||||
|
export type VariableFormType = {
|
||||||
|
variable: VariableItemType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VariableEditModal = ({
|
||||||
|
defaultVariable,
|
||||||
|
onClose,
|
||||||
|
onSubmit
|
||||||
|
}: {
|
||||||
|
defaultVariable: VariableItemType;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: VariableFormType) => void;
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
||||||
|
const { reset, getValues, setValue, register, control, handleSubmit } = useForm<VariableFormType>(
|
||||||
|
{
|
||||||
|
defaultValues: {
|
||||||
|
variable: defaultVariable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
fields: selectEnums,
|
||||||
|
append: appendEnums,
|
||||||
|
remove: removeEnums
|
||||||
|
} = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: 'variable.enums'
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent maxW={'Min(400px,90vw)'}>
|
||||||
|
<ModalHeader display={'flex'}>
|
||||||
|
<MyIcon name={'variable'} mr={2} w={'24px'} color={'#FF8A4C'} />
|
||||||
|
变量设置
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box w={'70px'}>必填</Box>
|
||||||
|
<Switch {...register('variable.required')} />
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={5} alignItems={'center'}>
|
||||||
|
<Box w={'80px'}>变量名</Box>
|
||||||
|
<Input {...register('variable.label', { required: '变量名不能为空' })} />
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={5} alignItems={'center'}>
|
||||||
|
<Box w={'80px'}>变量 key</Box>
|
||||||
|
<Input {...register('variable.key', { required: '变量 key 不能为空' })} />
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Box mt={5} mb={2}>
|
||||||
|
字段类型
|
||||||
|
</Box>
|
||||||
|
<Grid gridTemplateColumns={'repeat(2,130px)'} gridGap={4}>
|
||||||
|
{VariableTypeList.map((item) => (
|
||||||
|
<Flex
|
||||||
|
key={item.key}
|
||||||
|
px={4}
|
||||||
|
py={1}
|
||||||
|
border={theme.borders.base}
|
||||||
|
borderRadius={'md'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
{...(item.key === getValues('variable.type')
|
||||||
|
? {
|
||||||
|
bg: 'myWhite.600'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
_hover: {
|
||||||
|
boxShadow: 'md'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
setValue('variable.type', item.key);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MyIcon name={item.icon as any} w={'16px'} />
|
||||||
|
<Box ml={3}>{item.label}</Box>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{getValues('variable.type') === VariableInputEnum.input && (
|
||||||
|
<>
|
||||||
|
<Box mt={5} mb={2}>
|
||||||
|
最大长度
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<NumberInput max={100} min={1} step={1} position={'relative'}>
|
||||||
|
<NumberInputField
|
||||||
|
{...register('variable.maxLen', {
|
||||||
|
min: 1,
|
||||||
|
max: 100,
|
||||||
|
valueAsNumber: true
|
||||||
|
})}
|
||||||
|
max={100}
|
||||||
|
/>
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{getValues('variable.type') === VariableInputEnum.select && (
|
||||||
|
<>
|
||||||
|
<Box mt={5} mb={2}>
|
||||||
|
选项
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
{selectEnums.map((item, i) => (
|
||||||
|
<Flex key={item.id} mb={2} alignItems={'center'}>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...register(`variable.enums.${i}.value`, {
|
||||||
|
required: '选项内容不能为空'
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<MyIcon
|
||||||
|
ml={3}
|
||||||
|
name={'delete'}
|
||||||
|
w={'16px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
p={2}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
_hover={{ bg: 'red.100' }}
|
||||||
|
onClick={() => removeEnums(i)}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant={'solid'}
|
||||||
|
w={'100%'}
|
||||||
|
textAlign={'left'}
|
||||||
|
leftIcon={<SmallAddIcon />}
|
||||||
|
bg={'myGray.100 !important'}
|
||||||
|
onClick={() => appendEnums({ value: '' })}
|
||||||
|
>
|
||||||
|
添加选项
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(VariableEditModal);
|
||||||
|
|
||||||
|
export const defaultVariable: VariableItemType = {
|
||||||
|
id: nanoid(),
|
||||||
|
key: 'key',
|
||||||
|
label: 'label',
|
||||||
|
type: VariableInputEnum.input,
|
||||||
|
required: true,
|
||||||
|
maxLen: 50,
|
||||||
|
enums: [{ value: '' }]
|
||||||
|
};
|
||||||
|
export const addVariable = () => {
|
||||||
|
const newVariable = { ...defaultVariable, id: nanoid() };
|
||||||
|
return newVariable;
|
||||||
|
};
|
||||||
@ -54,7 +54,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
|
|
||||||
const tabList = useMemo(
|
const tabList = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{ label: '概览', id: TabEnum.overview, icon: 'overviewLight' },
|
{ label: '基础', id: TabEnum.overview, icon: 'overviewLight' },
|
||||||
{ label: '高级编排', id: TabEnum.settings, icon: 'settingLight' },
|
{ label: '高级编排', id: TabEnum.settings, icon: 'settingLight' },
|
||||||
{ label: '链接分享', id: TabEnum.share, icon: 'shareLight' },
|
{ label: '链接分享', id: TabEnum.share, icon: 'shareLight' },
|
||||||
{ label: 'API访问', id: TabEnum.API, icon: 'apiLight' },
|
{ label: 'API访问', id: TabEnum.API, icon: 'apiLight' },
|
||||||
@ -150,7 +150,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
<Tabs
|
<Tabs
|
||||||
mx={'auto'}
|
mx={'auto'}
|
||||||
mt={2}
|
mt={2}
|
||||||
w={'300px'}
|
w={'100%'}
|
||||||
list={tabList}
|
list={tabList}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
activeId={currentTab}
|
activeId={currentTab}
|
||||||
|
|||||||
@ -79,7 +79,7 @@ const ChatHistorySlider = ({
|
|||||||
cursor={appId ? 'pointer' : 'default'}
|
cursor={appId ? 'pointer' : 'default'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
appId &&
|
appId &&
|
||||||
router.push({
|
router.replace({
|
||||||
pathname: '/app/detail',
|
pathname: '/app/detail',
|
||||||
query: { appId }
|
query: { appId }
|
||||||
})
|
})
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const SliderApps = ({ appId }: { appId: string }) => {
|
|||||||
px={3}
|
px={3}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
_hover={{ bg: 'myGray.200' }}
|
_hover={{ bg: 'myGray.200' }}
|
||||||
onClick={() => router.push('/app/list')}
|
onClick={() => router.back()}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
mr={3}
|
mr={3}
|
||||||
|
|||||||
@ -239,7 +239,7 @@ const Chat = () => {
|
|||||||
top: item.top
|
top: item.top
|
||||||
}))}
|
}))}
|
||||||
onChangeChat={(chatId) => {
|
onChangeChat={(chatId) => {
|
||||||
router.push({
|
router.replace({
|
||||||
query: {
|
query: {
|
||||||
chatId: chatId || '',
|
chatId: chatId || '',
|
||||||
appId
|
appId
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { create } from 'zustand';
|
|||||||
import { devtools, persist } from 'zustand/middleware';
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
import { immer } from 'zustand/middleware/immer';
|
import { immer } from 'zustand/middleware/immer';
|
||||||
import type { UserType, UserUpdateParams } from '@/types/user';
|
import type { UserType, UserUpdateParams } from '@/types/user';
|
||||||
import { getMyModels, getModelById } from '@/api/app';
|
import { getMyModels, getModelById, putAppById } from '@/api/app';
|
||||||
import { formatPrice } from '@/utils/user';
|
import { formatPrice } from '@/utils/user';
|
||||||
import { getTokenLogin } from '@/api/user';
|
import { getTokenLogin } from '@/api/user';
|
||||||
import { defaultApp } from '@/constants/model';
|
import { defaultApp } from '@/constants/model';
|
||||||
import { AppListItemType } from '@/types/app';
|
import { AppListItemType, AppUpdateParams } from '@/types/app';
|
||||||
import type { KbItemType, KbListItemType } from '@/types/plugin';
|
import type { KbItemType, KbListItemType } from '@/types/plugin';
|
||||||
import { getKbList, getKbById } from '@/api/plugins/kb';
|
import { getKbList, getKbById } from '@/api/plugins/kb';
|
||||||
import { defaultKbDetail } from '@/constants/kb';
|
import { defaultKbDetail } from '@/constants/kb';
|
||||||
@ -22,6 +22,7 @@ type State = {
|
|||||||
loadMyModels: () => Promise<null>;
|
loadMyModels: () => Promise<null>;
|
||||||
appDetail: AppSchema;
|
appDetail: AppSchema;
|
||||||
loadAppDetail: (id: string, init?: boolean) => Promise<AppSchema>;
|
loadAppDetail: (id: string, init?: boolean) => Promise<AppSchema>;
|
||||||
|
updateAppDetail(appId: string, data: AppUpdateParams): Promise<void>;
|
||||||
clearAppModules(): void;
|
clearAppModules(): void;
|
||||||
// kb
|
// kb
|
||||||
myKbList: KbListItemType[];
|
myKbList: KbListItemType[];
|
||||||
@ -79,6 +80,15 @@ export const useUserStore = create<State>()(
|
|||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
async updateAppDetail(appId: string, data: AppUpdateParams) {
|
||||||
|
await putAppById(appId, data);
|
||||||
|
set((state) => {
|
||||||
|
state.appDetail = {
|
||||||
|
...state.appDetail,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
clearAppModules() {
|
clearAppModules() {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.appDetail = {
|
state.appDetail = {
|
||||||
|
|||||||
2
client/src/types/plugin.d.ts
vendored
2
client/src/types/plugin.d.ts
vendored
@ -1,5 +1,7 @@
|
|||||||
import type { kbSchema } from './mongoSchema';
|
import type { kbSchema } from './mongoSchema';
|
||||||
|
|
||||||
|
export type SelectedKbType = { kbId: string }[];
|
||||||
|
|
||||||
export type KbListItemType = {
|
export type KbListItemType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|||||||
611
client/src/utils/app.ts
Normal file
611
client/src/utils/app.ts
Normal file
@ -0,0 +1,611 @@
|
|||||||
|
import type { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||||
|
import { chatModelList, vectorModelList } from '@/store/static';
|
||||||
|
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||||
|
import { FlowInputItemType } from '@/types/flow';
|
||||||
|
import { SystemInputEnum } from '@/constants/app';
|
||||||
|
import type { SelectedKbType } from '@/types/plugin';
|
||||||
|
import {
|
||||||
|
VariableModule,
|
||||||
|
UserGuideModule,
|
||||||
|
ChatModule,
|
||||||
|
HistoryModule,
|
||||||
|
UserInputModule,
|
||||||
|
KBSearchModule,
|
||||||
|
AnswerModule
|
||||||
|
} from '@/constants/flow/ModuleTemplate';
|
||||||
|
import { rawSearchKey } from '@/constants/chat';
|
||||||
|
|
||||||
|
export type EditFormType = {
|
||||||
|
chatModel: {
|
||||||
|
model: string;
|
||||||
|
systemPrompt: string;
|
||||||
|
limitPrompt: string;
|
||||||
|
temperature: number;
|
||||||
|
maxToken: number;
|
||||||
|
frequency: number;
|
||||||
|
presence: number;
|
||||||
|
};
|
||||||
|
kb: {
|
||||||
|
list: SelectedKbType;
|
||||||
|
searchSimilarity: number;
|
||||||
|
searchLimit: number;
|
||||||
|
searchEmptyText: string;
|
||||||
|
};
|
||||||
|
guide: {
|
||||||
|
welcome: {
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
variables: VariableItemType[];
|
||||||
|
};
|
||||||
|
export const getDefaultAppForm = (): EditFormType => {
|
||||||
|
const defaultChatModel = chatModelList[0];
|
||||||
|
const defaultVectorModel = vectorModelList[0];
|
||||||
|
return {
|
||||||
|
chatModel: {
|
||||||
|
model: defaultChatModel.model,
|
||||||
|
systemPrompt: '',
|
||||||
|
limitPrompt: '',
|
||||||
|
temperature: 0,
|
||||||
|
maxToken: defaultChatModel.contextMaxToken / 2,
|
||||||
|
frequency: 0.5,
|
||||||
|
presence: -0.5
|
||||||
|
},
|
||||||
|
kb: {
|
||||||
|
list: [],
|
||||||
|
searchSimilarity: 0.8,
|
||||||
|
searchLimit: 5,
|
||||||
|
searchEmptyText: ''
|
||||||
|
},
|
||||||
|
guide: {
|
||||||
|
welcome: {
|
||||||
|
text: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
variables: []
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appModules2Form = (modules: AppModuleItemType[]) => {
|
||||||
|
const defaultAppForm = getDefaultAppForm();
|
||||||
|
const updateVal = ({
|
||||||
|
formKey,
|
||||||
|
inputs,
|
||||||
|
key
|
||||||
|
}: {
|
||||||
|
formKey: string;
|
||||||
|
inputs: FlowInputItemType[];
|
||||||
|
key: string;
|
||||||
|
}) => {
|
||||||
|
const propertyPath = formKey.split('.');
|
||||||
|
let currentObj: any = defaultAppForm;
|
||||||
|
for (let i = 0; i < propertyPath.length - 1; i++) {
|
||||||
|
currentObj = currentObj[propertyPath[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const val =
|
||||||
|
inputs.find((item) => item.key === key)?.value ||
|
||||||
|
currentObj[propertyPath[propertyPath.length - 1]];
|
||||||
|
|
||||||
|
currentObj[propertyPath[propertyPath.length - 1]] = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules.forEach((module) => {
|
||||||
|
if (module.flowType === FlowModuleTypeEnum.chatNode) {
|
||||||
|
updateVal({
|
||||||
|
formKey: 'chatModel.model',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'model'
|
||||||
|
});
|
||||||
|
updateVal({
|
||||||
|
formKey: 'chatModel.temperature',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'temperature'
|
||||||
|
});
|
||||||
|
updateVal({
|
||||||
|
formKey: 'chatModel.maxToken',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'maxToken'
|
||||||
|
});
|
||||||
|
updateVal({
|
||||||
|
formKey: 'chatModel.systemPrompt',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'systemPrompt'
|
||||||
|
});
|
||||||
|
updateVal({
|
||||||
|
formKey: 'chatModel.limitPrompt',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'limitPrompt'
|
||||||
|
});
|
||||||
|
} else if (module.flowType === FlowModuleTypeEnum.kbSearchNode) {
|
||||||
|
updateVal({
|
||||||
|
formKey: 'kb.list',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'kbList'
|
||||||
|
});
|
||||||
|
updateVal({
|
||||||
|
formKey: 'kb.searchSimilarity',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'similarity'
|
||||||
|
});
|
||||||
|
updateVal({
|
||||||
|
formKey: 'kb.searchLimit',
|
||||||
|
inputs: module.inputs,
|
||||||
|
key: 'limit'
|
||||||
|
});
|
||||||
|
// empty text
|
||||||
|
const emptyOutputs = module.outputs.find((item) => item.key === 'isEmpty')?.targets || [];
|
||||||
|
const emptyOutput = emptyOutputs[0];
|
||||||
|
if (emptyOutput) {
|
||||||
|
const target = modules.find((item) => item.moduleId === emptyOutput.moduleId);
|
||||||
|
defaultAppForm.kb.searchEmptyText =
|
||||||
|
target?.inputs?.find((item) => item.key === 'answerText')?.value || '';
|
||||||
|
}
|
||||||
|
} else if (module.flowType === FlowModuleTypeEnum.userGuide) {
|
||||||
|
const val =
|
||||||
|
module.inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
|
||||||
|
if (val) {
|
||||||
|
defaultAppForm.guide.welcome = {
|
||||||
|
text: val
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (module.flowType === FlowModuleTypeEnum.variable) {
|
||||||
|
defaultAppForm.variables =
|
||||||
|
module.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return defaultAppForm;
|
||||||
|
};
|
||||||
|
|
||||||
|
const chatModelInput = (formData: EditFormType) => [
|
||||||
|
{
|
||||||
|
key: 'model',
|
||||||
|
type: 'custom',
|
||||||
|
label: '对话模型',
|
||||||
|
value: formData.chatModel.model,
|
||||||
|
list: chatModelList.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.model
|
||||||
|
})),
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'temperature',
|
||||||
|
type: 'custom',
|
||||||
|
label: '温度',
|
||||||
|
value: formData.chatModel.temperature,
|
||||||
|
min: 0,
|
||||||
|
max: 10,
|
||||||
|
step: 1,
|
||||||
|
markList: [
|
||||||
|
{
|
||||||
|
label: '严谨',
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '发散',
|
||||||
|
value: 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maxToken',
|
||||||
|
type: 'custom',
|
||||||
|
label: '回复上限',
|
||||||
|
value: formData.chatModel.maxToken,
|
||||||
|
min: 100,
|
||||||
|
max: 16000,
|
||||||
|
step: 50,
|
||||||
|
markList: [
|
||||||
|
{
|
||||||
|
label: '0',
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '16000',
|
||||||
|
value: 16000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'systemPrompt',
|
||||||
|
type: 'textarea',
|
||||||
|
label: '系统提示词',
|
||||||
|
description:
|
||||||
|
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
|
||||||
|
placeholder:
|
||||||
|
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
|
||||||
|
value: formData.chatModel.systemPrompt,
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'limitPrompt',
|
||||||
|
type: 'textarea',
|
||||||
|
label: '限定词',
|
||||||
|
description:
|
||||||
|
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||||
|
placeholder:
|
||||||
|
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
|
||||||
|
value: formData.chatModel.limitPrompt,
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'quotePrompt',
|
||||||
|
type: 'target',
|
||||||
|
label: '引用内容',
|
||||||
|
connected: formData.kb.list.length > 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
type: 'target',
|
||||||
|
label: '聊天记录',
|
||||||
|
connected: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'userChatInput',
|
||||||
|
type: 'target',
|
||||||
|
label: '用户问题',
|
||||||
|
connected: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const welcomeTemplate = (formData: EditFormType) =>
|
||||||
|
formData.guide?.welcome?.text
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
...UserGuideModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'welcomeText',
|
||||||
|
type: 'input',
|
||||||
|
label: '开场白',
|
||||||
|
value: formData.guide.welcome.text,
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
position: {
|
||||||
|
x: 447.98520778293346,
|
||||||
|
y: 721.4016845336229
|
||||||
|
},
|
||||||
|
moduleId: 'v7lq0x'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
const variableTemplate = (formData: EditFormType) =>
|
||||||
|
formData.variables.length > 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
...VariableModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'variables',
|
||||||
|
type: 'systemInput',
|
||||||
|
label: '变量输入',
|
||||||
|
value: formData.variables,
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
position: {
|
||||||
|
x: 444.0369195277651,
|
||||||
|
y: 1008.5185781784537
|
||||||
|
},
|
||||||
|
moduleId: '7blchb'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
const simpleChatTemplate = (formData: EditFormType) => [
|
||||||
|
{
|
||||||
|
...UserInputModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'userChatInput',
|
||||||
|
type: 'systemInput',
|
||||||
|
label: '用户问题',
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'userChatInput',
|
||||||
|
label: '用户问题',
|
||||||
|
type: 'source',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
moduleId: 'chatModule',
|
||||||
|
key: 'userChatInput'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 464.32198615344566,
|
||||||
|
y: 1602.2698463081606
|
||||||
|
},
|
||||||
|
moduleId: '7z5g5h'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...HistoryModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'maxContext',
|
||||||
|
type: 'numberInput',
|
||||||
|
label: '最长记录数',
|
||||||
|
value: 10,
|
||||||
|
min: 0,
|
||||||
|
max: 50,
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
type: 'hidden',
|
||||||
|
label: '聊天记录',
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
label: '聊天记录',
|
||||||
|
type: 'source',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
moduleId: 'chatModule',
|
||||||
|
key: 'history'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 452.5466249541586,
|
||||||
|
y: 1276.3930310334215
|
||||||
|
},
|
||||||
|
moduleId: 'xj0c9p'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...ChatModule,
|
||||||
|
inputs: chatModelInput(formData),
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'answerText',
|
||||||
|
label: '模型回复',
|
||||||
|
description: '直接响应,无需配置',
|
||||||
|
type: 'hidden',
|
||||||
|
targets: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 981.9682828103937,
|
||||||
|
y: 890.014595014464
|
||||||
|
},
|
||||||
|
moduleId: 'chatModule'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const kbTemplate = (formData: EditFormType) => [
|
||||||
|
{
|
||||||
|
...UserInputModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'userChatInput',
|
||||||
|
type: 'systemInput',
|
||||||
|
label: '用户问题',
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'userChatInput',
|
||||||
|
label: '用户问题',
|
||||||
|
type: 'source',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
moduleId: 'chatModule',
|
||||||
|
key: 'userChatInput'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleId: 'q9v14m',
|
||||||
|
key: 'userChatInput'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 464.32198615344566,
|
||||||
|
y: 1602.2698463081606
|
||||||
|
},
|
||||||
|
moduleId: '7z5g5h'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...HistoryModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'maxContext',
|
||||||
|
type: 'numberInput',
|
||||||
|
label: '最长记录数',
|
||||||
|
value: 10,
|
||||||
|
min: 0,
|
||||||
|
max: 50,
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
type: 'hidden',
|
||||||
|
label: '聊天记录',
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
label: '聊天记录',
|
||||||
|
type: 'source',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
moduleId: 'chatModule',
|
||||||
|
key: 'history'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 452.5466249541586,
|
||||||
|
y: 1276.3930310334215
|
||||||
|
},
|
||||||
|
moduleId: 'xj0c9p'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...KBSearchModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'kbList',
|
||||||
|
type: 'custom',
|
||||||
|
label: '关联的知识库',
|
||||||
|
value: formData.kb.list,
|
||||||
|
list: [],
|
||||||
|
connected: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'similarity',
|
||||||
|
type: 'custom',
|
||||||
|
label: '相似度',
|
||||||
|
value: formData.kb.searchSimilarity,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.01,
|
||||||
|
markList: [
|
||||||
|
{
|
||||||
|
label: '0',
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1',
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'limit',
|
||||||
|
type: 'custom',
|
||||||
|
label: '单次搜索上限',
|
||||||
|
description: '最多取 n 条记录作为本次问题引用',
|
||||||
|
value: formData.kb.searchLimit,
|
||||||
|
min: 1,
|
||||||
|
max: 20,
|
||||||
|
step: 1,
|
||||||
|
markList: [
|
||||||
|
{
|
||||||
|
label: '1',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '20',
|
||||||
|
value: 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'switch',
|
||||||
|
type: 'target',
|
||||||
|
label: '触发器',
|
||||||
|
connected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'userChatInput',
|
||||||
|
type: 'target',
|
||||||
|
label: '用户问题',
|
||||||
|
connected: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'isEmpty',
|
||||||
|
label: '搜索结果为空',
|
||||||
|
type: 'source',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
moduleId: 'emptyText',
|
||||||
|
key: 'switch'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'quotePrompt',
|
||||||
|
label: '引用内容',
|
||||||
|
description: '搜索结果为空时不返回',
|
||||||
|
type: 'source',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
moduleId: 'chatModule',
|
||||||
|
key: 'quotePrompt'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 956.0838440206068,
|
||||||
|
y: 887.462827870246
|
||||||
|
},
|
||||||
|
moduleId: 'q9v14m'
|
||||||
|
},
|
||||||
|
...(formData.kb.searchEmptyText
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
...AnswerModule,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
key: 'switch',
|
||||||
|
type: 'target',
|
||||||
|
label: '触发器',
|
||||||
|
connected: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'answerText',
|
||||||
|
value: formData.kb.searchEmptyText,
|
||||||
|
type: 'input',
|
||||||
|
label: '回复的内容',
|
||||||
|
connected: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
position: {
|
||||||
|
x: 1570.7651822907549,
|
||||||
|
y: 637.8753731306779
|
||||||
|
},
|
||||||
|
moduleId: 'emptyText'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
...ChatModule,
|
||||||
|
inputs: chatModelInput(formData),
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
key: 'answerText',
|
||||||
|
label: '模型回复',
|
||||||
|
description: '直接响应,无需配置',
|
||||||
|
type: 'hidden',
|
||||||
|
targets: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
x: 1551.71405495818,
|
||||||
|
y: 977.4911578918461
|
||||||
|
},
|
||||||
|
moduleId: 'chatModule'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const appForm2Modules = (formData: EditFormType) => {
|
||||||
|
const modules = [
|
||||||
|
...welcomeTemplate(formData),
|
||||||
|
...variableTemplate(formData),
|
||||||
|
...(formData.kb.list.length > 0 ? kbTemplate(formData) : simpleChatTemplate(formData))
|
||||||
|
];
|
||||||
|
|
||||||
|
return modules as AppModuleItemType[];
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user