From 9643649efabe6f3c51b961d5473aeb8909d19a61 Mon Sep 17 00:00:00 2001 From: lrhh123 Date: Sun, 30 Jun 2024 21:37:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=85=8D=E7=BD=AE=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +- .../backend/controllers/configController.ts | 233 ++++++++---------- .../controllers/keywordReplyController.ts | 1 + src/main/backend/entities/config.ts | 60 ++++- src/main/backend/services/dispatchService.ts | 41 ++- src/main/backend/services/messageService.ts | 58 +++-- src/main/backend/types/index.ts | 3 + .../common/services/platform/platform.d.ts | 3 + .../main-window/components/Panels/index.tsx | 57 +++-- .../components/EditKeyword/ReplyInput.tsx | 59 +++++ .../components/EditKeyword/ReplyList.tsx | 66 +++++ .../components/EditKeyword/index.tsx | 123 +++++++++ .../components/Settings/GeneralSettings.tsx | 42 +++- 13 files changed, 570 insertions(+), 179 deletions(-) create mode 100644 src/renderer/settings-window/components/EditKeyword/ReplyInput.tsx create mode 100644 src/renderer/settings-window/components/EditKeyword/ReplyList.tsx create mode 100644 src/renderer/settings-window/components/EditKeyword/index.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 24fa12e..36b333a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,6 +28,7 @@ "*.{css,sass,scss}.d.ts": true }, "cSpell.words": [ - "dify" + "dify", + "jinritemai" ] } diff --git a/src/main/backend/controllers/configController.ts b/src/main/backend/controllers/configController.ts index 04ca13b..ab148bf 100644 --- a/src/main/backend/controllers/configController.ts +++ b/src/main/backend/controllers/configController.ts @@ -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 { + 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 { - 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 { + 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; + } } diff --git a/src/main/backend/controllers/keywordReplyController.ts b/src/main/backend/controllers/keywordReplyController.ts index 735e767..558f150 100644 --- a/src/main/backend/controllers/keywordReplyController.ts +++ b/src/main/backend/controllers/keywordReplyController.ts @@ -79,6 +79,7 @@ export class KeywordReplyController { autoReplies.push({ keyword: String(keyword), reply: String(reply), + mode: 'fuzzy', // 废弃字段,这里只是兼容旧数据 platform_id: String(platformId), fuzzy, has_regular, diff --git a/src/main/backend/entities/config.ts b/src/main/backend/entities/config.ts index 844606c..46049af 100644 --- a/src/main/backend/entities/config.ts +++ b/src/main/backend/entities/config.ts @@ -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, diff --git a/src/main/backend/services/dispatchService.ts b/src/main/backend/services/dispatchService.ts index 2b8a911..2192f6a 100644 --- a/src/main/backend/services/dispatchService.ts +++ b/src/main/backend/services/dispatchService.ts @@ -125,7 +125,7 @@ export class DispatchService { public async syncConfig(): Promise { 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 { - try { - return await emitAndWait(this.io, 'strategyService-updateStatus', { - status, - }); - } catch (error) { - console.error('Failed to update status', error); - return null; - } - } - public async getAllPlatforms(): Promise { const maxRetries = 10; let attempt = 0; diff --git a/src/main/backend/services/messageService.ts b/src/main/backend/services/messageService.ts index 13d9ddb..6db8af7 100644 --- a/src/main/backend/services/messageService.ts +++ b/src/main/backend/services/messageService.ts @@ -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 是否可用 */ diff --git a/src/main/backend/types/index.ts b/src/main/backend/types/index.ts index 70f7d63..c86b204 100644 --- a/src/main/backend/types/index.ts +++ b/src/main/backend/types/index.ts @@ -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; } diff --git a/src/renderer/common/services/platform/platform.d.ts b/src/renderer/common/services/platform/platform.d.ts index 1b33153..253cfe1 100644 --- a/src/renderer/common/services/platform/platform.d.ts +++ b/src/renderer/common/services/platform/platform.d.ts @@ -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 { diff --git a/src/renderer/main-window/components/Panels/index.tsx b/src/renderer/main-window/components/Panels/index.tsx index f843d80..4e04397 100644 --- a/src/renderer/main-window/components/Panels/index.tsx +++ b/src/renderer/main-window/components/Panels/index.tsx @@ -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 = () => { */} + handleUpdateConfig({ hasTransfer: e.target.checked }) + } + > + + 关键词转人工 + + + + handleUpdateConfig({ hasReplace: e.target.checked }) + } + > + + 关键词替换 + + + handleUpdateConfig({ hasEscClose: e.target.checked }) } diff --git a/src/renderer/settings-window/components/EditKeyword/ReplyInput.tsx b/src/renderer/settings-window/components/EditKeyword/ReplyInput.tsx new file mode 100644 index 0000000..2b34f3d --- /dev/null +++ b/src/renderer/settings-window/components/EditKeyword/ReplyInput.tsx @@ -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) => ( + <> + + + 回复内容 + + + + + + + + + + + + setNewReply(e.target.value)} + /> + + + +); + +export default ReplyInput; diff --git a/src/renderer/settings-window/components/EditKeyword/ReplyList.tsx b/src/renderer/settings-window/components/EditKeyword/ReplyList.tsx new file mode 100644 index 0000000..089fc9a --- /dev/null +++ b/src/renderer/settings-window/components/EditKeyword/ReplyList.tsx @@ -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) => ( + + {replyList.map((item, index) => ( + handleReplyClick(item, index)} + style={{ whiteSpace: 'normal', wordWrap: 'break-word' }} + > + {item} + } + colorScheme="red" + size="xs" + onClick={(e) => { + e.stopPropagation(); + handleDeleteReply(index); + }} + /> + + ))} + +); + +export default ReplyList; diff --git a/src/renderer/settings-window/components/EditKeyword/index.tsx b/src/renderer/settings-window/components/EditKeyword/index.tsx new file mode 100644 index 0000000..a7512b6 --- /dev/null +++ b/src/renderer/settings-window/components/EditKeyword/index.tsx @@ -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( + reply.split('[or]') || [], + ); + + const [newReply, setNewReply] = useState(''); + + 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 ( + + + + 编辑默认回复 + + + + + + + + + + + + ); +}; + +export default EditKeyword; diff --git a/src/renderer/settings-window/components/Settings/GeneralSettings.tsx b/src/renderer/settings-window/components/Settings/GeneralSettings.tsx index 7fad126..7a9096d 100644 --- a/src/renderer/settings-window/components/Settings/GeneralSettings.tsx +++ b/src/renderer/settings-window/components/Settings/GeneralSettings.tsx @@ -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 = ({ handleUpdateConfig({ defaultReply: e.target.value }) } /> + @@ -296,7 +308,7 @@ const GeneralSettings = ({ @@ -327,6 +339,34 @@ const GeneralSettings = ({ /> + + + + {' '} + + 抖店默认首条回复 + + + + + handleUpdateConfig({ + jinritemaiDefaultReplyMatch: e.target.value, + }) + } + /> + + + + handleUpdateConfig({ defaultReply: reply })} + /> ); };