fix: 配置读取错误

This commit is contained in:
lrhh123
2024-06-30 21:37:50 +08:00
parent 95465ae679
commit 9643649efa
13 changed files with 570 additions and 179 deletions
+2 -1
View File
@@ -28,6 +28,7 @@
"*.{css,sass,scss}.d.ts": true
},
"cSpell.words": [
"dify"
"dify",
"jinritemai"
]
}
+109 -124
View File
@@ -22,55 +22,70 @@ export class ConfigController {
let config;
// 先查找实例配置
if (instanceId) {
config = await Config.findOne({
where: { platform_id: appId, instance_id: instanceId },
});
// 如果实例配置存在且激活,直接返回
if (config && config.active) {
return this.mergeWithGlobalConfig(config);
}
}
// 查找应用级别配置
if (appId) {
config = await Config.findOne({
where: { platform_id: appId, instance_id: '' },
});
// 如果应用级别配置存在且激活,直接返回
if (config && config.active) {
return this.mergeWithGlobalConfig(config);
}
}
// 查找全局配置
config = await Config.findOne({
where: { instance_id: instanceId, active: true },
where: { global: true },
});
if (!config && appId) {
config = await Config.findOne({
where: { platform_id: appId, active: true },
});
}
if (!config) {
config = await Config.findOne({
where: { global: true },
});
}
// 如果全局配置不存在,创建一个默认的全局配置
if (!config) {
config = await Config.create({
global: true,
});
} else {
const globalConfig = await Config.findOne({
where: { global: true },
});
// 这三个配置项是全局配置,需要合并到实例配置中
if (globalConfig) {
config.has_keyword_match = globalConfig.has_keyword_match;
config.has_paused = globalConfig.has_paused;
config.has_use_gpt = globalConfig.has_use_gpt;
config.has_mouse_close = globalConfig.has_mouse_close;
config.has_esc_close = globalConfig.has_esc_close;
}
// 再检查 key 和 base_url 是否存在,不存在则使用全局配置
if (!config.key || !config.base_url) {
config.llm_type = globalConfig?.llm_type || 'chatgpt';
config.model = globalConfig?.model || 'gpt-3.5-turbo';
}
if (!config.key) {
config.key = globalConfig?.key || '';
}
if (!config.base_url) {
config.base_url = globalConfig?.base_url || '';
}
}
return this.mergeWithGlobalConfig(config);
}
/**
* 合并全局配置到指定配置
* @param config 指定的配置
* @returns 合并后的配置
*/
private async mergeWithGlobalConfig(config: Config): Promise<Config> {
const globalConfig = await Config.findOne({
where: { global: true },
});
if (globalConfig) {
// 合并特定的全局配置项到实例配置
config.has_keyword_match = globalConfig.has_keyword_match;
config.has_paused = globalConfig.has_paused;
config.has_use_gpt = globalConfig.has_use_gpt;
config.has_mouse_close = globalConfig.has_mouse_close;
config.has_esc_close = globalConfig.has_esc_close;
}
// 检查 key 和 base_url,如果不存在则使用全局配置
config.llm_type = config.llm_type || globalConfig?.llm_type || 'chatgpt';
config.model = config.model || globalConfig?.model || 'gpt-3.5-turbo';
config.key = config.key || globalConfig?.key || '';
config.base_url = config.base_url || globalConfig?.base_url || '';
return config;
}
@@ -93,6 +108,7 @@ export class ConfigController {
config = await Config.findOne({
where: { instance_id: instanceId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
@@ -105,6 +121,7 @@ export class ConfigController {
config = await Config.findOne({
where: { platform_id: appId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
@@ -236,27 +253,7 @@ export class ConfigController {
appId: string | undefined;
instanceId: string | undefined;
}): Promise<boolean> {
let config = null;
if (instanceId) {
config = await Config.findOne({
where: { instance_id: instanceId },
});
return config?.active || false;
}
if (appId) {
config = await Config.findOne({
where: { platform_id: appId },
});
return config?.active || false;
}
config = await Config.findOne({
where: { global: true },
});
const config = await this.findConfig(appId, instanceId);
return config?.active || false;
}
@@ -283,37 +280,7 @@ export class ConfigController {
| DriverConfig
| undefined
> {
let config = null;
if (instanceId) {
config = await Config.findOne({
where: { instance_id: instanceId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
instance_id: instanceId,
});
}
}
if (!config && appId) {
config = await Config.findOne({
where: { platform_id: appId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
});
}
}
if (!config) {
config = await Config.findOne({
where: { global: true },
});
}
const config = await this.findConfig(appId, instanceId);
if (type === 'generic') {
return {
@@ -329,6 +296,8 @@ export class ConfigController {
defaultReply: config?.default_reply || '',
truncateWordCount: config?.truncate_word_count || 0,
truncateWordKey: config?.truncate_word_key || '',
jinritemaiDefaultReplyMatch:
config?.jinritemai_default_reply_match || '',
};
}
@@ -359,6 +328,8 @@ export class ConfigController {
hasUseGpt: config?.has_use_gpt || false,
hasMouseClose: config?.has_mouse_close || false,
hasEscClose: config?.has_esc_close || false,
hasTransfer: config?.has_transfer || false,
hasReplace: config?.has_replace || false,
};
}
@@ -387,38 +358,9 @@ export class ConfigController {
| PluginConfig
| DriverConfig;
}) {
let dbConfig = null;
if (instanceId) {
dbConfig = await Config.findOne({
where: { instance_id: instanceId },
});
if (!dbConfig) {
dbConfig = await Config.create({
platform_id: appId,
instance_id: instanceId,
});
}
} else if (appId) {
dbConfig = await Config.findOne({
where: { platform_id: appId },
});
if (!dbConfig) {
dbConfig = await Config.create({
platform_id: appId,
});
}
} else {
dbConfig = await Config.findOne({
where: { global: true },
});
if (!dbConfig) {
dbConfig = await Config.create({
global: true,
});
}
let dbConfig = await this.findConfig(appId, instanceId);
if (!dbConfig) {
return;
}
if (type === 'generic') {
@@ -434,6 +376,7 @@ export class ConfigController {
default_reply: config.defaultReply,
truncate_word_count: config.truncateWordCount,
truncate_word_key: config.truncateWordKey,
jinritemai_default_reply_match: config.jinritemaiDefaultReplyMatch,
});
} else if (type === 'llm') {
const config = cfg as LLMConfig;
@@ -469,6 +412,8 @@ export class ConfigController {
has_use_gpt: config.hasUseGpt,
has_mouse_close: config.hasMouseClose,
has_esc_close: config.hasEscClose,
has_transfer: config.hasTransfer,
has_replace: config.hasReplace,
});
} else {
const config = cfg as AccountConfig;
@@ -527,4 +472,44 @@ export class ConfigController {
return false;
}
/**
* 查找配置
* @param appId
* @param instanceId
* @returns
*/
private async findConfig(
appId: string | undefined,
instanceId: string | undefined,
): Promise<Config | null> {
let config = null;
if (instanceId) {
config = await Config.findOne({
where: { instance_id: instanceId },
});
if (config && config.active) {
return config;
}
}
if (!config && appId) {
config = await Config.findOne({
where: { platform_id: appId },
});
if (config && config.active) {
return config;
}
}
if (!config) {
config = await Config.findOne({
where: { global: true },
});
}
return config;
}
}
@@ -79,6 +79,7 @@ export class KeywordReplyController {
autoReplies.push({
keyword: String(keyword),
reply: String(reply),
mode: 'fuzzy', // 废弃字段,这里只是兼容旧数据
platform_id: String(platformId),
fuzzy,
has_regular,
+55 -5
View File
@@ -59,6 +59,12 @@ export class Config extends Model {
declare truncate_word_count: number; // 截断词数
declare truncate_word_key: string; // 截断词
declare has_transfer: boolean; // 是否开启关键词转接给其它客服
declare has_replace: boolean; // 是否开启关键词替换
declare jinritemai_default_reply_match: string; // 抖店默认回复
}
export async function checkAndAddFields(sequelize: Sequelize) {
@@ -68,7 +74,7 @@ export async function checkAndAddFields(sequelize: Sequelize) {
if (!tableDescription.has_esc_close) {
await sequelize.getQueryInterface().addColumn('n_config', 'has_esc_close', {
type: DataTypes.BOOLEAN,
allowNull: false,
allowNull: true,
defaultValue: true,
});
}
@@ -79,8 +85,8 @@ export async function checkAndAddFields(sequelize: Sequelize) {
.getQueryInterface()
.addColumn('n_config', 'truncate_word_count', {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 4000,
allowNull: true,
defaultValue: 210,
});
}
@@ -90,10 +96,39 @@ export async function checkAndAddFields(sequelize: Sequelize) {
.getQueryInterface()
.addColumn('n_config', 'truncate_word_key', {
type: DataTypes.STRING,
allowNull: false,
allowNull: true,
defaultValue: '',
});
}
// @ts-ignore
if (!tableDescription.has_transfer) {
await sequelize.getQueryInterface().addColumn('n_config', 'has_transfer', {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: true,
});
}
// @ts-ignore
if (!tableDescription.has_replace) {
await sequelize.getQueryInterface().addColumn('n_config', 'has_replace', {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: true,
});
}
// @ts-ignore
if (!tableDescription.jinritemai_default_reply_match) {
await sequelize
.getQueryInterface()
.addColumn('n_config', 'jinritemai_default_reply_match', {
type: DataTypes.STRING,
allowNull: true,
defaultValue: '很高兴为您服务,请问有什么可以帮您?',
});
}
}
export function initConfig(sequelize: Sequelize) {
@@ -225,13 +260,28 @@ export function initConfig(sequelize: Sequelize) {
truncate_word_count: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 4000,
defaultValue: 210,
},
truncate_word_key: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: '',
},
has_transfer: {
type: DataTypes.BOOLEAN,
defaultValue: true,
allowNull: true,
},
has_replace: {
type: DataTypes.BOOLEAN,
defaultValue: true,
allowNull: true,
},
jinritemai_default_reply_match: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: '很高兴为您服务,请问有什么可以帮您?',
},
},
{
sequelize,
+29 -12
View File
@@ -125,7 +125,7 @@ export class DispatchService {
public async syncConfig(): Promise<boolean> {
try {
const cfg = await this.configController.getConfigByType({
let cfg = await this.configController.getConfigByType({
appId: undefined,
instanceId: undefined,
type: 'driver',
@@ -140,10 +140,38 @@ export class DispatchService {
hasPaused = cfg.hasPaused || false;
}
cfg = await this.configController.getConfigByType({
appId: undefined,
instanceId: undefined,
type: 'generic',
});
if (!cfg) {
return false;
}
let jdr = '很高兴为您服务,请问有什么可以帮您?';
if ('jinritemaiDefaultReplyMatch' in cfg) {
jdr = cfg.jinritemaiDefaultReplyMatch || '';
}
let twkey = '';
if ('truncateWordKey' in cfg) {
twkey = cfg.truncateWordKey || '';
}
let twcount = 210;
if ('truncateWordCount' in cfg) {
twcount = cfg.truncateWordCount || 210;
}
await emitAndWait(this.io, 'strategyService-updateStatus', {
status: hasPaused
? StrategyServiceStatusEnum.STOPPED
: StrategyServiceStatusEnum.RUNNING,
jdr,
twkey,
twcount,
});
const instances = await Instance.findAll();
@@ -182,17 +210,6 @@ export class DispatchService {
}
}
public async updateStatus(status: StrategyServiceStatusEnum): Promise<any> {
try {
return await emitAndWait(this.io, 'strategyService-updateStatus', {
status,
});
} catch (error) {
console.error('Failed to update status', error);
return null;
}
}
public async getAllPlatforms(): Promise<Platform[]> {
const maxRetries = 10;
let attempt = 0;
+38 -20
View File
@@ -77,6 +77,8 @@ export class MessageService {
.reverse()
.find((msg) => msg.role === 'OTHER');
let hasDefaultReply = true;
let reply = {
type: 'TEXT',
content: cfg.default_reply || '当前消息有点多,我稍后再回复你',
@@ -85,14 +87,17 @@ export class MessageService {
if (!lastUserMsg) {
this.log.warn(`未匹配到用户消息,所以使用默认回复: ${reply.content}`);
} else {
// 检查是否需要转接
const isTransfer = await this.matchTransferKeyword(ctx, lastUserMsg);
if (isTransfer) {
this.log.info('需要转接');
return {
type: 'TRANSFER',
content: '',
};
if (cfg.has_transfer) {
// 检查是否需要转接
const isTransfer = await this.matchTransferKeyword(ctx, lastUserMsg);
if (isTransfer) {
this.log.info('需要转接');
hasDefaultReply = false;
return {
type: 'TRANSFER',
content: '',
};
}
}
// 再根据 context_count 去保留最后几条消息
@@ -115,17 +120,14 @@ export class MessageService {
if (data && data.content) {
this.log.success(`匹配关键词: ${data.content}`);
reply = data;
hasDefaultReply = false;
} else {
this.log.warn(`未匹配到关键词`);
}
}
// 最后检查是否使用 GPT 生成回复
if (
cfg.has_use_gpt &&
reply.content ===
(cfg.default_reply || '当前消息有点多,我稍后再回复你')
) {
if (cfg.has_use_gpt && hasDefaultReply) {
this.log.info(`开始使用 GPT 生成回复`);
const data = await this.getLLMResponse(cfg, ctx, messages);
@@ -133,15 +135,25 @@ export class MessageService {
if (data && data.content) {
this.log.success(`GPT 生成回复: ${data.content}`);
reply = data;
hasDefaultReply = false;
} else {
this.log.warn(`AI 回复生成失败`);
}
}
}
this.log.info('使用默认回复');
if (reply.type === 'TEXT') {
reply.content = await this.matchReplaceKeyword(ctx, reply.content);
if (hasDefaultReply) {
const replyContent = await this.choseRandomReply(reply.content);
reply = {
type: reply.type as MessageType,
content: replyContent,
};
}
if (cfg.has_replace) {
if (reply.type === 'TEXT') {
reply.content = await this.matchReplaceKeyword(ctx, reply.content);
}
}
return reply;
@@ -261,10 +273,7 @@ export class MessageService {
});
if (foundKeywordObj) {
const replies = foundKeywordObj.reply.split('[or]');
const chosenReply = specialTokenReplace(
replies[Math.floor(Math.random() * replies.length)],
);
const chosenReply = await this.choseRandomReply(foundKeywordObj.reply);
let msgType = 'TEXT';
if (chosenReply.includes('[@]') && chosenReply.includes('[/@]')) {
@@ -287,6 +296,15 @@ export class MessageService {
return null;
}
public async choseRandomReply(reply: string) {
const replies = reply.split('[or]');
const chosenReply = specialTokenReplace(
replies[Math.floor(Math.random() * replies.length)],
);
return chosenReply;
}
/**
* 检查 LLM 是否可用
*/
+3
View File
@@ -51,6 +51,7 @@ export interface GenericConfig {
defaultReply: string;
truncateWordCount: number;
truncateWordKey: string;
jinritemaiDefaultReplyMatch: string;
}
export interface LLMConfig {
@@ -79,4 +80,6 @@ export interface DriverConfig {
hasUseGpt: boolean;
hasMouseClose: boolean;
hasEscClose: boolean;
hasTransfer: boolean;
hasReplace: boolean;
}
+3
View File
@@ -74,6 +74,7 @@ export interface GenericConfig {
defaultReply: string;
truncateWordCount: number;
truncateWordKey: string;
jinritemaiDefaultReplyMatch: string;
}
export interface LLMConfig {
@@ -102,6 +103,8 @@ export interface DriverConfig {
hasUseGpt: boolean;
hasMouseClose: boolean;
hasEscClose: boolean;
hasTransfer: boolean;
hasReplace: boolean;
}
export interface Session {
@@ -18,6 +18,8 @@ const Panels = () => {
hasUseGpt: false,
hasMouseClose: true,
hasEscClose: true,
hasTransfer: true,
hasReplace: true,
});
const { data } = useQuery(['config', 'driver'], async () => {
@@ -38,22 +40,25 @@ const Panels = () => {
});
// eslint-disable-next-line react-hooks/exhaustive-deps
const pausedHandler = useCallback((message: any) => {
if (message.event === 'has_paused') {
setDriverSettings((prevSettings) => ({
...prevSettings,
hasPaused: true,
}));
const pausedHandler = useCallback(
(message: any) => {
if (message.event === 'has_paused') {
setDriverSettings((prevSettings) => ({
...prevSettings,
hasPaused: true,
}));
toast({
title: '自动回复已暂停',
status: 'info',
position: 'top',
duration: 5000,
isClosable: true,
});
}
}, []);
toast({
title: '自动回复已暂停',
status: 'info',
position: 'top',
duration: 5000,
isClosable: true,
});
}
},
[toast],
);
useEffect(() => {
const unregister = registerEventHandler(pausedHandler);
@@ -149,7 +154,27 @@ const Panels = () => {
</Tooltip>
</Checkbox> */}
<Checkbox
isChecked={driverSettings.hasEscClose}
isChecked={driverSettings.hasTransfer}
onChange={(e) =>
handleUpdateConfig({ hasTransfer: e.target.checked })
}
>
<Tooltip label="如果匹配到设定的关键词,将自动转人工">
</Tooltip>
</Checkbox>
<Checkbox
isChecked={driverSettings.hasReplace}
onChange={(e) =>
handleUpdateConfig({ hasReplace: e.target.checked })
}
>
<Tooltip label="如果匹配到设定的关键词,将自动替换成自定义的关键词">
</Tooltip>
</Checkbox>
<Checkbox
isChecked={driverSettings.hasReplace}
onChange={(e) =>
handleUpdateConfig({ hasEscClose: e.target.checked })
}
@@ -0,0 +1,59 @@
import React from 'react';
import {
Button,
Stack,
Text,
Icon,
Box,
Tooltip,
Flex,
} from '@chakra-ui/react';
import { FiHelpCircle } from 'react-icons/fi';
import { AddIcon } from '@chakra-ui/icons';
import MyTextarea from '../../../common/components/MyTextarea';
type ReplyInputProps = {
newReply: string;
setNewReply: (value: string) => void;
handleAddReply: () => void;
handleInsertRandomChar: () => void;
};
const ReplyInput = ({
newReply,
setNewReply,
handleAddReply,
handleInsertRandomChar,
}: ReplyInputProps) => (
<>
<Flex mb="8px" mt="22px">
<Text mr={2} fontSize={'large'} fontWeight={'bold'}>
</Text>
<Tooltip label="添加的多个关键词只要一个匹配上了,将会触发回复,如果有多个回复,将会随机选择一个回复。">
<Box color={'gray.500'}>
<Icon as={FiHelpCircle} w={6} h={6} />
</Box>
</Tooltip>
</Flex>
<Tooltip label="在拼多多平台等平台,是不允许每次重复一个回答的,所以可以插入一个随机符,以规避这个问题">
<Button onClick={handleInsertRandomChar} mt="4" mr={4} colorScheme="teal">
</Button>
</Tooltip>
<Stack direction="row" mt="4">
<MyTextarea
mb="4"
maxLength={200}
placeholder="回复内容"
value={newReply}
onChange={(e) => setNewReply(e.target.value)}
/>
<Button onClick={handleAddReply} colorScheme="green">
<AddIcon />
</Button>
</Stack>
</>
);
export default ReplyInput;
@@ -0,0 +1,66 @@
import React, { useState, useEffect } from 'react';
import {
Button,
Input,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
Stack,
Text,
Icon,
HStack,
Box,
Switch,
Select,
IconButton,
Tooltip,
Flex,
useToast,
} from '@chakra-ui/react';
import { DeleteIcon } from '@chakra-ui/icons';
type ReplyListProps = {
replyList: string[];
handleReplyClick: (reply: string, index: number) => void;
handleDeleteReply: (index: number) => void;
};
const ReplyList = ({
replyList,
handleReplyClick,
handleDeleteReply,
}: ReplyListProps) => (
<Stack direction="row" spacing={4} wrap="wrap">
{replyList.map((item, index) => (
<Text
key={index}
p="1"
borderRadius="md"
borderWidth="1px"
cursor="pointer"
maxWidth="220px"
onClick={() => handleReplyClick(item, index)}
style={{ whiteSpace: 'normal', wordWrap: 'break-word' }}
>
{item}
<IconButton
ml={3}
aria-label="Delete reply"
icon={<DeleteIcon />}
colorScheme="red"
size="xs"
onClick={(e) => {
e.stopPropagation();
handleDeleteReply(index);
}}
/>
</Text>
))}
</Stack>
);
export default ReplyList;
@@ -0,0 +1,123 @@
import React, { useState, useEffect } from 'react';
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
useToast,
} from '@chakra-ui/react';
import ReplyInput from './ReplyInput';
import ReplyList from './ReplyList';
type EditKeywordProps = {
reply: string;
isOpen: boolean;
onClose: () => void;
handleEdit: (val: string) => void;
};
const EditKeyword = ({
reply,
isOpen,
onClose,
handleEdit,
}: EditKeywordProps) => {
const toast = useToast();
const [replyList, setReplyList] = useState<string[]>(
reply.split('[or]') || [],
);
const [newReply, setNewReply] = useState<string>('');
useEffect(() => {
if (!reply) {
setReplyList([]);
} else {
setReplyList(reply.split('[or]') || []);
}
}, [reply]);
const handleAddReply = () => {
if (newReply) {
setReplyList([...replyList, newReply]);
setNewReply('');
}
};
const handleDeleteReply = (index: number) => {
setReplyList(replyList.filter((_, i) => i !== index));
};
const handleInsertRandomChar = () => {
setNewReply(`${newReply}[~]`);
};
const handleReplyClick = (item: string, index: number) => {
setNewReply(item);
handleDeleteReply(index);
};
const handleSave = async () => {
try {
if (reply === '') {
throw new Error('回复内容不能为空');
}
await handleEdit(replyList.join('[or]'));
toast({
position: 'top',
title: '保存成功',
status: 'success',
duration: 3000,
isClosable: true,
});
} catch (error: any) {
toast({
position: 'top',
title: error.message,
status: 'error',
duration: 3000,
isClosable: true,
});
} finally {
onClose();
}
};
return (
<Modal isOpen={isOpen} onClose={onClose} size={'4xl'}>
<ModalOverlay />
<ModalContent>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<ModalBody>
<ReplyInput
newReply={newReply}
setNewReply={setNewReply}
handleAddReply={handleAddReply}
handleInsertRandomChar={handleInsertRandomChar}
/>
<ReplyList
replyList={replyList}
handleReplyClick={handleReplyClick}
handleDeleteReply={handleDeleteReply}
/>
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" mr={3} onClick={handleSave}>
</Button>
<Button variant="ghost" onClick={onClose}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default EditKeyword;
@@ -24,6 +24,7 @@ import {
useToast,
Stack,
Skeleton,
useDisclosure,
} from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import {
@@ -31,6 +32,7 @@ import {
updateConfig,
} from '../../../common/services/platform/controller';
import { GenericConfig } from '../../../common/services/platform/platform.d';
import EditKeyword from '../EditKeyword';
const GeneralSettings = ({
appId,
@@ -42,6 +44,12 @@ const GeneralSettings = ({
style?: React.CSSProperties;
}) => {
const toast = useToast();
const {
isOpen: isOpenEditKeyword,
onOpen: onOpenEditKeyword,
onClose: onCloseEditKeyword,
} = useDisclosure();
const { data, isLoading } = useQuery(
['config', 'generic', appId, instanceId],
async () => {
@@ -204,10 +212,14 @@ const GeneralSettings = ({
<Input
placeholder="输入默认回复内容"
value={config.defaultReply}
disabled
onChange={(e) =>
handleUpdateConfig({ defaultReply: e.target.value })
}
/>
<Button ml={2} onClick={onOpenEditKeyword}>
</Button>
</Flex>
</FormControl>
@@ -296,7 +308,7 @@ const GeneralSettings = ({
</Flex>
<Slider
min={50}
max={4000}
max={210}
step={5}
value={config.truncateWordCount}
onChange={(truncateWordCount) =>
@@ -327,6 +339,34 @@ const GeneralSettings = ({
/>
</Flex>
</FormControl>
<FormControl mt={3}>
<FormLabel>
{' '}
<Tooltip label="因为抖店默认有个不可关闭的首条自动回复,所以如果要自动回复后还能继续回复,这里需要设置的和抖店那个回复内容一致">
<Text mb="8px"></Text>
</Tooltip>
</FormLabel>
<Flex>
<Input
placeholder="抖店默认首条回复"
max={5}
value={config.jinritemaiDefaultReplyMatch}
onChange={(e) =>
handleUpdateConfig({
jinritemaiDefaultReplyMatch: e.target.value,
})
}
/>
</Flex>
</FormControl>
<EditKeyword
isOpen={isOpenEditKeyword}
onClose={onCloseEditKeyword}
reply={config.defaultReply}
handleEdit={(reply) => handleUpdateConfig({ defaultReply: reply })}
/>
</VStack>
);
};