diff --git a/CHANGELOG.md b/CHANGELOG.md index 5310ab5..c8139de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,7 @@ - Add experimental support for vscode debugging - Revert https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2365 as default for users, provide as opt in option -# 1.1.0 +# 1.2.0-beta.1 - Fix #2402 - Simplify configs (https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2406) diff --git a/README.md b/README.md index dcb6547..e9ac560 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ ## 下载地址 -点击下载 +点击下载 如果网络环境导致下载不了,可以使用百度云盘下载: diff --git a/package.json b/package.json index c229259..773921f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "chatgpt-on-cs", "description": "多平台智能客服,允许使用 ChatGPT 作为客服机器人", - "version": "1.1.0", + "version": "1.2.0-beta.1", "keywords": [ "electron", "boilerplate", diff --git a/release/app/package-lock.json b/release/app/package-lock.json index 65bbd6f..bd6b4fc 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "chatgpt-on-cs", - "version": "1.1.0", + "version": "1.2.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/release/app/package.json b/release/app/package.json index 40d6732..352b272 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "chatgpt-on-cs", - "version": "1.1.0", + "version": "1.2.0-beta.1", "description": "多平台智能客服,允许使用 ChatGPT 作为客服机器人", "license": "AGPL-3.0", "author": { diff --git a/src/main/backend/backend.ts b/src/main/backend/backend.ts index 7333778..b96b720 100644 --- a/src/main/backend/backend.ts +++ b/src/main/backend/backend.ts @@ -284,6 +284,8 @@ class BKServer { keyword: item.keyword, reply: item.reply, mode: item.mode, + fuzzy: item.fuzzy, + has_regular: item.has_regular, app_name: ptf ? ptf.name : '全局', }; @@ -300,23 +302,42 @@ class BKServer { }); this.app.post('/api/v1/reply/create', async (req, res) => { - const { platform_id: platformId, keyword, reply, mode } = req.body; + const { + platform_id: platformId, + keyword, + reply, + mode, + fuzzy, + has_regular, + } = req.body; await this.keywordReplyController.create({ mode, platform_id: platformId, keyword, reply, + fuzzy, + has_regular, }); res.json({ success: true }); }); this.app.post('/api/v1/reply/update', async (req, res) => { - const { id, platform_id: platformId, keyword, reply, mode } = req.body; + const { + id, + platform_id: platformId, + keyword, + reply, + mode, + fuzzy, + has_regular, + } = req.body; await this.keywordReplyController.update(id, { mode, platform_id: platformId, keyword, reply, + fuzzy, + has_regular, }); res.json({ success: true }); }); @@ -474,6 +495,8 @@ class BKServer { replace: item.replace, app_id: item.app_id, app_name: ptf ? ptf.name : '全局', + has_regular: item.has_regular, + fuzzy: item.fuzzy, }; results.push(result); diff --git a/src/main/backend/controllers/keywordReplyController.ts b/src/main/backend/controllers/keywordReplyController.ts index f144b72..735e767 100644 --- a/src/main/backend/controllers/keywordReplyController.ts +++ b/src/main/backend/controllers/keywordReplyController.ts @@ -68,6 +68,8 @@ export class KeywordReplyController { const keyword = row.getCell(1).text.trim(); const reply = row.getCell(2).text.trim(); const platform = row.getCell(3).text.trim(); + const fuzzy = row.getCell(4).text.trim() === '是'; + const has_regular = row.getCell(5).text.trim() === '是'; let platformId = ''; if (platform && platformMap.has(platform)) { @@ -78,7 +80,8 @@ export class KeywordReplyController { keyword: String(keyword), reply: String(reply), platform_id: String(platformId), - mode: 'fuzzy', + fuzzy, + has_regular, }); } }); @@ -122,6 +125,8 @@ export class KeywordReplyController { { key: 'keyword', width: 32 }, { key: 'reply', width: 100 }, { key: 'platform', width: 32 }, + { key: 'fuzzy', width: 10 }, + { key: 'has_regular', width: 10 }, ]; // 添加复杂的标题描述并设置换行和加粗 @@ -149,12 +154,24 @@ export class KeywordReplyController { }; worksheet.getRow(1).height = 150; - worksheet.addRow(['匹配关键词', '回复内容', '平台']); + worksheet.addRow([ + '匹配关键词', + '回复内容', + '平台', + '模糊匹配', + '支持正则', + ]); // 添加数据行 autoReplies.forEach((autoReply) => { const name = platformMap.get(autoReply.platform_id) || ''; - worksheet.addRow([autoReply.keyword, autoReply.reply, name]); + worksheet.addRow([ + autoReply.keyword, + autoReply.reply, + name, + autoReply.fuzzy ? '是' : '否', + autoReply.has_regular ? '是' : '否', + ]); }); // 检查是否存在 excels 文件夹,不存在则创建 @@ -186,6 +203,42 @@ export class KeywordReplyController { return [...globalKeywords, ...autoReplies]; } + async getReplaceKeywords(platformId: string) { + const replaceKeywords = await ReplaceKeyword.findAll({ + where: { + app_id: platformId, + }, + }); + + const globalKeywords = await ReplaceKeyword.findAll({ + where: { + app_id: { + [Op.or]: [null, ''], + }, + }, + }); + + return [...globalKeywords, ...replaceKeywords]; + } + + async getTransferKeywords(platformId: string) { + const transferKeywords = await TransferKeyword.findAll({ + where: { + app_id: platformId, + }, + }); + + const globalKeywords = await TransferKeyword.findAll({ + where: { + app_id: { + [Op.or]: [null, ''], + }, + }, + }); + + return [...globalKeywords, ...transferKeywords]; + } + async list({ page, pageSize, @@ -409,8 +462,8 @@ export class KeywordReplyController { const name = platformMap.get(autoReply.app_id) || ''; worksheet.addRow([ autoReply.keyword, - autoReply.fuzzy, - autoReply.has_regular, + autoReply.fuzzy ? '是' : '否', + autoReply.has_regular ? '是' : '否', name, ]); }); diff --git a/src/main/backend/entities/keyword.ts b/src/main/backend/entities/keyword.ts index a4b800c..3648683 100644 --- a/src/main/backend/entities/keyword.ts +++ b/src/main/backend/entities/keyword.ts @@ -11,6 +11,32 @@ export class Keyword extends Model { declare mode: string; declare platform_id: string; + + declare fuzzy: boolean; + + declare has_regular: boolean; +} + +export async function checkAndAddFields(sequelize: Sequelize) { + const tableDescription = await Keyword.describe(); + + // @ts-ignore + if (!tableDescription.fuzzy) { + await sequelize.getQueryInterface().addColumn('keyword', 'fuzzy', { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: true, + }); + } + + // @ts-ignore + if (!tableDescription.has_regular) { + await sequelize.getQueryInterface().addColumn('keyword', 'has_regular', { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: false, + }); + } } export function initKeyword(sequelize: Sequelize) { @@ -37,6 +63,16 @@ export function initKeyword(sequelize: Sequelize) { type: DataTypes.STRING(255), allowNull: true, }, + fuzzy: { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: true, + }, + has_regular: { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: false, + }, }, { sequelize, @@ -45,4 +81,6 @@ export function initKeyword(sequelize: Sequelize) { timestamps: false, }, ); + + checkAndAddFields(sequelize); } diff --git a/src/main/backend/entities/plugin.ts b/src/main/backend/entities/plugin.ts index d92e04d..63d1605 100644 --- a/src/main/backend/entities/plugin.ts +++ b/src/main/backend/entities/plugin.ts @@ -128,7 +128,7 @@ export function initPlugin(sequelize: Sequelize) { }, version: { type: DataTypes.STRING(255), - defaultValue: '1.1.0', + defaultValue: '1.2.0-beta.1', allowNull: true, }, source: { diff --git a/src/main/backend/entities/replace.ts b/src/main/backend/entities/replace.ts index b560cd2..15f52c4 100644 --- a/src/main/backend/entities/replace.ts +++ b/src/main/backend/entities/replace.ts @@ -33,7 +33,8 @@ export function initReplace(sequelize: Sequelize) { }, has_regular: { type: DataTypes.BOOLEAN, - allowNull: false, + allowNull: true, + defaultValue: false, }, app_id: { type: DataTypes.STRING(255), diff --git a/src/main/backend/entities/transfer.ts b/src/main/backend/entities/transfer.ts index 0f0aa6f..0b42cfa 100644 --- a/src/main/backend/entities/transfer.ts +++ b/src/main/backend/entities/transfer.ts @@ -27,7 +27,8 @@ export function initTransfer(sequelize: Sequelize) { }, has_regular: { type: DataTypes.BOOLEAN, - allowNull: false, + allowNull: true, + defaultValue: false, }, app_id: { type: DataTypes.STRING(255), diff --git a/src/main/backend/services/messageService.ts b/src/main/backend/services/messageService.ts index 47c1113..13d9ddb 100644 --- a/src/main/backend/services/messageService.ts +++ b/src/main/backend/services/messageService.ts @@ -17,7 +17,11 @@ import { CTX_FAN_TAG, CTX_NEW_CUSTOMER_TAG, } from '../constants'; -import { rangeMatch, specialTokenReplace } from '../../utils/strings'; +import { + rangeMatch, + specialTokenReplace, + replaceKeyword, +} from '../../utils/strings'; import { ErnieAI, GeminiAI, @@ -55,66 +59,91 @@ export class MessageService { this.llmClientMap = new Map(); } + /** + * 获取默认回复 + * @param cfg + * @param ctx + * @param messages + * @returns + */ public async getDefaultReply( cfg: Config, ctx: Context, messages: MessageDTO[], - ) { + ): Promise<{ type: string; content: string }> { // 先检查是否存在用户的消息 const lastUserMsg = messages .slice() .reverse() .find((msg) => msg.role === 'OTHER'); - const reply = { + let reply = { type: 'TEXT', content: cfg.default_reply || '当前消息有点多,我稍后再回复你', }; if (!lastUserMsg) { this.log.warn(`未匹配到用户消息,所以使用默认回复: ${reply.content}`); - return reply; - } - - // 再根据 context_count 去保留最后几条消息 - if (cfg.context_count > 0) { - // eslint-disable-next-line no-param-reassign - messages = messages.slice(-cfg.context_count); - } - - // 等待随机时间 - await new Promise((resolve) => { - const min = cfg.reply_speed; - const max = cfg.reply_random_speed + cfg.reply_speed; - const randomTime = min + Math.random() * (max - min); - setTimeout(resolve, randomTime * 1000); - }); - - // 再检查是否使用关键词匹配 - if (cfg.has_keyword_match) { - const data = await this.matchKeyword(ctx, lastUserMsg); - if (data && data.content) { - this.log.success(`匹配关键词: ${data.content}`); - return data; - } - this.log.warn(`未匹配到关键词`); - } - - // 最后检查是否使用 GPT 生成回复 - if (cfg.has_use_gpt) { - this.log.info(`开始使用 GPT 生成回复`); - - const data = await this.getLLMResponse(cfg, ctx, messages); - - if (data && data.content) { - this.log.success(`GPT 生成回复: ${data.content}`); - return data; + } else { + // 检查是否需要转接 + const isTransfer = await this.matchTransferKeyword(ctx, lastUserMsg); + if (isTransfer) { + this.log.info('需要转接'); + return { + type: 'TRANSFER', + content: '', + }; } - this.log.warn(`AI 回复生成失败`); + // 再根据 context_count 去保留最后几条消息 + if (cfg.context_count > 0) { + // eslint-disable-next-line no-param-reassign + messages = messages.slice(-cfg.context_count); + } + + // 等待随机时间 + await new Promise((resolve) => { + const min = cfg.reply_speed; + const max = cfg.reply_random_speed + cfg.reply_speed; + const randomTime = min + Math.random() * (max - min); + setTimeout(resolve, randomTime * 1000); + }); + + // 再检查是否使用关键词匹配 + if (cfg.has_keyword_match) { + const data = await this.matchKeyword(ctx, lastUserMsg); + if (data && data.content) { + this.log.success(`匹配关键词: ${data.content}`); + reply = data; + } else { + this.log.warn(`未匹配到关键词`); + } + } + + // 最后检查是否使用 GPT 生成回复 + if ( + cfg.has_use_gpt && + reply.content === + (cfg.default_reply || '当前消息有点多,我稍后再回复你') + ) { + this.log.info(`开始使用 GPT 生成回复`); + + const data = await this.getLLMResponse(cfg, ctx, messages); + + if (data && data.content) { + this.log.success(`GPT 生成回复: ${data.content}`); + reply = data; + } else { + this.log.warn(`AI 回复生成失败`); + } + } } this.log.info('使用默认回复'); + if (reply.type === 'TEXT') { + reply.content = await this.matchReplaceKeyword(ctx, reply.content); + } + return reply; } @@ -125,6 +154,85 @@ export class MessageService { }; } + /** + * 匹配需要替换的关键词 + * @param ctx + * @param message + * @returns + */ + public async matchReplaceKeyword( + ctx: Context, + reply: string, + ): Promise { + const appId = ctx.get(CTX_APP_ID); + if (!appId) return reply; + + const replaceKeywords = + await this.autoReplyController.getReplaceKeywords(appId); + + // 先找到匹配的关键词 + const foundKeywordObj = replaceKeywords.find((keywordObj) => { + return keywordObj.keyword.split('|').some((pattern) => { + return rangeMatch( + pattern, + reply, + keywordObj.fuzzy, + keywordObj.has_regular, + ); + }); + }); + + // 如果找到匹配的关键词对象,进行替换 + if (foundKeywordObj) { + foundKeywordObj.keyword.split('|').forEach((pattern) => { + // eslint-disable-next-line no-param-reassign + reply = replaceKeyword( + pattern, + reply, + foundKeywordObj.replace, + foundKeywordObj.fuzzy, + foundKeywordObj.has_regular, + ); + }); + } + + return reply; + } + + /** + * 匹配关键词 + * @param ctx + * @param message + * @returns + */ + public async matchTransferKeyword( + ctx: Context, + message: MessageDTO, + ): Promise { + const appId = ctx.get(CTX_APP_ID); + if (!appId) return false; + + const keywords = await this.autoReplyController.getTransferKeywords(appId); + + // 先找到匹配的关键词 + const foundKeywordObj = keywords.find((keywordObj) => { + return keywordObj.keyword.split('|').some((pattern) => { + return rangeMatch( + pattern, + message.content, + keywordObj.fuzzy, + keywordObj.has_regular, + ); + }); + }); + + if (foundKeywordObj) { + return true; + } + + return false; + } + /** * 匹配关键词 * @param ctx @@ -143,7 +251,12 @@ export class MessageService { // 先找到匹配的关键词 const foundKeywordObj = keywords.find((keywordObj) => { return keywordObj.keyword.split('|').some((pattern) => { - return rangeMatch(pattern, message.content); + return rangeMatch( + pattern, + message.content, + keywordObj.fuzzy, + keywordObj.has_regular, + ); }); }); diff --git a/src/main/utils/strings.ts b/src/main/utils/strings.ts index bdfb93b..75fe0e9 100644 --- a/src/main/utils/strings.ts +++ b/src/main/utils/strings.ts @@ -2,56 +2,195 @@ * 范围匹配 * @param ptt 范围查询关键词 e.g. 'hello [and] world' * @param msg 消息 + * @param fuzzy 是否模糊匹配 + * @param has_regular 是否使用正则表达式 * @returns */ -export function rangeMatch(ptt: string, msg: string): boolean { +export function rangeMatch( + ptt: string, + msg: string, + fuzzy: boolean, + has_regular: boolean, +): boolean { if (ptt.includes('[and]')) { const keywords = ptt.split('[and]'); - return keywords.every((keyword) => matchKeyword(keyword.trim(), msg)); + return keywords.every((keyword, index) => { + if (index === 0) { + return matchKeyword(keyword.trim(), msg, fuzzy, has_regular); + } + const prevKeyword = keywords[index - 1].trim(); + const prevIndex = msg.indexOf(prevKeyword); + if (prevIndex === -1) { + return false; + } + return matchKeyword( + keyword.trim(), + msg.slice(prevIndex + prevKeyword.length), + fuzzy, + has_regular, + ); + }); } - return matchKeyword(ptt, msg); + return matchKeyword(ptt, msg, fuzzy, has_regular); } /** * 匹配关键词 * @param ptt 匹配模式 * @param msg 消息 + * @param fuzzy 是否模糊匹配 + * @param has_regular 是否使用正则表达式 * @returns */ -export function matchKeyword(ptt: string, msg: string): boolean { - let pattern = ptt.trim(); +export function matchKeyword( + ptt: string, + msg: string, + fuzzy: boolean, + has_regular: boolean, +): boolean { + try { + const pattern = ptt.trim(); + if (has_regular) { + const regex = new RegExp(pattern); + return regex.test(msg); + } - // 如果模式只是一个星号,它应该匹配任何消息。 - if (pattern === '*') { - return true; + if (fuzzy) { + return msg.includes(pattern); + } + + return msg.trim() === pattern; + } catch (e) { + console.error(e); + return false; + } +} + +/** + * 范围匹配 + * @param ptt 范围查询关键词 e.g. 'hello [and] world' + * @param msg 消息 + * @param fuzzy 是否模糊匹配 + * @param has_regular 是否使用正则表达式 + * @returns 匹配到的开始和结束位置数组 + */ +export function rangeMatchPosition( + ptt: string, + msg: string, + fuzzy: boolean, + has_regular: boolean, +): Array<[number, number]> { + const matchPositions: Array<[number, number]> = []; + + if (ptt.includes('[and]')) { + const keywords = ptt.split('[and]'); + let searchStart = 0; + // eslint-disable-next-line no-restricted-syntax + for (const keyword of keywords) { + const trimmedKeyword = keyword.trim(); + const matchPosition = matchKeywordPosition( + trimmedKeyword, + msg, + fuzzy, + has_regular, + searchStart, + ); + if (matchPosition) { + matchPositions.push(matchPosition); + // eslint-disable-next-line prefer-destructuring + searchStart = matchPosition[1]; + } else { + return []; + } + } + } else { + const matchPosition = matchKeywordPosition(ptt, msg, fuzzy, has_regular, 0); + if (matchPosition) { + matchPositions.push(matchPosition); + } } - // 合并连续的 '*' 字符为一个 '*' - pattern = pattern.replace(/\*+/g, '*'); + return matchPositions; +} - // 如果模式不包含 '*',则检查是否包含关键词 - if (!pattern.includes('*')) { - return msg.includes(pattern); +/** + * 匹配关键词并返回位置 + * @param ptt 匹配模式 + * @param msg 消息 + * @param fuzzy 是否模糊匹配 + * @param has_regular 是否使用正则表达式 + * @param searchStart 搜索的起始位置 + * @returns 匹配到的开始和结束位置 + */ +function matchKeywordPosition( + ptt: string, + msg: string, + fuzzy: boolean, + has_regular: boolean, + searchStart: number, +): [number, number] | null { + const pattern = ptt.trim(); + + if (has_regular) { + const regex = new RegExp(pattern); + const match = regex.exec(msg.slice(searchStart)); + if (match) { + return [ + searchStart + match.index, + searchStart + match.index + match[0].length, + ]; + } + return null; } - const parts = pattern.split('*'); + if (fuzzy) { + const index = msg.indexOf(pattern, searchStart); + if (index !== -1) { + return [index, index + pattern.length]; + } + return null; + } + + const index = msg.indexOf(pattern, searchStart); + if (index !== -1 && index + pattern.length === msg.length) { + return [index, index + pattern.length]; + } + + return null; +} + +/** + * 替换匹配的关键词 + * @param ptt 匹配模式 + * @param msg 消息 + * @param replace 替换字符串 + * @param fuzzy 是否模糊匹配 + * @param has_regular 是否使用正则表达式 + * @returns + */ +export function replaceKeyword( + ptt: string, + msg: string, + replace: string, + fuzzy: boolean, + has_regular: boolean, +): string { + const matchPositions = rangeMatchPosition(ptt, msg, fuzzy, has_regular); + if (matchPositions.length === 0) { + return msg; + } + + let result = ''; let lastIndex = 0; // eslint-disable-next-line no-restricted-syntax - for (const part of parts) { - // 跳过空字符串(它们来自模式开始、结束或连续 '*') - if (part === '') continue; - - const index = msg.indexOf(part, lastIndex); - // 如果找不到部分或部分不是按顺序出现,则匹配失败 - if (index === -1 || index < lastIndex) { - return false; - } - lastIndex = index + part.length; + for (const [start, end] of matchPositions) { + result += msg.slice(lastIndex, start) + replace; + lastIndex = end; } - // 确保消息的剩余部分可以被模式尾部的 '*' 匹配 - return parts[parts.length - 1] === '' || lastIndex <= msg.length; + result += msg.slice(lastIndex); + return result; } /** diff --git a/src/renderer/common/services/platform/platform.d.ts b/src/renderer/common/services/platform/platform.d.ts index 9863bfe..1b33153 100644 --- a/src/renderer/common/services/platform/platform.d.ts +++ b/src/renderer/common/services/platform/platform.d.ts @@ -130,6 +130,8 @@ export interface Keyword { platform_id?: string; keyword: string; reply: string; + fuzzy?: boolean; + has_regular?: boolean; } export interface TransferKeyword { diff --git a/src/renderer/common/utils/plugins/system/index.ts b/src/renderer/common/utils/plugins/system/index.ts index 1ea6e77..a8d3d0a 100644 --- a/src/renderer/common/utils/plugins/system/index.ts +++ b/src/renderer/common/utils/plugins/system/index.ts @@ -1,5 +1,4 @@ -import { WECHAT_NEWS_PLUGIN } from './wechat'; -import { QIANNIU_GOODS_PLUGIN } from './qianniu'; +import { NORMAL_PLUGIN } from './normal'; // 转换为 JSON 格式 // https://www.lambdatest.com/free-online-tools/json-escape @@ -13,21 +12,12 @@ export const SystemPluginList = [ }, { type: 'plugin', - title: '微信热榜播报插件', - author: '系统插件', - description: '当用户使用 @BOT 并且携带 [热榜] 关键字时,将会触发此插件。', - tags: ['微信', '热榜', '机器人'], - code: WECHAT_NEWS_PLUGIN, - icon: '📰', - }, - { - type: 'plugin', - title: '千牛商品查询插件', + title: '基础对话插件', author: '系统插件', description: - '会携带商品名称,商品 ID 等信息去询问 GPT 知识库(需要自己配置知识库)', - tags: ['千牛', '商品', '机器人'], - code: QIANNIU_GOODS_PLUGIN, - icon: '🎁', + '默认的回复流程,会根据设置去选择使用关键词回复或者使用 GPT 回复。', + tags: ['系统'], + code: NORMAL_PLUGIN, + icon: '⚙️', }, ]; diff --git a/src/renderer/common/utils/plugins/system/normal.ts b/src/renderer/common/utils/plugins/system/normal.ts new file mode 100644 index 0000000..15bc2ce --- /dev/null +++ b/src/renderer/common/utils/plugins/system/normal.ts @@ -0,0 +1,13 @@ +export const NORMAL_PLUGIN = `const cc = require('config_srv'); +const rp = require('reply_srv'); + +/** + * 插件主函数 + * @param {AppContext} ctx - 上下文信息 + * @param {Message[]} messages - 消息数组 + * @returns {Reply} 插件执行结果 + */ +async function main(ctx, messages) { + const cfg = await cc.get(ctx); + return await rp.getDefaultReply(cfg, ctx, messages); +}`; diff --git a/src/renderer/dataview-window/components/EditKeyword/index.tsx b/src/renderer/dataview-window/components/EditKeyword/index.tsx index 6f9a6f7..c5c33b7 100644 --- a/src/renderer/dataview-window/components/EditKeyword/index.tsx +++ b/src/renderer/dataview-window/components/EditKeyword/index.tsx @@ -9,6 +9,11 @@ import { ModalBody, ModalFooter, useToast, + Flex, + Box, + Switch, + FormControl, + FormLabel, } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; import { @@ -54,6 +59,8 @@ const EditKeyword = ({ const [currentPlatform, setCurrentPlatform] = useState( undefined, ); + const [fuzzy, setFuzzy] = useState(true); + const [regular, setRegular] = useState(false); useEffect(() => { if (!editKeyword?.keyword) { @@ -204,6 +211,34 @@ const EditKeyword = ({ isLoading={isPlatformsLoading} /> )} + + + + + + 模糊匹配 + + setFuzzy(!fuzzy)} + /> + + + + + + 正则匹配 + + setRegular(!regular)} + /> + + + + { - 当 ChatGPT - 生成的回复中包含下面的关键词规则时,将该关键词自动替换为指定的关键词。 + 例如:关键词是“你好”,替换内容是 “您好”,当回复为 “你好,我是 ChatGPT” + 时,将替换为“您好,我是 ChatGPT”。 diff --git a/src/renderer/dataview-window/components/ReplyKeyword/index.tsx b/src/renderer/dataview-window/components/ReplyKeyword/index.tsx index bed9f64..fec3a41 100644 --- a/src/renderer/dataview-window/components/ReplyKeyword/index.tsx +++ b/src/renderer/dataview-window/components/ReplyKeyword/index.tsx @@ -229,6 +229,8 @@ const ReplyKeyword = () => { 平台 关键词 回复内容 + 模糊匹配 + 正则 操作 @@ -256,6 +258,8 @@ const ReplyKeyword = () => { > {keyword.reply} + {keyword.fuzzy ? '是' : '否'} + {keyword.has_regular ? '是' : '否'} diff --git a/src/renderer/dataview-window/components/TransferKeyword/index.tsx b/src/renderer/dataview-window/components/TransferKeyword/index.tsx index 059eeb0..8164ba9 100644 --- a/src/renderer/dataview-window/components/TransferKeyword/index.tsx +++ b/src/renderer/dataview-window/components/TransferKeyword/index.tsx @@ -232,8 +232,8 @@ const TransferKeyword = () => { 平台 关键词 - 是否模糊匹配 - 是否含正则表达式 + 模糊匹配 + 正则 操作 diff --git a/src/renderer/main-window/components/AppManager/InstanceListComponent.tsx b/src/renderer/main-window/components/AppManager/InstanceListComponent.tsx index 81be04d..347fbe6 100644 --- a/src/renderer/main-window/components/AppManager/InstanceListComponent.tsx +++ b/src/renderer/main-window/components/AppManager/InstanceListComponent.tsx @@ -31,7 +31,6 @@ const InstanceListComponent = () => { const toast = useToast(); useEffect(() => { - console.log('selectedAppId', selectedAppId, filteredInstances); setCurrentAppId(selectedAppId); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedAppId]); diff --git a/src/renderer/settings-window/App.tsx b/src/renderer/settings-window/App.tsx index ed319a5..a29c160 100644 --- a/src/renderer/settings-window/App.tsx +++ b/src/renderer/settings-window/App.tsx @@ -19,7 +19,7 @@ import { Button, } from '@chakra-ui/react'; import { loader } from '@monaco-editor/react'; -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import { HashRouter as Router, Route, Routes } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import GeneralSettings from './components/Settings/GeneralSettings'; import LLMSettings from './components/Settings/LLMSettings'; @@ -64,6 +64,9 @@ const App = () => { trackPageView('Settings'); }, []); + // 打印当前的 url + // console.log('current url:', window.location.href); + const fetchConfigActive = useCallback( async (appId: string, instanceId?: string) => { try { @@ -236,7 +239,7 @@ const App = () => { { /> } /> - } - /> + } /> diff --git a/src/renderer/settings-window/pages/Plugin/index.tsx b/src/renderer/settings-window/pages/Plugin/index.tsx index 069008e..cfb1169 100644 --- a/src/renderer/settings-window/pages/Plugin/index.tsx +++ b/src/renderer/settings-window/pages/Plugin/index.tsx @@ -198,12 +198,12 @@ const PluginPage = ({ appId, instanceId }: PluginPageProps) => { const handleEdit = (plugin: Plugin) => { if (plugin.type === 'plugin') { setCurrentPlugin(plugin); - navigate('/settings.html/editor'); + navigate('/editor'); } if (plugin.type === 'custom') { setCurrentPlugin(null); - navigate('/settings.html/editor'); + navigate('/editor'); } }; diff --git a/src/renderer/settings-window/pages/PluginEdit/index.tsx b/src/renderer/settings-window/pages/PluginEdit/index.tsx index 310baba..d34dcb5 100644 --- a/src/renderer/settings-window/pages/PluginEdit/index.tsx +++ b/src/renderer/settings-window/pages/PluginEdit/index.tsx @@ -165,7 +165,7 @@ const PluginEdit = () => { @@ -258,7 +258,7 @@ const PluginEdit = () => { onClick={async () => { await handleDeletePlugin(); onClose(); - navigate('/settings.html'); + navigate('/'); }} ml={3} >