mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-22 12:20:34 +00:00
feat: question guide (#1508)
* feat: question guide * fix * fix * fix * change interface * fix
This commit is contained in:
BIN
docSite/assets/imgs/questionGuide.png
Normal file
BIN
docSite/assets/imgs/questionGuide.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
43
docSite/content/docs/course/custom_link.md
Normal file
43
docSite/content/docs/course/custom_link.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "自定义词库地址"
|
||||
description: "FastGPT 自定义输入提示的接口地址"
|
||||
icon: "code"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 350
|
||||
---
|
||||
|
||||

|
||||
|
||||
## 什么是输入提示
|
||||
可自定义开启或关闭,当输入提示开启,并且词库中存在数据时,用户在输入问题时如果输入命中词库,那么会在输入框上方展示对应的智能推荐数据
|
||||
|
||||
用户可配置词库,选择存储在 FastGPT 数据库中,或者提供自定义接口获取词库
|
||||
|
||||
## 数据格式
|
||||
词库的形式为一个字符串数组,定义的词库接口应该有两种方法 —— GET & POST
|
||||
|
||||
### GET
|
||||
对于 GET 方法,用于获取词库数据,FastGPT 会给接口发送数据 query 为
|
||||
```
|
||||
{
|
||||
appId: 'xxxx'
|
||||
}
|
||||
```
|
||||
返回数据格式应当为
|
||||
```
|
||||
{
|
||||
data: ['xxx', 'xxxx']
|
||||
}
|
||||
```
|
||||
|
||||
### POST
|
||||
对于 POST 方法,用于更新词库数据,FastGPT 会给接口发送数据 body 为
|
||||
```
|
||||
{
|
||||
appId: 'xxxx',
|
||||
text: ['xxx', 'xxxx']
|
||||
}
|
||||
```
|
||||
接口应当按照获取的数据格式存储相对应的词库数组
|
||||
|
@@ -18,3 +18,9 @@ export const defaultWhisperConfig: AppWhisperConfigType = {
|
||||
autoSend: false,
|
||||
autoTTSResponse: false
|
||||
};
|
||||
|
||||
export const defaultQuestionGuideTextConfig = {
|
||||
open: false,
|
||||
textList: [],
|
||||
customURL: ''
|
||||
};
|
||||
|
7
packages/global/core/app/type.d.ts
vendored
7
packages/global/core/app/type.d.ts
vendored
@@ -88,6 +88,7 @@ export type AppSimpleEditFormType = {
|
||||
};
|
||||
whisper: AppWhisperConfigType;
|
||||
scheduleTrigger: AppScheduledTriggerConfigType | null;
|
||||
questionGuideText: AppQuestionGuideTextConfigType;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -123,6 +124,12 @@ export type AppWhisperConfigType = {
|
||||
autoSend: boolean;
|
||||
autoTTSResponse: boolean;
|
||||
};
|
||||
// question guide text
|
||||
export type AppQuestionGuideTextConfigType = {
|
||||
open: boolean;
|
||||
textList: string[];
|
||||
customURL: string;
|
||||
};
|
||||
// interval timer
|
||||
export type AppScheduledTriggerConfigType = {
|
||||
cronString: string;
|
||||
|
@@ -5,7 +5,7 @@ import type { FlowNodeInputItemType } from '../workflow/type/io.d';
|
||||
import { getGuideModule, splitGuideModule } from '../workflow/utils';
|
||||
import { StoreNodeItemType } from '../workflow/type';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||
import { defaultWhisperConfig } from './constants';
|
||||
import { defaultQuestionGuideTextConfig, defaultWhisperConfig } from './constants';
|
||||
|
||||
export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
||||
return {
|
||||
@@ -35,7 +35,8 @@ export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
||||
type: 'web'
|
||||
},
|
||||
whisper: defaultWhisperConfig,
|
||||
scheduleTrigger: null
|
||||
scheduleTrigger: null,
|
||||
questionGuideText: defaultQuestionGuideTextConfig
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -109,7 +110,8 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
||||
questionGuide,
|
||||
ttsConfig,
|
||||
whisperConfig,
|
||||
scheduledTriggerConfig
|
||||
scheduledTriggerConfig,
|
||||
questionGuideText
|
||||
} = splitGuideModule(getGuideModule(nodes));
|
||||
|
||||
defaultAppForm.userGuide = {
|
||||
@@ -118,7 +120,8 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
||||
questionGuide: questionGuide,
|
||||
tts: ttsConfig,
|
||||
whisper: whisperConfig,
|
||||
scheduleTrigger: scheduledTriggerConfig
|
||||
scheduleTrigger: scheduledTriggerConfig,
|
||||
questionGuideText: questionGuideText
|
||||
};
|
||||
} else if (node.flowNodeType === FlowNodeTypeEnum.pluginModule) {
|
||||
if (!node.pluginId) return;
|
||||
|
@@ -45,6 +45,7 @@ export enum NodeInputKeyEnum {
|
||||
whisper = 'whisper',
|
||||
variables = 'variables',
|
||||
scheduleTrigger = 'scheduleTrigger',
|
||||
questionGuideText = 'questionGuideText',
|
||||
|
||||
// entry
|
||||
userChatInput = 'userChatInput',
|
||||
|
@@ -56,6 +56,12 @@ export const SystemConfigNode: FlowNodeTemplateType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: ''
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.questionGuideText,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: ''
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
|
@@ -11,7 +11,8 @@ import type {
|
||||
VariableItemType,
|
||||
AppTTSConfigType,
|
||||
AppWhisperConfigType,
|
||||
AppScheduledTriggerConfigType
|
||||
AppScheduledTriggerConfigType,
|
||||
AppQuestionGuideTextConfigType
|
||||
} from '../app/type';
|
||||
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
|
||||
import { defaultWhisperConfig } from '../app/constants';
|
||||
@@ -59,13 +60,20 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
|
||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.scheduleTrigger)?.value ??
|
||||
null;
|
||||
|
||||
const questionGuideText: AppQuestionGuideTextConfigType = guideModules?.inputs?.find(
|
||||
(item) => item.key === NodeInputKeyEnum.questionGuideText
|
||||
)?.value || {
|
||||
open: false
|
||||
};
|
||||
|
||||
return {
|
||||
welcomeText,
|
||||
variableNodes,
|
||||
questionGuide,
|
||||
ttsConfig,
|
||||
whisperConfig,
|
||||
scheduledTriggerConfig
|
||||
scheduledTriggerConfig,
|
||||
questionGuideText
|
||||
};
|
||||
};
|
||||
export const replaceAppChatConfig = ({
|
||||
|
40
packages/service/core/app/qGuideSchema.ts
Normal file
40
packages/service/core/app/qGuideSchema.ts
Normal 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();
|
@@ -1,6 +1,7 @@
|
||||
// @ts-nocheck
|
||||
|
||||
export const iconPaths = {
|
||||
book: () => import('./icons/book.svg'),
|
||||
change: () => import('./icons/change.svg'),
|
||||
chatSend: () => import('./icons/chatSend.svg'),
|
||||
closeSolid: () => import('./icons/closeSolid.svg'),
|
||||
@@ -64,6 +65,7 @@ export const iconPaths = {
|
||||
'core/app/appApiLight': () => import('./icons/core/app/appApiLight.svg'),
|
||||
'core/app/customFeedback': () => import('./icons/core/app/customFeedback.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/markLight': () => import('./icons/core/app/markLight.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/userLight': () => import('./icons/support/user/userLight.svg'),
|
||||
text: () => import('./icons/text.svg'),
|
||||
union: () => import('./icons/union.svg'),
|
||||
user: () => import('./icons/user.svg'),
|
||||
wx: () => import('./icons/wx.svg')
|
||||
};
|
||||
|
3
packages/web/components/common/Icon/icons/book.svg
Normal file
3
packages/web/components/common/Icon/icons/book.svg
Normal 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 |
@@ -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 |
7
packages/web/components/common/Icon/icons/union.svg
Normal file
7
packages/web/components/common/Icon/icons/union.svg
Normal 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 |
@@ -85,14 +85,16 @@ export function useScrollPagination<
|
||||
...props
|
||||
}: { children: React.ReactNode; isLoading?: boolean } & BoxProps) => {
|
||||
return (
|
||||
<>
|
||||
<MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}>
|
||||
<Box ref={wrapperRef}>{children}</Box>
|
||||
</MyBox>
|
||||
{noMore.current && (
|
||||
<Box pb={2} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}>
|
||||
{t('common.No more data')}
|
||||
</Box>
|
||||
)}
|
||||
</MyBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -115,6 +117,7 @@ export function useScrollPagination<
|
||||
return {
|
||||
containerRef,
|
||||
list,
|
||||
data,
|
||||
isLoading,
|
||||
ScrollList,
|
||||
fetchData: loadData
|
||||
|
@@ -47,6 +47,14 @@
|
||||
"type": "\"{{type}}\" type\n{{description}}"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
"Move": "Move",
|
||||
"Name": "Name",
|
||||
"New Create": "Create New",
|
||||
"No data": "No data",
|
||||
"Rename": "Rename",
|
||||
"Running": "Running",
|
||||
"UnKnow": "Unknown",
|
||||
@@ -45,6 +46,7 @@
|
||||
"Delete Tip": "Delete Tip",
|
||||
"Delete Warning": "Delete Warning",
|
||||
"Detail": "Detail",
|
||||
"Documents": "Documents",
|
||||
"Done": "Done",
|
||||
"Edit": "Edit",
|
||||
"Exit": "Exit",
|
||||
@@ -101,6 +103,7 @@
|
||||
"Search": "Search",
|
||||
"Select File Failed": "Select File Failed",
|
||||
"Select One Folder": "Select a folder",
|
||||
"Select all": "Select all",
|
||||
"Select template": "Select template",
|
||||
"Set Avatar": "Click to set avatar",
|
||||
"Set Name": "Set a name",
|
||||
|
@@ -46,6 +46,14 @@
|
||||
"type": "\"{{type}}\"类型\n{{description}}"
|
||||
},
|
||||
"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": "模块名不能为空"
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
"Move": "移动",
|
||||
"Name": "名称",
|
||||
"New Create": "新建",
|
||||
"No data": "暂无数据",
|
||||
"Rename": "重命名",
|
||||
"Running": "运行中",
|
||||
"UnKnow": "未知",
|
||||
@@ -45,6 +46,7 @@
|
||||
"Delete Tip": "删除提示",
|
||||
"Delete Warning": "删除警告",
|
||||
"Detail": "详情",
|
||||
"Documents": "文档",
|
||||
"Done": "完成",
|
||||
"Edit": "编辑",
|
||||
"Exit": "退出",
|
||||
@@ -102,6 +104,7 @@
|
||||
"Search": "搜索",
|
||||
"Select File Failed": "选择文件异常",
|
||||
"Select One Folder": "选择一个目录",
|
||||
"Select all": "全选",
|
||||
"Select template": "选择模板",
|
||||
"Set Avatar": "点击设置头像",
|
||||
"Set Name": "取个名字",
|
||||
|
@@ -16,6 +16,11 @@ import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from '.
|
||||
import { textareaMinH } from './constants';
|
||||
import { UseFormReturn, useFieldArray } from 'react-hook-form';
|
||||
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 MessageInput = ({
|
||||
@@ -53,6 +58,7 @@ const MessageInput = ({
|
||||
const { isPc, whisperModel } = useSystemStore();
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useAppStore();
|
||||
|
||||
const havInput = !!inputValue || fileList.length > 0;
|
||||
const hasFileUploading = fileList.some((item) => !item.url);
|
||||
@@ -205,6 +211,23 @@ const MessageInput = ({
|
||||
startSpeak(finishWhisperTranscription);
|
||||
}, [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 (
|
||||
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}>
|
||||
<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)`}
|
||||
borderRadius={['none', 'md']}
|
||||
bg={'white'}
|
||||
overflow={'hidden'}
|
||||
overflow={'display'}
|
||||
{...(isPc
|
||||
? {
|
||||
border: '1px solid',
|
||||
@@ -243,6 +266,21 @@ const MessageInput = ({
|
||||
{t('core.chat.Converting to text')}
|
||||
</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 */}
|
||||
<Flex wrap={'wrap'} px={[2, 4]} userSelect={'none'}>
|
||||
{fileList.map((item, index) => (
|
||||
@@ -377,7 +415,12 @@ const MessageInput = ({
|
||||
// @ts-ignore
|
||||
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();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -58,7 +58,7 @@ import ChatProvider, { useChatProviderStore } from './Provider';
|
||||
import ChatItem from './components/ChatItem';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useCreation, useUpdateEffect } from 'ahooks';
|
||||
import { useCreation } from 'ahooks';
|
||||
|
||||
const ResponseTags = dynamic(() => import('./ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
|
||||
|
479
projects/app/src/components/core/app/QGuidesConfig.tsx
Normal file
479
projects/app/src/components/core/app/QGuidesConfig.tsx
Normal 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>
|
||||
);
|
||||
};
|
@@ -16,8 +16,10 @@ import MyTooltip from '@/components/MyTooltip';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import {
|
||||
checkChatSupportSelectFileByModules,
|
||||
getAppQuestionGuidesByModules
|
||||
} from '@/web/core/chat/utils';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
initWorkflowEdgeStatus,
|
||||
storeNodes2RuntimeNodes
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
export type ChatTestComponentRef = {
|
||||
resetChatTest: () => void;
|
||||
|
@@ -9,6 +9,7 @@ import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
|
||||
import QGSwitch from '@/components/core/app/QGSwitch';
|
||||
import TTSSelect from '@/components/core/app/TTSSelect';
|
||||
import WhisperConfig from '@/components/core/app/WhisperConfig';
|
||||
import QGuidesConfig from '@/components/core/app/QGuidesConfig';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
@@ -21,11 +22,6 @@ import { WorkflowContext } from '../../context';
|
||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
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 theme = useTheme();
|
||||
@@ -60,6 +56,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<ScheduledTrigger data={data} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<QuestionInputGuide data={data} />
|
||||
</Box>
|
||||
</Box>
|
||||
</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
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
|
||||
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
@@ -46,6 +47,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoAppQGuide.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
// delete app
|
||||
await MongoApp.deleteOne(
|
||||
{
|
||||
|
41
projects/app/src/pages/api/core/app/questionGuides/import.ts
Normal file
41
projects/app/src/pages/api/core/app/questionGuides/import.ts
Normal 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);
|
48
projects/app/src/pages/api/core/app/questionGuides/list.ts
Normal file
48
projects/app/src/pages/api/core/app/questionGuides/list.ts
Normal 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);
|
@@ -27,6 +27,9 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
|
||||
import { useInterval, useUpdateEffect } from 'ahooks';
|
||||
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 PublishHistories = dynamic(
|
||||
@@ -139,8 +142,18 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
const data = await flowData2StoreDataAndCheck();
|
||||
if (data) {
|
||||
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, {
|
||||
...data,
|
||||
nodes: newNodes,
|
||||
type: AppTypeEnum.advanced,
|
||||
//@ts-ignore
|
||||
version: 'v2'
|
||||
|
@@ -28,7 +28,7 @@ const Render = ({ app, onClose }: Props) => {
|
||||
useEffect(() => {
|
||||
if (!isV2Workflow) return;
|
||||
initData(JSON.parse(workflowStringData));
|
||||
}, [isV2Workflow, initData, app._id]);
|
||||
}, [isV2Workflow, initData, app._id, workflowStringData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isV2Workflow) {
|
||||
|
@@ -104,7 +104,7 @@ const ChatTest = ({
|
||||
return () => {
|
||||
wat.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
}, [setWorkflowData, watch]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
@@ -12,7 +12,11 @@ import { useTranslation } from 'next-i18next';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
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 MyTooltip from '@/components/MyTooltip';
|
||||
@@ -30,6 +34,7 @@ import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
import { getSystemVariables } from '@/web/core/app/utils';
|
||||
import { useUpdate } from 'ahooks';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { importQuestionGuides } from '@/web/core/app/api';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
|
||||
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 QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch'));
|
||||
const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'));
|
||||
const QGuidesConfigModal = dynamic(() => import('@/components/core/app/QGuidesConfig'));
|
||||
|
||||
const BoxStyles: BoxProps = {
|
||||
px: 5,
|
||||
@@ -64,7 +70,7 @@ const EditForm = ({
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { publishApp, appDetail } = useAppStore();
|
||||
const { appDetail, publishApp } = useAppStore();
|
||||
|
||||
const { allDatasets } = useDatasetStore();
|
||||
const { llmModelList } = useSystemStore();
|
||||
@@ -103,7 +109,7 @@ const EditForm = ({
|
||||
const datasetSearchSetting = watch('dataset');
|
||||
const variables = watch('userGuide.variables');
|
||||
|
||||
const formatVariables = useMemo(
|
||||
const formatVariables: any = useMemo(
|
||||
() => formatEditorVariablePickerIcon([...getSystemVariables(t), ...variables]),
|
||||
[t, variables]
|
||||
);
|
||||
@@ -112,6 +118,7 @@ const EditForm = ({
|
||||
const whisperConfig = getValues('userGuide.whisper');
|
||||
const postQuestionGuide = getValues('userGuide.questionGuide');
|
||||
const selectedTools = watch('selectedTools');
|
||||
const QGuidesConfig = watch('userGuide.questionGuideText');
|
||||
|
||||
const selectDatasets = useMemo(
|
||||
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
|
||||
@@ -125,10 +132,19 @@ const EditForm = ({
|
||||
/* on save app */
|
||||
const { mutate: onSubmitPublish, isLoading: isSaving } = useRequest({
|
||||
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 newNodes = getNodesWithNoQGuide(nodes, questionGuideText);
|
||||
|
||||
await publishApp(appDetail._id, {
|
||||
nodes,
|
||||
nodes: newNodes,
|
||||
edges,
|
||||
type: AppTypeEnum.simple
|
||||
});
|
||||
@@ -435,7 +451,7 @@ const EditForm = ({
|
||||
</Box>
|
||||
|
||||
{/* question guide */}
|
||||
<Box {...BoxStyles} borderBottom={'none'}>
|
||||
<Box {...BoxStyles}>
|
||||
<QGSwitch
|
||||
isChecked={postQuestionGuide}
|
||||
size={'lg'}
|
||||
@@ -444,6 +460,16 @@ const EditForm = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* question tips */}
|
||||
<Box {...BoxStyles} borderBottom={'none'}>
|
||||
<QGuidesConfigModal
|
||||
value={QGuidesConfig}
|
||||
onChange={(e) => {
|
||||
setValue('userGuide.questionGuideText', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
@@ -110,7 +110,6 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
|
||||
maxW={['90vw', '700px']}
|
||||
w={'700px'}
|
||||
h={['90vh', '80vh']}
|
||||
overflow={'none'}
|
||||
>
|
||||
{/* Header: row and search */}
|
||||
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
|
||||
|
@@ -18,6 +18,7 @@ import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import Head from 'next/head';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
|
||||
|
||||
const FlowEdit = dynamic(() => import('./components/FlowEdit'), {
|
||||
loading: () => <Loading />
|
||||
|
@@ -33,10 +33,15 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
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 { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
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 router = useRouter();
|
||||
|
@@ -20,7 +20,10 @@ import PageContainer from '@/components/PageContainer';
|
||||
import ChatHeader from './components/ChatHeader';
|
||||
import ChatHistorySlider from './components/ChatHistorySlider';
|
||||
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 { getInitOutLinkChatInfo } from '@/web/core/chat/api';
|
||||
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 { addLog } from '@fastgpt/service/common/system/log';
|
||||
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 = ({
|
||||
appName,
|
||||
@@ -378,6 +384,7 @@ const OutLink = ({
|
||||
history={chatData.history}
|
||||
showHistory={showHistory === '1'}
|
||||
onOpenSlider={onOpenSlider}
|
||||
appId={chatData.appId}
|
||||
/>
|
||||
{/* chat box */}
|
||||
<Box flex={1}>
|
||||
|
@@ -21,7 +21,10 @@ import ChatHistorySlider from './components/ChatHistorySlider';
|
||||
import ChatHeader from './components/ChatHeader';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
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 { customAlphabet } from 'nanoid';
|
||||
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 SliderApps from './components/SliderApps';
|
||||
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 { t } = useTranslation();
|
||||
|
@@ -1,7 +1,12 @@
|
||||
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 { 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
|
||||
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);
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { getMyApps, getModelById, putAppById } from '@/web/core/app/api';
|
||||
import { defaultApp } from '../constants';
|
||||
import { getMyApps, getModelById, putAppById, getMyQuestionGuides } from '@/web/core/app/api';
|
||||
import type { AppUpdateParams } from '@/global/core/app/api.d';
|
||||
import { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d';
|
||||
import { PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { postPublishApp } from '../versionApi';
|
||||
import { defaultApp } from '../constants';
|
||||
|
||||
type State = {
|
||||
myApps: AppListItemType[];
|
||||
|
@@ -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 {
|
||||
FlowNodeInputTypeEnum,
|
||||
@@ -64,6 +69,12 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
label: '',
|
||||
value: formData.userGuide.scheduleTrigger
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.questionGuideText,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
label: '',
|
||||
value: formData.userGuide.questionGuideText
|
||||
}
|
||||
],
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
export function checkChatSupportSelectFileByChatModels(models: string[] = []) {
|
||||
const llmModelList = useSystemStore.getState().llmModelList;
|
||||
@@ -25,3 +26,23 @@ export function checkChatSupportSelectFileByModules(modules: StoreNodeItemType[]
|
||||
);
|
||||
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 : [];
|
||||
}
|
||||
|
Reference in New Issue
Block a user