feat: question guide (#1508)

* feat: question guide

* fix

* fix

* fix

* change interface

* fix
This commit is contained in:
heheer
2024-05-19 17:34:16 +08:00
committed by GitHub
parent fd31a0b763
commit e35ce2caa0
40 changed files with 1071 additions and 34 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,43 @@
---
title: "自定义词库地址"
description: "FastGPT 自定义输入提示的接口地址"
icon: "code"
draft: false
toc: true
weight: 350
---
![](/imgs/questionGuide.png)
## 什么是输入提示
可自定义开启或关闭,当输入提示开启,并且词库中存在数据时,用户在输入问题时如果输入命中词库,那么会在输入框上方展示对应的智能推荐数据
用户可配置词库,选择存储在 FastGPT 数据库中,或者提供自定义接口获取词库
## 数据格式
词库的形式为一个字符串数组,定义的词库接口应该有两种方法 —— GET & POST
### GET
对于 GET 方法用于获取词库数据FastGPT 会给接口发送数据 query 为
```
{
appId: 'xxxx'
}
```
返回数据格式应当为
```
{
data: ['xxx', 'xxxx']
}
```
### POST
对于 POST 方法用于更新词库数据FastGPT 会给接口发送数据 body 为
```
{
appId: 'xxxx',
text: ['xxx', 'xxxx']
}
```
接口应当按照获取的数据格式存储相对应的词库数组

View File

@@ -18,3 +18,9 @@ export const defaultWhisperConfig: AppWhisperConfigType = {
autoSend: false, autoSend: false,
autoTTSResponse: false autoTTSResponse: false
}; };
export const defaultQuestionGuideTextConfig = {
open: false,
textList: [],
customURL: ''
};

View File

@@ -88,6 +88,7 @@ export type AppSimpleEditFormType = {
}; };
whisper: AppWhisperConfigType; whisper: AppWhisperConfigType;
scheduleTrigger: AppScheduledTriggerConfigType | null; scheduleTrigger: AppScheduledTriggerConfigType | null;
questionGuideText: AppQuestionGuideTextConfigType;
}; };
}; };
@@ -123,6 +124,12 @@ export type AppWhisperConfigType = {
autoSend: boolean; autoSend: boolean;
autoTTSResponse: boolean; autoTTSResponse: boolean;
}; };
// question guide text
export type AppQuestionGuideTextConfigType = {
open: boolean;
textList: string[];
customURL: string;
};
// interval timer // interval timer
export type AppScheduledTriggerConfigType = { export type AppScheduledTriggerConfigType = {
cronString: string; cronString: string;

View File

@@ -5,7 +5,7 @@ import type { FlowNodeInputItemType } from '../workflow/type/io.d';
import { getGuideModule, splitGuideModule } from '../workflow/utils'; import { getGuideModule, splitGuideModule } from '../workflow/utils';
import { StoreNodeItemType } from '../workflow/type'; import { StoreNodeItemType } from '../workflow/type';
import { DatasetSearchModeEnum } from '../dataset/constants'; import { DatasetSearchModeEnum } from '../dataset/constants';
import { defaultWhisperConfig } from './constants'; import { defaultQuestionGuideTextConfig, defaultWhisperConfig } from './constants';
export const getDefaultAppForm = (): AppSimpleEditFormType => { export const getDefaultAppForm = (): AppSimpleEditFormType => {
return { return {
@@ -35,7 +35,8 @@ export const getDefaultAppForm = (): AppSimpleEditFormType => {
type: 'web' type: 'web'
}, },
whisper: defaultWhisperConfig, whisper: defaultWhisperConfig,
scheduleTrigger: null scheduleTrigger: null,
questionGuideText: defaultQuestionGuideTextConfig
} }
}; };
}; };
@@ -109,7 +110,8 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
questionGuide, questionGuide,
ttsConfig, ttsConfig,
whisperConfig, whisperConfig,
scheduledTriggerConfig scheduledTriggerConfig,
questionGuideText
} = splitGuideModule(getGuideModule(nodes)); } = splitGuideModule(getGuideModule(nodes));
defaultAppForm.userGuide = { defaultAppForm.userGuide = {
@@ -118,7 +120,8 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
questionGuide: questionGuide, questionGuide: questionGuide,
tts: ttsConfig, tts: ttsConfig,
whisper: whisperConfig, whisper: whisperConfig,
scheduleTrigger: scheduledTriggerConfig scheduleTrigger: scheduledTriggerConfig,
questionGuideText: questionGuideText
}; };
} else if (node.flowNodeType === FlowNodeTypeEnum.pluginModule) { } else if (node.flowNodeType === FlowNodeTypeEnum.pluginModule) {
if (!node.pluginId) return; if (!node.pluginId) return;

View File

@@ -45,6 +45,7 @@ export enum NodeInputKeyEnum {
whisper = 'whisper', whisper = 'whisper',
variables = 'variables', variables = 'variables',
scheduleTrigger = 'scheduleTrigger', scheduleTrigger = 'scheduleTrigger',
questionGuideText = 'questionGuideText',
// entry // entry
userChatInput = 'userChatInput', userChatInput = 'userChatInput',

View File

@@ -56,6 +56,12 @@ export const SystemConfigNode: FlowNodeTemplateType = {
renderTypeList: [FlowNodeInputTypeEnum.hidden], renderTypeList: [FlowNodeInputTypeEnum.hidden],
valueType: WorkflowIOValueTypeEnum.any, valueType: WorkflowIOValueTypeEnum.any,
label: '' label: ''
},
{
key: NodeInputKeyEnum.questionGuideText,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
valueType: WorkflowIOValueTypeEnum.any,
label: ''
} }
], ],
outputs: [] outputs: []

View File

