This commit is contained in:
Archer
2023-09-26 21:17:13 +08:00
committed by GitHub
parent a11e0bd9c3
commit 11848b8f44
34 changed files with 395 additions and 62 deletions

View File

@@ -26,6 +26,7 @@ ARG name
# copy common node_modules and one project node_modules
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages ./packages
COPY ./projects/$name ./projects/$name
COPY --from=deps /app/projects/$name/node_modules ./projects/$name/node_modules
COPY pnpm-lock.yaml pnpm-workspace.yaml ./

View File

@@ -1,6 +1,6 @@
---
title: '升级到 V4.4.5'
description: 'FastGPT 从旧版本升级到 V4.4.5 操作指南'
title: 'V4.4.5'
description: 'FastGPT V4.4.5 更新(需执行升级脚本)'
icon: 'upgrade'
draft: false
toc: true
@@ -21,3 +21,11 @@ curl --location --request POST 'https://{{host}}/api/admin/initv445' \
初始化了 variable 模块,将其合并到用户引导模块中。
## 功能介绍
### Fast GPT V4.4.5
1. 新增 - 下一步指引选项,可以通过模型生成 3 个预测问题。
2. 新增 - 分享链接 hook 身份校验。
3. 新增 - Api Key 使用。增加别名、额度限制和过期时间。自带 appId无需额外连接。
4. 优化 - 全局变量与开场白合并成同一模块。

View File

@@ -1,7 +1,7 @@
---
weight: 760
title: "版本升级"
description: "FastGPT 升级指南"
title: "版本更新/升级操作"
description: "FastGPT 版本更新介绍及升级操作"
icon: upgrade
draft: false
images: []

View File

@@ -75,5 +75,13 @@
"maxToken": 16000,
"price": 0,
"prompt": ""
},
"QGModel": {
"model": "gpt-3.5-turbo",
"name": "GPT35-4k",
"maxToken": 4000,
"price": 0,
"prompt": "",
"functionCall": false
}
}

View File

