diff --git a/apps/switchers.js b/apps/switchers.js index 7d06c35..50a6519 100644 --- a/apps/switchers.js +++ b/apps/switchers.js @@ -1,6 +1,6 @@ -import config from "../model/config.js"; import schedule from 'node-schedule'; import { REDIS_YUNZAI_ISOVERSEA, REDIS_YUNZAI_WHITELIST } from "../constants/constant.js"; +import config from "../model/config.js"; import { deleteFolderRecursive, readCurrentDir } from "../utils/file.js"; import { redisExistAndGetKey, redisGetKey, redisSetKey } from "../utils/redis-util.js"; diff --git a/apps/tools.js b/apps/tools.js index 5f6b246..a50563d 100644 --- a/apps/tools.js +++ b/apps/tools.js @@ -27,6 +27,7 @@ import { TWITTER_BEARER_TOKEN, XHS_NO_WATERMARK_HEADER } from "../constants/constant.js"; +import { REDIS_YUNZAI_RESOLVE_CONTROLLER, RESOLVE_CONTROLLER_NAME_ENUM } from "../constants/resolve.js"; import { ANIME_SERIES_SEARCH_LINK, ANIME_SERIES_SEARCH_LINK2, @@ -99,7 +100,7 @@ import { contentEstimator } from "../utils/link-share-summary-util.js"; import { deepSeekChat, llmRead } from "../utils/llm-util.js"; import { getDS } from "../utils/mihoyo.js"; import { OpenaiBuilder } from "../utils/openai-builder.js"; -import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js"; +import { redisExistAndGetKey, redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js"; import { saveTDL, startTDL } from "../utils/tdl-util.js"; import { genVerifyFp } from "../utils/tiktok.js"; import Translate from "../utils/trans-strategy.js"; @@ -341,6 +342,11 @@ export class tools extends plugin { // 抖音解析 async douyin(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.douyin))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.douyin } 已拦截`); + return true; + } const urlRex = /(http:\/\/|https:\/\/)(v|live).douyin.com\/[A-Za-z\d._?%&+\-=\/#]*/; // 检测无效链接,例如:v.douyin.com if (!urlRex.test(e.msg)) { @@ -610,6 +616,11 @@ export class tools extends plugin { // tiktok解析 async tiktok(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.tiktok))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.tiktok } 已拦截`); + return true; + } // 判断海外 const isOversea = await this.isOverseasServer(); // 如果不是海外用户且没有梯子直接返回 @@ -712,6 +723,11 @@ export class tools extends plugin { // B 站解析 async bili(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.bili))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.bili } 已拦截`); + return true; + } const urlRex = /(?:https?:\/\/)?www\.bilibili\.com\/[A-Za-z\d._?%&+\-=\/#]*/g; const bShortRex = /(http:|https:)\/\/b23.tv\/[A-Za-z\d._?%&+\-=\/#]*/g; let url = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim().replaceAll("\\", ""); @@ -1232,6 +1248,11 @@ export class tools extends plugin { // 使用现有api解析小蓝鸟 async twitter_x(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.twitter_x))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.twitter_x } 已拦截`); + return true; + } if (!(await this.isTrustUser(e.user_id))) { e.reply("你没有权限使用此命令"); return; @@ -1297,6 +1318,11 @@ export class tools extends plugin { // acfun解析 async acfun(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.acfun))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.acfun } 已拦截`); + return true; + } const path = `${ this.getCurDownloadPath(e) }/temp/`; await mkdirIfNotExists(path); @@ -1324,6 +1350,11 @@ export class tools extends plugin { // 小红书解析 async xhs(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.xhs))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.xhs } 已拦截`); + return true; + } // 正则说明:匹配手机链接、匹配小程序、匹配PC链接 let msgUrl = /(http:|https:)\/\/(xhslink|xiaohongshu).com\/[A-Za-z\d._?%&+\-=\/#@]*/.exec( @@ -1447,6 +1478,11 @@ export class tools extends plugin { // 波点音乐解析 async bodianMusic(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.bodianMusic))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.bodianMusic } 已拦截`); + return true; + } // 音频例子:https://h5app.kuwo.cn/m/bodian/playMusic.html?uid=3216773&musicId=192015898&opusId=&extendType=together // 视频例子:https://h5app.kuwo.cn/m/bodian/play.html?uid=3216773&mvId=118987&opusId=770096&extendType=together const id = @@ -1644,6 +1680,11 @@ export class tools extends plugin { // 网易云解析 async netease(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.netease))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.netease } 已拦截`); + return true; + } let message = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); // 处理短号,此时会变成y.music.163.com @@ -1901,6 +1942,11 @@ export class tools extends plugin { // 微博解析 async weibo(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.weibo))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.weibo } 已拦截`); + return true; + } let weiboId; const weiboUrl = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim().replaceAll("\\", ""); // 对已知情况进行判断 @@ -1994,6 +2040,11 @@ export class tools extends plugin { * @return {Promise} */ async general(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.general))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.general } 已拦截`); + return true; + } try { const adapter = await GeneralLinkAdapter.create(e.msg); e.reply(`${ this.identifyPrefix }识别:${ adapter.name }${ adapter.desc ? `, ${ adapter.desc }` : '' }`); @@ -2026,6 +2077,11 @@ export class tools extends plugin { // 油管解析 async sy2b(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.sy2b))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.sy2b } 已拦截`); + return true; + } const timeRange = ytbFormatTime(this.youtubeClipTime); const isOversea = await this.isOverseasServer(); if (!isOversea && !(await testProxy(this.proxyAddr, this.proxyPort))) { @@ -2098,6 +2154,11 @@ export class tools extends plugin { // 米游社 async miyoushe(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.miyoushe))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.miyoushe } 已拦截`); + return true; + } let url = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); let msg = /(?:https?:\/\/)?(m|www)\.miyoushe\.com\/[A-Za-z\d._?%&+\-=\/#]*/.exec(url)?.[0]; const id = /\/(\d+)$/.exec(msg)?.[0].replace("\/", ""); @@ -2169,6 +2230,11 @@ export class tools extends plugin { // 微视 async weishi(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.weishi))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.weishi } 已拦截`); + return true; + } let url = e.msg; const urlRegex = /https?:\/\/video\.weishi\.qq\.com\/\S+/g; // 执行匹配 @@ -2216,6 +2282,11 @@ export class tools extends plugin { } async zuiyou(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.zuiyou))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.zuiyou } 已拦截`); + return true; + } // #最右#分享一条有趣的内容给你,不好看算我输。请戳链接>>https://share.xiaochuankeji.cn/hybrid/share/post?pid=365367131&zy_to=applink&share_count=1&m=dc114ccc8e55492642f6a702b510c1f6&d=9e18ca2dace030af656baea96321e0ea353fe5c46097a7f3962b93f995641e962796dd5faa231feea5531ac65547045f&app=zuiyou&recommend=r0&name=n0&title_type=t0 let msg = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); const url = /(?:https?:\/\/)?(share|share.xiaochuankeji)\.cn\/[A-Za-z\d._?%&+\-=\/#]*/.exec(msg)[0]; @@ -2279,6 +2350,11 @@ export class tools extends plugin { } async freyr(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.freyr))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.freyr } 已拦截`); + return true; + } // https://music.apple.com/cn/album/hectopascal-from-yagate-kimi-ni-naru-piano-arrangement/1468323115?i=1468323724 // 过滤参数 const message = e.msg.replace("&ls", ""); @@ -2415,6 +2491,11 @@ export class tools extends plugin { // q q m u s i c 解析 async qqMusic(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.qqMusic))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.qqMusic } 已拦截`); + return true; + } // case1: Taylor Swift/Bleachers《Anti-Hero (Feat. Bleachers) (Explicit)》 https://c6.y.qq.com/base/fcgi-bin/u?__=lg19lFgQerbo @QQ音乐 /** case 2: * {"app":"com.tencent.structmsg","config":{"ctime":1722497864,"forward":1,"token":"987908ab4a1c566d3645ef0ca52a162a","type":"normal"},"extra":{"app_type":1,"appid":100497308,"uin":542716863},"meta":{"news":{"action":"","android_pkg_name":"","app_type":1,"appid":100497308,"ctime":1722497864,"desc":"Taylor Swift/Bleachers","jumpUrl":"https://i.y.qq.com/v8/playsong.html?hosteuin=7KvA7i6sNeCi&sharefrom=gedan&from_id=1674373010&from_idtype=10014&from_name=(7rpl)&songid=382775503&songmid=&type=0&platform=1&appsongtype=1&_wv=1&source=qq&appshare=iphone&media_mid=000dKYJS3KCzpu&ADTAG=qfshare","preview":"https://pic.ugcimg.cn/1070bf5a6962b75263eee1404953c9b2/jpg1","source_icon":"https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0","source_url":"","tag":"QQ音乐","title":"Anti-Hero (Feat. Bleachers) (E…","uin":542716863}},"prompt":"[分享]Anti-Hero (Feat. Bleachers) (E…","ver":"0.0.0.1","view":"news"} @@ -2461,6 +2542,11 @@ export class tools extends plugin { // 汽水音乐 async qishuiMusic(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.qishuiMusic))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.qishuiMusic } 已拦截`); + return true; + } const normalRegex = /^(.*?)\s*https?:\/\//; const musicInfo = normalRegex.exec(e.msg)?.[1].trim().replace("@汽水音乐", ""); logger.info(`[R插件][qishuiMusic] 识别音乐为:${ musicInfo }`); @@ -2483,6 +2569,11 @@ export class tools extends plugin { // 小飞机下载 async aircraft(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.aircraft))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.aircraft } 已拦截`); + return true; + } if (!(await this.isTrustUser(e.user_id))) { e.reply("你没有权限使用此命令"); return; @@ -2539,6 +2630,11 @@ export class tools extends plugin { // 贴吧 async tieba(e) { + // 切面判断是否需要解析 + if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.tieba))) { + logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.tieba } 已拦截`); + return true; + } // 提取链接和ID const msg = /https:\/\/tieba\.baidu\.com\/p\/[A-Za-z0-9]+/.exec(e.msg)?.[0]; const id = /\/p\/([A-Za-z0-9]+)/.exec(msg)?.[1]; @@ -3022,6 +3118,26 @@ export class tools extends plugin { } } + /** + * 判断是否启用解析 + * @param resolveName + * @returns {Promise} + */ + async isEnableResolve(resolveName) { + const controller = await redisExistAndGetKey(REDIS_YUNZAI_RESOLVE_CONTROLLER); + // 如果不存在说明用户没有启动过webui,那么直接放行 + if (controller == null) { + return true; + } + const foundItem = controller.find(item => item.label === resolveName); + // 未知解析,可能是写错,放行 + if (!foundItem) { + logger.warn(`[R插件][启用解析] 未知解析,可能存在写错`); + return true; + } + return foundItem.value === 1; + } + /** * 判断是否是海外服务器 * @return {Promise} diff --git a/constants/resolve.js b/constants/resolve.js new file mode 100644 index 0000000..9738925 --- /dev/null +++ b/constants/resolve.js @@ -0,0 +1,23 @@ +export const REDIS_YUNZAI_RESOLVE_CONTROLLER = "Yz:rconsole:resolve:controller" + +export const RESOLVE_CONTROLLER_NAME_ENUM = { + "douyin": "抖音", + "bili": "哔哩哔哩", + "tiktok": "TikTok", + "twitter_x": "Twitter", + "acfun": "Acfun", + "xhs": "小红书", + "bodianMusic": "波点", + "general": "通用(包含快手等)", + "sy2b": "YouTube", + "miyoushe": "米游社", + "netease": "网易云音乐", + "weibo": "微博", + "weishi": "微视", + "zuiyou": "最右", + "freyr": "AM+Spotify", + "qqMusic": "扣扣音乐", + "qishuiMusic": "汽水音乐", + "aircraft": "小飞机", + "tieba": "贴吧", +} diff --git a/server/app/r/api/resolveControl/route.js b/server/app/r/api/resolveControl/route.js new file mode 100644 index 0000000..200e6d9 --- /dev/null +++ b/server/app/r/api/resolveControl/route.js @@ -0,0 +1,49 @@ +import { REDIS_RESOLVE_CONTROLLER } from "../../../../constants/redis.js"; +import { GLOBAL_RESOLE_CONTROLLER } from "../../../../constants/resolve.js"; +import { redis } from "../../../../utils/redis.js"; + +export async function GET(req, res) { + let resolveList = await redis.get(REDIS_RESOLVE_CONTROLLER); + if (resolveList == null) { + // Redis中不存在就初始化进去 + await redis.set(REDIS_RESOLVE_CONTROLLER, JSON.stringify(GLOBAL_RESOLE_CONTROLLER)); + return new Response(JSON.stringify(GLOBAL_RESOLE_CONTROLLER), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } + + return new Response(resolveList, { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); +} + +export async function POST(req) { + try { + const data = await req.json(); + const { selectedTags } = data; + + // 获取所有可能的标签 + const allTags = GLOBAL_RESOLE_CONTROLLER.map(item => item.label); + + // 更新控制器状态 + const updatedController = GLOBAL_RESOLE_CONTROLLER.map(item => ({ + ...item, + value: selectedTags.includes(item.label) ? 1 : 0 + })); + + // 保存到Redis + await redis.set(REDIS_RESOLVE_CONTROLLER, JSON.stringify(updatedController)); + + return new Response(JSON.stringify({ success: true }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + return new Response(JSON.stringify({ success: false, error: error.message }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/server/app/r/api/update/route.js b/server/app/r/api/update/route.js index 3bbd73e..db50ca8 100644 --- a/server/app/r/api/update/route.js +++ b/server/app/r/api/update/route.js @@ -2,6 +2,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import fs from 'fs/promises'; import path from 'path'; +import { REDIS_UPDATE_PATH, REDIS_UPDATE_STATUS } from "../../../../constants/redis.js"; import { redis } from "../../../../utils/redis.js"; const execAsync = promisify(exec); @@ -20,7 +21,7 @@ function handleGitError(error, stderr) { if (error.message.includes('Timed out')) { return '连接超时,请检查网络后重试'; } - if (error.message.includes('Could not resolve host')) { + if (error.message.includes('Could not resolveControl host')) { return '无法解析主机地址,请检查网络'; } if (error.message.includes('Permission denied')) { @@ -60,8 +61,8 @@ async function cleanupUpdate(tempDir) { // 清理临时文件 await fs.rm(tempDir, { recursive: true, force: true }); // 清理Redis中的更新状态 - await redis.del('rconsole:update:status'); - await redis.del('rconsole:update:paths'); + await redis.del(REDIS_UPDATE_STATUS); + await redis.del(REDIS_UPDATE_PATH); console.log('清理完成'); } catch (error) { console.error('清理失败:', error); @@ -77,7 +78,7 @@ export async function GET(req) { // 如果是检查请求 if (isCheck) { - const updateStatus = await redis.get('rconsole:update:status'); + const updateStatus = await redis.get(REDIS_UPDATE_STATUS); return new Response(JSON.stringify({ needsRestore: updateStatus === 'restoring' }), { @@ -87,8 +88,8 @@ export async function GET(req) { // 如果是恢复请求 if (isRestore) { - const updateStatus = await redis.get('rconsole:update:status'); - const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}'); + const updateStatus = await redis.get(REDIS_UPDATE_STATUS); + const paths = JSON.parse(await redis.get(REDIS_UPDATE_PATH) || '{}'); if (updateStatus === 'restoring' && paths.tempDir && paths.configDir) { try { @@ -123,10 +124,10 @@ export async function GET(req) { const configDir = path.join(projectRoot, 'config'); // 检查是否有未完成的更新 - const updateStatus = await redis.get('rconsole:update:status'); + const updateStatus = await redis.get(REDIS_UPDATE_STATUS); if (updateStatus === 'restoring') { // 如果有未完成的更新,尝试恢复 - const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}'); + const paths = JSON.parse(await redis.get(REDIS_UPDATE_PATH) || '{}'); if (paths.tempDir && paths.configDir) { try { await copyConfig(paths.tempDir, paths.configDir); @@ -140,11 +141,11 @@ export async function GET(req) { console.log('开始新的更新流程'); // 保存路径信息到Redis - await redis.set('rconsole:update:paths', JSON.stringify({ + await redis.set(REDIS_UPDATE_PATH, JSON.stringify({ tempDir, configDir })); - await redis.set('rconsole:update:status', 'started'); + await redis.set(REDIS_UPDATE_STATUS, 'started'); // 确保临时目录存在 await ensureDirectory(tempDir); @@ -156,7 +157,7 @@ export async function GET(req) { await copyConfig(configDir, tempDir); configBackedUp = true; console.log('配置文件备份成功'); - await redis.set('rconsole:update:status', 'backed_up'); + await redis.set(REDIS_UPDATE_STATUS, 'backed_up'); } catch (error) { console.log('无配置文件需要备份或备份失败:', error.message); } @@ -169,7 +170,7 @@ export async function GET(req) { } // 标记状态为需要恢复 - await redis.set('rconsole:update:status', 'restoring'); + await redis.set(REDIS_UPDATE_STATUS, 'restoring'); console.log('执行git pull...'); const { stdout } = await execAsync(`git -C "${projectRoot}" pull --no-rebase`); @@ -225,7 +226,7 @@ export async function GET(req) { console.error('更新过程出错:', error); // 确保清理所有临时状态 - const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}'); + const paths = JSON.parse(await redis.get(REDIS_UPDATE_PATH) || '{}'); if (paths.tempDir) { await cleanupUpdate(paths.tempDir); } @@ -237,4 +238,4 @@ export async function GET(req) { headers: { 'Content-Type': 'application/json' }, }); } -} \ No newline at end of file +} diff --git a/server/components/TagSelector.jsx b/server/components/TagSelector.jsx new file mode 100644 index 0000000..cd22a68 --- /dev/null +++ b/server/components/TagSelector.jsx @@ -0,0 +1,65 @@ +import React, { useState, useEffect } from "react"; + +const TagSelector = ({ options = [], initialTags = [], onChange }) => { + const [selectedTags, setSelectedTags] = useState(initialTags); + + useEffect(() => { + setSelectedTags(initialTags); + }, [initialTags]); + + const addTag = (tag) => { + if (!selectedTags.includes(tag)) { + const updatedTags = [...selectedTags, tag]; + setSelectedTags(updatedTags); + if (onChange) onChange(updatedTags); + } + }; + + const removeTag = (tag) => { + const updatedTags = selectedTags.filter((t) => t !== tag); + setSelectedTags(updatedTags); + if (onChange) onChange(updatedTags); + }; + + return ( +
+
+ {selectedTags.length > 0 ? ( + selectedTags.map((tag, index) => ( + removeTag(tag)} + > + {tag} + + )) + ) : ( + 暂无标签,请选择一个选项。 + )} +
+ + +
+ ); +}; + +export default TagSelector; diff --git a/server/components/contents/generic.jsx b/server/components/contents/generic.jsx index 321f5ad..53d2746 100644 --- a/server/components/contents/generic.jsx +++ b/server/components/contents/generic.jsx @@ -1,8 +1,8 @@ -import { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { readYamlConfig, updateYamlConfig } from '../../utils/yamlHelper'; +import { ConfigInput, ConfigSelect, ConfigToggle } from '../common/ConfigItem'; +import TagSelector from "../TagSelector.jsx"; import Toast from "../toast.jsx"; -import { ConfigToggle, ConfigInput, ConfigSelect } from '../common/ConfigItem'; -import { AI_MODEL_LIST } from "../../../constants/constant.js"; // 定义配置项 const GENERIC_CONFIG = { @@ -146,6 +146,8 @@ const DEFAULT_CONFIG = Object.values(GENERIC_CONFIG).reduce((acc, group) => { export default function Generic() { const [config, setConfig] = useState(DEFAULT_CONFIG); const [loading, setLoading] = useState(false); + const [resolveOptions, setResolveOptions] = useState([]); + const [selectedResolveTags, setSelectedResolveTags] = useState([]); useEffect(() => { const loadConfig = async () => { @@ -161,6 +163,25 @@ export default function Generic() { loadConfig(); }, []); + useEffect(() => { + // 获取解析控制器配置 + const fetchResolveControl = async () => { + try { + const response = await fetch('/r/api/resolveControl'); + const data = await response.json(); + const enabledTags = data + .filter(item => item.value === 1) + .map(item => item.label); + setSelectedResolveTags(enabledTags); + setResolveOptions(data.map(item => item.label)); + } catch (error) { + console.error('获取解析控制器配置失败:', error); + } + }; + + fetchResolveControl(); + }, []); + const handleSave = async () => { setLoading(true); try { @@ -182,6 +203,24 @@ export default function Generic() { setConfig(prev => ({ ...prev, [key]: value })); }; + const handleResolveTagsChange = async (tags) => { + try { + const response = await fetch('/r/api/resolveControl', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ selectedTags: tags }), + }); + + if (response.ok) { + setSelectedResolveTags(tags); + } + } catch (error) { + console.error('更新解析控制器配置失败:', error); + } + }; + // 渲染输入框组 const renderInputGroup = (inputs, title) => (
@@ -218,6 +257,14 @@ export default function Generic() { {/* 基础配置 */} {renderInputGroup(GENERIC_CONFIG.basicInputs)} + {/* 解析控制 */} +

全局解析控制

+ + {/* 代理配置 */}

代理设置

{renderInputGroup(GENERIC_CONFIG.proxyInputs)} diff --git a/server/components/home/bot-config.jsx b/server/components/home/bot-config.jsx index 265c198..f40e6c9 100644 --- a/server/components/home/bot-config.jsx +++ b/server/components/home/bot-config.jsx @@ -80,7 +80,7 @@ export function BotConfig() {