@@ -11,7 +11,8 @@ import type {
VariableItemType, VariableItemType,
AppTTSConfigType, AppTTSConfigType,
AppWhisperConfigType, AppWhisperConfigType,
AppScheduledTriggerConfigType AppScheduledTriggerConfigType,
AppQuestionGuideTextConfigType
} from '../app/type'; } from '../app/type';
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type'; import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
import { defaultWhisperConfig } from '../app/constants'; import { defaultWhisperConfig } from '../app/constants';
@@ -59,13 +60,20 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.scheduleTrigger)?.value ?? guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.scheduleTrigger)?.value ??
null; null;
const questionGuideText: AppQuestionGuideTextConfigType = guideModules?.inputs?.find(
(item) => item.key === NodeInputKeyEnum.questionGuideText
)?.value || {
open: false
};
return { return {
welcomeText, welcomeText,
variableNodes, variableNodes,
questionGuide, questionGuide,
ttsConfig, ttsConfig,
whisperConfig, whisperConfig,
scheduledTriggerConfig scheduledTriggerConfig,
questionGuideText
}; };
}; };
export const replaceAppChatConfig = ({ export const replaceAppChatConfig = ({

View File

@@ -0,0 +1,40 @@
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
export const AppQGuideCollectionName = 'app_question_guides';
type AppQGuideSchemaType = {
_id: string;
appId: string;
teamId: string;
text: string;
};
const AppQGuideSchema = new Schema({
appId: {
type: Schema.Types.ObjectId,
ref: AppQGuideCollectionName,
required: true
},
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
text: {
type: String,
default: ''
}
});
try {
AppQGuideSchema.index({ appId: 1 });
} catch (error) {
console.log(error);
}
export const MongoAppQGuide: Model<AppQGuideSchemaType> =
models[AppQGuideCollectionName] || model(AppQGuideCollectionName, AppQGuideSchema);
MongoAppQGuide.syncIndexes();

View File

@@ -1,6 +1,7 @@
// @ts-nocheck // @ts-nocheck
export const iconPaths = { export const iconPaths = {
book: () => import('./icons/book.svg'),
change: () => import('./icons/change.svg'), change: () => import('./icons/change.svg'),
chatSend: () => import('./icons/chatSend.svg'), chatSend: () => import('./icons/chatSend.svg'),
closeSolid: () => import('./icons/closeSolid.svg'), closeSolid: () => import('./icons/closeSolid.svg'),
@@ -64,6 +65,7 @@ export const iconPaths = {
'core/app/appApiLight': () => import('./icons/core/app/appApiLight.svg'), 'core/app/appApiLight': () => import('./icons/core/app/appApiLight.svg'),
'core/app/customFeedback': () => import('./icons/core/app/customFeedback.svg'), 'core/app/customFeedback': () => import('./icons/core/app/customFeedback.svg'),
'core/app/headphones': () => import('./icons/core/app/headphones.svg'), 'core/app/headphones': () => import('./icons/core/app/headphones.svg'),
'core/app/inputGuides': () => import('./icons/core/app/inputGuides.svg'),
'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'), 'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'),
'core/app/markLight': () => import('./icons/core/app/markLight.svg'), 'core/app/markLight': () => import('./icons/core/app/markLight.svg'),
'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'), 'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'),
@@ -219,6 +221,7 @@ export const iconPaths = {
'support/user/userFill': () => import('./icons/support/user/userFill.svg'), 'support/user/userFill': () => import('./icons/support/user/userFill.svg'),
'support/user/userLight': () => import('./icons/support/user/userLight.svg'), 'support/user/userLight': () => import('./icons/support/user/userLight.svg'),
text: () => import('./icons/text.svg'), text: () => import('./icons/text.svg'),
union: () => import('./icons/union.svg'),
user: () => import('./icons/user.svg'), user: () => import('./icons/user.svg'),
wx: () => import('./icons/wx.svg') wx: () => import('./icons/wx.svg')
}; };

View File

@@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.18118 2.74428L4.49007 2.74428C5.17179 2.74427 5.72618 2.74427 6.17608 2.78102C6.64077 2.81899 7.0558 2.89966 7.44194 3.09642C7.85435 3.30655 8.21445 3.60159 8.50012 3.95943C8.78579 3.60159 9.14588 3.30655 9.55829 3.09642C9.94444 2.89966 10.3595 2.81899 10.8242 2.78102C11.2741 2.74427 11.8284 2.74427 12.5102 2.74428L12.8191 2.74428C13.1499 2.74426 13.4396 2.74425 13.6792 2.76382C13.9333 2.78459 14.1925 2.83086 14.4436 2.95881C14.8139 3.14753 15.1151 3.44864 15.3038 3.81901C15.4317 4.07013 15.478 4.32926 15.4988 4.58343C15.5183 4.82296 15.5183 5.11271 15.5183 5.44351V11.5565C15.5183 11.8873 15.5183 12.177 15.4988 12.4166C15.478 12.6707 15.4317 12.9299 15.3038 13.181C15.1151 13.5514 14.8139 13.8525 14.4436 14.0412C14.1925 14.1691 13.9333 14.2154 13.6792 14.2362C13.4396 14.2557 13.1499 14.2557 12.819 14.2557H4.18119C3.85038 14.2557 3.56062 14.2557 3.32108 14.2362C3.06691 14.2154 2.80778 14.1691 2.55666 14.0412C2.1863 13.8525 1.88518 13.5514 1.69647 13.181C1.56852 12.9299 1.52225 12.6707 1.50148 12.4166C1.48191 12.177 1.48192 11.8873 1.48193 11.5565V5.44352C1.48192 5.11272 1.48191 4.82296 1.50148 4.58343C1.52225 4.32926 1.56852 4.07013 1.69647 3.81901C1.88518 3.44864 2.1863 3.14753 2.55666 2.95881C2.80778 2.83086 3.06691 2.78459 3.32108 2.76382C3.56062 2.74425 3.85037 2.74426 4.18118 2.74428ZM7.79425 7.49003C7.79425 6.77134 7.7937 6.27477 7.76219 5.88914C7.73136 5.51176 7.67443 5.3032 7.59598 5.14924C7.42158 4.80696 7.1433 4.52869 6.80103 4.35429C6.64706 4.27584 6.43851 4.21891 6.06112 4.18808C5.6755 4.15657 5.17893 4.15602 4.46024 4.15602H4.20775C3.84258 4.15602 3.61115 4.15657 3.43604 4.17088C3.26918 4.18451 3.21652 4.20704 3.19758 4.21669C3.09285 4.27005 3.0077 4.3552 2.95434 4.45993C2.94469 4.47886 2.92217 4.53152 2.90853 4.69839C2.89423 4.87349 2.89368 5.10492 2.89368 5.47009V11.5299C2.89368 11.8951 2.89423 12.1265 2.90853 12.3016C2.92217 12.4685 2.94469 12.5211 2.95434 12.5401C3.0077 12.6448 3.09285 12.73 3.19758 12.7833C3.21652 12.793 3.26918 12.8155 3.43604 12.8291C3.61115 12.8434 3.84258 12.844 4.20775 12.844H7.79425V7.49003ZM9.20599 12.844V7.49003C9.20599 6.77134 9.20654 6.27477 9.23805 5.88914C9.26888 5.51176 9.32581 5.3032 9.40425 5.14924C9.57865 4.80696 9.85693 4.52869 10.1992 4.35429C10.3532 4.27584 10.5617 4.21891 10.9391 4.18808C11.3247 4.15657 11.8213 4.15602 12.54 4.15602H12.7925C13.1577 4.15602 13.3891 4.15657 13.5642 4.17088C13.7311 4.18451 13.7837 4.20704 13.8027 4.21669C13.9074 4.27005 13.9925 4.3552 14.0459 4.45993C14.0555 4.47886 14.0781 4.53152 14.0917 4.69839C14.106 4.87349 14.1066 5.10492 14.1066 5.47009V11.5299C14.1066 11.8951 14.106 12.1265 14.0917 12.3016C14.0781 12.4685 14.0555 12.5211 14.0459 12.5401C13.9925 12.6448 13.9074 12.73 13.8027 12.7833C13.7837 12.793 13.7311 12.8155 13.5642 12.8291C13.3891 12.8434 13.1577 12.844 12.7925 12.844H9.20599Z" fill="#2B5FD9"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,3 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.1665 4.49049C2.1665 3.57002 2.9127 2.82382 3.83317 2.82382H17.1665C18.087 2.82382 18.8332 3.57002 18.8332 4.49049V10.9765C18.8332 11.897 18.087 12.6432 17.1665 12.6432H3.83317C2.9127 12.6432 2.1665 11.897 2.1665 10.9765V4.49049ZM4.49984 5.07737C4.13165 5.07737 3.83317 5.37585 3.83317 5.74404C3.83317 6.11223 4.13165 6.4107 4.49984 6.4107H9.15084C9.51903 6.4107 9.8175 6.11223 9.8175 5.74404C9.8175 5.37585 9.51903 5.07737 9.15084 5.07737H4.49984ZM4.49984 7.73349C4.13165 7.73349 3.83317 8.03197 3.83317 8.40016C3.83317 8.76835 4.13165 9.06683 4.49984 9.06683H12.5075C12.8757 9.06683 13.1742 8.76835 13.1742 8.40016C13.1742 8.03197 12.8757 7.73349 12.5075 7.73349H4.49984ZM2.1665 15.0673C2.1665 14.423 2.68884 13.9006 3.33317 13.9006H17.6665C18.3108 13.9006 18.8332 14.423 18.8332 15.0673V17.0095C18.8332 17.6538 18.3108 18.1762 17.6665 18.1762H3.33317C2.68884 18.1762 2.1665 17.6538 2.1665 17.0095V15.0673ZM16.1207 15.2885C16.2166 15.1223 16.4564 15.1223 16.5523 15.2885L17.369 16.703C17.4745 16.8857 17.3194 17.1087 17.1115 17.0733L16.3783 16.9487C16.3506 16.944 16.3224 16.944 16.2948 16.9487L15.5616 17.0733C15.3536 17.1087 15.1985 16.8857 15.304 16.703L16.1207 15.2885Z" fill="#8774EE"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,7 @@
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.60825 0.195121C5.62729 0.160749 5.67671 0.160749 5.69574 0.195121L6.18303 1.07516C6.18758 1.08337 6.19434 1.09014 6.20255 1.09468L7.08259 1.58197C7.11696 1.60101 7.11696 1.65042 7.08259 1.66946L6.20255 2.15675C6.19434 2.1613 6.18758 2.16806 6.18303 2.17627L5.69574 3.05631C5.67671 3.09068 5.62729 3.09068 5.60825 3.05631L5.12096 2.17627C5.11642 2.16806 5.10965 2.1613 5.10144 2.15675L4.2214 1.66946C4.18703 1.65042 4.18703 1.60101 4.2214 1.58197L5.10144 1.09468C5.10965 1.09014 5.11642 1.08337 5.12096 1.07516L5.60825 0.195121Z" fill="#485264"/>
<path d="M0.635575 0.628892C0.908942 0.355524 1.35216 0.355524 1.62552 0.628892L8.6323 7.63567C8.90567 7.90904 8.90567 8.35225 8.6323 8.62562C8.35894 8.89899 7.91572 8.89899 7.64235 8.62562L0.635575 1.61884C0.362208 1.34547 0.362208 0.902259 0.635575 0.628892Z" fill="#485264"/>
<path d="M1.69571 5.19046C1.67668 5.15609 1.62726 5.15609 1.60822 5.19046L1.12093 6.0705C1.11639 6.07871 1.10962 6.08547 1.10141 6.09002L0.221372 6.57731C0.187 6.59634 0.187 6.64576 0.221373 6.6648L1.10141 7.15209C1.10962 7.15663 1.11639 7.1634 1.12093 7.17161L1.60822 8.05165C1.62726 8.08602 1.67668 8.08602 1.69571 8.05165L2.183 7.17161C2.18755 7.1634 2.19431 7.15663 2.20252 7.15209L3.08256 6.6648C3.11693 6.64576 3.11693 6.59634 3.08256 6.57731L2.20252 6.09002C2.19431 6.08547 2.18755 6.07871 2.183 6.0705L1.69571 5.19046Z" fill="#485264"/>
<path d="M0.177731 3.65576C0.158204 3.63623 0.158204 3.60457 0.177731 3.58504L0.607417 3.15536C0.626943 3.13583 0.658601 3.13583 0.678127 3.15536L1.10781 3.58505C1.12734 3.60457 1.12734 3.63623 1.10781 3.65576L0.678127 4.08544C0.658601 4.10497 0.626943 4.10497 0.607417 4.08544L0.177731 3.65576Z" fill="#485264"/>
<path d="M7.17189 4.08037C7.15236 4.0999 7.15236 4.13156 7.17189 4.15108L7.60158 4.58077C7.6211 4.6003 7.65276 4.6003 7.67229 4.58077L8.10197 4.15108C8.1215 4.13156 8.1215 4.0999 8.10197 4.08037L7.67229 3.65069C7.65276 3.63116 7.6211 3.63116 7.60158 3.65069L7.17189 4.08037Z" fill="#485264"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -85,14 +85,16 @@ export function useScrollPagination<
...props ...props
}: { children: React.ReactNode; isLoading?: boolean } & BoxProps) => { }: { children: React.ReactNode; isLoading?: boolean } & BoxProps) => {
return ( return (
<MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}> <>
<Box ref={wrapperRef}>{children}</Box> <MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}>
<Box ref={wrapperRef}>{children}</Box>
</MyBox>
{noMore.current && ( {noMore.current && (
<Box pb={2} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}> <Box pb={2} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}>
{t('common.No more data')} {t('common.No more data')}
</Box> </Box>
)} )}
</MyBox> </>
); );
} }
); );
@@ -115,6 +117,7 @@ export function useScrollPagination<
return { return {
containerRef, containerRef,
list, list,
data,
isLoading, isLoading,
ScrollList, ScrollList,
fetchData: loadData fetchData: loadData

View File

@@ -47,6 +47,14 @@
"type": "\"{{type}}\" type\n{{description}}" "type": "\"{{type}}\" type\n{{description}}"
}, },
"modules": { "modules": {
"Config Texts": "Config Texts",
"Config question guide": "Config question guide",
"Custom question guide URL": "Custom question guide URL",
"Input Guide": "Input Guide",
"Only support CSV": "Only support CSV",
"Question Guide": "Question guide",
"Question Guide Switch": "Open question guide",
"Question Guide Texts": "Texts",
"Title is required": "Module name cannot be empty" "Title is required": "Module name cannot be empty"
} }
} }