@@ -1,8 +1,10 @@
### Fast GPT V4.4.5
1. 优化 - Api Key 使用。增加别名、额度限制和过期时间。自带 appId无需额外连接
2. 去除 - 限定词。目前旧应用仍生效9/25 后全面去除,请及时替换
3. 新增 - 引用模板/引用提示词设置,可以 DIY 引用内容的格式,从而更好的适配场景。[参考文档](https://doc.fastgpt.run/docs/use-cases/prompt/)
4. [使用文档](https://doc.fastgpt.run/docs/intro/)
5. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
6. [点击查看商业版](https://doc.fastgpt.run/docs/commercial/)
1. 新增 - 下一步指引选项,可以通过模型生成 3 个预测问题
2. 新增 - 分享链接 hook 身份校验
3. 新增 - Api Key 使用。增加别名、额度限制和过期时间。自带 appId无需额外连接。
4. 去除 - 限定词。目前旧应用仍生效9/25 后全面去除,请及时替换。
5. 新增 - 引用模板/引用提示词设置,可以 DIY 引用内容的格式,从而更好的适配场景。[参考文档](https://doc.fastgpt.run/docs/use-cases/prompt/)
6. [使用文档](https://doc.fastgpt.run/docs/intro/)
7. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
8. [点击查看商业版](https://doc.fastgpt.run/docs/commercial/)

View File

@@ -67,6 +67,7 @@
"Mark Description": "The annotation feature is currently in beta. \n\n After clicking Add annotation, you need to select a knowledge base in order to store annotation data. You can use this feature to quickly annotate questions and expected answers to guide the model to the next answer. At present, the annotation function, like other data in the knowledge base, is affected by the model, which does not mean that the annotation meets 100% expectations. The \n\n annotation data is only unidirectional synchronization with the knowledge base. If the knowledge base modifies the annotation data, the annotation data displayed in the log cannot be synchronized",
"Mark Description Title": "Mark Description",
"New Chat": "New Chat",
"Question Guide Tips": "I guess what you're asking is",
"Read Mark Description": "Read mark description",
"Read User Feedback": "Read user feedback",
"Select Mark Kb": "Select Dataset",

View File

@@ -67,6 +67,7 @@
"Mark Description": "当前标注功能为测试版。\n\n点击添加标注后需要选择一个知识库以便存储标注数据。你可以通过该功能快速的标注问题和预期回答以便引导模型下次的回答。\n\n目前标注功能同知识库其他数据一样受模型的影响不代表标注后 100% 符合预期。\n\n标注数据仅单向与知识库同步如果知识库修改了该标注数据日志展示的标注数据无法同步",
"Mark Description Title": "标注功能介绍",
"New Chat": "新对话",
"Question Guide Tips": "猜你想问",
"Read Mark Description": "查看标注功能介绍",
"Read User Feedback": "查看用户反馈",
"Select Mark Kb": "选择知识库",

View File

@@ -0,0 +1,5 @@
import { GET, POST, PUT, DELETE } from '@/api/request';
import { CreateQuestionGuideProps } from './type';
export const postQuestionGuide = (data: CreateQuestionGuideProps, cancelToken: AbortController) =>
POST<string[]>('/core/ai/agent/createQuestionGuide', data, { cancelToken });

View File

@@ -0,0 +1,5 @@
import { ChatCompletionRequestMessage } from '@fastgpt/core/aiApi/type';
export type CreateQuestionGuideProps = {
messages: ChatCompletionRequestMessage[];
};

View File

@@ -12,6 +12,7 @@ interface ConfigType {
hold?: boolean;
timeout?: number;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
cancelToken?: AbortController;
}
interface ResponseDataType {
code: number;
@@ -88,7 +89,12 @@ instance.interceptors.request.use(requestStart, (err) => Promise.reject(err));
/* 响应拦截 */
instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err));
function request(url: string, data: any, config: ConfigType, method: Method): any {
function request(
url: string,
data: any,
{ cancelToken, ...config }: ConfigType,
method: Method
): any {
/* 去空 */
for (const key in data) {
if (data[key] === null || data[key] === undefined) {
@@ -103,6 +109,7 @@ function request(url: string, data: any, config: ConfigType, method: Method): an
method,
data: ['POST', 'PUT'].includes(method) ? data : null,
params: !['POST', 'PUT'].includes(method) ? data : null,
signal: cancelToken?.signal,
...config // 用户自定义配置,可以覆盖前面的配置
})
.then((res) => checkRes(res.data))

View File

@@ -1,13 +1,12 @@
import type { AppSchema } from '@/types/mongoSchema';
import type { ChatItemType } from '@/types/chat';
import { VariableItemType } from '@/types/app';
import { AppModuleItemType, VariableItemType } from '@/types/app';
export interface InitChatResponse {
chatId: string;
appId: string;
app: {
variableModules?: VariableItemType[];
welcomeText?: string;
userGuideModule?: AppModuleItemType;
chatModels?: string[];
name: string;
avatar: string;

View File

@@ -24,7 +24,7 @@ import { feConfigs } from '@/store/static';
import { event } from '@/utils/plugin/eventbus';
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { useMarkdown } from '@/hooks/useMarkdown';
import { VariableItemType } from '@/types/app';
import { AppModuleItemType, VariableItemType } from '@/types/app';
import { VariableInputEnum } from '@/constants/app';
import { useForm } from 'react-hook-form';
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
@@ -51,6 +51,8 @@ const InputDataModal = dynamic(() => import('@/pages/kb/detail/components/InputD
import styles from './index.module.scss';
import Script from 'next/script';
import { postQuestionGuide } from '@/api/core/ai/agent/api';
import { splitGuideModule } from './utils';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
@@ -137,8 +139,7 @@ const ChatBox = (
chatId,
appAvatar,
userAvatar,
variableModules,
welcomeText,
userGuideModule,
active = true,
onUpdateVariable,
onStartChat,
@@ -151,8 +152,7 @@ const ChatBox = (
chatId?: string;
appAvatar?: string;
userAvatar?: string;
variableModules?: VariableItemType[];
welcomeText?: string;
userGuideModule?: AppModuleItemType;
active?: boolean;
onUpdateVariable?: (e: Record<string, any>) => void;
onStartChat?: (e: StartChatFnProps) => Promise<{
@@ -171,24 +171,28 @@ const ChatBox = (
const { toast } = useToast();
const { isPc } = useGlobalStore();
const TextareaDom = useRef<HTMLTextAreaElement>(null);
const controller = useRef(new AbortController());
const chatController = useRef(new AbortController());
const questionGuideController = useRef(new AbortController());
const [refresh, setRefresh] = useState(false);
const [variables, setVariables] = useState<Record<string, any>>({});
const [variables, setVariables] = useState<Record<string, any>>({}); // settings variable
const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
const [feedbackId, setFeedbackId] = useState<string>();
const [readFeedbackData, setReadFeedbackData] = useState<{
// read feedback modal data
chatItemId: string;
content: string;
isMarked: boolean;
}>();
const [adminMarkData, setAdminMarkData] = useState<{
// mark modal data
kbId?: string;
chatItemId: string;
dataId?: string;
q: string;
a: string;
}>();
const [questionGuides, setQuestionGuide] = useState<string[]>([]);
const isChatting = useMemo(
() =>
@@ -196,6 +200,12 @@ const ChatBox = (
chatHistory[chatHistory.length - 1]?.status !== 'finish',
[chatHistory]
);
const { welcomeText, variableModules, questionGuide } = useMemo(
() => splitGuideModule(userGuideModule),
[userGuideModule]
);
// compute variable input is finish.
const [variableInputFinish, setVariableInputFinish] = useState(false);
const variableIsFinish = useMemo(() => {
@@ -287,6 +297,32 @@ const ChatBox = (
}, 100);
}, []);
// create question guide
const createQuestionGuide = useCallback(
async ({ history }: { history: ChatSiteItemType[] }) => {
if (!questionGuide || chatController.current?.signal?.aborted) return;
try {
const abortSignal = new AbortController();
questionGuideController.current = abortSignal;
const result = await postQuestionGuide(
{
messages: adaptChat2GptMessages({ messages: history, reserveId: false }).slice(-6)
},
abortSignal
);
if (Array.isArray(result)) {
setQuestionGuide(result);
setTimeout(() => {
scrollToBottom();
}, 100);
}
} catch (error) {}
},
[questionGuide, scrollToBottom]
);
/**
* user confirm send prompt
*/
@@ -300,6 +336,7 @@ const ChatBox = (
});
return;
}
questionGuideController.current?.abort('stop');
// get input value
const val = inputVal.trim();
@@ -332,6 +369,7 @@ const ChatBox = (
// 清空输入内容
resetInputVal('');
setQuestionGuide([]);
setTimeout(() => {
scrollToBottom();
}, 100);
@@ -339,11 +377,11 @@ const ChatBox = (
try {
// create abort obj
const abortSignal = new AbortController();
controller.current = abortSignal;
chatController.current = abortSignal;
const messages = adaptChat2GptMessages({ messages: newChatList, reserveId: true });
const { responseData } = await onStartChat({
const { responseData, responseText } = await onStartChat({
chatList: newChatList,
messages,
controller: abortSignal,
@@ -364,6 +402,16 @@ const ChatBox = (
);
setTimeout(() => {
createQuestionGuide({
history: newChatList.map((item, i) =>
i === newChatList.length - 1
? {
...item,
value: responseText
}
: item
)
});
generatingScroll();
isPc && TextareaDom.current?.focus();
}, 100);
@@ -393,13 +441,14 @@ const ChatBox = (
}
},
[
isChatting,
chatHistory,
onStartChat,
isChatting,
resetInputVal,
toast,
scrollToBottom,
onStartChat,
generatingMessage,
createQuestionGuide,
generatingScroll,
isPc
]
@@ -494,7 +543,8 @@ const ChatBox = (
// page change and abort request
useEffect(() => {
return () => {
controller.current?.abort('leave');
chatController.current?.abort('leave');
questionGuideController.current?.abort('leave');
// close voice
cancelBroadcast();
};
@@ -528,6 +578,7 @@ const ChatBox = (
<Flex flexDirection={'column'} h={'100%'}>
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
{/* chat box container */}
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
<Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
{showEmpty && <Empty />}
@@ -839,6 +890,43 @@ const ChatBox = (
contentId={item.dataId}
responseData={item.responseData}
/>
{/* question guide */}
{index === chatHistory.length - 1 &&
!isChatting &&
questionGuides.length > 0 && (
<Flex
mt={2}
borderTop={theme.borders.sm}
alignItems={'center'}
flexWrap={'wrap'}
>
<Box
color={'myGray.500'}
mt={2}
mr={2}
fontSize={'sm'}
fontStyle={'italic'}
>
{t('chat.Question Guide Tips')}
</Box>
{questionGuides.map((item) => (
<Button
mt={2}
key={item}
mr="2"
borderRadius={'md'}
variant={'outline'}
colorScheme={'gray'}
size={'xs'}
onClick={() => {
resetInputVal(item);
}}
>
{item}
</Button>
))}
</Flex>
)}
{/* admin mark content */}
{showMarkIcon && item.adminFeedback && (
<Box>
@@ -928,7 +1016,7 @@ const ChatBox = (
cursor={'pointer'}
name={'stop'}
color={'gray.500'}
onClick={() => controller.current?.abort('stop')}
onClick={() => chatController.current?.abort('stop')}
/>
) : (
<MyIcon

View File

@@ -3,18 +3,24 @@ import { FlowModuleTypeEnum } from '@/constants/flow';
import { getChatModel } from '@/service/utils/data';
import { AppModuleItemType, VariableItemType } from '@/types/app';
export const getGuideModules = (modules: AppModuleItemType[]) => {
const guideModules = modules.find((item) => item.flowType === FlowModuleTypeEnum.userGuide);
export const getGuideModule = (modules: AppModuleItemType[]) =>
modules.find((item) => item.flowType === FlowModuleTypeEnum.userGuide);
export const splitGuideModule = (guideModules?: AppModuleItemType) => {
const welcomeText: string =
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
const variableModules: VariableItemType[] =
guideModules?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
const questionGuide: boolean =
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.questionGuide)?.value ||
false;
return {
welcomeText,
variableModules
variableModules,
questionGuide
};
};
export const getChatModelNameList = (modules: AppModuleItemType[]): string[] => {

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M10.8275 1.33325H5.17245C4.63581 1.33324 4.19289 1.33324 3.8321 1.36272C3.45737 1.39333 3.1129 1.45904 2.78934 1.6239C2.28758 1.87956 1.87963 2.28751 1.62397 2.78928C1.45911 3.11284 1.3934 3.4573 1.36278 3.83204C1.3333 4.19283 1.33331 4.63574 1.33332 5.17239L1.33328 9.42497C1.333 9.95523 1.33278 10.349 1.42418 10.6901C1.67076 11.6103 2.38955 12.3291 3.3098 12.5757C3.51478 12.6306 3.73878 12.6525 3.99998 12.6611L3.99998 13.5806C3.99995 13.7374 3.99992 13.8973 4.01182 14.0283C4.0232 14.1536 4.05333 14.3901 4.21844 14.5969C4.40843 14.8349 4.69652 14.9734 5.00106 14.973C5.26572 14.9728 5.46921 14.8486 5.57416 14.7792C5.6839 14.7066 5.80872 14.6067 5.93117 14.5087L7.53992 13.2217C7.88564 12.9451 7.98829 12.8671 8.09494 12.8126C8.20192 12.7579 8.3158 12.718 8.43349 12.6938C8.55081 12.6697 8.67974 12.6666 9.12248 12.6666H10.8275C11.3642 12.6666 11.8071 12.6666 12.1679 12.6371C12.5426 12.6065 12.8871 12.5408 13.2106 12.3759C13.7124 12.1203 14.1203 11.7123 14.376 11.2106C14.5409 10.887 14.6066 10.5425 14.6372 10.1678C14.6667 9.80701 14.6667 9.36411 14.6667 8.82747V5.17237C14.6667 4.63573 14.6667 4.19283 14.6372 3.83204C14.6066 3.4573 14.5409 3.11284 14.376 2.78928C14.1203 2.28751 13.7124 1.87956 13.2106 1.6239C12.8871 1.45904 12.5426 1.39333 12.1679 1.36272C11.8071 1.33324 11.3642 1.33324 10.8275 1.33325ZM8.99504 4.99992C8.99504 4.44763 9.44275 3.99992 9.99504 3.99992C10.5473 3.99992 10.995 4.44763 10.995 4.99992C10.995 5.5522 10.5473 5.99992 9.99504 5.99992C9.44275 5.99992 8.99504 5.5522 8.99504 4.99992ZM4.92837 7.79996C5.222 7.57974 5.63816 7.63837 5.85961 7.93051C5.90071 7.98295 5.94593 8.03229 5.99199 8.08035C6.09019 8.18282 6.23775 8.32184 6.42882 8.4608C6.81353 8.74059 7.3454 8.99996 7.99504 8.99996C8.64469 8.99996 9.17655 8.74059 9.56126 8.4608C9.75233 8.32184 9.89989 8.18282 9.99809 8.08035C10.0441 8.0323 10.0894 7.98294 10.1305 7.93051C10.3519 7.63837 10.7681 7.57974 11.0617 7.79996C11.3563 8.02087 11.416 8.43874 11.195 8.73329C11.1967 8.73112 11.1928 8.7361 11.186 8.74466C11.1697 8.7651 11.1372 8.80597 11.1261 8.81916C11.087 8.86575 11.0317 8.92884 10.9607 9.00289C10.8194 9.15043 10.6128 9.34474 10.3455 9.53912C9.81353 9.92599 9.01206 10.3333 7.99504 10.3333C6.97802 10.3333 6.17655 9.92599 5.64459 9.53912C5.37733 9.34474 5.17072 9.15043 5.02934 9.00289C4.95837 8.92884 4.90305 8.86575 4.86395 8.81916C4.84438 8.79585 4.82881 8.77659 4.81731 8.76207C4.58702 8.46455 4.61798 8.03275 4.92837 7.79996ZM5.99504 3.99992C5.44275 3.99992 4.99504 4.44763 4.99504 4.99992C4.99504 5.5522 5.44275 5.99992 5.99504 5.99992C6.54732 5.99992 6.99504 5.5522 6.99504 4.99992C6.99504 4.44763 6.54732 3.99992 5.99504 3.99992Z"
fill="#06AED4"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -86,7 +86,8 @@ const iconPaths = {
rightArrowLight: () => import('./icons/light/rightArrow.svg'),
searchLight: () => import('./icons/light/search.svg'),
plusFill: () => import('./icons/fill/plus.svg'),
moveLight: () => import('./icons/light/move.svg')
moveLight: () => import('./icons/light/move.svg'),
questionGuide: () => import('./icons/app/questionGuide.svg')
};
export type IconName = keyof typeof iconPaths;

View File

@@ -4,7 +4,8 @@ export enum SystemInputEnum {
'variables' = 'variables',
'switch' = 'switch', // a trigger switch
'history' = 'history',
'userChatInput' = 'userChatInput'
'userChatInput' = 'userChatInput',
'questionGuide' = 'questionGuide'
}
export enum VariableInputEnum {

View File

@@ -26,6 +26,7 @@ export const welcomeTextTip =
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
export const variableTip =
'可以在对话开始前,要求用户填写一些内容作为本轮对话的特定变量。该模块位于开场引导之后。\n变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等';
export const questionGuideTip = `对话结束后,会为生成 3 个引导性问题。`;
export const VariableModule: FlowModuleTemplateType = {
flowType: FlowModuleTypeEnum.variable,
@@ -52,14 +53,19 @@ export const UserGuideModule: FlowModuleTemplateType = {
inputs: [
{
key: SystemInputEnum.welcomeText,
type: FlowInputItemTypeEnum.input,
type: FlowInputItemTypeEnum.hidden,
label: '开场白'
},
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
type: FlowInputItemTypeEnum.hidden,
label: '对话框变量',
value: []
},
{
key: SystemInputEnum.questionGuide,
type: FlowInputItemTypeEnum.switch,
label: '问题引导'
}
],
outputs: []

View File

@@ -10,6 +10,7 @@ export enum FlowInputItemTypeEnum {
custom = 'custom',
target = 'target',
none = 'none',
switch = 'switch',
hidden = 'hidden'
}

View File

@@ -6,7 +6,7 @@ import { authUser } from '@/service/utils/auth';
import { ChatItemType } from '@/types/chat';
import { authApp } from '@/service/utils/auth';
import type { ChatSchema } from '@/types/mongoSchema';
import { getGuideModules, getChatModelNameList } from '@/components/ChatBox/utils';
import { getChatModelNameList, getGuideModule } from '@/components/ChatBox/utils';
import { TaskResponseKeyEnum } from '@/constants/chat';
/* 初始化我的聊天框,需要身份验证 */
@@ -81,7 +81,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
chatId,
appId,
app: {
...getGuideModules(app.modules),
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameList(app.modules),
name: app.name,
avatar: app.avatar,

View File

@@ -0,0 +1,77 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { CreateQuestionGuideProps } from '@/api/core/ai/agent/type';
import { getAIChatApi } from '@fastgpt/core/aiApi/config';
import { Prompt_QuestionGuide } from '@/prompts/core/agent';
import { pushQuestionGuideBill } from '@/service/common/bill/push';
import { defaultQGModel } from '@/pages/api/system/getInitData';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { messages } = req.body as CreateQuestionGuideProps;
const { user } = await authUser({ req, authToken: true, authApiKey: true, authBalance: true });
if (!user) {
throw new Error('user not found');
}
const qgModel = global.qgModel || defaultQGModel;
const chatAPI = getAIChatApi(user.openaiAccount);
const { data } = await chatAPI.createChatCompletion({
model: qgModel.model,
temperature: 0,
max_tokens: 200,
messages: [
...messages,
{
role: 'user',
content: Prompt_QuestionGuide
}
],
stream: false
});
const answer = data.choices?.[0].message?.content || '';
const totalTokens = data.usage?.total_tokens || 0;
const start = answer.indexOf('[');
const end = answer.lastIndexOf(']');
if (start === -1 || end === -1) {
return jsonRes(res, {
data: []
});
}
const jsonStr = answer
.substring(start, end + 1)
.replace(/(\\n|\\)/g, '')
.replace(/ /g, '');
try {
jsonRes(res, {
data: JSON.parse(jsonStr)
});
pushQuestionGuideBill({
tokens: totalTokens,
userId: user._id
});
return;
} catch (error) {
return jsonRes(res, {
data: []
});
}
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -4,7 +4,7 @@ import { connectToDatabase, OutLink, User } from '@/service/mongo';
import type { InitShareChatResponse } from '@/api/response/chat';
import { authApp } from '@/service/utils/auth';
import { HUMAN_ICON } from '@/constants/chat';
import { getChatModelNameList, getGuideModules } from '@/components/ChatBox/utils';
import { getChatModelNameList, getGuideModule } from '@/components/ChatBox/utils';
import { authShareChatInit } from '@/service/support/outLink/auth';
/* init share chat window */
@@ -46,7 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
data: {
userAvatar: user?.avatar || HUMAN_ICON,
app: {
...getGuideModules(app.modules),
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameList(app.modules),
name: app.name,
avatar: app.avatar,

View File

@@ -97,6 +97,14 @@ export const defaultCQModel: FunctionModelItemType = {
prompt: '',
functionCall: true
};
export const defaultQGModel: FunctionModelItemType = {
model: 'gpt-3.5-turbo',
name: 'FastAI-4k',
maxToken: 4000,
price: 1.5,
prompt: '',
functionCall: false
};
const defaultVectorModels: VectorModelItemType[] = [
{
@@ -130,6 +138,7 @@ export async function getInitConfig() {
global.qaModel = res.QAModel || defaultQAModel;
global.extractModel = res.ExtractModel || defaultExtractModel;
global.cqModel = res.CQModel || defaultCQModel;
global.qgModel = res.QGModel || defaultQGModel;
global.vectorModels = res.VectorModels || defaultVectorModels;
} catch (error) {
setDefaultData();
@@ -143,6 +152,9 @@ export function setDefaultData() {
global.chatModels = defaultChatModels;
global.qaModel = defaultQAModel;
global.vectorModels = defaultVectorModels;
global.extractModel = defaultExtractModel;
global.cqModel = defaultCQModel;
global.qgModel = defaultQGModel;
}
export function getSystemVersion() {

View File

@@ -15,7 +15,7 @@ import { streamFetch } from '@/api/fetch';
import MyTooltip from '@/components/MyTooltip';
import { useUserStore } from '@/store/user';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import { getGuideModules } from '@/components/ChatBox/utils';
import { getGuideModule } from '@/components/ChatBox/utils';
export type ChatTestComponentRef = {
resetChatTest: () => void;
@@ -114,7 +114,7 @@ const ChatTest = (
appAvatar={app.avatar}
userAvatar={userInfo?.avatar}
showMarkIcon
{...getGuideModules(modules)}
userGuideModule={getGuideModule(modules)}
onStartChat={startChat}
onDelMessage={() => {}}
/>

View File

@@ -4,19 +4,20 @@ import {
Box,
Flex,
Textarea,
Button,
useTheme,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
TableContainer,
Switch
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { FlowModuleItemType } from '@/types/flow';
import { SystemInputEnum } from '@/constants/app';
import { welcomeTextTip, variableTip } from '@/constants/flow/ModuleTemplate';
import { welcomeTextTip, variableTip, questionGuideTip } from '@/constants/flow/ModuleTemplate';
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
import MyIcon from '@/components/Icon';
@@ -26,14 +27,18 @@ import NodeCard from '../modules/NodeCard';
import { VariableItemType } from '@/types/app';
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const theme = useTheme();
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<WelcomeText data={data} />
<Box mt={3}>
<Box pt={4} pb={2}>
<ChatStartVariable data={data} />
</Box>
<Box pt={3} borderTop={theme.borders.base}>
<QuestionGuide data={data} />
</Box>
</Container>
</NodeCard>
</>
@@ -196,3 +201,40 @@ function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
</>
);
}
function QuestionGuide({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId, onChangeNode } = data;
const questionGuide = useMemo(
() =>
(inputs.find((item) => item.key === SystemInputEnum.questionGuide)?.value as boolean) ||
false,
[inputs]
);
return (
<Flex alignItems={'center'}>
<MyIcon name={'questionGuide'} mr={2} w={'16px'} />
<Box></Box>
<MyTooltip label={questionGuideTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Switch
isChecked={questionGuide}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
onChangeNode({
moduleId,
key: SystemInputEnum.questionGuide,
type: 'inputs',
value: {
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
value
}
});
}}
/>
</Flex>
);
}

View File

@@ -16,7 +16,8 @@ import {
useDisclosure,
Button,
IconButton,
Text
Text,
Switch
} from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
import { useQuery } from '@tanstack/react-query';
@@ -34,7 +35,8 @@ import { formatPrice } from '@fastgpt/common/bill/index';
import {
ChatModelSystemTip,
ChatModelLimitTip,
welcomeTextTip
welcomeTextTip,
questionGuideTip
} from '@/constants/flow/ModuleTemplate';
import { AppModuleItemType, VariableItemType } from '@/types/app';
import { useRequest } from '@/hooks/useRequest';
@@ -46,7 +48,7 @@ import { useToast } from '@/hooks/useToast';
import { AppSchema } from '@/types/mongoSchema';
import { delModelById } from '@/api/app';
import { useTranslation } from 'react-i18next';
import { getGuideModules } from '@/components/ChatBox/utils';
import { getGuideModule } from '@/components/ChatBox/utils';
import dynamic from 'next/dynamic';
import MySelect from '@/components/Select';
@@ -89,6 +91,7 @@ const Settings = ({ appId }: { appId: string }) => {
const { register, setValue, getValues, reset, handleSubmit, control } = useForm<EditFormType>({
defaultValues: getDefaultAppForm()
});
const {
fields: variables,
append: appendVariable,
@@ -157,7 +160,9 @@ const Settings = ({ appId }: { appId: string }) => {
const appModule2Form = useCallback(() => {
const formVal = appModules2Form(appDetail.modules);
reset(formVal);
setRefresh((state) => !state);
setTimeout(() => {
setRefresh((state) => !state);
}, 100);
}, [appDetail.modules, reset]);
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
@@ -536,6 +541,26 @@ const Settings = ({ appId }: { appId: string }) => {
</Grid>
</Box>
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<MyIcon name={'questionGuide'} mr={2} w={'16px'} />
<Box></Box>
<MyTooltip label={questionGuideTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Switch
isChecked={getValues('questionGuide')}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue('questionGuide', value);
setRefresh((state) => !state);
}}
/>
</Flex>
</Box>
<ConfirmSaveModal />
<ConfirmDelModal />
{settingAppInfo && (
@@ -678,7 +703,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
appAvatar={appDetail.avatar}
userAvatar={userInfo?.avatar}
showMarkIcon
{...getGuideModules(modules)}
userGuideModule={getGuideModule(modules)}
onStartChat={startChat}
onDelMessage={() => {}}
/>

View File

@@ -293,8 +293,7 @@ function DetailLogsModal({
feedbackType={'admin'}
showMarkIcon
showVoiceIcon={false}
variableModules={chat?.app.variableModules}
welcomeText={chat?.app.welcomeText}
userGuideModule={chat?.app?.userGuideModule}
/>
</Box>
</Flex>

View File

@@ -97,7 +97,7 @@ const Share = ({ appId }: { appId: string }) => {
<Th>()</Th>
<Th>IP限流/</Th>
<Th></Th>
<Th>token校</Th>
<Th></Th>
</>
)}
<Th>使</Th>

View File

@@ -361,9 +361,8 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
chatId={chatId}
appAvatar={chatData.app.avatar}
userAvatar={userInfo?.avatar}
variableModules={chatData.app.variableModules}
userGuideModule={chatData.app?.userGuideModule}
feedbackType={'user'}
welcomeText={chatData.app.welcomeText}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={delOneHistoryItem}

View File

@@ -245,8 +245,7 @@ const OutLink = ({
ref={ChatBoxRef}
appAvatar={shareChatData.app.avatar}
userAvatar={shareChatData.userAvatar}
variableModules={shareChatData.app.variableModules}
welcomeText={shareChatData.app.welcomeText}
userGuideModule={shareChatData.app?.userGuideModule}
feedbackType={'user'}
onUpdateVariable={(e) => {
setShareChatData((state) => ({

View File

@@ -56,3 +56,5 @@ export const Prompt_CQJson = `我会给你几个问题类型,请参考额外
{{text}}
"""
`;
export const Prompt_QuestionGuide = `我不太清楚问你什么问题,请帮我生成 3 个问题引导我继续提问。问题的长度应小于20个字符按 JSON 格式返回: ["问题1", "问题2", "问题3"]`;

View File

@@ -5,6 +5,7 @@ import { ChatHistoryItemResType } from '@/types/chat';
import { formatPrice } from '@fastgpt/common/bill/index';
import { addLog } from '@/service/utils/tools';
import type { CreateBillType } from '@/types/common/bill';
import { defaultQGModel } from '@/pages/api/system/getInitData';
async function createBill(data: CreateBillType) {
try {
@@ -170,3 +171,22 @@ export const countModelPrice = ({ model, tokens }: { model: string; tokens: numb
if (!modelData) return 0;
return modelData.price * tokens;
};
export const pushQuestionGuideBill = ({ tokens, userId }: { tokens: number; userId: string }) => {
const qgModel = global.qgModel || defaultQGModel;
const total = qgModel.price * tokens;
createBill({
userId,
appName: '问题指引',
total,
source: BillSourceEnum.fastgpt,
list: [
{
moduleName: '问题指引',
amount: total,
model: qgModel.name,
tokenLen: tokens
}
]
});
};

View File

@@ -24,7 +24,7 @@ export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
res,
event: detail ? sseResponseEventEnum.answer : undefined,
data: textAdaptGptResponse({
text: text.replace(/\\n/g, '\n')
text: text.replace?.(/\\n/g, '\n') || ''
})
});
}

View File

@@ -69,6 +69,7 @@ declare global {
var qaModel: QAModelItemType;
var extractModel: FunctionModelItemType;
var cqModel: FunctionModelItemType;
var qgModel: FunctionModelItemType;
var vectorModels: VectorModelItemType[];
var systemVersion: string;

View File

@@ -10,7 +10,7 @@ import { SystemInputEnum } from '@/constants/app';
import type { SelectedDatasetType } from '@/types/core/dataset';
import { FlowInputItemType } from '@/types/flow';
import type { AIChatProps } from '@/types/core/aiChat';
import { getGuideModules } from '@/components/ChatBox/utils';
import { getGuideModule, splitGuideModule } from '@/components/ChatBox/utils';
export type EditFormType = {
chatModel: AIChatProps;
@@ -26,6 +26,7 @@ export type EditFormType = {
};
};
variables: VariableItemType[];
questionGuide: boolean;
};
export const getDefaultAppForm = (): EditFormType => {
const defaultChatModel = chatModelList[0];
@@ -52,7 +53,8 @@ export const getDefaultAppForm = (): EditFormType => {
text: ''
}
},
variables: []
variables: [],
questionGuide: false
};
};
@@ -137,13 +139,17 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
target?.inputs?.find((item) => item.key === SpecialInputKeyEnum.answerText)?.value || '';
}
} else if (module.flowType === FlowModuleTypeEnum.userGuide) {
const { welcomeText, variableModules } = getGuideModules(modules);
const { welcomeText, variableModules, questionGuide } = splitGuideModule(
getGuideModule(modules)
);
if (welcomeText) {
defaultAppForm.guide.welcome = {
text: welcomeText
};
}
defaultAppForm.variables = variableModules;
defaultAppForm.questionGuide = !!questionGuide;
}
});
@@ -225,15 +231,21 @@ const userGuideTemplate = (formData: EditFormType): AppModuleItemType[] => [
inputs: [
{
key: SystemInputEnum.welcomeText,
type: FlowInputItemTypeEnum.input,
type: FlowInputItemTypeEnum.hidden,
label: '开场白',
value: formData.guide.welcome.text
},
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
type: FlowInputItemTypeEnum.hidden,
label: '对话框变量',
value: formData.variables
},
{
key: SystemInputEnum.questionGuide,
type: FlowInputItemTypeEnum.hidden,
label: '问题引导',
value: formData.questionGuide
}
],
outputs: [],