diff --git a/apps/webUI.js b/apps/webUI.js new file mode 100644 index 0000000..06c23d7 --- /dev/null +++ b/apps/webUI.js @@ -0,0 +1,56 @@ +import { REDIS_YUNZAI_WEBUI } from "../constants/constant.js"; +import config from "../model/config.js"; +import { redisSetKey } from "../utils/redis-util.js"; +import { getBotLoginInfo, getBotStatus, getBotVersionInfo } from "../utils/yunzai-util.js"; + +export class WebUI extends plugin { + constructor() { + super({ + name: "R插件 WebUI 开关", + dsc: "R插件 WebUI 开关", + event: "message", + priority: 4000, + rule: [ + { + reg: "^#(r|R)wss$", + fnc: "rWebSwitch", + permission: "master", + }, + { + reg: "^#(r|R)ws$", + fnc: "rWebStatus", + permission: "master", + } + ] + }); + // 配置文件 + this.toolsConfig = config.getConfig("tools"); + // 加载WebUI开关 + this.isOpenWebUI = this.toolsConfig.isOpenWebUI; + } + + async rWebSwitch(e) { + config.updateField("tools", "isOpenWebUI", !this.isOpenWebUI); + const realIsOpenWebUI = config.getConfig("tools").isOpenWebUI; + if (realIsOpenWebUI) { + Promise.all([getBotStatus(e), getBotVersionInfo(e), getBotLoginInfo(e)]).then(values => { + const status = values[0].data; + const versionInfo = values[1].data; + const loginInfo = values[2].data; + redisSetKey(REDIS_YUNZAI_WEBUI, { + ...status, + ...versionInfo, + ...loginInfo + }) + }) + } + // 这里有点延迟,需要写反 + e.reply(`R插件 WebUI:${ realIsOpenWebUI ? "开启\n🚀 请重启以启动 WebUI" : "关闭" }`); + return true; + } + + async rWebStatus(e) { + e.reply(`R插件 WebUI:${ this.toolsConfig.isOpenWebUI ? "开启" : "关闭" }`); + return true; + } +} diff --git a/config/tools.yaml b/config/tools.yaml index b86c1f6..96ed8ee 100644 --- a/config/tools.yaml +++ b/config/tools.yaml @@ -1,3 +1,4 @@ +isOpenWebUI: false # 是否开启webui defaultPath: './data/rcmp4/' # 保存视频的位置 videoSizeLimit: 70 # 视频大小限制(单位MB),超过大小则转换成群文件上传 proxyAddr: '127.0.0.1' # 魔法地址 diff --git a/constants/constant.js b/constants/constant.js index 0becdb5..930046f 100644 --- a/constants/constant.js +++ b/constants/constant.js @@ -93,10 +93,10 @@ export const REDIS_YUNZAI_CLOUDSONGLIST = "Yz:rconsole:tools:cloudsonglist"; export const REDIS_YUNZAI_WHITELIST = "Yz:rconsole:tools:whitelist"; /** - * 番剧列表缓存 + * WEBUI需要数据的缓存 * @type {string} */ -export const REDIS_YUNZAI_ANIMELIST = "Yz:rconsole:tools:anime"; +export const REDIS_YUNZAI_WEBUI = "Yz:rconsole:tools:webui"; export const TWITTER_BEARER_TOKEN = ""; diff --git a/index.js b/index.js index d2f5c43..c80cc07 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,15 @@ import fs from "node:fs"; import path from "path"; import config from "./model/config.js"; +import { buildNextJs, startNextJs } from "./start-nextjs.js"; if (!global.segment) { global.segment = (await import("oicq")).segment } // 加载版本号 const versionData = config.getConfig("version"); +// 加载是否使用WebUI +const isOpenWebUI = config.getConfig("tools").isOpenWebUI; // 加载名称 const packageJsonPath = path.join('./plugins', 'rconsole-plugin', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); @@ -35,4 +38,11 @@ for (let i in files) { } apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]; } + +// 检查是否启动 webui +if (isOpenWebUI) { + buildNextJs() + .then(() => startNextJs('start')) +} + export { apps }; diff --git a/package.json b/package.json index 8c73196..221716a 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,31 @@ "name": "rconsole-plugin", "description": "R-Plugin", "type": "module", + "scripts": { + "dev": "cd server && next dev -p 4016", + "start": "cd server && next start -p 4016", + "build": "cd server && next build" + }, "dependencies": { "axios": "^1.3.4", + "chart.js": "^4.4.6", "form-data": "^4.0.1", + "ioredis": "^5.4.1", + "js-yaml": "^4.1.0", + "next": "^14.2.16", "node-id3": "^0.2.6", + "node-os-utils": "^1.3.7", + "os-utils": "^0.0.14", + "p-queue": "^8.0.1", "qrcode": "^1.5.3", - "p-queue": "^8.0.1" + "react": "^18.3.1", + "react-chartjs-2": "^5.2.0", + "react-circular-progressbar": "^2.1.0", + "react-dom": "^18.3.1", + "systeminformation": "^5.23.5" + }, + "devDependencies": { + "daisyui": "^4.12.14", + "tailwindcss": "^3.4.14" } } diff --git a/server/app/layout.jsx b/server/app/layout.jsx new file mode 100644 index 0000000..e8acbcc --- /dev/null +++ b/server/app/layout.jsx @@ -0,0 +1,9 @@ +import "../styles/global.css"; + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/server/app/page.jsx b/server/app/page.jsx new file mode 100644 index 0000000..63aed4c --- /dev/null +++ b/server/app/page.jsx @@ -0,0 +1,12 @@ +import Header from "../components/header.jsx"; +import Sidebar from "../components/sidebar.jsx"; +import { DrawerProvider } from "../contexts/drawer-context.js"; + +export default function Page() { + return ( + +
+ + + ) +} diff --git a/server/app/r/api/bot/route.js b/server/app/r/api/bot/route.js new file mode 100644 index 0000000..9066032 --- /dev/null +++ b/server/app/r/api/bot/route.js @@ -0,0 +1,12 @@ +import { REDIS_YUNZAI_WEBUI } from "../../../../../constants/constant.js"; +import { redis } from "../../../../utils/redis.js"; + + +export async function GET(req, res) { + const botInfo = JSON.parse(await redis.get(REDIS_YUNZAI_WEBUI)); + + return new Response(JSON.stringify(botInfo), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); +} diff --git a/server/app/r/api/commit/route.js b/server/app/r/api/commit/route.js new file mode 100644 index 0000000..4860c3e --- /dev/null +++ b/server/app/r/api/commit/route.js @@ -0,0 +1,30 @@ +async function getLatestCommit(platform = "github") { + // 构建 API URL + const baseUrl = + platform === "github" + ? `https://api.github.com/repos/zhiyu1998/rconsole-plugin/commits` + : `https://gitee.com/api/v5/repos/kyrzy0416/rconsole-plugin/commits`; + + try { + const response = await fetch(baseUrl); + if (!response.ok) throw new Error("获取提交信息失败"); + + const commits = await response.json(); + const latestCommit = commits[0]; // 最新提交 + const { sha, commit, html_url } = latestCommit; + + return { sha, author: commit.author.name, message: commit.message, url: html_url }; + } catch (error) { + console.error("无法获取最新的提交:", error.message); + return null; + } +} + +export async function GET(req, res) { + const latestCommit = await getLatestCommit(); + + return new Response(JSON.stringify(latestCommit), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); +} diff --git a/server/app/r/api/config/route.js b/server/app/r/api/config/route.js new file mode 100644 index 0000000..2e6f69e --- /dev/null +++ b/server/app/r/api/config/route.js @@ -0,0 +1,52 @@ +import fs from 'fs'; +import yaml from 'js-yaml'; +import path from 'path'; + +const configPath = path.join(process.cwd(), "../", 'config', 'tools.yaml'); + +export async function GET(req, res) { + try { + const yamlContent = await fs.promises.readFile(configPath, 'utf8'); + const config = yaml.load(yamlContent); + return new Response(JSON.stringify(config), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error('读取配置文件失败:', error); + return new Response(JSON.stringify({ error: '读取配置文件失败' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} + +export async function POST(req, res) { + try { + const updates = await req.json(); + + const yamlContent = await fs.promises.readFile(configPath, 'utf8'); + const currentConfig = yaml.load(yamlContent); + + // 只更新指定的字段 + const newConfig = { ...currentConfig, ...updates }; + + // 转换回YAML并保存 + const newYamlContent = yaml.dump(newConfig, { + indent: 2, + lineWidth: -1, + quotingType: '"' + }); + await fs.promises.writeFile(configPath, newYamlContent, 'utf8'); + return new Response(JSON.stringify({ success: true }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error('更新配置文件失败:', error); + return new Response(JSON.stringify({ error: '更新配置文件失败' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/server/app/r/api/network/route.js b/server/app/r/api/network/route.js new file mode 100644 index 0000000..ba2c6af --- /dev/null +++ b/server/app/r/api/network/route.js @@ -0,0 +1,25 @@ +import axios from "axios"; + +export async function GET(request) { + const url = new URL(request.url); // 获取请求的 URL + const targetUrl = url.searchParams.get("url"); // 从查询参数中获取目标 URL + const start = Date.now(); // 记录请求开始时间 + + try { + await axios.get(targetUrl); + // 计算结束时间减去开始时间 + return new Response(JSON.stringify({ + time: Date.now() - start + }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + return new Response(JSON.stringify({ + time: 0 + }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }) + } +} diff --git a/server/app/r/api/network2/route.js b/server/app/r/api/network2/route.js new file mode 100644 index 0000000..1b96685 --- /dev/null +++ b/server/app/r/api/network2/route.js @@ -0,0 +1,116 @@ +import { promises as fs } from 'fs'; +import os from 'os'; +import si from 'systeminformation'; + +let lastBytesReceived = 0; +let lastBytesSent = 0; +let lastTimestamp = Date.now(); +let isFirstRun = true; + +async function getLinuxStats() { + const data = await fs.readFile('/proc/net/dev', 'utf8'); + const lines = data.trim().split('\n'); + + let bytesReceived = 0; + let bytesSent = 0; + + for (let i = 2; i < lines.length; i++) { + const line = lines[i].trim(); + const parts = line.split(/\s+/); + + if (parts[0].startsWith('lo:')) continue; + + bytesReceived += parseInt(parts[1], 10); + bytesSent += parseInt(parts[9], 10); + } + + return { bytesReceived, bytesSent }; +} + +async function getWindowsStats() { + const networkStats = await si.networkStats(); + let bytesReceived = 0; + let bytesSent = 0; + + for (const stat of networkStats) { + bytesReceived += stat.rx_bytes || 0; + bytesSent += stat.tx_bytes || 0; + } + + return { bytesReceived, bytesSent }; +} + +async function getNetworkStats() { + try { + const platform = os.platform(); + let bytesReceived = 0; + let bytesSent = 0; + + if (platform === 'linux') { + const stats = await getLinuxStats(); + bytesReceived = stats.bytesReceived; + bytesSent = stats.bytesSent; + } else { + const stats = await getWindowsStats(); + bytesReceived = stats.bytesReceived; + bytesSent = stats.bytesSent; + } + + const now = Date.now(); + const timeDiff = (now - lastTimestamp) / 1000; + + let downloadSpeed = 0; + let uploadSpeed = 0; + + if (!isFirstRun) { + // 检查是否发生了计数器重置或异常值 + if (bytesReceived >= lastBytesReceived && bytesSent >= lastBytesSent) { + downloadSpeed = (bytesReceived - lastBytesReceived) / timeDiff; + uploadSpeed = (bytesSent - lastBytesSent) / timeDiff; + + // 设置合理的上限值(比如 1GB/s) + const MAX_SPEED = 1024 * 1024 * 1024; // 1 GB/s + downloadSpeed = Math.min(downloadSpeed, MAX_SPEED); + uploadSpeed = Math.min(uploadSpeed, MAX_SPEED); + } + } + + // 更新状态 + lastBytesReceived = bytesReceived; + lastBytesSent = bytesSent; + lastTimestamp = now; + isFirstRun = false; + + return { + downloadSpeed: (downloadSpeed / 1024).toFixed(2), // KB/s + uploadSpeed: (uploadSpeed / 1024).toFixed(2), // KB/s + totalReceived: (bytesReceived / (1024 * 1024 * 1024)).toFixed(2), // GB + totalSent: (bytesSent / (1024 * 1024 * 1024)).toFixed(2), // GB + timestamp: now + }; + } catch (error) { + console.error('获取网络统计信息失败:', error); + return { + downloadSpeed: "0", + uploadSpeed: "0", + totalReceived: "0", + totalSent: "0", + timestamp: Date.now() + }; + } +} + +export async function GET() { + try { + const stats = await getNetworkStats(); + return new Response(JSON.stringify(stats), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/server/app/r/api/system/route.js b/server/app/r/api/system/route.js new file mode 100644 index 0000000..af08533 --- /dev/null +++ b/server/app/r/api/system/route.js @@ -0,0 +1,60 @@ +import si from 'systeminformation'; +import os from 'os'; + +export async function GET(request, { params }) { + try { + // 获取CPU信息 + const cpuInfo = await si.cpu(); + const cpuUsage = await si.currentLoad(); + const totalCpuCores = cpuInfo.cores; + const cpuCoresUsed = ((cpuUsage.currentLoad / 100) * totalCpuCores).toFixed(1); // 使用的核心数 + + // 获取内存信息 + const totalMemory = (os.totalmem() / (1024 ** 3)).toFixed(2); // 转换为 GB + const freeMemory = (os.freemem() / (1024 ** 3)).toFixed(2); // 转换为 GB + const usedMemory = (totalMemory - freeMemory).toFixed(2); + const memoryUsagePercent = ((usedMemory / totalMemory) * 100).toFixed(2); + + // 获取磁盘信息 + const diskInfo = await si.fsSize(); + const totalDisk = (diskInfo[0].size / (1024 ** 3)).toFixed(2); // 转换为 GB + const usedDisk = (diskInfo[0].used / (1024 ** 3)).toFixed(2); // 转换为 GB + const diskUsagePercent = ((usedDisk / totalDisk) * 100).toFixed(2); + + // 获取网络信息 + const networkInterfaces = os.networkInterfaces(); + const ipAddress = Object.values(networkInterfaces) + .flat() + .filter(detail => detail.family === 'IPv4' && !detail.internal)[0].address; + + // 获取系统信息 + const hostname = os.hostname(); + const uptime = os.uptime(); + const osInfo = await si.osInfo(); + + return new Response(JSON.stringify({ + cpuUsage: cpuUsage.currentLoad.toFixed(2), + cpuUsageDetail: `${cpuUsage.currentLoad.toFixed(2)}%`, + totalCpuCores, + cpuCoresUsed, + memoryUsage: memoryUsagePercent, + usedMemory: `${usedMemory} GB`, + totalMemory: `${totalMemory} GB`, + diskUsage: diskUsagePercent, + usedDisk: `${usedDisk} GB`, + totalDisk: `${totalDisk} GB`, + loadAverage: cpuUsage.avgLoad.toFixed(2), + ipAddress, + hostname, + uptime: `${Math.floor(uptime / 60 / 60)} hours`, + distro: osInfo.distro, + kernelVersion: osInfo.kernel, + arch: os.arch(), + }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { status: 500 }); + } +} diff --git a/server/app/r/api/version/route.js b/server/app/r/api/version/route.js new file mode 100644 index 0000000..de810b7 --- /dev/null +++ b/server/app/r/api/version/route.js @@ -0,0 +1,43 @@ +async function getLatestTag() { + // GitHub 和 Gitee 的 API URL + const githubUrl = `https://api.github.com/repos/zhiyu1998/rconsole-plugin/tags`; + const giteeUrl = `https://gitee.com/api/v5/repos/kyrzy0416/rconsole-plugin/tags`; + + // 定义 fetch 请求 + const fetchGitHub = fetch(githubUrl).then(async (response) => { + if (!response.ok) throw new Error("GitHub请求失败"); + const data = await response.json(); + return { source: "GitHub", tag: data }; + }); + + const fetchGitee = fetch(giteeUrl).then(async (response) => { + if (!response.ok) throw new Error("Gitee请求失败"); + const data = await response.json(); + return { source: "Gitee", tag: data }; + }); + + // 使用 Promise.race 竞速 + try { + return await Promise.race([fetchGitHub, fetchGitee]); + } catch (error) { + console.error("无法获取最新的标签:", error.message); + return null; + } +} + +export async function GET(req, res) { + const tags = await getLatestTag(); + console.log(tags); + + let latestTag; + if (tags.source === "Gitee") { + latestTag = tags.tag[tags.length - 1]; + } + latestTag = tags.tag[0]; + console.log(latestTag); + + return new Response(JSON.stringify(latestTag), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); +} diff --git a/server/components/ThemeToggle.jsx b/server/components/ThemeToggle.jsx new file mode 100644 index 0000000..67ba3a8 --- /dev/null +++ b/server/components/ThemeToggle.jsx @@ -0,0 +1,23 @@ +import React, { useState } from 'react'; + +function ThemeToggle() { + // 用于保存主题状态,默认为“light”主题 + const [isDarkTheme, setIsDarkTheme] = useState(false); + + // 切换主题时的处理函数 + const handleThemeChange = () => { + setIsDarkTheme(!isDarkTheme); + }; + + return ( + + ); +} + +export default ThemeToggle; diff --git a/server/components/content.jsx b/server/components/content.jsx new file mode 100644 index 0000000..135534a --- /dev/null +++ b/server/components/content.jsx @@ -0,0 +1,13 @@ +import { SIDEBAR_ITEMS } from "../constants/sidebar.js"; + +export function Content({ activeItem }) { + // 查找当前激活项 + const currentItem = SIDEBAR_ITEMS.find(item => item.name === activeItem); + + // 如果没找到则返回总控制台 + return ( +
+ {currentItem?.component || SIDEBAR_ITEMS[0].component} +
+ ); +} diff --git a/server/components/contents/bili.jsx b/server/components/contents/bili.jsx new file mode 100644 index 0000000..61a0a46 --- /dev/null +++ b/server/components/contents/bili.jsx @@ -0,0 +1,306 @@ +import { useState, useEffect } from 'react'; +import { BILI_CDN_SELECT_LIST, BILI_DOWNLOAD_METHOD, BILI_RESOLUTION_LIST } from "../../../constants/constant.js"; +import { readYamlConfig, updateYamlConfig } from '../../utils/yamlHelper'; + +export default function Bili() { + const [config, setConfig] = useState({ + biliSessData: '', + biliDuration: 480, + biliIntroLenLimit: 50, + biliDisplayCover: true, + biliDisplayInfo: true, + biliDisplayIntro: true, + biliDisplayOnline: true, + biliDisplaySummary: false, + biliUseBBDown: false, + biliCDN: 0, + biliDownloadMethod: 0, + biliResolution: 5 + }); + + const [loading, setLoading] = useState(false); + + // 读取配置 + useEffect(() => { + const loadConfig = async () => { + const yamlConfig = await readYamlConfig(); + if (yamlConfig) { + setConfig({ + biliSessData: yamlConfig.biliSessData || '', + biliDuration: yamlConfig.biliDuration || 480, + biliIntroLenLimit: yamlConfig.biliIntroLenLimit || 50, + biliDisplayCover: yamlConfig.biliDisplayCover ?? true, + biliDisplayInfo: yamlConfig.biliDisplayInfo ?? true, + biliDisplayIntro: yamlConfig.biliDisplayIntro ?? true, + biliDisplayOnline: yamlConfig.biliDisplayOnline ?? true, + biliDisplaySummary: yamlConfig.biliDisplaySummary ?? false, + biliUseBBDown: yamlConfig.biliUseBBDown ?? false, + biliCDN: yamlConfig.biliCDN || 0, + biliDownloadMethod: yamlConfig.biliDownloadMethod || 0, + biliResolution: yamlConfig.biliResolution || 5 + }); + } + }; + + loadConfig(); + }, []); + + // 保存配置 + const handleSave = async () => { + setLoading(true); + try { + const success = await updateYamlConfig({ + biliSessData: config.biliSessData, + biliDuration: config.biliDuration, + biliIntroLenLimit: config.biliIntroLenLimit, + biliDisplayCover: config.biliDisplayCover, + biliDisplayInfo: config.biliDisplayInfo, + biliDisplayIntro: config.biliDisplayIntro, + biliDisplayOnline: config.biliDisplayOnline, + biliDisplaySummary: config.biliDisplaySummary, + biliUseBBDown: config.biliUseBBDown, + biliCDN: config.biliCDN, + biliDownloadMethod: config.biliDownloadMethod, + biliResolution: config.biliResolution + }); + + if (success) { + // 使用 daisyUI 的 toast 提示 + document.getElementById('toast-success').classList.remove('hidden'); + setTimeout(() => { + document.getElementById('toast-success').classList.add('hidden'); + }, 3000); + } + } catch (error) { + console.error('保存配置失败:', error); + } finally { + setLoading(false); + } + }; + + // 重置配置 + const handleReset = async () => { + const yamlConfig = await readYamlConfig(); + if (yamlConfig) { + setConfig({ + biliSessData: yamlConfig.biliSessData || '', + biliDuration: yamlConfig.biliDuration || 480, + biliIntroLenLimit: yamlConfig.biliIntroLenLimit || 50, + biliDisplayCover: yamlConfig.biliDisplayCover ?? true, + biliDisplayInfo: yamlConfig.biliDisplayInfo ?? true, + biliDisplayIntro: yamlConfig.biliDisplayIntro ?? true, + biliDisplayOnline: yamlConfig.biliDisplayOnline ?? true, + biliDisplaySummary: yamlConfig.biliDisplaySummary ?? false, + biliUseBBDown: yamlConfig.biliUseBBDown ?? false, + biliCDN: yamlConfig.biliCDN || 0, + biliDownloadMethod: yamlConfig.biliDownloadMethod || 0, + biliResolution: yamlConfig.biliResolution || 5 + }); + } + }; + + return ( +
+ {/* 成功提示 */} +
+
+ 配置保存成功! +
+
+ +
+

Bilibili 配置

+ + {/* 基础配置部分 */} +
+
+

基础配置

+ + {/* SESSDATA配置 */} +
+ + setConfig({ ...config, biliSessData: e.target.value })} + placeholder="请输入Bilibili SESSDATA" + className="input input-bordered w-full" + /> +
+ + {/* 数值配置部分 */} +
+
+ + setConfig({ ...config, biliDuration: parseInt(e.target.value) })} + className="input input-bordered" + /> +
+
+ + setConfig({ ...config, biliIntroLenLimit: parseInt(e.target.value) })} + className="input input-bordered" + /> +
+
+ + {/* 开关配置部分 */} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + {/* 下拉选择配置部分 */} +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + {/* 保存按钮 */} +
+ + +
+
+
+ ) +} diff --git a/server/components/contents/generic.jsx b/server/components/contents/generic.jsx new file mode 100644 index 0000000..bf08997 --- /dev/null +++ b/server/components/contents/generic.jsx @@ -0,0 +1,289 @@ +import { useState, useEffect } from 'react'; +import { readYamlConfig, updateYamlConfig } from '../../utils/yamlHelper'; + +export default function Generic() { + const [config, setConfig] = useState({ + defaultPath: './data/rcmp4/', + videoSizeLimit: 70, + proxyAddr: '127.0.0.1', + proxyPort: '7890', + identifyPrefix: '', + streamDuration: 10, + streamCompatibility: false, + queueConcurrency: 1, + videoDownloadConcurrency: 1, + autoclearTrashtime: '0 0 8 * * ?', + deeplApiUrls: '' + }); + + const [loading, setLoading] = useState(false); + + // 读取配置 + useEffect(() => { + const loadConfig = async () => { + const yamlConfig = await readYamlConfig(); + if (yamlConfig) { + setConfig({ + defaultPath: yamlConfig.defaultPath || './data/rcmp4/', + videoSizeLimit: yamlConfig.videoSizeLimit || 70, + proxyAddr: yamlConfig.proxyAddr || '127.0.0.1', + proxyPort: yamlConfig.proxyPort || '7890', + identifyPrefix: yamlConfig.identifyPrefix || '', + streamDuration: yamlConfig.streamDuration || 10, + streamCompatibility: yamlConfig.streamCompatibility ?? false, + queueConcurrency: yamlConfig.queueConcurrency || 1, + videoDownloadConcurrency: yamlConfig.videoDownloadConcurrency || 1, + autoclearTrashtime: yamlConfig.autoclearTrashtime || '0 0 8 * * ?', + deeplApiUrls: yamlConfig.deeplApiUrls || '' + }); + } + }; + + loadConfig(); + }, []); + + // 保存配置 + const handleSave = async () => { + setLoading(true); + try { + const success = await updateYamlConfig({ + defaultPath: config.defaultPath, + videoSizeLimit: config.videoSizeLimit, + proxyAddr: config.proxyAddr, + proxyPort: config.proxyPort, + identifyPrefix: config.identifyPrefix, + streamDuration: config.streamDuration, + streamCompatibility: config.streamCompatibility, + queueConcurrency: config.queueConcurrency, + videoDownloadConcurrency: config.videoDownloadConcurrency, + autoclearTrashtime: config.autoclearTrashtime, + deeplApiUrls: config.deeplApiUrls + }); + + if (success) { + document.getElementById('generic-toast-success').classList.remove('hidden'); + setTimeout(() => { + document.getElementById('generic-toast-success').classList.add('hidden'); + }, 3000); + } + } catch (error) { + console.error('保存配置失败:', error); + } finally { + setLoading(false); + } + }; + + // 重置配置 + const handleReset = async () => { + const yamlConfig = await readYamlConfig(); + if (yamlConfig) { + setConfig({ + defaultPath: yamlConfig.defaultPath || './data/rcmp4/', + videoSizeLimit: yamlConfig.videoSizeLimit || 70, + proxyAddr: yamlConfig.proxyAddr || '127.0.0.1', + proxyPort: yamlConfig.proxyPort || '7890', + identifyPrefix: yamlConfig.identifyPrefix || '', + streamDuration: yamlConfig.streamDuration || 10, + streamCompatibility: yamlConfig.streamCompatibility ?? false, + queueConcurrency: yamlConfig.queueConcurrency || 1, + videoDownloadConcurrency: yamlConfig.videoDownloadConcurrency || 1, + autoclearTrashtime: yamlConfig.autoclearTrashtime || '0 0 8 * * ?', + deeplApiUrls: yamlConfig.deeplApiUrls || '' + }); + } + }; + + return ( +
+ {/* 成功提示 */} +
+
+ 配置保存成功! +
+
+ +
+

通用配置

+ + {/* 基础配置部分 */} +
+
+

基础配置

+ + {/* 路径和大小限制配置 */} +
+
+ + setConfig({ ...config, defaultPath: e.target.value })} + placeholder="请输入视频保存路径..." + className="input input-bordered" + /> +
+
+ + setConfig({ ...config, videoSizeLimit: parseInt(e.target.value) })} + className="input input-bordered" + /> +
+
+ + {/* 代理配置 */} +
+
+ + setConfig({ ...config, proxyAddr: e.target.value })} + placeholder="请输入代理地址..." + className="input input-bordered" + /> +
+
+ + setConfig({ ...config, proxyPort: e.target.value })} + placeholder="请输入代理端口..." + className="input input-bordered" + /> +
+
+ + {/* 其他基础配置 */} +
+
+ + setConfig({ ...config, identifyPrefix: e.target.value })} + placeholder="请输入识别前缀..." + className="input input-bordered" + /> +
+
+ + setConfig({ ...config, streamDuration: parseInt(e.target.value) })} + className="input input-bordered" + /> +
+
+ + {/* 并发和定时配置 */} +
+
+ + setConfig({ ...config, queueConcurrency: parseInt(e.target.value) })} + className="input input-bordered" + /> +
+
+ + setConfig({ ...config, videoDownloadConcurrency: parseInt(e.target.value) })} + className="input input-bordered" + /> +
+
+ + {/* DeepL API配置 */} +
+ +