View File

@@ -7,6 +7,7 @@
"Move": "Move", "Move": "Move",
"Name": "Name", "Name": "Name",
"New Create": "Create New", "New Create": "Create New",
"No data": "No data",
"Rename": "Rename", "Rename": "Rename",
"Running": "Running", "Running": "Running",
"UnKnow": "Unknown", "UnKnow": "Unknown",
@@ -45,6 +46,7 @@
"Delete Tip": "Delete Tip", "Delete Tip": "Delete Tip",
"Delete Warning": "Delete Warning", "Delete Warning": "Delete Warning",
"Detail": "Detail", "Detail": "Detail",
"Documents": "Documents",
"Done": "Done", "Done": "Done",
"Edit": "Edit", "Edit": "Edit",
"Exit": "Exit", "Exit": "Exit",
@@ -101,6 +103,7 @@
"Search": "Search", "Search": "Search",
"Select File Failed": "Select File Failed", "Select File Failed": "Select File Failed",
"Select One Folder": "Select a folder", "Select One Folder": "Select a folder",
"Select all": "Select all",
"Select template": "Select template", "Select template": "Select template",
"Set Avatar": "Click to set avatar", "Set Avatar": "Click to set avatar",
"Set Name": "Set a name", "Set Name": "Set a name",

View File

@@ -46,6 +46,14 @@
"type": "\"{{type}}\"类型\n{{description}}" "type": "\"{{type}}\"类型\n{{description}}"
}, },
"modules": { "modules": {
"Config Texts": "配置词库",
"Config question guide": "配置输入提示",
"Custom question guide URL": "自定义词库地址",
"Input Guide": "智能推荐",
"Only support CSV": "仅支持 CSV 导入,点击下载模板",
"Question Guide": "输入提示",
"Question Guide Switch": "是否开启",
"Question Guide Texts": "词库",
"Title is required": "模块名不能为空" "Title is required": "模块名不能为空"
} }
} }

View File

