message / plugins /audio /frontend /index.html
hunian
refactor(plugins): 插件短名并统一 MCP tool 为 {plugin}-{tool}
cc826a1
Raw
History Blame Contribute Delete
6.44 kB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>音频转录</title>
<link rel="stylesheet" href="/static/plugin-ui.css">
</head>
<body>
<main class="plugin-shell">
<header class="plugin-header">
<div>
<h1 class="plugin-title">音频转录</h1>
<p class="plugin-subtitle">上传音频并转录为文本。模型首次加载约需 10-30 秒。</p>
</div>
<span id="statusPill" class="plugin-status">检查中</span>
</header>
<div id="message" class="plugin-message"></div>
<section class="plugin-grid">
<div class="plugin-panel">
<h2 class="plugin-panel-title">音频</h2>
<div class="plugin-stack">
<!-- Provider 选择 -->
<div class="plugin-field">
<label for="provider">ASR Provider</label>
<select id="provider" class="plugin-select">
<option value="">加载中...</option>
</select>
<div id="providerStatus" class="plugin-muted"></div>
</div>
<!-- 文件上传 -->
<input id="file" type="file" accept="audio/*">
<input id="language" placeholder="语言代码(可选,如 zh、en)">
<button id="transcribeBtn" class="plugin-button" onclick="transcribe()">开始转录</button>
</div>
</div>
<!-- 运行日志面板(由后端事件驱动) -->
<div id="logPanel"></div>
</section>
<!-- 结果区 -->
<section class="plugin-grid" id="resultSection" style="display:none;">
<div class="plugin-panel">
<h2 class="plugin-panel-title">转录结果</h2>
<pre id="output" class="plugin-output"></pre>
</div>
</section>
</main>
<script src="/static/plugin-ui.js"></script>
<script>
const API = "/plugins/audio/api";
let currentLogPanel = null;
// 加载 provider 列表
async function loadProviders() {
try {
const data = await PluginUI.request(`${API}/providers`);
const select = document.getElementById("provider");
select.innerHTML = "";
for (const p of data.providers) {
const opt = document.createElement("option");
opt.value = p.name;
opt.textContent = `${p.name} (${p.status})`;
opt.disabled = p.status !== "available";
select.appendChild(opt);
}
// 设置默认 provider
if (data.default) {
select.value = data.default;
}
updateProviderStatus();
} catch (err) {
console.error("加载 provider 失败:", err);
}
}
// 更新 provider 状态提示
function updateProviderStatus() {
const select = document.getElementById("provider");
const statusEl = document.getElementById("providerStatus");
const selected = select.options[select.selectedIndex];
if (selected && selected.disabled) {
statusEl.textContent = `⚠ 该 provider 在当前环境不可用`;
statusEl.className = "plugin-text-error";
} else {
statusEl.textContent = "";
statusEl.className = "plugin-muted";
}
}
document.getElementById("provider").addEventListener("change", updateProviderStatus);
// 加载插件状态
async function loadStatus() {
try {
const data = await PluginUI.request(`${API}/status`);
const pill = document.getElementById("statusPill");
pill.textContent = data.enabled === false ? "插件未启用" : "可用";
pill.className = `plugin-status ${data.enabled === false ? "warn" : "ok"}`;
} catch {
document.getElementById("statusPill").textContent = "状态未知";
}
}
// 转录函数
async function transcribe() {
const btn = document.getElementById("transcribeBtn");
btn.disabled = true;
// 清理旧日志面板
if (currentLogPanel) {
PluginUI.stopPolling(currentLogPanel);
currentLogPanel.remove();
}
// 隐藏结果区
document.getElementById("resultSection").style.display = "none";
PluginUI.clearMessage("message");
try {
const file = document.getElementById("file").files[0];
if (!file) throw new Error("请选择音频文件");
const provider = document.getElementById("provider").value;
const language = document.getElementById("language").value.trim();
// 构建请求 URL
let url = `${API}/upload`;
const params = [];
if (provider) params.push(`provider=${encodeURIComponent(provider)}`);
if (language) params.push(`language=${encodeURIComponent(language)}`);
if (params.length > 0) url += `?${params.join("&")}`;
// 上传文件
const form = new FormData();
form.append("file", file);
const data = await PluginUI.request(url, { method: "POST", body: form });
if (data.run_id) {
// 创建日志面板
currentLogPanel = PluginUI.createRunLog({ title: "转录日志" });
document.getElementById("logPanel").appendChild(currentLogPanel);
// 开始轮询事件
PluginUI.pollRunEvents("audio", data.run_id, currentLogPanel, {
onRunComplete: (run) => {
btn.disabled = false;
if (run.status === "succeeded" && run.result) {
// 显示结果区
document.getElementById("resultSection").style.display = "block";
PluginUI.setJson("output", run.result);
}
},
onError: (err) => {
btn.disabled = false;
PluginUI.showMessage("message", `轮询错误: ${err.message}`, "error");
},
});
} else {
// 同步返回结果(wait=true)
btn.disabled = false;
if (data.status === "succeeded") {
document.getElementById("resultSection").style.display = "block";
PluginUI.setJson("output", data);
} else {
PluginUI.showMessage("message", data.error || "转录失败", "error");
}
}
} catch (error) {
btn.disabled = false;
PluginUI.showMessage("message", error.message, "error");
}
}
// 初始化
loadProviders();
loadStatus();
</script>
</body>
</html>