feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI, HuggingFace, NVIDIA Parakeet; new 9router-stt skill - Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG - Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor Gemini CLI usage to use retrieveUserQuota with per-model buckets - Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route - Header search: reusable Zustand store (headerSearchStore) wired into Header - CLI tools: add Claude Cowork tool card and cowork-settings API - Providers: introduce mediaPriority sorting in getProvidersByKind, add Kimi K2.6, reorder hermes, drop qwen STT kind - UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal, ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits - Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia, ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft, runwayml, stability-ai, topaz, black-forest-labs
This commit is contained in:
parent
bfb7d42164
commit
d4bc42e1f5
67 changed files with 2930 additions and 234 deletions
|
|
@ -4,7 +4,32 @@ const path = require("path");
|
|||
const os = require("os");
|
||||
const { log, err } = require("../logger");
|
||||
const { TOOL_HOSTS } = require("../../shared/constants/mitmToolHosts");
|
||||
const { runElevatedPowerShell, quotePs, isAdmin } = require("../winElevated.js");
|
||||
const { runElevatedPowerShell, isAdmin } = require("../winElevated.js");
|
||||
|
||||
/**
|
||||
* Atomic-ish write for Windows hosts file with rollback on failure.
|
||||
* Strategy: write `.new` sibling → rename current to `.bak` → rename `.new` to target.
|
||||
* If anything fails mid-way, restore from `.bak`. Same-volume renames are atomic on NTFS.
|
||||
*/
|
||||
function atomicWriteHostsWin(target, originalContent, newContent) {
|
||||
const tmpNew = `${target}.9router.new`;
|
||||
const tmpBak = `${target}.9router.bak`;
|
||||
try {
|
||||
fs.writeFileSync(tmpNew, newContent, "utf8");
|
||||
try { fs.unlinkSync(tmpBak); } catch { /* none */ }
|
||||
fs.renameSync(target, tmpBak);
|
||||
try {
|
||||
fs.renameSync(tmpNew, target);
|
||||
} catch (e) {
|
||||
// Rollback: restore original
|
||||
try { fs.renameSync(tmpBak, target); } catch { fs.writeFileSync(target, originalContent, "utf8"); }
|
||||
throw e;
|
||||
}
|
||||
try { fs.unlinkSync(tmpBak); } catch { /* best effort */ }
|
||||
} finally {
|
||||
try { fs.unlinkSync(tmpNew); } catch { /* already moved or never created */ }
|
||||
}
|
||||
}
|
||||
|
||||
const IS_WIN = process.platform === "win32";
|
||||
const IS_MAC = process.platform === "darwin";
|
||||
|
|
@ -130,16 +155,13 @@ async function addDNSEntry(tool, sudoPassword) {
|
|||
|
||||
try {
|
||||
if (IS_WIN) {
|
||||
// Read → trim → append → write (avoids stacked blank lines from Add-Content)
|
||||
// Read → trim → append → atomic write (Node-side, no CLI size limit)
|
||||
const current = fs.readFileSync(HOSTS_FILE, "utf8");
|
||||
const trimmed = current.replace(/[\r\n\s]+$/g, "");
|
||||
const toAppend = entriesToAdd.map(h => `127.0.0.1 ${h}`).join("\r\n");
|
||||
const next = `${trimmed}\r\n${toAppend}\r\n`;
|
||||
const script = `
|
||||
Set-Content -LiteralPath ${quotePs(HOSTS_FILE)} -Value ${quotePs(next)} -NoNewline
|
||||
ipconfig /flushdns | Out-Null
|
||||
`;
|
||||
await runElevatedPowerShell(script);
|
||||
atomicWriteHostsWin(HOSTS_FILE, current, next);
|
||||
await runElevatedPowerShell("ipconfig /flushdns | Out-Null");
|
||||
} else {
|
||||
const current = fs.readFileSync(HOSTS_FILE, "utf8");
|
||||
const trimmed = current.replace(/[\r\n\s]+$/g, "");
|
||||
|
|
@ -175,11 +197,8 @@ async function removeDNSEntry(tool, sudoPassword) {
|
|||
const current = fs.readFileSync(HOSTS_FILE, "utf8");
|
||||
const filtered = current.split(/\r?\n/).filter(l => !entriesToRemove.some(h => l.includes(h))).join("\r\n");
|
||||
const next = filtered.replace(/[\r\n\s]+$/g, "") + "\r\n";
|
||||
const script = `
|
||||
Set-Content -LiteralPath ${quotePs(HOSTS_FILE)} -Value ${quotePs(next)} -NoNewline
|
||||
ipconfig /flushdns | Out-Null
|
||||
`;
|
||||
await runElevatedPowerShell(script);
|
||||
atomicWriteHostsWin(HOSTS_FILE, current, next);
|
||||
await runElevatedPowerShell("ipconfig /flushdns | Out-Null");
|
||||
} else {
|
||||
const current = fs.readFileSync(HOSTS_FILE, "utf8");
|
||||
const filtered = current.split(/\r?\n/).filter(l => !entriesToRemove.some(h => l.includes(h))).join("\n");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue