diff --git a/Dockerfile b/Dockerfile index c91bd69b8..bb71d714a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 ./ diff --git a/docSite/content/docs/installation/upgrading/445.md b/docSite/content/docs/installation/upgrading/445.md index 68253b764..a4a6086ea 100644 --- a/docSite/content/docs/installation/upgrading/445.md +++ b/docSite/content/docs/installation/upgrading/445.md @@ -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. 优化 - 全局变量与开场白合并成同一模块。 \ No newline at end of file diff --git a/docSite/content/docs/installation/upgrading/_index.md b/docSite/content/docs/installation/upgrading/_index.md index bf6d6a1b8..ad350831b 100644 --- a/docSite/content/docs/installation/upgrading/_index.md +++ b/docSite/content/docs/installation/upgrading/_index.md @@ -1,7 +1,7 @@ --- weight: 760 -title: "版本升级" -description: "FastGPT 升级指南" +title: "版本更新/升级操作" +description: "FastGPT 版本更新介绍及升级操作" icon: upgrade draft: false images: [] diff --git a/projects/app/data/config.json b/projects/app/data/config.json index 5f22e1b27..352e3a2d2 100644 --- a/projects/app/data/config.json +++ b/projects/app/data/config.json @@ -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 } } diff --git a/projects/app/public/docs/versionIntro.md b/projects/app/public/docs/versionIntro.md index ee8bab486..4b314b865 100644 --- a/projects/app/public/docs/versionIntro.md +++ b/projects/app/public/docs/versionIntro.md @@ -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/) diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index acc68aafe..bd3701f7d 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -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", diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index 7ecb67eff..c7ff65b90 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -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": "选择知识库", diff --git a/projects/app/src/api/core/ai/agent/api.ts b/projects/app/src/api/core/ai/agent/api.ts new file mode 100644 index 000000000..c19c2b765 --- /dev/null +++ b/projects/app/src/api/core/ai/agent/api.ts @@ -0,0 +1,5 @@ +import { GET, POST, PUT, DELETE } from '@/api/request'; +import { CreateQuestionGuideProps } from './type'; + +export const postQuestionGuide = (data: CreateQuestionGuideProps, cancelToken: AbortController) => + POST('/core/ai/agent/createQuestionGuide', data, { cancelToken }); diff --git a/projects/app/src/api/core/ai/agent/type.d.ts b/projects/app/src/api/core/ai/agent/type.d.ts new file mode 100644 index 000000000..e2d93013f --- /dev/null +++ b/projects/app/src/api/core/ai/agent/type.d.ts @@ -0,0 +1,5 @@ +import { ChatCompletionRequestMessage } from '@fastgpt/core/aiApi/type'; + +export type CreateQuestionGuideProps = { + messages: ChatCompletionRequestMessage[]; +}; diff --git a/projects/app/src/api/request.ts b/projects/app/src/api/request.ts index ed754c9e4..20ea00d8c 100644 --- a/projects/app/src/api/request.ts +++ b/projects/app/src/api/request.ts @@ -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)) diff --git a/projects/app/src/api/response/chat.d.ts b/projects/app/src/api/response/chat.d.ts index 323410b4c..5c07ad124 100644 --- a/projects/app/src/api/response/chat.d.ts +++ b/projects/app/src/api/response/chat.d.ts @@ -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; diff --git a/projects/app/src/components/ChatBox/index.tsx b/projects/app/src/components/ChatBox/index.tsx index 169480512..775112649 100644 --- a/projects/app/src/components/ChatBox/index.tsx +++ b/projects/app/src/components/ChatBox/index.tsx @@ -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) => void; onStartChat?: (e: StartChatFnProps) => Promise<{ @@ -171,24 +171,28 @@ const ChatBox = ( const { toast } = useToast(); const { isPc } = useGlobalStore(); const TextareaDom = useRef(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>({}); + const [variables, setVariables] = useState>({}); // settings variable const [chatHistory, setChatHistory] = useState([]); const [feedbackId, setFeedbackId] = useState(); 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([]); 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 = ( + {/* chat box container */} {showEmpty && } @@ -839,6 +890,43 @@ const ChatBox = ( contentId={item.dataId} responseData={item.responseData} /> + {/* question guide */} + {index === chatHistory.length - 1 && + !isChatting && + questionGuides.length > 0 && ( + + + {t('chat.Question Guide Tips')} + + {questionGuides.map((item) => ( + + ))} + + )} {/* admin mark content */} {showMarkIcon && item.adminFeedback && ( @@ -928,7 +1016,7 @@ const ChatBox = ( cursor={'pointer'} name={'stop'} color={'gray.500'} - onClick={() => controller.current?.abort('stop')} + onClick={() => chatController.current?.abort('stop')} /> ) : ( { - 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[] => { diff --git a/projects/app/src/components/Icon/icons/app/questionGuide.svg b/projects/app/src/components/Icon/icons/app/questionGuide.svg new file mode 100644 index 000000000..4903f93dc --- /dev/null +++ b/projects/app/src/components/Icon/icons/app/questionGuide.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/projects/app/src/components/Icon/index.tsx b/projects/app/src/components/Icon/index.tsx index 5cb0840de..580ebfcc5 100644 --- a/projects/app/src/components/Icon/index.tsx +++ b/projects/app/src/components/Icon/index.tsx @@ -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; diff --git a/projects/app/src/constants/app.ts b/projects/app/src/constants/app.ts index a9b070690..b3b5aad26 100644 --- a/projects/app/src/constants/app.ts +++ b/projects/app/src/constants/app.ts @@ -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 { diff --git a/projects/app/src/constants/flow/ModuleTemplate.ts b/projects/app/src/constants/flow/ModuleTemplate.ts index f45278b70..735c9f5f6 100644 --- a/projects/app/src/constants/flow/ModuleTemplate.ts +++ b/projects/app/src/constants/flow/ModuleTemplate.ts @@ -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: [] diff --git a/projects/app/src/constants/flow/index.ts b/projects/app/src/constants/flow/index.ts index e9945ee90..036d2770a 100644 --- a/projects/app/src/constants/flow/index.ts +++ b/projects/app/src/constants/flow/index.ts @@ -10,6 +10,7 @@ export enum FlowInputItemTypeEnum { custom = 'custom', target = 'target', none = 'none', + switch = 'switch', hidden = 'hidden' } diff --git a/projects/app/src/pages/api/chat/init.ts b/projects/app/src/pages/api/chat/init.ts index 82107adc9..1c30987ed 100644 --- a/projects/app/src/pages/api/chat/init.ts +++ b/projects/app/src/pages/api/chat/init.ts @@ -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, diff --git a/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts b/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts new file mode 100644 index 000000000..a9bc96110 --- /dev/null +++ b/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts @@ -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) { + 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 + }); + } +} diff --git a/projects/app/src/pages/api/support/outLink/init.ts b/projects/app/src/pages/api/support/outLink/init.ts index b98d6797e..69b3421f6 100644 --- a/projects/app/src/pages/api/support/outLink/init.ts +++ b/projects/app/src/pages/api/support/outLink/init.ts @@ -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, diff --git a/projects/app/src/pages/api/system/getInitData.ts b/projects/app/src/pages/api/system/getInitData.ts index 6abe9691a..8e52bdbf1 100644 --- a/projects/app/src/pages/api/system/getInitData.ts +++ b/projects/app/src/pages/api/system/getInitData.ts @@ -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() { diff --git a/projects/app/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx b/projects/app/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx index f3f43abc1..df282b8cd 100644 --- a/projects/app/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx +++ b/projects/app/src/pages/app/detail/components/AdEdit/components/ChatTest.tsx @@ -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={() => {}} /> diff --git a/projects/app/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx b/projects/app/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx index b95e05ba4..5e497c286 100644 --- a/projects/app/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx +++ b/projects/app/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx @@ -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) => { + const theme = useTheme(); return ( <> - + + + + @@ -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 ( + + + 下一步指引 + + + + + { + const value = e.target.checked; + onChangeNode({ + moduleId, + key: SystemInputEnum.questionGuide, + type: 'inputs', + value: { + ...inputs.find((item) => item.key === SystemInputEnum.questionGuide), + value + } + }); + }} + /> + + ); +} diff --git a/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx b/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx index fd4a833df..44cd4c6ce 100644 --- a/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx @@ -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({ 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 }) => { + + + + 下一步指引 + + + + + { + const value = e.target.checked; + setValue('questionGuide', value); + setRefresh((state) => !state); + }} + /> + + + {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={() => {}} /> diff --git a/projects/app/src/pages/app/detail/components/Logs.tsx b/projects/app/src/pages/app/detail/components/Logs.tsx index 41acfc5df..41c2d6c17 100644 --- a/projects/app/src/pages/app/detail/components/Logs.tsx +++ b/projects/app/src/pages/app/detail/components/Logs.tsx @@ -293,8 +293,7 @@ function DetailLogsModal({ feedbackType={'admin'} showMarkIcon showVoiceIcon={false} - variableModules={chat?.app.variableModules} - welcomeText={chat?.app.welcomeText} + userGuideModule={chat?.app?.userGuideModule} /> diff --git a/projects/app/src/pages/app/detail/components/OutLink/Share.tsx b/projects/app/src/pages/app/detail/components/OutLink/Share.tsx index 3a8dcff78..c5f03a4e6 100644 --- a/projects/app/src/pages/app/detail/components/OutLink/Share.tsx +++ b/projects/app/src/pages/app/detail/components/OutLink/Share.tsx @@ -97,7 +97,7 @@ const Share = ({ appId }: { appId: string }) => { 金额限制(¥) IP限流(人/分钟) 过期时间 - token校验 + 身份校验 )} 最后使用时间 diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index 0e1935deb..bc3900ae8 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -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} diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index 48d53d359..26b449ab2 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -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) => ({ diff --git a/projects/app/src/prompts/core/agent.ts b/projects/app/src/prompts/core/agent.ts index 1757a872b..e6edde2f0 100644 --- a/projects/app/src/prompts/core/agent.ts +++ b/projects/app/src/prompts/core/agent.ts @@ -56,3 +56,5 @@ export const Prompt_CQJson = `我会给你几个问题类型,请参考额外 {{text}} """ `; + +export const Prompt_QuestionGuide = `我不太清楚问你什么问题,请帮我生成 3 个问题,引导我继续提问。问题的长度应小于20个字符,按 JSON 格式返回: ["问题1", "问题2", "问题3"]`; diff --git a/projects/app/src/service/common/bill/push.ts b/projects/app/src/service/common/bill/push.ts index ed5d789f0..da4487268 100644 --- a/projects/app/src/service/common/bill/push.ts +++ b/projects/app/src/service/common/bill/push.ts @@ -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 + } + ] + }); +}; diff --git a/projects/app/src/service/moduleDispatch/tools/answer.ts b/projects/app/src/service/moduleDispatch/tools/answer.ts index a56982517..db092c2b0 100644 --- a/projects/app/src/service/moduleDispatch/tools/answer.ts +++ b/projects/app/src/service/moduleDispatch/tools/answer.ts @@ -24,7 +24,7 @@ export const dispatchAnswer = (props: Record): AnswerResponse => { res, event: detail ? sseResponseEventEnum.answer : undefined, data: textAdaptGptResponse({ - text: text.replace(/\\n/g, '\n') + text: text.replace?.(/\\n/g, '\n') || '' }) }); } diff --git a/projects/app/src/types/index.d.ts b/projects/app/src/types/index.d.ts index 3f2695ce7..66008d469 100644 --- a/projects/app/src/types/index.d.ts +++ b/projects/app/src/types/index.d.ts @@ -69,6 +69,7 @@ declare global { var qaModel: QAModelItemType; var extractModel: FunctionModelItemType; var cqModel: FunctionModelItemType; + var qgModel: FunctionModelItemType; var vectorModels: VectorModelItemType[]; var systemVersion: string; diff --git a/projects/app/src/utils/app.ts b/projects/app/src/utils/app.ts index d3e4e3e49..00fd9e8ba 100644 --- a/projects/app/src/utils/app.ts +++ b/projects/app/src/utils/app.ts @@ -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: [],