@@ -7,6 +7,7 @@
"Move": "移动", "Move": "移动",
"Name": "名称", "Name": "名称",
"New Create": "新建", "New Create": "新建",
"No data": "暂无数据",
"Rename": "重命名", "Rename": "重命名",
"Running": "运行中", "Running": "运行中",
"UnKnow": "未知", "UnKnow": "未知",
@@ -45,6 +46,7 @@
"Delete Tip": "删除提示", "Delete Tip": "删除提示",
"Delete Warning": "删除警告", "Delete Warning": "删除警告",
"Detail": "详情", "Detail": "详情",
"Documents": "文档",
"Done": "完成", "Done": "完成",
"Edit": "编辑", "Edit": "编辑",
"Exit": "退出", "Exit": "退出",
@@ -102,6 +104,7 @@
"Search": "搜索", "Search": "搜索",
"Select File Failed": "选择文件异常", "Select File Failed": "选择文件异常",
"Select One Folder": "选择一个目录", "Select One Folder": "选择一个目录",
"Select all": "全选",
"Select template": "选择模板", "Select template": "选择模板",
"Set Avatar": "点击设置头像", "Set Avatar": "点击设置头像",
"Set Name": "取个名字", "Set Name": "取个名字",

View File

@@ -16,6 +16,11 @@ import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from '.
import { textareaMinH } from './constants'; import { textareaMinH } from './constants';
import { UseFormReturn, useFieldArray } from 'react-hook-form'; import { UseFormReturn, useFieldArray } from 'react-hook-form';
import { useChatProviderStore } from './Provider'; import { useChatProviderStore } from './Provider';
import QuestionGuide from './components/QustionGuide';
import { useQuery } from '@tanstack/react-query';
import { getMyQuestionGuides } from '@/web/core/app/api';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
import { useAppStore } from '@/web/core/app/store/useAppStore';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const MessageInput = ({ const MessageInput = ({
@@ -53,6 +58,7 @@ const MessageInput = ({
const { isPc, whisperModel } = useSystemStore(); const { isPc, whisperModel } = useSystemStore();
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const { appDetail } = useAppStore();
const havInput = !!inputValue || fileList.length > 0; const havInput = !!inputValue || fileList.length > 0;
const hasFileUploading = fileList.some((item) => !item.url); const hasFileUploading = fileList.some((item) => !item.url);
@@ -205,6 +211,23 @@ const MessageInput = ({
startSpeak(finishWhisperTranscription); startSpeak(finishWhisperTranscription);
}, [finishWhisperTranscription, isSpeaking, startSpeak, stopSpeak]); }, [finishWhisperTranscription, isSpeaking, startSpeak, stopSpeak]);
const { data } = useQuery(
[appId, inputValue],
async () => {
if (!appId) return { list: [], total: 0 };
return getMyQuestionGuides({
appId,
customURL: getAppQGuideCustomURL(appDetail),
pageSize: 5,
current: 1,
searchKey: inputValue
});
},
{
enabled: !!appId
}
);
return ( return (
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}> <Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}>
<Box <Box
@@ -214,7 +237,7 @@ const MessageInput = ({
boxShadow={isSpeaking ? `0 0 10px rgba(54,111,255,0.4)` : `0 0 10px rgba(0,0,0,0.2)`} boxShadow={isSpeaking ? `0 0 10px rgba(54,111,255,0.4)` : `0 0 10px rgba(0,0,0,0.2)`}
borderRadius={['none', 'md']} borderRadius={['none', 'md']}
bg={'white'} bg={'white'}
overflow={'hidden'} overflow={'display'}
{...(isPc {...(isPc
? { ? {
border: '1px solid', border: '1px solid',
@@ -243,6 +266,21 @@ const MessageInput = ({
{t('core.chat.Converting to text')} {t('core.chat.Converting to text')}
</Flex> </Flex>
{/* popup */}
{havInput && (
<QuestionGuide
guides={data?.list || []}
setDropdownValue={(value) => setValue('input', value)}
bottom={'100%'}
top={'auto'}
left={0}
right={0}
mb={2}
overflowY={'auto'}
boxShadow={'sm'}
/>
)}
{/* file preview */} {/* file preview */}
<Flex wrap={'wrap'} px={[2, 4]} userSelect={'none'}> <Flex wrap={'wrap'} px={[2, 4]} userSelect={'none'}>
{fileList.map((item, index) => ( {fileList.map((item, index) => (
@@ -377,7 +415,12 @@ const MessageInput = ({
// @ts-ignore // @ts-ignore
e.key === 'a' && e.ctrlKey && e.target?.select(); e.key === 'a' && e.ctrlKey && e.target?.select();
if ((isPc || window !== parent) && e.keyCode === 13 && !e.shiftKey) { if (
(isPc || window !== parent) &&
e.keyCode === 13 &&
!e.shiftKey &&
!(havInput && data?.list.length && data?.list.length > 0)
) {
handleSend(); handleSend();
e.preventDefault(); e.preventDefault();
} }

View File

@@ -0,0 +1,98 @@
import { Box, BoxProps, Flex } from '@chakra-ui/react';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import React, { useCallback, useEffect } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useI18n } from '@/web/context/I18n';
export default function QuestionGuide({
guides,
setDropdownValue,
...props
}: {
guides: string[];
setDropdownValue?: (value: string) => void;
} & BoxProps) {
const [highlightedIndex, setHighlightedIndex] = React.useState(0);
const { appT } = useI18n();
const handleKeyDown = useCallback(
(event: any) => {
if (event.keyCode === 38) {
setHighlightedIndex((prevIndex) => Math.max(prevIndex - 1, 0));
} else if (event.keyCode === 40) {
setHighlightedIndex((prevIndex) => Math.min(prevIndex + 1, guides.length - 1));
} else if (event.keyCode === 13 && guides[highlightedIndex]) {
setDropdownValue?.(guides[highlightedIndex]);
event.preventDefault();
}
},
[highlightedIndex, setDropdownValue, guides]
);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]);
return guides.length ? (
<Box
bg={'white'}
boxShadow={'lg'}
borderWidth={'1px'}
borderColor={'borderColor.base'}
p={2}
borderRadius={'md'}
position={'absolute'}
top={'100%'}
w={'auto'}
zIndex={99999}
maxH={'300px'}
overflow={'auto'}
className="nowheel"
{...props}
>
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'} gap={2} mb={2} px={2}>
<MyIcon name={'union'} />
<Box>{appT('modules.Input Guide')}</Box>
</Flex>
{guides.map((item, index) => (
<Flex
alignItems={'center'}
as={'li'}
key={item}
px={4}
py={3}
borderRadius={'sm'}
cursor={'pointer'}
maxH={'300px'}
overflow={'auto'}
_notLast={{
mb: 1
}}
{...(highlightedIndex === index
? {
bg: 'primary.50',
color: 'primary.600'
}
: {
bg: 'myGray.50',
color: 'myGray.600'
})}
onMouseDown={(e) => {
e.preventDefault();
setDropdownValue?.(item);
}}
onMouseEnter={() => {
setHighlightedIndex(index);
}}
>
<Box fontSize={'sm'}>{item}</Box>
</Flex>
))}
</Box>
) : null;
}

View File

@@ -58,7 +58,7 @@ import ChatProvider, { useChatProviderStore } from './Provider';
import ChatItem from './components/ChatItem'; import ChatItem from './components/ChatItem';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useCreation, useUpdateEffect } from 'ahooks'; import { useCreation } from 'ahooks';
const ResponseTags = dynamic(() => import('./ResponseTags')); const ResponseTags = dynamic(() => import('./ResponseTags'));
const FeedbackModal = dynamic(() => import('./FeedbackModal')); const FeedbackModal = dynamic(() => import('./FeedbackModal'));

View File

@@ -0,0 +1,479 @@
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip';
import {
Box,
Button,
Flex,
ModalBody,
useDisclosure,
Switch,
Input,
Textarea,
InputGroup,
InputRightElement,
Checkbox,
useCheckboxGroup,
ModalFooter,
BoxProps
} from '@chakra-ui/react';
import React, { ChangeEvent, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'next-i18next';
import type { AppQuestionGuideTextConfigType } from '@fastgpt/global/core/app/type.d';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import MyInput from '@/components/MyInput';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { useI18n } from '@/web/context/I18n';
import { fileDownload } from '@/web/common/file/utils';
import { getDocPath } from '@/web/common/system/doc';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import { getMyQuestionGuides } from '@/web/core/app/api';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
import { useQuery } from '@tanstack/react-query';
const csvTemplate = `"第一列内容"
"必填列"
"只会将第一列内容导入,其余列会被忽略"
"AIGC发展分为几个阶段"
`;
const QGuidesConfig = ({
value,
onChange
}: {
value: AppQuestionGuideTextConfigType;
onChange: (e: AppQuestionGuideTextConfigType) => void;
}) => {
const { t } = useTranslation();
const { appT, commonT } = useI18n();
const { isOpen, onOpen, onClose } = useDisclosure();
const { isOpen: isOpenTexts, onOpen: onOpenTexts, onClose: onCloseTexts } = useDisclosure();
const isOpenQuestionGuide = value.open;
const { appDetail } = useAppStore();
const [searchKey, setSearchKey] = React.useState<string>('');
const { data } = useQuery(
[appDetail._id, searchKey],
async () => {
return getMyQuestionGuides({
appId: appDetail._id,
customURL: getAppQGuideCustomURL(appDetail),
pageSize: 30,
current: 1,
searchKey
});
},
{
enabled: !!appDetail._id
}
);
useEffect(() => {
onChange({
...value,
textList: data?.list || []
});
}, [data]);
const formLabel = useMemo(() => {
if (!isOpenQuestionGuide) {
return t('core.app.whisper.Close');
}
return t('core.app.whisper.Open');
}, [t, isOpenQuestionGuide]);
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/app/inputGuides'} mr={2} w={'20px'} />
<Box fontWeight={'medium'}>{appT('modules.Question Guide')}</Box>
<Box flex={1} />
<MyTooltip label={appT('modules.Config question guide')}>
<Button
variant={'transparentBase'}
iconSpacing={1}
size={'sm'}
mr={'-5px'}
onClick={onOpen}
>
{formLabel}
</Button>
</MyTooltip>
<MyModal
title={appT('modules.Question Guide')}
iconSrc="core/app/inputGuides"
isOpen={isOpen}
onClose={onClose}
>
<ModalBody px={[5, 16]} pt={[4, 8]} w={'500px'}>
<Flex justifyContent={'space-between'} alignItems={'center'}>
{appT('modules.Question Guide Switch')}
<Switch
isChecked={isOpenQuestionGuide}
size={'lg'}
onChange={(e) => {
onChange({
...value,
open: e.target.checked
});
}}
/>
</Flex>
{isOpenQuestionGuide && (
<>
<Flex mt={8} alignItems={'center'}>
{appT('modules.Question Guide Texts')}
<Box fontSize={'xs'} px={2} bg={'myGray.100'} ml={1} rounded={'full'}>
{value.textList.length || 0}
</Box>
<Box flex={'1 0 0'} />
<Button
variant={'whiteBase'}
size={'sm'}
leftIcon={<MyIcon boxSize={'4'} name={'common/settingLight'} />}
onClick={() => {
onOpenTexts();
onClose();
}}
>
{appT('modules.Config Texts')}
</Button>
</Flex>
<>
<Flex mt={8} alignItems={'center'}>
{appT('modules.Custom question guide URL')}
<Flex
onClick={() => window.open(getDocPath('/docs/course/custom_link'))}
color={'primary.700'}
alignItems={'center'}
cursor={'pointer'}
>
<MyIcon name={'book'} ml={4} mr={1} />
{commonT('common.Documents')}
</Flex>
<Box flex={'1 0 0'} />
</Flex>
<Textarea
mt={2}
bg={'myGray.50'}
defaultValue={value.customURL}
onBlur={(e) =>
onChange({
...value,
customURL: e.target.value
})
}
/>
</>
</>
)}
</ModalBody>
<ModalFooter px={[5, 16]} pb={[4, 8]}>
<Button onClick={() => onClose()}>{commonT('common.Confirm')}</Button>
</ModalFooter>
</MyModal>
{isOpenTexts && (
<TextConfigModal
onCloseTexts={onCloseTexts}
onOpen={onOpen}
value={value}
onChange={onChange}
setSearchKey={setSearchKey}
/>
)}
</Flex>
);
};
export default React.memo(QGuidesConfig);
const TextConfigModal = ({
onCloseTexts,
onOpen,
value,
onChange,
setSearchKey
}: {
onCloseTexts: () => void;
onOpen: () => void;
value: AppQuestionGuideTextConfigType;
onChange: (e: AppQuestionGuideTextConfigType) => void;
setSearchKey: (key: string) => void;
}) => {
const { appT, commonT } = useI18n();
const fileInputRef = useRef<HTMLInputElement>(null);
const [checkboxValue, setCheckboxValue] = React.useState<string[]>([]);
const [isEditIndex, setIsEditIndex] = React.useState(-1);
const [isAdding, setIsAdding] = React.useState(false);
const [showIcons, setShowIcons] = React.useState<number | null>(null);
const { getCheckboxProps } = useCheckboxGroup();
const handleFileSelected = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
const content = e.target?.result as string;
const rows = content.split('\n');
const texts = rows.map((row) => row.split(',')[0]);
const newText = texts.filter((row) => value.textList.indexOf(row) === -1 && !!row);
onChange({
...value,
textList: [...newText, ...value.textList]
});
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
reader.readAsText(file);
}
};
const allSelected = useMemo(() => {
return value.textList.length === checkboxValue.length && value.textList.length !== 0;
}, [value.textList, checkboxValue]);
return (
<MyModal
title={appT('modules.Config Texts')}
iconSrc="core/app/inputGuides"
isOpen={true}
onClose={() => {
setCheckboxValue([]);
onCloseTexts();
onOpen();
}}
>
<ModalBody w={'500px'} px={0}>
<Flex gap={4} px={8} alignItems={'center'} borderBottom={'1px solid #E8EBF0'} pb={4}>
<Box flex={1}>
<MyInput
leftIcon={<MyIcon name={'common/searchLight'} boxSize={4} />}
bg={'myGray.50'}
w={'full'}
h={9}
placeholder={commonT('common.Search')}
onChange={(e) => setSearchKey(e.target.value)}
/>
</Box>
<Input
type="file"
accept=".csv"
style={{ display: 'none' }}
ref={fileInputRef}
onChange={handleFileSelected}
/>
<Button
onClick={() => {
fileInputRef.current?.click();
}}
variant={'whiteBase'}
size={'sm'}
leftIcon={<MyIcon name={'common/importLight'} boxSize={4} />}
>
{commonT('common.Import')}
</Button>
<Box
cursor={'pointer'}
onClick={() => {
fileDownload({
text: csvTemplate,
type: 'text/csv;charset=utf-8',
filename: 'questionGuide_template.csv'
});
}}
>
<QuestionTip ml={-2} label={appT('modules.Only support CSV')} />
</Box>
</Flex>
<Box mt={4}>
<Flex justifyContent={'space-between'} px={8}>
<Flex alignItems={'center'}>
<Checkbox
sx={{
'.chakra-checkbox__control': {
bg: allSelected ? 'primary.50' : 'none',
boxShadow: allSelected && '0 0 0 2px #F0F4FF',
_hover: {
bg: 'primary.50'
},
border: allSelected && '1px solid #3370FF',
color: 'primary.600'
},
svg: {
strokeWidth: '1px !important'
}
}}
value={'all'}
size={'lg'}
mr={2}
isChecked={allSelected}
onChange={(e) => {
if (e.target.checked) {
setCheckboxValue(value.textList);
} else {
setCheckboxValue([]);
}
}}
/>
<Box fontSize={'sm'} color={'myGray.600'} fontWeight={'medium'}>
{commonT('common.Select all')}
</Box>
</Flex>
<Flex gap={4}>
<Button
variant={'whiteBase'}
display={checkboxValue.length === 0 ? 'none' : 'flex'}
size={'sm'}
leftIcon={<MyIcon name={'delete'} boxSize={4} />}
onClick={() => {
setCheckboxValue([]);
onChange({
...value,
textList: value.textList.filter((_) => !checkboxValue.includes(_))
});
}}
>
{commonT('common.Delete')}
</Button>
<Button
display={checkboxValue.length !== 0 ? 'none' : 'flex'}
onClick={() => {
onChange({
...value,
textList: ['', ...value.textList]
});
setIsEditIndex(0);
setIsAdding(true);
}}
size={'sm'}
leftIcon={<MyIcon name={'common/addLight'} boxSize={4} />}
>
{commonT('common.Add')}
</Button>
</Flex>
</Flex>
<Box h={'400px'} pb={4} overflow={'auto'} px={8}>
{value.textList.map((text, index) => {
const selected = checkboxValue.includes(text);
return (
<Flex
key={index}
alignItems={'center'}
h={10}
mt={2}
onMouseEnter={() => setShowIcons(index)}
onMouseLeave={() => setShowIcons(null)}
>
<Checkbox
{...getCheckboxProps({ value: text })}
sx={{
'.chakra-checkbox__control': {
bg: selected ? 'primary.50' : 'none',
boxShadow: selected ? '0 0 0 2px #F0F4FF' : 'none',
_hover: {
bg: 'primary.50'
},
border: selected && '1px solid #3370FF',
color: 'primary.600'
},
svg: {
strokeWidth: '1px !important'
}
}}
size={'lg'}
mr={2}
isChecked={selected}
onChange={(e) => {
if (e.target.checked) {
setCheckboxValue([...checkboxValue, text]);
} else {
setCheckboxValue(checkboxValue.filter((_) => _ !== text));
}
}}
/>
{index === isEditIndex ? (
<InputGroup alignItems={'center'} h={'full'}>
<Input
autoFocus
h={'full'}
defaultValue={text}
onBlur={(e) => {
setIsEditIndex(-1);
if (
!e.target.value ||
(value.textList.indexOf(e.target.value) !== -1 &&
value.textList.indexOf(e.target.value) !== index)
) {
isAdding &&
onChange({
...value,
textList: value.textList.filter((_, i) => i !== index)
});
} else {
onChange({
...value,
textList: value.textList?.map((v, i) =>
i !== index ? v : e.target.value
)
});
}
setIsAdding(false);
}}
/>
<InputRightElement alignItems={'center'} pr={4} display={'flex'}>
<MyIcon name={'save'} boxSize={4} cursor={'pointer'} />
</InputRightElement>
</InputGroup>
) : (
<Flex
h={10}
w={'full'}
rounded={'md'}
px={4}
bg={'myGray.50'}
alignItems={'center'}
border={'1px solid #F0F1F6'}
_hover={{ border: '1px solid #94B5FF' }}
>
{text}
<Box flex={1} />
{checkboxValue.length === 0 && (
<Box display={showIcons === index ? 'flex' : 'none'}>
<MyIcon
name={'edit'}
boxSize={4}
mr={2}
color={'myGray.600'}
cursor={'pointer'}
onClick={() => setIsEditIndex(index)}
/>
<MyIcon
name={'delete'}
boxSize={4}
color={'myGray.600'}
cursor={'pointer'}
onClick={() => {
const temp = value.textList?.filter((_, i) => i !== index);
onChange({
...value,
textList: temp
});
}}
/>
</Box>
)}
</Flex>
)}
</Flex>
);
})}
</Box>
</Box>
</ModalBody>
</MyModal>
);
};

View File

@@ -16,8 +16,10 @@ import MyTooltip from '@/components/MyTooltip';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import ChatBox from '@/components/ChatBox'; import ChatBox from '@/components/ChatBox';
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d'; import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils'; import {
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils'; checkChatSupportSelectFileByModules,
getAppQuestionGuidesByModules
} from '@/web/core/chat/utils';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
@@ -26,6 +28,7 @@ import {
initWorkflowEdgeStatus, initWorkflowEdgeStatus,
storeNodes2RuntimeNodes storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils'; } from '@fastgpt/global/core/workflow/runtime/utils';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
export type ChatTestComponentRef = { export type ChatTestComponentRef = {
resetChatTest: () => void; resetChatTest: () => void;

View File

@@ -9,6 +9,7 @@ import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
import QGSwitch from '@/components/core/app/QGSwitch'; import QGSwitch from '@/components/core/app/QGSwitch';
import TTSSelect from '@/components/core/app/TTSSelect'; import TTSSelect from '@/components/core/app/TTSSelect';
import WhisperConfig from '@/components/core/app/WhisperConfig'; import WhisperConfig from '@/components/core/app/WhisperConfig';
import QGuidesConfig from '@/components/core/app/QGuidesConfig';
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils'; import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { TTSTypeEnum } from '@/web/core/app/constants'; import { TTSTypeEnum } from '@/web/core/app/constants';
@@ -21,11 +22,6 @@ import { WorkflowContext } from '../../context';
import { VariableItemType } from '@fastgpt/global/core/app/type'; import { VariableItemType } from '@fastgpt/global/core/app/type';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import VariableEdit from '@/components/core/app/VariableEdit'; import VariableEdit from '@/components/core/app/VariableEdit';
import {
FlowNodeOutputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const theme = useTheme(); const theme = useTheme();
@@ -60,6 +56,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<Box mt={3} pt={3} borderTop={theme.borders.base}> <Box mt={3} pt={3} borderTop={theme.borders.base}>
<ScheduledTrigger data={data} /> <ScheduledTrigger data={data} />
</Box> </Box>
<Box mt={3} pt={3} borderTop={theme.borders.base}>
<QuestionInputGuide data={data} />
</Box>
</Box> </Box>
</NodeCard> </NodeCard>
</> </>
@@ -240,3 +239,26 @@ function ScheduledTrigger({ data }: { data: FlowNodeItemType }) {
/> />
); );
} }
function QuestionInputGuide({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data;
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { questionGuideText } = splitGuideModule({ inputs } as StoreNodeItemType);
return (
<QGuidesConfig
value={questionGuideText}
onChange={(e) => {
onChangeNode({
nodeId,
key: NodeInputKeyEnum.questionGuideText,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === NodeInputKeyEnum.questionGuideText),
value: e
}
});
}}
/>
);
}

View File

@@ -6,6 +6,7 @@ import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema'; import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -46,6 +47,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
}, },
{ session } { session }
); );
await MongoAppQGuide.deleteMany(
{
appId
},
{ session }
);
// delete app // delete app
await MongoApp.deleteOne( await MongoApp.deleteOne(
{ {

View File

@@ -0,0 +1,41 @@
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { NextApiRequest, NextApiResponse } from 'next';
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
import axios from 'axios';
import { NextAPI } from '@/service/middleware/entry';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { textList = [], appId, customURL } = req.body;
if (!customURL) {
const { teamId } = await authUserNotVisitor({ req, authToken: true });
const currentQGuide = await MongoAppQGuide.find({ appId, teamId });
const currentTexts = currentQGuide.map((item) => item.text);
const textsToDelete = currentTexts.filter((text) => !textList.includes(text));
await MongoAppQGuide.deleteMany({ text: { $in: textsToDelete }, appId, teamId });
const newTexts = textList.filter((text: string) => !currentTexts.includes(text));
const newDocuments = newTexts.map((text: string) => ({
text: text,
appId: appId,
teamId: teamId
}));
await MongoAppQGuide.insertMany(newDocuments);
} else {
try {
const response = await axios.post(customURL, {
textList,
appId
});
res.status(200).json(response.data);
} catch (error) {
res.status(500).json({ error });
}
}
}
export default NextAPI(handler);

View File

@@ -0,0 +1,48 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
import axios from 'axios';
import { PaginationProps } from '@fastgpt/web/common/fetch/type';
import { NextAPI } from '@/service/middleware/entry';
type Props = PaginationProps<{
appId: string;
customURL: string;
searchKey: string;
}>;
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { appId, customURL, current, pageSize, searchKey } = req.query as unknown as Props;
if (!customURL) {
const [result, total] = await Promise.all([
MongoAppQGuide.find({
appId,
...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } })
})
.sort({
time: -1
})
.skip((current - 1) * pageSize)
.limit(pageSize),
MongoAppQGuide.countDocuments({ appId })
]);
return {
list: result.map((item) => item.text) || [],
total
};
} else {
try {
const response = await axios.get(customURL as string, {
params: {
appid: appId
}
});
res.status(200).json(response.data);
} catch (error) {
res.status(500).json({ error });
}
}
}
export default NextAPI(handler);

View File

@@ -27,6 +27,9 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context'; import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
import { useInterval, useUpdateEffect } from 'ahooks'; import { useInterval, useUpdateEffect } from 'ahooks';
import { useI18n } from '@/web/context/I18n'; import { useI18n } from '@/web/context/I18n';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { importQuestionGuides } from '@/web/core/app/api';
import { getAppQGuideCustomURL, getNodesWithNoQGuide } from '@/web/core/app/utils';
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
const PublishHistories = dynamic( const PublishHistories = dynamic(
@@ -139,8 +142,18 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const data = await flowData2StoreDataAndCheck(); const data = await flowData2StoreDataAndCheck();
if (data) { if (data) {
try { try {
const { questionGuideText } = splitGuideModule(getGuideModule(data.nodes));
await importQuestionGuides({
appId: app._id,
textList: questionGuideText.textList,
customURL: getAppQGuideCustomURL(app)
});
const newNodes = getNodesWithNoQGuide(data.nodes, questionGuideText);
await publishApp(app._id, { await publishApp(app._id, {
...data, ...data,
nodes: newNodes,
type: AppTypeEnum.advanced, type: AppTypeEnum.advanced,
//@ts-ignore //@ts-ignore
version: 'v2' version: 'v2'

View File

@@ -28,7 +28,7 @@ const Render = ({ app, onClose }: Props) => {
useEffect(() => { useEffect(() => {
if (!isV2Workflow) return; if (!isV2Workflow) return;
initData(JSON.parse(workflowStringData)); initData(JSON.parse(workflowStringData));
}, [isV2Workflow, initData, app._id]); }, [isV2Workflow, initData, app._id, workflowStringData]);
useEffect(() => { useEffect(() => {
if (!isV2Workflow) { if (!isV2Workflow) {

View File

@@ -104,7 +104,7 @@ const ChatTest = ({
return () => { return () => {
wat.unsubscribe(); wat.unsubscribe();
}; };
}, []); }, [setWorkflowData, watch]);
return ( return (
<Flex <Flex

View File

@@ -12,7 +12,11 @@ import { useTranslation } from 'next-i18next';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';
import { form2AppWorkflow } from '@/web/core/app/utils'; import {
form2AppWorkflow,
getAppQGuideCustomURL,
getNodesWithNoQGuide
} from '@/web/core/app/utils';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
@@ -30,6 +34,7 @@ import { TTSTypeEnum } from '@/web/core/app/constants';
import { getSystemVariables } from '@/web/core/app/utils'; import { getSystemVariables } from '@/web/core/app/utils';
import { useUpdate } from 'ahooks'; import { useUpdate } from 'ahooks';
import { useI18n } from '@/web/context/I18n'; import { useI18n } from '@/web/context/I18n';
import { importQuestionGuides } from '@/web/core/app/api';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
@@ -37,6 +42,7 @@ const ToolSelectModal = dynamic(() => import('./ToolSelectModal'));
const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect')); const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect'));
const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch')); const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch'));
const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig')); const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'));
const QGuidesConfigModal = dynamic(() => import('@/components/core/app/QGuidesConfig'));
const BoxStyles: BoxProps = { const BoxStyles: BoxProps = {
px: 5, px: 5,
@@ -64,7 +70,7 @@ const EditForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n(); const { appT } = useI18n();
const { publishApp, appDetail } = useAppStore(); const { appDetail, publishApp } = useAppStore();
const { allDatasets } = useDatasetStore(); const { allDatasets } = useDatasetStore();
const { llmModelList } = useSystemStore(); const { llmModelList } = useSystemStore();
@@ -103,7 +109,7 @@ const EditForm = ({
const datasetSearchSetting = watch('dataset'); const datasetSearchSetting = watch('dataset');
const variables = watch('userGuide.variables'); const variables = watch('userGuide.variables');
const formatVariables = useMemo( const formatVariables: any = useMemo(
() => formatEditorVariablePickerIcon([...getSystemVariables(t), ...variables]), () => formatEditorVariablePickerIcon([...getSystemVariables(t), ...variables]),
[t, variables] [t, variables]
); );
@@ -112,6 +118,7 @@ const EditForm = ({
const whisperConfig = getValues('userGuide.whisper'); const whisperConfig = getValues('userGuide.whisper');
const postQuestionGuide = getValues('userGuide.questionGuide'); const postQuestionGuide = getValues('userGuide.questionGuide');
const selectedTools = watch('selectedTools'); const selectedTools = watch('selectedTools');
const QGuidesConfig = watch('userGuide.questionGuideText');
const selectDatasets = useMemo( const selectDatasets = useMemo(
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)), () => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
@@ -125,10 +132,19 @@ const EditForm = ({
/* on save app */ /* on save app */
const { mutate: onSubmitPublish, isLoading: isSaving } = useRequest({ const { mutate: onSubmitPublish, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => { mutationFn: async (data: AppSimpleEditFormType) => {
const questionGuideText = data.userGuide.questionGuideText;
await importQuestionGuides({
appId: appDetail._id,
textList: questionGuideText.textList,
customURL: getAppQGuideCustomURL(appDetail)
});
const { nodes, edges } = form2AppWorkflow(data); const { nodes, edges } = form2AppWorkflow(data);
const newNodes = getNodesWithNoQGuide(nodes, questionGuideText);
await publishApp(appDetail._id, { await publishApp(appDetail._id, {
nodes, nodes: newNodes,
edges, edges,
type: AppTypeEnum.simple type: AppTypeEnum.simple
}); });
@@ -435,7 +451,7 @@ const EditForm = ({
</Box> </Box>
{/* question guide */} {/* question guide */}
<Box {...BoxStyles} borderBottom={'none'}> <Box {...BoxStyles}>
<QGSwitch <QGSwitch
isChecked={postQuestionGuide} isChecked={postQuestionGuide}
size={'lg'} size={'lg'}
@@ -444,6 +460,16 @@ const EditForm = ({
}} }}
/> />
</Box> </Box>
{/* question tips */}
<Box {...BoxStyles} borderBottom={'none'}>
<QGuidesConfigModal
value={QGuidesConfig}
onChange={(e) => {
setValue('userGuide.questionGuideText', e);
}}
/>
</Box>
</Box> </Box>
</Box> </Box>

View File

@@ -110,7 +110,6 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
maxW={['90vw', '700px']} maxW={['90vw', '700px']}
w={'700px'} w={'700px'}
h={['90vh', '80vh']} h={['90vh', '80vh']}
overflow={'none'}
> >
{/* Header: row and search */} {/* Header: row and search */}
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}> <Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>

View File

@@ -18,6 +18,7 @@ import { useAppStore } from '@/web/core/app/store/useAppStore';
import Head from 'next/head'; import Head from 'next/head';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useI18n } from '@/web/context/I18n'; import { useI18n } from '@/web/context/I18n';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
const FlowEdit = dynamic(() => import('./components/FlowEdit'), { const FlowEdit = dynamic(() => import('./components/FlowEdit'), {
loading: () => <Loading /> loading: () => <Loading />

View File

@@ -33,10 +33,15 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils'; import {
checkChatSupportSelectFileByChatModels,
getAppQuestionGuidesByUserGuideModule
} from '@/web/core/chat/utils';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils'; import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
const router = useRouter(); const router = useRouter();

View File

@@ -20,7 +20,10 @@ import PageContainer from '@/components/PageContainer';
import ChatHeader from './components/ChatHeader'; import ChatHeader from './components/ChatHeader';
import ChatHistorySlider from './components/ChatHistorySlider'; import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils'; import {
checkChatSupportSelectFileByChatModels,
getAppQuestionGuidesByUserGuideModule
} from '@/web/core/chat/utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api'; import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils'; import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
@@ -31,6 +34,9 @@ import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type'; import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
const OutLink = ({ const OutLink = ({
appName, appName,
@@ -378,6 +384,7 @@ const OutLink = ({
history={chatData.history} history={chatData.history}
showHistory={showHistory === '1'} showHistory={showHistory === '1'}
onOpenSlider={onOpenSlider} onOpenSlider={onOpenSlider}
appId={chatData.appId}
/> />
{/* chat box */} {/* chat box */}
<Box flex={1}> <Box flex={1}>

View File

@@ -21,7 +21,10 @@ import ChatHistorySlider from './components/ChatHistorySlider';
import ChatHeader from './components/ChatHeader'; import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils'; import {
checkChatSupportSelectFileByChatModels,
getAppQuestionGuidesByUserGuideModule
} from '@/web/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat'; import { useChatStore } from '@/web/core/chat/storeChat';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
@@ -35,6 +38,9 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@fastgpt/web/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import SliderApps from './components/SliderApps'; import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
const OutLink = () => { const OutLink = () => {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -1,7 +1,12 @@
import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d'; import type {
AppDetailType,
AppListItemType,
AppQuestionGuideTextConfigType
} from '@fastgpt/global/core/app/type.d';
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d'; import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
import { AppUpdateParams, CreateAppParams } from '@/global/core/app/api'; import { AppUpdateParams, CreateAppParams } from '@/global/core/app/api';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
/** /**
* 获取模型列表 * 获取模型列表
@@ -32,3 +37,19 @@ export const putAppById = (id: string, data: AppUpdateParams) =>
// =================== chat logs // =================== chat logs
export const getAppChatLogs = (data: GetAppChatLogsParams) => POST(`/core/app/getChatLogs`, data); export const getAppChatLogs = (data: GetAppChatLogsParams) => POST(`/core/app/getChatLogs`, data);
/**
* 导入提示词库
*/
export const importQuestionGuides = (data: {
appId: string;
textList: string[];
customURL: string;
}) => POST(`/core/app/questionGuides/import`, data);
/**
* 获取提示词库
*/
export const getMyQuestionGuides = (
data: PaginationProps<{ appId: string; customURL: string; searchKey: string }>
) => GET<PaginationResponse<string>>(`/core/app/questionGuides/list`, data);

View File

@@ -1,12 +1,12 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { getMyApps, getModelById, putAppById } from '@/web/core/app/api'; import { getMyApps, getModelById, putAppById, getMyQuestionGuides } from '@/web/core/app/api';
import { defaultApp } from '../constants';
import type { AppUpdateParams } from '@/global/core/app/api.d'; import type { AppUpdateParams } from '@/global/core/app/api.d';
import { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d'; import { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d';
import { PostPublishAppProps } from '@/global/core/app/api'; import { PostPublishAppProps } from '@/global/core/app/api';
import { postPublishApp } from '../versionApi'; import { postPublishApp } from '../versionApi';
import { defaultApp } from '../constants';
type State = { type State = {
myApps: AppListItemType[]; myApps: AppListItemType[];

View File

@@ -1,4 +1,9 @@
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; import {
AppDetailType,
AppQuestionGuideTextConfigType,
AppSchema,
AppSimpleEditFormType
} from '@fastgpt/global/core/app/type';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { import {
FlowNodeInputTypeEnum, FlowNodeInputTypeEnum,
@@ -64,6 +69,12 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType {
renderTypeList: [FlowNodeInputTypeEnum.hidden], renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '', label: '',
value: formData.userGuide.scheduleTrigger value: formData.userGuide.scheduleTrigger
},
{
key: NodeInputKeyEnum.questionGuideText,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.userGuide.questionGuideText
} }
], ],
outputs: [] outputs: []
@@ -757,3 +768,34 @@ export const getSystemVariables = (t: TFunction): EditorVariablePickerType[] =>
} }
]; ];
}; };
export const getAppQGuideCustomURL = (appDetail: AppDetailType | AppSchema): string => {
return (
appDetail?.modules
.find((m) => m.flowNodeType === FlowNodeTypeEnum.systemConfig)
?.inputs.find((i) => i.key === NodeInputKeyEnum.questionGuideText)?.value.customURL || ''
);
};
export const getNodesWithNoQGuide = (
nodes: StoreNodeItemType[],
questionGuideText: AppQuestionGuideTextConfigType
): StoreNodeItemType[] => {
return nodes.map((node) => {
if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
return {
...node,
inputs: node.inputs.map((input) => {
if (input.key === NodeInputKeyEnum.questionGuideText) {
return {
...input,
value: { ...questionGuideText, textList: [] }
};
}
return input;
})
};
}
return node;
});
};

View File

@@ -1,6 +1,7 @@
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
export function checkChatSupportSelectFileByChatModels(models: string[] = []) { export function checkChatSupportSelectFileByChatModels(models: string[] = []) {
const llmModelList = useSystemStore.getState().llmModelList; const llmModelList = useSystemStore.getState().llmModelList;
@@ -25,3 +26,23 @@ export function checkChatSupportSelectFileByModules(modules: StoreNodeItemType[]
); );
return checkChatSupportSelectFileByChatModels(models); return checkChatSupportSelectFileByChatModels(models);
} }
export function getAppQuestionGuidesByModules(modules: StoreNodeItemType[] = []) {
const systemModule = modules.find((item) => item.flowNodeType === FlowNodeTypeEnum.systemConfig);
const questionGuideText = systemModule?.inputs.find(
(item) => item.key === NodeInputKeyEnum.questionGuideText
)?.value;
return questionGuideText?.open ? questionGuideText?.textList : [];
}
export function getAppQuestionGuidesByUserGuideModule(
module: StoreNodeItemType,
qGuideText: string[] = []
) {
const questionGuideText = module?.inputs.find(
(item) => item.key === NodeInputKeyEnum.questionGuideText
)?.value;
return questionGuideText?.open ? qGuideText : [];
}