Perf input guide (#1557)
* perf: input guide code * perf: input guide ui * Chat input guide api * Update app chat config store * perf: app chat config field * perf: app context * perf: params * fix: ts * perf: filter private config * perf: filter private config * perf: import workflow * perf: limit max tip amount
This commit is contained in:
parent
8e8ceb7439
commit
fb368a581c
1
.vscode/i18n-ally-custom-framework.yml
vendored
1
.vscode/i18n-ally-custom-framework.yml
vendored
@ -25,6 +25,7 @@ usageMatchRegex:
|
|||||||
- "[^\\w\\d]publishT\\(['\"`]({key})['\"`]"
|
- "[^\\w\\d]publishT\\(['\"`]({key})['\"`]"
|
||||||
- "[^\\w\\d]workflowT\\(['\"`]({key})['\"`]"
|
- "[^\\w\\d]workflowT\\(['\"`]({key})['\"`]"
|
||||||
- "[^\\w\\d]userT\\(['\"`]({key})['\"`]"
|
- "[^\\w\\d]userT\\(['\"`]({key})['\"`]"
|
||||||
|
- "[^\\w\\d]chatT\\(['\"`]({key})['\"`]"
|
||||||
|
|
||||||
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
||||||
# and works like how the i18next framework identifies the namespace scope from the
|
# and works like how the i18next framework identifies the namespace scope from the
|
||||||
|
|||||||
48
docSite/content/docs/course/chat_input_guide.md
Normal file
48
docSite/content/docs/course/chat_input_guide.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: "对话问题引导"
|
||||||
|
description: "FastGPT 对话问题引导"
|
||||||
|
icon: "code"
|
||||||
|
draft: false
|
||||||
|
toc: true
|
||||||
|
weight: 350
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 什么是自定义问题引导
|
||||||
|
|
||||||
|
你可以为你的应用提前预设一些问题,用户在输入时,会根据输入的内容,动态搜索这些问题作为提示,从而引导用户更快的进行提问。
|
||||||
|
|
||||||
|
你可以直接在 FastGPT 中配置词库,或者提供自定义词库接口。
|
||||||
|
|
||||||
|
## 自定义词库接口
|
||||||
|
|
||||||
|
**请求:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request GET 'http://localhost:3000/api/core/chat/inputGuide/query?appId=663c75302caf8315b1c00194&searchKey=你'
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"statusText": "",
|
||||||
|
"message": "",
|
||||||
|
"data": [
|
||||||
|
"是你",
|
||||||
|
"你是谁呀",
|
||||||
|
"你好好呀",
|
||||||
|
"你好呀",
|
||||||
|
"你是谁!",
|
||||||
|
"你好"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**参数说明:**
|
||||||
|
|
||||||
|
- appId - 应用ID
|
||||||
|
- searchKey - 搜索关键字
|
||||||
@ -1,43 +0,0 @@
|
|||||||
---
|
|
||||||
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']
|
|
||||||
}
|
|
||||||
```
|
|
||||||
接口应当按照获取的数据格式存储相对应的词库数组
|
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { AppWhisperConfigType } from './type';
|
import { AppTTSConfigType, AppWhisperConfigType } from './type';
|
||||||
|
|
||||||
export enum AppTypeEnum {
|
export enum AppTypeEnum {
|
||||||
simple = 'simple',
|
simple = 'simple',
|
||||||
@ -13,14 +13,16 @@ export const AppTypeMap = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultTTSConfig: AppTTSConfigType = { type: 'web' };
|
||||||
|
|
||||||
export const defaultWhisperConfig: AppWhisperConfigType = {
|
export const defaultWhisperConfig: AppWhisperConfigType = {
|
||||||
open: false,
|
open: false,
|
||||||
autoSend: false,
|
autoSend: false,
|
||||||
autoTTSResponse: false
|
autoTTSResponse: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultQuestionGuideTextConfig = {
|
export const defaultChatInputGuideConfig = {
|
||||||
open: false,
|
open: false,
|
||||||
textList: [],
|
textList: [],
|
||||||
customURL: ''
|
customUrl: ''
|
||||||
};
|
};
|
||||||
|
|||||||
46
packages/global/core/app/type.d.ts
vendored
46
packages/global/core/app/type.d.ts
vendored
@ -8,7 +8,7 @@ import { DatasetSearchModeEnum } from '../dataset/constants';
|
|||||||
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
|
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
|
||||||
import { StoreEdgeItemType } from '../workflow/type/edge';
|
import { StoreEdgeItemType } from '../workflow/type/edge';
|
||||||
|
|
||||||
export interface AppSchema {
|
export type AppSchema = {
|
||||||
_id: string;
|
_id: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
@ -23,13 +23,14 @@ export interface AppSchema {
|
|||||||
edges: StoreEdgeItemType[];
|
edges: StoreEdgeItemType[];
|
||||||
|
|
||||||
// App system config
|
// App system config
|
||||||
|
chatConfig: AppChatConfigType;
|
||||||
scheduledTriggerConfig?: AppScheduledTriggerConfigType | null;
|
scheduledTriggerConfig?: AppScheduledTriggerConfigType | null;
|
||||||
scheduledTriggerNextTime?: Date;
|
scheduledTriggerNextTime?: Date;
|
||||||
|
|
||||||
permission: `${PermissionTypeEnum}`;
|
permission: `${PermissionTypeEnum}`;
|
||||||
inited?: boolean;
|
inited?: boolean;
|
||||||
teamTags: string[];
|
teamTags: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type AppListItemType = {
|
export type AppListItemType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@ -66,33 +67,19 @@ export type AppSimpleEditFormType = {
|
|||||||
datasetSearchExtensionBg?: string;
|
datasetSearchExtensionBg?: string;
|
||||||
};
|
};
|
||||||
selectedTools: FlowNodeTemplateType[];
|
selectedTools: FlowNodeTemplateType[];
|
||||||
userGuide: {
|
chatConfig: AppChatConfigType;
|
||||||
welcomeText: string;
|
|
||||||
variables: {
|
|
||||||
id: string;
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
type: `${VariableInputEnum}`;
|
|
||||||
required: boolean;
|
|
||||||
maxLen: number;
|
|
||||||
enums: {
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
}[];
|
|
||||||
questionGuide: boolean;
|
|
||||||
tts: {
|
|
||||||
type: 'none' | 'web' | 'model';
|
|
||||||
model?: string | undefined;
|
|
||||||
voice?: string | undefined;
|
|
||||||
speed?: number | undefined;
|
|
||||||
};
|
|
||||||
whisper: AppWhisperConfigType;
|
|
||||||
scheduleTrigger: AppScheduledTriggerConfigType | null;
|
|
||||||
questionGuideText: AppQuestionGuideTextConfigType;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* app function config */
|
/* app chat config type */
|
||||||
|
export type AppChatConfigType = {
|
||||||
|
welcomeText?: string;
|
||||||
|
variables?: VariableItemType[];
|
||||||
|
questionGuide?: boolean;
|
||||||
|
ttsConfig?: AppTTSConfigType;
|
||||||
|
whisperConfig?: AppWhisperConfigType;
|
||||||
|
scheduledTriggerConfig?: AppScheduledTriggerConfigType;
|
||||||
|
chatInputGuide?: ChatInputGuideConfigType;
|
||||||
|
};
|
||||||
export type SettingAIDataType = {
|
export type SettingAIDataType = {
|
||||||
model: string;
|
model: string;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
@ -125,10 +112,9 @@ export type AppWhisperConfigType = {
|
|||||||
autoTTSResponse: boolean;
|
autoTTSResponse: boolean;
|
||||||
};
|
};
|
||||||
// question guide text
|
// question guide text
|
||||||
export type AppQuestionGuideTextConfigType = {
|
export type ChatInputGuideConfigType = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
textList: string[];
|
customUrl: string;
|
||||||
customURL: string;
|
|
||||||
};
|
};
|
||||||
// interval timer
|
// interval timer
|
||||||
export type AppScheduledTriggerConfigType = {
|
export type AppScheduledTriggerConfigType = {
|
||||||
|
|||||||
@ -1,50 +1,42 @@
|
|||||||
import type { AppSimpleEditFormType } from '../app/type';
|
import type { AppChatConfigType, AppSimpleEditFormType } from '../app/type';
|
||||||
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
||||||
import { NodeInputKeyEnum, FlowNodeTemplateTypeEnum } from '../workflow/constants';
|
import { NodeInputKeyEnum, FlowNodeTemplateTypeEnum } from '../workflow/constants';
|
||||||
import type { FlowNodeInputItemType } from '../workflow/type/io.d';
|
import type { FlowNodeInputItemType } from '../workflow/type/io.d';
|
||||||
import { getGuideModule, splitGuideModule } from '../workflow/utils';
|
import { getAppChatConfig } from '../workflow/utils';
|
||||||
import { StoreNodeItemType } from '../workflow/type';
|
import { StoreNodeItemType } from '../workflow/type';
|
||||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||||
import { defaultQuestionGuideTextConfig, defaultWhisperConfig } from './constants';
|
|
||||||
|
|
||||||
export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
export const getDefaultAppForm = (): AppSimpleEditFormType => ({
|
||||||
return {
|
aiSettings: {
|
||||||
aiSettings: {
|
model: 'gpt-3.5-turbo',
|
||||||
model: 'gpt-3.5-turbo',
|
systemPrompt: '',
|
||||||
systemPrompt: '',
|
temperature: 0,
|
||||||
temperature: 0,
|
isResponseAnswerText: true,
|
||||||
isResponseAnswerText: true,
|
maxHistories: 6,
|
||||||
maxHistories: 6,
|
maxToken: 4000
|
||||||
maxToken: 4000
|
},
|
||||||
},
|
dataset: {
|
||||||
dataset: {
|
datasets: [],
|
||||||
datasets: [],
|
similarity: 0.4,
|
||||||
similarity: 0.4,
|
limit: 1500,
|
||||||
limit: 1500,
|
searchMode: DatasetSearchModeEnum.embedding,
|
||||||
searchMode: DatasetSearchModeEnum.embedding,
|
usingReRank: false,
|
||||||
usingReRank: false,
|
datasetSearchUsingExtensionQuery: true,
|
||||||
datasetSearchUsingExtensionQuery: true,
|
datasetSearchExtensionBg: ''
|
||||||
datasetSearchExtensionBg: ''
|
},
|
||||||
},
|
selectedTools: [],
|
||||||
selectedTools: [],
|
chatConfig: {}
|
||||||
userGuide: {
|
});
|
||||||
welcomeText: '',
|
|
||||||
variables: [],
|
|
||||||
questionGuide: false,
|
|
||||||
tts: {
|
|
||||||
type: 'web'
|
|
||||||
},
|
|
||||||
whisper: defaultWhisperConfig,
|
|
||||||
scheduleTrigger: null,
|
|
||||||
questionGuideText: defaultQuestionGuideTextConfig
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* format app nodes to edit form */
|
/* format app nodes to edit form */
|
||||||
export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
export const appWorkflow2Form = ({
|
||||||
|
nodes,
|
||||||
|
chatConfig
|
||||||
|
}: {
|
||||||
|
nodes: StoreNodeItemType[];
|
||||||
|
chatConfig: AppChatConfigType;
|
||||||
|
}) => {
|
||||||
const defaultAppForm = getDefaultAppForm();
|
const defaultAppForm = getDefaultAppForm();
|
||||||
|
|
||||||
const findInputValueByKey = (inputs: FlowNodeInputItemType[], key: string) => {
|
const findInputValueByKey = (inputs: FlowNodeInputItemType[], key: string) => {
|
||||||
return inputs.find((item) => item.key === key)?.value;
|
return inputs.find((item) => item.key === key)?.value;
|
||||||
};
|
};
|
||||||
@ -103,26 +95,6 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
|||||||
node.inputs,
|
node.inputs,
|
||||||
NodeInputKeyEnum.datasetSearchExtensionBg
|
NodeInputKeyEnum.datasetSearchExtensionBg
|
||||||
);
|
);
|
||||||
} else if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
|
|
||||||
const {
|
|
||||||
welcomeText,
|
|
||||||
variableNodes,
|
|
||||||
questionGuide,
|
|
||||||
ttsConfig,
|
|
||||||
whisperConfig,
|
|
||||||
scheduledTriggerConfig,
|
|
||||||
questionGuideText
|
|
||||||
} = splitGuideModule(getGuideModule(nodes));
|
|
||||||
|
|
||||||
defaultAppForm.userGuide = {
|
|
||||||
welcomeText: welcomeText,
|
|
||||||
variables: variableNodes,
|
|
||||||
questionGuide: questionGuide,
|
|
||||||
tts: ttsConfig,
|
|
||||||
whisper: whisperConfig,
|
|
||||||
scheduleTrigger: scheduledTriggerConfig,
|
|
||||||
questionGuideText: questionGuideText
|
|
||||||
};
|
|
||||||
} else if (node.flowNodeType === FlowNodeTypeEnum.pluginModule) {
|
} else if (node.flowNodeType === FlowNodeTypeEnum.pluginModule) {
|
||||||
if (!node.pluginId) return;
|
if (!node.pluginId) return;
|
||||||
|
|
||||||
@ -139,6 +111,12 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
|||||||
outputs: node.outputs,
|
outputs: node.outputs,
|
||||||
templateType: FlowNodeTemplateTypeEnum.other
|
templateType: FlowNodeTemplateTypeEnum.other
|
||||||
});
|
});
|
||||||
|
} else if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
|
||||||
|
defaultAppForm.chatConfig = getAppChatConfig({
|
||||||
|
chatConfig,
|
||||||
|
systemConfigNode: node,
|
||||||
|
isPublicFetch: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
2
packages/global/core/app/version.d.ts
vendored
2
packages/global/core/app/version.d.ts
vendored
@ -1,5 +1,6 @@
|
|||||||
import { StoreNodeItemType } from '../workflow/type';
|
import { StoreNodeItemType } from '../workflow/type';
|
||||||
import { StoreEdgeItemType } from '../workflow/type/edge';
|
import { StoreEdgeItemType } from '../workflow/type/edge';
|
||||||
|
import { AppChatConfigType } from './type';
|
||||||
|
|
||||||
export type AppVersionSchemaType = {
|
export type AppVersionSchemaType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@ -7,4 +8,5 @@ export type AppVersionSchemaType = {
|
|||||||
time: Date;
|
time: Date;
|
||||||
nodes: StoreNodeItemType[];
|
nodes: StoreNodeItemType[];
|
||||||
edges: StoreEdgeItemType[];
|
edges: StoreEdgeItemType[];
|
||||||
|
chatConfig: AppChatConfigType;
|
||||||
};
|
};
|
||||||
|
|||||||
5
packages/global/core/chat/inputGuide/type.d.ts
vendored
Normal file
5
packages/global/core/chat/inputGuide/type.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type ChatInputGuideSchemaType = {
|
||||||
|
_id: string;
|
||||||
|
appId: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
2
packages/global/core/chat/type.d.ts
vendored
2
packages/global/core/chat/type.d.ts
vendored
@ -10,7 +10,7 @@ import {
|
|||||||
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
||||||
import { NodeOutputKeyEnum } from '../workflow/constants';
|
import { NodeOutputKeyEnum } from '../workflow/constants';
|
||||||
import { DispatchNodeResponseKeyEnum } from '../workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '../workflow/runtime/constants';
|
||||||
import { AppSchema, VariableItemType } from '../app/type';
|
import { AppChatConfigType, AppSchema, VariableItemType } from '../app/type';
|
||||||
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
||||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||||
import { ChatBoxInputType } from '../../../../projects/app/src/components/ChatBox/type';
|
import { ChatBoxInputType } from '../../../../projects/app/src/components/ChatBox/type';
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export enum NodeInputKeyEnum {
|
|||||||
whisper = 'whisper',
|
whisper = 'whisper',
|
||||||
variables = 'variables',
|
variables = 'variables',
|
||||||
scheduleTrigger = 'scheduleTrigger',
|
scheduleTrigger = 'scheduleTrigger',
|
||||||
questionGuideText = 'questionGuideText',
|
chatInputGuide = 'chatInputGuide',
|
||||||
|
|
||||||
// entry
|
// entry
|
||||||
userChatInput = 'userChatInput',
|
userChatInput = 'userChatInput',
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
import { FlowNodeTemplateTypeEnum, WorkflowIOValueTypeEnum } from '../../constants';
|
|
||||||
import { getHandleConfig } from '../utils';
|
|
||||||
import { FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
|
||||||
import { VariableItemType } from '../../../app/type';
|
|
||||||
import { FlowNodeTemplateType } from '../../type';
|
|
||||||
|
|
||||||
export const getGlobalVariableNode = ({
|
|
||||||
id,
|
|
||||||
variables
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
variables: VariableItemType[];
|
|
||||||
}): FlowNodeTemplateType => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
templateType: FlowNodeTemplateTypeEnum.other,
|
|
||||||
flowNodeType: FlowNodeTypeEnum.systemConfig,
|
|
||||||
sourceHandle: getHandleConfig(true, true, true, true),
|
|
||||||
targetHandle: getHandleConfig(true, true, true, true),
|
|
||||||
avatar: '/imgs/workflow/variable.png',
|
|
||||||
name: '全局变量',
|
|
||||||
intro: '',
|
|
||||||
version: '481',
|
|
||||||
inputs: [],
|
|
||||||
outputs: variables.map((item) => ({
|
|
||||||
id: item.key,
|
|
||||||
key: item.key,
|
|
||||||
valueType: WorkflowIOValueTypeEnum.string,
|
|
||||||
type: FlowNodeOutputTypeEnum.static,
|
|
||||||
label: item.label
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,10 +1,6 @@
|
|||||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
import { FlowNodeTypeEnum } from '../../node/constant';
|
||||||
import { FlowNodeTemplateType } from '../../type/index.d';
|
import { FlowNodeTemplateType } from '../../type/index.d';
|
||||||
import {
|
import { FlowNodeTemplateTypeEnum, WorkflowIOValueTypeEnum } from '../../constants';
|
||||||
WorkflowIOValueTypeEnum,
|
|
||||||
NodeInputKeyEnum,
|
|
||||||
FlowNodeTemplateTypeEnum
|
|
||||||
} from '../../constants';
|
|
||||||
import { getHandleConfig } from '../utils';
|
import { getHandleConfig } from '../utils';
|
||||||
|
|
||||||
export const SystemConfigNode: FlowNodeTemplateType = {
|
export const SystemConfigNode: FlowNodeTemplateType = {
|
||||||
@ -19,50 +15,6 @@ export const SystemConfigNode: FlowNodeTemplateType = {
|
|||||||
unique: true,
|
unique: true,
|
||||||
forbidDelete: true,
|
forbidDelete: true,
|
||||||
version: '481',
|
version: '481',
|
||||||
inputs: [
|
inputs: [],
|
||||||
{
|
|
||||||
key: NodeInputKeyEnum.welcomeText,
|
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
|
||||||
valueType: WorkflowIOValueTypeEnum.string,
|
|
||||||
label: 'core.app.Welcome Text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: NodeInputKeyEnum.variables,
|
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
|
||||||
valueType: WorkflowIOValueTypeEnum.any,
|
|
||||||
label: 'core.module.Variable',
|
|
||||||
value: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: NodeInputKeyEnum.questionGuide,
|
|
||||||
valueType: WorkflowIOValueTypeEnum.boolean,
|
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
|
||||||
label: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: NodeInputKeyEnum.tts,
|
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
|
||||||
valueType: WorkflowIOValueTypeEnum.any,
|
|
||||||
label: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: NodeInputKeyEnum.whisper,
|
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
|
||||||
valueType: WorkflowIOValueTypeEnum.any,
|
|
||||||
label: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: NodeInputKeyEnum.scheduleTrigger,
|
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
|
||||||
valueType: WorkflowIOValueTypeEnum.any,
|
|
||||||
label: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: NodeInputKeyEnum.questionGuideText,
|
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
|
||||||
valueType: WorkflowIOValueTypeEnum.any,
|
|
||||||
label: ''
|
|
||||||
}
|
|
||||||
],
|
|
||||||
outputs: []
|
outputs: []
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,10 +12,15 @@ import type {
|
|||||||
AppTTSConfigType,
|
AppTTSConfigType,
|
||||||
AppWhisperConfigType,
|
AppWhisperConfigType,
|
||||||
AppScheduledTriggerConfigType,
|
AppScheduledTriggerConfigType,
|
||||||
AppQuestionGuideTextConfigType
|
ChatInputGuideConfigType,
|
||||||
|
AppChatConfigType
|
||||||
} 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 {
|
||||||
|
defaultChatInputGuideConfig,
|
||||||
|
defaultTTSConfig,
|
||||||
|
defaultWhisperConfig
|
||||||
|
} from '../app/constants';
|
||||||
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
||||||
|
|
||||||
export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
|
export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
|
||||||
@ -41,70 +46,81 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
|
|||||||
const welcomeText: string =
|
const welcomeText: string =
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.welcomeText)?.value || '';
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.welcomeText)?.value || '';
|
||||||
|
|
||||||
const variableNodes: VariableItemType[] =
|
const variables: VariableItemType[] =
|
||||||
guideModules?.inputs.find((item) => item.key === NodeInputKeyEnum.variables)?.value || [];
|
guideModules?.inputs.find((item) => item.key === NodeInputKeyEnum.variables)?.value || [];
|
||||||
|
|
||||||
const questionGuide: boolean =
|
const questionGuide: boolean =
|
||||||
!!guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.questionGuide)?.value ||
|
!!guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.questionGuide)?.value ||
|
||||||
false;
|
false;
|
||||||
|
|
||||||
const ttsConfig: AppTTSConfigType = guideModules?.inputs?.find(
|
const ttsConfig: AppTTSConfigType =
|
||||||
(item) => item.key === NodeInputKeyEnum.tts
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.tts)?.value ||
|
||||||
)?.value || { type: 'web' };
|
defaultTTSConfig;
|
||||||
|
|
||||||
const whisperConfig: AppWhisperConfigType =
|
const whisperConfig: AppWhisperConfigType =
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.whisper)?.value ||
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.whisper)?.value ||
|
||||||
defaultWhisperConfig;
|
defaultWhisperConfig;
|
||||||
|
|
||||||
const scheduledTriggerConfig: AppScheduledTriggerConfigType | null =
|
const scheduledTriggerConfig: AppScheduledTriggerConfigType = guideModules?.inputs?.find(
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.scheduleTrigger)?.value ??
|
(item) => item.key === NodeInputKeyEnum.scheduleTrigger
|
||||||
null;
|
)?.value;
|
||||||
|
|
||||||
const questionGuideText: AppQuestionGuideTextConfigType = guideModules?.inputs?.find(
|
const chatInputGuide: ChatInputGuideConfigType =
|
||||||
(item) => item.key === NodeInputKeyEnum.questionGuideText
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.chatInputGuide)?.value ||
|
||||||
)?.value || {
|
defaultChatInputGuideConfig;
|
||||||
open: false
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
welcomeText,
|
welcomeText,
|
||||||
variableNodes,
|
variables,
|
||||||
questionGuide,
|
questionGuide,
|
||||||
ttsConfig,
|
ttsConfig,
|
||||||
whisperConfig,
|
whisperConfig,
|
||||||
scheduledTriggerConfig,
|
scheduledTriggerConfig,
|
||||||
questionGuideText
|
chatInputGuide
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const replaceAppChatConfig = ({
|
export const getAppChatConfig = ({
|
||||||
node,
|
chatConfig,
|
||||||
variableList,
|
systemConfigNode,
|
||||||
welcomeText
|
storeVariables,
|
||||||
|
storeWelcomeText,
|
||||||
|
isPublicFetch = false
|
||||||
}: {
|
}: {
|
||||||
node?: StoreNodeItemType;
|
chatConfig?: AppChatConfigType;
|
||||||
variableList?: VariableItemType[];
|
systemConfigNode?: StoreNodeItemType;
|
||||||
welcomeText?: string;
|
storeVariables?: VariableItemType[];
|
||||||
}): StoreNodeItemType | undefined => {
|
storeWelcomeText?: string;
|
||||||
if (!node) return;
|
isPublicFetch: boolean;
|
||||||
return {
|
}): AppChatConfigType => {
|
||||||
...node,
|
const {
|
||||||
inputs: node.inputs.map((input) => {
|
welcomeText,
|
||||||
if (input.key === NodeInputKeyEnum.variables && variableList) {
|
variables,
|
||||||
return {
|
questionGuide,
|
||||||
...input,
|
ttsConfig,
|
||||||
value: variableList
|
whisperConfig,
|
||||||
};
|
scheduledTriggerConfig,
|
||||||
}
|
chatInputGuide
|
||||||
if (input.key === NodeInputKeyEnum.welcomeText && welcomeText) {
|
} = splitGuideModule(systemConfigNode);
|
||||||
return {
|
|
||||||
...input,
|
|
||||||
value: welcomeText
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
const config: AppChatConfigType = {
|
||||||
})
|
questionGuide,
|
||||||
|
ttsConfig,
|
||||||
|
whisperConfig,
|
||||||
|
scheduledTriggerConfig,
|
||||||
|
chatInputGuide,
|
||||||
|
...chatConfig,
|
||||||
|
variables: storeVariables ?? chatConfig?.variables ?? variables,
|
||||||
|
welcomeText: storeWelcomeText ?? chatConfig?.welcomeText ?? welcomeText
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!isPublicFetch) {
|
||||||
|
if (config?.chatInputGuide?.customUrl) {
|
||||||
|
config.chatInputGuide.customUrl = '';
|
||||||
|
}
|
||||||
|
config.scheduledTriggerConfig = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getOrInitModuleInputValue = (input: FlowNodeInputItemType) => {
|
export const getOrInitModuleInputValue = (input: FlowNodeInputItemType) => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { AppSchema } from '@fastgpt/global/core/app/type';
|
|||||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
import { getLLMModel } from '../ai/model';
|
import { getLLMModel } from '../ai/model';
|
||||||
import { MongoAppVersion } from './versionSchema';
|
import { MongoAppVersion } from './version/schema';
|
||||||
|
|
||||||
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
|
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
|
||||||
nodes
|
nodes
|
||||||
@ -55,11 +55,13 @@ export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
|
|||||||
if (version) {
|
if (version) {
|
||||||
return {
|
return {
|
||||||
nodes: version.nodes,
|
nodes: version.nodes,
|
||||||
edges: version.edges
|
edges: version.edges,
|
||||||
|
chatConfig: version.chatConfig || app?.chatConfig || {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
nodes: app?.modules || [],
|
nodes: app?.modules || [],
|
||||||
edges: app?.edges || []
|
edges: app?.edges || [],
|
||||||
|
chatConfig: app?.chatConfig || {}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
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();
|
|
||||||
@ -10,6 +10,16 @@ import {
|
|||||||
|
|
||||||
export const AppCollectionName = 'apps';
|
export const AppCollectionName = 'apps';
|
||||||
|
|
||||||
|
export const chatConfigType = {
|
||||||
|
welcomeText: String,
|
||||||
|
variables: Array,
|
||||||
|
questionGuide: Boolean,
|
||||||
|
ttsConfig: Object,
|
||||||
|
whisperConfig: Object,
|
||||||
|
scheduledTriggerConfig: Object,
|
||||||
|
chatInputGuide: Object
|
||||||
|
};
|
||||||
|
|
||||||
const AppSchema = new Schema({
|
const AppSchema = new Schema({
|
||||||
teamId: {
|
teamId: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
@ -47,6 +57,16 @@ const AppSchema = new Schema({
|
|||||||
default: () => new Date()
|
default: () => new Date()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// role and auth
|
||||||
|
permission: {
|
||||||
|
type: String,
|
||||||
|
enum: Object.keys(PermissionTypeMap),
|
||||||
|
default: PermissionTypeEnum.private
|
||||||
|
},
|
||||||
|
teamTags: {
|
||||||
|
type: [String]
|
||||||
|
},
|
||||||
|
|
||||||
// tmp store
|
// tmp store
|
||||||
modules: {
|
modules: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -56,6 +76,10 @@ const AppSchema = new Schema({
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: []
|
||||||
},
|
},
|
||||||
|
chatConfig: {
|
||||||
|
type: chatConfigType,
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
|
||||||
scheduledTriggerConfig: {
|
scheduledTriggerConfig: {
|
||||||
cronString: {
|
cronString: {
|
||||||
@ -74,14 +98,6 @@ const AppSchema = new Schema({
|
|||||||
|
|
||||||
inited: {
|
inited: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
|
||||||
permission: {
|
|
||||||
type: String,
|
|
||||||
enum: Object.keys(PermissionTypeMap),
|
|
||||||
default: PermissionTypeEnum.private
|
|
||||||
},
|
|
||||||
teamTags: {
|
|
||||||
type: [String]
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { connectionMongo, type Model } from '../../common/mongo';
|
import { connectionMongo, type Model } from '../../../common/mongo';
|
||||||
const { Schema, model, models } = connectionMongo;
|
const { Schema, model, models } = connectionMongo;
|
||||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||||
|
import { chatConfigType } from '../schema';
|
||||||
|
|
||||||
export const AppVersionCollectionName = 'app_versions';
|
export const AppVersionCollectionName = 'app_versions';
|
||||||
|
|
||||||
@ -21,6 +22,10 @@ const AppVersionSchema = new Schema({
|
|||||||
edges: {
|
edges: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: []
|
||||||
|
},
|
||||||
|
chatConfig: {
|
||||||
|
type: chatConfigType,
|
||||||
|
default: {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
29
packages/service/core/chat/inputGuide/schema.ts
Normal file
29
packages/service/core/chat/inputGuide/schema.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { AppCollectionName } from '../../app/schema';
|
||||||
|
import { connectionMongo, type Model } from '../../../common/mongo';
|
||||||
|
const { Schema, model, models } = connectionMongo;
|
||||||
|
import type { ChatInputGuideSchemaType } from '@fastgpt/global/core/chat/inputGuide/type.d';
|
||||||
|
|
||||||
|
export const ChatInputGuideCollectionName = 'chat_input_guides';
|
||||||
|
|
||||||
|
const ChatInputGuideSchema = new Schema({
|
||||||
|
appId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: AppCollectionName,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
ChatInputGuideSchema.index({ appId: 1, text: 1 }, { unique: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MongoChatInputGuide: Model<ChatInputGuideSchemaType> =
|
||||||
|
models[ChatInputGuideCollectionName] || model(ChatInputGuideCollectionName, ChatInputGuideSchema);
|
||||||
|
|
||||||
|
MongoChatInputGuide.syncIndexes();
|
||||||
@ -168,7 +168,8 @@ export async function pushDataListToTrainingQueue({
|
|||||||
indexes: item.indexes
|
indexes: item.indexes
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
session
|
session,
|
||||||
|
ordered: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
|||||||
import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils';
|
import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
import { dispatchSystemConfig } from './init/systemConfig';
|
import { dispatchSystemConfig } from './init/systemConfig';
|
||||||
import { dispatchUpdateVariable } from './tools/runUpdateVar';
|
import { dispatchUpdateVariable } from './tools/runUpdateVar';
|
||||||
|
import { addLog } from '../../../common/system/log';
|
||||||
|
|
||||||
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
|
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
|
||||||
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
|
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
|
||||||
@ -137,7 +138,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
}
|
}
|
||||||
if (nodeDispatchUsages) {
|
if (nodeDispatchUsages) {
|
||||||
chatNodeUsages = chatNodeUsages.concat(nodeDispatchUsages);
|
chatNodeUsages = chatNodeUsages.concat(nodeDispatchUsages);
|
||||||
props.maxRunTimes -= nodeDispatchUsages.length;
|
|
||||||
}
|
}
|
||||||
if (toolResponses !== undefined) {
|
if (toolResponses !== undefined) {
|
||||||
if (Array.isArray(toolResponses) && toolResponses.length === 0) return;
|
if (Array.isArray(toolResponses) && toolResponses.length === 0) return;
|
||||||
@ -213,9 +213,11 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (status === 'run') {
|
if (status === 'run') {
|
||||||
|
addLog.info(`[dispatchWorkFlow] nodeRunWithActive: ${node.name}`);
|
||||||
return nodeRunWithActive(node);
|
return nodeRunWithActive(node);
|
||||||
}
|
}
|
||||||
if (status === 'skip') {
|
if (status === 'skip') {
|
||||||
|
addLog.info(`[dispatchWorkFlow] nodeRunWithActive: ${node.name}`);
|
||||||
return nodeRunWithSkip(node);
|
return nodeRunWithSkip(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +277,8 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
props.maxRunTimes--;
|
||||||
|
|
||||||
// get node running params
|
// get node running params
|
||||||
const params = getNodeRunParams(node);
|
const params = getNodeRunParams(node);
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { NextApiRequest } from 'next';
|
import { ApiRequestProps } from '../../type/next';
|
||||||
|
|
||||||
export type ReqHeaderAuthType = {
|
export type ReqHeaderAuthType = {
|
||||||
cookie?: string;
|
cookie?: string;
|
||||||
@ -9,7 +9,7 @@ export type ReqHeaderAuthType = {
|
|||||||
authorization?: string;
|
authorization?: string;
|
||||||
};
|
};
|
||||||
export type AuthModeType = {
|
export type AuthModeType = {
|
||||||
req: NextApiRequest;
|
req: ApiRequestProps;
|
||||||
authToken?: boolean;
|
authToken?: boolean;
|
||||||
authRoot?: boolean;
|
authRoot?: boolean;
|
||||||
authApiKey?: boolean;
|
authApiKey?: boolean;
|
||||||
|
|||||||
@ -45,7 +45,7 @@ const UserSchema = new Schema({
|
|||||||
inviterId: {
|
inviterId: {
|
||||||
// 谁邀请注册的
|
// 谁邀请注册的
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'user'
|
ref: userCollectionName
|
||||||
},
|
},
|
||||||
promotionRate: {
|
promotionRate: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
|
import Papa from 'papaparse';
|
||||||
|
|
||||||
export const loadFile2Buffer = ({ file, onError }: { file: File; onError?: (err: any) => void }) =>
|
export const loadFile2Buffer = ({ file, onError }: { file: File; onError?: (err: any) => void }) =>
|
||||||
new Promise<ArrayBuffer>((resolve, reject) => {
|
new Promise<ArrayBuffer>((resolve, reject) => {
|
||||||
@ -29,3 +30,47 @@ export const loadFile2Buffer = ({ file, onError }: { file: File; onError?: (err:
|
|||||||
reject('The browser does not support file content reading');
|
reject('The browser does not support file content reading');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const readFileRawText = ({
|
||||||
|
file,
|
||||||
|
onError
|
||||||
|
}: {
|
||||||
|
file: File;
|
||||||
|
onError?: (err: any) => void;
|
||||||
|
}) => {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = async ({ target }) => {
|
||||||
|
if (!target?.result) {
|
||||||
|
onError?.('Load file error');
|
||||||
|
return reject('Load file error');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
resolve(target.result as string);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err, 'Load file error');
|
||||||
|
onError?.(err);
|
||||||
|
|
||||||
|
reject(getErrText(err, 'Load file error'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.onerror = (err) => {
|
||||||
|
console.log(err, 'Load file error');
|
||||||
|
onError?.(err);
|
||||||
|
|
||||||
|
reject(getErrText(err, 'Load file error'));
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
} catch (error) {
|
||||||
|
reject('The browser does not support file content reading');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readCsvRawText = async ({ file }: { file: File }) => {
|
||||||
|
const rawText = await readFileRawText({ file });
|
||||||
|
const csvArr = Papa.parse(rawText).data as string[][];
|
||||||
|
|
||||||
|
return csvArr;
|
||||||
|
};
|
||||||
|
|||||||
@ -10,7 +10,7 @@ type Props = FlexProps & {
|
|||||||
const EmptyTip = ({ text, ...props }: Props) => {
|
const EmptyTip = ({ text, ...props }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Flex mt={5} flexDirection={'column'} alignItems={'center'} pt={'10vh'} {...props}>
|
<Flex mt={5} flexDirection={'column'} alignItems={'center'} py={'10vh'} {...props}>
|
||||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
<Box mt={2} color={'myGray.500'}>
|
<Box mt={2} color={'myGray.500'}>
|
||||||
{text || t('common.empty.Common Tip')}
|
{text || t('common.empty.Common Tip')}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconNameType
|
|||||||
.catch((error) => console.log(error));
|
.catch((error) => console.log(error));
|
||||||
}, [name]);
|
}, [name]);
|
||||||
|
|
||||||
return !!name && !!iconPaths[name] ? (
|
return !!IconComponent ? (
|
||||||
<Icon
|
<Icon
|
||||||
{...IconComponent}
|
{...IconComponent}
|
||||||
w={w}
|
w={w}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ type Props = BoxProps & {
|
|||||||
|
|
||||||
const MyBox = ({ text, isLoading, children, ...props }: Props, ref: any) => {
|
const MyBox = ({ text, isLoading, children, ...props }: Props, ref: any) => {
|
||||||
return (
|
return (
|
||||||
<Box ref={ref} position={'relative'} {...props}>
|
<Box ref={ref} position={isLoading ? 'relative' : 'unset'} {...props}>
|
||||||
{isLoading && <Loading fixed={false} text={text} />}
|
{isLoading && <Loading fixed={false} text={text} />}
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -11,11 +11,13 @@ import {
|
|||||||
useMediaQuery
|
useMediaQuery
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import MyIcon from '../Icon';
|
import MyIcon from '../Icon';
|
||||||
|
import MyBox from '../MyBox';
|
||||||
|
|
||||||
export interface MyModalProps extends ModalContentProps {
|
export interface MyModalProps extends ModalContentProps {
|
||||||
iconSrc?: string;
|
iconSrc?: string;
|
||||||
title?: any;
|
title?: any;
|
||||||
isCentered?: boolean;
|
isCentered?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
@ -27,6 +29,7 @@ const MyModal = ({
|
|||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
isCentered,
|
isCentered,
|
||||||
|
isLoading,
|
||||||
w = 'auto',
|
w = 'auto',
|
||||||
maxW = ['90vw', '600px'],
|
maxW = ['90vw', '600px'],
|
||||||
...props
|
...props
|
||||||
@ -39,6 +42,7 @@ const MyModal = ({
|
|||||||
onClose={() => onClose && onClose()}
|
onClose={() => onClose && onClose()}
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
isCentered={isPc ? isCentered : true}
|
isCentered={isPc ? isCentered : true}
|
||||||
|
blockScrollOnMount={false}
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent
|
<ModalContent
|
||||||
@ -78,14 +82,15 @@ const MyModal = ({
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box
|
<MyBox
|
||||||
|
isLoading={isLoading}
|
||||||
overflow={props.overflow || 'overlay'}
|
overflow={props.overflow || 'overlay'}
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
display={'flex'}
|
display={'flex'}
|
||||||
flexDirection={'column'}
|
flexDirection={'column'}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</MyBox>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
40
packages/web/components/common/String/HighlightText.tsx
Normal file
40
packages/web/components/common/String/HighlightText.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const HighlightText = ({
|
||||||
|
rawText,
|
||||||
|
matchText,
|
||||||
|
color = 'primary.600'
|
||||||
|
}: {
|
||||||
|
rawText: string;
|
||||||
|
matchText: string;
|
||||||
|
color?: string;
|
||||||
|
}) => {
|
||||||
|
const regex = new RegExp(`(${matchText})`, 'gi');
|
||||||
|
const parts = rawText.split(regex);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{parts.map((part, index) => {
|
||||||
|
let highLight = part.toLowerCase() === matchText.toLowerCase();
|
||||||
|
|
||||||
|
if (highLight) {
|
||||||
|
parts.find((item, i) => {
|
||||||
|
if (i >= index) return;
|
||||||
|
if (item.toLowerCase() === matchText.toLowerCase()) {
|
||||||
|
highLight = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box as="span" key={index} color={highLight ? color : 'inherit'}>
|
||||||
|
{part}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HighlightText;
|
||||||
@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query';
|
|||||||
import type { UseMutationOptions } from '@tanstack/react-query';
|
import type { UseMutationOptions } from '@tanstack/react-query';
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useRequest as ahooksUseRequest } from 'ahooks';
|
||||||
|
|
||||||
interface Props extends UseMutationOptions<any, any, any, any> {
|
interface Props extends UseMutationOptions<any, any, any, any> {
|
||||||
successToast?: string | null;
|
successToast?: string | null;
|
||||||
@ -39,3 +40,50 @@ export const useRequest = ({ successToast, errorToast, onSuccess, onError, ...pr
|
|||||||
|
|
||||||
return mutation;
|
return mutation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UseRequestFunProps<TData, TParams extends any[]> = Parameters<
|
||||||
|
typeof ahooksUseRequest<TData, TParams>
|
||||||
|
>;
|
||||||
|
export const useRequest2 = <TData, TParams extends any[]>(
|
||||||
|
server: UseRequestFunProps<TData, TParams>[0],
|
||||||
|
options: UseRequestFunProps<TData, TParams>[1] & {
|
||||||
|
errorToast?: string;
|
||||||
|
successToast?: string;
|
||||||
|
} = {},
|
||||||
|
plugin?: UseRequestFunProps<TData, TParams>[2]
|
||||||
|
) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { errorToast, successToast, ...rest } = options || {};
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const res = ahooksUseRequest<TData, TParams>(
|
||||||
|
server,
|
||||||
|
{
|
||||||
|
...rest,
|
||||||
|
onError: (err, params) => {
|
||||||
|
rest?.onError?.(err, params);
|
||||||
|
if (errorToast !== undefined) {
|
||||||
|
const errText = t(getErrText(err, errorToast || ''));
|
||||||
|
if (errText) {
|
||||||
|
toast({
|
||||||
|
title: errText,
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: (res, params) => {
|
||||||
|
rest?.onSuccess?.(res, params);
|
||||||
|
if (successToast) {
|
||||||
|
toast({
|
||||||
|
title: successToast,
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugin
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
import { useRef, useState, useEffect } from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
import { Box, BoxProps } from '@chakra-ui/react';
|
import { Box, BoxProps } from '@chakra-ui/react';
|
||||||
import { useToast } from './useToast';
|
import { useToast } from './useToast';
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { PaginationProps, PaginationResponse } from '../common/fetch/type';
|
import { PaginationProps, PaginationResponse } from '../common/fetch/type';
|
||||||
import { useBoolean, useLockFn, useMemoizedFn, useMount, useScroll, useVirtualList } from 'ahooks';
|
import {
|
||||||
|
useBoolean,
|
||||||
|
useLockFn,
|
||||||
|
useMemoizedFn,
|
||||||
|
useMount,
|
||||||
|
useScroll,
|
||||||
|
useVirtualList,
|
||||||
|
useRequest
|
||||||
|
} from 'ahooks';
|
||||||
import MyBox from '../components/common/MyBox';
|
import MyBox from '../components/common/MyBox';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
@ -13,12 +21,19 @@ export function useScrollPagination<
|
|||||||
>(
|
>(
|
||||||
api: (data: TParams) => Promise<TData>,
|
api: (data: TParams) => Promise<TData>,
|
||||||
{
|
{
|
||||||
|
debounceWait,
|
||||||
|
throttleWait,
|
||||||
|
refreshDeps,
|
||||||
itemHeight = 50,
|
itemHeight = 50,
|
||||||
overscan = 10,
|
overscan = 10,
|
||||||
|
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
defaultParams = {}
|
defaultParams = {}
|
||||||
}: {
|
}: {
|
||||||
|
debounceWait?: number;
|
||||||
|
throttleWait?: number;
|
||||||
|
refreshDeps?: any[];
|
||||||
|
|
||||||
itemHeight: number;
|
itemHeight: number;
|
||||||
overscan?: number;
|
overscan?: number;
|
||||||
|
|
||||||
@ -45,7 +60,7 @@ export function useScrollPagination<
|
|||||||
});
|
});
|
||||||
|
|
||||||
const loadData = useLockFn(async (num: number = current) => {
|
const loadData = useLockFn(async (num: number = current) => {
|
||||||
if (noMore.current) return;
|
if (noMore.current && num !== 1) return;
|
||||||
|
|
||||||
setTrue();
|
setTrue();
|
||||||
|
|
||||||
@ -59,7 +74,7 @@ export function useScrollPagination<
|
|||||||
setCurrent(num);
|
setCurrent(num);
|
||||||
|
|
||||||
if (num === 1) {
|
if (num === 1) {
|
||||||
// reload
|
// init or reload
|
||||||
setData(res.list);
|
setData(res.list);
|
||||||
noMore.current = res.list.length >= res.total;
|
noMore.current = res.list.length >= res.total;
|
||||||
} else {
|
} else {
|
||||||
@ -78,34 +93,48 @@ export function useScrollPagination<
|
|||||||
setFalse();
|
setFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const scroll2Top = () => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
containerRef.current.scrollTop = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ScrollList = useMemoizedFn(
|
const ScrollList = useMemoizedFn(
|
||||||
({
|
({
|
||||||
children,
|
children,
|
||||||
|
EmptyChildren,
|
||||||
isLoading,
|
isLoading,
|
||||||
...props
|
...props
|
||||||
}: { children: React.ReactNode; isLoading?: boolean } & BoxProps) => {
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
EmptyChildren?: React.ReactNode;
|
||||||
|
isLoading?: boolean;
|
||||||
|
} & BoxProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}>
|
<MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}>
|
||||||
<Box ref={wrapperRef}>{children}</Box>
|
<Box ref={wrapperRef}>{children}</Box>
|
||||||
|
{noMore.current && list.length > 0 && (
|
||||||
|
<Box py={4} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}>
|
||||||
|
{t('common.No more data')}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{list.length === 0 && !isLoading && EmptyChildren && <>{EmptyChildren}</>}
|
||||||
</MyBox>
|
</MyBox>
|
||||||
{noMore.current && (
|
|
||||||
<Box pb={2} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}>
|
|
||||||
{t('common.No more data')}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
useMount(() => {
|
useRequest(() => loadData(1), {
|
||||||
loadData(1);
|
refreshDeps,
|
||||||
|
debounceWait: data.length === 0 ? 0 : debounceWait,
|
||||||
|
throttleWait
|
||||||
});
|
});
|
||||||
|
|
||||||
const scroll = useScroll(containerRef);
|
const scroll = useScroll(containerRef);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current || list.length === 0) return;
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
||||||
|
|
||||||
@ -118,8 +147,10 @@ export function useScrollPagination<
|
|||||||
containerRef,
|
containerRef,
|
||||||
list,
|
list,
|
||||||
data,
|
data,
|
||||||
|
setData,
|
||||||
isLoading,
|
isLoading,
|
||||||
ScrollList,
|
ScrollList,
|
||||||
fetchData: loadData
|
fetchData: loadData,
|
||||||
|
scroll2Top
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,6 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"next-i18next": "15.2.0",
|
"next-i18next": "15.2.0",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"pdfjs-dist": "4.0.269",
|
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"use-context-selector": "^1.4.4",
|
"use-context-selector": "^1.4.4",
|
||||||
"react-day-picker": "^8.7.1",
|
"react-day-picker": "^8.7.1",
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -283,9 +283,6 @@ importers:
|
|||||||
papaparse:
|
papaparse:
|
||||||
specifier: ^5.4.1
|
specifier: ^5.4.1
|
||||||
version: 5.4.1
|
version: 5.4.1
|
||||||
pdfjs-dist:
|
|
||||||
specifier: 4.0.269
|
|
||||||
version: 4.0.269(encoding@0.1.13)
|
|
||||||
react:
|
react:
|
||||||
specifier: 18.3.1
|
specifier: 18.3.1
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
|
|||||||
@ -47,14 +47,6 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
projects/app/i18n/en/chat.json
Normal file
18
projects/app/i18n/en/chat.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"Chat input guide lexicon is empty": "The lexicon has not been configured",
|
||||||
|
"Config Texts": "Config thesaurus ",
|
||||||
|
"Config input guide lexicon": "Config",
|
||||||
|
"Config input guide lexicon title": "Config lexicon",
|
||||||
|
"Config question guide": "Configuration input Prompt ",
|
||||||
|
"Csv input lexicon tip": "Only CSV can be imported in batches. Click to download the template",
|
||||||
|
"Custom input guide url": "Custom lexicon url",
|
||||||
|
"Custom question guide URL": "Custom lexicon address ",
|
||||||
|
"Input Guide": "Intelligent Recommendation ",
|
||||||
|
"Input guide": "Input guide",
|
||||||
|
"Input guide lexicon": "Lexicon",
|
||||||
|
"Input guide tip": "You can configure some preset questions. When the user enters a question, the relevant question is retrieved from these preset questions for prompt.",
|
||||||
|
"Insert input guide, Some data already exists": "Duplicate data, automatically filtered, insert: {{len}} data",
|
||||||
|
"New input guide lexicon": "New lexicon",
|
||||||
|
"Only support CSV": "Only support CSV import, click download template ",
|
||||||
|
"Question Guide Texts": "Lexicon"
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
"App": "App",
|
"App": "App",
|
||||||
"Export": "Export",
|
"Export": "Export",
|
||||||
"Folder": "Folder",
|
"Folder": "Folder",
|
||||||
|
"Is open": "Opened",
|
||||||
"Login": "Login",
|
"Login": "Login",
|
||||||
"Move": "Move",
|
"Move": "Move",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
@ -16,6 +17,7 @@
|
|||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Add New": "Add New",
|
"Add New": "Add New",
|
||||||
|
"Add Success": "Add successfully",
|
||||||
"All": "All",
|
"All": "All",
|
||||||
"Back": "Back",
|
"Back": "Back",
|
||||||
"Beta": "Beta",
|
"Beta": "Beta",
|
||||||
@ -1177,6 +1179,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
"Create failed": "Create failed",
|
||||||
"fileNotFound": "File not found~",
|
"fileNotFound": "File not found~",
|
||||||
"team": {
|
"team": {
|
||||||
"overSize": "Team members exceed the limit"
|
"overSize": "Team members exceed the limit"
|
||||||
|
|||||||
@ -46,14 +46,6 @@
|
|||||||
"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": "模块名不能为空"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
projects/app/i18n/zh/chat.json
Normal file
13
projects/app/i18n/zh/chat.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Chat input guide lexicon is empty": "还没有配置词库",
|
||||||
|
"Config input guide": "配置输入引导",
|
||||||
|
"Config input guide lexicon": "配置词库",
|
||||||
|
"Config input guide lexicon title": "配置词库",
|
||||||
|
"Csv input lexicon tip": "仅支持 CSV 批量导入,点击下载模板",
|
||||||
|
"Custom input guide url": "自定义词库地址",
|
||||||
|
"Input guide": "输入引导",
|
||||||
|
"Input guide lexicon": "词库",
|
||||||
|
"Input guide tip": "可以配置一些预设的问题。在用户输入问题时,会从这些预设问题中获取相关问题进行提示。",
|
||||||
|
"Insert input guide, Some data already exists": "有重复数据,已自动过滤,共插入: {{len}} 条数据",
|
||||||
|
"New input guide lexicon": "新词库"
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
"App": "应用",
|
"App": "应用",
|
||||||
"Export": "导出",
|
"Export": "导出",
|
||||||
"Folder": "文件夹",
|
"Folder": "文件夹",
|
||||||
|
"Is open": "是否开启",
|
||||||
"Login": "登录",
|
"Login": "登录",
|
||||||
"Move": "移动",
|
"Move": "移动",
|
||||||
"Name": "名称",
|
"Name": "名称",
|
||||||
@ -16,6 +17,7 @@
|
|||||||
"Action": "操作",
|
"Action": "操作",
|
||||||
"Add": "添加",
|
"Add": "添加",
|
||||||
"Add New": "新增",
|
"Add New": "新增",
|
||||||
|
"Add Success": "添加成功",
|
||||||
"All": "全部",
|
"All": "全部",
|
||||||
"Back": "返回",
|
"Back": "返回",
|
||||||
"Beta": "实验版",
|
"Beta": "实验版",
|
||||||
@ -1184,6 +1186,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
"Create failed": "创建失败",
|
||||||
"fileNotFound": "文件找不到了~",
|
"fileNotFound": "文件找不到了~",
|
||||||
"team": {
|
"team": {
|
||||||
"overSize": "团队成员超出上限"
|
"overSize": "团队成员超出上限"
|
||||||
|
|||||||
@ -86,7 +86,7 @@ const nextConfig = {
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
transpilePackages: ['@fastgpt/*', 'ahooks', '@chakra-ui/*', 'react'],
|
transpilePackages: ['@fastgpt/*', 'ahooks'],
|
||||||
experimental: {
|
experimental: {
|
||||||
// 指定导出包优化,按需引入包模块
|
// 指定导出包优化,按需引入包模块
|
||||||
optimizePackageImports: ['mongoose', 'pg'],
|
optimizePackageImports: ['mongoose', 'pg'],
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "4.8",
|
"version": "4.8.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { useSpeech } from '@/web/common/hooks/useSpeech';
|
import { useSpeech } from '@/web/common/hooks/useSpeech';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import { Box, Flex, Image, Spinner, Textarea } from '@chakra-ui/react';
|
import { Box, Flex, Image, Spinner, Textarea } from '@chakra-ui/react';
|
||||||
import React, { useRef, useEffect, useCallback, useTransition } from 'react';
|
import React, { useRef, useEffect, useCallback } from 'react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import MyTooltip from '../MyTooltip';
|
import MyTooltip from '../../MyTooltip';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||||
@ -12,18 +12,16 @@ import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
|||||||
import { addDays } from 'date-fns';
|
import { addDays } from 'date-fns';
|
||||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||||
import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from './type';
|
import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from '../type';
|
||||||
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 dynamic from 'next/dynamic';
|
||||||
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 InputGuideBox = dynamic(() => import('./InputGuideBox'));
|
||||||
|
|
||||||
|
const ChatInput = ({
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
onStop,
|
onStop,
|
||||||
TextareaDom,
|
TextareaDom,
|
||||||
@ -38,7 +36,7 @@ const MessageInput = ({
|
|||||||
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
|
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
|
||||||
resetInputVal: (val: ChatBoxInputType) => void;
|
resetInputVal: (val: ChatBoxInputType) => void;
|
||||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||||
appId?: string;
|
appId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { setValue, watch, control } = chatForm;
|
const { setValue, watch, control } = chatForm;
|
||||||
const inputValue = watch('input');
|
const inputValue = watch('input');
|
||||||
@ -53,12 +51,19 @@ const MessageInput = ({
|
|||||||
name: 'files'
|
name: 'files'
|
||||||
});
|
});
|
||||||
|
|
||||||
const { shareId, outLinkUid, teamId, teamToken, isChatting, whisperConfig, autoTTSResponse } =
|
const {
|
||||||
useChatProviderStore();
|
shareId,
|
||||||
|
outLinkUid,
|
||||||
|
teamId,
|
||||||
|
teamToken,
|
||||||
|
isChatting,
|
||||||
|
whisperConfig,
|
||||||
|
autoTTSResponse,
|
||||||
|
chatInputGuide
|
||||||
|
} = useChatProviderStore();
|
||||||
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);
|
||||||
@ -150,9 +155,9 @@ const MessageInput = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
/* on send */
|
/* on send */
|
||||||
const handleSend = async () => {
|
const handleSend = async (val?: string) => {
|
||||||
if (!canSendMessage) return;
|
if (!canSendMessage) return;
|
||||||
const textareaValue = TextareaDom.current?.value || '';
|
const textareaValue = val || TextareaDom.current?.value || '';
|
||||||
|
|
||||||
onSendMessage({
|
onSendMessage({
|
||||||
text: textareaValue.trim(),
|
text: textareaValue.trim(),
|
||||||
@ -211,23 +216,6 @@ 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
|
||||||
@ -248,6 +236,20 @@ const MessageInput = ({
|
|||||||
borderTopColor: 'rgba(0,0,0,0.15)'
|
borderTopColor: 'rgba(0,0,0,0.15)'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
{/* Chat input guide box */}
|
||||||
|
{chatInputGuide.open && (
|
||||||
|
<InputGuideBox
|
||||||
|
appId={appId}
|
||||||
|
text={inputValue}
|
||||||
|
onSelect={(e) => {
|
||||||
|
setValue('input', e);
|
||||||
|
}}
|
||||||
|
onSend={(e) => {
|
||||||
|
handleSend(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* translate loading */}
|
{/* translate loading */}
|
||||||
<Flex
|
<Flex
|
||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
@ -266,21 +268,6 @@ 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) => (
|
||||||
@ -415,12 +402,7 @@ const MessageInput = ({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
e.key === 'a' && e.ctrlKey && e.target?.select();
|
e.key === 'a' && e.ctrlKey && e.target?.select();
|
||||||
|
|
||||||
if (
|
if ((isPc || window !== parent) && e.keyCode === 13 && !e.shiftKey) {
|
||||||
(isPc || window !== parent) &&
|
|
||||||
e.keyCode === 13 &&
|
|
||||||
!e.shiftKey &&
|
|
||||||
!(havInput && data?.list.length && data?.list.length > 0)
|
|
||||||
) {
|
|
||||||
handleSend();
|
handleSend();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
@ -556,4 +538,4 @@ const MessageInput = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(MessageInput);
|
export default React.memo(ChatInput);
|
||||||
111
projects/app/src/components/ChatBox/Input/InputGuideBox.tsx
Normal file
111
projects/app/src/components/ChatBox/Input/InputGuideBox.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
import React from 'react';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { queryChatInputGuideList } from '@/web/core/chat/inputGuide/api';
|
||||||
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import HighlightText from '@fastgpt/web/components/common/String/HighlightText';
|
||||||
|
import { useChatProviderStore } from '../Provider';
|
||||||
|
|
||||||
|
export default function InputGuideBox({
|
||||||
|
appId,
|
||||||
|
text,
|
||||||
|
onSelect,
|
||||||
|
onSend
|
||||||
|
}: {
|
||||||
|
appId: string;
|
||||||
|
text: string;
|
||||||
|
onSelect: (text: string) => void;
|
||||||
|
onSend: (text: string) => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { chatT } = useI18n();
|
||||||
|
const { chatInputGuide } = useChatProviderStore();
|
||||||
|
|
||||||
|
const { data = [] } = useRequest2(
|
||||||
|
async () => {
|
||||||
|
if (!text) return [];
|
||||||
|
return await queryChatInputGuideList(
|
||||||
|
{
|
||||||
|
appId,
|
||||||
|
searchKey: text
|
||||||
|
},
|
||||||
|
chatInputGuide.customUrl ? chatInputGuide.customUrl : undefined
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refreshDeps: [text],
|
||||||
|
throttleWait: 300
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterData = data.filter((item) => item !== text).slice(0, 5);
|
||||||
|
|
||||||
|
return filterData.length ? (
|
||||||
|
<Box
|
||||||
|
bg={'white'}
|
||||||
|
boxShadow={'lg'}
|
||||||
|
borderWidth={'1px'}
|
||||||
|
borderColor={'borderColor.base'}
|
||||||
|
p={2}
|
||||||
|
borderRadius={'md'}
|
||||||
|
position={'absolute'}
|
||||||
|
top={-3}
|
||||||
|
w={'100%'}
|
||||||
|
zIndex={150}
|
||||||
|
transform={'translateY(-100%)'}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'} gap={2} mb={2} px={2}>
|
||||||
|
<MyIcon name={'union'} />
|
||||||
|
<Box>{chatT('Input guide')}</Box>
|
||||||
|
</Flex>
|
||||||
|
{data.map((item, index) => (
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
as={'li'}
|
||||||
|
key={item}
|
||||||
|
px={4}
|
||||||
|
py={3}
|
||||||
|
borderRadius={'sm'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
overflow={'auto'}
|
||||||
|
_notLast={{
|
||||||
|
mb: 1
|
||||||
|
}}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
color={'myGray.600'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'primary.50',
|
||||||
|
color: 'primary.600',
|
||||||
|
'.send-icon': {
|
||||||
|
display: 'block'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => onSelect(item)}
|
||||||
|
>
|
||||||
|
<Box fontSize={'sm'} flex={'1 0 0'}>
|
||||||
|
<HighlightText rawText={item} matchText={text} />
|
||||||
|
</Box>
|
||||||
|
<MyTooltip label={t('core.chat.markdown.Send Question')}>
|
||||||
|
<MyIcon
|
||||||
|
className="send-icon"
|
||||||
|
display={'none'}
|
||||||
|
name={'chatSend'}
|
||||||
|
boxSize={4}
|
||||||
|
color={'myGray.500'}
|
||||||
|
_hover={{
|
||||||
|
color: 'primary.600'
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSend(item);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
@ -2,17 +2,23 @@ import React, { useContext, createContext, useState, useMemo, useEffect, useCall
|
|||||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||||
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
|
||||||
import {
|
import {
|
||||||
|
AppChatConfigType,
|
||||||
AppTTSConfigType,
|
AppTTSConfigType,
|
||||||
AppWhisperConfigType,
|
AppWhisperConfigType,
|
||||||
|
ChatInputGuideConfigType,
|
||||||
VariableItemType
|
VariableItemType
|
||||||
} from '@fastgpt/global/core/app/type';
|
} from '@fastgpt/global/core/app/type';
|
||||||
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import {
|
||||||
|
defaultChatInputGuideConfig,
|
||||||
|
defaultTTSConfig,
|
||||||
|
defaultWhisperConfig
|
||||||
|
} from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
type useChatStoreType = OutLinkChatAuthProps & {
|
type useChatStoreType = OutLinkChatAuthProps & {
|
||||||
welcomeText: string;
|
welcomeText: string;
|
||||||
variableNodes: VariableItemType[];
|
variableList: VariableItemType[];
|
||||||
questionGuide: boolean;
|
questionGuide: boolean;
|
||||||
ttsConfig: AppTTSConfigType;
|
ttsConfig: AppTTSConfigType;
|
||||||
whisperConfig: AppWhisperConfigType;
|
whisperConfig: AppWhisperConfigType;
|
||||||
@ -38,10 +44,11 @@ type useChatStoreType = OutLinkChatAuthProps & {
|
|||||||
chatHistories: ChatSiteItemType[];
|
chatHistories: ChatSiteItemType[];
|
||||||
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||||
isChatting: boolean;
|
isChatting: boolean;
|
||||||
|
chatInputGuide: ChatInputGuideConfigType;
|
||||||
};
|
};
|
||||||
const StateContext = createContext<useChatStoreType>({
|
const StateContext = createContext<useChatStoreType>({
|
||||||
welcomeText: '',
|
welcomeText: '',
|
||||||
variableNodes: [],
|
variableList: [],
|
||||||
questionGuide: false,
|
questionGuide: false,
|
||||||
ttsConfig: {
|
ttsConfig: {
|
||||||
type: 'none',
|
type: 'none',
|
||||||
@ -87,11 +94,15 @@ const StateContext = createContext<useChatStoreType>({
|
|||||||
},
|
},
|
||||||
finishSegmentedAudio: function (): void {
|
finishSegmentedAudio: function (): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
|
chatInputGuide: {
|
||||||
|
open: false,
|
||||||
|
customUrl: ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||||
userGuideModule?: StoreNodeItemType;
|
chatConfig?: AppChatConfigType;
|
||||||
|
|
||||||
// not chat test params
|
// not chat test params
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
@ -105,15 +116,19 @@ const Provider = ({
|
|||||||
outLinkUid,
|
outLinkUid,
|
||||||
teamId,
|
teamId,
|
||||||
teamToken,
|
teamToken,
|
||||||
userGuideModule,
|
chatConfig = {},
|
||||||
children
|
children
|
||||||
}: ChatProviderProps) => {
|
}: ChatProviderProps) => {
|
||||||
const [chatHistories, setChatHistories] = useState<ChatSiteItemType[]>([]);
|
const [chatHistories, setChatHistories] = useState<ChatSiteItemType[]>([]);
|
||||||
|
|
||||||
const { welcomeText, variableNodes, questionGuide, ttsConfig, whisperConfig } = useMemo(
|
const {
|
||||||
() => splitGuideModule(userGuideModule),
|
welcomeText = '',
|
||||||
[userGuideModule]
|
variables = [],
|
||||||
);
|
questionGuide = false,
|
||||||
|
ttsConfig = defaultTTSConfig,
|
||||||
|
whisperConfig = defaultWhisperConfig,
|
||||||
|
chatInputGuide = defaultChatInputGuideConfig
|
||||||
|
} = useMemo(() => chatConfig, [chatConfig]);
|
||||||
|
|
||||||
// segment audio
|
// segment audio
|
||||||
const [audioPlayingChatId, setAudioPlayingChatId] = useState<string>();
|
const [audioPlayingChatId, setAudioPlayingChatId] = useState<string>();
|
||||||
@ -150,7 +165,7 @@ const Provider = ({
|
|||||||
teamId,
|
teamId,
|
||||||
teamToken,
|
teamToken,
|
||||||
welcomeText,
|
welcomeText,
|
||||||
variableNodes,
|
variableList: variables,
|
||||||
questionGuide,
|
questionGuide,
|
||||||
ttsConfig,
|
ttsConfig,
|
||||||
whisperConfig,
|
whisperConfig,
|
||||||
@ -167,7 +182,8 @@ const Provider = ({
|
|||||||
setAudioPlayingChatId,
|
setAudioPlayingChatId,
|
||||||
chatHistories,
|
chatHistories,
|
||||||
setChatHistories,
|
setChatHistories,
|
||||||
isChatting
|
isChatting,
|
||||||
|
chatInputGuide
|
||||||
};
|
};
|
||||||
|
|
||||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
||||||
|
|||||||
@ -1,98 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@ -12,12 +12,12 @@ import { ChatBoxInputFormType } from '../type.d';
|
|||||||
|
|
||||||
const VariableInput = ({
|
const VariableInput = ({
|
||||||
appAvatar,
|
appAvatar,
|
||||||
variableNodes,
|
variableList,
|
||||||
chatForm,
|
chatForm,
|
||||||
onSubmitVariables
|
onSubmitVariables
|
||||||
}: {
|
}: {
|
||||||
appAvatar?: string;
|
appAvatar?: string;
|
||||||
variableNodes: VariableItemType[];
|
variableList: VariableItemType[];
|
||||||
onSubmitVariables: (e: Record<string, any>) => void;
|
onSubmitVariables: (e: Record<string, any>) => void;
|
||||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||||
}) => {
|
}) => {
|
||||||
@ -40,7 +40,7 @@ const VariableInput = ({
|
|||||||
bg={'white'}
|
bg={'white'}
|
||||||
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
|
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
|
||||||
>
|
>
|
||||||
{variableNodes.map((item) => (
|
{variableList.map((item) => (
|
||||||
<Box key={item.id} mb={4}>
|
<Box key={item.id} mb={4}>
|
||||||
<Box as={'label'} display={'inline-block'} position={'relative'} mb={1}>
|
<Box as={'label'} display={'inline-block'} position={'relative'} mb={1}>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ import type {
|
|||||||
ChatBoxInputType,
|
ChatBoxInputType,
|
||||||
ChatBoxInputFormType
|
ChatBoxInputFormType
|
||||||
} from './type.d';
|
} from './type.d';
|
||||||
import MessageInput from './MessageInput';
|
import ChatInput from './Input/ChatInput';
|
||||||
import ChatBoxDivider from '../core/chat/Divider';
|
import ChatBoxDivider from '../core/chat/Divider';
|
||||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
@ -59,6 +59,7 @@ import ChatItem from './components/ChatItem';
|
|||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation } from 'ahooks';
|
||||||
|
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||||
|
|
||||||
const ResponseTags = dynamic(() => import('./ResponseTags'));
|
const ResponseTags = dynamic(() => import('./ResponseTags'));
|
||||||
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
|
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
|
||||||
@ -81,7 +82,7 @@ type Props = OutLinkChatAuthProps & {
|
|||||||
showEmptyIntro?: boolean;
|
showEmptyIntro?: boolean;
|
||||||
appAvatar?: string;
|
appAvatar?: string;
|
||||||
userAvatar?: string;
|
userAvatar?: string;
|
||||||
userGuideModule?: StoreNodeItemType;
|
chatConfig?: AppChatConfigType;
|
||||||
showFileSelector?: boolean;
|
showFileSelector?: boolean;
|
||||||
active?: boolean; // can use
|
active?: boolean; // can use
|
||||||
appId: string;
|
appId: string;
|
||||||
@ -149,7 +150,7 @@ const ChatBox = (
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
welcomeText,
|
welcomeText,
|
||||||
variableNodes,
|
variableList,
|
||||||
questionGuide,
|
questionGuide,
|
||||||
startSegmentedAudio,
|
startSegmentedAudio,
|
||||||
finishSegmentedAudio,
|
finishSegmentedAudio,
|
||||||
@ -174,8 +175,8 @@ const ChatBox = (
|
|||||||
|
|
||||||
/* variable */
|
/* variable */
|
||||||
const filterVariableNodes = useCreation(
|
const filterVariableNodes = useCreation(
|
||||||
() => variableNodes.filter((item) => item.type !== VariableInputEnum.custom),
|
() => variableList.filter((item) => item.type !== VariableInputEnum.custom),
|
||||||
[variableNodes]
|
[variableList]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 滚动到底部
|
// 滚动到底部
|
||||||
@ -390,9 +391,9 @@ const ChatBox = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete invalid variables, 只保留在 variableNodes 中的变量
|
// delete invalid variables, 只保留在 variableList 中的变量
|
||||||
const requestVariables: Record<string, any> = {};
|
const requestVariables: Record<string, any> = {};
|
||||||
variableNodes?.forEach((item) => {
|
variableList?.forEach((item) => {
|
||||||
requestVariables[item.key] = variables[item.key] || '';
|
requestVariables[item.key] = variables[item.key] || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -566,7 +567,7 @@ const ChatBox = (
|
|||||||
startSegmentedAudio,
|
startSegmentedAudio,
|
||||||
t,
|
t,
|
||||||
toast,
|
toast,
|
||||||
variableNodes
|
variableList
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -907,7 +908,7 @@ const ChatBox = (
|
|||||||
{!!filterVariableNodes?.length && (
|
{!!filterVariableNodes?.length && (
|
||||||
<VariableInput
|
<VariableInput
|
||||||
appAvatar={appAvatar}
|
appAvatar={appAvatar}
|
||||||
variableNodes={filterVariableNodes}
|
variableList={filterVariableNodes}
|
||||||
chatForm={chatForm}
|
chatForm={chatForm}
|
||||||
onSubmitVariables={(data) => {
|
onSubmitVariables={(data) => {
|
||||||
setValue('chatStarted', true);
|
setValue('chatStarted', true);
|
||||||
@ -1000,7 +1001,7 @@ const ChatBox = (
|
|||||||
</Box>
|
</Box>
|
||||||
{/* message input */}
|
{/* message input */}
|
||||||
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && (
|
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && (
|
||||||
<MessageInput
|
<ChatInput
|
||||||
onSendMessage={sendPrompt}
|
onSendMessage={sendPrompt}
|
||||||
onStop={() => chatController.current?.abort('stop')}
|
onStop={() => chatController.current?.abort('stop')}
|
||||||
TextareaDom={TextareaDom}
|
TextareaDom={TextareaDom}
|
||||||
|
|||||||
@ -13,10 +13,19 @@ import Auth from './auth';
|
|||||||
|
|
||||||
const Navbar = dynamic(() => import('./navbar'));
|
const Navbar = dynamic(() => import('./navbar'));
|
||||||
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
||||||
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
|
const UpdateInviteModal = dynamic(
|
||||||
const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'));
|
() => import('@/components/support/user/team/UpdateInviteModal'),
|
||||||
const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal'));
|
{ ssr: false }
|
||||||
const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform'));
|
);
|
||||||
|
const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
|
const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal'), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
|
const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform'), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
|
|
||||||
const pcUnShowLayoutRoute: Record<string, boolean> = {
|
const pcUnShowLayoutRoute: Record<string, boolean> = {
|
||||||
'/': true,
|
'/': true,
|
||||||
@ -114,12 +123,17 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{!!userInfo && <UpdateInviteModal />}
|
{feConfigs?.isPlus && (
|
||||||
{isNotSufficientModal && !isHideNavbar && <NotSufficientModal />}
|
<>
|
||||||
{!!userInfo && <SystemMsgModal />}
|
{!!userInfo && <UpdateInviteModal />}
|
||||||
{!!userInfo && importantInforms.length > 0 && (
|
{isNotSufficientModal && !isHideNavbar && <NotSufficientModal />}
|
||||||
<ImportantInform informs={importantInforms} refetch={refetchUnRead} />
|
{!!userInfo && <SystemMsgModal />}
|
||||||
|
{!!userInfo && importantInforms.length > 0 && (
|
||||||
|
<ImportantInform informs={importantInforms} refetch={refetchUnRead} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Loading loading={loading} zIndex={999999} />
|
<Loading loading={loading} zIndex={999999} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,24 +3,28 @@ import { Flex, Input, InputProps } from '@chakra-ui/react';
|
|||||||
|
|
||||||
interface Props extends InputProps {
|
interface Props extends InputProps {
|
||||||
leftIcon?: React.ReactNode;
|
leftIcon?: React.ReactNode;
|
||||||
|
rightIcon?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MyInput = ({ leftIcon, ...props }: Props) => {
|
const MyInput = ({ leftIcon, rightIcon, ...props }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Flex position={'relative'} alignItems={'center'}>
|
<Flex h={'100%'} position={'relative'} alignItems={'center'}>
|
||||||
<Input w={'100%'} pl={leftIcon ? '30px !important' : 3} {...props} />
|
<Input
|
||||||
|
w={'100%'}
|
||||||
|
pl={leftIcon ? '34px !important' : 3}
|
||||||
|
pr={rightIcon ? '34px !important' : 3}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
{leftIcon && (
|
{leftIcon && (
|
||||||
<Flex
|
<Flex alignItems={'center'} position={'absolute'} left={3} w={'20px'} zIndex={10}>
|
||||||
alignItems={'center'}
|
|
||||||
position={'absolute'}
|
|
||||||
left={3}
|
|
||||||
w={'20px'}
|
|
||||||
zIndex={10}
|
|
||||||
transform={'translateY(1.5px)'}
|
|
||||||
>
|
|
||||||
{leftIcon}
|
{leftIcon}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
{rightIcon && (
|
||||||
|
<Flex alignItems={'center'} position={'absolute'} right={3} w={'20px'} zIndex={10}>
|
||||||
|
{rightIcon}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,479 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -92,20 +92,15 @@ const ScheduledTriggerConfig = ({
|
|||||||
value,
|
value,
|
||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
value: AppScheduledTriggerConfigType | null;
|
value?: AppScheduledTriggerConfigType;
|
||||||
onChange: (e: AppScheduledTriggerConfigType | null) => void;
|
onChange: (e?: AppScheduledTriggerConfigType) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const { register, setValue, watch } = useForm<AppScheduledTriggerConfigType>({
|
|
||||||
defaultValues: {
|
const timezone = value?.timezone;
|
||||||
cronString: value?.cronString || '',
|
const cronString = value?.cronString;
|
||||||
timezone: value?.timezone,
|
const defaultPrompt = value?.defaultPrompt || '';
|
||||||
defaultPrompt: value?.defaultPrompt || ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const timezone = watch('timezone');
|
|
||||||
const cronString = watch('cronString');
|
|
||||||
|
|
||||||
const cronSelectList = useRef<MultipleSelectProps['list']>([
|
const cronSelectList = useRef<MultipleSelectProps['list']>([
|
||||||
{
|
{
|
||||||
@ -130,15 +125,39 @@ const ScheduledTriggerConfig = ({
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const onUpdate = useCallback(
|
||||||
|
({
|
||||||
|
cronString,
|
||||||
|
timezone,
|
||||||
|
defaultPrompt
|
||||||
|
}: {
|
||||||
|
cronString?: string;
|
||||||
|
timezone?: string;
|
||||||
|
defaultPrompt?: string;
|
||||||
|
}) => {
|
||||||
|
if (!cronString) {
|
||||||
|
onChange(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
cronString,
|
||||||
|
timezone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
defaultPrompt: defaultPrompt || ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[onChange, value]
|
||||||
|
);
|
||||||
|
|
||||||
/* cron string to config field */
|
/* cron string to config field */
|
||||||
const cronConfig = useMemo(() => {
|
const cronConfig = useMemo(() => {
|
||||||
if (!cronString) {
|
if (!cronString) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
const cronField = cronParser2Fields(cronString);
|
const cronField = cronParser2Fields(cronString);
|
||||||
|
|
||||||
if (!cronField) {
|
if (!cronField) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cronField.dayOfMonth.length !== 31) {
|
if (cronField.dayOfMonth.length !== 31) {
|
||||||
@ -169,19 +188,22 @@ const ScheduledTriggerConfig = ({
|
|||||||
|
|
||||||
const cronConfig2cronString = useCallback(
|
const cronConfig2cronString = useCallback(
|
||||||
(e: CronFieldType) => {
|
(e: CronFieldType) => {
|
||||||
if (e[0] === CronJobTypeEnum.month) {
|
const str = (() => {
|
||||||
setValue('cronString', `0 ${e[2]} ${e[1]} * *`);
|
if (e[0] === CronJobTypeEnum.month) {
|
||||||
} else if (e[0] === CronJobTypeEnum.week) {
|
return `0 ${e[2]} ${e[1]} * *`;
|
||||||
setValue('cronString', `0 ${e[2]} * * ${e[1]}`);
|
} else if (e[0] === CronJobTypeEnum.week) {
|
||||||
} else if (e[0] === CronJobTypeEnum.day) {
|
return `0 ${e[2]} * * ${e[1]}`;
|
||||||
setValue('cronString', `0 ${e[1]} * * *`);
|
} else if (e[0] === CronJobTypeEnum.day) {
|
||||||
} else if (e[0] === CronJobTypeEnum.interval) {
|
return `0 ${e[1]} * * *`;
|
||||||
setValue('cronString', `0 */${e[1]} * * *`);
|
} else if (e[0] === CronJobTypeEnum.interval) {
|
||||||
} else {
|
return `0 */${e[1]} * * *`;
|
||||||
setValue('cronString', '');
|
} else {
|
||||||
}
|
return '';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
onUpdate({ cronString: str });
|
||||||
},
|
},
|
||||||
[setValue]
|
[onUpdate]
|
||||||
);
|
);
|
||||||
|
|
||||||
// cron config to show label
|
// cron config to show label
|
||||||
@ -216,22 +238,9 @@ const ScheduledTriggerConfig = ({
|
|||||||
return t('common.Not open');
|
return t('common.Not open');
|
||||||
}, [cronField, isOpenSchedule, t]);
|
}, [cronField, isOpenSchedule, t]);
|
||||||
|
|
||||||
// update value
|
|
||||||
watch((data) => {
|
|
||||||
if (!data.cronString) {
|
|
||||||
onChange(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onChange({
|
|
||||||
cronString: data.cronString,
|
|
||||||
timezone: data.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
||||||
defaultPrompt: data.defaultPrompt || ''
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!value?.timezone) {
|
if (!value?.timezone) {
|
||||||
setValue('timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
onUpdate({ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone });
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -272,9 +281,9 @@ const ScheduledTriggerConfig = ({
|
|||||||
isChecked={isOpenSchedule}
|
isChecked={isOpenSchedule}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setValue('cronString', defaultCronString);
|
onUpdate({ cronString: defaultCronString });
|
||||||
} else {
|
} else {
|
||||||
setValue('cronString', '');
|
onUpdate({ cronString: '' });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -300,7 +309,7 @@ const ScheduledTriggerConfig = ({
|
|||||||
<TimezoneSelect
|
<TimezoneSelect
|
||||||
value={timezone}
|
value={timezone}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValue('timezone', e);
|
onUpdate({ timezone: e });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -308,10 +317,13 @@ const ScheduledTriggerConfig = ({
|
|||||||
<Box mt={5}>
|
<Box mt={5}>
|
||||||
<Box>{t('core.app.schedule.Default prompt')}</Box>
|
<Box>{t('core.app.schedule.Default prompt')}</Box>
|
||||||
<Textarea
|
<Textarea
|
||||||
{...register('defaultPrompt')}
|
value={defaultPrompt}
|
||||||
rows={8}
|
rows={8}
|
||||||
bg={'myGray.50'}
|
bg={'myGray.50'}
|
||||||
placeholder={t('core.app.schedule.Default prompt placeholder')}
|
placeholder={t('core.app.schedule.Default prompt placeholder')}
|
||||||
|
onChange={(e) => {
|
||||||
|
onUpdate({ defaultPrompt: e.target.value });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
@ -323,13 +335,13 @@ const ScheduledTriggerConfig = ({
|
|||||||
}, [
|
}, [
|
||||||
cronConfig2cronString,
|
cronConfig2cronString,
|
||||||
cronField,
|
cronField,
|
||||||
|
defaultPrompt,
|
||||||
formatLabel,
|
formatLabel,
|
||||||
isOpen,
|
isOpen,
|
||||||
isOpenSchedule,
|
isOpenSchedule,
|
||||||
onClose,
|
onClose,
|
||||||
onOpen,
|
onOpen,
|
||||||
register,
|
onUpdate,
|
||||||
setValue,
|
|
||||||
t,
|
t,
|
||||||
timezone
|
timezone
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -11,12 +11,13 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import MySlider from '@/components/Slider';
|
import MySlider from '@/components/Slider';
|
||||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||||
|
import { defaultTTSConfig } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
const TTSSelect = ({
|
const TTSSelect = ({
|
||||||
value,
|
value = defaultTTSConfig,
|
||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
value: AppTTSConfigType;
|
value?: AppTTSConfigType;
|
||||||
onChange: (e: AppTTSConfigType) => void;
|
onChange: (e: AppTTSConfigType) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@ -39,10 +39,10 @@ import MyRadio from '@/components/common/MyRadio';
|
|||||||
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
|
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
|
||||||
|
|
||||||
const VariableEdit = ({
|
const VariableEdit = ({
|
||||||
variables,
|
variables = [],
|
||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
variables: VariableItemType[];
|
variables?: VariableItemType[];
|
||||||
onChange: (data: VariableItemType[]) => void;
|
onChange: (data: VariableItemType[]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@ -6,14 +6,15 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import type { AppWhisperConfigType } from '@fastgpt/global/core/app/type.d';
|
import type { AppWhisperConfigType } from '@fastgpt/global/core/app/type.d';
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
|
import { defaultWhisperConfig } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
const WhisperConfig = ({
|
const WhisperConfig = ({
|
||||||
isOpenAudio,
|
isOpenAudio,
|
||||||
value,
|
value = defaultWhisperConfig,
|
||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
isOpenAudio: boolean;
|
isOpenAudio: boolean;
|
||||||
value: AppWhisperConfigType;
|
value?: AppWhisperConfigType;
|
||||||
onChange: (e: AppWhisperConfigType) => void;
|
onChange: (e: AppWhisperConfigType) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@ -0,0 +1,482 @@
|
|||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
|
ModalBody,
|
||||||
|
useDisclosure,
|
||||||
|
Switch,
|
||||||
|
Textarea,
|
||||||
|
Checkbox,
|
||||||
|
HStack
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import type { ChatInputGuideConfigType } from '@fastgpt/global/core/app/type.d';
|
||||||
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
|
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 {
|
||||||
|
delChatInputGuide,
|
||||||
|
getChatInputGuideList,
|
||||||
|
getCountChatInputGuideTotal,
|
||||||
|
postChatInputGuides,
|
||||||
|
putChatInputGuide
|
||||||
|
} from '@/web/core/chat/inputGuide/api';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
|
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||||
|
import { readCsvRawText } from '@fastgpt/web/common/file/utils';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
import HighlightText from '@fastgpt/web/components/common/String/HighlightText';
|
||||||
|
import { defaultChatInputGuideConfig } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
|
const csvTemplate = `"第一列内容"
|
||||||
|
"只会将第一列内容导入,其余列会被忽略"
|
||||||
|
"AIGC发展分为几个阶段?"`;
|
||||||
|
|
||||||
|
const InputGuideConfig = ({
|
||||||
|
appId,
|
||||||
|
value = defaultChatInputGuideConfig,
|
||||||
|
onChange
|
||||||
|
}: {
|
||||||
|
appId: string;
|
||||||
|
value?: ChatInputGuideConfigType;
|
||||||
|
onChange: (e: ChatInputGuideConfigType) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { chatT, commonT } = useI18n();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const {
|
||||||
|
isOpen: isOpenLexiconConfig,
|
||||||
|
onOpen: onOpenLexiconConfig,
|
||||||
|
onClose: onCloseLexiconConfig
|
||||||
|
} = useDisclosure();
|
||||||
|
const isOpenQuestionGuide = value.open;
|
||||||
|
|
||||||
|
const { data } = useQuery(
|
||||||
|
[appId, isOpenLexiconConfig],
|
||||||
|
() => {
|
||||||
|
return getCountChatInputGuideTotal({
|
||||||
|
appId
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!appId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const total = data?.total || 0;
|
||||||
|
|
||||||
|
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'} />
|
||||||
|
<HStack>
|
||||||
|
<Box>{chatT('Input guide')}</Box>
|
||||||
|
<QuestionTip label={chatT('Input guide tip')} />
|
||||||
|
</HStack>
|
||||||
|
<Box flex={1} />
|
||||||
|
<MyTooltip label={chatT('Config input guide')}>
|
||||||
|
<Button
|
||||||
|
variant={'transparentBase'}
|
||||||
|
iconSpacing={1}
|
||||||
|
size={'sm'}
|
||||||
|
mr={'-5px'}
|
||||||
|
onClick={onOpen}
|
||||||
|
>
|
||||||
|
{formLabel}
|
||||||
|
</Button>
|
||||||
|
</MyTooltip>
|
||||||
|
<MyModal
|
||||||
|
title={chatT('Input guide')}
|
||||||
|
iconSrc="core/app/inputGuides"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<ModalBody px={[5, 16]} py={[4, 8]} w={'500px'}>
|
||||||
|
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||||
|
{t('Is open')}
|
||||||
|
<Switch
|
||||||
|
isChecked={isOpenQuestionGuide}
|
||||||
|
size={'lg'}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
open: e.target.checked
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{isOpenQuestionGuide && (
|
||||||
|
<>
|
||||||
|
<Flex mt={8} alignItems={'center'}>
|
||||||
|
{chatT('Input guide lexicon')}
|
||||||
|
<Box fontSize={'xs'} px={2} bg={'myGray.100'} ml={1} rounded={'full'}>
|
||||||
|
{total}
|
||||||
|
</Box>
|
||||||
|
<Box flex={'1 0 0'} />
|
||||||
|
<Button
|
||||||
|
variant={'whiteBase'}
|
||||||
|
size={'sm'}
|
||||||
|
leftIcon={<MyIcon boxSize={'4'} name={'common/settingLight'} />}
|
||||||
|
onClick={() => {
|
||||||
|
onOpenLexiconConfig();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chatT('Config input guide lexicon')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<>
|
||||||
|
<Flex mt={8} alignItems={'center'}>
|
||||||
|
{chatT('Custom input guide url')}
|
||||||
|
<Flex
|
||||||
|
onClick={() => window.open(getDocPath('/docs/course/chat_input_guide'))}
|
||||||
|
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>
|
||||||
|
</MyModal>
|
||||||
|
|
||||||
|
{isOpenLexiconConfig && <LexiconConfigModal appId={appId} onClose={onCloseLexiconConfig} />}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(InputGuideConfig);
|
||||||
|
|
||||||
|
const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () => void }) => {
|
||||||
|
const { chatT, commonT } = useI18n();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
|
fileType: '.csv'
|
||||||
|
});
|
||||||
|
const [newData, setNewData] = useState<string>();
|
||||||
|
|
||||||
|
const [selectedRows, setSelectedRows] = useState<string[]>([]);
|
||||||
|
const [editDataId, setEditDataId] = useState<string>();
|
||||||
|
|
||||||
|
const [searchKey, setSearchKey] = useState('');
|
||||||
|
|
||||||
|
const {
|
||||||
|
list,
|
||||||
|
setData,
|
||||||
|
ScrollList,
|
||||||
|
isLoading: isRequesting,
|
||||||
|
fetchData,
|
||||||
|
scroll2Top
|
||||||
|
} = useScrollPagination(getChatInputGuideList, {
|
||||||
|
refreshDeps: [searchKey],
|
||||||
|
debounceWait: 300,
|
||||||
|
|
||||||
|
itemHeight: 46,
|
||||||
|
overscan: 20,
|
||||||
|
|
||||||
|
pageSize: 20,
|
||||||
|
defaultParams: {
|
||||||
|
appId,
|
||||||
|
searchKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { run: createNewData, loading: isCreating } = useRequest2(
|
||||||
|
(textList: string[]) => {
|
||||||
|
if (textList.filter(Boolean).length === 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
scroll2Top();
|
||||||
|
return postChatInputGuides({
|
||||||
|
appId,
|
||||||
|
textList
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.insertLength < textList.length) {
|
||||||
|
toast({
|
||||||
|
status: 'warning',
|
||||||
|
title: chatT('Insert input guide, Some data already exists', { len: res.insertLength })
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
status: 'success',
|
||||||
|
title: t('common.Add Success')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fetchData(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
onSuccess() {
|
||||||
|
setNewData(undefined);
|
||||||
|
},
|
||||||
|
errorToast: t('error.Create failed')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const onUpdateData = ({ text, dataId }: { text: string; dataId: string }) => {
|
||||||
|
setData((state) =>
|
||||||
|
state.map((item) => {
|
||||||
|
if (item._id === dataId) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
putChatInputGuide({
|
||||||
|
appId,
|
||||||
|
text,
|
||||||
|
dataId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditDataId(undefined);
|
||||||
|
};
|
||||||
|
const onDeleteData = (dataIdList: string[]) => {
|
||||||
|
setData((state) => state.filter((item) => !dataIdList.includes(item._id)));
|
||||||
|
delChatInputGuide({
|
||||||
|
appId,
|
||||||
|
dataIdList
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectFile = async (files: File[]) => {
|
||||||
|
const file = files?.[0];
|
||||||
|
if (file) {
|
||||||
|
const list = await readCsvRawText({ file });
|
||||||
|
const textList = list.map((item) => item[0]?.trim() || '').filter(Boolean);
|
||||||
|
createNewData(textList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoading = isRequesting || isCreating;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
title={chatT('Config input guide lexicon title')}
|
||||||
|
iconSrc="core/app/inputGuides"
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
isLoading={isLoading}
|
||||||
|
h={'600px'}
|
||||||
|
w={'500px'}
|
||||||
|
>
|
||||||
|
<Flex gap={4} px={8} py={4} mb={4} alignItems={'center'} borderBottom={'base'}>
|
||||||
|
<Box flex={1}>
|
||||||
|
<MyInput
|
||||||
|
leftIcon={<MyIcon name={'common/searchLight'} boxSize={4} color={'myGray.500'} />}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
w={'full'}
|
||||||
|
h={9}
|
||||||
|
placeholder={commonT('common.Search')}
|
||||||
|
onChange={(e) => setSearchKey(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
onClick={onOpenSelectFile}
|
||||||
|
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={chatT('Csv input lexicon tip')} />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Box px={8}>
|
||||||
|
{/* button */}
|
||||||
|
<Flex mb={1} justifyContent={'space-between'}>
|
||||||
|
<Box flex={1} />
|
||||||
|
<Flex gap={4}>
|
||||||
|
<Button
|
||||||
|
variant={'whiteBase'}
|
||||||
|
display={selectedRows.length === 0 ? 'none' : 'flex'}
|
||||||
|
size={'sm'}
|
||||||
|
leftIcon={<MyIcon name={'delete'} boxSize={4} />}
|
||||||
|
onClick={() => {
|
||||||
|
onDeleteData(selectedRows);
|
||||||
|
setSelectedRows([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{commonT('common.Delete')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
display={selectedRows.length !== 0 ? 'none' : 'flex'}
|
||||||
|
onClick={() => {
|
||||||
|
setNewData('');
|
||||||
|
}}
|
||||||
|
size={'sm'}
|
||||||
|
leftIcon={<MyIcon name={'common/addLight'} boxSize={4} />}
|
||||||
|
>
|
||||||
|
{commonT('common.Add')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
{/* new data input */}
|
||||||
|
{newData !== undefined && (
|
||||||
|
<Box mt={5} ml={list.length > 0 ? 7 : 0}>
|
||||||
|
<MyInput
|
||||||
|
autoFocus
|
||||||
|
rightIcon={<MyIcon name={'save'} w={'14px'} cursor={'pointer'} />}
|
||||||
|
placeholder={chatT('New input guide lexicon')}
|
||||||
|
onBlur={(e) => {
|
||||||
|
createNewData([e.target.value.trim()]);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
createNewData([e.currentTarget.value.trim()]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<ScrollList
|
||||||
|
px={8}
|
||||||
|
flex={'1 0 0'}
|
||||||
|
EmptyChildren={<EmptyTip text={chatT('Chat input guide lexicon is empty')} />}
|
||||||
|
>
|
||||||
|
{list.map((data, index) => {
|
||||||
|
const item = data.data;
|
||||||
|
|
||||||
|
const selected = selectedRows.includes(item._id);
|
||||||
|
const edited = editDataId === item._id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={index}
|
||||||
|
alignItems={'center'}
|
||||||
|
h={10}
|
||||||
|
mt={3}
|
||||||
|
_hover={{
|
||||||
|
'& .icon-list': {
|
||||||
|
display: 'flex'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
size={'lg'}
|
||||||
|
mr={2}
|
||||||
|
isChecked={selected}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedRows([...selectedRows, item._id]);
|
||||||
|
} else {
|
||||||
|
setSelectedRows(selectedRows.filter((id) => id !== item._id));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{edited ? (
|
||||||
|
<Box h={'full'} flex={'1 0 0'}>
|
||||||
|
<MyInput
|
||||||
|
autoFocus
|
||||||
|
defaultValue={item.text}
|
||||||
|
rightIcon={<MyIcon name={'save'} boxSize={4} cursor={'pointer'} />}
|
||||||
|
onBlur={(e) => {
|
||||||
|
onUpdateData({
|
||||||
|
text: e.target.value.trim(),
|
||||||
|
dataId: item._id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
onUpdateData({
|
||||||
|
text: e.currentTarget.value.trim(),
|
||||||
|
dataId: item._id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
h={'40px'}
|
||||||
|
w={0}
|
||||||
|
flex={'1 0 0'}
|
||||||
|
rounded={'md'}
|
||||||
|
px={4}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
alignItems={'center'}
|
||||||
|
border={'base'}
|
||||||
|
_hover={{ borderColor: 'primary.300' }}
|
||||||
|
>
|
||||||
|
<Box className="textEllipsis" w={0} flex={'1 0 0'}>
|
||||||
|
<HighlightText rawText={item.text} matchText={searchKey} />
|
||||||
|
</Box>
|
||||||
|
{selectedRows.length === 0 && (
|
||||||
|
<Box className="icon-list" display={'none'}>
|
||||||
|
<MyIcon
|
||||||
|
name={'edit'}
|
||||||
|
boxSize={4}
|
||||||
|
mr={2}
|
||||||
|
color={'myGray.600'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => setEditDataId(item._id)}
|
||||||
|
/>
|
||||||
|
<MyIcon
|
||||||
|
name={'delete'}
|
||||||
|
boxSize={4}
|
||||||
|
color={'myGray.600'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ color: 'red.600' }}
|
||||||
|
onClick={() => onDeleteData([item._id])}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ScrollList>
|
||||||
|
|
||||||
|
<File onSelect={onSelectFile} />
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -29,7 +29,8 @@ 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';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
export type ChatTestComponentRef = {
|
export type ChatTestComponentRef = {
|
||||||
resetChatTest: () => void;
|
resetChatTest: () => void;
|
||||||
@ -37,13 +38,11 @@ export type ChatTestComponentRef = {
|
|||||||
|
|
||||||
const ChatTest = (
|
const ChatTest = (
|
||||||
{
|
{
|
||||||
app,
|
|
||||||
isOpen,
|
isOpen,
|
||||||
nodes = [],
|
nodes = [],
|
||||||
edges = [],
|
edges = [],
|
||||||
onClose
|
onClose
|
||||||
}: {
|
}: {
|
||||||
app: AppSchema;
|
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
nodes?: StoreNodeItemType[];
|
nodes?: StoreNodeItemType[];
|
||||||
edges?: StoreEdgeItemType[];
|
edges?: StoreEdgeItemType[];
|
||||||
@ -54,6 +53,7 @@ const ChatTest = (
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const startChat = useCallback(
|
const startChat = useCallback(
|
||||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||||
@ -70,8 +70,8 @@ const ChatTest = (
|
|||||||
nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
||||||
edges: initWorkflowEdgeStatus(edges),
|
edges: initWorkflowEdgeStatus(edges),
|
||||||
variables,
|
variables,
|
||||||
appId: app._id,
|
appId: appDetail._id,
|
||||||
appName: `调试-${app.name}`,
|
appName: `调试-${appDetail.name}`,
|
||||||
mode: 'test'
|
mode: 'test'
|
||||||
},
|
},
|
||||||
onMessage: generatingMessage,
|
onMessage: generatingMessage,
|
||||||
@ -80,7 +80,7 @@ const ChatTest = (
|
|||||||
|
|
||||||
return { responseText, responseData, newVariables };
|
return { responseText, responseData, newVariables };
|
||||||
},
|
},
|
||||||
[app._id, app.name, edges, nodes]
|
[appDetail._id, appDetail.name, edges, nodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@ -139,11 +139,11 @@ const ChatTest = (
|
|||||||
<Box flex={1}>
|
<Box flex={1}>
|
||||||
<ChatBox
|
<ChatBox
|
||||||
ref={ChatBoxRef}
|
ref={ChatBoxRef}
|
||||||
appId={app._id}
|
appId={appDetail._id}
|
||||||
appAvatar={app.avatar}
|
appAvatar={appDetail.avatar}
|
||||||
userAvatar={userInfo?.avatar}
|
userAvatar={userInfo?.avatar}
|
||||||
showMarkIcon
|
showMarkIcon
|
||||||
userGuideModule={getGuideModule(nodes)}
|
chatConfig={appDetail.chatConfig}
|
||||||
showFileSelector={checkChatSupportSelectFileByModules(nodes)}
|
showFileSelector={checkChatSupportSelectFileByModules(nodes)}
|
||||||
onStartChat={startChat}
|
onStartChat={startChat}
|
||||||
onDelMessage={() => {}}
|
onDelMessage={() => {}}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useCallback, useState } from 'react';
|
|||||||
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
|
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { flowNode2StoreNodes } from '../../utils';
|
import { uiWorkflow2StoreWorkflow } from '../../utils';
|
||||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
@ -52,7 +52,7 @@ export const useDebug = () => {
|
|||||||
|
|
||||||
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
|
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
|
||||||
if (!checkResults) {
|
if (!checkResults) {
|
||||||
const storeNodes = flowNode2StoreNodes({ nodes, edges });
|
const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges });
|
||||||
|
|
||||||
return JSON.stringify(storeNodes);
|
return JSON.stringify(storeNodes);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import { useContextSelector } from 'use-context-selector';
|
|||||||
import { WorkflowContext } from '../../../context';
|
import { WorkflowContext } from '../../../context';
|
||||||
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
const CurlImportModal = dynamic(() => import('./CurlImportModal'));
|
const CurlImportModal = dynamic(() => import('./CurlImportModal'));
|
||||||
|
|
||||||
export const HttpHeaders = [
|
export const HttpHeaders = [
|
||||||
@ -251,6 +252,7 @@ export function RenderHttpProps({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
|
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value;
|
const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value;
|
||||||
const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
|
const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
|
||||||
@ -262,7 +264,11 @@ export function RenderHttpProps({
|
|||||||
|
|
||||||
// get variable
|
// get variable
|
||||||
const variables = useMemo(() => {
|
const variables = useMemo(() => {
|
||||||
const globalVariables = getWorkflowGlobalVariables(nodeList, t);
|
const globalVariables = getWorkflowGlobalVariables({
|
||||||
|
nodes: nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
|
t
|
||||||
|
});
|
||||||
|
|
||||||
const moduleVariables = formatEditorVariablePickerIcon(
|
const moduleVariables = formatEditorVariablePickerIcon(
|
||||||
inputs
|
inputs
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import { SourceHandle } from '../render/Handle';
|
|||||||
import { Position, useReactFlow } from 'reactflow';
|
import { Position, useReactFlow } from 'reactflow';
|
||||||
import { getReferenceDataValueType } from '@/web/core/workflow/utils';
|
import { getReferenceDataValueType } from '@/web/core/workflow/utils';
|
||||||
import DragIcon from '@fastgpt/web/components/common/DndDrag/DragIcon';
|
import DragIcon from '@fastgpt/web/components/common/DndDrag/DragIcon';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const ListItem = ({
|
const ListItem = ({
|
||||||
provided,
|
provided,
|
||||||
@ -342,15 +343,17 @@ const ConditionSelect = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
|
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||||
|
|
||||||
// get condition type
|
// get condition type
|
||||||
const valueType = useMemo(() => {
|
const valueType = useMemo(() => {
|
||||||
return getReferenceDataValueType({
|
return getReferenceDataValueType({
|
||||||
variable,
|
variable,
|
||||||
nodeList,
|
nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
t
|
t
|
||||||
});
|
});
|
||||||
}, [nodeList, t, variable]);
|
}, [appDetail.chatConfig, nodeList, t, variable]);
|
||||||
|
|
||||||
const conditionList = useMemo(() => {
|
const conditionList = useMemo(() => {
|
||||||
if (valueType === WorkflowIOValueTypeEnum.string) return stringConditionList;
|
if (valueType === WorkflowIOValueTypeEnum.string) return stringConditionList;
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import React, { useMemo, useTransition } from 'react';
|
import React, { Dispatch, useMemo, useTransition } from 'react';
|
||||||
import { NodeProps } from 'reactflow';
|
import { NodeProps } from 'reactflow';
|
||||||
import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react';
|
import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react';
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
|
||||||
import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
|
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 InputGuideConfig from '@/components/core/chat/appConfig/InputGuideConfig';
|
||||||
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
import { getAppChatConfig } 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';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
@ -19,12 +18,35 @@ import NodeCard from './render/NodeCard';
|
|||||||
import ScheduledTriggerConfig from '@/components/core/app/ScheduledTriggerConfig';
|
import ScheduledTriggerConfig from '@/components/core/app/ScheduledTriggerConfig';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext } from '../../context';
|
import { WorkflowContext } from '../../context';
|
||||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
import { AppChatConfigType, AppDetailType, 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 { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
|
type ComponentProps = {
|
||||||
|
chatConfig: AppChatConfigType;
|
||||||
|
setAppDetail: Dispatch<React.SetStateAction<AppDetailType>>;
|
||||||
|
};
|
||||||
|
|
||||||
const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
|
const chatConfig = useMemo<AppChatConfigType>(() => {
|
||||||
|
return getAppChatConfig({
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
|
systemConfigNode: data,
|
||||||
|
isPublicFetch: true
|
||||||
|
});
|
||||||
|
}, [data, appDetail]);
|
||||||
|
|
||||||
|
const componentsProps = useMemo(
|
||||||
|
() => ({
|
||||||
|
chatConfig,
|
||||||
|
setAppDetail
|
||||||
|
}),
|
||||||
|
[chatConfig, setAppDetail]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -40,24 +62,24 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
{...data}
|
{...data}
|
||||||
>
|
>
|
||||||
<Box px={4} py={'10px'} position={'relative'} borderRadius={'md'} className="nodrag">
|
<Box px={4} py={'10px'} position={'relative'} borderRadius={'md'} className="nodrag">
|
||||||
<WelcomeText data={data} />
|
<WelcomeText {...componentsProps} />
|
||||||
<Box pt={4}>
|
<Box pt={4}>
|
||||||
<ChatStartVariable data={data} />
|
<ChatStartVariable {...componentsProps} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||||
<TTSGuide data={data} />
|
<TTSGuide {...componentsProps} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||||
<WhisperGuide data={data} />
|
<WhisperGuide {...componentsProps} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||||
<QuestionGuide data={data} />
|
<QuestionGuide {...componentsProps} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||||
<ScheduledTrigger data={data} />
|
<ScheduledTrigger {...componentsProps} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||||
<QuestionInputGuide data={data} />
|
<QuestionInputGuide {...componentsProps} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</NodeCard>
|
</NodeCard>
|
||||||
@ -67,13 +89,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
|
|
||||||
export default React.memo(NodeUserGuide);
|
export default React.memo(NodeUserGuide);
|
||||||
|
|
||||||
function WelcomeText({ data }: { data: FlowNodeItemType }) {
|
function WelcomeText({ chatConfig: { welcomeText }, setAppDetail }: ComponentProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { inputs, nodeId } = data;
|
|
||||||
const [, startTst] = useTransition();
|
const [, startTst] = useTransition();
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
|
||||||
|
|
||||||
const welcomeText = inputs.find((item) => item.key === NodeInputKeyEnum.welcomeText);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -84,181 +102,136 @@ function WelcomeText({ data }: { data: FlowNodeItemType }) {
|
|||||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
{welcomeText && (
|
<Textarea
|
||||||
<Textarea
|
className="nodrag"
|
||||||
className="nodrag"
|
rows={6}
|
||||||
rows={6}
|
fontSize={'12px'}
|
||||||
fontSize={'12px'}
|
resize={'both'}
|
||||||
resize={'both'}
|
defaultValue={welcomeText}
|
||||||
defaultValue={welcomeText.value}
|
bg={'myWhite.500'}
|
||||||
bg={'myWhite.500'}
|
placeholder={t(welcomeTextTip)}
|
||||||
placeholder={t(welcomeTextTip)}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
startTst(() => {
|
||||||
startTst(() => {
|
setAppDetail((state) => ({
|
||||||
onChangeNode({
|
...state,
|
||||||
nodeId,
|
chatConfig: {
|
||||||
key: NodeInputKeyEnum.welcomeText,
|
...state.chatConfig,
|
||||||
type: 'updateInput',
|
welcomeText: e.target.value
|
||||||
value: {
|
}
|
||||||
...welcomeText,
|
}));
|
||||||
value: e.target.value
|
});
|
||||||
}
|
}}
|
||||||
});
|
/>
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatStartVariable({ data }: { data: FlowNodeItemType }) {
|
function ChatStartVariable({ chatConfig: { variables = [] }, setAppDetail }: ComponentProps) {
|
||||||
const { inputs, nodeId } = data;
|
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
|
||||||
|
|
||||||
const variables = useMemo(
|
|
||||||
() =>
|
|
||||||
(inputs.find((item) => item.key === NodeInputKeyEnum.variables)
|
|
||||||
?.value as VariableItemType[]) || [],
|
|
||||||
[inputs]
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateVariables = useMemoizedFn((value: VariableItemType[]) => {
|
const updateVariables = useMemoizedFn((value: VariableItemType[]) => {
|
||||||
// update system config node
|
setAppDetail((state) => ({
|
||||||
onChangeNode({
|
...state,
|
||||||
nodeId,
|
chatConfig: {
|
||||||
key: NodeInputKeyEnum.variables,
|
...state.chatConfig,
|
||||||
type: 'updateInput',
|
variables: value
|
||||||
value: {
|
|
||||||
...inputs.find((item) => item.key === NodeInputKeyEnum.variables),
|
|
||||||
value
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
|
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function QuestionGuide({ data }: { data: FlowNodeItemType }) {
|
function QuestionGuide({ chatConfig: { questionGuide = false }, setAppDetail }: ComponentProps) {
|
||||||
const { inputs, nodeId } = data;
|
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
|
||||||
|
|
||||||
const questionGuide = useMemo(
|
|
||||||
() =>
|
|
||||||
(inputs.find((item) => item.key === NodeInputKeyEnum.questionGuide)?.value as boolean) ||
|
|
||||||
false,
|
|
||||||
[inputs]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QGSwitch
|
<QGSwitch
|
||||||
isChecked={questionGuide}
|
isChecked={questionGuide}
|
||||||
size={'md'}
|
size={'md'}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.checked;
|
const value = e.target.checked;
|
||||||
onChangeNode({
|
setAppDetail((state) => ({
|
||||||
nodeId,
|
...state,
|
||||||
key: NodeInputKeyEnum.questionGuide,
|
chatConfig: {
|
||||||
type: 'updateInput',
|
...state.chatConfig,
|
||||||
value: {
|
questionGuide: value
|
||||||
...inputs.find((item) => item.key === NodeInputKeyEnum.questionGuide),
|
|
||||||
value
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TTSGuide({ data }: { data: FlowNodeItemType }) {
|
function TTSGuide({ chatConfig: { ttsConfig }, setAppDetail }: ComponentProps) {
|
||||||
const { inputs, nodeId } = data;
|
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
|
||||||
const { ttsConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TTSSelect
|
<TTSSelect
|
||||||
value={ttsConfig}
|
value={ttsConfig}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChangeNode({
|
setAppDetail((state) => ({
|
||||||
nodeId,
|
...state,
|
||||||
key: NodeInputKeyEnum.tts,
|
chatConfig: {
|
||||||
type: 'updateInput',
|
...state.chatConfig,
|
||||||
value: {
|
ttsConfig: e
|
||||||
...inputs.find((item) => item.key === NodeInputKeyEnum.tts),
|
|
||||||
value: e
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WhisperGuide({ data }: { data: FlowNodeItemType }) {
|
function WhisperGuide({ chatConfig: { whisperConfig, ttsConfig }, setAppDetail }: ComponentProps) {
|
||||||
const { inputs, nodeId } = data;
|
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WhisperConfig
|
<WhisperConfig
|
||||||
isOpenAudio={ttsConfig.type !== TTSTypeEnum.none}
|
isOpenAudio={ttsConfig?.type !== TTSTypeEnum.none}
|
||||||
value={whisperConfig}
|
value={whisperConfig}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChangeNode({
|
setAppDetail((state) => ({
|
||||||
nodeId,
|
...state,
|
||||||
key: NodeInputKeyEnum.whisper,
|
chatConfig: {
|
||||||
type: 'updateInput',
|
...state.chatConfig,
|
||||||
value: {
|
whisperConfig: e
|
||||||
...inputs.find((item) => item.key === NodeInputKeyEnum.whisper),
|
|
||||||
value: e
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ScheduledTrigger({ data }: { data: FlowNodeItemType }) {
|
function ScheduledTrigger({
|
||||||
const { inputs, nodeId } = data;
|
chatConfig: { scheduledTriggerConfig },
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
setAppDetail
|
||||||
const { scheduledTriggerConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
|
}: ComponentProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScheduledTriggerConfig
|
<ScheduledTriggerConfig
|
||||||
value={scheduledTriggerConfig}
|
value={scheduledTriggerConfig}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChangeNode({
|
setAppDetail((state) => ({
|
||||||
nodeId,
|
...state,
|
||||||
key: NodeInputKeyEnum.scheduleTrigger,
|
chatConfig: {
|
||||||
type: 'updateInput',
|
...state.chatConfig,
|
||||||
value: {
|
scheduledTriggerConfig: e
|
||||||
...inputs.find((item) => item.key === NodeInputKeyEnum.scheduleTrigger),
|
|
||||||
value: e
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function QuestionInputGuide({ data }: { data: FlowNodeItemType }) {
|
function QuestionInputGuide({ chatConfig: { chatInputGuide }, setAppDetail }: ComponentProps) {
|
||||||
const { inputs, nodeId } = data;
|
const appId = useContextSelector(WorkflowContext, (v) => v.appId);
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
|
||||||
const { questionGuideText } = splitGuideModule({ inputs } as StoreNodeItemType);
|
|
||||||
|
|
||||||
return (
|
return appId ? (
|
||||||
<QGuidesConfig
|
<InputGuideConfig
|
||||||
value={questionGuideText}
|
appId={appId}
|
||||||
|
value={chatInputGuide}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChangeNode({
|
setAppDetail((state) => ({
|
||||||
nodeId,
|
...state,
|
||||||
key: NodeInputKeyEnum.questionGuideText,
|
chatConfig: {
|
||||||
type: 'updateInput',
|
...state.chatConfig,
|
||||||
value: {
|
chatInputGuide: e
|
||||||
...inputs.find((item) => item.key === NodeInputKeyEnum.questionGuideText),
|
|
||||||
value: e
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
|
|||||||
import { ReferSelector, useReference } from './render/RenderInput/templates/Reference';
|
import { ReferSelector, useReference } from './render/RenderInput/templates/Reference';
|
||||||
import { getReferenceDataValueType } from '@/web/core/workflow/utils';
|
import { getReferenceDataValueType } from '@/web/core/workflow/utils';
|
||||||
import { isReferenceValue } from '@fastgpt/global/core/workflow/utils';
|
import { isReferenceValue } from '@fastgpt/global/core/workflow/utils';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
const { inputs = [], nodeId } = data;
|
const { inputs = [], nodeId } = data;
|
||||||
@ -39,6 +40,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
|||||||
|
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
|
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||||
|
|
||||||
const updateList = useMemo(
|
const updateList = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -85,6 +87,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
|||||||
const valueType = getReferenceDataValueType({
|
const valueType = getReferenceDataValueType({
|
||||||
variable: updateItem.variable,
|
variable: updateItem.variable,
|
||||||
nodeList,
|
nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
t
|
t
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -13,14 +13,20 @@ import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
|||||||
import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
|
import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { nodeId, outputs } = data;
|
const { nodeId, outputs } = data;
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const variablesOutputs = useCreation(() => {
|
const variablesOutputs = useCreation(() => {
|
||||||
const variables = getWorkflowGlobalVariables(nodeList, t);
|
const variables = getWorkflowGlobalVariables({
|
||||||
|
nodes: nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
|
t
|
||||||
|
});
|
||||||
|
|
||||||
return variables.map<FlowNodeOutputItemType>((item) => ({
|
return variables.map<FlowNodeOutputItemType>((item) => ({
|
||||||
id: item.key,
|
id: item.key,
|
||||||
|
|||||||
@ -7,15 +7,21 @@ import { WorkflowContext } from '@/components/core/workflow/context';
|
|||||||
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation } from 'ahooks';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
// get variable
|
// get variable
|
||||||
const variables = useCreation(() => {
|
const variables = useCreation(() => {
|
||||||
const globalVariables = getWorkflowGlobalVariables(nodeList, t);
|
const globalVariables = getWorkflowGlobalVariables({
|
||||||
|
nodes: nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
|
t
|
||||||
|
});
|
||||||
|
|
||||||
const moduleVariables = formatEditorVariablePickerIcon(
|
const moduleVariables = formatEditorVariablePickerIcon(
|
||||||
inputs
|
inputs
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import dynamic from 'next/dynamic';
|
|||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext } from '@/components/core/workflow/context';
|
import { WorkflowContext } from '@/components/core/workflow/context';
|
||||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const MultipleRowSelect = dynamic(
|
const MultipleRowSelect = dynamic(
|
||||||
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
|
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
|
||||||
@ -98,6 +99,7 @@ export const useReference = ({
|
|||||||
value?: any;
|
value?: any;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||||
|
|
||||||
@ -106,6 +108,7 @@ export const useReference = ({
|
|||||||
nodeId,
|
nodeId,
|
||||||
nodes: nodeList,
|
nodes: nodeList,
|
||||||
edges: edges,
|
edges: edges,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
t
|
t
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,6 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type';
|
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import {
|
|
||||||
formatEditorVariablePickerIcon,
|
|
||||||
getGuideModule,
|
|
||||||
splitGuideModule
|
|
||||||
} from '@fastgpt/global/core/workflow/utils';
|
|
||||||
import { ModalBody } from '@chakra-ui/react';
|
import { ModalBody } from '@chakra-ui/react';
|
||||||
import MyTooltip from '@/components/MyTooltip';
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
import {
|
import {
|
||||||
@ -22,12 +17,12 @@ import PromptTemplate from '@/components/PromptTemplate';
|
|||||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import Reference from './Reference';
|
import Reference from './Reference';
|
||||||
import { getSystemVariables } from '@/web/core/app/utils';
|
|
||||||
import ValueTypeLabel from '../../ValueTypeLabel';
|
import ValueTypeLabel from '../../ValueTypeLabel';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext } from '@/components/core/workflow/context';
|
import { WorkflowContext } from '@/components/core/workflow/context';
|
||||||
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation } from 'ahooks';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const LabelStyles: BoxProps = {
|
const LabelStyles: BoxProps = {
|
||||||
fontSize: ['sm', 'md']
|
fontSize: ['sm', 'md']
|
||||||
@ -52,9 +47,14 @@ const SettingQuotePrompt = (props: RenderInputProps) => {
|
|||||||
});
|
});
|
||||||
const aiChatQuoteTemplate = watch('quoteTemplate');
|
const aiChatQuoteTemplate = watch('quoteTemplate');
|
||||||
const aiChatQuotePrompt = watch('quotePrompt');
|
const aiChatQuotePrompt = watch('quotePrompt');
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const variables = useCreation(() => {
|
const variables = useCreation(() => {
|
||||||
const globalVariables = getWorkflowGlobalVariables(nodeList, t);
|
const globalVariables = getWorkflowGlobalVariables({
|
||||||
|
nodes: nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
|
t
|
||||||
|
});
|
||||||
|
|
||||||
return globalVariables;
|
return globalVariables;
|
||||||
}, [nodeList, t]);
|
}, [nodeList, t]);
|
||||||
|
|||||||
@ -7,15 +7,21 @@ import { useContextSelector } from 'use-context-selector';
|
|||||||
import { WorkflowContext } from '@/components/core/workflow/context';
|
import { WorkflowContext } from '@/components/core/workflow/context';
|
||||||
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation } from 'ahooks';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
// get variable
|
// get variable
|
||||||
const variables = useCreation(() => {
|
const variables = useCreation(() => {
|
||||||
const globalVariables = getWorkflowGlobalVariables(nodeList, t);
|
const globalVariables = getWorkflowGlobalVariables({
|
||||||
|
nodes: nodeList,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
|
t
|
||||||
|
});
|
||||||
|
|
||||||
const moduleVariables = formatEditorVariablePickerIcon(
|
const moduleVariables = formatEditorVariablePickerIcon(
|
||||||
inputs
|
inputs
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { Box, Button, Flex } from '@chakra-ui/react';
|
|||||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext } from '../context';
|
import { WorkflowContext } from '../context';
|
||||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
|
||||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
@ -16,6 +15,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
|||||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
|
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const PublishHistoriesSlider = () => {
|
const PublishHistoriesSlider = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -23,7 +23,7 @@ const PublishHistoriesSlider = () => {
|
|||||||
content: t('core.workflow.publish.OnRevert version confirm')
|
content: t('core.workflow.publish.OnRevert version confirm')
|
||||||
});
|
});
|
||||||
|
|
||||||
const { appDetail, setAppDetail } = useAppStore();
|
const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
const appId = useContextSelector(WorkflowContext, (e) => e.appId);
|
const appId = useContextSelector(WorkflowContext, (e) => e.appId);
|
||||||
const setIsShowVersionHistories = useContextSelector(
|
const setIsShowVersionHistories = useContextSelector(
|
||||||
WorkflowContext,
|
WorkflowContext,
|
||||||
@ -73,11 +73,11 @@ const PublishHistoriesSlider = () => {
|
|||||||
editEdges: appDetail.edges
|
editEdges: appDetail.edges
|
||||||
});
|
});
|
||||||
|
|
||||||
setAppDetail({
|
setAppDetail((state) => ({
|
||||||
...appDetail,
|
...state,
|
||||||
modules: data.nodes,
|
modules: data.nodes,
|
||||||
edges: data.edges
|
edges: data.edges
|
||||||
});
|
}));
|
||||||
|
|
||||||
onCloseSlider(data);
|
onCloseSlider(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/wor
|
|||||||
import { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe';
|
import { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe';
|
||||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { useCreation, useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import React, {
|
import React, {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
@ -32,11 +32,13 @@ import {
|
|||||||
useEdgesState,
|
useEdgesState,
|
||||||
useNodesState
|
useNodesState
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import { createContext } from 'use-context-selector';
|
import { createContext, useContextSelector } from 'use-context-selector';
|
||||||
import { defaultRunningStatus } from './constants';
|
import { defaultRunningStatus } from './constants';
|
||||||
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
|
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||||
|
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||||
|
|
||||||
@ -83,7 +85,11 @@ type WorkflowContextType = {
|
|||||||
toolInputs: FlowNodeInputItemType[];
|
toolInputs: FlowNodeInputItemType[];
|
||||||
commonInputs: FlowNodeInputItemType[];
|
commonInputs: FlowNodeInputItemType[];
|
||||||
};
|
};
|
||||||
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise<void>;
|
initData: (e: {
|
||||||
|
nodes: StoreNodeItemType[];
|
||||||
|
edges: StoreEdgeItemType[];
|
||||||
|
chatConfig?: AppChatConfigType;
|
||||||
|
}) => Promise<void>;
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
workflowDebugData:
|
workflowDebugData:
|
||||||
@ -223,6 +229,7 @@ const WorkflowContextProvider = ({
|
|||||||
const { appId, pluginId } = value;
|
const { appId, pluginId } = value;
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||||
|
const setAppDetail = useContextSelector(AppContext, (v) => v.setAppDetail);
|
||||||
|
|
||||||
/* edge */
|
/* edge */
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
@ -426,12 +433,18 @@ const WorkflowContextProvider = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const initData = useMemoizedFn(
|
const initData = useMemoizedFn(async (e: Parameters<WorkflowContextType['initData']>[0]) => {
|
||||||
async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
|
setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item })) || []);
|
||||||
setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item })) || []);
|
setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })) || []);
|
||||||
setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })) || []);
|
|
||||||
|
const chatConfig = e.chatConfig;
|
||||||
|
if (chatConfig) {
|
||||||
|
setAppDetail((state) => ({
|
||||||
|
...state,
|
||||||
|
chatConfig
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
/* debug */
|
/* debug */
|
||||||
const [workflowDebugData, setWorkflowDebugData] = useState<DebugDataType>();
|
const [workflowDebugData, setWorkflowDebugData] = useState<DebugDataType>();
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
|||||||
import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||||
import { type Node, type Edge } from 'reactflow';
|
import { type Node, type Edge } from 'reactflow';
|
||||||
|
|
||||||
export const flowNode2StoreNodes = ({
|
export const uiWorkflow2StoreWorkflow = ({
|
||||||
nodes,
|
nodes,
|
||||||
edges
|
edges
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { TeamContext } from '.';
|
import { TeamContext } from '.';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
TeamMemberStatusMap
|
TeamMemberStatusMap
|
||||||
} from '@fastgpt/global/support/user/team/constant';
|
} from '@fastgpt/global/support/user/team/constant';
|
||||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { TeamContext } from '.';
|
import { TeamContext } from '.';
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
|||||||
2
projects/app/src/global/core/app/api.d.ts
vendored
2
projects/app/src/global/core/app/api.d.ts
vendored
@ -16,6 +16,7 @@ export type AppUpdateParams = {
|
|||||||
intro?: string;
|
intro?: string;
|
||||||
nodes?: AppSchema['modules'];
|
nodes?: AppSchema['modules'];
|
||||||
edges?: AppSchema['edges'];
|
edges?: AppSchema['edges'];
|
||||||
|
chatConfig?: AppSchema['chatConfig'];
|
||||||
permission?: AppSchema['permission'];
|
permission?: AppSchema['permission'];
|
||||||
teamTags?: AppSchema['teamTags'];
|
teamTags?: AppSchema['teamTags'];
|
||||||
};
|
};
|
||||||
@ -24,6 +25,7 @@ export type PostPublishAppProps = {
|
|||||||
type: `${AppTypeEnum}`;
|
type: `${AppTypeEnum}`;
|
||||||
nodes: AppSchema['modules'];
|
nodes: AppSchema['modules'];
|
||||||
edges: AppSchema['edges'];
|
edges: AppSchema['edges'];
|
||||||
|
chatConfig: AppSchema['chatConfig'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PostRevertAppProps = {
|
export type PostRevertAppProps = {
|
||||||
|
|||||||
4
projects/app/src/global/core/chat/api.d.ts
vendored
4
projects/app/src/global/core/chat/api.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import type { AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
import type { AppChatConfigType, AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
||||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||||
import { AdminFbkType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
import { AdminFbkType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
||||||
@ -34,7 +34,7 @@ export type InitChatResponse = {
|
|||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
history: ChatItemType[];
|
history: ChatItemType[];
|
||||||
app: {
|
app: {
|
||||||
userGuideModule?: StoreNodeItemType;
|
chatConfig?: AppChatConfigType;
|
||||||
chatModels?: string[];
|
chatModels?: string[];
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { MongoApp } from '@fastgpt/service/core/app/schema';
|
|||||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||||
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
|
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||||
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/version/schema';
|
||||||
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>) {
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
|||||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
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/version/schema';
|
||||||
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
|
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
const { appId } = req.query as { appId: string };
|
const { appId } = req.query as { appId: string };
|
||||||
@ -47,7 +47,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
},
|
},
|
||||||
{ session }
|
{ session }
|
||||||
);
|
);
|
||||||
await MongoAppQGuide.deleteMany(
|
await MongoChatInputGuide.deleteMany(
|
||||||
{
|
{
|
||||||
appId
|
appId
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { jsonRes } from '@fastgpt/service/common/response';
|
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
|
||||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
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>) {
|
||||||
const { textList = [], appId, customURL } = req.body;
|
const { textList = [], appId, customUrl } = req.body;
|
||||||
|
|
||||||
if (!customURL) {
|
if (!customUrl) {
|
||||||
const { teamId } = await authUserNotVisitor({ req, authToken: true });
|
const { teamId } = await authUserNotVisitor({ req, authToken: true });
|
||||||
|
|
||||||
const currentQGuide = await MongoAppQGuide.find({ appId, teamId });
|
const currentQGuide = await MongoChatInputGuide.find({ appId, teamId });
|
||||||
const currentTexts = currentQGuide.map((item) => item.text);
|
const currentTexts = currentQGuide.map((item) => item.text);
|
||||||
const textsToDelete = currentTexts.filter((text) => !textList.includes(text));
|
const textsToDelete = currentTexts.filter((text) => !textList.includes(text));
|
||||||
|
|
||||||
await MongoAppQGuide.deleteMany({ text: { $in: textsToDelete }, appId, teamId });
|
await MongoChatInputGuide.deleteMany({ text: { $in: textsToDelete }, appId, teamId });
|
||||||
|
|
||||||
const newTexts = textList.filter((text: string) => !currentTexts.includes(text));
|
const newTexts = textList.filter((text: string) => !currentTexts.includes(text));
|
||||||
|
|
||||||
@ -24,10 +24,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
teamId: teamId
|
teamId: teamId
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await MongoAppQGuide.insertMany(newDocuments);
|
await MongoChatInputGuide.insertMany(newDocuments);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(customURL, {
|
const response = await axios.post(customUrl, {
|
||||||
textList,
|
textList,
|
||||||
appId
|
appId
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
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);
|
|
||||||
@ -7,7 +7,7 @@ import { NextAPI } from '@/service/middleware/entry';
|
|||||||
|
|
||||||
/* 获取我的模型 */
|
/* 获取我的模型 */
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
const { name, avatar, type, intro, nodes, edges, permission, teamTags } =
|
const { name, avatar, type, intro, nodes, edges, chatConfig, permission, teamTags } =
|
||||||
req.body as AppUpdateParams;
|
req.body as AppUpdateParams;
|
||||||
const { appId } = req.query as { appId: string };
|
const { appId } = req.query as { appId: string };
|
||||||
|
|
||||||
@ -39,7 +39,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
}),
|
}),
|
||||||
...(edges && {
|
...(edges && {
|
||||||
edges
|
edges
|
||||||
})
|
}),
|
||||||
|
...(chatConfig && { chatConfig })
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
|
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
|
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||||
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
|
||||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||||
import { PostPublishAppProps } from '@/global/core/app/api';
|
import { PostPublishAppProps } from '@/global/core/app/api';
|
||||||
|
|
||||||
@ -13,14 +12,12 @@ type Response = {};
|
|||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
|
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
|
||||||
const { appId } = req.query as { appId: string };
|
const { appId } = req.query as { appId: string };
|
||||||
const { nodes = [], edges = [], type } = req.body as PostPublishAppProps;
|
const { nodes = [], edges = [], chatConfig, type } = req.body as PostPublishAppProps;
|
||||||
|
|
||||||
await authApp({ appId, req, per: 'w', authToken: true });
|
await authApp({ appId, req, per: 'w', authToken: true });
|
||||||
|
|
||||||
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
|
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
|
||||||
|
|
||||||
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(formatNodes || []));
|
|
||||||
|
|
||||||
await mongoSessionRun(async (session) => {
|
await mongoSessionRun(async (session) => {
|
||||||
// create version histories
|
// create version histories
|
||||||
await MongoAppVersion.create(
|
await MongoAppVersion.create(
|
||||||
@ -28,7 +25,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
|||||||
{
|
{
|
||||||
appId,
|
appId,
|
||||||
nodes: formatNodes,
|
nodes: formatNodes,
|
||||||
edges
|
edges,
|
||||||
|
chatConfig
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
{ session }
|
{ session }
|
||||||
@ -38,12 +36,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
|||||||
await MongoApp.findByIdAndUpdate(appId, {
|
await MongoApp.findByIdAndUpdate(appId, {
|
||||||
modules: formatNodes,
|
modules: formatNodes,
|
||||||
edges,
|
edges,
|
||||||
|
chatConfig,
|
||||||
updateTime: new Date(),
|
updateTime: new Date(),
|
||||||
version: 'v2',
|
version: 'v2',
|
||||||
type,
|
type,
|
||||||
scheduledTriggerConfig,
|
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
|
||||||
scheduledTriggerNextTime: scheduledTriggerConfig
|
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig
|
||||||
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
|
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
|
||||||
: null
|
: null
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
|
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||||
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
|
||||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||||
import { PostRevertAppProps } from '@/global/core/app/api';
|
import { PostRevertAppProps } from '@/global/core/app/api';
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
|||||||
|
|
||||||
const { nodes: formatEditNodes } = beforeUpdateAppFormat({ nodes: editNodes });
|
const { nodes: formatEditNodes } = beforeUpdateAppFormat({ nodes: editNodes });
|
||||||
|
|
||||||
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(version.nodes));
|
const scheduledTriggerConfig = version.chatConfig.scheduledTriggerConfig;
|
||||||
|
|
||||||
await mongoSessionRun(async (session) => {
|
await mongoSessionRun(async (session) => {
|
||||||
// 为编辑中的数据创建一个版本
|
// 为编辑中的数据创建一个版本
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { jsonRes } from '@fastgpt/service/common/response';
|
import { jsonRes } from '@fastgpt/service/common/response';
|
||||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
import { getGuideModule, replaceAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
||||||
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
|
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
|
||||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||||
@ -61,10 +61,12 @@ async function handler(
|
|||||||
variables: chat?.variables || {},
|
variables: chat?.variables || {},
|
||||||
history,
|
history,
|
||||||
app: {
|
app: {
|
||||||
userGuideModule: replaceAppChatConfig({
|
chatConfig: getAppChatConfig({
|
||||||
node: getGuideModule(nodes),
|
chatConfig: app.chatConfig,
|
||||||
variableList: chat?.variableList,
|
systemConfigNode: getGuideModule(nodes),
|
||||||
welcomeText: chat?.welcomeText
|
storeVariables: chat?.variableList,
|
||||||
|
storeWelcomeText: chat?.welcomeText,
|
||||||
|
isPublicFetch: false
|
||||||
}),
|
}),
|
||||||
chatModels: getChatModelNameListByModules(nodes),
|
chatModels: getChatModelNameListByModules(nodes),
|
||||||
name: app.name,
|
name: app.name,
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
|
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||||
|
|
||||||
|
export type countChatInputGuideTotalQuery = { appId: string };
|
||||||
|
|
||||||
|
export type countChatInputGuideTotalBody = {};
|
||||||
|
|
||||||
|
export type countChatInputGuideTotalResponse = { total: number };
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<countChatInputGuideTotalBody, countChatInputGuideTotalQuery>,
|
||||||
|
res: ApiResponseType<any>
|
||||||
|
): Promise<countChatInputGuideTotalResponse> {
|
||||||
|
await authCert({ req, authToken: true });
|
||||||
|
|
||||||
|
const appId = req.query.appId;
|
||||||
|
if (!appId) {
|
||||||
|
return {
|
||||||
|
total: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: await MongoChatInputGuide.countDocuments({ appId })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
45
projects/app/src/pages/api/core/chat/inputGuide/create.ts
Normal file
45
projects/app/src/pages/api/core/chat/inputGuide/create.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
|
|
||||||
|
export type createChatInputGuideQuery = {};
|
||||||
|
|
||||||
|
export type createInputGuideBody = {
|
||||||
|
appId: string;
|
||||||
|
textList: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type createInputGuideResponse = {
|
||||||
|
insertLength: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<createInputGuideBody, createChatInputGuideQuery>,
|
||||||
|
res: ApiResponseType<any>
|
||||||
|
): Promise<createInputGuideResponse> {
|
||||||
|
const { appId, textList } = req.body;
|
||||||
|
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await MongoChatInputGuide.insertMany(
|
||||||
|
textList.map((text) => ({
|
||||||
|
appId,
|
||||||
|
text
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
ordered: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
insertLength: result.length
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
const errLength = error.writeErrors?.length ?? textList.length;
|
||||||
|
return {
|
||||||
|
insertLength: textList.length - errLength
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
27
projects/app/src/pages/api/core/chat/inputGuide/delete.ts
Normal file
27
projects/app/src/pages/api/core/chat/inputGuide/delete.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
|
|
||||||
|
export type deleteChatInputGuideQuery = {};
|
||||||
|
|
||||||
|
export type deleteInputGuideBody = { appId: string; dataIdList: string[] };
|
||||||
|
|
||||||
|
export type deleteInputGuideResponse = {};
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<deleteInputGuideBody, deleteChatInputGuideQuery>,
|
||||||
|
res: ApiResponseType<any>
|
||||||
|
): Promise<deleteInputGuideResponse> {
|
||||||
|
const { appId, dataIdList } = req.body;
|
||||||
|
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||||
|
console.log(dataIdList);
|
||||||
|
await MongoChatInputGuide.deleteMany({
|
||||||
|
_id: { $in: dataIdList },
|
||||||
|
appId
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
42
projects/app/src/pages/api/core/chat/inputGuide/list.ts
Normal file
42
projects/app/src/pages/api/core/chat/inputGuide/list.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import type { NextApiResponse } from 'next';
|
||||||
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
|
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||||
|
import { ChatInputGuideSchemaType } from '@fastgpt/global/core/chat/inputGuide/type';
|
||||||
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
|
|
||||||
|
export type ChatInputGuideProps = PaginationProps<{
|
||||||
|
appId: string;
|
||||||
|
searchKey: string;
|
||||||
|
}>;
|
||||||
|
export type ChatInputGuideResponse = PaginationResponse<ChatInputGuideSchemaType>;
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<{}, ChatInputGuideProps>,
|
||||||
|
res: NextApiResponse<any>
|
||||||
|
): Promise<ChatInputGuideResponse> {
|
||||||
|
const { appId, pageSize, current, searchKey } = req.query;
|
||||||
|
|
||||||
|
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
appId,
|
||||||
|
...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } })
|
||||||
|
};
|
||||||
|
|
||||||
|
const [result, total] = await Promise.all([
|
||||||
|
MongoChatInputGuide.find(params)
|
||||||
|
.sort({ _id: -1 })
|
||||||
|
.skip(pageSize * (current - 1))
|
||||||
|
.limit(pageSize),
|
||||||
|
MongoChatInputGuide.countDocuments(params)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: result,
|
||||||
|
total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
34
projects/app/src/pages/api/core/chat/inputGuide/query.ts
Normal file
34
projects/app/src/pages/api/core/chat/inputGuide/query.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { NextApiResponse } from 'next';
|
||||||
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||||
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
|
|
||||||
|
export type QueryChatInputGuideProps = {
|
||||||
|
appId: string;
|
||||||
|
searchKey: string;
|
||||||
|
};
|
||||||
|
export type QueryChatInputGuideResponse = string[];
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<{}, QueryChatInputGuideProps>,
|
||||||
|
res: NextApiResponse<any>
|
||||||
|
): Promise<QueryChatInputGuideResponse> {
|
||||||
|
const { appId, searchKey } = req.query;
|
||||||
|
|
||||||
|
await authApp({ req, appId, authToken: true, authApiKey: true, per: 'r' });
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
appId,
|
||||||
|
...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } })
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await MongoChatInputGuide.find(params).sort({ _id: -1 }).limit(6);
|
||||||
|
|
||||||
|
return result
|
||||||
|
.map((item) => item.text)
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter((item) => item !== searchKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
36
projects/app/src/pages/api/core/chat/inputGuide/update.ts
Normal file
36
projects/app/src/pages/api/core/chat/inputGuide/update.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||||
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
|
|
||||||
|
export type updateChatInputGuideQuery = {};
|
||||||
|
|
||||||
|
export type updateInputGuideBody = {
|
||||||
|
appId: string;
|
||||||
|
dataId: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type updateInputGuideResponse = {};
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<updateInputGuideBody, updateChatInputGuideQuery>,
|
||||||
|
res: ApiResponseType<any>
|
||||||
|
): Promise<updateInputGuideResponse> {
|
||||||
|
const { appId, dataId, text } = req.body;
|
||||||
|
await authApp({ req, appId, authToken: true, per: 'r' });
|
||||||
|
|
||||||
|
await MongoChatInputGuide.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: dataId,
|
||||||
|
appId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
import { jsonRes } from '@fastgpt/service/common/response';
|
import { jsonRes } from '@fastgpt/service/common/response';
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
import { connectToDatabase } from '@/service/mongo';
|
||||||
import type { InitChatResponse, InitOutLinkChatProps } from '@/global/core/chat/api.d';
|
import type { InitChatResponse, InitOutLinkChatProps } from '@/global/core/chat/api.d';
|
||||||
import { getGuideModule, replaceAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||||
@ -72,10 +72,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
variables: chat?.variables || {},
|
variables: chat?.variables || {},
|
||||||
history,
|
history,
|
||||||
app: {
|
app: {
|
||||||
userGuideModule: replaceAppChatConfig({
|
chatConfig: getAppChatConfig({
|
||||||
node: getGuideModule(nodes),
|
chatConfig: app.chatConfig,
|
||||||
variableList: chat?.variableList,
|
systemConfigNode: getGuideModule(nodes),
|
||||||
welcomeText: chat?.welcomeText
|
storeVariables: chat?.variableList,
|
||||||
|
storeWelcomeText: chat?.welcomeText,
|
||||||
|
isPublicFetch: false
|
||||||
}),
|
}),
|
||||||
chatModels: getChatModelNameListByModules(nodes),
|
chatModels: getChatModelNameListByModules(nodes),
|
||||||
name: app.name,
|
name: app.name,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { jsonRes } from '@fastgpt/service/common/response';
|
import { jsonRes } from '@fastgpt/service/common/response';
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
import { connectToDatabase } from '@/service/mongo';
|
||||||
import { getGuideModule, replaceAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import type { InitChatResponse, InitTeamChatProps } from '@/global/core/chat/api.d';
|
import type { InitChatResponse, InitTeamChatProps } from '@/global/core/chat/api.d';
|
||||||
@ -73,10 +73,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
variables: chat?.variables || {},
|
variables: chat?.variables || {},
|
||||||
history,
|
history,
|
||||||
app: {
|
app: {
|
||||||
userGuideModule: replaceAppChatConfig({
|
chatConfig: getAppChatConfig({
|
||||||
node: getGuideModule(nodes),
|
chatConfig: app.chatConfig,
|
||||||
variableList: chat?.variableList,
|
systemConfigNode: getGuideModule(nodes),
|
||||||
welcomeText: chat?.welcomeText
|
storeVariables: chat?.variableList,
|
||||||
|
storeWelcomeText: chat?.welcomeText,
|
||||||
|
isPublicFetch: false
|
||||||
}),
|
}),
|
||||||
chatModels: getChatModelNameListByModules(nodes),
|
chatModels: getChatModelNameListByModules(nodes),
|
||||||
name: app.name,
|
name: app.name,
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
||||||
import { jsonRes } from '@fastgpt/service/common/response';
|
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
|
||||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
|
||||||
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
|
||||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
|
||||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
|
||||||
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
|
|
||||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
|
||||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
|
||||||
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
|
||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
try {
|
|
||||||
await connectToDatabase();
|
|
||||||
|
|
||||||
let { appId, chatId, loadCustomFeedbacks } = req.query as InitChatProps;
|
|
||||||
|
|
||||||
if (!appId) {
|
|
||||||
return jsonRes(res, {
|
|
||||||
code: 501,
|
|
||||||
message: "You don't have an app yet"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// auth app permission
|
|
||||||
const [{ app, tmbId }, chat] = await Promise.all([
|
|
||||||
authApp({
|
|
||||||
req,
|
|
||||||
authToken: true,
|
|
||||||
appId,
|
|
||||||
per: 'r'
|
|
||||||
}),
|
|
||||||
chatId ? MongoChat.findOne({ appId, chatId }) : undefined
|
|
||||||
]);
|
|
||||||
|
|
||||||
// // auth chat permission
|
|
||||||
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
|
|
||||||
// throw new Error(ChatErrEnum.unAuthChat);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get app and history
|
|
||||||
const { history } = await getChatItems({
|
|
||||||
appId,
|
|
||||||
chatId,
|
|
||||||
limit: 30,
|
|
||||||
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${
|
|
||||||
DispatchNodeResponseKeyEnum.nodeResponse
|
|
||||||
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`
|
|
||||||
});
|
|
||||||
|
|
||||||
jsonRes<InitChatResponse>(res, {
|
|
||||||
data: {
|
|
||||||
chatId,
|
|
||||||
appId,
|
|
||||||
title: chat?.title || '新对话',
|
|
||||||
userAvatar: undefined,
|
|
||||||
variables: chat?.variables || {},
|
|
||||||
history,
|
|
||||||
app: {
|
|
||||||
userGuideModule: getGuideModule(app.modules),
|
|
||||||
chatModels: getChatModelNameListByModules(app.modules),
|
|
||||||
name: app.name,
|
|
||||||
avatar: app.avatar,
|
|
||||||
intro: app.intro
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
jsonRes(res, {
|
|
||||||
code: 500,
|
|
||||||
error: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
api: {
|
|
||||||
responseLimit: '10mb'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -26,7 +26,7 @@ async function handler(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [rebuildingCount, trainingCount] = await Promise.all([
|
const [rebuildingCount, trainingCount] = await Promise.all([
|
||||||
MongoDatasetData.countDocuments({ teamId, datasetId, rebuilding: true }),
|
MongoDatasetData.countDocuments({ rebuilding: true, teamId, datasetId }),
|
||||||
MongoDatasetTraining.countDocuments({ teamId, datasetId })
|
MongoDatasetTraining.countDocuments({ teamId, datasetId })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { getAIApi } from '@fastgpt/service/core/ai/config';
|
|||||||
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
|
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
|
||||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
|
||||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
|
||||||
@ -47,14 +46,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
// auth role
|
// auth role
|
||||||
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
|
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
|
||||||
// auth app
|
// auth app
|
||||||
const app = await MongoApp.findById(appId, 'modules').lean();
|
// const app = await MongoApp.findById(appId, 'modules').lean();
|
||||||
if (!app) {
|
// if (!app) {
|
||||||
throw new Error('app not found');
|
// throw new Error('app not found');
|
||||||
}
|
// }
|
||||||
const { whisperConfig } = splitGuideModule(getGuideModule(app?.modules));
|
// if (!whisperConfig?.open) {
|
||||||
if (!whisperConfig?.open) {
|
// throw new Error('Whisper is not open in the app');
|
||||||
throw new Error('Whisper is not open in the app');
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
const ai = getAIApi();
|
const ai = getAIApi();
|
||||||
|
|
||||||
|
|||||||
@ -170,7 +170,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
// 1. get and concat history; 2. get app workflow
|
// 1. get and concat history; 2. get app workflow
|
||||||
const limit = getMaxHistoryLimitFromNodes(app.modules);
|
const limit = getMaxHistoryLimitFromNodes(app.modules);
|
||||||
const [{ history }, { nodes, edges }] = await Promise.all([
|
const [{ history }, { nodes, edges, chatConfig }] = await Promise.all([
|
||||||
getChatItems({
|
getChatItems({
|
||||||
appId: app._id,
|
appId: app._id,
|
||||||
chatId,
|
chatId,
|
||||||
@ -249,6 +249,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
teamId,
|
teamId,
|
||||||
tmbId: tmbId,
|
tmbId: tmbId,
|
||||||
nodes,
|
nodes,
|
||||||
|
appChatConfig: chatConfig,
|
||||||
variables: newVariables,
|
variables: newVariables,
|
||||||
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
|
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
|
||||||
shareId,
|
shareId,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
|
import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
|
||||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
@ -9,8 +8,7 @@ import dynamic from 'next/dynamic';
|
|||||||
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest';
|
import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest';
|
||||||
import { flowNode2StoreNodes } from '@/components/core/workflow/utils';
|
import { uiWorkflow2StoreWorkflow } from '@/components/core/workflow/utils';
|
||||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
@ -27,19 +25,16 @@ 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 { AppContext } from '@/web/core/app/context/appContext';
|
||||||
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(
|
||||||
() => import('@/components/core/workflow/components/PublishHistoriesSlider')
|
() => import('@/components/core/workflow/components/PublishHistoriesSlider')
|
||||||
);
|
);
|
||||||
|
|
||||||
type Props = { app: AppSchema; onClose: () => void };
|
type Props = { onClose: () => void };
|
||||||
|
|
||||||
const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||||
app,
|
|
||||||
ChatTestRef,
|
ChatTestRef,
|
||||||
setWorkflowTestData,
|
setWorkflowTestData,
|
||||||
onClose
|
onClose
|
||||||
@ -55,7 +50,9 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
}) {
|
}) {
|
||||||
const isV2Workflow = app?.version === 'v2';
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
|
const isV2Workflow = appDetail?.version === 'v2';
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -66,7 +63,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({
|
const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({
|
||||||
content: t('core.app.Publish Confirm')
|
content: t('core.app.Publish Confirm')
|
||||||
});
|
});
|
||||||
const { publishApp, updateAppDetail } = useAppStore();
|
const { publishApp, updateAppDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||||
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
@ -90,7 +87,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
|
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
|
||||||
|
|
||||||
if (!checkResults) {
|
if (!checkResults) {
|
||||||
const storeNodes = flowNode2StoreNodes({ nodes, edges });
|
const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges });
|
||||||
|
|
||||||
return storeNodes;
|
return storeNodes;
|
||||||
} else {
|
} else {
|
||||||
@ -112,12 +109,13 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
if (nodes.length === 0) return null;
|
if (nodes.length === 0) return null;
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
|
||||||
const storeWorkflow = flowNode2StoreNodes({ nodes, edges });
|
const storeWorkflow = uiWorkflow2StoreWorkflow({ nodes, edges });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateAppDetail(app._id, {
|
await updateAppDetail({
|
||||||
...storeWorkflow,
|
...storeWorkflow,
|
||||||
type: AppTypeEnum.advanced,
|
type: AppTypeEnum.advanced,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
version: 'v2'
|
version: 'v2'
|
||||||
});
|
});
|
||||||
@ -134,7 +132,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[isV2Workflow, isShowVersionHistories, edges, updateAppDetail, app._id, t]
|
[isV2Workflow, isShowVersionHistories, edges, updateAppDetail, appDetail.chatConfig, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onclickPublish = useCallback(async () => {
|
const onclickPublish = useCallback(async () => {
|
||||||
@ -142,19 +140,10 @@ 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 publishApp({
|
||||||
await importQuestionGuides({
|
|
||||||
appId: app._id,
|
|
||||||
textList: questionGuideText.textList,
|
|
||||||
customURL: getAppQGuideCustomURL(app)
|
|
||||||
});
|
|
||||||
|
|
||||||
const newNodes = getNodesWithNoQGuide(data.nodes, questionGuideText);
|
|
||||||
|
|
||||||
await publishApp(app._id, {
|
|
||||||
...data,
|
...data,
|
||||||
nodes: newNodes,
|
|
||||||
type: AppTypeEnum.advanced,
|
type: AppTypeEnum.advanced,
|
||||||
|
chatConfig: appDetail.chatConfig,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
version: 'v2'
|
version: 'v2'
|
||||||
});
|
});
|
||||||
@ -172,7 +161,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}, [flowData2StoreDataAndCheck, publishApp, app._id, toast, t, ChatTestRef]);
|
}, [flowData2StoreDataAndCheck, publishApp, appDetail.chatConfig, toast, t, ChatTestRef]);
|
||||||
|
|
||||||
const saveAndBack = useCallback(async () => {
|
const saveAndBack = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@ -188,7 +177,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
nodes: filterSensitiveNodesData(data.nodes),
|
nodes: filterSensitiveNodesData(data.nodes),
|
||||||
edges: data.edges
|
edges: data.edges,
|
||||||
|
chatConfig: appDetail.chatConfig
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
@ -196,7 +186,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
appT('Export Config Successful')
|
appT('Export Config Successful')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [appT, copyData, flowData2StoreDataAndCheck]);
|
}, [appDetail.chatConfig, appT, copyData, flowData2StoreDataAndCheck]);
|
||||||
|
|
||||||
// effect
|
// effect
|
||||||
useBeforeunload({
|
useBeforeunload({
|
||||||
@ -205,7 +195,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
if (!app._id) return;
|
if (!appDetail._id) return;
|
||||||
onclickSave(!!workflowDebugData);
|
onclickSave(!!workflowDebugData);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
@ -235,7 +225,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
/>
|
/>
|
||||||
<Box ml={[2, 4]}>
|
<Box ml={[2, 4]}>
|
||||||
<Box fontSize={['md', 'lg']} fontWeight={'bold'}>
|
<Box fontSize={['md', 'lg']} fontWeight={'bold'}>
|
||||||
{app.name}
|
{appDetail.name}
|
||||||
</Box>
|
</Box>
|
||||||
{!isShowVersionHistories && isV2Workflow && (
|
{!isShowVersionHistories && isV2Workflow && (
|
||||||
<MyTooltip label={t('core.app.Onclick to save')}>
|
<MyTooltip label={t('core.app.Onclick to save')}>
|
||||||
@ -327,7 +317,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
theme.borders.base,
|
theme.borders.base,
|
||||||
isSaving,
|
isSaving,
|
||||||
saveAndBack,
|
saveAndBack,
|
||||||
app.name,
|
appDetail.name,
|
||||||
isShowVersionHistories,
|
isShowVersionHistories,
|
||||||
isV2Workflow,
|
isV2Workflow,
|
||||||
t,
|
t,
|
||||||
@ -354,7 +344,6 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Header = (props: Props) => {
|
const Header = (props: Props) => {
|
||||||
const { app } = props;
|
|
||||||
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
||||||
|
|
||||||
const [workflowTestData, setWorkflowTestData] = useState<{
|
const [workflowTestData, setWorkflowTestData] = useState<{
|
||||||
@ -374,13 +363,7 @@ const Header = (props: Props) => {
|
|||||||
ChatTestRef={ChatTestRef}
|
ChatTestRef={ChatTestRef}
|
||||||
setWorkflowTestData={setWorkflowTestData}
|
setWorkflowTestData={setWorkflowTestData}
|
||||||
/>
|
/>
|
||||||
<ChatTest
|
<ChatTest ref={ChatTestRef} isOpen={isOpenTest} {...workflowTestData} onClose={onCloseTest} />
|
||||||
ref={ChatTestRef}
|
|
||||||
isOpen={isOpenTest}
|
|
||||||
{...workflowTestData}
|
|
||||||
app={app}
|
|
||||||
onClose={onCloseTest}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,11 +7,15 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
|||||||
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
|
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
|
||||||
import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context';
|
import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
import { useMount } from 'ahooks';
|
||||||
|
|
||||||
type Props = { app: AppSchema; onClose: () => void };
|
type Props = { onClose: () => void };
|
||||||
|
|
||||||
const Render = ({ app, onClose }: Props) => {
|
const Render = ({ onClose }: Props) => {
|
||||||
const isV2Workflow = app?.version === 'v2';
|
const appDetail = useContextSelector(AppContext, (e) => e.appDetail);
|
||||||
|
|
||||||
|
const isV2Workflow = appDetail?.version === 'v2';
|
||||||
const { openConfirm, ConfirmModal } = useConfirm({
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
content:
|
content:
|
||||||
@ -21,26 +25,23 @@ const Render = ({ app, onClose }: Props) => {
|
|||||||
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
|
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
|
||||||
|
|
||||||
const workflowStringData = JSON.stringify({
|
const workflowStringData = JSON.stringify({
|
||||||
nodes: app.modules || [],
|
nodes: appDetail.modules || [],
|
||||||
edges: app.edges || []
|
edges: appDetail.edges || []
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useMount(() => {
|
||||||
if (!isV2Workflow) return;
|
|
||||||
initData(JSON.parse(workflowStringData));
|
|
||||||
}, [isV2Workflow, initData, app._id, workflowStringData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isV2Workflow) {
|
if (!isV2Workflow) {
|
||||||
openConfirm(() => {
|
openConfirm(() => {
|
||||||
initData(JSON.parse(JSON.stringify(v1Workflow2V2((app.modules || []) as any))));
|
initData(JSON.parse(JSON.stringify(v1Workflow2V2((appDetail.modules || []) as any))));
|
||||||
})();
|
})();
|
||||||
|
} else {
|
||||||
|
initData(JSON.parse(workflowStringData));
|
||||||
}
|
}
|
||||||
}, [app.modules, initData, isV2Workflow, openConfirm]);
|
});
|
||||||
|
|
||||||
const memoRender = useMemo(() => {
|
const memoRender = useMemo(() => {
|
||||||
return <Flow Header={<Header app={app} onClose={onClose} />} />;
|
return <Flow Header={<Header onClose={onClose} />} />;
|
||||||
}, [app, onClose]);
|
}, [onClose]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -51,12 +52,13 @@ const Render = ({ app, onClose }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(function FlowEdit(props: Props) {
|
export default React.memo(function FlowEdit(props: Props) {
|
||||||
const filterAppIds = useMemo(() => [props.app._id], [props.app._id]);
|
const appDetail = useContextSelector(AppContext, (e) => e.appDetail);
|
||||||
|
const filterAppIds = useMemo(() => [appDetail._id], [appDetail._id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowContextProvider
|
<WorkflowContextProvider
|
||||||
value={{
|
value={{
|
||||||
appId: props.app._id,
|
appId: appDetail._id,
|
||||||
mode: 'app',
|
mode: 'app',
|
||||||
filterAppIds,
|
filterAppIds,
|
||||||
basicNodeTemplates: appSystemModuleTemplates
|
basicNodeTemplates: appSystemModuleTemplates
|
||||||
|
|||||||
@ -19,10 +19,11 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
|||||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
|
||||||
import PermissionRadio from '@/components/support/permission/Radio';
|
import PermissionRadio from '@/components/support/permission/Radio';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
|
||||||
const InfoModal = ({
|
const InfoModal = ({
|
||||||
defaultApp,
|
defaultApp,
|
||||||
@ -35,7 +36,7 @@ const InfoModal = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { updateAppDetail } = useAppStore();
|
const { updateAppDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
fileType: '.jpg,.png',
|
fileType: '.jpg,.png',
|
||||||
@ -55,7 +56,7 @@ const InfoModal = ({
|
|||||||
// submit config
|
// submit config
|
||||||
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
|
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
|
||||||
mutationFn: async (data: AppSchema) => {
|
mutationFn: async (data: AppSchema) => {
|
||||||
await updateAppDetail(data._id, {
|
await updateAppDetail({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
avatar: data.avatar,
|
avatar: data.avatar,
|
||||||
intro: data.intro,
|
intro: data.intro,
|
||||||
|
|||||||
@ -335,7 +335,7 @@ const DetailLogsModal = ({
|
|||||||
feedbackType={'admin'}
|
feedbackType={'admin'}
|
||||||
showMarkIcon
|
showMarkIcon
|
||||||
showVoiceIcon={false}
|
showVoiceIcon={false}
|
||||||
userGuideModule={chat?.app?.userGuideModule}
|
chatConfig={chat?.app?.chatConfig}
|
||||||
appId={appId}
|
appId={appId}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
|||||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||||
import { delModelById } from '@/web/core/app/api';
|
import { delModelById } from '@/web/core/app/api';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
|
||||||
import PermissionIconText from '@/components/support/permission/IconText';
|
import PermissionIconText from '@/components/support/permission/IconText';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
@ -16,6 +15,8 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
|||||||
import TagsEditModal from './TagsEditModal';
|
import TagsEditModal from './TagsEditModal';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import { useI18n } from '@/web/context/I18n';
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||||
|
|
||||||
const AppCard = ({ appId }: { appId: string }) => {
|
const AppCard = ({ appId }: { appId: string }) => {
|
||||||
@ -24,7 +25,7 @@ const AppCard = ({ appId }: { appId: string }) => {
|
|||||||
const { appT } = useI18n();
|
const { appT } = useI18n();
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { appDetail } = useAppStore();
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
const { feConfigs } = useSystemStore();
|
const { feConfigs } = useSystemStore();
|
||||||
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
|
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
|
||||||
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
|
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
|
||||||
|
|||||||
@ -16,12 +16,13 @@ import {
|
|||||||
initWorkflowEdgeStatus,
|
initWorkflowEdgeStatus,
|
||||||
storeNodes2RuntimeNodes
|
storeNodes2RuntimeNodes
|
||||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
import { useCreation, useMemoizedFn, useSafeState } from 'ahooks';
|
import { useMemoizedFn, useSafeState } from 'ahooks';
|
||||||
import { UseFormReturn } from 'react-hook-form';
|
import { UseFormReturn } from 'react-hook-form';
|
||||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
|
||||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||||
import { useI18n } from '@/web/context/I18n';
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
|
|
||||||
const ChatTest = ({
|
const ChatTest = ({
|
||||||
editForm,
|
editForm,
|
||||||
@ -35,18 +36,15 @@ const ChatTest = ({
|
|||||||
|
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||||
const { appDetail } = useAppStore();
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const { watch } = editForm;
|
const { watch } = editForm;
|
||||||
|
const chatConfig = watch('chatConfig');
|
||||||
|
|
||||||
const [workflowData, setWorkflowData] = useSafeState({
|
const [workflowData, setWorkflowData] = useSafeState({
|
||||||
nodes: appDetail.modules || [],
|
nodes: appDetail.modules || [],
|
||||||
edges: appDetail.edges || []
|
edges: appDetail.edges || []
|
||||||
});
|
});
|
||||||
const userGuideModule = useCreation(
|
|
||||||
() => getGuideModule(workflowData.nodes),
|
|
||||||
[workflowData.nodes]
|
|
||||||
);
|
|
||||||
|
|
||||||
const startChat = useMemoizedFn(
|
const startChat = useMemoizedFn(
|
||||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||||
@ -131,7 +129,7 @@ const ChatTest = ({
|
|||||||
appAvatar={appDetail.avatar}
|
appAvatar={appDetail.avatar}
|
||||||
userAvatar={userInfo?.avatar}
|
userAvatar={userInfo?.avatar}
|
||||||
showMarkIcon
|
showMarkIcon
|
||||||
userGuideModule={userGuideModule}
|
chatConfig={chatConfig}
|
||||||
showFileSelector={checkChatSupportSelectFileByModules(workflowData.nodes)}
|
showFileSelector={checkChatSupportSelectFileByModules(workflowData.nodes)}
|
||||||
onStartChat={startChat}
|
onStartChat={startChat}
|
||||||
onDelMessage={() => {}}
|
onDelMessage={() => {}}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user