koishi-plugin-beta-dxl-bert-vits
Version:
beta-dxl-bert-vits。
156 lines (124 loc) • 5.79 kB
JavaScript
// ==UserScript==
// @name ModelScope Studio Data Exporter
// @namespace https://github.com/shangxueink
// @version 3.1
// @description For ModelScope's Bert-VITS2 studio pages, it adds a button to download speaker data as a JSON file for plugins.
// @author shangxueink
// @license MIT
// @match https://www.modelscope.cn/studios/*/*
// @icon https://g.alicdn.com/sail-web/maas/2.9.96/favicon/128.ico
// @supportURL https://github.com/shangxueink/koishi-shangxue-apps/issues
// @grant GM_xmlhttpRequest
// @grant GM_download
// @connect *.ms.show
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// --- Activation Check ---
const currentUrl = window.location.href.toLowerCase();
if (!currentUrl.includes('bert') || !currentUrl.includes('vits')) {
return;
}
// --- Main Logic ---
createFloatingButton();
function createFloatingButton() {
const downloadBtn = document.createElement('button');
downloadBtn.id = 'download-json-btn-floating';
downloadBtn.textContent = '下载数据JSON';
Object.assign(downloadBtn.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '9999',
backgroundColor: '#ffc107',
color: '#000',
border: '1px solid #dee2e6',
borderRadius: '0.5rem',
padding: '10px 20px',
cursor: 'pointer',
boxShadow: '0 4px 8px rgba(0,0,0,0.1)'
});
downloadBtn.onclick = handleDownload;
document.body.appendChild(downloadBtn);
console.log("ModelScope导出器:已注入浮动下载按钮。");
}
async function getConfigAndRoot() {
const pathParts = window.location.pathname.split('/');
const author = pathParts[2];
const modelName = pathParts[3];
if (!author || !modelName) {
throw new Error(`无法从URL(${window.location.pathname})中解析作者或模型名称。`);
}
const gradioHost = `${author.toLowerCase()}-${modelName.toLowerCase().replace(/\./g, '-')}.ms.show`;
const rootUrl = `https://${gradioHost}`;
const configUrl = `${rootUrl}/config`;
console.log(`ModelScope导出器:已构建预测URL。正在从以下地址获取配置: ${configUrl}`);
const response = await fetch(configUrl);
if (!response.ok) {
throw new Error(`无法从预测的URL(${configUrl})获取配置: ${response.statusText}`);
}
const configData = await response.json();
return { configData, rootUrl };
}
async function handleDownload() {
try {
// 移除了“开始获取数据”的提示
const { configData, rootUrl } = await getConfigAndRoot();
const apiPrefix = configData.api_prefix || '';
const infoUrl = `${rootUrl}${apiPrefix}/info`;
console.log(`ModelScope导出器:已动态构建info URL: ${infoUrl}`);
const infoResponse = await fetch(infoUrl);
if (!infoResponse.ok) throw new Error(`获取 info URL 失败: ${infoResponse.statusText}`);
const infoData = await infoResponse.json();
const generatedJson = processGradioData(configData, infoData);
const blob = new Blob([JSON.stringify(generatedJson, null, 4)], { type: 'application/json' });
const downloadUrl = URL.createObjectURL(blob);
const pathParts = window.location.pathname.split('/');
const author = pathParts[2] || 'unknown_author';
const modelName = pathParts[3] || 'unknown_model';
const aiNameElement = document.querySelector('.text_Generation_name');
const aiName = aiNameElement ? aiNameElement.textContent.trim() : 'unknown_ai';
const filename = `speakers_${aiName}_${author}_${modelName}.json`;
const a = document.createElement('a');
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(downloadUrl);
alert(`数据已成功生成并开始下载!\n文件名: ${filename}`);
} catch (error) {
console.error('ModelScope导出器:生成JSON失败:', error);
alert(`生成JSON失败: ${error.message}`);
}
}
function processGradioData(config, info) {
const pathParts = window.location.pathname.split('/');
const author = pathParts[2] || 'unknown_author';
const modelName = pathParts[3] || 'unknown_model';
let speakerDropdown = config.components.find(c => c.props.label === '角色' && c.type === 'dropdown');
if (!speakerDropdown) {
speakerDropdown = config.components.find(c => c.props.label === 'Speaker' && c.type === 'dropdown');
}
if (!speakerDropdown) {
throw new Error('无法在配置中找到“角色”或“Speaker”下拉列表组件。');
}
const speakers = {};
const speakerNames = speakerDropdown.props.choices.map(choice => {
return Array.isArray(choice) ? choice[0] : choice;
});
speakerNames.forEach(name => {
speakers[name] = {
speaker: name
};
});
const baseApiUrl = `https://www.modelscope.cn/api/v1/studio/${author}/${modelName}/gradio`;
const finalJson = {
api: `${baseApiUrl}/run/predict`,
fileApi: `${baseApiUrl}/file=`,
speakers: speakers
};
return finalJson;
}
})();