feat: agent and ui
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
BIN
client/public/imgs/module/AI.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
client/public/imgs/module/cq.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
client/public/imgs/module/db.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/public/imgs/module/history.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
client/public/imgs/module/reply.png
Normal file
|
After Width: | Height: | Size: 776 B |
BIN
client/public/imgs/module/userChatInput.png
Normal file
|
After Width: | Height: | Size: 572 B |
1
client/src/components/Icon/icons/minus.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688632968712" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3386" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M507.904 52.224q95.232 0 179.2 36.352t145.92 98.304 98.304 145.408 36.352 178.688-36.352 179.2-98.304 145.92-145.92 98.304-179.2 36.352-178.688-36.352-145.408-98.304-98.304-145.92-36.352-179.2 36.352-178.688 98.304-145.408 145.408-98.304 178.688-36.352zM736.256 573.44q30.72 0 55.296-15.872t24.576-47.616q0-30.72-24.576-45.568t-55.296-14.848l-452.608 0q-30.72 0-56.32 14.848t-25.6 45.568q0 31.744 25.6 47.616t56.32 15.872l452.608 0z" p-id="3387"></path></svg>
|
||||||
|
After Width: | Height: | Size: 768 B |
@ -35,7 +35,8 @@ const map = {
|
|||||||
kbTest: require('./icons/kbTest.svg').default,
|
kbTest: require('./icons/kbTest.svg').default,
|
||||||
date: require('./icons/date.svg').default,
|
date: require('./icons/date.svg').default,
|
||||||
apikey: require('./icons/apikey.svg').default,
|
apikey: require('./icons/apikey.svg').default,
|
||||||
save: require('./icons/save.svg').default
|
save: require('./icons/save.svg').default,
|
||||||
|
minus: require('./icons/minus.svg').default
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IconName = keyof typeof map;
|
export type IconName = keyof typeof map;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Box, Flex, Tooltip, Link } from '@chakra-ui/react';
|
import { Box, Flex, Link } from '@chakra-ui/react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import MyIcon from '../Icon';
|
import MyIcon from '../Icon';
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
import React, { memo } from 'react';
|
|
||||||
import { Box } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
const Loading = ({ text }: { text?: string }) => {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box
|
|
||||||
minW={'100px'}
|
|
||||||
w={'100%'}
|
|
||||||
h={'80px'}
|
|
||||||
backgroundImage={'url("/imgs/loading.gif")'}
|
|
||||||
backgroundSize={'contain'}
|
|
||||||
backgroundRepeat={'no-repeat'}
|
|
||||||
backgroundPosition={'center'}
|
|
||||||
/>
|
|
||||||
{text && (
|
|
||||||
<Box mt={1} textAlign={'center'} fontSize={'sm'} color={'myGray.600'}>
|
|
||||||
{text}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(Loading);
|
|
||||||
24
client/src/components/MyTooltip/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tooltip, TooltipProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const MyTooltip = ({ children, ...props }: TooltipProps) => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
bg={'white'}
|
||||||
|
arrowShadowColor={' rgba(0,0,0,0.1)'}
|
||||||
|
hasArrow
|
||||||
|
arrowSize={12}
|
||||||
|
offset={[-15, 15]}
|
||||||
|
color={'myGray.800'}
|
||||||
|
px={4}
|
||||||
|
py={2}
|
||||||
|
borderRadius={'8px'}
|
||||||
|
whiteSpace={'pre-wrap'}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyTooltip;
|
||||||
@ -155,145 +155,145 @@ export const chatAppDemo: AppItemType = {
|
|||||||
modules: [chatModule({ id: 'chat' })]
|
modules: [chatModule({ id: 'chat' })]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const kbChatAppDemo: AppItemType = {
|
// export const kbChatAppDemo: AppItemType = {
|
||||||
id: 'kbchat',
|
// id: 'kbchat',
|
||||||
name: 'kbchat',
|
// name: 'kbchat',
|
||||||
// 标记字段
|
// // 标记字段
|
||||||
modules: [
|
// modules: [
|
||||||
{
|
// {
|
||||||
moduleId: 'kbsearch',
|
// moduleId: 'kbsearch',
|
||||||
flowType: FlowModuleTypeEnum.kbSearchNode,
|
// flowType: FlowModuleTypeEnum.kbSearchNode,
|
||||||
type: AppModuleItemTypeEnum.http,
|
// type: AppModuleItemTypeEnum.http,
|
||||||
url: '/openapi/modules/kb/search',
|
// url: '/openapi/modules/kb/search',
|
||||||
position: { x: -500, y: 0 },
|
// position: { x: -500, y: 0 },
|
||||||
inputs: [
|
// inputs: [
|
||||||
{
|
// {
|
||||||
key: 'kb_ids',
|
// key: 'kb_ids',
|
||||||
type: FlowInputItemTypeEnum.custom,
|
// type: FlowInputItemTypeEnum.custom,
|
||||||
label: '关联的知识库',
|
// label: '关联的知识库',
|
||||||
value: ['646627f4f7b896cfd8910e38'],
|
// value: ['646627f4f7b896cfd8910e38'],
|
||||||
list: []
|
// list: []
|
||||||
},
|
// },
|
||||||
|
|
||||||
{
|
// {
|
||||||
key: 'similarity',
|
// key: 'similarity',
|
||||||
type: FlowInputItemTypeEnum.slider,
|
// type: FlowInputItemTypeEnum.slider,
|
||||||
label: '相似度',
|
// label: '相似度',
|
||||||
value: 0.8,
|
// value: 0.8,
|
||||||
min: 0,
|
// min: 0,
|
||||||
max: 1,
|
// max: 1,
|
||||||
step: 0.01,
|
// step: 0.01,
|
||||||
markList: [
|
// markList: [
|
||||||
{ label: '0', value: 0 },
|
// { label: '0', value: 0 },
|
||||||
{ label: '1', value: 1 }
|
// { label: '1', value: 1 }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'limit',
|
// key: 'limit',
|
||||||
type: FlowInputItemTypeEnum.slider,
|
// type: FlowInputItemTypeEnum.slider,
|
||||||
label: '单次搜索上限',
|
// label: '单次搜索上限',
|
||||||
value: 5,
|
// value: 5,
|
||||||
min: 1,
|
// min: 1,
|
||||||
max: 20,
|
// max: 20,
|
||||||
step: 1,
|
// step: 1,
|
||||||
markList: [
|
// markList: [
|
||||||
{ label: '1', value: 1 },
|
// { label: '1', value: 1 },
|
||||||
{ label: '20', value: 20 }
|
// { label: '20', value: 20 }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: SystemInputEnum.history,
|
// key: SystemInputEnum.history,
|
||||||
type: FlowInputItemTypeEnum.hidden,
|
// type: FlowInputItemTypeEnum.hidden,
|
||||||
label: '引用复用数量',
|
// label: '引用复用数量',
|
||||||
value: 1
|
// value: 1
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: SystemInputEnum.userChatInput,
|
// key: SystemInputEnum.userChatInput,
|
||||||
type: FlowInputItemTypeEnum.none,
|
// type: FlowInputItemTypeEnum.none,
|
||||||
label: '用户输入(系统自动填写)',
|
// label: '用户输入(系统自动填写)',
|
||||||
description: ''
|
// description: ''
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
outputs: [
|
// outputs: [
|
||||||
{
|
// {
|
||||||
key: 'rawSearch',
|
// key: 'rawSearch',
|
||||||
label: '源搜索数据',
|
// label: '源搜索数据',
|
||||||
type: FlowOutputItemTypeEnum.none,
|
// type: FlowOutputItemTypeEnum.none,
|
||||||
response: true,
|
// response: true,
|
||||||
targets: []
|
// targets: []
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'isEmpty',
|
// key: 'isEmpty',
|
||||||
label: '无搜索结果',
|
// label: '无搜索结果',
|
||||||
type: FlowOutputItemTypeEnum.source,
|
// type: FlowOutputItemTypeEnum.source,
|
||||||
targets: [
|
// targets: [
|
||||||
{
|
// {
|
||||||
moduleId: 'tfswitch',
|
// moduleId: 'tfswitch',
|
||||||
key: SystemInputEnum.switch
|
// key: SystemInputEnum.switch
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'quotePrompt',
|
// key: 'quotePrompt',
|
||||||
label: '引用内容(字符串)',
|
// label: '引用内容(字符串)',
|
||||||
type: FlowOutputItemTypeEnum.source,
|
// type: FlowOutputItemTypeEnum.source,
|
||||||
targets: [
|
// targets: [
|
||||||
{
|
// {
|
||||||
moduleId: 'chat',
|
// moduleId: 'chat',
|
||||||
key: 'quotePrompt'
|
// key: 'quotePrompt'
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
moduleId: 'tfswitch',
|
// moduleId: 'tfswitch',
|
||||||
type: AppModuleItemTypeEnum.switch,
|
// type: AppModuleItemTypeEnum.switch,
|
||||||
flowType: FlowModuleTypeEnum.tfSwitchNode,
|
// flowType: FlowModuleTypeEnum.tfSwitchNode,
|
||||||
position: { x: 0, y: 510 },
|
// position: { x: 0, y: 510 },
|
||||||
inputs: [
|
// inputs: [
|
||||||
{
|
// {
|
||||||
key: SystemInputEnum.switch,
|
// key: SystemInputEnum.switch,
|
||||||
type: FlowInputItemTypeEnum.target,
|
// type: FlowInputItemTypeEnum.target,
|
||||||
label: '触发器',
|
// label: '触发器',
|
||||||
connected: true
|
// connected: true
|
||||||
}
|
// }
|
||||||
],
|
// ],
|
||||||
outputs: [
|
// outputs: [
|
||||||
{
|
// {
|
||||||
key: 'true',
|
// key: 'true',
|
||||||
label: '无搜索数据',
|
// label: '无搜索数据',
|
||||||
type: FlowOutputItemTypeEnum.source,
|
// type: FlowOutputItemTypeEnum.source,
|
||||||
targets: [
|
// targets: [
|
||||||
{
|
// {
|
||||||
moduleId: 'answer',
|
// moduleId: 'answer',
|
||||||
key: SystemInputEnum.switch
|
// key: SystemInputEnum.switch
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'false',
|
// key: 'false',
|
||||||
label: '有搜索数据',
|
// label: '有搜索数据',
|
||||||
type: FlowOutputItemTypeEnum.source,
|
// type: FlowOutputItemTypeEnum.source,
|
||||||
targets: [
|
// targets: [
|
||||||
{
|
// {
|
||||||
moduleId: 'chat',
|
// moduleId: 'chat',
|
||||||
key: SystemInputEnum.switch
|
// key: SystemInputEnum.switch
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
...chatModule({ id: 'chat', limitPrompt: '参考知识库内容进行回答', history: 5 }),
|
// ...chatModule({ id: 'chat', limitPrompt: '参考知识库内容进行回答', history: 5 }),
|
||||||
position: { x: 300, y: 240 }
|
// position: { x: 300, y: 240 }
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
...answerModule({ id: 'answer' }),
|
// ...answerModule({ id: 'answer' }),
|
||||||
position: { x: 300, y: 0 }
|
// position: { x: 300, y: 0 }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
};
|
// };
|
||||||
|
|
||||||
// export const classifyQuestionDemo: AppItemType = {
|
// export const classifyQuestionDemo: AppItemType = {
|
||||||
// id: 'classifyQuestionDemo',
|
// id: 'classifyQuestionDemo',
|
||||||
|
|||||||
@ -25,4 +25,4 @@ export const ChatRoleMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const HUMAN_ICON = `https://fastgpt.run/icon/human.png`;
|
export const HUMAN_ICON = `https://fastgpt.run/icon/human.png`;
|
||||||
export const LOGO_ICON = `https://fastgpt.run/imgs/modelAvatar.png`;
|
export const LOGO_ICON = `https://fastgpt.run/icon/logo.png`;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AppModuleItemTypeEnum, SystemInputEnum } from '../app';
|
import { AppModuleItemTypeEnum, SystemInputEnum, SpecificInputEnum } from '../app';
|
||||||
import { FlowModuleTypeEnum, FlowInputItemTypeEnum, FlowOutputItemTypeEnum } from './index';
|
import { FlowModuleTypeEnum, FlowInputItemTypeEnum, FlowOutputItemTypeEnum } from './index';
|
||||||
import type { AppModuleTemplateItemType } from '@/types/app';
|
import type { AppModuleTemplateItemType } from '@/types/app';
|
||||||
import { chatModelList } from '../data';
|
import { chatModelList } from '../data';
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from './inputTemplate';
|
} from './inputTemplate';
|
||||||
|
|
||||||
export const UserInputModule: AppModuleTemplateItemType = {
|
export const UserInputModule: AppModuleTemplateItemType = {
|
||||||
logo: '',
|
logo: '/imgs/module/userChatInput.png',
|
||||||
name: '用户问题',
|
name: '用户问题',
|
||||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||||
type: AppModuleItemTypeEnum.initInput,
|
type: AppModuleItemTypeEnum.initInput,
|
||||||
@ -32,7 +32,7 @@ export const UserInputModule: AppModuleTemplateItemType = {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
export const HistoryModule: AppModuleTemplateItemType = {
|
export const HistoryModule: AppModuleTemplateItemType = {
|
||||||
logo: '',
|
logo: '/imgs/module/history.png',
|
||||||
name: '聊天记录',
|
name: '聊天记录',
|
||||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||||
type: AppModuleItemTypeEnum.initInput,
|
type: AppModuleItemTypeEnum.initInput,
|
||||||
@ -64,7 +64,7 @@ export const HistoryModule: AppModuleTemplateItemType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ChatModule: AppModuleTemplateItemType = {
|
export const ChatModule: AppModuleTemplateItemType = {
|
||||||
logo: '',
|
logo: '/imgs/module/AI.png',
|
||||||
name: 'AI 对话',
|
name: 'AI 对话',
|
||||||
intro: 'OpenAI GPT 大模型对话。',
|
intro: 'OpenAI GPT 大模型对话。',
|
||||||
flowType: FlowModuleTypeEnum.chatNode,
|
flowType: FlowModuleTypeEnum.chatNode,
|
||||||
@ -135,7 +135,7 @@ export const ChatModule: AppModuleTemplateItemType = {
|
|||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{
|
{
|
||||||
key: 'answer',
|
key: SpecificInputEnum.answerText,
|
||||||
label: '模型回复',
|
label: '模型回复',
|
||||||
description: '直接响应,无需配置',
|
description: '直接响应,无需配置',
|
||||||
type: FlowOutputItemTypeEnum.hidden,
|
type: FlowOutputItemTypeEnum.hidden,
|
||||||
@ -145,7 +145,7 @@ export const ChatModule: AppModuleTemplateItemType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const KBSearchModule: AppModuleTemplateItemType = {
|
export const KBSearchModule: AppModuleTemplateItemType = {
|
||||||
logo: '',
|
logo: '/imgs/module/db.png',
|
||||||
name: '知识库搜索',
|
name: '知识库搜索',
|
||||||
intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。',
|
intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。',
|
||||||
flowType: FlowModuleTypeEnum.kbSearchNode,
|
flowType: FlowModuleTypeEnum.kbSearchNode,
|
||||||
@ -205,7 +205,7 @@ export const KBSearchModule: AppModuleTemplateItemType = {
|
|||||||
{
|
{
|
||||||
key: 'quotePrompt',
|
key: 'quotePrompt',
|
||||||
label: '引用内容',
|
label: '引用内容',
|
||||||
description: '搜索结果为空时不触发',
|
description: '搜索结果为空时不返回',
|
||||||
type: FlowOutputItemTypeEnum.source,
|
type: FlowOutputItemTypeEnum.source,
|
||||||
targets: []
|
targets: []
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ export const KBSearchModule: AppModuleTemplateItemType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AnswerModule: AppModuleTemplateItemType = {
|
export const AnswerModule: AppModuleTemplateItemType = {
|
||||||
logo: '',
|
logo: '/imgs/module/reply.png',
|
||||||
name: '指定回复',
|
name: '指定回复',
|
||||||
intro: '该模块可以直接回复一段指定的内容。常用于引导、提示。',
|
intro: '该模块可以直接回复一段指定的内容。常用于引导、提示。',
|
||||||
type: AppModuleItemTypeEnum.answer,
|
type: AppModuleItemTypeEnum.answer,
|
||||||
@ -221,7 +221,7 @@ export const AnswerModule: AppModuleTemplateItemType = {
|
|||||||
inputs: [
|
inputs: [
|
||||||
Input_Template_TFSwitch,
|
Input_Template_TFSwitch,
|
||||||
{
|
{
|
||||||
key: 'answerText',
|
key: SpecificInputEnum.answerText,
|
||||||
value: '',
|
value: '',
|
||||||
type: FlowInputItemTypeEnum.input,
|
type: FlowInputItemTypeEnum.input,
|
||||||
label: '回复的内容'
|
label: '回复的内容'
|
||||||
@ -257,31 +257,52 @@ export const TFSwitchModule: AppModuleTemplateItemType = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClassifyQuestionModule: AppModuleTemplateItemType = {
|
export const ClassifyQuestionModule: AppModuleTemplateItemType = {
|
||||||
logo: '',
|
logo: '/imgs/module/cq.png',
|
||||||
name: '意图识别',
|
name: '意图识别',
|
||||||
intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。',
|
intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。',
|
||||||
type: AppModuleItemTypeEnum.switch,
|
type: AppModuleItemTypeEnum.http,
|
||||||
flowType: FlowModuleTypeEnum.tfSwitchNode,
|
url: '/openapi/modules/agent/classifyQuestion',
|
||||||
|
flowType: FlowModuleTypeEnum.classifyQuestionNode,
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
key: SystemInputEnum.switch,
|
key: 'systemPrompt',
|
||||||
type: FlowInputItemTypeEnum.target,
|
type: FlowInputItemTypeEnum.textarea,
|
||||||
label: '输入'
|
label: '系统提示词',
|
||||||
|
description:
|
||||||
|
'你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。',
|
||||||
|
placeholder: '例如: \n1. Laf 是一个云函数开发平台……\n2. Sealos 是一个集群操作系统',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
Input_Template_History,
|
||||||
|
Input_Template_UserChatInput,
|
||||||
|
{
|
||||||
|
key: 'agents',
|
||||||
|
type: FlowInputItemTypeEnum.custom,
|
||||||
|
label: '',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
key: 'a'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
key: 'b'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{
|
{
|
||||||
key: 'true',
|
key: 'a',
|
||||||
label: 'True',
|
label: '',
|
||||||
type: FlowOutputItemTypeEnum.source,
|
type: FlowOutputItemTypeEnum.hidden,
|
||||||
targets: []
|
targets: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'false',
|
key: 'b',
|
||||||
label: 'False',
|
label: '',
|
||||||
type: FlowOutputItemTypeEnum.source,
|
type: FlowOutputItemTypeEnum.hidden,
|
||||||
targets: []
|
targets: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -302,7 +323,7 @@ export const ModuleTemplates = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '工具',
|
label: '工具',
|
||||||
list: [AnswerModule, TFSwitchModule]
|
list: [AnswerModule]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Agent',
|
label: 'Agent',
|
||||||
|
|||||||
@ -24,7 +24,8 @@ export enum FlowModuleTypeEnum {
|
|||||||
chatNode = 'chatNode',
|
chatNode = 'chatNode',
|
||||||
kbSearchNode = 'kbSearchNode',
|
kbSearchNode = 'kbSearchNode',
|
||||||
tfSwitchNode = 'tfSwitchNode',
|
tfSwitchNode = 'tfSwitchNode',
|
||||||
answerNode = 'answerNode'
|
answerNode = 'answerNode',
|
||||||
|
classifyQuestionNode = 'classifyQuestionNode'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const edgeOptions = {
|
export const edgeOptions = {
|
||||||
|
|||||||
@ -202,6 +202,16 @@ const Select = selectMultiStyle({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Tooltip = defineStyleConfig({
|
||||||
|
baseStyle: {
|
||||||
|
p: 3,
|
||||||
|
bg: 'white',
|
||||||
|
color: 'blackAlpha.800',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '1px 1px 10px rgba(0,0,0,0.2)'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 全局主题
|
// 全局主题
|
||||||
export const theme = extendTheme({
|
export const theme = extendTheme({
|
||||||
styles: {
|
styles: {
|
||||||
@ -297,6 +307,7 @@ export const theme = extendTheme({
|
|||||||
Textarea,
|
Textarea,
|
||||||
Switch,
|
Switch,
|
||||||
Select,
|
Select,
|
||||||
|
Tooltip,
|
||||||
NumberInput
|
NumberInput
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export async function classifyQuestion({
|
|||||||
properties: {
|
properties: {
|
||||||
type: {
|
type: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: agents.map((item) => `${item.desc},返回: '${item.key}'`).join('; '),
|
description: agents.map((item) => `${item.value},返回: '${item.key}'`).join('; '),
|
||||||
enum: agents.map((item) => item.key)
|
enum: agents.map((item) => item.key)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -106,7 +106,10 @@ export async function classifyQuestion({
|
|||||||
if (!arg.type) {
|
if (!arg.type) {
|
||||||
throw new Error('');
|
throw new Error('');
|
||||||
}
|
}
|
||||||
console.log(arg.type);
|
console.log(
|
||||||
|
'意图结果',
|
||||||
|
agents.findIndex((item) => item.key === arg.type)
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[arg.type]: 1
|
[arg.type]: 1
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type { ChatItemType } from '@/types/chat';
|
|||||||
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
|
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
|
||||||
import { parseStreamChunk, textAdaptGptResponse } from '@/utils/adapt';
|
import { parseStreamChunk, textAdaptGptResponse } from '@/utils/adapt';
|
||||||
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
|
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
|
||||||
|
import { SpecificInputEnum } from '@/constants/app';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
model: `${OpenAiChatEnum}`;
|
model: `${OpenAiChatEnum}`;
|
||||||
@ -22,7 +23,7 @@ export type Props = {
|
|||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
limitPrompt?: string;
|
limitPrompt?: string;
|
||||||
};
|
};
|
||||||
export type Response = { answer: string };
|
export type Response = { [SpecificInputEnum.answerText]: string };
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
@ -132,7 +133,8 @@ export async function chatCompletion({
|
|||||||
const chatAPI = getOpenAIApi();
|
const chatAPI = getOpenAIApi();
|
||||||
|
|
||||||
/* count response max token */
|
/* count response max token */
|
||||||
const promptsToken = modelToolMap[model].countTokens({
|
const promptsToken = modelToolMap.countTokens({
|
||||||
|
model,
|
||||||
messages: filterMessages
|
messages: filterMessages
|
||||||
});
|
});
|
||||||
maxToken = maxToken + promptsToken > modelTokenLimit ? modelTokenLimit - promptsToken : maxToken;
|
maxToken = maxToken + promptsToken > modelTokenLimit ? modelTokenLimit - promptsToken : maxToken;
|
||||||
@ -143,8 +145,8 @@ export async function chatCompletion({
|
|||||||
temperature: Number(temperature || 0),
|
temperature: Number(temperature || 0),
|
||||||
max_tokens: maxToken,
|
max_tokens: maxToken,
|
||||||
messages: adaptMessages,
|
messages: adaptMessages,
|
||||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
// frequency_penalty: 0.5, // 越大,重复内容越少
|
||||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
// presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||||
stream
|
stream
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -184,7 +186,7 @@ export async function chatCompletion({
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
answer
|
answerText: answer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -92,8 +92,9 @@ export async function kbSearch({
|
|||||||
const searchRes: QuoteItemType[] = res?.[2]?.rows || [];
|
const searchRes: QuoteItemType[] = res?.[2]?.rows || [];
|
||||||
|
|
||||||
// filter part quote by maxToken
|
// filter part quote by maxToken
|
||||||
const sliceResult = modelToolMap['gpt-3.5-turbo']
|
const sliceResult = modelToolMap
|
||||||
.tokenSlice({
|
.tokenSlice({
|
||||||
|
model: 'gpt-3.5-turbo',
|
||||||
maxToken,
|
maxToken,
|
||||||
messages: searchRes.map((item, i) => ({
|
messages: searchRes.map((item, i) => ({
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
|
|||||||
@ -10,12 +10,7 @@ import { getChatHistory } from './getHistory';
|
|||||||
import { saveChat } from '@/pages/api/chat/saveChat';
|
import { saveChat } from '@/pages/api/chat/saveChat';
|
||||||
import { sseResponse } from '@/service/utils/tools';
|
import { sseResponse } from '@/service/utils/tools';
|
||||||
import { type ChatCompletionRequestMessage } from 'openai';
|
import { type ChatCompletionRequestMessage } from 'openai';
|
||||||
import {
|
import { SpecificInputEnum, AppModuleItemTypeEnum } from '@/constants/app';
|
||||||
kbChatAppDemo,
|
|
||||||
chatAppDemo,
|
|
||||||
SpecificInputEnum,
|
|
||||||
AppModuleItemTypeEnum
|
|
||||||
} from '@/constants/app';
|
|
||||||
import { model, Types } from 'mongoose';
|
import { model, Types } from 'mongoose';
|
||||||
import { moduleFetch } from '@/service/api/request';
|
import { moduleFetch } from '@/service/api/request';
|
||||||
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
|
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
|
||||||
@ -42,7 +37,6 @@ export type ChatResponseType = {
|
|||||||
quoteLen?: number;
|
quoteLen?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 发送提示词 */
|
|
||||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
res.on('close', () => {
|
res.on('close', () => {
|
||||||
res.end();
|
res.end();
|
||||||
@ -117,7 +111,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
},
|
},
|
||||||
stream
|
stream
|
||||||
});
|
});
|
||||||
console.log(responseData, answerText);
|
|
||||||
|
|
||||||
// save chat
|
// save chat
|
||||||
if (typeof chatId === 'string') {
|
if (typeof chatId === 'string') {
|
||||||
@ -354,7 +347,7 @@ function loadModules(modules: AppModuleItemType[]): RunningModuleItemType[] {
|
|||||||
})),
|
})),
|
||||||
outputs: module.outputs.map((item) => ({
|
outputs: module.outputs.map((item) => ({
|
||||||
key: item.key,
|
key: item.key,
|
||||||
answer: item.type === FlowOutputItemTypeEnum.answer,
|
answer: item.key === SpecificInputEnum.answerText,
|
||||||
response: item.response,
|
response: item.response,
|
||||||
value: undefined,
|
value: undefined,
|
||||||
targets: item.targets
|
targets: item.targets
|
||||||
|
|||||||
@ -1,394 +0,0 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
Flex,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
useDisclosure,
|
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalBody,
|
|
||||||
ModalHeader,
|
|
||||||
ModalFooter,
|
|
||||||
ModalCloseButton,
|
|
||||||
Grid,
|
|
||||||
useTheme,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
Textarea
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { useUserStore } from '@/store/user';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import { AddIcon, DeleteIcon, QuestionOutlineIcon } from '@chakra-ui/icons';
|
|
||||||
import { putAppById } from '@/api/app';
|
|
||||||
import { useToast } from '@/hooks/useToast';
|
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import MyIcon from '@/components/Icon';
|
|
||||||
import MySlider from '@/components/Slider';
|
|
||||||
|
|
||||||
const Kb = ({ modelId }: { modelId: string }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const router = useRouter();
|
|
||||||
const { toast } = useToast();
|
|
||||||
const { appDetail, loadKbList, loadAppDetail } = useUserStore();
|
|
||||||
const { Loading, setIsLoading } = useLoading();
|
|
||||||
const [selectedIdList, setSelectedIdList] = useState<string[]>([]);
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
|
||||||
const { register, reset, getValues, setValue } = useForm({
|
|
||||||
defaultValues: {
|
|
||||||
searchSimilarity: appDetail.chat.searchSimilarity,
|
|
||||||
searchLimit: appDetail.chat.searchLimit,
|
|
||||||
searchEmptyText: appDetail.chat.searchEmptyText
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
isOpen: isOpenKbSelect,
|
|
||||||
onOpen: onOpenKbSelect,
|
|
||||||
onClose: onCloseKbSelect
|
|
||||||
} = useDisclosure();
|
|
||||||
const {
|
|
||||||
isOpen: isOpenEditParams,
|
|
||||||
onOpen: onOpenEditParams,
|
|
||||||
onClose: onCloseEditParams
|
|
||||||
} = useDisclosure();
|
|
||||||
|
|
||||||
const onchangeKb = useCallback(
|
|
||||||
async (
|
|
||||||
data: {
|
|
||||||
relatedKbs?: string[];
|
|
||||||
searchSimilarity?: number;
|
|
||||||
searchLimit?: number;
|
|
||||||
searchEmptyText?: string;
|
|
||||||
} = {}
|
|
||||||
) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
await putAppById(modelId, {
|
|
||||||
chat: {
|
|
||||||
...appDetail.chat,
|
|
||||||
...data
|
|
||||||
}
|
|
||||||
});
|
|
||||||
loadAppDetail(modelId, true);
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '更新失败',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
},
|
|
||||||
[setIsLoading, modelId, appDetail.chat, loadAppDetail, toast]
|
|
||||||
);
|
|
||||||
|
|
||||||
// init kb select list
|
|
||||||
const { isLoading, data: kbList = [] } = useQuery(['loadKbList'], () => loadKbList());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box position={'relative'} px={5} minH={'50vh'}>
|
|
||||||
<Box fontWeight={'bold'}>关联的知识库({appDetail.chat?.relatedKbs.length})</Box>
|
|
||||||
{(() => {
|
|
||||||
const kbs =
|
|
||||||
appDetail.chat?.relatedKbs
|
|
||||||
?.map((id) => kbList.find((kb) => kb._id === id))
|
|
||||||
.filter((item) => item) || [];
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
mt={2}
|
|
||||||
gridTemplateColumns={[
|
|
||||||
'repeat(1,1fr)',
|
|
||||||
'repeat(2,1fr)',
|
|
||||||
'repeat(3,1fr)',
|
|
||||||
'repeat(4,1fr)'
|
|
||||||
]}
|
|
||||||
gridGap={[3, 4]}
|
|
||||||
>
|
|
||||||
<Card
|
|
||||||
p={3}
|
|
||||||
border={theme.borders.base}
|
|
||||||
boxShadow={'sm'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
bg={'myGray.100'}
|
|
||||||
_hover={{
|
|
||||||
bg: 'white',
|
|
||||||
color: 'myBlue.800'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
reset({
|
|
||||||
searchSimilarity: appDetail.chat.searchSimilarity,
|
|
||||||
searchLimit: appDetail.chat.searchLimit,
|
|
||||||
searchEmptyText: appDetail.chat.searchEmptyText
|
|
||||||
});
|
|
||||||
onOpenEditParams();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'} h={'38px'} fontWeight={'bold'}>
|
|
||||||
<IconButton
|
|
||||||
mr={2}
|
|
||||||
size={'sm'}
|
|
||||||
borderRadius={'lg'}
|
|
||||||
icon={<MyIcon name={'edit'} w={'14px'} color={'myGray.600'} />}
|
|
||||||
aria-label={''}
|
|
||||||
variant={'base'}
|
|
||||||
/>
|
|
||||||
调整搜索参数
|
|
||||||
</Flex>
|
|
||||||
<Flex mt={3} h={'30px'} color={'myGray.600'} fontSize={'sm'}>
|
|
||||||
相似度: {appDetail.chat.searchSimilarity}, 单次搜索数量:{' '}
|
|
||||||
{appDetail.chat.searchLimit}, 空搜索时拒绝回复:{' '}
|
|
||||||
{appDetail.chat.searchEmptyText !== '' ? 'true' : 'false'}
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
<Card
|
|
||||||
p={3}
|
|
||||||
border={theme.borders.base}
|
|
||||||
boxShadow={'sm'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
bg={'myGray.100'}
|
|
||||||
_hover={{
|
|
||||||
bg: 'white',
|
|
||||||
color: 'myBlue.800'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedIdList(
|
|
||||||
appDetail.chat?.relatedKbs ? [...appDetail.chat?.relatedKbs] : []
|
|
||||||
);
|
|
||||||
onOpenKbSelect();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'} h={'38px'} fontWeight={'bold'}>
|
|
||||||
<IconButton
|
|
||||||
mr={2}
|
|
||||||
size={'sm'}
|
|
||||||
borderRadius={'lg'}
|
|
||||||
icon={<AddIcon />}
|
|
||||||
aria-label={''}
|
|
||||||
variant={'base'}
|
|
||||||
/>
|
|
||||||
选择关联知识库
|
|
||||||
</Flex>
|
|
||||||
<Flex mt={3} h={'30px'} color={'myGray.600'} fontSize={'sm'}>
|
|
||||||
关联知识库,让 AI 应用回答你的特有内容。
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
{kbs.map((item) =>
|
|
||||||
item ? (
|
|
||||||
<Card
|
|
||||||
key={item._id}
|
|
||||||
p={3}
|
|
||||||
border={theme.borders.base}
|
|
||||||
boxShadow={'sm'}
|
|
||||||
_hover={{
|
|
||||||
boxShadow: 'lg',
|
|
||||||
'& .detailBtn': {
|
|
||||||
display: 'block'
|
|
||||||
},
|
|
||||||
'& .delete': {
|
|
||||||
display: 'block'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'} h={'38px'}>
|
|
||||||
<Avatar src={item.avatar} w={['26px', '32px', '38px']}></Avatar>
|
|
||||||
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
|
|
||||||
{item.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex mt={3} alignItems={'flex-end'} justifyContent={'flex-end'} h={'30px'}>
|
|
||||||
<Button
|
|
||||||
mr={3}
|
|
||||||
className="detailBtn"
|
|
||||||
display={['flex', 'none']}
|
|
||||||
variant={'base'}
|
|
||||||
size={'sm'}
|
|
||||||
onClick={() => router.push(`/kb?kbId=${item._id}`)}
|
|
||||||
>
|
|
||||||
查看详情
|
|
||||||
</Button>
|
|
||||||
<IconButton
|
|
||||||
className="delete"
|
|
||||||
display={['flex', 'none']}
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
variant={'outline'}
|
|
||||||
aria-label={'delete'}
|
|
||||||
size={'sm'}
|
|
||||||
_hover={{ color: 'red.600' }}
|
|
||||||
onClick={() => {
|
|
||||||
const ids = appDetail.chat?.relatedKbs
|
|
||||||
? [...appDetail.chat.relatedKbs]
|
|
||||||
: [];
|
|
||||||
const i = ids.findIndex((id) => id === item._id);
|
|
||||||
ids.splice(i, 1);
|
|
||||||
onchangeKb({ relatedKbs: ids });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
) : null
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
{/* select kb modal */}
|
|
||||||
<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}
|
|
||||||
>
|
|
||||||
{kbList.map((item) => (
|
|
||||||
<Card
|
|
||||||
key={item._id}
|
|
||||||
p={3}
|
|
||||||
border={theme.borders.base}
|
|
||||||
boxShadow={'sm'}
|
|
||||||
h={'80px'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
order={appDetail.chat?.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) => kbList.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();
|
|
||||||
onchangeKb({ relatedKbs: selectedIdList });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
完成
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
{/* edit mode */}
|
|
||||||
<Modal isOpen={isOpenEditParams} onClose={onCloseEditParams}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent display={'flex'} flexDirection={'column'} w={'600px'} maxW={'90vw'}>
|
|
||||||
<ModalHeader>搜索参数调整</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
<Flex pt={3} pb={5}>
|
|
||||||
<Box flex={'0 0 100px'}>
|
|
||||||
相似度
|
|
||||||
<Tooltip label={'高相似度推荐0.8及以上。'}>
|
|
||||||
<QuestionOutlineIcon ml={1} />
|
|
||||||
</Tooltip>
|
|
||||||
</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={onCloseEditParams}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
onCloseEditParams();
|
|
||||||
onchangeKb({
|
|
||||||
searchSimilarity: getValues('searchSimilarity'),
|
|
||||||
searchLimit: getValues('searchLimit'),
|
|
||||||
searchEmptyText: getValues('searchEmptyText')
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
完成
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
<Loading loading={isLoading} fixed={false} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Kb;
|
|
||||||
@ -2,7 +2,6 @@ import React, { useCallback, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
Box,
|
Box,
|
||||||
Tooltip,
|
|
||||||
Button,
|
Button,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Table,
|
Table,
|
||||||
@ -37,6 +36,7 @@ import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { defaultShareChat } from '@/constants/model';
|
import { defaultShareChat } from '@/constants/model';
|
||||||
import type { ShareChatEditType } from '@/types/app';
|
import type { ShareChatEditType } from '@/types/app';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const Share = ({ modelId }: { modelId: string }) => {
|
const Share = ({ modelId }: { modelId: string }) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -112,9 +112,9 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
|
|||||||
<Flex justifyContent={'space-between'}>
|
<Flex justifyContent={'space-between'}>
|
||||||
<Box fontWeight={'bold'}>
|
<Box fontWeight={'bold'}>
|
||||||
免登录聊天窗口
|
免登录聊天窗口
|
||||||
<Tooltip label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的tokens。请保管好链接和密码。">
|
<MyTooltip label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的tokens。请保管好链接和密码。">
|
||||||
<QuestionOutlineIcon ml={1} />
|
<QuestionOutlineIcon ml={1} />
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
variant={'base'}
|
variant={'base'}
|
||||||
|
|||||||
@ -10,13 +10,7 @@ const NodeAnswer = ({
|
|||||||
data: { moduleId, inputs, outputs, onChangeNode, ...props }
|
data: { moduleId, inputs, outputs, onChangeNode, ...props }
|
||||||
}: NodeProps<FlowModuleItemType>) => {
|
}: NodeProps<FlowModuleItemType>) => {
|
||||||
return (
|
return (
|
||||||
<NodeCard
|
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
|
||||||
minW={'400px'}
|
|
||||||
logo={'/icon/logo.png'}
|
|
||||||
name={'SSE 响应'}
|
|
||||||
moduleId={moduleId}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<Divider text="Input" />
|
<Divider text="Input" />
|
||||||
<Container>
|
<Container>
|
||||||
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
|
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
|
||||||
|
|||||||
@ -0,0 +1,136 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NodeProps } from 'reactflow';
|
||||||
|
import { Box, Input, Button, Flex } from '@chakra-ui/react';
|
||||||
|
import NodeCard from './modules/NodeCard';
|
||||||
|
import { FlowModuleItemType } from '@/types/flow';
|
||||||
|
import Divider from './modules/Divider';
|
||||||
|
import Container from './modules/Container';
|
||||||
|
import RenderInput from './render/RenderInput';
|
||||||
|
import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
||||||
|
import { Handle, Position } from 'reactflow';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
import { FlowOutputItemTypeEnum } from '@/constants/flow';
|
||||||
|
|
||||||
|
const NodeCQNode = ({
|
||||||
|
data: { moduleId, inputs, outputs, onChangeNode, ...props }
|
||||||
|
}: NodeProps<FlowModuleItemType>) => {
|
||||||
|
return (
|
||||||
|
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
|
||||||
|
<Divider text="Input" />
|
||||||
|
<Container>
|
||||||
|
<RenderInput
|
||||||
|
moduleId={moduleId}
|
||||||
|
onChangeNode={onChangeNode}
|
||||||
|
flowInputList={inputs}
|
||||||
|
CustomComponent={{
|
||||||
|
agents: ({
|
||||||
|
key: agentKey,
|
||||||
|
value: agents = []
|
||||||
|
}: {
|
||||||
|
key: string;
|
||||||
|
value?: ClassifyQuestionAgentItemType[];
|
||||||
|
}) => (
|
||||||
|
<Box>
|
||||||
|
{agents.map((item, i) => (
|
||||||
|
<Flex key={item.key} mb={4} alignItems={'center'}>
|
||||||
|
<MyIcon
|
||||||
|
mr={2}
|
||||||
|
name={'minus'}
|
||||||
|
w={'14px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
color={'myGray.600'}
|
||||||
|
_hover={{ color: 'myGray.900' }}
|
||||||
|
onClick={() => {
|
||||||
|
const newInputValue = agents.filter((input) => input.key !== item.key);
|
||||||
|
const newOutputVal = outputs.filter((output) => output.key !== item.key);
|
||||||
|
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
type: 'inputs',
|
||||||
|
key: agentKey,
|
||||||
|
value: newInputValue
|
||||||
|
});
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
type: 'outputs',
|
||||||
|
key: agentKey,
|
||||||
|
value: newOutputVal
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box flex={1}>
|
||||||
|
<Box flex={1}>意图{i + 1}</Box>
|
||||||
|
<Box position={'relative'}>
|
||||||
|
<Input
|
||||||
|
mt={1}
|
||||||
|
defaultValue={item.value}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newVal = agents.map((val) =>
|
||||||
|
val.key === item.key
|
||||||
|
? {
|
||||||
|
...val,
|
||||||
|
value: e.target.value
|
||||||
|
}
|
||||||
|
: val
|
||||||
|
);
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
key: agentKey,
|
||||||
|
value: newVal
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
style={{
|
||||||
|
top: '50%',
|
||||||
|
right: '-14px',
|
||||||
|
transform: 'translate(50%,-50%)',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
background: '#9CA2A8'
|
||||||
|
}}
|
||||||
|
type="source"
|
||||||
|
id={item.key}
|
||||||
|
position={Position.Right}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const key = nanoid();
|
||||||
|
const newInputValue = agents.concat({ value: '', key });
|
||||||
|
const newOutputValue = outputs.concat({
|
||||||
|
key,
|
||||||
|
label: '',
|
||||||
|
type: FlowOutputItemTypeEnum.hidden,
|
||||||
|
targets: []
|
||||||
|
});
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
type: 'inputs',
|
||||||
|
key: agentKey,
|
||||||
|
value: newInputValue
|
||||||
|
});
|
||||||
|
onChangeNode({
|
||||||
|
moduleId,
|
||||||
|
type: 'outputs',
|
||||||
|
key: agentKey,
|
||||||
|
value: newOutputValue
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
添加意图
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
</NodeCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default React.memo(NodeCQNode);
|
||||||
@ -26,7 +26,7 @@ const NodeKbSearch = ({
|
|||||||
onChangeNode={onChangeNode}
|
onChangeNode={onChangeNode}
|
||||||
flowInputList={inputs}
|
flowInputList={inputs}
|
||||||
CustomComponent={{
|
CustomComponent={{
|
||||||
kb_ids: ({ key, value, onChangeNode }) => (
|
kb_ids: ({ key, value }) => (
|
||||||
<KBSelect
|
<KBSelect
|
||||||
relatedKbs={value}
|
relatedKbs={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex, useOutsideClick } from '@chakra-ui/react';
|
||||||
import { ModuleTemplates } from '@/constants/flow/ModuleTemplate';
|
import { ModuleTemplates } from '@/constants/flow/ModuleTemplate';
|
||||||
import type { AppModuleTemplateItemType } from '@/types/app';
|
import type { AppModuleTemplateItemType } from '@/types/app';
|
||||||
import type { XYPosition } from 'reactflow';
|
import type { XYPosition } from 'reactflow';
|
||||||
@ -7,62 +7,85 @@ import Avatar from '@/components/Avatar';
|
|||||||
|
|
||||||
const ModuleStoreList = ({
|
const ModuleStoreList = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onAddNode
|
onAddNode,
|
||||||
|
onClose
|
||||||
}: {
|
}: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onAddNode: (e: { template: AppModuleTemplateItemType; position: XYPosition }) => void;
|
onAddNode: (e: { template: AppModuleTemplateItemType; position: XYPosition }) => void;
|
||||||
|
onClose: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const ContextMenuRef = useRef(null);
|
||||||
|
|
||||||
|
useOutsideClick({
|
||||||
|
ref: ContextMenuRef,
|
||||||
|
handler: () => {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<>
|
||||||
flexDirection={'column'}
|
<Box
|
||||||
position={'absolute'}
|
zIndex={2}
|
||||||
top={'65px'}
|
display={isOpen ? 'block' : 'none'}
|
||||||
left={0}
|
position={'fixed'}
|
||||||
h={isOpen ? '90%' : '0'}
|
top={0}
|
||||||
w={isOpen ? '360px' : '0'}
|
left={0}
|
||||||
bg={'white'}
|
right={0}
|
||||||
zIndex={1}
|
bottom={0}
|
||||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
></Box>
|
||||||
borderRadius={'20px'}
|
<Flex
|
||||||
overflow={'hidden'}
|
zIndex={3}
|
||||||
transition={'.2s ease'}
|
ref={ContextMenuRef}
|
||||||
px={'15px'}
|
flexDirection={'column'}
|
||||||
userSelect={'none'}
|
position={'absolute'}
|
||||||
>
|
top={'65px'}
|
||||||
<Box w={'330px'} py={4} fontSize={'xl'} fontWeight={'bold'}>
|
left={0}
|
||||||
添加模块
|
h={isOpen ? '90%' : '0'}
|
||||||
</Box>
|
w={isOpen ? '360px' : '0'}
|
||||||
<Box w={'330px'} flex={'1 0 0'} overflow={'overlay'}>
|
bg={'white'}
|
||||||
{ModuleTemplates.map((item) =>
|
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||||
item.list.map((item) => (
|
borderRadius={'20px'}
|
||||||
<Flex
|
overflow={'hidden'}
|
||||||
key={item.name}
|
transition={'.2s ease'}
|
||||||
alignItems={'center'}
|
px={'15px'}
|
||||||
p={5}
|
userSelect={'none'}
|
||||||
cursor={'pointer'}
|
>
|
||||||
_hover={{ bg: 'myWhite.600' }}
|
<Box w={'330px'} py={4} fontSize={'xl'} fontWeight={'bold'}>
|
||||||
borderRadius={'md'}
|
添加模块
|
||||||
draggable
|
</Box>
|
||||||
onDragEnd={(e) => {
|
<Box w={'330px'} flex={'1 0 0'} overflow={'overlay'}>
|
||||||
if (e.clientX < 400) return;
|
{ModuleTemplates.map((item) =>
|
||||||
onAddNode({
|
item.list.map((item) => (
|
||||||
template: item,
|
<Flex
|
||||||
position: { x: e.clientX, y: e.clientY }
|
key={item.name}
|
||||||
});
|
alignItems={'center'}
|
||||||
}}
|
p={5}
|
||||||
>
|
cursor={'pointer'}
|
||||||
<Avatar src={item.logo} w={'34px'} />
|
_hover={{ bg: 'myWhite.600' }}
|
||||||
<Box ml={5} flex={'1 0 0'}>
|
borderRadius={'md'}
|
||||||
<Box color={'black'}>{item.name}</Box>
|
draggable
|
||||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
onDragEnd={(e) => {
|
||||||
{item.intro}
|
// if (e.clientX < 400) return;
|
||||||
|
onAddNode({
|
||||||
|
template: item,
|
||||||
|
position: { x: e.clientX, y: e.clientY }
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar src={item.logo} w={'34px'} borderRadius={'0'} />
|
||||||
|
<Box ml={5} flex={'1 0 0'}>
|
||||||
|
<Box color={'black'}>{item.name}</Box>
|
||||||
|
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||||
|
{item.intro}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Flex>
|
||||||
</Flex>
|
))
|
||||||
))
|
)}
|
||||||
)}
|
</Box>
|
||||||
</Box>
|
</Flex>
|
||||||
</Flex>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Tooltip } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const Label = ({
|
const Label = ({
|
||||||
required = false,
|
required = false,
|
||||||
@ -19,9 +20,9 @@ const Label = ({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{description && (
|
{description && (
|
||||||
<Tooltip label={description}>
|
<MyTooltip label={description}>
|
||||||
<QuestionOutlineIcon display={['none', 'inline']} fontSize={'12px'} mb={1} ml={1} />
|
<QuestionOutlineIcon display={['none', 'inline']} fontSize={'12px'} mb={1} ml={1} />
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Textarea,
|
Textarea,
|
||||||
Input,
|
Input,
|
||||||
Tooltip,
|
|
||||||
NumberInput,
|
NumberInput,
|
||||||
NumberInputField,
|
NumberInputField,
|
||||||
NumberInputStepper,
|
NumberInputStepper,
|
||||||
@ -16,6 +15,7 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
|||||||
import { Handle, Position } from 'reactflow';
|
import { Handle, Position } from 'reactflow';
|
||||||
import MySelect from '@/components/Select';
|
import MySelect from '@/components/Select';
|
||||||
import MySlider from '@/components/Slider';
|
import MySlider from '@/components/Slider';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const Label = ({
|
const Label = ({
|
||||||
required = false,
|
required = false,
|
||||||
@ -34,9 +34,9 @@ const Label = ({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{description && (
|
{description && (
|
||||||
<Tooltip label={description}>
|
<MyTooltip label={description}>
|
||||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -49,14 +49,7 @@ const RenderBody = ({
|
|||||||
}: {
|
}: {
|
||||||
flowInputList: FlowInputItemType[];
|
flowInputList: FlowInputItemType[];
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
CustomComponent?: Record<
|
CustomComponent?: Record<string, (e: FlowInputItemType) => React.ReactNode>;
|
||||||
string,
|
|
||||||
(e: {
|
|
||||||
key: string;
|
|
||||||
value: any;
|
|
||||||
onChangeNode: FlowModuleItemType['onChangeNode'];
|
|
||||||
}) => React.ReactNode
|
|
||||||
>;
|
|
||||||
onChangeNode: FlowModuleItemType['onChangeNode'];
|
onChangeNode: FlowModuleItemType['onChangeNode'];
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@ -65,9 +58,11 @@ const RenderBody = ({
|
|||||||
(item) =>
|
(item) =>
|
||||||
item.type !== FlowInputItemTypeEnum.hidden && (
|
item.type !== FlowInputItemTypeEnum.hidden && (
|
||||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||||
<Label required={item.required} description={item.description}>
|
{!!item.label && (
|
||||||
{item.label}
|
<Label required={item.required} description={item.description}>
|
||||||
</Label>
|
{item.label}
|
||||||
|
</Label>
|
||||||
|
)}
|
||||||
<Box mt={2} className={'nodrag'}>
|
<Box mt={2} className={'nodrag'}>
|
||||||
{item.type === FlowInputItemTypeEnum.numberInput && (
|
{item.type === FlowInputItemTypeEnum.numberInput && (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
@ -151,9 +146,7 @@ const RenderBody = ({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{item.type === FlowInputItemTypeEnum.custom && CustomComponent[item.key] && (
|
{item.type === FlowInputItemTypeEnum.custom && CustomComponent[item.key] && (
|
||||||
<>
|
<>{CustomComponent[item.key]({ ...item })}</>
|
||||||
{CustomComponent[item.key]({ key: item.key, value: item.value, onChangeNode })}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{item.type === FlowInputItemTypeEnum.target && (
|
{item.type === FlowInputItemTypeEnum.target && (
|
||||||
<Handle
|
<Handle
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { FlowOutputItemType } from '@/types/flow';
|
import type { FlowOutputItemType } from '@/types/flow';
|
||||||
import { Box, Tooltip, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import { FlowOutputItemTypeEnum } from '@/constants/flow';
|
import { FlowOutputItemTypeEnum } from '@/constants/flow';
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
import { Handle, Position } from 'reactflow';
|
import { Handle, Position } from 'reactflow';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const Label = ({
|
const Label = ({
|
||||||
children,
|
children,
|
||||||
@ -14,9 +15,9 @@ const Label = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<Flex as={'label'} justifyContent={'right'} alignItems={'center'} position={'relative'}>
|
<Flex as={'label'} justifyContent={'right'} alignItems={'center'} position={'relative'}>
|
||||||
{description && (
|
{description && (
|
||||||
<Tooltip label={description}>
|
<MyTooltip label={description}>
|
||||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
Controls,
|
Controls,
|
||||||
@ -7,7 +7,8 @@ import ReactFlow, {
|
|||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
Connection
|
Connection,
|
||||||
|
useViewport
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||||
@ -47,6 +48,9 @@ const NodeQuestionInput = dynamic(() => import('./components/NodeQuestionInput')
|
|||||||
const TemplateList = dynamic(() => import('./components/TemplateList'), {
|
const TemplateList = dynamic(() => import('./components/TemplateList'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
|
const NodeCQNode = dynamic(() => import('./components/NodeCQNode'), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
|
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
@ -60,14 +64,18 @@ const nodeTypes = {
|
|||||||
[FlowModuleTypeEnum.chatNode]: NodeChat,
|
[FlowModuleTypeEnum.chatNode]: NodeChat,
|
||||||
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
|
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
|
||||||
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
|
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
|
||||||
[FlowModuleTypeEnum.answerNode]: NodeAnswer
|
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
|
||||||
|
[FlowModuleTypeEnum.classifyQuestionNode]: NodeCQNode
|
||||||
};
|
};
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
buttonedge: ButtonEdge
|
buttonedge: ButtonEdge
|
||||||
};
|
};
|
||||||
|
type Props = { app: AppSchema; onBack: () => void };
|
||||||
|
|
||||||
const AppEdit = ({ app, onBack }: { app: AppSchema; onBack: () => void }) => {
|
const AppEdit = ({ app, onBack }: Props) => {
|
||||||
|
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { x, y, zoom } = useViewport();
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
const {
|
const {
|
||||||
@ -77,24 +85,33 @@ const AppEdit = ({ app, onBack }: { app: AppSchema; onBack: () => void }) => {
|
|||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
const onChangeNode = useCallback(
|
const onChangeNode = useCallback(
|
||||||
({ moduleId, key, value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
|
({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
|
||||||
setNodes((nodes) =>
|
setNodes((nodes) =>
|
||||||
nodes.map((node) => {
|
nodes.map((node) => {
|
||||||
if (node.id !== moduleId) return node;
|
if (node.id !== moduleId) return node;
|
||||||
|
if (type === 'inputs') {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
inputs: node.data.inputs.map((item) => {
|
||||||
|
if (item.key === key) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
[valueKey]: value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
data: {
|
data: {
|
||||||
...node.data,
|
...node.data,
|
||||||
inputs: node.data.inputs.map((item) => {
|
outputs: value
|
||||||
if (item.key === key) {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
[valueKey]: value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -111,12 +128,17 @@ const AppEdit = ({ app, onBack }: { app: AppSchema; onBack: () => void }) => {
|
|||||||
);
|
);
|
||||||
const onAddNode = useCallback(
|
const onAddNode = useCallback(
|
||||||
({ template, position }: { template: AppModuleTemplateItemType; position: XYPosition }) => {
|
({ template, position }: { template: AppModuleTemplateItemType; position: XYPosition }) => {
|
||||||
|
if (!reactFlowWrapper.current) return;
|
||||||
|
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||||
|
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
|
||||||
|
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
|
||||||
|
|
||||||
setNodes((state) =>
|
setNodes((state) =>
|
||||||
state.concat(
|
state.concat(
|
||||||
appModule2FlowNode({
|
appModule2FlowNode({
|
||||||
item: {
|
item: {
|
||||||
...template,
|
...template,
|
||||||
position,
|
position: { x: mouseX, y: mouseY },
|
||||||
moduleId: nanoid()
|
moduleId: nanoid()
|
||||||
},
|
},
|
||||||
onChangeNode,
|
onChangeNode,
|
||||||
@ -125,7 +147,7 @@ const AppEdit = ({ app, onBack }: { app: AppSchema; onBack: () => void }) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[onChangeNode, onDelNode, setNodes]
|
[onChangeNode, onDelNode, setNodes, x, y, zoom]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDelConnect = useCallback(
|
const onDelConnect = useCallback(
|
||||||
@ -245,6 +267,13 @@ const AppEdit = ({ app, onBack }: { app: AppSchema; onBack: () => void }) => {
|
|||||||
borderRadius={'lg'}
|
borderRadius={'lg'}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
aria-label={'save'}
|
aria-label={'save'}
|
||||||
|
bg={'myBlue.200'}
|
||||||
|
variant={'base'}
|
||||||
|
border={'none'}
|
||||||
|
color={'myGray.900'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'myBlue.300'
|
||||||
|
}}
|
||||||
onClick={onclickSave}
|
onClick={onclickSave}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -270,43 +299,50 @@ const AppEdit = ({ app, onBack }: { app: AppSchema; onBack: () => void }) => {
|
|||||||
transition={'0.2s ease'}
|
transition={'0.2s ease'}
|
||||||
aria-label={''}
|
aria-label={''}
|
||||||
zIndex={1}
|
zIndex={1}
|
||||||
boxShadow={'1px 1px 6px #4e83fd'}
|
boxShadow={'2px 2px 6px #85b1ff'}
|
||||||
onClick={() => (isOpenTemplate ? onCloseTemplate() : onOpenTemplate())}
|
onClick={() => {
|
||||||
|
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ReactFlowProvider>
|
<ReactFlow
|
||||||
<ReactFlow
|
ref={reactFlowWrapper}
|
||||||
className={styles.panel}
|
className={styles.panel}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
minZoom={0.4}
|
minZoom={0.4}
|
||||||
maxZoom={1.5}
|
maxZoom={1.5}
|
||||||
fitView
|
fitView
|
||||||
defaultEdgeOptions={edgeOptions}
|
defaultEdgeOptions={edgeOptions}
|
||||||
connectionLineStyle={connectionLineStyle}
|
connectionLineStyle={connectionLineStyle}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={(connect) => {
|
onConnect={(connect) => {
|
||||||
connect.sourceHandle &&
|
connect.sourceHandle &&
|
||||||
connect.targetHandle &&
|
connect.targetHandle &&
|
||||||
onConnect({
|
onConnect({
|
||||||
connect
|
connect
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Background />
|
<Background />
|
||||||
<Controls
|
<Controls
|
||||||
position={'bottom-center'}
|
position={'bottom-center'}
|
||||||
style={{ display: 'flex' }}
|
style={{ display: 'flex' }}
|
||||||
showInteractive={false}
|
showInteractive={false}
|
||||||
/>
|
/>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</ReactFlowProvider>
|
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} />
|
||||||
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} />
|
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AppEdit;
|
const Flow = (data: Props) => (
|
||||||
|
<ReactFlowProvider>
|
||||||
|
<AppEdit {...data} />
|
||||||
|
</ReactFlowProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Flow;
|
||||||
|
|||||||
@ -173,7 +173,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
|||||||
boxShadow={'sm'}
|
boxShadow={'sm'}
|
||||||
{...(getValues('templateId') === item.id
|
{...(getValues('templateId') === item.id
|
||||||
? {
|
? {
|
||||||
bg: 'myBlue.300'
|
bg: 'myWhite.600'
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
_hover: {
|
_hover: {
|
||||||
|
|||||||
@ -120,7 +120,13 @@ const MyApps = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box className={styles.intro} py={2} fontSize={'sm'} color={'myGray.600'}>
|
<Box
|
||||||
|
className={styles.intro}
|
||||||
|
py={2}
|
||||||
|
wordBreak={'break-all'}
|
||||||
|
fontSize={'sm'}
|
||||||
|
color={'myGray.600'}
|
||||||
|
>
|
||||||
{app.intro || '这个应用还没写介绍~'}
|
{app.intro || '这个应用还没写介绍~'}
|
||||||
</Box>
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Flex, Button, Tooltip, Card } from '@chakra-ui/react';
|
import { Box, Flex, Button, Card } from '@chakra-ui/react';
|
||||||
import type { ShareAppItem } from '@/types/app';
|
import type { ShareAppItem } from '@/types/app';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import styles from '../index.module.scss';
|
import styles from '../index.module.scss';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const ShareModelList = ({
|
const ShareModelList = ({
|
||||||
models = [],
|
models = [],
|
||||||
@ -44,7 +45,7 @@ const ShareModelList = ({
|
|||||||
{model.name}
|
{model.name}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Tooltip label={model.intro}>
|
<MyTooltip label={model.intro}>
|
||||||
<Box
|
<Box
|
||||||
className={styles.intro}
|
className={styles.intro}
|
||||||
flex={1}
|
flex={1}
|
||||||
@ -55,7 +56,7 @@ const ShareModelList = ({
|
|||||||
>
|
>
|
||||||
{model.intro || '这个应用还没有介绍~'}
|
{model.intro || '这个应用还没有介绍~'}
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
|
|
||||||
<Flex justifyContent={'space-between'}>
|
<Flex justifyContent={'space-between'}>
|
||||||
<Flex
|
<Flex
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import {
|
|||||||
DrawerOverlay,
|
DrawerOverlay,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
Card,
|
Card,
|
||||||
Tooltip,
|
|
||||||
useOutsideClick,
|
useOutsideClick,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
@ -48,6 +47,7 @@ import Avatar from '@/components/Avatar';
|
|||||||
import Empty from './components/Empty';
|
import Empty from './components/Empty';
|
||||||
import QuoteModal from './components/QuoteModal';
|
import QuoteModal from './components/QuoteModal';
|
||||||
import { HUMAN_ICON } from '@/constants/chat';
|
import { HUMAN_ICON } from '@/constants/chat';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const Markdown = dynamic(async () => await import('@/components/Markdown'));
|
const Markdown = dynamic(async () => await import('@/components/Markdown'));
|
||||||
const PhoneSliderBar = dynamic(() => import('./components/PhoneSliderBar'), {
|
const PhoneSliderBar = dynamic(() => import('./components/PhoneSliderBar'), {
|
||||||
@ -701,7 +701,7 @@ const Chat = () => {
|
|||||||
{item.obj === 'Human' && <Box flex={1} />}
|
{item.obj === 'Human' && <Box flex={1} />}
|
||||||
{/* avatar */}
|
{/* avatar */}
|
||||||
<Menu autoSelect={false} isLazy>
|
<Menu autoSelect={false} isLazy>
|
||||||
<Tooltip label={item.obj === 'AI' ? '应用详情' : ''}>
|
<MyTooltip label={item.obj === 'AI' ? '应用详情' : ''}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Box}
|
as={Box}
|
||||||
{...(item.obj === 'AI'
|
{...(item.obj === 'AI'
|
||||||
@ -730,7 +730,7 @@ const Chat = () => {
|
|||||||
h={['20px', '34px']}
|
h={['20px', '34px']}
|
||||||
/>
|
/>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
{!isPc && <RenderContextMenu history={item} index={index} AiDetail />}
|
{!isPc && <RenderContextMenu history={item} index={index} AiDetail />}
|
||||||
</Menu>
|
</Menu>
|
||||||
{/* message */}
|
{/* message */}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
DrawerOverlay,
|
DrawerOverlay,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
Card,
|
Card,
|
||||||
Tooltip,
|
|
||||||
useOutsideClick,
|
useOutsideClick,
|
||||||
useTheme,
|
useTheme,
|
||||||
Input,
|
Input,
|
||||||
@ -49,6 +48,7 @@ import SideBar from '@/components/SideBar';
|
|||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import Empty from './components/Empty';
|
import Empty from './components/Empty';
|
||||||
import { HUMAN_ICON } from '@/constants/chat';
|
import { HUMAN_ICON } from '@/constants/chat';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const ShareHistory = dynamic(() => import('./components/ShareHistory'), {
|
const ShareHistory = dynamic(() => import('./components/ShareHistory'), {
|
||||||
loading: () => <Loading fixed={false} />,
|
loading: () => <Loading fixed={false} />,
|
||||||
@ -619,7 +619,7 @@ const Chat = () => {
|
|||||||
{item.obj === 'Human' && <Box flex={1} />}
|
{item.obj === 'Human' && <Box flex={1} />}
|
||||||
{/* avatar */}
|
{/* avatar */}
|
||||||
<Menu autoSelect={false} isLazy>
|
<Menu autoSelect={false} isLazy>
|
||||||
<Tooltip label={item.obj === 'AI' ? '应用详情' : ''}>
|
<MyTooltip label={item.obj === 'AI' ? '应用详情' : ''}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Box}
|
as={Box}
|
||||||
{...(item.obj === 'AI'
|
{...(item.obj === 'AI'
|
||||||
@ -642,7 +642,7 @@ const Chat = () => {
|
|||||||
h={['20px', '34px']}
|
h={['20px', '34px']}
|
||||||
/>
|
/>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
{!isPc && <RenderContextMenu history={item} index={index} />}
|
{!isPc && <RenderContextMenu history={item} index={index} />}
|
||||||
</Menu>
|
</Menu>
|
||||||
{/* message */}
|
{/* message */}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import React, {
|
|||||||
ForwardedRef
|
ForwardedRef
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { Box, Flex, Button, FormControl, IconButton, Tooltip, Input, Card } from '@chakra-ui/react';
|
import { Box, Flex, Button, FormControl, IconButton, Input, Card } from '@chakra-ui/react';
|
||||||
import { QuestionOutlineIcon, DeleteIcon } from '@chakra-ui/icons';
|
import { QuestionOutlineIcon, DeleteIcon } from '@chakra-ui/icons';
|
||||||
import { delKbById, putKbById } from '@/api/plugins/kb';
|
import { delKbById, putKbById } from '@/api/plugins/kb';
|
||||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
import { useSelectFile } from '@/hooks/useSelectFile';
|
||||||
@ -19,6 +19,7 @@ import { compressImg } from '@/utils/file';
|
|||||||
import type { KbItemType } from '@/types/plugin';
|
import type { KbItemType } from '@/types/plugin';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import Tag from '@/components/Tag';
|
import Tag from '@/components/Tag';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
export interface ComponentRef {
|
export interface ComponentRef {
|
||||||
initInput: (tags: string) => void;
|
initInput: (tags: string) => void;
|
||||||
@ -173,9 +174,9 @@ const Info = (
|
|||||||
<Flex mt={8} alignItems={'center'} w={'100%'} maxW={'350px'} flexWrap={'wrap'}>
|
<Flex mt={8} alignItems={'center'} w={'100%'} maxW={'350px'} flexWrap={'wrap'}>
|
||||||
<Box flex={'0 0 90px'} w={0}>
|
<Box flex={'0 0 90px'} w={0}>
|
||||||
分类标签
|
分类标签
|
||||||
<Tooltip label={'用空格隔开多个标签,便于搜索'}>
|
<MyTooltip label={'用空格隔开多个标签,便于搜索'}>
|
||||||
<QuestionOutlineIcon ml={1} />
|
<QuestionOutlineIcon ml={1} />
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<Input
|
<Input
|
||||||
flex={1}
|
flex={1}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useState, useMemo } from 'react';
|
import React, { useCallback, useState, useMemo } from 'react';
|
||||||
import { Box, Flex, useTheme, Input, IconButton, Tooltip } from '@chakra-ui/react';
|
import { Box, Flex, useTheme, Input, IconButton } from '@chakra-ui/react';
|
||||||
import { AddIcon } from '@chakra-ui/icons';
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { postCreateKb } from '@/api/plugins/kb';
|
import { postCreateKb } from '@/api/plugins/kb';
|
||||||
@ -10,6 +10,7 @@ import { useUserStore } from '@/store/user';
|
|||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import Tag from '@/components/Tag';
|
import Tag from '@/components/Tag';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
|
||||||
const KbList = ({ kbId }: { kbId: string }) => {
|
const KbList = ({ kbId }: { kbId: string }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -78,7 +79,7 @@ const KbList = ({ kbId }: { kbId: string }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Tooltip label={'新建一个知识库'}>
|
<MyTooltip label={'新建一个知识库'}>
|
||||||
<IconButton
|
<IconButton
|
||||||
h={'32px'}
|
h={'32px'}
|
||||||
icon={<AddIcon />}
|
icon={<AddIcon />}
|
||||||
@ -86,7 +87,7 @@ const KbList = ({ kbId }: { kbId: string }) => {
|
|||||||
variant={'base'}
|
variant={'base'}
|
||||||
onClick={handleCreateModel}
|
onClick={handleCreateModel}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</MyTooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box flex={'1 0 0'} h={0} pl={[0, 2]} overflowY={'scroll'} userSelect={'none'}>
|
<Box flex={'1 0 0'} h={0} pl={[0, 2]} overflowY={'scroll'} userSelect={'none'}>
|
||||||
{kbs.map((item) => (
|
{kbs.map((item) => (
|
||||||
|
|||||||
@ -1,388 +0,0 @@
|
|||||||
import React, { useCallback, useState, useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Flex,
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
Textarea,
|
|
||||||
Divider,
|
|
||||||
Tooltip
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useUserStore } from '@/store/user';
|
|
||||||
import { useToast } from '@/hooks/useToast';
|
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
|
||||||
import { delModelById, putAppById } from '@/api/app';
|
|
||||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
|
||||||
import { compressImg } from '@/utils/file';
|
|
||||||
import { getErrText } from '@/utils/tools';
|
|
||||||
import { useConfirm } from '@/hooks/useConfirm';
|
|
||||||
import { ChatModelMap, chatModelList } from '@/constants/model';
|
|
||||||
import { formatPrice } from '@/utils/user';
|
|
||||||
|
|
||||||
import type { AppSchema } from '@/types/mongoSchema';
|
|
||||||
|
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import MySelect from '@/components/Select';
|
|
||||||
import MySlider from '@/components/Slider';
|
|
||||||
|
|
||||||
const systemPromptTip =
|
|
||||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。';
|
|
||||||
const limitPromptTip =
|
|
||||||
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"';
|
|
||||||
|
|
||||||
const Settings = ({ modelId }: { modelId: string }) => {
|
|
||||||
const { toast } = useToast();
|
|
||||||
const router = useRouter();
|
|
||||||
const { Loading, setIsLoading } = useLoading();
|
|
||||||
const { userInfo, appDetail, myApps, loadAppDetail, refreshModel, setLastModelId } =
|
|
||||||
useUserStore();
|
|
||||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
|
||||||
fileType: '.jpg,.png',
|
|
||||||
multiple: false
|
|
||||||
});
|
|
||||||
const { openConfirm, ConfirmChild } = useConfirm({
|
|
||||||
content: '确认删除该应用?'
|
|
||||||
});
|
|
||||||
|
|
||||||
const [btnLoading, setBtnLoading] = useState(false);
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
|
||||||
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
setValue,
|
|
||||||
getValues,
|
|
||||||
formState: { errors },
|
|
||||||
reset,
|
|
||||||
handleSubmit
|
|
||||||
} = useForm({
|
|
||||||
defaultValues: appDetail
|
|
||||||
});
|
|
||||||
|
|
||||||
const isOwner = useMemo(
|
|
||||||
() => appDetail.userId === userInfo?._id,
|
|
||||||
[appDetail.userId, userInfo?._id]
|
|
||||||
);
|
|
||||||
const tokenLimit = useMemo(() => {
|
|
||||||
const max = ChatModelMap[getValues('chat.chatModel')]?.contextMaxToken || 4000;
|
|
||||||
|
|
||||||
if (max < getValues('chat.maxToken')) {
|
|
||||||
setValue('chat.maxToken', max);
|
|
||||||
}
|
|
||||||
|
|
||||||
return max;
|
|
||||||
}, [getValues, setValue, refresh]);
|
|
||||||
|
|
||||||
// 提交保存模型修改
|
|
||||||
const saveSubmitSuccess = useCallback(
|
|
||||||
async (data: AppSchema) => {
|
|
||||||
setBtnLoading(true);
|
|
||||||
try {
|
|
||||||
await putAppById(data._id, {
|
|
||||||
name: data.name,
|
|
||||||
avatar: data.avatar,
|
|
||||||
intro: data.intro,
|
|
||||||
chat: data.chat,
|
|
||||||
share: data.share
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshModel.updateModelDetail(data);
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '更新失败',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setBtnLoading(false);
|
|
||||||
},
|
|
||||||
[refreshModel, toast]
|
|
||||||
);
|
|
||||||
// 提交保存表单失败
|
|
||||||
const saveSubmitError = useCallback(() => {
|
|
||||||
// deep search message
|
|
||||||
const deepSearch = (obj: any): string => {
|
|
||||||
if (!obj) return '提交表单错误';
|
|
||||||
if (!!obj.message) {
|
|
||||||
return obj.message;
|
|
||||||
}
|
|
||||||
return deepSearch(Object.values(obj)[0]);
|
|
||||||
};
|
|
||||||
toast({
|
|
||||||
title: deepSearch(errors),
|
|
||||||
status: 'error',
|
|
||||||
duration: 4000,
|
|
||||||
isClosable: true
|
|
||||||
});
|
|
||||||
}, [errors, toast]);
|
|
||||||
|
|
||||||
const saveUpdateModel = useCallback(
|
|
||||||
() => handleSubmit(saveSubmitSuccess, saveSubmitError)(),
|
|
||||||
[handleSubmit, saveSubmitError, saveSubmitSuccess]
|
|
||||||
);
|
|
||||||
|
|
||||||
/* 点击删除 */
|
|
||||||
const handleDelModel = useCallback(async () => {
|
|
||||||
if (!appDetail) return;
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
await delModelById(appDetail._id);
|
|
||||||
toast({
|
|
||||||
title: '删除成功',
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
refreshModel.removeModelDetail(appDetail._id);
|
|
||||||
router.replace(`/model?modelId=${myApps[1]?._id}`);
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '删除失败',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
}, [appDetail, setIsLoading, toast, refreshModel, router, myApps]);
|
|
||||||
|
|
||||||
const onSelectFile = useCallback(
|
|
||||||
async (e: File[]) => {
|
|
||||||
const file = e[0];
|
|
||||||
if (!file) return;
|
|
||||||
try {
|
|
||||||
const src = await compressImg({
|
|
||||||
file,
|
|
||||||
maxW: 100,
|
|
||||||
maxH: 100
|
|
||||||
});
|
|
||||||
setValue('avatar', src);
|
|
||||||
setRefresh((state) => !state);
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: getErrText(err, '头像选择异常'),
|
|
||||||
status: 'warning'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setValue, toast]
|
|
||||||
);
|
|
||||||
|
|
||||||
// load model data
|
|
||||||
const { isLoading } = useQuery([modelId], () => loadAppDetail(modelId, true), {
|
|
||||||
onSuccess(res) {
|
|
||||||
res && reset(res);
|
|
||||||
modelId && setLastModelId(modelId);
|
|
||||||
setRefresh(!refresh);
|
|
||||||
},
|
|
||||||
onError(err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '获取应用异常',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
setLastModelId('');
|
|
||||||
refreshModel.freshMyModels();
|
|
||||||
router.replace('/model');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
pb={3}
|
|
||||||
px={[5, '25px', '50px']}
|
|
||||||
fontSize={['sm', 'lg']}
|
|
||||||
maxW={['auto', '800px']}
|
|
||||||
position={'relative'}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
头像
|
|
||||||
</Box>
|
|
||||||
<Avatar
|
|
||||||
src={getValues('avatar')}
|
|
||||||
w={['32px', '40px']}
|
|
||||||
h={['32px', '40px']}
|
|
||||||
cursor={isOwner ? 'pointer' : 'default'}
|
|
||||||
title={'点击切换头像'}
|
|
||||||
onClick={() => isOwner && onOpenSelectFile()}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<FormControl mt={5}>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
名称
|
|
||||||
</Box>
|
|
||||||
<Input
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
{...register('name', {
|
|
||||||
required: '展示名称不能为空'
|
|
||||||
})}
|
|
||||||
></Input>
|
|
||||||
</Flex>
|
|
||||||
</FormControl>
|
|
||||||
<Flex mt={5} alignItems={'flex-start'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
介绍
|
|
||||||
</Box>
|
|
||||||
<Textarea
|
|
||||||
rows={4}
|
|
||||||
maxLength={500}
|
|
||||||
placeholder={'给你的 AI 应用一个介绍'}
|
|
||||||
{...register('intro')}
|
|
||||||
></Textarea>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Divider mt={5} />
|
|
||||||
|
|
||||||
<Flex alignItems={'center'} mt={5}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
对话模型
|
|
||||||
</Box>
|
|
||||||
<MySelect
|
|
||||||
width={['100%', '300px']}
|
|
||||||
value={getValues('chat.chatModel')}
|
|
||||||
list={chatModelList.map((item) => ({
|
|
||||||
value: item.chatModel,
|
|
||||||
label: `${item.name} (${formatPrice(
|
|
||||||
ChatModelMap[item.chatModel]?.price,
|
|
||||||
1000
|
|
||||||
)} 元/1k tokens)`
|
|
||||||
}))}
|
|
||||||
onchange={(val: any) => {
|
|
||||||
setValue('chat.chatModel', val);
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Flex alignItems={'center'} my={10}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
温度
|
|
||||||
</Box>
|
|
||||||
<Box flex={1} ml={'10px'}>
|
|
||||||
<MySlider
|
|
||||||
markList={[
|
|
||||||
{ label: '严谨', value: 0 },
|
|
||||||
{ label: '发散', value: 10 }
|
|
||||||
]}
|
|
||||||
width={['95%', '280px']}
|
|
||||||
min={0}
|
|
||||||
max={10}
|
|
||||||
value={getValues('chat.temperature')}
|
|
||||||
onChange={(val) => {
|
|
||||||
setValue('chat.temperature', val);
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex alignItems={'center'} mt={12} mb={10}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
回复上限
|
|
||||||
</Box>
|
|
||||||
<Box flex={1} ml={'10px'}>
|
|
||||||
<MySlider
|
|
||||||
markList={[
|
|
||||||
{ label: '100', value: 100 },
|
|
||||||
{ label: `${tokenLimit}`, value: tokenLimit }
|
|
||||||
]}
|
|
||||||
width={['95%', '280px']}
|
|
||||||
min={100}
|
|
||||||
max={tokenLimit}
|
|
||||||
step={50}
|
|
||||||
value={getValues('chat.maxToken')}
|
|
||||||
onChange={(val) => {
|
|
||||||
setValue('chat.maxToken', val);
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex mt={10} alignItems={'flex-start'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
提示词
|
|
||||||
<Tooltip label={systemPromptTip}>
|
|
||||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
<Textarea
|
|
||||||
rows={8}
|
|
||||||
placeholder={systemPromptTip}
|
|
||||||
{...register('chat.systemPrompt')}
|
|
||||||
></Textarea>
|
|
||||||
</Flex>
|
|
||||||
<Flex mt={5} alignItems={'flex-start'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
|
||||||
限定词
|
|
||||||
<Tooltip label={limitPromptTip}>
|
|
||||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
<Textarea
|
|
||||||
rows={5}
|
|
||||||
placeholder={limitPromptTip}
|
|
||||||
{...register('chat.limitPrompt')}
|
|
||||||
></Textarea>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex mt={5} alignItems={'center'}>
|
|
||||||
<Box w={['60px', '100px', '140px']} flexShrink={0}></Box>
|
|
||||||
<Button
|
|
||||||
mr={3}
|
|
||||||
w={'120px'}
|
|
||||||
size={['sm', 'md']}
|
|
||||||
isLoading={btnLoading}
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await saveUpdateModel();
|
|
||||||
toast({
|
|
||||||
title: '更新成功',
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
error;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isOwner ? '保存' : '仅读,无法修改'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
mr={3}
|
|
||||||
w={'100px'}
|
|
||||||
size={['sm', 'md']}
|
|
||||||
variant={'base'}
|
|
||||||
color={'myBlue.600'}
|
|
||||||
borderColor={'myBlue.600'}
|
|
||||||
isLoading={btnLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
router.prefetch('/chat');
|
|
||||||
await saveUpdateModel();
|
|
||||||
} catch (error) {}
|
|
||||||
router.push(`/chat?modelId=${modelId}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
对话
|
|
||||||
</Button>
|
|
||||||
{isOwner && (
|
|
||||||
<Button
|
|
||||||
colorScheme={'gray'}
|
|
||||||
variant={'base'}
|
|
||||||
size={['sm', 'md']}
|
|
||||||
isLoading={btnLoading}
|
|
||||||
_hover={{ color: 'red.600' }}
|
|
||||||
onClick={openConfirm(handleDelModel)}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<File onSelect={onSelectFile} />
|
|
||||||
<ConfirmChild />
|
|
||||||
<Loading loading={isLoading} fixed={false} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Settings;
|
|
||||||
2
client/src/types/app.d.ts
vendored
@ -40,7 +40,7 @@ export type ShareChatEditType = {
|
|||||||
/* agent */
|
/* agent */
|
||||||
/* question classify */
|
/* question classify */
|
||||||
export type ClassifyQuestionAgentItemType = {
|
export type ClassifyQuestionAgentItemType = {
|
||||||
desc: string;
|
value: string;
|
||||||
key: string;
|
key: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
1
client/src/types/flow.d.ts
vendored
@ -37,6 +37,7 @@ export type FlowOutputItemType = {
|
|||||||
|
|
||||||
export type FlowModuleItemChangeProps = {
|
export type FlowModuleItemChangeProps = {
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
|
type?: 'inputs' | 'outputs';
|
||||||
key: string;
|
key: string;
|
||||||
value: any;
|
value: any;
|
||||||
valueKey?: keyof FlowInputItemType & keyof FlowBodyItemType;
|
valueKey?: keyof FlowInputItemType & keyof FlowBodyItemType;
|
||||||
|
|||||||
@ -109,8 +109,6 @@ export const appModule2FlowEdge = ({
|
|||||||
modules: AppModuleItemType[];
|
modules: AppModuleItemType[];
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
console.log(modules);
|
|
||||||
|
|
||||||
const edges: Edge[] = [];
|
const edges: Edge[] = [];
|
||||||
modules.forEach((module) =>
|
modules.forEach((module) =>
|
||||||
module.outputs.forEach((output) =>
|
module.outputs.forEach((output) =>
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
import { OpenAiChatEnum } from '@/constants/model';
|
|
||||||
import type { ChatModelType } from '@/constants/model';
|
|
||||||
import type { ChatItemType } from '@/types/chat';
|
|
||||||
import { countOpenAIToken, openAiSliceTextByToken } from './openai';
|
import { countOpenAIToken, openAiSliceTextByToken } from './openai';
|
||||||
import { gpt_chatItemTokenSlice } from '@/pages/api/openapi/text/gptMessagesSlice';
|
import { gpt_chatItemTokenSlice } from '@/pages/api/openapi/text/gptMessagesSlice';
|
||||||
|
|
||||||
|
|||||||