diff --git a/Plugin/NovelAIGen/NovelAIGen.js b/Plugin/NovelAIGen/NovelAIGen.js index fff4992..8eb68d0 100644 --- a/Plugin/NovelAIGen/NovelAIGen.js +++ b/Plugin/NovelAIGen/NovelAIGen.js @@ -1,377 +1,1252 @@ #!/usr/bin/env node +/* + * NovelAIGen + * 版本: 2.1.0 + * 职责: NovelAI 六端点多渠道、全参数网关。 + * 作者: VCP-Assistant + * 重构: CodeCC & infinite-vector + */ import axios from "axios"; -import fs from 'fs/promises'; -import path from 'path'; -import { v4 as uuidv4 } from 'uuid'; -import yauzl from 'yauzl'; -import { promisify } from 'util'; +import fs from "fs/promises"; +import path from "path"; +import crypto from "crypto"; +import { v4 as uuidv4 } from "uuid"; +import yauzl from "yauzl"; +import { HttpsProxyAgent } from "https-proxy-agent"; +import { HttpProxyAgent } from "http-proxy-agent"; -// --- Configuration (from environment variables set by Plugin.js) --- -const NOVELAI_API_KEY = process.env.NOVELAI_API_KEY; // NovelAI API Key -const debugMode = (process.env.DebugMode || "false").toLowerCase() === "true"; // Debug mode -const PROJECT_BASE_PATH = process.env.PROJECT_BASE_PATH; -const SERVER_PORT = process.env.SERVER_PORT; -const IMAGESERVER_IMAGE_KEY = process.env.IMAGESERVER_IMAGE_KEY; -const VAR_HTTP_URL = process.env.VarHttpUrl; -const VAR_HTTPS_URL = process.env.VarHttpsUrl; +// ==================== 段 01 · 文件头 + import + 环境变量 ==================== +const env = process.env; +const NOVELAI_API_KEY = env.NOVELAI_API_KEY || ""; +const NOVELAI_BASE_URL = env.NOVELAI_BASE_URL || "https://image.novelai.net"; +const NOVELAI_ACCOUNT_URL = + env.NOVELAI_ACCOUNT_URL || "https://api.novelai.net"; +const MULTI_CHANNEL = + String(env.MULTI_CHANNEL || "false").toLowerCase() === "true"; +const NOVELAI_CHANNELS = env.NOVELAI_CHANNELS || ""; +const NOVELAI_PATH_PREFIX = (env.NOVELAI_PATH_PREFIX || "").replace(/\/$/, ""); +const VIBE_UNSUPPORTED_PREFIXES = + env.VIBE_UNSUPPORTED_PREFIXES !== undefined + ? env.VIBE_UNSUPPORTED_PREFIXES + : ""; +const MODEL_ALIASES = env.MODEL_ALIASES || ""; +const INPAINT_MODELS = env.INPAINT_MODELS || ""; +const INPAINT_FALLBACK_CHAIN = env.INPAINT_FALLBACK_CHAIN || ""; +const DEFAULT_MODEL = env.DEFAULT_MODEL || "v4.5"; +const DEFAULT_STEPS = Number(env.DEFAULT_STEPS || 23); +const DEFAULT_SCALE = Number(env.DEFAULT_SCALE || 5); +const DEFAULT_SAMPLER = env.DEFAULT_SAMPLER || "k_euler_ancestral"; +const DEFAULT_NOISE_SCHEDULE = env.DEFAULT_NOISE_SCHEDULE || "karras"; +const DEFAULT_UC = + env.DEFAULT_UC || + "lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page"; +const RESOLUTION_PRESETS = env.RESOLUTION_PRESETS || ""; +const MAX_RETRIES = Number(env.MAX_RETRIES || 2); +const RETRY_BASE_DELAY_MS = Number(env.RETRY_BASE_DELAY_MS || 2000); +const MAX_IMAGE_SIZE_MB = Number(env.MAX_IMAGE_SIZE_MB || 8); +const ENUM_PROBE = String(env.ENUM_PROBE || "true").toLowerCase() === "true"; +const NOVELAI_PROXY = env.NovelAIProxy || ""; +const DEBUG_MODE = String(env.DebugMode || "false").toLowerCase() === "true"; +const PROJECT_BASE_PATH = env.PROJECT_BASE_PATH || ""; +const SERVER_PORT = env.SERVER_PORT || ""; +const IMAGESERVER_IMAGE_KEY = env.IMAGESERVER_IMAGE_KEY || ""; +const VAR_HTTP_URL = env.VarHttpUrl || ""; +const VAR_HTTPS_URL = env.VarHttpsUrl || ""; -// Debug logging function - outputs to stderr for VCP compatibility -function FORCE_LOG(...args) { - console.error(...args); // 强制日志输出到 stderr -} - -// NovelAI API specific configurations -const NOVELAI_API_CONFIG = { - BASE_URL: 'https://image.novelai.net', - IMAGE_GENERATION_ENDPOINT: '/ai/generate-image', - DEFAULT_PARAMS: { - model: "nai-diffusion-4-5-full", // NAI Diffusion V4.5 Full 模型 - parameters: { - // V4 API 基础参数 (width和height由用户指定) - steps: 23, - scale: 5, - sampler: "k_euler_ancestral", - n_samples: 1, - ucPreset: 0, - qualityToggle: true, - - // V4 新增参数 - params_version: 3, - noise_schedule: "karras", - prefer_brownian: true, - add_original_image: false, - autoSmea: false, - cfg_rescale: 0, - controlnet_strength: 1, - deliberate_euler_ancestral_bug: false, - dynamic_thresholding: false, - legacy: false, - legacy_uc: false, - legacy_v3_extend: false, - normalize_reference_strength_multiple: true, - skip_cfg_above_sigma: null, - use_coords: false - }, - - // V4 专用负面提示词格式 - negative_prompt_base: "lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page" - } +// ==================== 段 02 · 常量表与枚举候选池 ==================== +const ENDPOINTS = Object.freeze({ + GENERATE: "/ai/generate-image", + ENCODE_VIBE: "/ai/encode-vibe", + AUGMENT: "/ai/augment-image", + UPSCALE: "/ai/upscale", + SUGGEST_TAGS: "/ai/generate-image/suggest-tags", + SUBSCRIPTION: "/user/subscription", +}); +const ACTION = Object.freeze({ + GENERATE: "generate", + IMG2IMG: "img2img", + INFILL: "infill", +}); +// V5 标识符来源:YesNovelAI (nai.rinko.ai) GET /v1/models 实证,2026-08-29。 +// 官方直连是否接受同一组 ID 尚未验证;若官方拒绝,用 MODEL_ALIASES 覆盖。 +const BUILTIN_MODEL_ALIASES = { + v5: "nai-diffusion-5-full", + v5c: "nai-diffusion-5-curated", + "v4.5": "nai-diffusion-4-5-full", + "v4.5c": "nai-diffusion-4-5-curated", + v4: "nai-diffusion-4-full", + v4c: "nai-diffusion-4-curated", + v3: "nai-diffusion-3", + // furry 有两种写法:前者来自 novelai-python SDK 枚举,后者来自中转站 + // /v1/models。两者可能分别对应不同渠道,都保留。 + furry: "nai-diffusion-furry-3", + furry3: "nai-diffusion-3-furry", }; -// Helper to validate input arguments -function isValidNovelAIGenArgs(args) { - if (!args || typeof args !== 'object') return false; - if (typeof args.prompt !== 'string' || !args.prompt.trim()) return false; - if (typeof args.resolution !== 'string') return false; - const parts = args.resolution.split('x'); - if (parts.length !== 2) return false; - const width = parseInt(parts[0], 10); - const height = parseInt(parts[1], 10); - if (isNaN(width) || isNaN(height)) return false; - return true; -} - -// 解压ZIP文件并提取图片 -async function extractImagesFromZip(zipBuffer) { - return new Promise((resolve, reject) => { - const images = []; - - yauzl.fromBuffer(zipBuffer, { lazyEntries: true }, (err, zipfile) => { - if (err) { - reject(new Error(`NovelAI Plugin Error: Failed to read ZIP buffer: ${err.message}`)); - return; - } - - zipfile.readEntry(); - - zipfile.on("entry", (entry) => { - if (/\/$/.test(entry.fileName)) { - // Directory entry, skip - zipfile.readEntry(); - } else { - // File entry - if (entry.fileName.toLowerCase().match(/\.(png|jpg|jpeg|webp)$/)) { - zipfile.openReadStream(entry, (err, readStream) => { - if (err) { - reject(new Error(`NovelAI Plugin Error: Failed to read entry: ${err.message}`)); - return; - } - - const chunks = []; - readStream.on('data', (chunk) => { - chunks.push(chunk); - }); - - readStream.on('end', () => { - const imageBuffer = Buffer.concat(chunks); - const fileExtension = path.extname(entry.fileName).substring(1) || 'png'; - images.push({ - name: entry.fileName, - buffer: imageBuffer, - extension: fileExtension - }); - zipfile.readEntry(); - }); - - readStream.on('error', (err) => { - reject(new Error(`NovelAI Plugin Error: Failed to read stream: ${err.message}`)); - }); - }); - } else { - zipfile.readEntry(); - } - } - }); +// 注:nai-diffusion-4-5-full-inpainting 与 nai-diffusion-5-*-inpainting +// 仅在部分中转站的 /v1/models 中出现,官方直连是否提供未经验证。 +// 为避免官方用户从"降级可用"退化为"直接报错",此处不内置 4-5-full 的映射; +// 需要时通过 INPAINT_MODELS 配置项添加,例如: +// INPAINT_MODELS=nai-diffusion-4-5-full=nai-diffusion-4-5-full-inpainting +const BUILTIN_INPAINT_MODELS = { + "nai-diffusion-4-5-curated": "nai-diffusion-4-5-curated-inpainting", + "nai-diffusion-4-full": "nai-diffusion-4-full-inpainting", + "nai-diffusion-4-curated": "nai-diffusion-4-curated-inpainting", + "nai-diffusion-3": "nai-diffusion-3-inpainting", + "nai-diffusion-3-furry": "nai-diffusion-3-furry-inpainting", + "nai-diffusion-furry-3": "nai-diffusion-furry-3-inpainting", +}; - zipfile.on("end", () => { - if (images.length === 0) { - reject(new Error("NovelAI Plugin Error: No valid images found in ZIP response")); - } else { - resolve(images); - } - }); +const BUILTIN_INPAINT_FALLBACK = [ + ["nai-diffusion-4-5-curated", "nai-diffusion-4-5-curated-inpainting"], + ["nai-diffusion-4-full", "nai-diffusion-4-full-inpainting"], + ["nai-diffusion-4-curated", "nai-diffusion-4-curated-inpainting"], + ["nai-diffusion-3", "nai-diffusion-3-inpainting"], +]; +// 站点/官方共用的 HTTP 语义提示,用于把裸状态码翻译成可读原因 +const STATUS_HINTS = Object.freeze({ + 400: "参数错误 / 模型不支持 / 尺寸不合法", + 401: "Token 无效、过期或已禁用", + 402: "余额不足(Gems / Anlas)", + 403: "该 Token 无此模型权限", + 405: "此路径不接受该方法;若为中转站请检查 PATH_PREFIX 配置", + 413: "请求体或图片超出大小限制", + 429: "限速或额度限制", + 502: "上游响应无效或 Key 不可用", + 503: "上游排队超时或不可用", + 504: "网关超时(上游或前置 CDN 未在限时内响应)", +}); +const DEFAULT_RESOLUTIONS = [ + "512x768", + "768x512", + "640x640", + "832x1216", + "1216x832", + "1024x1024", + "1024x1536", + "1536x1024", + "1472x1472", + "1088x1920", + "1920x1088", +]; +const AUGMENT_REQ_TYPES = [ + "emotion", + "colorize", + "lineart", + "sketch", + "declutter", + "bg-removal", +]; +const ENUM_CANDIDATES = { + action_infill: ["infill", "inpainting", "infill_v2"], + augment_bgremoval: ["bg-removal", "bg_removal", "removebg"], +}; +const BASE_PARAMETERS = { + n_samples: 1, + ucPreset: 0, + qualityToggle: true, + params_version: 3, + prefer_brownian: true, + add_original_image: false, + autoSmea: false, + cfg_rescale: 0, + controlnet_strength: 1, + deliberate_euler_ancestral_bug: false, + dynamic_thresholding: false, + legacy: false, + legacy_uc: false, + legacy_v3_extend: false, + normalize_reference_strength_multiple: true, + skip_cfg_above_sigma: null, + use_coords: false, +}; - zipfile.on("error", (err) => { - reject(new Error(`NovelAI Plugin Error: ZIP processing error: ${err.message}`)); - }); - }); - }); +// ==================== 段 03 · 基础工具函数 ==================== +function log(...args) { + if (DEBUG_MODE) console.error("[NovelAIGen]", ...args); +} +function redact(value, key = "") { + if (value === null || value === undefined) return value; + if (/token|key|authorization/i.test(key)) return ""; + if (typeof value === "string") { + const compact = value.replace(/\s/g, ""); + if (value.length > 200 && /^[A-Za-z0-9+/=_-]+$/.test(compact)) + return ``; + return value; + } + if (Array.isArray(value)) return value.map((item) => redact(item)); + if (typeof value === "object") + return Object.fromEntries( + Object.entries(value).map(([k, v]) => [k, redact(v, k)]), + ); + return value; +} +function parseKvList(str, sep1 = ";", sep2 = "=") { + const out = {}; + if (!str) return out; + for (const group of String(str).split(sep1)) { + const i = group.indexOf(sep2); + if (i < 0) continue; + const k = group.slice(0, i).trim(); + const v = group.slice(i + sep2.length).trim(); + if (k) out[k] = v; + } + return out; +} +function toPosixRelative(...segments) { + return segments.join("/").split("\\").join("/"); +} +function assertPathInside(baseDir, targetPath) { + const base = path.resolve(baseDir); + const target = path.resolve(targetPath); + if (target !== base && !target.startsWith(`${base}${path.sep}`)) + throw new Error("目标路径越出允许目录"); + return target; +} +function stripDataUriPrefix(input) { + return String(input || "").replace(/^data:[^;,]+;base64,/i, ""); +} +function guessMimeFromBuffer(buffer) { + if ( + buffer + ?.subarray(0, 8) + .equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) + ) + return "image/png"; + if (buffer?.subarray(0, 3).equals(Buffer.from([255, 216, 255]))) + return "image/jpeg"; + if ( + buffer?.subarray(0, 4).toString() === "RIFF" && + buffer.subarray(8, 12).toString() === "WEBP" + ) + return "image/webp"; + if (buffer?.subarray(0, 3).toString() === "GIF") return "image/gif"; + return "image/png"; +} +function clampNumber(value, min, max, fallback) { + const n = Number(value); + return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : fallback; +} +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +function parseResolution(value) { + const allowed = RESOLUTION_PRESETS + ? RESOLUTION_PRESETS.split(",") + .map((x) => x.trim()) + .filter(Boolean) + : DEFAULT_RESOLUTIONS; + const resolution = value || allowed[0]; + if (!/^\d+x\d+$/i.test(resolution) || !allowed.includes(resolution)) + throw new Error( + `不支持的分辨率 ${resolution},可选:${allowed.join(", ")}`, + ); + const [width, height] = resolution.toLowerCase().split("x").map(Number); + return { width, height, resolution }; +} +function parseFreeSize(value, fallbackWidth = 1024, fallbackHeight = 1024) { + const m = /^(\d+)x(\d+)$/i.exec(String(value || "").trim()); + if (m) return { width: Number(m[1]), height: Number(m[2]) }; + return { width: fallbackWidth, height: fallbackHeight }; +} +function asJson(value, fallback = null) { + try { + return typeof value === "string" ? JSON.parse(value) : value; + } catch { + return fallback; + } +} +function truncate(value, n = 500) { + const s = String(value ?? ""); + return s.length > n ? `${s.slice(0, n)}...` : s; +} +function describeResponseError(error) { + const status = error?.response?.status; + const hint = STATUS_HINTS[status] ? ` [${STATUS_HINTS[status]}]` : ""; + const head = status ? `HTTP ${status}${hint} ` : ""; + const data = error?.response?.data; + if (data === undefined || data === null) + return head + (error?.message || String(error)); + try { + if (Buffer.isBuffer(data)) return head + truncate(data.toString("utf8"), 300); + if (data instanceof ArrayBuffer) + return head + truncate(Buffer.from(data).toString("utf8"), 300); + if (typeof data === "string") return head + truncate(data, 300); + return head + truncate(JSON.stringify(data), 300); + } catch { + return head + (error?.message || "无法解析的错误响应"); + } } -async function generateImageAndSave(args) { - if (debugMode) { - FORCE_LOG('[NovelAIGen] Starting image generation with parameters:', { - prompt: args.prompt?.substring(0, 100) + (args.prompt?.length > 100 ? '...' : ''), - model: NOVELAI_API_CONFIG.DEFAULT_PARAMS.model, - config: 'Using official recommended default settings' - }); - } - - // Check for essential environment variables - if (!NOVELAI_API_KEY) { - const errorMsg = "NovelAI API密钥未配置。请在环境变量中设置NOVELAI_API_KEY。"; - if (debugMode) FORCE_LOG('[NovelAIGen] Error:', errorMsg); - throw new Error("NovelAI Plugin Error: NOVELAI_API_KEY environment variable is required."); - } - if (!PROJECT_BASE_PATH) { - throw new Error("NovelAI Plugin Error: PROJECT_BASE_PATH environment variable is required for saving images."); - } - if (!SERVER_PORT) { - throw new Error("NovelAI Plugin Error: SERVER_PORT environment variable is required for constructing image URL."); +// ==================== 段 04 · 渠道层 ==================== +// 渠道语法:URL|KEY|MODELS|CAPS|PATH_PREFIX +// PATH_PREFIX 用于中转站的原生协议前缀,例如 YesNovelAI 的 /native。 +// 留空即官方直连行为,完全向后兼容。 +function parseChannels() { + const channels = []; + if (MULTI_CHANNEL && NOVELAI_CHANNELS) { + for (const item of NOVELAI_CHANNELS.split(";")) { + const [url, key, models = "", caps = "", prefix = ""] = item.split("|"); + if (!url || !key) continue; + channels.push({ + url: url.replace(/\/$/, ""), + key, + models: models + ? models + .split(",") + .map((x) => x.trim()) + .filter(Boolean) + : [], + caps: caps + ? caps + .split(",") + .map((x) => x.trim()) + .filter(Boolean) + : [], + prefix: prefix.trim().replace(/\/$/, ""), + }); } - if (!IMAGESERVER_IMAGE_KEY) { - throw new Error("NovelAI Plugin Error: IMAGESERVER_IMAGE_KEY environment variable is required for constructing image URL."); - } - if (!VAR_HTTP_URL) { - throw new Error("NovelAI Plugin Error: VarHttpUrl environment variable is required for constructing image URL."); - } - - if (!isValidNovelAIGenArgs(args)) { - throw new Error(`NovelAI Plugin Error: Invalid arguments received: ${JSON.stringify(args)}. Required: prompt (string), resolution (string).`); + } + if (!channels.length && NOVELAI_API_KEY) + channels.push({ + url: NOVELAI_BASE_URL.replace(/\/$/, ""), + key: NOVELAI_API_KEY, + models: [], + caps: [], + prefix: NOVELAI_PATH_PREFIX, + }); + console.error( + `[NovelAIGen] channels=${channels.length} ${channels + .map((c) => c.url + (c.prefix || "")) + .join(", ")}`, + ); + return channels; +} +const CHANNELS = parseChannels(); +function channelSupports(channel, capability) { + return !channel.caps.length || channel.caps.includes(capability); +} +function buildChannelPlan(capability, requestedModel) { + const plan = []; + for (const channel of CHANNELS) { + if (!channelSupports(channel, capability)) continue; + let models; + if (!channel.models.length) { + models = [requestedModel]; + } else if (!requestedModel) { + models = channel.models; + } else { + const matched = channel.models.filter( + (m) => m === requestedModel || resolveAlias(m) === requestedModel, + ); + models = matched.length ? matched : []; } + for (const model of models) + plan.push({ + url: channel.url, + key: channel.key, + model, + prefix: channel.prefix || "", + }); + } + for (let i = plan.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [plan[i], plan[j]] = [plan[j], plan[i]]; + } + return plan; +} - // 解析分辨率 - const parts = args.resolution.split('x'); - const width = parseInt(parts[0], 10); - const height = parseInt(parts[1], 10); - - // 构建请求payload - 根据NovelAI V4 API格式 - const payload = { - action: "generate", // 必需字段!根据官方API文档 - model: NOVELAI_API_CONFIG.DEFAULT_PARAMS.model, - input: args.prompt, // 保留旧格式的input字段 - - parameters: { - ...NOVELAI_API_CONFIG.DEFAULT_PARAMS.parameters, - - // 使用用户指定的分辨率 - width: width, - height: height, - - // V4专用提示词结构(在parameters内部) - v4_prompt: { - caption: { - base_caption: args.prompt, - char_captions: [] - }, - use_coords: false, - use_order: true - }, - - // V4专用负面提示词结构(在parameters内部) - v4_negative_prompt: { - caption: { - base_caption: NOVELAI_API_CONFIG.DEFAULT_PARAMS.negative_prompt_base, - char_captions: [] - }, - legacy_uc: false - }, - - // 保留旧格式的negative_prompt字段 - negative_prompt: NOVELAI_API_CONFIG.DEFAULT_PARAMS.negative_prompt_base, - - // 动态生成随机种子 - seed: Math.floor(Math.random() * 4294967295), - characterPrompts: [], - inpaintImg2ImgStrength: 1 - } - }; - - if (debugMode) FORCE_LOG('[NovelAIGen] Sending payload to NovelAI API:', JSON.stringify(payload, null, 2)); +// ==================== 段 05 · 模型解析层 ==================== +function mergedAliases() { + return { ...BUILTIN_MODEL_ALIASES, ...parseKvList(MODEL_ALIASES) }; +} +function resolveAlias(value) { + const aliases = mergedAliases(); + return Object.prototype.hasOwnProperty.call(aliases, value) + ? aliases[value] + : value; +} +function inpaintMap() { + return { ...BUILTIN_INPAINT_MODELS, ...parseKvList(INPAINT_MODELS) }; +} +function fallbackChain() { + if (!INPAINT_FALLBACK_CHAIN) return BUILTIN_INPAINT_FALLBACK; + return INPAINT_FALLBACK_CHAIN.split(";") + .map((x) => x.split(">").map((y) => y.trim())) + .filter((x) => x.length === 2); +} +function resolveModel(input, purpose = "base") { + const requested = input || DEFAULT_MODEL; + const aliases = mergedAliases(); + let model; + if (/^(nai-|safe-)/i.test(requested)) model = requested; + else if (Object.prototype.hasOwnProperty.call(aliases, requested)) { + if (aliases[requested] === null || aliases[requested] === "") + throw new Error( + `模型别名 ${requested} 的标识符尚未确认,请在 MODEL_ALIASES 中配置,或直接传入原始标识符`, + ); + model = aliases[requested]; + } else + throw new Error( + `未知模型别名 ${requested},可用别名:${Object.keys(aliases).join(", ")}`, + ); + if (purpose === "base") return { model, note: null }; + const mapped = inpaintMap()[model]; + if (mapped) return { model: mapped, note: null }; + for (const [base, variant] of fallbackChain()) { + const available = CHANNELS.some( + (channel) => !channel.models.length || channel.models.includes(variant), + ); + if (base && variant && available) + return { + model: variant, + note: `模型 ${model} 无对应 inpainting 变体,已降级为 ${variant}`, + }; + } + throw new Error( + `模型 ${model} 无可用 inpainting 变体,可用模型:${Object.values(inpaintMap()).join(", ")}`, + ); +} +// 本地拦截名单改为配置驱动。默认空串表示不拦截——让上游用自己的错误码说话。 +// 若确认某系模型不支持 Vibe,填入 VIBE_UNSUPPORTED_PREFIXES(逗号分隔前缀)。 +function assertVibeSupported(model) { + const prefixes = String(VIBE_UNSUPPORTED_PREFIXES) + .split(",") + .map((x) => x.trim()) + .filter(Boolean); + if (!prefixes.length) return; + if (prefixes.some((prefix) => model.startsWith(prefix))) + throw new Error( + `模型 ${model} 命中本地拦截名单 VIBE_UNSUPPORTED_PREFIXES(当前值:${VIBE_UNSUPPORTED_PREFIXES})。若该渠道已支持 Vibe Transfer,请调整该配置项。`, + ); +} - const headers = { - 'Authorization': `Bearer ${NOVELAI_API_KEY}`, - 'Content-Type': 'application/json' - }; +function listAvailableModels() { + return { aliases: mergedAliases(), inpainting: inpaintMap() }; +} - const novelaiAxiosInstance = axios.create({ - baseURL: NOVELAI_API_CONFIG.BASE_URL, - headers: headers, - timeout: 180000, // 3分钟超时 - responseType: 'arraybuffer' // 重要:设置为arraybuffer以接收二进制数据 +// ==================== 段 06 · 输入管道层 ==================== +function parseImageArrayInput(value) { + if (Array.isArray(value)) return value.filter(Boolean); + if (typeof value === "string" && value.trim().startsWith("[")) { + const parsed = asJson(value, null); + if (Array.isArray(parsed)) return parsed.filter(Boolean); + } + return value ? [value] : []; +} +function collectImageInputs(args) { + const values = []; + const add = (value) => values.push(...parseImageArrayInput(value)); + for (const key of [ + "image", + "Image", + "image_url", + "source_image", + "image_base64", + ]) + if (args[key]) add(args[key]); + const numbered = Object.keys(args) + .filter((k) => /^(image|image_url|image_base64)_\d+$/i.test(k)) + .sort((a, b) => { + const na = Number(a.match(/\d+$/)?.[0] ?? 0); + const nb = Number(b.match(/\d+$/)?.[0] ?? 0); + return na - nb || a.localeCompare(b); }); - - const response = await novelaiAxiosInstance.post( - NOVELAI_API_CONFIG.IMAGE_GENERATION_ENDPOINT, - payload + for (const key of numbered) add(args[key]); + return [...new Set(values.map(String))]; +} +function checkImageSize(buffer) { + const mb = buffer.length / 1024 / 1024; + if (mb > MAX_IMAGE_SIZE_MB) + throw new Error( + `图片大小 ${mb.toFixed(2)}MB 超过上限 ${MAX_IMAGE_SIZE_MB}MB`, ); +} +async function processImageInput(input) { + if (/^data:/i.test(input)) { + const raw = Buffer.from(stripDataUriPrefix(input), "base64"); + checkImageSize(raw); + return input; + } + let buffer, mime; + if (/^https?:\/\//i.test(input)) { + const response = await axios.get(input, { + responseType: "arraybuffer", + timeout: 30000, + ...buildAgents(), + }); + buffer = Buffer.from(response.data); + mime = + response.headers["content-type"]?.split(";")[0] || + guessMimeFromBuffer(buffer); + } else { + const local = path.isAbsolute(input) + ? input + : path.resolve(PROJECT_BASE_PATH, input); + buffer = await fs.readFile(local); + mime = + { + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".webp": "image/webp", + ".gif": "image/gif", + }[path.extname(local).toLowerCase()] || "image/png"; + } + checkImageSize(buffer); + return `data:${mime};base64,${buffer.toString("base64")}`; +} +function toNaiImageField(dataUri) { + return stripDataUriPrefix(dataUri); +} +function parseCharacterPrompts(args) { + let list = asJson(args.characters, null); + if (!Array.isArray(list)) { + list = []; + const nums = [ + ...new Set( + Object.keys(args) + .map((k) => k.match(/^char_(\d+)(?:_|$)/)?.[1]) + .filter(Boolean), + ), + ].sort((a, b) => Number(a) - Number(b)); + for (const n of nums) + list.push({ + prompt: args[`char_${n}`] || args[`char_${n}_prompt`] || "", + uc: args[`char_${n}_uc`], + x: args[`char_${n}_x`], + y: args[`char_${n}_y`], + }); + } + return list + .filter((x) => x && x.prompt !== undefined) + .map((x) => ({ + char_caption: String(x.prompt || ""), + char_uc: String(x.uc || ""), + centers: (Array.isArray(x.centers) + ? x.centers + : [{ x: x.x, y: x.y }] + ).map((p) => ({ + x: clampNumber(p.x, 0, 1, 0.5), + y: clampNumber(p.y, 0, 1, 0.5), + })), + })); +} - if (debugMode) FORCE_LOG(`[NovelAIGen] Received response from NovelAI API, content-type: ${response.headers['content-type']}`); +// ==================== 段 07 · Payload 构造层 ==================== +function buildBaseParameters(args) { + const { width, height } = parseResolution( + args.resolution || args.size || args.image_size, + ); + return { + ...BASE_PARAMETERS, + width, + height, + steps: clampNumber(args.steps, 1, 50, DEFAULT_STEPS), + scale: clampNumber(args.scale, 0, 20, DEFAULT_SCALE), + sampler: args.sampler || DEFAULT_SAMPLER, + noise_schedule: args.noise_schedule || DEFAULT_NOISE_SCHEDULE, + seed: Number.isFinite(Number(args.seed)) + ? Number(args.seed) + : Math.floor(Math.random() * 4294967296), + n_samples: clampNumber(args.n_samples, 1, 4, 1), + cfg_rescale: clampNumber(args.cfg_rescale, 0, 1, 0), + negative_prompt: + args.uc || args.negative_prompt || args.undesired_content || DEFAULT_UC, + }; +} +function buildV4Prompt(basePrompt, charCaptions) { + const useCoords = charCaptions.some((x) => + x.centers.some((p) => p.x !== 0.5 || p.y !== 0.5), + ); + return { + caption: { + base_caption: basePrompt || "", + char_captions: charCaptions.map((x) => ({ + char_caption: x.char_caption, + centers: x.centers, + })), + }, + use_coords: useCoords, + use_order: true, + }; +} +function buildV4NegativePrompt(uc, charCaptions) { + return { + caption: { + base_caption: uc || DEFAULT_UC, + char_captions: charCaptions.map((x) => ({ + char_caption: x.char_uc || "", + centers: x.centers, + })), + }, + legacy_uc: false, + }; +} +async function buildVibeFields(vibeEntries) { + if (vibeEntries.length > 16) + throw new Error("Vibe Transfer 最多支持 16 个参考图"); + const images = await Promise.all( + vibeEntries.map((x) => processImageInput(x.image)), + ); + return { + reference_image_multiple: images.map(toNaiImageField), + reference_information_extracted_multiple: vibeEntries.map((x) => + Number(x.informationExtracted ?? x.information_extracted ?? 1), + ), + reference_strength_multiple: vibeEntries.map((x) => + Number(x.strength ?? 0.6), + ), + }; +} +async function buildGeneratePayload(args, model) { + const chars = parseCharacterPrompts(args); + const p = buildBaseParameters(args); + p.v4_prompt = buildV4Prompt(args.prompt, chars); + p.v4_negative_prompt = buildV4NegativePrompt( + args.uc || args.negative_prompt, + chars, + ); + p.characterPrompts = []; + p.inpaintImg2ImgStrength = 1; + if (args.vibe) + Object.assign(p, await buildVibeFields(asJson(args.vibe, args.vibe) || [])); + return { + action: ACTION.GENERATE, + model, + input: args.prompt || "", + parameters: p, + }; +} +async function buildImg2ImgPayload(args, model, imageDataUri) { + const payload = await buildGeneratePayload(args, model); + payload.action = ACTION.IMG2IMG; + payload.parameters.image = toNaiImageField(imageDataUri); + payload.parameters.strength = clampNumber(args.strength, 0.01, 0.99, 0.7); + payload.parameters.noise = clampNumber(args.noise, 0, 0.99, 0); + return payload; +} +async function buildInfillPayload( + args, + model, + imageDataUri, + maskDataUri, + actionValue = ACTION.INFILL, +) { + const payload = await buildGeneratePayload(args, model); + payload.action = actionValue; + payload.parameters.image = toNaiImageField(imageDataUri); + payload.parameters.mask = toNaiImageField(maskDataUri); + payload.parameters.add_original_image = args.add_original_image === true; + payload.parameters.strength = clampNumber(args.strength, 0.01, 0.99, 0.7); + payload.parameters.noise = clampNumber(args.noise, 0, 0.99, 0); + return payload; +} +async function buildUpscalePayload(args, imageDataUri, width, height) { + const size = parseFreeSize(args.resolution || args.size); + return { + image: toNaiImageField(imageDataUri), + width: Number(args.width) || width || size.width, + height: Number(args.height) || height || size.height, + scale: [2, 4].includes(Number(args.scale)) ? Number(args.scale) : 4, + }; +} +async function buildAugmentPayload( + reqType, + imageDataUri, + width, + height, + extra = {}, +) { + const size = parseFreeSize(extra.resolution || extra.size); + const p = { + req_type: reqType, + image: toNaiImageField(imageDataUri), + width: Number(extra.width) || width || size.width, + height: Number(extra.height) || height || size.height, + }; + if (reqType === "emotion") + Object.assign(p, { emotion: extra.emotion, prompt: extra.prompt }); + if (reqType === "colorize") p.defry = Number(extra.defry || 0); + return p; +} +async function buildEncodeVibePayload( + imageDataUri, + informationExtracted, + model, +) { + return { + image: toNaiImageField(imageDataUri), + information_extracted: Number(informationExtracted ?? 1), + model, + }; +} - // 检查响应是否为ZIP格式 - const contentType = response.headers['content-type'] || ''; - const isZipResponse = contentType.includes('application/zip') || - contentType.includes('application/octet-stream') || - contentType.includes('binary/octet-stream') || - contentType.includes('octet-stream'); - - if (!isZipResponse) { - // 如果不是ZIP,可能是错误响应,尝试解析为JSON - try { - const errorText = Buffer.from(response.data).toString('utf8'); - const errorJson = JSON.parse(errorText); - throw new Error(`NovelAI Plugin Error: API returned error: ${JSON.stringify(errorJson)}`); - } catch (parseError) { - throw new Error(`NovelAI Plugin Error: Unexpected response format. Expected ZIP file but got: ${contentType}`); - } +// ==================== 段 08 · 传输层 ==================== +function buildAgents() { + if (!NOVELAI_PROXY) return { httpAgent: undefined, httpsAgent: undefined }; + return { + httpAgent: new HttpProxyAgent(NOVELAI_PROXY), + httpsAgent: new HttpsProxyAgent(NOVELAI_PROXY), + }; +} +async function requestOnce(url, payload, key, options = {}) { + const method = options.method || "POST"; + const config = { + method, + url, + data: method === "GET" ? undefined : payload, + params: options.params, + responseType: options.responseType || "arraybuffer", + timeout: options.timeout || 180000, + headers: { + Authorization: `Bearer ${key}`, + "Content-Type": "application/json", + }, + ...buildAgents(), + }; + if (DEBUG_MODE && method !== "GET") log("request", url, redact(payload)); + return axios(config); +} +async function requestWithRetry(url, payload, key, options = {}) { + let last; + for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { + try { + return await requestOnce(url, payload, key, options); + } catch (error) { + last = error; + const status = error.response?.status; + // 504 由前置网关(Cloudflare 等)返回,不在上游文档的错误码表内, + // 但属于典型瞬时故障,应与 502/503 同等对待。 + if ( + ![429, 500, 502, 503, 504].includes(status) || + attempt === MAX_RETRIES + ) + throw error; + await sleep(RETRY_BASE_DELAY_MS * Math.pow(3, attempt)); } - - // 解压ZIP并提取图片 - const zipBuffer = Buffer.from(response.data); - const extractedImages = await extractImagesFromZip(zipBuffer); - - if (debugMode) FORCE_LOG(`[NovelAIGen] Extracted ${extractedImages.length} images from ZIP`); - - // 保存图片并生成URL - const novelaiImageDir = path.join(PROJECT_BASE_PATH, 'image', 'novelaigen'); - await fs.mkdir(novelaiImageDir, { recursive: true }); - - const savedImages = []; - - for (let i = 0; i < extractedImages.length; i++) { - const image = extractedImages[i]; - const generatedFileName = `${uuidv4()}.${image.extension}`; - const localImageServerPath = path.join(novelaiImageDir, generatedFileName); - - await fs.writeFile(localImageServerPath, image.buffer); - if (debugMode) FORCE_LOG(`[NovelAIGen] Image ${i + 1} saved to: ${localImageServerPath}`); - - const relativeServerPathForUrl = path.join('novelaigen', generatedFileName).replace(/\\\\/g, '/'); - // 优先使用HTTPS公网URL,如果没有配置则回退到HTTP本地URL - const baseUrl = VAR_HTTPS_URL ? VAR_HTTPS_URL : `${VAR_HTTP_URL}:${SERVER_PORT}`; - const accessibleImageUrl = `${baseUrl}/pw=${IMAGESERVER_IMAGE_KEY}/images/${relativeServerPathForUrl}`; - - savedImages.push({ - filename: generatedFileName, - url: accessibleImageUrl, - localPath: localImageServerPath - }); + } + throw last; +} +async function dispatch( + capability, + requestedModelInput, + purpose, + payloadBuilder, + options = {}, +) { + const resolved = + requestedModelInput === null + ? { model: null, note: null } + : resolveModel(requestedModelInput, purpose); + const plan = buildChannelPlan(capability, resolved.model); + if (!plan.length) throw new Error(`没有支持 ${capability} 的可用渠道`); + const failures = []; + for (const candidate of plan) { + try { + const payload = await payloadBuilder(candidate.model); + const requestUrl = `${candidate.url}${candidate.prefix || ""}${options.endpoint}`; + const response = await requestWithRetry( + requestUrl, + payload, + candidate.key, + options, + ); + return { + response, + model: candidate.model, + note: resolved.note, + channelUrl: candidate.url, + requestUrl, + }; + } catch (error) { + failures.push( + `${candidate.url}${candidate.prefix || ""} / ${candidate.model || "-"}: ${describeResponseError(error)}`, + ); + } + } + throw new Error(`全部渠道请求失败:\n${failures.join("\n")}`); +} +async function dispatchWithEnumProbe( + enumKey, + capability, + modelInput, + purpose, + payloadBuilderFactory, + options = {}, +) { + const candidates = ENUM_PROBE ? ENUM_CANDIDATES[enumKey] || [null] : [null]; + const failures = []; + for (const value of candidates) { + try { + const result = await dispatch( + capability, + modelInput, + purpose, + (model) => payloadBuilderFactory(model, value), + options, + ); + if (value) + console.error( + `[ENUM_PROBE] ${enumKey} 命中值: ${value},建议固化到配置`, + ); + return result; + } catch (error) { + failures.push(`${value || "default"}: ${error.message}`); + const message = String(error.message); + const looksLikeEnumRejection = + /\b400\b/.test(message) || + /infill|inpainting|bg.?removal|req_type|invalid\s+action/i.test(message); + if (!ENUM_PROBE || !looksLikeEnumRejection) throw error; } + } + throw new Error(`枚举候选耗尽:${failures.join("\n")}`); +} - // 生成结果消息 - const altText = args.prompt ? args.prompt.substring(0, 80) + (args.prompt.length > 80 ? "..." : "") : "NovelAI生成的图片"; - - let successMessage = `NovelAI 图片生成成功!共生成 ${savedImages.length} 张图片\n\n`; - - successMessage += `生成参数:\n`; - successMessage += `- 模型: ${payload.model}\n`; - successMessage += `- 尺寸: ${payload.parameters.width}x${payload.parameters.height}\n`; - successMessage += `- 采样器: ${payload.parameters.sampler}\n`; - successMessage += `- 步数: ${payload.parameters.steps}\n`; - successMessage += `- 引导系数: ${payload.parameters.scale}\n\n`; - - successMessage += `详细信息:\n`; - - savedImages.forEach((image, index) => { - successMessage += `图片 ${index + 1}:\n`; - successMessage += `- 图片URL: ${image.url}\n`; - successMessage += `- 服务器路径: image/novelaigen/${image.filename}\n`; - successMessage += `- 文件名: ${image.filename}\n\n`; - }); - - successMessage += `请务必使用以下HTML 标签将图片直接展示给用户 (您可以调整width属性,建议200-500像素):\n`; - - savedImages.forEach((image, index) => { - successMessage += `${altText} ${index + 1}\n`; +// ==================== 段 09 · 响应解析层 ==================== +async function extractImagesFromZip(zipBuffer) { + return new Promise((resolve, reject) => { + const images = []; + let settled = false; + yauzl.fromBuffer(zipBuffer, { lazyEntries: true }, (err, zip) => { + if (err) return reject(new Error(`读取 ZIP 失败: ${err.message}`)); + zip.readEntry(); + zip.on("entry", (entry) => { + if ( + /\/$/.test(entry.fileName) || + !/\.(png|jpe?g|webp|gif)$/i.test(entry.fileName) + ) + return zip.readEntry(); + zip.openReadStream(entry, (e, stream) => { + if (e) return reject(e); + const chunks = []; + stream.on("data", (c) => chunks.push(c)); + stream.on("error", reject); + stream.on("end", () => { + const buffer = Buffer.concat(chunks); + images.push({ buffer, mimeType: guessMimeFromBuffer(buffer) }); + zip.readEntry(); + }); + }); + }); + zip.on("end", () => { + if (settled) return; + settled = true; + images.length + ? resolve(images) + : reject(new Error("NovelAI ZIP 响应为空,未找到图片")); + }); + zip.on("error", reject); }); - - return successMessage; + }); +} +function decodeImageValue(value) { + if (typeof value !== "string") return null; + const m = value.match(/^data:(image\/[^;]+);base64,(.+)$/i); + if (m) return { buffer: Buffer.from(m[2], "base64"), mimeType: m[1] }; + if (/^[A-Za-z0-9+/=]{100,}$/.test(value)) + return { buffer: Buffer.from(value, "base64"), mimeType: "image/png" }; + return null; +} +function parseJsonImageResponse(parsed) { + const out = []; + const add = (x) => { + if (typeof x === "string" && /^https?:\/\//i.test(x)) return; + const y = decodeImageValue(x); + if (y) out.push(y); + }; + for (const x of parsed?.images || []) add(x.image || x.b64 || x.url); + for (const x of parsed?.data || []) add(x.b64_json || x.url); + for (const x of parsed?.content || []) + if (x.type === "image_url") add(x.image_url?.url || x.url); + const text = JSON.stringify(parsed); + for (const x of text.match(/data:image\/[^;]+;base64,[A-Za-z0-9+/=]+/g) || []) + add(x); + return out; +} +async function parseImageResponse(response) { + const type = String(response.headers?.["content-type"] || "").toLowerCase(); + const buffer = Buffer.from(response.data); + if (type.includes("zip") || type.includes("octet-stream")) + return extractImagesFromZip(buffer); + if (type.includes("application/json")) { + const parsed = JSON.parse(buffer.toString("utf8")); + const images = parseJsonImageResponse(parsed); + if (!images.length) throw new Error("JSON 响应中未找到图片"); + return images; + } + if (type.includes("image/")) + return [{ buffer, mimeType: type.split(";")[0] }]; + if (type.includes("msgpack")) + throw new Error( + `响应为 MessagePack 格式(${type}),当前版本未实现解析。该渠道的此端点可能仅提供 MessagePack 输出,请改用其他端点或渠道。`, + ); + const text = buffer.toString("utf8"); + throw new Error(`未知响应类型 ${type}: ${truncate(text, 500)}`); } -async function main() { - if (debugMode) FORCE_LOG('[NovelAIGen] Plugin started, debug mode enabled'); - - let inputChunks = []; - process.stdin.setEncoding('utf8'); - - for await (const chunk of process.stdin) { - inputChunks.push(chunk); - } - const inputData = inputChunks.join(''); - let parsedArgs; +// ==================== 段 10 · 输出层 ==================== +function extensionForMime(mime) { + return ( + { "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif" }[mime] || + "png" + ); +} +function buildAccessibleUrl(relativePath) { + const base = (VAR_HTTPS_URL || `${VAR_HTTP_URL}:${SERVER_PORT}`).replace( + /\/$/, + "", + ); + return `${base}/pw=${IMAGESERVER_IMAGE_KEY}/images/${relativePath}`; +} +async function saveImages(images, subDir = "novelaigen") { + const dir = assertPathInside( + PROJECT_BASE_PATH, + path.resolve(PROJECT_BASE_PATH, "image", subDir), + ); + await fs.mkdir(dir, { recursive: true }); + const saved = []; + for (const image of images) { + const fileName = `${uuidv4()}.${extensionForMime(image.mimeType)}`; + const localPath = assertPathInside(dir, path.resolve(dir, fileName)); + await fs.writeFile(localPath, image.buffer); + const serverPath = `image/${subDir}/${fileName}`; + saved.push({ + fileName, + serverPath, + localPath, + accessibleUrl: buildAccessibleUrl(toPosixRelative(subDir, fileName)), + }); + } + return saved; +} +function buildSuccessResult( + savedImages, + meta = {}, + showBase64 = false, + imageBuffers = [], +) { + const lines = [ + `NovelAI ${meta.command || "操作"}成功,共 ${savedImages.length} 张图片`, + `模型: ${meta.model || "-"} | 尺寸: ${meta.size || "-"} | 采样器: ${meta.sampler || "-"} | 步数: ${meta.steps || "-"} | scale: ${meta.scale || "-"} | seed: ${meta.seed ?? "-"} | 数量: ${savedImages.length}`, + ]; + if (meta.note) lines.push(`提示: ${meta.note}`); + savedImages.forEach((x, i) => + lines.push( + `图片 ${i + 1}: ${x.accessibleUrl} | ${x.serverPath} | ${x.fileName}`, + ), + ); + lines.push("请使用返回的 URL 生成 标签展示图片。"); + const content = [{ type: "text", text: lines.join("\n") }]; + if (showBase64) + imageBuffers.forEach((x) => + content.push({ + type: "image_url", + image_url: { + url: `data:${x.mimeType};base64,${x.buffer.toString("base64")}`, + }, + }), + ); + return { + content, + details: { + serverPath: savedImages.map((x) => x.serverPath), + fileName: savedImages.map((x) => x.fileName), + imageUrls: savedImages.map((x) => x.accessibleUrl), + prompt: truncate(meta.prompt), + command: meta.command, + model: meta.model, + size: meta.size, + seed: meta.seed, + imageCount: savedImages.length, + note: meta.note || null, + }, + }; +} +// ==================== 段 11 · 命令 handler 层 ==================== +function normalizeArgs(rawArgs) { + const args = { ...rawArgs }; + const raw = String( + args.command || args.commandIdentifier || "", + ).toLowerCase(); + const map = { + generate: "generate", + txt2img: "generate", + t2i: "generate", + novelaigenerateimage: "generate", + img2img: "img2img", + i2i: "img2img", + edit: "img2img", + inpaint: "inpaint", + infill: "inpaint", + novelaiinpaint: "inpaint", + upscale: "upscale", + enhance: "upscale", + augment: "augment", + director: "augment", + encode_vibe: "encode_vibe", + vibe: "encode_vibe", + suggest_tags: "suggest_tags", + tags: "suggest_tags", + subscription: "subscription", + account: "subscription", + anlas: "subscription", + }; + args.command = + map[raw] || + (args.mask + ? "inpaint" + : collectImageInputs(args).length + ? "img2img" + : "generate"); + args.prompt = args.prompt || args.Prompt || args.text; + args.resolution = args.resolution || args.size || args.image_size; + args.uc = args.uc || args.negative_prompt || args.undesired_content; + return args; +} +async function saveGenerationResult(result, args, showBase64, command) { + const images = await parseImageResponse(result.response); + const saved = await saveImages(images); + return buildSuccessResult( + saved, + { + command, + model: result.model, + note: result.note, + prompt: args.prompt, + size: args.resolution, + seed: asJson(args.seed, args.seed), + sampler: args.sampler || DEFAULT_SAMPLER, + steps: args.steps || DEFAULT_STEPS, + scale: args.scale || DEFAULT_SCALE, + }, + showBase64, + images, + ); +} +async function handleGenerate(args, showBase64) { + if (args.vibe) assertVibeSupported(resolveModel(args.model, "base").model); + const result = await dispatch( + "gen", + args.model, + "base", + (model) => buildGeneratePayload(args, model), + { endpoint: ENDPOINTS.GENERATE }, + ); + return saveGenerationResult(result, args, showBase64, "generate"); +} +async function handleImg2Img(args, showBase64) { + const input = collectImageInputs(args)[0]; + if (!input) throw new Error("img2img 缺少 image 参数"); + const image = await processImageInput(input); + const result = await dispatch( + "i2i", + args.model, + "base", + (model) => buildImg2ImgPayload(args, model, image), + { endpoint: ENDPOINTS.GENERATE }, + ); + return saveGenerationResult(result, args, showBase64, "img2img"); +} +async function handleInpaint(args, showBase64) { + const input = collectImageInputs(args)[0]; + if (!input || !args.mask) + throw new Error("inpaint 必须同时提供 image 与 mask"); + const image = await processImageInput(input), + mask = await processImageInput(args.mask); + const result = await dispatchWithEnumProbe( + "action_infill", + "infill", + args.model, + "inpainting", + (model, action) => buildInfillPayload(args, model, image, mask, action), + { endpoint: ENDPOINTS.GENERATE }, + ); + return saveGenerationResult(result, args, showBase64, "inpaint"); +} +async function handleUpscale(args, showBase64) { + const input = collectImageInputs(args)[0]; + if (!input) throw new Error("upscale 缺少 image 参数"); + const image = await processImageInput(input); + const result = await dispatch( + "upscale", + null, + "base", + () => buildUpscalePayload(args, image), + { endpoint: ENDPOINTS.UPSCALE }, + ); + return saveGenerationResult(result, args, showBase64, "upscale"); +} +async function handleAugment(args, showBase64) { + const req = args.req_type || args.reqType || "emotion"; + if (!AUGMENT_REQ_TYPES.includes(req)) + throw new Error( + `未知 augment req_type ${req},可选:${AUGMENT_REQ_TYPES.join(", ")}`, + ); + const input = collectImageInputs(args)[0]; + if (!input) throw new Error("augment 缺少 image 参数"); + const image = await processImageInput(input); + const factory = (model, value) => + buildAugmentPayload(value || req, image, undefined, undefined, args); + const result = + req === "bg-removal" + ? await dispatchWithEnumProbe( + "augment_bgremoval", + "augment", + null, + "base", + factory, + { endpoint: ENDPOINTS.AUGMENT }, + ) + : await dispatch("augment", null, "base", () => factory(null, req), { + endpoint: ENDPOINTS.AUGMENT, + }); + return saveGenerationResult(result, args, showBase64, "augment"); +} +async function handleEncodeVibe(args) { + const input = collectImageInputs(args)[0]; + if (!input) throw new Error("encode_vibe 缺少 image 参数"); + const image = await processImageInput(input); + const raw = toNaiImageField(image), + info = Number(args.informationExtracted ?? args.information_extracted ?? 1), + model = resolveModel(args.model, "base").model; + assertVibeSupported(model); + const dir = assertPathInside( + PROJECT_BASE_PATH, + path.resolve(PROJECT_BASE_PATH, "image", "novelaigen", "vibes"), + ); + await fs.mkdir(dir, { recursive: true }); + const file = path.resolve( + dir, + `${crypto + .createHash("sha256") + .update(raw + info) + .digest("hex") + .slice(0, 16)}.json`, + ); + try { + const cached = JSON.parse(await fs.readFile(file, "utf8")); + return { + content: [ + { + type: "text", + text: `Vibe 编码命中缓存,未消耗 Anlas: ${toPosixRelative("image", "novelaigen", "vibes", path.basename(file))}`, + }, + ], + details: { cacheHit: true, vibeFilePath: file, vibe: cached }, + }; + } catch {} + const result = await dispatch( + "vibe", + model, + "base", + (m) => buildEncodeVibePayload(image, info, m), + { endpoint: ENDPOINTS.ENCODE_VIBE }, + ); + const data = JSON.parse(Buffer.from(result.response.data).toString("utf8")); + await fs.writeFile(file, JSON.stringify(data, null, 2)); + return { + content: [{ type: "text", text: `Vibe 编码完成,已保存 ${file}` }], + details: { cacheHit: false, vibeFilePath: file, vibe: data }, + }; +} +async function handleSuggestTags(args) { + const result = await dispatch("tags", null, "base", () => null, { + endpoint: ENDPOINTS.SUGGEST_TAGS, + method: "GET", + responseType: "json", + params: { prompt: args.prompt || "" }, + }); + return { + content: [{ type: "text", text: JSON.stringify(result.response.data) }], + details: { command: "suggest_tags" }, + }; +} +// 官方账户 API 在独立 host(api.novelai.net);中转站在同 host 加前缀。 +// 按渠道是否配置 prefix 分流,并逐渠道汇总结果。 +async function handleSubscription() { + if (!CHANNELS.length) throw new Error("订阅查询需要至少一个已配置渠道"); + const results = []; + const failures = []; + for (const channel of CHANNELS) { + const base = channel.prefix + ? `${channel.url}${channel.prefix}` + : NOVELAI_ACCOUNT_URL.replace(/\/$/, ""); + const url = `${base}${ENDPOINTS.SUBSCRIPTION}`; try { - if (!inputData.trim()) { - const errorMsg = "NovelAI Plugin Error: No input data received from stdin."; - if (debugMode) FORCE_LOG('[NovelAIGen] Error:', errorMsg); - console.log(JSON.stringify({ status: "error", error: errorMsg })); - process.exit(1); - return; - } - - if (debugMode) FORCE_LOG('[NovelAIGen] Received input data:', inputData.substring(0, 200) + (inputData.length > 200 ? '...' : '')); - - parsedArgs = JSON.parse(inputData); - const formattedResultString = await generateImageAndSave(parsedArgs); - console.log(JSON.stringify({ status: "success", result: formattedResultString })); - } catch (e) { - let detailedError = e.message || "Unknown error in NovelAI plugin"; - - if (debugMode) { - FORCE_LOG('[NovelAIGen] Error caught in main:', e.toString()); - if (e.stack) { - FORCE_LOG('[NovelAIGen] Error stack:', e.stack); - } - } - - if (e.response && e.response.data) { - // 如果API返回了特定的错误消息,包含它 - try { - const errorText = Buffer.from(e.response.data).toString('utf8'); - detailedError += ` - API Response: ${errorText}`; - if (debugMode) FORCE_LOG('[NovelAIGen] API Error Response:', errorText); - } catch (parseError) { - detailedError += ` - API Response: [Binary data, cannot parse]`; - if (debugMode) FORCE_LOG('[NovelAIGen] API returned binary data, cannot parse as text'); - } - } - - const finalErrorMessage = detailedError.startsWith("NovelAI Plugin Error:") ? detailedError : `NovelAI Plugin Error: ${detailedError}`; - if (debugMode) FORCE_LOG('[NovelAIGen] Final error message:', finalErrorMessage); - - console.log(JSON.stringify({ status: "error", error: finalErrorMessage })); - process.exit(1); + const response = await requestWithRetry(url, null, channel.key, { + method: "GET", + responseType: "json", + }); + results.push({ channel: channel.url, url, data: response.data }); + } catch (error) { + failures.push(`${url}: ${describeResponseError(error)}`); } + } + if (!results.length) + throw new Error(`全部渠道订阅查询失败:\n${failures.join("\n")}`); + const lines = results.map( + (x) => `渠道 ${x.channel}\n${JSON.stringify(x.data, null, 2)}`, + ); + if (failures.length) + lines.push(`以下渠道查询失败:\n${failures.join("\n")}`); + return { + content: [{ type: "text", text: lines.join("\n\n") }], + details: { command: "subscription", accounts: results, failures }, + }; } -main(); \ No newline at end of file +// ==================== 段 12 · main 入口 ==================== +function outputAndExit(payload, code = 0) { + const text = JSON.stringify(payload); + process.stdout.write(text, () => process.exit(code)); +} +async function main() { + let raw = ""; + for await (const chunk of process.stdin) raw += chunk; + if (!raw.trim()) + return outputAndExit( + { status: "error", error: "NovelAI Plugin Error: 未收到 stdin 输入" }, + 1, + ); + let args; + try { + args = normalizeArgs(JSON.parse(raw)); + if ( + !PROJECT_BASE_PATH || + !SERVER_PORT || + !IMAGESERVER_IMAGE_KEY || + !VAR_HTTP_URL + ) + throw new Error( + "缺少 PROJECT_BASE_PATH、SERVER_PORT、IMAGESERVER_IMAGE_KEY 或 VarHttpUrl", + ); + if (!CHANNELS.length) + throw new Error("未配置 NOVELAI_API_KEY 或有效的多渠道"); + const showBase64 = args.showbase64 === "true" || args.showbase64 === true; + const handlers = { + generate: handleGenerate, + img2img: handleImg2Img, + inpaint: handleInpaint, + upscale: handleUpscale, + augment: handleAugment, + encode_vibe: handleEncodeVibe, + suggest_tags: handleSuggestTags, + subscription: handleSubscription, + }; + if (!handlers[args.command]) throw new Error(`未知命令 ${args.command}`); + outputAndExit({ + status: "success", + result: await handlers[args.command](args, showBase64), + }); + } catch (error) { + let text = error.message || String(error); + if (error.response?.data) + text += ` - API Response: ${describeResponseError(error)}`; + outputAndExit( + { status: "error", error: `NovelAI Plugin Error: ${text}` }, + 1, + ); + } +} +main(); diff --git a/Plugin/NovelAIGen/NovelAIGen.zip b/Plugin/NovelAIGen/NovelAIGen.zip index ea5b797..5857cba 100644 Binary files a/Plugin/NovelAIGen/NovelAIGen.zip and b/Plugin/NovelAIGen/NovelAIGen.zip differ diff --git a/Plugin/NovelAIGen/README.md b/Plugin/NovelAIGen/README.md index 778890e..579573e 100644 --- a/Plugin/NovelAIGen/README.md +++ b/Plugin/NovelAIGen/README.md @@ -1,185 +1,333 @@ -# NovelAI 图片生成 VCP 插件 +# NovelAIGen v2.1.0 -这是一个基于 VCP (Virtual Character Plugin) 架构的 NovelAI 图片生成插件,允许 AI 通过 VCP 协议调用 NovelAI API 生成高质量的动漫风格图片。 +NovelAI 六端点、多渠道、全参数 VCP 网关。覆盖文生图、图生图、局部重绘、放大、Director 工具、Vibe 编码、标签建议与订阅查询,支持官方直连与第三方中转站。 -## 功能特点 +## 1. 能力清单 -- **高质量图片生成**:使用固定的 NAI Diffusion 4.5 Curated 模型生成高质量动漫风格图片 -- **极简使用**:只需提供提示词,其他参数自动使用官方推荐的最佳配置 -- **官方优化**:所有参数均使用 NovelAI 官方推荐的最佳默认设置,确保稳定性和最优效果 -- **ZIP 文件处理**:自动解压 NovelAI 返回的 ZIP 格式图片包 -- **本地缓存**:生成的图片保存到本地并提供访问链接 -- **调试支持**:可选的调试模式,提供详细执行日志 +| 命令 | 端点 | 用途 | +| ------------------- | --------------------------------- | ------------------------- | +| NovelAIGenerate | `/ai/generate-image` | 文生图 | +| NovelAIImg2Img | `/ai/generate-image` | 图生图 | +| NovelAIInpaint | `/ai/generate-image` | 局部重绘,action=`infill` | +| NovelAIUpscale | `/ai/upscale` | 图片放大 | +| NovelAIAugment | `/ai/augment-image` | Director 工具 | +| NovelAIEncodeVibe | `/ai/encode-vibe` | Vibe 编码 | +| NovelAISuggestTags | `/ai/generate-image/suggest-tags` | 标签建议 | +| NovelAISubscription | `/user/subscription` | 订阅与额度查询 | -## 系统要求 +使用中转站时,上表端点会自动加上配置的路径前缀(见第 4 节)。 -- Node.js v18.0.0 或更高版本 -- VCP 工具箱环境 +## 2. 快速开始 -您可以通过以下命令验证Node.js安装: +1. 取得 API token:官方为 NovelAI 账户的 Persistent API Token;中转站为该站签发的 token。 +2. 复制 `config.env.example` 到 `config.env`,至少设置 + `NOVELAI_API_KEY`、`PROJECT_BASE_PATH`、`SERVER_PORT`、`IMAGESERVER_IMAGE_KEY`、`VarHttpUrl`。 +3. 安装依赖并检查语法: ```bash -node --version # 应显示v18.0.0或更高版本 +cd Plugin/NovelAIGen +npm install +node --check NovelAIGen.js ``` -## 安装步骤 - -1. 确保插件文件位于 VCP 工具箱的 `Plugin/NovelAIGen/` 目录中 +最小调用: -2. 安装依赖: +```text +tool_name: NovelAIGen +command: generate +prompt: 1girl, blue eyes, anime illustration +resolution: 832x1216 +``` -```bash -cd Plugin/NovelAIGen -npm install +推荐先跑一次 `subscription` 命令——它是 GET 请求、不生成图片、不消耗额度,但能一次性验证 +token 有效性、路径前缀是否正确、以及渠道遍历是否通畅。 + +## 3. 完整配置项 + +| 变量名 | 类型 | 默认值 | 说明 | +| ------------------------- | ------- | --------------------------- | ---------------------------------------------------------- | +| NOVELAI_API_KEY | string | 空 | 单渠道 Bearer token,多渠道时可空 | +| NOVELAI_BASE_URL | string | `https://image.novelai.net` | 单渠道图像 API 基地址 | +| NOVELAI_PATH_PREFIX | string | 空 | 单渠道原生协议路径前缀,中转站常用 `/native`;官方留空 | +| NOVELAI_ACCOUNT_URL | string | `https://api.novelai.net` | 官方账户 API 域,仅在渠道无前缀时使用 | +| MULTI_CHANNEL | boolean | false | 启用多渠道 | +| NOVELAI_CHANNELS | string | 空 | `URL\|KEY\|MODELS\|CAPS\|PATH_PREFIX;...` | +| MODEL_ALIASES | string | 空 | `alias=model;...` 覆盖内置别名表 | +| INPAINT_MODELS | string | 空 | `base=variant;...` 追加 inpainting 映射 | +| INPAINT_FALLBACK_CHAIN | string | 空 | `base>variant;...` 覆盖降级链 | +| VIBE_UNSUPPORTED_PREFIXES | string | 空 | 本地拦截 Vibe 的模型前缀,逗号分隔;留空表示交给上游判断 | +| DEFAULT_MODEL | string | `v4.5` | 默认模型别名 | +| DEFAULT_STEPS | number | 23 | 默认步数,范围 1–50 | +| DEFAULT_SCALE | number | 5 | 默认引导系数 | +| DEFAULT_SAMPLER | string | `k_euler_ancestral` | 默认采样器 | +| DEFAULT_NOISE_SCHEDULE | string | `karras` | 默认噪声调度 | +| DEFAULT_UC | string | `lowres, artistic error, ...`(完整值见 config.env.example) | 默认负面提示词 | +| RESOLUTION_PRESETS | string | 内置白名单 | 逗号分隔覆盖分辨率白名单 | +| MAX_RETRIES | number | 2 | 429/5xx 最大重试次数 | +| RETRY_BASE_DELAY_MS | number | 2000 | 指数退避基础毫秒数,实际延迟为 base × 3^attempt | +| MAX_IMAGE_SIZE_MB | number | 8 | 输入图片大小上限 | +| ENUM_PROBE | boolean | true | 枚举候选链探测 | +| NovelAIProxy | string | 空 | HTTP/HTTPS 代理,两种协议分别使用对应 agent | +| DebugMode | boolean | false | 脱敏调试日志,base64 与密钥不会落入日志 | +| PROJECT_BASE_PATH | string | 空 | VCP 项目根目录,通常由框架注入 | +| SERVER_PORT | string | 空 | 图片服务器端口,通常由框架注入 | +| IMAGESERVER_IMAGE_KEY | string | 空 | 图片访问密钥,通常由框架注入 | +| VarHttpUrl | string | 空 | HTTP 图片服务地址 | +| VarHttpsUrl | string | 空 | HTTPS 图片服务地址,设置时优先于 HTTP | + +## 4. 中转站支持 + +部分第三方站点在 NovelAI 原生协议路由前加一段路径前缀。以 YesNovelAI(nai.rinko.ai)为例, +其原生入口是 `/native/ai/generate-image` 而非 `/ai/generate-image`。 + +单渠道配置: + +```env +NOVELAI_BASE_URL=https://nai.rinko.ai +NOVELAI_PATH_PREFIX=/native +NOVELAI_API_KEY=ynai-xxxxxxxx ``` -## 配置说明 +请求会被拼接为 `baseUrl + prefix + endpoint`。前缀留空时拼接结果与不带前缀完全相同, +因此官方直连用户无需改动任何配置。 -### API密钥配置 +`subscription` 命令按渠道分流:配置了前缀的渠道走 `url + prefix + /user/subscription` +(中转站通常与图像 API 同域),未配置前缀的渠道走 `NOVELAI_ACCOUNT_URL`(官方账户 API +在独立域名 api.novelai.net)。 -1. 在 NovelAI 网站 (https://novelai.net/) 注册账户并获取 API 密钥 -2. 在项目根目录的 `.env` 文件中添加您的 NovelAI API 密钥: +**中转站的能力边界**:站点可能只为部分端点配置了计费与路由。实测遇到过 Director(augment) +返回 `PRICE_NOT_CONFIGURED`——这表示请求已经到达站点计费层,路径与鉴权都正确,只是该端点 +未开放。这类错误属于站点侧策略,不是插件问题。 -``` -NOVELAI_API_KEY=pst-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +## 5. 多渠道与能力位 + +```env +MULTI_CHANNEL=true +NOVELAI_CHANNELS=https://image.novelai.net|pst-xxxx|||;https://nai.rinko.ai|ynai-xxxx|||/native ``` -> **注意**:您需要自备 NovelAI 的 API 密钥才能使用此服务。NovelAI 使用订阅制,不同订阅等级有不同的使用限制。 +五段依次为 URL、KEY、模型清单、能力清单、路径前缀。`MODELS` 为空表示接受任意模型, +`CAPS` 为空表示全能力,`PATH_PREFIX` 为空表示官方行为。 +能力位取值:`gen`、`i2i`、`infill`、`vibe`、`augment`、`upscale`、`tags`。 -### 可选配置 +分发流程:按能力位过滤渠道 → 按模型匹配收窄 → 洗牌 → 逐个尝试。任一渠道失败会继续尝试 +下一个,全部失败时聚合报错并逐行列出各渠道原因(含 URL 与模型名,不含密钥)。 +官方渠道与中转站渠道可以混合配置,一方不可用时自动落到另一方。 -在 `.env` 文件中可以添加以下可选配置: +## 6. 模型别名与三维解析 -``` -# 调试模式(可选,默认false) -DebugMode=false +| 别名 | 标识符 | +| ------ | ----------------------------- | +| v5 | `nai-diffusion-5-full` | +| v5c | `nai-diffusion-5-curated` | +| v4.5 | `nai-diffusion-4-5-full` | +| v4.5c | `nai-diffusion-4-5-curated` | +| v4 | `nai-diffusion-4-full` | +| v4c | `nai-diffusion-4-curated` | +| v3 | `nai-diffusion-3` | +| furry | `nai-diffusion-furry-3` | +| furry3 | `nai-diffusion-3-furry` | + +V5 标识符来源为中转站 `/v1/models` 实证。官方直连是否接受同一组 ID 未经验证; +若遇拒绝,用 `MODEL_ALIASES` 覆盖。`furry` 与 `furry3` 是同一模型的两种写法, +分别来自 SDK 枚举与中转站接口,按渠道选用。 + +也可以直接传原始标识符(以 `nai-` 或 `safe-` 开头时跳过别名表)。 + +模型按 `version` × `tier` × `purpose` 三维解析。inpaint 的 purpose 不是普通模型上的开关, +而是独立训练的 `-inpainting` checkpoint。内置映射覆盖 V3、V4、V4.5-curated 与 furry; +未命中时沿降级链选择并在返回文本中显式告知实际使用的模型。 + +出于保守,`nai-diffusion-4-5-full-inpainting` 与 V5 系的 inpainting 变体未全部内置—— +它们只在部分中转站的模型列表中出现。需要时通过 `INPAINT_MODELS` 添加: + +```env +INPAINT_MODELS=nai-diffusion-4-5-full=nai-diffusion-4-5-full-inpainting ``` -## 使用方法 +## 7. 协议要点 -### 作为 VCP 插件使用 +- inpaint 的 action 字面值是 `infill`(SDK 枚举成员名为 INPAINTING,但序列化值不同) +- 图片与 mask 写入 payload 前必须剥离 data URI 前缀,只留裸 base64 +- `params_version` 当前为 3 +- 多角色由 `v4_prompt.caption.char_captions[]` 三层结构表达,每个角色带 `centers` 坐标数组 +- Vibe 在生成请求中由三个平行数组表达,上限 16 个参考图 -在系统提示词中添加:`{{VCPNovelAIGen}}` +## 8. 各命令示例 -### 固定配置 +### 8.1 generate -为了确保最佳生成效果和稳定性,插件使用以下固定的官方推荐配置: +```text +tool_name: NovelAIGen +command: generate +prompt: 1girl, blue eyes, long hair +resolution: 832x1216 +model: v4.5 +steps: 23 +``` -- **模型**: NAI Diffusion 4.5 Curated -- **尺寸**: 832x1216 (适合人物画像的纵向比例) -- **生成步数**: 28 (质量与速度的最佳平衡) -- **引导系数**: 5.0 (适中的提示词遵循度) -- **采样器**: k_euler (官方推荐) -- **随机种子**: 每次生成随机 -- **生成数量**: 1张图片 +多角色(V4 起支持,最多 6 个): -### 参数说明 +```text +char_1: 1girl, silver hair, blue eyes, reading a book +char_1_x: 0.3 +char_1_y: 0.5 +char_1_uc: red hair +char_2: 1girl, colorful hair, yellow eyes, smiling +char_2_x: 0.7 +char_2_y: 0.5 +``` -| 参数 | 类型 | 必需 | 描述 | -|------|------|------|------| -| prompt | string | 是 | 图片生成提示词,支持中英文,推荐使用标签格式如"1girl, blue eyes, long hair" | +或用 JSON 数组形态的 `characters` 参数。角色 prompt 内可写 +`source#动作` / `target#动作` / `mutual#动作` 表达角色间交互。 -### 使用示例 +### 8.2 img2img +```text +command: img2img +image: path/to/input.png +prompt: change the background to night sky +strength: 0.65 +noise: 0.05 ``` -<<<[TOOL_REQUEST]>>> -tool_name:「始」NovelAIGen「末」, -prompt:「始」1girl, beautiful anime girl, blue eyes, long blonde hair, school uniform, cherry blossoms, spring, masterpiece, best quality「末」 -<<<[END_TOOL_REQUEST]>>> + +### 8.3 inpaint + +```text +command: inpaint +image: path/to/input.png +mask: path/to/mask.png +prompt: replace the masked area ``` -## 技术细节 +遮罩图白色区域重绘,黑色区域保留。 + +### 8.4 upscale -### 官方推荐配置 +```text +command: upscale +image: path/to/input.png +scale: 2 +``` -本插件采用 NovelAI 官方推荐的最佳默认配置: +### 8.5 augment -```json -{ - "model": "nai-diffusion-4-5-curated-preview", - "width": 832, - "height": 1216, - "scale": 5.0, - "sampler": "k_euler", - "steps": 28, - "n_samples": 1, - "ucPreset": 0, - "qualityToggle": true -} +```text +command: augment +req_type: colorize +image: path/to/input.png ``` -这些配置经过 NovelAI 官方测试,能够在质量、速度和稳定性之间达到最佳平衡。 +`req_type` 可选:`emotion`(可配 emotion 与 prompt)、`colorize`(可配 defry)、 +`lineart`、`sketch`、`declutter`、`bg-removal`。 -### ZIP 文件处理 +### 8.6 encode_vibe -NovelAI API 返回的是包含图片的 ZIP 文件,本插件会: +```text +command: encode_vibe +image: path/to/input.png +information_extracted: 1 +``` -1. 接收 ZIP 格式的响应数据 -2. 使用 `yauzl` 库解压 ZIP 文件 -3. 提取其中的图片文件(支持 PNG、JPG、JPEG、WebP 格式) -4. 将图片保存到本地目录 -5. 生成可访问的图片 URL +编码消耗 2 Anlas。相同图片与相同 `information_extracted` 会命中本地 JSON 缓存 +(落盘于 `image/novelaigen/vibes/`,键为内容与参数的哈希),不重复消耗。 -### 目录结构 +在生成中应用: -生成的图片会保存在以下目录: +```text +vibe: [{"image":"ref1.png","informationExtracted":1,"strength":0.6}] ``` -PROJECT_BASE_PATH/ - image/ - novelaigen/ - [UUID].png - [UUID].jpg - ... + +### 8.7 suggest_tags + +```text +command: suggest_tags +prompt: blue eyes, school uniform ``` -## 优势 +### 8.8 subscription -### 为什么选择固定配置? +```text +command: subscription +``` -1. **稳定性**: 避免了参数配置错误导致的生成失败 -2. **最优效果**: 使用 NovelAI 官方推荐的最佳参数组合 -3. **简化使用**: 用户只需专注于提示词创作,无需关心技术参数 -4. **一致性**: 确保每次生成都使用相同的高质量标准 +逐渠道查询并汇总。返回订阅等级、额度余额与配额状态;未知字段原样输出 JSON。 -### 适用场景 +生成类命令返回图片 URL 后,应使用 `` 展示。 -- 快速原型设计 -- 角色概念图生成 -- 插画创作辅助 -- 内容创作支持 +## 9. 已实测与未实测 -## 故障排除 +以下能力经真实 API 调用验证(渠道为 YesNovelAI / nai.rinko.ai,走 `/native` 前缀): -### 常见问题 +- 文生图:v3、v4、v4.5、v5 四代模型均出图成功 +- 图生图:strength 0.65 下姿态保真与风格注入均正常 +- 多角色坐标:2 角色与 3 角色场景,坐标分离生效,角色特征无交叉污染 +- 角色交互语法:`source#` / `target#` 标注下攻防姿态正确呈现 +- 分辨率:512x768、768x512、832x1216、1216x832、1536x1024 均通过 +- 路径前缀拼接、模型三维解析、ZIP 响应解包、图片落盘与 URL 构造 +- 订阅查询(含中转站同域前缀分流) +- 错误路径:400、402、504 三种状态码的语义化提示与聚合报错 +- 多渠道 failover 的聚合错误格式 -1. **API 密钥错误**:确保在 `.env` 文件中正确设置了 `NOVELAI_API_KEY` -2. **网络连接问题**:检查网络连接,NovelAI API 需要稳定的网络连接 -3. **ZIP 解压失败**:检查 Node.js 版本是否符合要求 -4. **图片保存失败**:确保项目目录有写入权限 +以下尚未验证: -### 调试信息 +- 官方直连路径。前缀机制设计为空前缀时与旧版行为等价(`url + "" + endpoint` + 与原拼接逐字相同),但未做真实调用确认 +- inpaint 命令与 inpainting 模型降级链 +- Vibe 编码与缓存命中 +- upscale、suggest_tags 端点 +- MessagePack 响应分支(未遇到该响应类型) +- V5 标识符在官方直连下是否可用 -启用调试模式后,插件会在控制台输出详细信息: -- 发送到 API 的请求参数 -- 接收到的响应类型 -- ZIP 文件解压过程 -- 图片保存路径 +## 10. 已知限制 -启用调试模式: -``` -DebugMode=true -``` +- 未实现 `/ai/generate-image-stream` 流式端点 +- 不解析或生成 `.naiv4vibe` 文件 +- 不做本地额度消耗预估——服务端是价格与余额的最终权威 +- 不提供 GUI,不做图片后处理链 +- upscale 与 augment 的部分字段名基于官方 schema 名推定,仍待实测确认 +- 部分中转站未为非核心端点(augment / vibe / upscale / tags)配置计费, + 调用会返回 `PRICE_NOT_CONFIGURED` 或 405 -## 许可证 +## 11. 故障排查 -本插件遵循 MIT 许可证。 +按以下顺序排查: -## 贡献 +1. **405,且响应 Content-Type 是 text/html** + 请求未进入应用层,通常是路径不对。中转站需检查 `NOVELAI_PATH_PREFIX` + 或渠道第五段是否填了正确前缀。判别技巧:返回 `application/json` 说明进了 API 层, + 返回 `text/html` 且长度接近站点首页说明被前端路由接管了。 -欢迎提交 Issue 和 Pull Request 来改进这个插件。 +2. **401 / 403** + 检查 token 拼写与前缀(部分站点要求完整前缀如 `ynai-`)、渠道 URL 与 KEY 的绑定关系。 + 403 也可能是该 token 没有请求模型的权限。 -## 相关链接 +3. **400** + 核对分辨率是否在白名单内、模型标识符是否被渠道支持、steps 是否在 1–50。 + inpaint 报 400 时先确认 inpainting 模型是否可用。 + +4. **402 额度不足** + 调用 `subscription` 查询余额。注意中转站可能对下游用户设虚拟限额—— + 即使账户显示 Opus,`unlimitedImageGeneration` 也可能为 false。 + +5. **429 / 502 / 503 / 504** + 瞬时故障,插件会按 `MAX_RETRIES` 与指数退避自动重试。持续出现说明上游过载。 + +6. **全渠道失败** + 读聚合错误的每一行——它列出了每个渠道的独立失败原因。检查能力位过滤是否把 + 唯一支持该命令的渠道排除了。 + +7. **图片无法展示** + 检查 `PROJECT_BASE_PATH`、`SERVER_PORT`、`IMAGESERVER_IMAGE_KEY`、 + `VarHttpUrl` / `VarHttpsUrl`,以及 `image/novelaigen/` 目录写权限。 + +## 12. 依赖与检查 + +依赖版本全部固定,详见 `package.json`。 + +```bash +node --check NovelAIGen.js +``` -- [NovelAI 官网](https://novelai.net/) -- [VCP 工具箱](https://github.com/lioensky/VCPToolBox) -- [NovelAI API 文档](https://docs.novelai.net/) \ No newline at end of file +`DebugMode=true` 会输出脱敏后的请求体——base64 会被替换为长度标记, +含 token / key / authorization 的字段会被替换为 ``。 \ No newline at end of file diff --git a/Plugin/NovelAIGen/config.env.example b/Plugin/NovelAIGen/config.env.example index 4cf1f3d..aae61b6 100644 --- a/Plugin/NovelAIGen/config.env.example +++ b/Plugin/NovelAIGen/config.env.example @@ -1,6 +1,75 @@ -# NovelAI API 配置 -# 在 https://novelai.net/ 获取您的 API 密钥 +# 单渠道 NovelAI Bearer token NOVELAI_API_KEY=your_novelai_api_key_here +# 单渠道图像 API 基地址 +NOVELAI_BASE_URL=https://image.novelai.net +# 账户 API 基地址(订阅查询) +NOVELAI_ACCOUNT_URL=https://api.novelai.net +# 是否启用多渠道 +MULTI_CHANNEL=false +# 多渠道串:URL|KEY|MODELS|CAPS;URL|KEY|MODELS|CAPS +NOVELAI_CHANNELS= +# 模型别名覆盖,格式 alias=model;alias=model +# 内置已含 v5=nai-diffusion-5-full、v5c=nai-diffusion-5-curated +# (来源为中转站 /v1/models 实证;官方直连若拒绝该 ID,可在此覆盖) +MODEL_ALIASES= +# 主模型到 inpainting 模型映射,格式 base=variant;base=variant +# 内置表出于保守未含 nai-diffusion-4-5-full 的映射(官方是否提供该变体未验证)。 +# 若你的渠道支持,可解注下面这行以避免降级: +# INPAINT_MODELS=nai-diffusion-4-5-full=nai-diffusion-4-5-full-inpainting +INPAINT_MODELS= +# inpainting 降级链,格式 base>variant;... +INPAINT_FALLBACK_CHAIN= +# 默认模型别名 +DEFAULT_MODEL=v4.5 +# 默认采样步数 +DEFAULT_STEPS=23 +# 默认引导系数 +DEFAULT_SCALE=5 +# 默认采样器 +DEFAULT_SAMPLER=k_euler_ancestral +# 默认噪声调度 +DEFAULT_NOISE_SCHEDULE=karras +# 默认负面提示词;可整行删除以使用源码内置默认值 +DEFAULT_UC=lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page +# 分辨率白名单,留空使用内置值 +RESOLUTION_PRESETS= +# 429/5xx 最大重试次数 +MAX_RETRIES=2 +# 指数退避基础毫秒数 +RETRY_BASE_DELAY_MS=2000 +# 输入图片大小上限(MB) +MAX_IMAGE_SIZE_MB=8 +# 是否启用枚举候选链 +ENUM_PROBE=true +# HTTP/HTTPS 代理地址 +NovelAIProxy= +# 是否输出脱敏调试日志 +DebugMode=false +# VCP 项目根目录(通常由框架注入) +PROJECT_BASE_PATH= +# 图片服务器端口(通常由框架注入) +SERVER_PORT= +# 图片服务器访问密钥(通常由框架注入) +IMAGESERVER_IMAGE_KEY= +# 图片服务器 HTTP 地址(通常由框架注入) +VarHttpUrl=http://127.0.0.1 +# 图片服务器 HTTPS 地址,可选 +VarHttpsUrl= -# 是否为此插件启用调试模式 (true/false) -DebugMode=false \ No newline at end of file +# 多渠道示例(启用时取消注释,并可清空 NOVELAI_API_KEY) +# MULTI_CHANNEL=true +# NOVELAI_CHANNELS=https://image.example-a|token-a|v4.5,v4.5c|gen,i2i,infill;https://image.example-b|token-b|| + +# ── 中转站支持 ─────────────────────────────────────────────── +# 单渠道模式的原生协议路径前缀。 +# 官方直连留空;YesNovelAI (nai.rinko.ai) 填 /native +NOVELAI_PATH_PREFIX= + +# 本地拦截 Vibe Transfer 的模型前缀,逗号分隔。 +# 留空 = 不拦截,让上游用自己的错误码回答 +VIBE_UNSUPPORTED_PREFIXES= + +# 多渠道语法已扩展为五段:URL|KEY|MODELS|CAPS|PATH_PREFIX +# 官方 + 中转站混合示例(挂一个自动落另一个): +# MULTI_CHANNEL=true +# NOVELAI_CHANNELS=https://image.novelai.net|pst-xxxx|||;https://nai.rinko.ai|ynai-xxxx|||/native \ No newline at end of file diff --git a/Plugin/NovelAIGen/package.json b/Plugin/NovelAIGen/package.json index 5eecf6f..887cf50 100644 --- a/Plugin/NovelAIGen/package.json +++ b/Plugin/NovelAIGen/package.json @@ -1,26 +1,27 @@ { "name": "novelaigen", - "version": "1.0.0", - "description": "NovelAI image generation plugin for VCP", + "version": "2.0.0", + "description": "NovelAI multi-channel full-capability gateway plugin for VCP", "main": "NovelAIGen.js", "type": "module", "scripts": { - "test": "node NovelAIGen.js", + "check": "node --check NovelAIGen.js", "start": "node NovelAIGen.js" }, "keywords": [ "novelai", "image-generation", - "ai", "vcp", - "plugin", - "anime", - "diffusion" + "gateway" ], - "author": "VCP-Assistant", + "author": "VCP-Assistant; CodeCC & infinite-vector", "license": "MIT", "dependencies": { - "yauzl": "^2.10.0" + "axios": "1.7.9", + "uuid": "11.0.5", + "yauzl": "3.2.0", + "https-proxy-agent": "7.0.6", + "http-proxy-agent": "7.0.2" }, "engines": { "node": ">=18.0.0" @@ -28,9 +29,5 @@ "repository": { "type": "git", "url": "https://github.com/lioensky/VCPToolBox" - }, - "bugs": { - "url": "https://github.com/lioensky/VCPToolBox/issues" - }, - "homepage": "https://github.com/lioensky/VCPToolBox#readme" -} \ No newline at end of file + } +} diff --git a/Plugin/NovelAIGen/plugin-manifest.json b/Plugin/NovelAIGen/plugin-manifest.json index 072a596..c6bce7f 100644 --- a/Plugin/NovelAIGen/plugin-manifest.json +++ b/Plugin/NovelAIGen/plugin-manifest.json @@ -1,39 +1,225 @@ { "manifestVersion": "1.0.0", "name": "NovelAIGen", - "displayName": "NovelAI 图片生成器", - "version": "1.0.0", - "description": "通过 NovelAI API 使用 NovelAI Diffusion 模型生成高质量的动漫风格图片。支持多种模型和参数调节。", - "author": "VCP-Assistant", + "displayName": "NovelAI 图像生成器 (全能力网关)", + "version": "2.1.0", + "description": "NovelAI 六端点多渠道全参数网关。", + "author": "VCP-Assistant; infinite-vector", "pluginType": "synchronous", - "entryPoint": { - "type": "nodejs", - "command": "node NovelAIGen.js" - }, - "communication": { - "protocol": "stdio" - }, + "entryPoint": { "type": "nodejs", "command": "node NovelAIGen.js" }, + "communication": { "protocol": "stdio", "timeout": 300000 }, "configSchema": { "NOVELAI_API_KEY": { "type": "string", - "description": "您的NovelAI API密钥 (从 https://novelai.net/ 获取)", + "description": "单渠道 Bearer token,多渠道模式下可留空。", "default": "", - "required": true + "required": false + }, + "NOVELAI_BASE_URL": { + "type": "string", + "description": "单渠道图像 API 基地址。", + "default": "https://image.novelai.net", + "required": false + }, + "NOVELAI_ACCOUNT_URL": { + "type": "string", + "description": "账户 API 基地址。", + "default": "https://api.novelai.net", + "required": false + }, + "MULTI_CHANNEL": { + "type": "boolean", + "description": "启用多渠道。", + "default": false, + "required": false + }, + "NOVELAI_CHANNELS": { + "type": "string", + "description": "URL|KEY|MODELS|CAPS|PATH_PREFIX;...,能力位为 gen,i2i,infill,vibe,augment,upscale,tags;第五段为原生协议路径前缀(如 /native),留空即官方行为。", + "default": "", + "required": false + }, + "NOVELAI_PATH_PREFIX": { + "type": "string", + "description": "单渠道模式的原生协议路径前缀。官方直连留空;YesNovelAI 等中转站填 /native。多渠道模式请用 NOVELAI_CHANNELS 的第五段。", + "default": "", + "required": false + }, + "VIBE_UNSUPPORTED_PREFIXES": { + "type": "string", + "description": "本地拦截 Vibe Transfer 的模型前缀,逗号分隔。留空表示不拦截,由上游返回错误。若确认某系模型不支持,可填 nai-diffusion-5。", + "default": "", + "required": false + }, + "MODEL_ALIASES": { + "type": "string", + "description": "alias=model;... 覆盖内置别名表。内置 v5=nai-diffusion-5-full、v5c=nai-diffusion-5-curated(来源为中转站 /v1/models 实证,官方直连未验证)。", + "default": "", + "required": false + }, + "INPAINT_MODELS": { + "type": "string", + "description": "base=variant;...。", + "default": "", + "required": false + }, + "INPAINT_FALLBACK_CHAIN": { + "type": "string", + "description": "base>variant;...。", + "default": "", + "required": false + }, + "DEFAULT_MODEL": { + "type": "string", + "description": "默认模型。", + "default": "v4.5", + "required": false + }, + "DEFAULT_STEPS": { + "type": "number", + "description": "默认步数。", + "default": 23, + "required": false + }, + "DEFAULT_SCALE": { + "type": "number", + "description": "默认 scale。", + "default": 5, + "required": false + }, + "DEFAULT_SAMPLER": { + "type": "string", + "description": "默认采样器。", + "default": "k_euler_ancestral", + "required": false + }, + "DEFAULT_NOISE_SCHEDULE": { + "type": "string", + "description": "默认噪声调度。", + "default": "karras", + "required": false + }, + "DEFAULT_UC": { + "type": "string", + "description": "默认负面提示词。留空时使用内置默认负面提示词(与本项 default 一致)。", + "default": "lowres, artistic error, film grain, scan artifacts, worst quality, bad quality, jpeg artifacts, very displeasing, chromatic aberration, dithering, halftone, screentone, multiple views, logo, too many watermarks, negative space, blank page", + "required": false + }, + "RESOLUTION_PRESETS": { + "type": "string", + "description": "分辨率白名单。", + "default": "", + "required": false + }, + "MAX_RETRIES": { + "type": "number", + "description": "最大重试次数。", + "default": 2, + "required": false + }, + "RETRY_BASE_DELAY_MS": { + "type": "number", + "description": "退避基础延迟。", + "default": 2000, + "required": false + }, + "MAX_IMAGE_SIZE_MB": { + "type": "number", + "description": "输入图片大小上限。", + "default": 8, + "required": false + }, + "ENUM_PROBE": { + "type": "boolean", + "description": "启用枚举探测。", + "default": true, + "required": false + }, + "NovelAIProxy": { + "type": "string", + "description": "代理地址。", + "default": "", + "required": false }, "DebugMode": { "type": "boolean", - "description": "是否为此插件启用详细的调试日志输出到stderr。", + "description": "脱敏调试日志。", "default": false, "required": false + }, + "PROJECT_BASE_PATH": { + "type": "string", + "description": "VCP 项目根目录。", + "default": "", + "required": true + }, + "SERVER_PORT": { + "type": "string", + "description": "图片服务器端口。", + "default": "", + "required": true + }, + "IMAGESERVER_IMAGE_KEY": { + "type": "string", + "description": "图片服务器密钥。", + "default": "", + "required": true + }, + "VarHttpUrl": { + "type": "string", + "description": "HTTP 图片服务地址。", + "default": "", + "required": true + }, + "VarHttpsUrl": { + "type": "string", + "description": "HTTPS 图片服务地址。", + "default": "", + "required": false } }, "capabilities": { "invocationCommands": [ { - "commandIdentifier": "NovelAIGenerateImage", - "description": "调用此工具通过 NovelAI API 使用 NAI Diffusion 4.5 Full 模型生成高质量的动漫风格图片。请在您的回复中,使用以下精确格式来请求图片生成,确保所有参数值都用「始」和「末」准确包裹:\n<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\nprompt:「始」(必需) 用于图片生成的详细【英文】提示词。「末」,\nresolution:「始」(必需) 图片分辨率,可选值:「512x768」、「768x512」、「640x640」、「832x1216」、「1216x832」、「1024x1024」、「1024x1536」、「1536x1024」、「1472x1472」、「1088x1920」、「1920x1088」。「末」\n<<<[END_TOOL_REQUEST]>>>\n\n**NovelAI官方分辨率选项**:\n• **SMALL**: 512x768(竖版), 768x512(横版), 640x640(方形)\n• **NORMAL**: 832x1216(竖版), 1216x832(横版), 1024x1024(方形)\n• **LARGE**: 1024x1536(竖版), 1536x1024(横版), 1472x1472(方形)\n• **WALLPAPER**: 1088x1920(竖版), 1920x1088(横版)\n\n重要提示给AI:\n当此工具执行完毕后,您将收到包含以下信息的结果:\n1. 生成图片的公开访问URL。\n2. 图片在服务器上的存储相对路径 (例如:image/novelaigen/图片名.png)。\n3. 图片的文件名。\n请在您的最终回复中,使用返回的【图片URL】为用户生成一个HTML的 `` 标签来直接展示图片,例如:`\"[此处可填写部分prompt作为描述]\"`。请确保替换占位符,并可调整 `width` 属性(建议200-500像素)。同时,也可以附带图片的直接URL链接。", - "example": "```text\n<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\nprompt:「始」A majestic cat warrior in futuristic armor, standing on a neon-lit city rooftop at night, cyberpunk style.「末」,\nresolution:「始」1024x1024「末」\n<<<[END_TOOL_REQUEST]>>>\n```" + "commandIdentifier": "NovelAIGenerate", + "description": "文生图。必需 prompt;可选 model、resolution、steps、scale、sampler、noise_schedule、seed、n_samples(1-4)、uc、vibe、characters。示例使用 command=generate。返回后用 img 标签展示。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」generate「末」,\nprompt:「始」1girl, blue eyes「末」,\nresolution:「始」1024x1024「末」\n<<<[END_TOOL_REQUEST]>>>" + }, + { + "commandIdentifier": "NovelAIImg2Img", + "description": "图生图。必需 image;可选 prompt、model、resolution、strength(0.01-0.99,默认0.7)、noise(0-0.99,默认0)。返回后用 展示。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」img2img「末」,\nimage:「始」path/to/image.png「末」\n<<<[END_TOOL_REQUEST]>>>" + }, + { + "commandIdentifier": "NovelAIInpaint", + "description": "局部重绘。必需 image、mask;action 固定使用 infill。本命令会自动切换到对应的 inpainting 专用模型;没有变体时沿版本链降级并告知。可选 prompt、model、strength、noise、add_original_image。角色 prompt 支持 source#动作 / target#动作 / mutual#动作。返回后用 展示。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」inpaint「末」,\nimage:「始」input.png「末」,\nmask:「始」mask.png「末」\n<<<[END_TOOL_REQUEST]>>>" + }, + { + "commandIdentifier": "NovelAIUpscale", + "description": "图片放大。必需 image;scale 允许2或4,默认4;可选 width、height、resolution。返回后用 img 标签展示。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」upscale「末」,\nimage:「始」input.png「末」,\nscale:「始」2「末」\n<<<[END_TOOL_REQUEST]>>>" + }, + { + "commandIdentifier": "NovelAIAugment", + "description": "Director 工具。必需 image、req_type;req_type 为 emotion/colorize/lineart/sketch/declutter/bg-removal,emotion 可选 emotion/prompt,colorize 可选 defry。返回后用 img 标签展示。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」augment「末」,\nreq_type:「始」colorize「末」,\nimage:「始」input.png「末」\n<<<[END_TOOL_REQUEST]>>>" + }, + { + "commandIdentifier": "NovelAIEncodeVibe", + "description": "编码 Vibe。必需 image;information_extracted 为数字,默认1;可选 model。编码消耗 2 Anlas。相同图片与相同 information_extracted 值命中本地缓存,不重复消耗。V5 系不支持 Vibe Transfer。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」encode_vibe「末」,\nimage:「始」input.png「末」\n<<<[END_TOOL_REQUEST]>>>" + }, + { + "commandIdentifier": "NovelAISuggestTags", + "description": "标签建议。必需 prompt;返回纯文本标签列表。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」suggest_tags「末」,\nprompt:「始」blue eyes「末」\n<<<[END_TOOL_REQUEST]>>>" + }, + { + "commandIdentifier": "NovelAISubscription", + "description": "查询订阅等级、Anlas 余额与配额状态;未知字段原样返回 JSON。", + "example": "<<<[TOOL_REQUEST]>>>\ntool_name:「始」NovelAIGen「末」,\ncommand:「始」subscription「末」\n<<<[END_TOOL_REQUEST]>>>" } ] } -} \ No newline at end of file +} diff --git a/plugins.json b/plugins.json index 7e1b812..1983bea 100644 --- a/plugins.json +++ b/plugins.json @@ -1,612 +1,612 @@ -{ - "schemaVersion": 1, - "generatedAt": "2026-08-15T14:27:42.644209+00:00", - "source": { - "name": "VCP 官方插件商店", - "repository": "https://github.com/lioensky/VCPDistributedServer", - "branch": "main" - }, - "plugins": [ - { - "name": "1PanelInfoProvider", - "displayName": "1Panel 信息提供器", - "description": "从1Panel服务器获取Dashboard基础信息和操作系统信息,并通过独立的系统提示词占位符提供这些数据。", - "version": "1.0.0", - "author": "B3000Kcn", - "icon": "extension", - "category": "data-provider", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/1PanelInfoProvider/1PanelInfoProvider.zip" - }, - { - "name": "AnkiSearch", - "displayName": "Anki 搜索与查询", - "description": "VCP 体系下的独立 Anki 查询工具。支持使用 Anki 强大的查询语法搜索卡片,获取牌组统计数据,以及查询笔记类型(Model)。", - "version": "1.2.0", - "author": "VCPAnki Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/AnkiSearch/AnkiSearch.zip" - }, - { - "name": "AnkiManage", - "displayName": "Anki 管理与操作", - "description": "VCP 体系下的独立 Anki 管理工具。支持添加笔记、更新字段、挂起卡片及重新调度。", - "version": "1.2.0", - "author": "VCPAnki Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/AnkiManage/AnkiManage.zip" - }, - { - "name": "SynBiliVision", - "displayName": "B站用户信息综合查询", - "description": "获取当前登录 B 站账号的综合信息,包括浏览历史、收藏夹、最近收藏、投币记录和观看偏好分析。", - "version": "2.3.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SynBiliVision/SynBiliVision.zip" - }, - { - "name": "ComfyCloudGen", - "displayName": "Comfy Cloud 云端图像/视频生成", - "description": "通过Comfy Cloud云端GPU生成图像或视频。数据驱动架构,自动匹配模型生态,支持895+云端模型。三种模式:auto(默认)、template、raw。", - "version": "0.4.0", - "author": "Rosa", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ComfyCloudGen/ComfyCloudGen.zip" - }, - { - "name": "FRPSInfoProvider", - "displayName": "FRPS 设备信息提供器", - "description": "定期从FRPS服务器获取所有类型的代理设备信息,并整合成一个文本文件供占位符使用。", - "version": "1.0.0", - "author": "B3000Kcn", - "icon": "extension", - "category": "data-provider", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/FRPSInfoProvider/FRPSInfoProvider.zip" - }, - { - "name": "NanoBananaGenOR", - "displayName": "Gemini 2.5 NanoBanana 图像生成 (OpenRouter)", - "description": "使用 OpenRouter 接口调用 Google Gemini 2.5 Flash Image Preview 模型进行高级的图像生成和编辑。支持代理和多密钥随机选择。", - "version": "1.0.0", - "author": "Kilo Code", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/NanoBananaGenOR/NanoBananaGenOR.zip" - }, - { - "name": "GitOperator", - "displayName": "Git 仓库管理器", - "description": "基于配置档驱动的智能 Git 管理器。支持多仓库 Profile 管理、凭证注入、串行调用和 Token 脱敏输出。", - "version": "1.1.0", - "author": "Nova & hjhjd", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GitOperator/GitOperator.zip" - }, - { - "name": "GodotBridge", - "displayName": "Godot MCP 桥接器", - "description": "作为标准 MCP Client 连接 Godot MCP Native 插件(默认 http://127.0.0.1:9080/mcp),让 VCP Agent 通过渐进式工具发现来读写 Godot 项目的场景、脚本、节点、资源,并控制编辑器与运行时调试。", - "version": "1.0.0", - "author": "ATRI", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GodotBridge/GodotBridge.zip" - }, - { - "name": "GodotEventReceiver", - "displayName": "Godot 事件接收器", - "description": "二期反向通道:作为 WebSocket 服务端接收来自 Godot 侧 godot_vcp_bridge 插件主动推送的运行时/编辑器事件(服务器启停、工具执行、错误、日志、游戏内自定义事件),并转发到 VCP 前端广播。与 GodotBridge(请求-响应)物理隔离。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GodotEventReceiver/GodotEventReceiver.zip" - }, - { - "name": "GrokVideoGen", - "displayName": "Grok 视频生成器", - "description": "使用 Grok API 进行视频生成(支持文生视频、图生视频、视频续写、视频拼接)。", - "version": "1.3.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GrokVideo/GrokVideoGen.zip" - }, - { - "name": "HealthQuery", - "displayName": "HealthQuery (Fitbit)", - "description": "Integrated Google Fitbit health data query tool. Supports Steps, Heart Rate, Sleep analysis via Fitbit Web API.", - "version": "2.1.0", - "author": "VCP Developer", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/HealthQuery/HealthQuery.zip" - }, - { - "name": "IMAPSearch", - "displayName": "IMAP 邮件本地搜索", - "description": "一个同步 VCP 插件,用于在 IMAPIndex 插件生成的本地邮件存储中执行全文搜索。它支持分页和可配置的索引目录。", - "version": "1.1.0", - "author": "B3000Kcn", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/IMAPSearch/IMAPSearch.zip" - }, - { - "name": "IMAPIndex", - "displayName": "IMAP 邮件本地索引插件", - "description": "静态插件:定期通过 IMAP 拉取白名单匹配邮件到本地(.eml -> .md 转换),合并生成索引文本并输出到 stdout,供占位符注入系统提示词使用。仅邮件链路支持 HTTP(S) CONNECT 代理(可选)。", - "version": "1.0.0", - "author": "B3000Kcn", - "icon": "extension", - "category": "data-provider", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/IMAPIndex/IMAPIndex.zip" - }, - { - "name": "KarakeepSearch", - "displayName": "Karakeep 搜索书签", - "description": "在 Karakeep 中全文搜索书签。参数:\\n- query (字符串, 必需): 搜索关键词,支持 is:fav, #tag 等高级语法。\\n- limit (数字, 可选, 默认 10): 返回结果数量。\\n- nextCursor (字符串, 可选): 用于分页的游标。\\n\\n调用格式示例:\\n<<<[TOOL_REQUEST]>>>\\ntool_name:「始」KarakeepSearch「末」,\\nquery:「始」machine learning is:fav「末」,\\nlimit:「始」5「末」\\n<<<[END_TOOL_REQUEST]>>>", - "version": "0.1.0", - "author": "Kilo Code", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/KarakeepSearch/KarakeepSearch.zip" - }, - { - "name": "KEGGSearch", - "displayName": "KEGG 数据库查询", - "description": "一个用于查询 KEGG 数据库的 VCP 插件,提供通路、基因、化合物等多维度的数据检索与分析功能。", - "version": "1.0.0", - "author": "B3000Kcn & DBL1F7E5", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/KEGGSearch/KEGGSearch.zip" - }, - { - "name": "MCPO", - "displayName": "MCPO 工具桥接器", - "description": "基于 mcpo 的 MCP 工具桥接插件,能够自动发现、缓存和调用 MCP 工具,支持多种 MCP 服务器类型。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/MCPO/MCPO.zip" - }, - { - "name": "MCPOMonitor", - "displayName": "MCPO 服务状态监控器", - "description": "监控 MCPO 服务器状态并提供所有可用 MCP 工具的详细信息,通过 {{MCPOServiceStatus}} 占位符集成到系统提示词中。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "data-provider", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/MCPOMonitor/MCPOMonitor.zip" - }, - { - "name": "MIDITranslator", - "displayName": "MIDI翻译器", - "description": "一个高性能的MIDI文件解析与生成插件,可以从midi-input目录读取MIDI文件并解析为DSL格式,也可以从DSL生成MIDI文件到midi-output目录。核心引擎由Rust编写,确保性能与安全。提供DSL语法验证、事件提取和双向转换测试等功能。", - "version": "0.3.0", - "author": "ATRI", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/MIDITranslator/MIDITranslator.zip" - }, - { - "name": "NovelAIGen", - "displayName": "NovelAI 图片生成器", - "description": "通过 NovelAI API 使用 NovelAI Diffusion 模型生成高质量的动漫风格图片。支持多种模型和参数调节。", - "version": "1.0.0", - "author": "VCP-Assistant", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/NovelAIGen/NovelAIGen.zip" - }, - { - "name": "ObsidianBridge", - "displayName": "Obsidian 笔记桥接器", - "description": "通过 Obsidian CLI (v1.12+) 桥接 VCP 与 Obsidian 笔记库。支持笔记读写、全文搜索(含上下文)、每日笔记、文件/文件夹浏览、任务管理、反向链接/出站链接查询、标签统计、属性管理、大纲查看、字数统计、模板列表等 22 项能力。v1.3.0 安全加固:execSync→execFileSync 迁移,消除 Shell 注入面;.bat/.cmd 自动降级兼容。", - "version": "1.3.0", - "author": "Nova & 小夜 | 扩展: infinite-vector", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ObsidianBridge/ObsidianBridge.zip" - }, - { - "name": "PubMedSearch", - "displayName": "PubMed 文献检索插件", - "description": "提供基于 NCBI E-utilities 和 PMC 的 PubMed 文献检索、详情获取、引用/相似文献分析与标识符转换等能力。实现参考 PubMed-MCP-Server 原始实现,保持结果结构和语义尽量一致,便于从 MCP 平滑迁移。", - "version": "1.0.0", - "author": "B3000Kcn & DBL1F7E5", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/PubMedSearch/PubMedSearch.zip" - }, - { - "name": "PyScreenshot", - "displayName": "Python截图插件", - "description": "一个使用Python和Pillow从桌面截取屏幕图像的同步插件。", - "version": "1.0.0", - "author": "Cline", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/PyScreenshot/PyScreenshot.zip" - }, - { - "name": "PyCameraCapture", - "displayName": "Python摄像头插件", - "description": "一个使用Python和OpenCV从摄像头捕获图像的同步插件。", - "version": "1.0.0", - "author": "Cline", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/PyCameraCapture/PyCameraCapture.zip" - }, - { - "name": "SenseGen", - "displayName": "SenseGen 图文文档型图片生成器", - "description": "通过 SenseNova 的 sensenova-u1-fast 模型生成适合 PPT、PDF、杂志、信息图、海报等图文并茂风格的高分辨率图片文档。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SenseGen/SenseGen.zip" - }, - { - "name": "SerpSearch", - "displayName": "Serp API 搜索引擎", - "description": "一个使用SerpApi提供多种搜索引擎(如Bing, DuckDuckGo, Google Scholar)的插件。", - "version": "1.0.0", - "author": "Your Name", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SerpSearch/SerpSearch.zip" - }, - { - "name": "SunoGen", - "displayName": "Suno AI 音乐生成", - "description": "使用 Suno API 生成原创歌曲。该版本为Newapi代理的通用版本。支持通过歌词、风格、标题进行自定义创作,或通过描述获取灵感创作,还可以继续生成已有的歌曲片段。", - "version": "0.1.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SunoGen/SunoGen.zip" - }, - { - "name": "SunoMusicGen", - "displayName": "Suno音乐生成器(新API)", - "description": "使用新的Suno API(sunoapi.org)生成高质量AI音乐、支持音乐延长和音频处理(异步版本)", - "version": "2.2.0", - "author": "Ava", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SunoMusicGen/SunoMusicGen.zip" - }, - { - "name": "TinyFishBrowser", - "displayName": "TinyFish 搜索与网页抓取", - "description": "基于 TinyFish API 的搜索与网页内容抓取插件。支持网络搜索(返回结构化结果)和网页内容抓取(真实浏览器渲染,支持JS页面,返回Markdown/HTML/JSON)。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TinyFishBrowser/TinyFishBrowser.zip" - }, - { - "name": "DomainSafetyChecker", - "displayName": "URL/域名安全核查器", - "description": "低交互、静态、非侵入式 URL/域名安全核查工具。可帮助 AI 对用户提供的网址、域名、短链落地页、疑似钓鱼站、下载页、登录页、支付页进行安全风险初筛,并返回合并完整 JSON 证据的详细 Markdown 报告。", - "version": "1.2.0", - "author": "VCPToolBox", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/DomainSafetyChecker/DomainSafetyChecker.zip" - }, - { - "name": "VCPQQBotServer", - "displayName": "VCP QQBot 单聊/群聊桥接服务", - "description": "常驻连接腾讯 QQBot Gateway,接收 QQ 单聊与群聊 @ 消息后转发到 VCP 主服务器 /v1/chat/completions,并将非流式 AI 回复拆分为 QQ 文本与真正的 QQ 图片消息发回用户。", - "version": "0.1.0", - "author": "VCPToolBox", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPQQBotServer/VCPQQBotServer.zip" - }, - { - "name": "VCPRedisOperator", - "displayName": "VCP Redis 操作工具", - "description": "VCP Redis 操作插件。AI 通过 ExecuteRedis 命令安全操作 Redis,插件做三道安全闸:命令白名单 / Key 前缀白名单 / 按 Agent 分桶限频。核心用途:SQL大师写入通知后 INCR 未读计数,触发前端实时角标更新。", - "version": "1.0.0", - "author": "pioneer798", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPRedisOperator/VCPRedisOperator.zip" - }, - { - "name": "VCPWeCom", - "displayName": "VCP 企业微信桥接", - "description": "混合服务插件:常驻企微 WebSocket 长连接,收到文本消息后自动唤醒 config.env 中指定的 AgentAssistant Agent,生成回复后通过流式回复推回企微。同时提供 WeComSend 工具供其他 Agent 主动推送企微消息。", - "version": "0.1.0", - "author": "小夜", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPWeCom/VCPWeCom.zip" - }, - { - "name": "SynapsePusher", - "displayName": "VCP 日志 Synapse 推送器", - "description": "将 VCP 工具调用日志实时推送到指定的 Synapse (Matrix) 房间。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SynapsePusher/SynapsePusher.zip" - }, - { - "name": "VCPFeishu", - "displayName": "VCP 飞书桥接", - "description": "混合服务插件:常驻飞书 WebSocket 长连接,收到消息后在绑定 Agent 下创建或复用 VCPChat 话题会话,并通过系统 settings 与 Agent 配置调用 VCP 后端生成回复。", - "version": "0.1.0", - "author": "VCP Team", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPFeishu/VCPFeishu.zip" - }, - { - "name": "DistributeFileOperator", - "displayName": "VCP分布式文件操作器", - "description": "VCP服务器专用的一个强大的文件系统操作插件,允许AI对受限目录进行读、写、列出、移动、复制、删除等多种文件和目录操作。特别增强了文件读取能力,可自动提取PDF、Word(.docx)和表格(.xlsx, .csv)文件的纯文本内容。", - "version": "1.0.1", - "author": "VCPToolBox", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/FileOperator/DistributeFileOperator.zip", - "license": "MIT" - }, - { - "name": "VCPDatabaseOperator", - "displayName": "VCP数据库操作工具", - "description": "VCP数据库操作插件(SQL-First)。AI 直接写原生 MySQL 语句,插件做 AST 解析 + 五道安全闸(动词过滤/表白名单/CRUD权限矩阵/参数化绑定/写操作熔断)。推荐用 ExecuteSQL;保留 QueryTable/InsertRow/UpdateRow/DeleteRow 结构化命令做兼容。", - "version": "1.0.3", - "author": "pioneer798", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPDatabaseOperator/VCPDatabaseOperator.zip" - }, - { - "name": "WebUIGen", - "displayName": "WebUI云算力生图", - "description": "调用云算力API生成高质量图像,支持多种模型和自定义参数。", - "version": "1.0.0", - "author": "Kilo Code", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/WebUIGen/WebUIGen.zip" - }, - { - "name": "YoucomSearch", - "displayName": "You.com 搜索插件", - "description": "使用 You.com API 进行网络搜索。AI可以指定搜索查询、返回结果数量、时效范围、国家及语言,获取网页与新闻结果。支持使用 || 分隔多个关键词进行并发搜索,所有结果同时返回。", - "version": "0.1.0", - "author": "Emos21", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/YoucomSearch/YoucomSearch.zip" - }, - { - "name": "YTFetch", - "displayName": "YouTube Gemini Fetch", - "description": "Use Gemini API to directly read public YouTube URLs and return multimodal video/audio analysis without downloading the video locally.", - "version": "0.1.0", - "author": "VCP Community", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/YTFetch/YTFetch.zip" - }, - { - "name": "YoutubeFetch", - "displayName": "YouTube 内容获取插件", - "description": "YouTube 内容获取插件。基于 YouTube Data API v3 获取视频信息、搜索结果、频道信息、频道投稿、热门视频与评论,并通过 youtube-transcript-api 获取字幕/自动字幕。支持 youtube.com、youtu.be、Shorts、embed、live 链接与直接 videoId。", - "version": "1.0.0", - "author": "Bocchi777", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/YoutubeFetch/YoutubeFetch.zip" - }, - { - "name": "ZImageGen", - "displayName": "Z-Image 文生图(阿里通义)", - "description": "通过 Hugging Face Spaces API 使用阿里巴巴通义实验室的 Z-Image-Turbo 模型生成高质量图片。支持中英文提示词,8步推理,亚秒级延迟。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ZImageGen/ZImageGen.zip" - }, - { - "name": "GitSearch", - "displayName": "代码托管平台聚合搜索", - "description": "聚合 GitHub、GitLab、Gitee 三大代码托管平台的纯读取操作,提供统一的工具名和调用方式。", - "version": "1.0.0", - "author": "VCPToolBox", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GitSearch/GitSearch.zip" - }, - { - "name": "XiaohongshuFetch", - "displayName": "小红书爬虫", - "description": "用于抓取小红书图文/视频笔记内容的专属爬虫。", - "version": "3.0.0", - "author": "VCP", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/XiaohongshuFetch/XiaohongshuFetch.zip" - }, - { - "name": "WorkspaceInjector", - "displayName": "工作区动态注入", - "description": "通过在系统提示词中使用 {{Workspace::alias}} 占位符,将预设的本地文件夹目录树动态注入到上下文中。", - "version": "1.2.0", - "author": "Gemini & User", - "icon": "extension", - "category": "system-integration", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/WorkspaceInjector/WorkspaceInjector.zip" - }, - { - "name": "SVCardFinder", - "displayName": "影之诗查卡器(WB)", - "description": "一个用于查询《影之诗:世界超越》卡牌信息的插件。", - "version": "1.0.0", - "author": "greyfilm", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SVCardFinder/SVCardFinder.zip" - }, - { - "name": "JapaneseHelper", - "displayName": "日语学习助手", - "description": "日语学习增强插件(纯Python基础层 + optional增强层)。支持深度语法解析、动态句型识别(含部分活用补偿)、错题本分析推荐、复习调度、汉字查询、资源状态检查、JLPT统计(jlpt_stats)与可选 OJAD 声调接口。结构化接口支持 Lookup/KanjiInfo/JLPTCheck/ReadingAid/ParseTree/SchemaProbe。SemanticEnhance 默认使用 rule 风格判定,已修复礼貌体被误判为 mixed 的问题;SyntaxEnhance 默认 heuristic,可选 GiNZA 增强;ReadingEnhance 支持可选 romaji 后端。已补齐 SentenceSplit/ReadingEnhance/SyntaxEnhance/SemanticEnhance/TeachingAssetsStatus 五项分层命令。Romanize/Furigana 已修复促音拆裂与活用显示问题;已完成全命令回归与重复旧函数清理。", - "version": "2.14.2", - "author": "Nova", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/JapaneseHelper/JapaneseHelper.zip" - }, - { - "name": "TagFolder", - "displayName": "标签折叠器", - "description": "读取 list.md 中的标签白名单,生成折叠行为指令注入系统提示词,实现对 nvim 系列标签包裹内容的上下文折叠管理。", - "version": "1.0.0", - "author": "ATRI & Lucifer", - "icon": "extension", - "category": "data-provider", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TagFolder/TagFolder.zip" - }, - { - "name": "TicktickManager", - "displayName": "滴答清单管理器", - "description": "混合插件:提供滴答清单/TickTick/Dida365 任务同步调用和白名单项目静态任务快照注入。", - "version": "1.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TicktickManager/TicktickManager.zip" - }, - { - "name": "VolcSearch", - "displayName": "火山引擎搜索插件", - "description": "使用火山引擎联网搜索 API 进行中文网络搜索。AI可以指定搜索查询、返回结果数量,并可选择包含正文内容、设置搜索时间范围等。支持使用 || 分隔多个关键词进行并发搜索,所有结果同时返回。", - "version": "0.3.0", - "author": "Roo", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VolcSearch/VolcSearch.zip" - }, - { - "name": "TomatoNovel", - "displayName": "番茄小说增强器", - "description": "提供多功能搜书、全本精准下载、以及极速下载大纲目录等功能。", - "version": "1.0.0", - "author": "Antigravity", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TomatoNovel/TomatoNovel.zip" - }, - { - "name": "ZhihuSearch", - "displayName": "知乎搜索插件", - "description": "调用知乎开放平台 zhihu_search 与 global_search API 搜索知乎内容,返回面向 AI 阅读的 Markdown content、sources,以及标题、链接、作者、摘要、编辑时间等结构化结果;站内搜索额外返回点赞数和评论数。", - "version": "1.1.2", - "author": "OpenClaw / VCP", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ZhihuSearch/ZhihuSearch.zip" - }, - { - "name": "SciCalculator", - "displayName": "科学计算器", - "description": "执行数学表达式计算。AI应使用特定格式请求此工具。", - "version": "1.1.1", - "author": "UserProvided (Adapted by Roo)", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SciCalculator/SciCalculator.zip" - }, - { - "name": "NeteaseMusic", - "displayName": "网易云音乐助手", - "description": "网易云音乐 VCP 同步插件,支持搜索、歌单、歌曲详情、歌词、分享短链解析、每日推荐、红心、歌单操作、个人数据查询、下载与多模态音乐分析。", - "version": "1.2.0", - "author": "VCP Team", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/NeteaseMusic/NeteaseMusic.zip" - }, - { - "name": "ServerTencentCOSBackup", - "displayName": "腾讯云COS备份插件", - "description": "一个功能完整的腾讯云对象存储(COS)插件,支持文件上传、下载、复制、移动、删除和列出操作,具有权限控制和自动压缩功能。动态读取config.env中的AGENT_FOLDERS_CONFIG,带有对AGENT_FOLDERS_CONFIG中子文件夹权限的文字描述。", - "version": "1.1.1", - "author": "VCP Developer", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TencentCOSBackup/ServerTencentCOSBackup.zip" - }, - { - "name": "GuanLan", - "displayName": "观澜-A股数据引擎", - "description": "A股智能数据引擎,38+专业命令。支持双数据源容灾(Tushare+AKShare)、真实费率引擎(佣金/印花税/过户费)、filelock跨进程并发安全。核心模块:实时行情与技术指标(MA/MACD/RSI/布林带/KDJ等)、基本面数据(PE/PB/ROE/市值)、资金流向分析、板块排名与轮动追踪、持仓管理与交易记录(含胜率统计)、组合压力测试(5场景)、选股框架v4.0(五风格组分路由+三问+禁买清单+版本戳)、策略回测引擎(4种策略)、事件型异动扫描(龙虎榜/大宗交易/解禁/业绩预告)、舆情分析(5维度)、盘中异动扫描与持仓监控、盘后日报。作者:观澜 & 冬竹子(翔)", - "version": "4.0.0", - "author": "观澜 & 冬竹子(翔)", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GuanLan/GuanLan.zip" - }, - { - "name": "SynchroVision", - "displayName": "视界同调 (SynchroVision)", - "description": "全面浏览器感知系统:实时页面感知、浏览历史、书签、下载记录、打开标签页、搜索记录(含 ChatGPT/Grok 网页与浏览器 App/PWA 提问轨迹)、系统窗口状态、B站精准历史、AI意图捕获。让Agent完全了解你的上网情况。", - "version": "2.0.0", - "author": "VCP Team", - "icon": "extension", - "category": "service", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SynchroVision/SynchroVision.zip" - }, - { - "name": "GoogleSearch", - "displayName": "谷歌搜索 (API版)", - "description": "一个使用Google Custom Search API进行搜索的同步插件。", - "version": "2.0.0", - "author": "Kilo Code", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GoogleSearch/GoogleSearch.zip" - }, - { - "name": "Randomness", - "displayName": "随机事件生成器", - "description": "一个多功能后端插件,用于生成各种可信的随机事件。支持无状态的单次随机事件(如抽牌、掷骰)和有状态的、可持久化的牌堆管理(创建、抽取、重置、销毁),适用于需要连续操作的场景。", - "version": "5.2.0", - "author": "VincentHDLee & Gemini", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/Randomness/Randomness.zip" - }, - { - "name": "ProjectAnalyst", - "displayName": "项目分析器", - "description": "分析指定的项目文件夹,生成详细的分析报告,并支持后续查询分析结果。", - "version": "1.0.0", - "author": "Roo", - "icon": "extension", - "category": "tool", - "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ProjectAnalyst/ProjectAnalyst.zip" - } - ] -} +{ + "schemaVersion": 1, + "generatedAt": "2026-08-29T16:44:30.502197+00:00", + "source": { + "name": "VCP 官方插件商店", + "repository": "https://github.com/lioensky/VCPDistributedServer", + "branch": "main" + }, + "plugins": [ + { + "name": "1PanelInfoProvider", + "displayName": "1Panel 信息提供器", + "description": "从1Panel服务器获取Dashboard基础信息和操作系统信息,并通过独立的系统提示词占位符提供这些数据。", + "version": "1.0.0", + "author": "B3000Kcn", + "icon": "extension", + "category": "data-provider", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/1PanelInfoProvider/1PanelInfoProvider.zip" + }, + { + "name": "AnkiSearch", + "displayName": "Anki 搜索与查询", + "description": "VCP 体系下的独立 Anki 查询工具。支持使用 Anki 强大的查询语法搜索卡片,获取牌组统计数据,以及查询笔记类型(Model)。", + "version": "1.2.0", + "author": "VCPAnki Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/AnkiSearch/AnkiSearch.zip" + }, + { + "name": "AnkiManage", + "displayName": "Anki 管理与操作", + "description": "VCP 体系下的独立 Anki 管理工具。支持添加笔记、更新字段、挂起卡片及重新调度。", + "version": "1.2.0", + "author": "VCPAnki Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/AnkiManage/AnkiManage.zip" + }, + { + "name": "SynBiliVision", + "displayName": "B站用户信息综合查询", + "description": "获取当前登录 B 站账号的综合信息,包括浏览历史、收藏夹、最近收藏、投币记录和观看偏好分析。", + "version": "2.3.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SynBiliVision/SynBiliVision.zip" + }, + { + "name": "ComfyCloudGen", + "displayName": "Comfy Cloud 云端图像/视频生成", + "description": "通过Comfy Cloud云端GPU生成图像或视频。数据驱动架构,自动匹配模型生态,支持895+云端模型。三种模式:auto(默认)、template、raw。", + "version": "0.4.0", + "author": "Rosa", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ComfyCloudGen/ComfyCloudGen.zip" + }, + { + "name": "FRPSInfoProvider", + "displayName": "FRPS 设备信息提供器", + "description": "定期从FRPS服务器获取所有类型的代理设备信息,并整合成一个文本文件供占位符使用。", + "version": "1.0.0", + "author": "B3000Kcn", + "icon": "extension", + "category": "data-provider", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/FRPSInfoProvider/FRPSInfoProvider.zip" + }, + { + "name": "NanoBananaGenOR", + "displayName": "Gemini 2.5 NanoBanana 图像生成 (OpenRouter)", + "description": "使用 OpenRouter 接口调用 Google Gemini 2.5 Flash Image Preview 模型进行高级的图像生成和编辑。支持代理和多密钥随机选择。", + "version": "1.0.0", + "author": "Kilo Code", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/NanoBananaGenOR/NanoBananaGenOR.zip" + }, + { + "name": "GitOperator", + "displayName": "Git 仓库管理器", + "description": "基于配置档驱动的智能 Git 管理器。支持多仓库 Profile 管理、凭证注入、串行调用和 Token 脱敏输出。", + "version": "1.1.0", + "author": "Nova & hjhjd", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GitOperator/GitOperator.zip" + }, + { + "name": "GodotBridge", + "displayName": "Godot MCP 桥接器", + "description": "作为标准 MCP Client 连接 Godot MCP Native 插件(默认 http://127.0.0.1:9080/mcp),让 VCP Agent 通过渐进式工具发现来读写 Godot 项目的场景、脚本、节点、资源,并控制编辑器与运行时调试。", + "version": "1.0.0", + "author": "ATRI", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GodotBridge/GodotBridge.zip" + }, + { + "name": "GodotEventReceiver", + "displayName": "Godot 事件接收器", + "description": "二期反向通道:作为 WebSocket 服务端接收来自 Godot 侧 godot_vcp_bridge 插件主动推送的运行时/编辑器事件(服务器启停、工具执行、错误、日志、游戏内自定义事件),并转发到 VCP 前端广播。与 GodotBridge(请求-响应)物理隔离。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GodotEventReceiver/GodotEventReceiver.zip" + }, + { + "name": "GrokVideoGen", + "displayName": "Grok 视频生成器", + "description": "使用 Grok API 进行视频生成(支持文生视频、图生视频、视频续写、视频拼接)。", + "version": "1.3.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GrokVideo/GrokVideoGen.zip" + }, + { + "name": "HealthQuery", + "displayName": "HealthQuery (Fitbit)", + "description": "Integrated Google Fitbit health data query tool. Supports Steps, Heart Rate, Sleep analysis via Fitbit Web API.", + "version": "2.1.0", + "author": "VCP Developer", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/HealthQuery/HealthQuery.zip" + }, + { + "name": "IMAPSearch", + "displayName": "IMAP 邮件本地搜索", + "description": "一个同步 VCP 插件,用于在 IMAPIndex 插件生成的本地邮件存储中执行全文搜索。它支持分页和可配置的索引目录。", + "version": "1.1.0", + "author": "B3000Kcn", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/IMAPSearch/IMAPSearch.zip" + }, + { + "name": "IMAPIndex", + "displayName": "IMAP 邮件本地索引插件", + "description": "静态插件:定期通过 IMAP 拉取白名单匹配邮件到本地(.eml -> .md 转换),合并生成索引文本并输出到 stdout,供占位符注入系统提示词使用。仅邮件链路支持 HTTP(S) CONNECT 代理(可选)。", + "version": "1.0.0", + "author": "B3000Kcn", + "icon": "extension", + "category": "data-provider", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/IMAPIndex/IMAPIndex.zip" + }, + { + "name": "KarakeepSearch", + "displayName": "Karakeep 搜索书签", + "description": "在 Karakeep 中全文搜索书签。参数:\\n- query (字符串, 必需): 搜索关键词,支持 is:fav, #tag 等高级语法。\\n- limit (数字, 可选, 默认 10): 返回结果数量。\\n- nextCursor (字符串, 可选): 用于分页的游标。\\n\\n调用格式示例:\\n<<<[TOOL_REQUEST]>>>\\ntool_name:「始」KarakeepSearch「末」,\\nquery:「始」machine learning is:fav「末」,\\nlimit:「始」5「末」\\n<<<[END_TOOL_REQUEST]>>>", + "version": "0.1.0", + "author": "Kilo Code", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/KarakeepSearch/KarakeepSearch.zip" + }, + { + "name": "KEGGSearch", + "displayName": "KEGG 数据库查询", + "description": "一个用于查询 KEGG 数据库的 VCP 插件,提供通路、基因、化合物等多维度的数据检索与分析功能。", + "version": "1.0.0", + "author": "B3000Kcn & DBL1F7E5", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/KEGGSearch/KEGGSearch.zip" + }, + { + "name": "MCPO", + "displayName": "MCPO 工具桥接器", + "description": "基于 mcpo 的 MCP 工具桥接插件,能够自动发现、缓存和调用 MCP 工具,支持多种 MCP 服务器类型。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/MCPO/MCPO.zip" + }, + { + "name": "MCPOMonitor", + "displayName": "MCPO 服务状态监控器", + "description": "监控 MCPO 服务器状态并提供所有可用 MCP 工具的详细信息,通过 {{MCPOServiceStatus}} 占位符集成到系统提示词中。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "data-provider", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/MCPOMonitor/MCPOMonitor.zip" + }, + { + "name": "MIDITranslator", + "displayName": "MIDI翻译器", + "description": "一个高性能的MIDI文件解析与生成插件,可以从midi-input目录读取MIDI文件并解析为DSL格式,也可以从DSL生成MIDI文件到midi-output目录。核心引擎由Rust编写,确保性能与安全。提供DSL语法验证、事件提取和双向转换测试等功能。", + "version": "0.3.0", + "author": "ATRI", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/MIDITranslator/MIDITranslator.zip" + }, + { + "name": "NovelAIGen", + "displayName": "NovelAI 图像生成器 (全能力网关)", + "description": "NovelAI 六端点多渠道全参数网关。", + "version": "2.1.0", + "author": "VCP-Assistant; infinite-vector", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/NovelAIGen/NovelAIGen.zip" + }, + { + "name": "ObsidianBridge", + "displayName": "Obsidian 笔记桥接器", + "description": "通过 Obsidian CLI (v1.12+) 桥接 VCP 与 Obsidian 笔记库。支持笔记读写、全文搜索(含上下文)、每日笔记、文件/文件夹浏览、任务管理、反向链接/出站链接查询、标签统计、属性管理、大纲查看、字数统计、模板列表等 22 项能力。v1.3.0 安全加固:execSync→execFileSync 迁移,消除 Shell 注入面;.bat/.cmd 自动降级兼容。", + "version": "1.3.0", + "author": "Nova & 小夜 | 扩展: infinite-vector", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ObsidianBridge/ObsidianBridge.zip" + }, + { + "name": "PubMedSearch", + "displayName": "PubMed 文献检索插件", + "description": "提供基于 NCBI E-utilities 和 PMC 的 PubMed 文献检索、详情获取、引用/相似文献分析与标识符转换等能力。实现参考 PubMed-MCP-Server 原始实现,保持结果结构和语义尽量一致,便于从 MCP 平滑迁移。", + "version": "1.0.0", + "author": "B3000Kcn & DBL1F7E5", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/PubMedSearch/PubMedSearch.zip" + }, + { + "name": "PyScreenshot", + "displayName": "Python截图插件", + "description": "一个使用Python和Pillow从桌面截取屏幕图像的同步插件。", + "version": "1.0.0", + "author": "Cline", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/PyScreenshot/PyScreenshot.zip" + }, + { + "name": "PyCameraCapture", + "displayName": "Python摄像头插件", + "description": "一个使用Python和OpenCV从摄像头捕获图像的同步插件。", + "version": "1.0.0", + "author": "Cline", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/PyCameraCapture/PyCameraCapture.zip" + }, + { + "name": "SenseGen", + "displayName": "SenseGen 图文文档型图片生成器", + "description": "通过 SenseNova 的 sensenova-u1-fast 模型生成适合 PPT、PDF、杂志、信息图、海报等图文并茂风格的高分辨率图片文档。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SenseGen/SenseGen.zip" + }, + { + "name": "SerpSearch", + "displayName": "Serp API 搜索引擎", + "description": "一个使用SerpApi提供多种搜索引擎(如Bing, DuckDuckGo, Google Scholar)的插件。", + "version": "1.0.0", + "author": "Your Name", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SerpSearch/SerpSearch.zip" + }, + { + "name": "SunoGen", + "displayName": "Suno AI 音乐生成", + "description": "使用 Suno API 生成原创歌曲。该版本为Newapi代理的通用版本。支持通过歌词、风格、标题进行自定义创作,或通过描述获取灵感创作,还可以继续生成已有的歌曲片段。", + "version": "0.1.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SunoGen/SunoGen.zip" + }, + { + "name": "SunoMusicGen", + "displayName": "Suno音乐生成器(新API)", + "description": "使用新的Suno API(sunoapi.org)生成高质量AI音乐、支持音乐延长和音频处理(异步版本)", + "version": "2.2.0", + "author": "Ava", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SunoMusicGen/SunoMusicGen.zip" + }, + { + "name": "TinyFishBrowser", + "displayName": "TinyFish 搜索与网页抓取", + "description": "基于 TinyFish API 的搜索与网页内容抓取插件。支持网络搜索(返回结构化结果)和网页内容抓取(真实浏览器渲染,支持JS页面,返回Markdown/HTML/JSON)。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TinyFishBrowser/TinyFishBrowser.zip" + }, + { + "name": "DomainSafetyChecker", + "displayName": "URL/域名安全核查器", + "description": "低交互、静态、非侵入式 URL/域名安全核查工具。可帮助 AI 对用户提供的网址、域名、短链落地页、疑似钓鱼站、下载页、登录页、支付页进行安全风险初筛,并返回合并完整 JSON 证据的详细 Markdown 报告。", + "version": "1.2.0", + "author": "VCPToolBox", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/DomainSafetyChecker/DomainSafetyChecker.zip" + }, + { + "name": "VCPQQBotServer", + "displayName": "VCP QQBot 单聊/群聊桥接服务", + "description": "常驻连接腾讯 QQBot Gateway,接收 QQ 单聊与群聊 @ 消息后转发到 VCP 主服务器 /v1/chat/completions,并将非流式 AI 回复拆分为 QQ 文本与真正的 QQ 图片消息发回用户。", + "version": "0.1.0", + "author": "VCPToolBox", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPQQBotServer/VCPQQBotServer.zip" + }, + { + "name": "VCPRedisOperator", + "displayName": "VCP Redis 操作工具", + "description": "VCP Redis 操作插件。AI 通过 ExecuteRedis 命令安全操作 Redis,插件做三道安全闸:命令白名单 / Key 前缀白名单 / 按 Agent 分桶限频。核心用途:SQL大师写入通知后 INCR 未读计数,触发前端实时角标更新。", + "version": "1.0.0", + "author": "pioneer798", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPRedisOperator/VCPRedisOperator.zip" + }, + { + "name": "VCPWeCom", + "displayName": "VCP 企业微信桥接", + "description": "混合服务插件:常驻企微 WebSocket 长连接,收到文本消息后自动唤醒 config.env 中指定的 AgentAssistant Agent,生成回复后通过流式回复推回企微。同时提供 WeComSend 工具供其他 Agent 主动推送企微消息。", + "version": "0.1.0", + "author": "小夜", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPWeCom/VCPWeCom.zip" + }, + { + "name": "SynapsePusher", + "displayName": "VCP 日志 Synapse 推送器", + "description": "将 VCP 工具调用日志实时推送到指定的 Synapse (Matrix) 房间。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SynapsePusher/SynapsePusher.zip" + }, + { + "name": "VCPFeishu", + "displayName": "VCP 飞书桥接", + "description": "混合服务插件:常驻飞书 WebSocket 长连接,收到消息后在绑定 Agent 下创建或复用 VCPChat 话题会话,并通过系统 settings 与 Agent 配置调用 VCP 后端生成回复。", + "version": "0.1.0", + "author": "VCP Team", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPFeishu/VCPFeishu.zip" + }, + { + "name": "DistributeFileOperator", + "displayName": "VCP分布式文件操作器", + "description": "VCP服务器专用的一个强大的文件系统操作插件,允许AI对受限目录进行读、写、列出、移动、复制、删除等多种文件和目录操作。特别增强了文件读取能力,可自动提取PDF、Word(.docx)和表格(.xlsx, .csv)文件的纯文本内容。", + "version": "1.0.1", + "author": "VCPToolBox", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/FileOperator/DistributeFileOperator.zip", + "license": "MIT" + }, + { + "name": "VCPDatabaseOperator", + "displayName": "VCP数据库操作工具", + "description": "VCP数据库操作插件(SQL-First)。AI 直接写原生 MySQL 语句,插件做 AST 解析 + 五道安全闸(动词过滤/表白名单/CRUD权限矩阵/参数化绑定/写操作熔断)。推荐用 ExecuteSQL;保留 QueryTable/InsertRow/UpdateRow/DeleteRow 结构化命令做兼容。", + "version": "1.0.3", + "author": "pioneer798", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VCPDatabaseOperator/VCPDatabaseOperator.zip" + }, + { + "name": "WebUIGen", + "displayName": "WebUI云算力生图", + "description": "调用云算力API生成高质量图像,支持多种模型和自定义参数。", + "version": "1.0.0", + "author": "Kilo Code", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/WebUIGen/WebUIGen.zip" + }, + { + "name": "YoucomSearch", + "displayName": "You.com 搜索插件", + "description": "使用 You.com API 进行网络搜索。AI可以指定搜索查询、返回结果数量、时效范围、国家及语言,获取网页与新闻结果。支持使用 || 分隔多个关键词进行并发搜索,所有结果同时返回。", + "version": "0.1.0", + "author": "Emos21", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/YoucomSearch/YoucomSearch.zip" + }, + { + "name": "YTFetch", + "displayName": "YouTube Gemini Fetch", + "description": "Use Gemini API to directly read public YouTube URLs and return multimodal video/audio analysis without downloading the video locally.", + "version": "0.1.0", + "author": "VCP Community", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/YTFetch/YTFetch.zip" + }, + { + "name": "YoutubeFetch", + "displayName": "YouTube 内容获取插件", + "description": "YouTube 内容获取插件。基于 YouTube Data API v3 获取视频信息、搜索结果、频道信息、频道投稿、热门视频与评论,并通过 youtube-transcript-api 获取字幕/自动字幕。支持 youtube.com、youtu.be、Shorts、embed、live 链接与直接 videoId。", + "version": "1.0.0", + "author": "Bocchi777", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/YoutubeFetch/YoutubeFetch.zip" + }, + { + "name": "ZImageGen", + "displayName": "Z-Image 文生图(阿里通义)", + "description": "通过 Hugging Face Spaces API 使用阿里巴巴通义实验室的 Z-Image-Turbo 模型生成高质量图片。支持中英文提示词,8步推理,亚秒级延迟。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ZImageGen/ZImageGen.zip" + }, + { + "name": "GitSearch", + "displayName": "代码托管平台聚合搜索", + "description": "聚合 GitHub、GitLab、Gitee 三大代码托管平台的纯读取操作,提供统一的工具名和调用方式。", + "version": "1.0.0", + "author": "VCPToolBox", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GitSearch/GitSearch.zip" + }, + { + "name": "XiaohongshuFetch", + "displayName": "小红书爬虫", + "description": "用于抓取小红书图文/视频笔记内容的专属爬虫。", + "version": "3.0.0", + "author": "VCP", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/XiaohongshuFetch/XiaohongshuFetch.zip" + }, + { + "name": "WorkspaceInjector", + "displayName": "工作区动态注入", + "description": "通过在系统提示词中使用 {{Workspace::alias}} 占位符,将预设的本地文件夹目录树动态注入到上下文中。", + "version": "1.2.0", + "author": "Gemini & User", + "icon": "extension", + "category": "system-integration", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/WorkspaceInjector/WorkspaceInjector.zip" + }, + { + "name": "SVCardFinder", + "displayName": "影之诗查卡器(WB)", + "description": "一个用于查询《影之诗:世界超越》卡牌信息的插件。", + "version": "1.0.0", + "author": "greyfilm", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SVCardFinder/SVCardFinder.zip" + }, + { + "name": "JapaneseHelper", + "displayName": "日语学习助手", + "description": "日语学习增强插件(纯Python基础层 + optional增强层)。支持深度语法解析、动态句型识别(含部分活用补偿)、错题本分析推荐、复习调度、汉字查询、资源状态检查、JLPT统计(jlpt_stats)与可选 OJAD 声调接口。结构化接口支持 Lookup/KanjiInfo/JLPTCheck/ReadingAid/ParseTree/SchemaProbe。SemanticEnhance 默认使用 rule 风格判定,已修复礼貌体被误判为 mixed 的问题;SyntaxEnhance 默认 heuristic,可选 GiNZA 增强;ReadingEnhance 支持可选 romaji 后端。已补齐 SentenceSplit/ReadingEnhance/SyntaxEnhance/SemanticEnhance/TeachingAssetsStatus 五项分层命令。Romanize/Furigana 已修复促音拆裂与活用显示问题;已完成全命令回归与重复旧函数清理。", + "version": "2.14.2", + "author": "Nova", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/JapaneseHelper/JapaneseHelper.zip" + }, + { + "name": "TagFolder", + "displayName": "标签折叠器", + "description": "读取 list.md 中的标签白名单,生成折叠行为指令注入系统提示词,实现对 nvim 系列标签包裹内容的上下文折叠管理。", + "version": "1.0.0", + "author": "ATRI & Lucifer", + "icon": "extension", + "category": "data-provider", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TagFolder/TagFolder.zip" + }, + { + "name": "TicktickManager", + "displayName": "滴答清单管理器", + "description": "混合插件:提供滴答清单/TickTick/Dida365 任务同步调用和白名单项目静态任务快照注入。", + "version": "1.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TicktickManager/TicktickManager.zip" + }, + { + "name": "VolcSearch", + "displayName": "火山引擎搜索插件", + "description": "使用火山引擎联网搜索 API 进行中文网络搜索。AI可以指定搜索查询、返回结果数量,并可选择包含正文内容、设置搜索时间范围等。支持使用 || 分隔多个关键词进行并发搜索,所有结果同时返回。", + "version": "0.3.0", + "author": "Roo", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/VolcSearch/VolcSearch.zip" + }, + { + "name": "TomatoNovel", + "displayName": "番茄小说增强器", + "description": "提供多功能搜书、全本精准下载、以及极速下载大纲目录等功能。", + "version": "1.0.0", + "author": "Antigravity", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TomatoNovel/TomatoNovel.zip" + }, + { + "name": "ZhihuSearch", + "displayName": "知乎搜索插件", + "description": "调用知乎开放平台 zhihu_search 与 global_search API 搜索知乎内容,返回面向 AI 阅读的 Markdown content、sources,以及标题、链接、作者、摘要、编辑时间等结构化结果;站内搜索额外返回点赞数和评论数。", + "version": "1.1.2", + "author": "OpenClaw / VCP", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ZhihuSearch/ZhihuSearch.zip" + }, + { + "name": "SciCalculator", + "displayName": "科学计算器", + "description": "执行数学表达式计算。AI应使用特定格式请求此工具。", + "version": "1.1.1", + "author": "UserProvided (Adapted by Roo)", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SciCalculator/SciCalculator.zip" + }, + { + "name": "NeteaseMusic", + "displayName": "网易云音乐助手", + "description": "网易云音乐 VCP 同步插件,支持搜索、歌单、歌曲详情、歌词、分享短链解析、每日推荐、红心、歌单操作、个人数据查询、下载与多模态音乐分析。", + "version": "1.2.0", + "author": "VCP Team", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/NeteaseMusic/NeteaseMusic.zip" + }, + { + "name": "ServerTencentCOSBackup", + "displayName": "腾讯云COS备份插件", + "description": "一个功能完整的腾讯云对象存储(COS)插件,支持文件上传、下载、复制、移动、删除和列出操作,具有权限控制和自动压缩功能。动态读取config.env中的AGENT_FOLDERS_CONFIG,带有对AGENT_FOLDERS_CONFIG中子文件夹权限的文字描述。", + "version": "1.1.1", + "author": "VCP Developer", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/TencentCOSBackup/ServerTencentCOSBackup.zip" + }, + { + "name": "GuanLan", + "displayName": "观澜-A股数据引擎", + "description": "A股智能数据引擎,38+专业命令。支持双数据源容灾(Tushare+AKShare)、真实费率引擎(佣金/印花税/过户费)、filelock跨进程并发安全。核心模块:实时行情与技术指标(MA/MACD/RSI/布林带/KDJ等)、基本面数据(PE/PB/ROE/市值)、资金流向分析、板块排名与轮动追踪、持仓管理与交易记录(含胜率统计)、组合压力测试(5场景)、选股框架v4.0(五风格组分路由+三问+禁买清单+版本戳)、策略回测引擎(4种策略)、事件型异动扫描(龙虎榜/大宗交易/解禁/业绩预告)、舆情分析(5维度)、盘中异动扫描与持仓监控、盘后日报。作者:观澜 & 冬竹子(翔)", + "version": "4.0.0", + "author": "观澜 & 冬竹子(翔)", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GuanLan/GuanLan.zip" + }, + { + "name": "SynchroVision", + "displayName": "视界同调 (SynchroVision)", + "description": "全面浏览器感知系统:实时页面感知、浏览历史、书签、下载记录、打开标签页、搜索记录(含 ChatGPT/Grok 网页与浏览器 App/PWA 提问轨迹)、系统窗口状态、B站精准历史、AI意图捕获。让Agent完全了解你的上网情况。", + "version": "2.0.0", + "author": "VCP Team", + "icon": "extension", + "category": "service", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/SynchroVision/SynchroVision.zip" + }, + { + "name": "GoogleSearch", + "displayName": "谷歌搜索 (API版)", + "description": "一个使用Google Custom Search API进行搜索的同步插件。", + "version": "2.0.0", + "author": "Kilo Code", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/GoogleSearch/GoogleSearch.zip" + }, + { + "name": "Randomness", + "displayName": "随机事件生成器", + "description": "一个多功能后端插件,用于生成各种可信的随机事件。支持无状态的单次随机事件(如抽牌、掷骰)和有状态的、可持久化的牌堆管理(创建、抽取、重置、销毁),适用于需要连续操作的场景。", + "version": "5.2.0", + "author": "VincentHDLee & Gemini", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/Randomness/Randomness.zip" + }, + { + "name": "ProjectAnalyst", + "displayName": "项目分析器", + "description": "分析指定的项目文件夹,生成详细的分析报告,并支持后续查询分析结果。", + "version": "1.0.0", + "author": "Roo", + "icon": "extension", + "category": "tool", + "downloadUrl": "https://raw.githubusercontent.com/lioensky/VCPDistributedServer/main/Plugin/ProjectAnalyst/ProjectAnalyst.zip" + } + ] +}