2026-01-17 16:34:39 +08:00
<!DOCTYPE html>
< html lang = "zh-CN" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" >
< meta name = "apple-mobile-web-app-capable" content = "yes" >
< meta name = "mobile-web-app-capable" content = "yes" >
2026-01-18 02:55:49 +08:00
< title > TTS <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / title >
2026-01-17 16:34:39 +08:00
< link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" >
< style >
* { margin: 0; padding: 0; box-sizing: border-box; }
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
:root {
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
<20> <> <EFBFBD> <EFBFBD> <EFBFBD> Ƽ <EFBFBD> <C6BC> <EFBFBD> <EFBFBD> <EFBFBD> ɫ - <20> ڰ<DAB0> + <20> <> ɫ<EFBFBD> <C9AB>
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-18 02:55:49 +08:00
/* <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> */
2026-01-18 01:48:30 +08:00
--bg-primary: #0a0a0c;
--bg-secondary: #111114;
--bg-tertiary: #18181c;
--bg-elevated: #1e1e24;
--bg-input: rgba(255, 255, 255, 0.04);
2026-01-18 02:55:49 +08:00
/* <20> <> <EFBFBD> ֲ<EFBFBD> <D6B2> <EFBFBD> */
2026-01-18 01:48:30 +08:00
--text-primary: #f0f0f2;
--text-secondary: #a0a0a8;
--text-muted: #606068;
--text-dim: #404048;
2026-01-18 02:55:49 +08:00
/* <20> ߿<EFBFBD> */
2026-01-18 01:48:30 +08:00
--border: rgba(255, 255, 255, 0.08);
--border-light: rgba(255, 255, 255, 0.12);
--border-focus: rgba(140, 200, 255, 0.4);
2026-01-18 02:55:49 +08:00
/* Ψһ ǿ<D2BB> <C7BF> ɫ - <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ƽ <EFBFBD> <C6BC> У <EFBFBD> */
2026-01-18 01:48:30 +08:00
--accent: #8cc8ff;
--accent-soft: rgba(140, 200, 255, 0.1);
--accent-glow: rgba(140, 200, 255, 0.15);
2026-01-18 02:55:49 +08:00
/* <20> <> <EFBFBD> <EFBFBD> ɫ - <20> <> <EFBFBD> ͱ<EFBFBD> <CDB1> Ͷ<EFBFBD> */
2026-01-18 01:48:30 +08:00
--success: #90d4a0;
--success-soft: rgba(144, 212, 160, 0.08);
--error: #e08080;
--error-soft: rgba(224, 128, 128, 0.08);
2026-01-18 02:55:49 +08:00
/* <20> <> <EFBFBD> <EFBFBD> /<2F> <> Ȩ - <20> <> <EFBFBD> ò<EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> */
2026-01-18 01:48:30 +08:00
--tag-free: rgba(255, 255, 255, 0.7);
--tag-free-bg: rgba(255, 255, 255, 0.06);
--tag-auth: var(--accent);
--tag-auth-bg: var(--accent-soft);
/* Safe Area */
2026-01-17 16:34:39 +08:00
--safe-area-top: env(safe-area-inset-top, 0px);
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
--safe-area-left: env(safe-area-inset-left, 0px);
--safe-area-right: env(safe-area-inset-right, 0px);
}
html {
height: 100%;
height: -webkit-fill-available;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 14px;
line-height: 1.5;
min-height: 100%;
min-height: -webkit-fill-available;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.app-container {
display: flex;
flex-direction: column;
min-height: 100vh;
2026-01-18 01:48:30 +08:00
min-height: 100dvh;
2026-01-17 16:34:39 +08:00
min-height: -webkit-fill-available;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Header
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.app-header {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
gap: 12px;
2026-01-17 16:34:39 +08:00
padding: 12px 20px;
padding-top: calc(12px + var(--safe-area-top));
padding-left: calc(20px + var(--safe-area-left));
padding-right: calc(20px + var(--safe-area-right));
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
flex-shrink: 0;
}
2026-01-18 01:48:30 +08:00
.header-logo {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
white-space: nowrap;
color: var(--text-primary);
}
.header-logo i {
color: var(--accent);
opacity: 0.9;
}
.header-status {
display: flex;
align-items: center;
gap: 8px;
}
2026-01-17 16:34:39 +08:00
.header-badge {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 12px;
font-size: 10px;
color: var(--text-muted);
transition: all 0.2s;
}
.header-badge i {
font-size: 5px;
opacity: 0.5;
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.header-badge.active {
color: var(--text-secondary);
border-color: var(--border-light);
}
.header-badge.active i {
color: var(--accent);
opacity: 1;
}
2026-01-17 16:34:39 +08:00
.header-spacer { flex: 1; min-width: 10px; }
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.header-close {
width: 36px; height: 36px; min-width: 36px;
2026-01-18 01:48:30 +08:00
border: 1px solid var(--border);
border-radius: 8px;
background: transparent;
color: var(--text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.2s;
}
.header-close:hover {
background: rgba(255,255,255,0.05);
color: var(--text-primary);
border-color: var(--border-light);
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Layout
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-17 16:34:39 +08:00
.app-body {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
.app-sidebar {
2026-01-18 01:48:30 +08:00
width: 200px;
min-width: 200px;
background: var(--bg-secondary);
border-right: 1px solid var(--border);
padding: 16px 8px;
2026-01-17 16:34:39 +08:00
padding-left: calc(8px + var(--safe-area-left));
2026-01-18 01:48:30 +08:00
display: flex;
flex-direction: column;
gap: 4px;
2026-01-17 16:34:39 +08:00
overflow-y: auto;
flex-shrink: 0;
}
.app-main {
flex: 1;
padding: 24px;
padding-right: calc(24px + var(--safe-area-right));
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Navigation
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.nav-item {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 8px;
color: var(--text-muted);
cursor: pointer;
transition: all 0.15s;
font-size: 13px;
}
.nav-item:hover {
background: rgba(255,255,255,0.03);
color: var(--text-secondary);
}
.nav-item.active {
background: var(--accent-soft);
color: var(--accent);
font-weight: 500;
2026-01-17 16:34:39 +08:00
}
.nav-item i { width: 18px; text-align: center; }
2026-01-18 01:48:30 +08:00
.nav-divider {
height: 1px;
background: var(--border);
margin: 8px 0;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Views
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
.view {
display: none;
max-width: 800px;
margin: 0 auto;
padding-bottom: 24px;
}
.view.active {
display: block;
animation: viewIn 0.2s ease;
}
@keyframes viewIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; }
}
2026-01-17 16:34:39 +08:00
.view-header { margin-bottom: 20px; }
2026-01-18 01:48:30 +08:00
.view-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 4px;
color: var(--text-primary);
}
.view-desc {
font-size: 13px;
color: var(--text-muted);
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Cards
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-17 16:34:39 +08:00
.card {
2026-01-18 01:48:30 +08:00
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.card-title {
2026-01-18 01:48:30 +08:00
font-size: 11px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
2026-01-17 16:34:39 +08:00
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Forms
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.form-group { margin-bottom: 16px; }
.form-group:last-child { margin-bottom: 0; }
2026-01-18 01:48:30 +08:00
.form-label {
display: block;
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 6px;
font-weight: 500;
}
.form-hint {
font-size: 11px;
color: var(--text-muted);
margin-top: 4px;
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
}
2026-01-17 16:34:39 +08:00
.input {
2026-01-18 01:48:30 +08:00
width: 100%;
padding: 10px 12px;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 13px;
transition: all 0.15s;
}
.input:focus {
outline: none;
border-color: var(--border-focus);
background: rgba(255,255,255,0.06);
}
.input::placeholder { color: var(--text-dim); }
textarea.input {
min-height: 80px;
resize: vertical;
font-family: inherit;
2026-01-17 16:34:39 +08:00
}
select.input { cursor: pointer; }
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.input-row { display: flex; gap: 8px; }
.input-row .input { flex: 1; min-width: 0; }
.checkbox-row {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
2026-01-17 16:34:39 +08:00
}
.checkbox-row input[type="checkbox"] {
2026-01-18 01:48:30 +08:00
width: 16px;
height: 16px;
accent-color: var(--accent);
}
.checkbox-row label {
font-size: 13px;
cursor: pointer;
color: var(--text-secondary);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Buttons
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-17 16:34:39 +08:00
.btn {
2026-01-18 01:48:30 +08:00
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px 16px;
min-height: 40px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-tertiary);
color: var(--text-secondary);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.btn:hover {
background: var(--bg-elevated);
color: var(--text-primary);
border-color: var(--border-light);
2026-01-17 16:34:39 +08:00
}
.btn:active { transform: scale(0.98); }
2026-01-18 01:48:30 +08:00
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary {
background: var(--accent-soft);
border-color: rgba(140, 200, 255, 0.2);
color: var(--accent);
font-weight: 500;
}
.btn-primary:hover {
background: rgba(140, 200, 255, 0.15);
border-color: rgba(140, 200, 255, 0.3);
}
.btn-danger {
color: var(--error);
border-color: rgba(224, 128, 128, 0.2);
}
.btn-danger:hover {
background: var(--error-soft);
}
2026-01-17 16:34:39 +08:00
.btn-icon { width: 40px; padding: 0; }
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; }
.btn-sm { padding: 6px 10px; min-height: 32px; font-size: 12px; }
.btn-xs { padding: 4px 8px; min-height: 28px; font-size: 11px; }
.btn.saving { pointer-events: none; opacity: 0.7; }
2026-01-18 01:48:30 +08:00
.btn.save-success {
background: var(--success-soft) !important;
border-color: rgba(144, 212, 160, 0.3) !important;
color: var(--success) !important;
pointer-events: none;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Slider
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-17 16:34:39 +08:00
.slider-row { display: flex; align-items: center; gap: 12px; }
2026-01-18 01:48:30 +08:00
.slider-row input[type="range"] {
flex: 1;
height: 4px;
accent-color: var(--accent);
cursor: pointer;
opacity: 0.8;
}
.slider-row input[type="range"]:hover { opacity: 1; }
.slider-row .slider-val {
min-width: 50px;
text-align: right;
font-size: 13px;
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Rules Editor
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
.rules-editor {
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
2026-01-17 16:34:39 +08:00
.rules-header {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.rules-header-title {
font-size: 11px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
2026-01-17 16:34:39 +08:00
.rules-list { max-height: 200px; overflow-y: auto; }
2026-01-18 01:48:30 +08:00
.rules-empty {
padding: 20px;
text-align: center;
color: var(--text-dim);
font-size: 12px;
}
2026-01-17 16:34:39 +08:00
.rule-item {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
2026-01-17 16:34:39 +08:00
}
.rule-item:last-child { border-bottom: none; }
.rule-item:hover { background: rgba(255,255,255,0.02); }
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.rule-input {
2026-01-18 01:48:30 +08:00
flex: 1;
padding: 6px 10px;
min-width: 0;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-size: 12px;
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.rule-input:focus {
outline: none;
border-color: var(--border-focus);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.rule-input::placeholder { color: var(--text-dim); }
.rule-arrow {
color: var(--text-dim);
font-size: 11px;
flex-shrink: 0;
}
.rule-delete {
width: 28px;
height: 28px;
border: none;
background: transparent;
color: var(--text-dim);
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
}
.rule-delete:hover {
background: var(--error-soft);
color: var(--error);
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Current Voice Card - <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-17 16:34:39 +08:00
.current-voice-card {
2026-01-18 01:48:30 +08:00
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px;
margin-bottom: 20px;
transition: all 0.2s;
}
.current-voice-card:hover {
border-color: var(--border-light);
}
.current-voice-label {
font-size: 10px;
color: var(--text-dim);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.current-voice-display {
display: flex;
align-items: center;
gap: 14px;
}
2026-01-17 16:34:39 +08:00
.current-voice-icon {
2026-01-18 01:48:30 +08:00
width: 44px;
height: 44px;
border-radius: 50%;
background: var(--bg-elevated);
border: 1px solid var(--border);
color: var(--text-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
transition: all 0.2s;
}
.current-voice-card:hover .current-voice-icon {
color: var(--accent);
border-color: rgba(140, 200, 255, 0.2);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.current-voice-info { flex: 1; }
2026-01-18 01:48:30 +08:00
.current-voice-name {
font-size: 15px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
color: var(--text-primary);
}
.current-voice-source {
font-size: 12px;
color: var(--text-muted);
margin-top: 2px;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Source Badge - <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ڰװ<DAB0>
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.source-badge {
2026-01-18 01:48:30 +08:00
display: inline-flex;
align-items: center;
gap: 3px;
padding: 2px 7px;
border-radius: 4px;
font-size: 9px;
font-weight: 600;
letter-spacing: 0.02em;
}
2026-01-18 02:55:49 +08:00
/* <20> <> <EFBFBD> <EFBFBD> - <20> <> ɫ/dzɫ<C7B3> <C9AB> */
2026-01-18 01:48:30 +08:00
.source-badge.trial {
background: var(--tag-free-bg);
color: var(--tag-free);
border: 1px solid rgba(255, 255, 255, 0.1);
2026-01-17 16:34:39 +08:00
}
2026-01-18 02:55:49 +08:00
/* <20> <> Ȩ - <20> <> ɫ<EFBFBD> <C9AB> */
2026-01-18 01:48:30 +08:00
.source-badge.auth {
background: var(--tag-auth-bg);
color: var(--tag-auth);
border: 1px solid rgba(140, 200, 255, 0.15);
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Voice Tabs - <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.voice-tabs {
2026-01-18 01:48:30 +08:00
display: flex;
gap: 2px;
margin-bottom: 16px;
background: var(--bg-tertiary);
padding: 3px;
border-radius: 10px;
border: 1px solid var(--border);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.voice-tab {
2026-01-18 01:48:30 +08:00
flex: 1;
padding: 10px 12px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--text-muted);
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.voice-tab:hover {
color: var(--text-secondary);
}
.voice-tab.active {
background: var(--bg-elevated);
color: var(--text-primary);
font-weight: 500;
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.voice-tab-count {
2026-01-18 01:48:30 +08:00
background: var(--bg-input);
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
color: var(--text-dim);
}
.voice-tab.active .voice-tab-count {
background: var(--accent-soft);
color: var(--accent);
2026-01-17 16:34:39 +08:00
}
.voice-panel { display: none; }
.voice-panel.active { display: block; }
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Test Voice Box
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.test-voice-box {
2026-01-18 01:48:30 +08:00
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 10px;
padding: 12px;
margin-bottom: 16px;
}
.test-voice-row {
display: flex;
gap: 8px;
align-items: center;
2026-01-17 16:34:39 +08:00
}
.test-voice-row .input { flex: 1; }
2026-01-18 01:48:30 +08:00
.test-voice-status {
font-size: 11px;
color: var(--text-dim);
margin-top: 6px;
min-height: 16px;
}
.test-voice-status.playing { color: var(--accent); }
.test-voice-status.error { color: var(--error); }
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Voice Filters
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
.voice-filters {
display: flex;
gap: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
}
2026-01-17 16:34:39 +08:00
.voice-filters select {
2026-01-18 01:48:30 +08:00
flex: 1;
min-width: 80px;
padding: 8px 10px;
font-size: 12px;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-secondary);
}
.voice-filters select:focus {
outline: none;
border-color: var(--border-focus);
}
.voice-search {
display: flex;
gap: 8px;
margin-bottom: 12px;
2026-01-17 16:34:39 +08:00
}
.voice-search .input { flex: 1; }
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Voice List - <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
.voice-list {
display: flex;
flex-direction: column;
gap: 4px;
max-height: 320px;
overflow-y: auto;
}
2026-01-17 16:34:39 +08:00
.voice-item {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
transition: all 0.15s;
}
.voice-item:hover {
border-color: var(--border-light);
background: var(--bg-elevated);
}
.voice-item.selected {
border-color: var(--accent);
background: var(--accent-soft);
}
.voice-item.in-my-list {
opacity: 0.5;
}
.voice-item.disabled {
opacity: 0.4;
cursor: not-allowed;
}
.voice-item.disabled:hover {
border-color: var(--border);
background: var(--bg-tertiary);
}
2026-01-17 16:34:39 +08:00
.voice-item-radio {
2026-01-18 01:48:30 +08:00
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid var(--text-dim);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
}
.voice-item:hover .voice-item-radio {
border-color: var(--text-muted);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.voice-item.selected .voice-item-radio {
border-color: var(--accent);
background: var(--accent);
}
.voice-item.selected .voice-item-radio::after {
2026-01-18 02:55:49 +08:00
content: '?';
2026-01-18 01:48:30 +08:00
color: var(--bg-primary);
font-size: 9px;
font-weight: bold;
}
2026-01-17 16:34:39 +08:00
.voice-item-info { flex: 1; min-width: 0; }
2026-01-18 01:48:30 +08:00
.voice-item-name {
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
color: var(--text-primary);
}
.voice-item-meta {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
}
.voice-item-actions {
display: flex;
gap: 4px;
flex-shrink: 0;
}
/* Editing State */
.voice-item.editing {
background: var(--accent-soft);
border-color: var(--accent);
}
.voice-item-edit-form {
display: none;
width: 100%;
margin-top: 8px;
}
.voice-item.editing .voice-item-edit-form {
display: flex;
gap: 8px;
}
.voice-item.editing .voice-item-info > *:not(.voice-item-edit-form) {
display: none;
}
.voice-item-edit-form input {
flex: 1;
padding: 6px 10px;
font-size: 12px;
}
/* Add Form */
.voice-add-form {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--border);
}
.voice-add-row {
display: flex;
gap: 8px;
align-items: flex-end;
}
.voice-add-row .form-group {
flex: 1;
margin-bottom: 0;
}
.voice-add-row .form-label {
font-size: 11px;
margin-bottom: 4px;
}
.preset-save-row {
display: flex;
gap: 8px;
align-items: center;
margin-top: 12px;
}
.preset-save-row input {
flex: 1;
padding: 10px 12px;
font-size: 13px;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
API Status Box
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.api-status-box {
2026-01-18 01:48:30 +08:00
display: flex;
align-items: center;
gap: 12px;
padding: 14px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 10px;
2026-01-17 16:34:39 +08:00
margin-bottom: 16px;
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.api-status-icon {
2026-01-18 01:48:30 +08:00
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
2026-01-17 16:34:39 +08:00
font-size: 14px;
2026-01-18 01:48:30 +08:00
background: var(--bg-elevated);
border: 1px solid var(--border);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.api-status-box.configured .api-status-icon {
color: var(--accent);
border-color: rgba(140, 200, 255, 0.2);
}
.api-status-box.not-configured .api-status-icon {
color: var(--text-dim);
}
2026-01-17 16:34:39 +08:00
.api-status-info { flex: 1; }
2026-01-18 01:48:30 +08:00
.api-status-title {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
.api-status-desc {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Stats Card
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
.stats-card {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px;
}
.stats-group {
display: flex;
gap: 32px;
}
2026-01-17 16:34:39 +08:00
.stats-item { text-align: center; }
2026-01-18 01:48:30 +08:00
.stats-value {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
font-variant-numeric: tabular-nums;
}
.stats-label {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Tip Box - <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.tip-box {
2026-01-18 01:48:30 +08:00
display: flex;
gap: 10px;
padding: 12px 14px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
font-size: 12px;
color: var(--text-secondary);
line-height: 1.6;
}
.tip-box i {
color: var(--accent);
flex-shrink: 0;
margin-top: 2px;
opacity: 0.7;
}
.tip-box.warning {
border-left: 3px solid var(--accent);
}
.tip-box.warning i {
color: var(--accent);
2026-01-17 16:34:39 +08:00
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Guide Box
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.guide-box {
2026-01-18 01:48:30 +08:00
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px;
margin-bottom: 16px;
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.guide-box h3 {
2026-01-18 01:48:30 +08:00
font-size: 14px;
color: var(--text-primary);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.guide-box h3 i {
color: var(--accent);
opacity: 0.8;
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.guide-box p {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 10px;
line-height: 1.6;
}
.guide-box ol, .guide-box ul {
margin-left: 18px;
font-size: 13px;
color: var(--text-secondary);
}
.guide-box li {
margin-bottom: 8px;
line-height: 1.6;
}
2026-01-17 16:34:39 +08:00
.guide-box a { color: var(--accent); }
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.guide-box code {
2026-01-18 01:48:30 +08:00
background: var(--bg-input);
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
color: var(--accent);
font-family: monospace;
border: 1px solid var(--border);
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.guide-box pre {
2026-01-18 01:48:30 +08:00
background: var(--bg-primary);
padding: 12px;
border-radius: 6px;
font-size: 11px;
color: var(--text-secondary);
font-family: monospace;
overflow-x: auto;
margin: 10px 0;
line-height: 1.5;
2026-01-17 16:34:39 +08:00
border: 1px solid var(--border);
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.guide-image {
2026-01-18 01:48:30 +08:00
margin-top: 12px;
width: 100%;
height: auto;
border: 1px solid var(--border);
border-radius: 8px;
display: block;
opacity: 0.9;
2026-01-17 16:34:39 +08:00
}
2026-01-18 01:48:30 +08:00
.guide-image:hover { opacity: 1; }
2026-01-17 16:34:39 +08:00
.guide-link {
2026-01-18 01:48:30 +08:00
display: block;
padding: 10px 12px;
margin: 10px 0;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 12px;
word-break: break-all;
2026-01-17 16:34:39 +08:00
}
.guide-link a { color: var(--accent); }
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Mobile Navigation
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.mobile-nav {
display: none;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: calc(60px + var(--safe-area-bottom));
padding-bottom: var(--safe-area-bottom);
background: var(--bg-secondary);
border-top: 1px solid var(--border);
z-index: 100;
}
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.mobile-nav-inner {
display: flex;
height: 60px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding-left: var(--safe-area-left);
padding-right: var(--safe-area-right);
}
.mobile-nav-inner::-webkit-scrollbar { display: none; }
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
.mobile-nav-item {
2026-01-18 01:48:30 +08:00
flex: 1;
min-width: 60px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: var(--text-dim);
font-size: 10px;
cursor: pointer;
padding: 8px 4px;
transition: color 0.15s;
2026-01-17 16:34:39 +08:00
}
.mobile-nav-item i { font-size: 18px; }
2026-01-18 01:48:30 +08:00
.mobile-nav-item:hover { color: var(--text-muted); }
2026-01-17 16:34:39 +08:00
.mobile-nav-item.active { color: var(--accent); }
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Responsive
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
@media (max-width: 768px) {
.app-sidebar { display: none; }
.mobile-nav { display: block; }
.app-body {
padding-bottom: calc(60px + var(--safe-area-bottom));
}
.app-main {
padding: 16px;
padding-left: calc(16px + var(--safe-area-left));
padding-right: calc(16px + var(--safe-area-right));
padding-bottom: calc(80px + var(--safe-area-bottom));
}
2026-01-18 01:48:30 +08:00
.view { padding-bottom: 20px; }
2026-01-17 16:34:39 +08:00
.app-header {
padding: 10px 12px;
padding-top: calc(10px + var(--safe-area-top));
padding-left: calc(12px + var(--safe-area-left));
padding-right: calc(12px + var(--safe-area-right));
gap: 8px;
}
.header-logo span { display: none; }
.header-badge span { display: none; }
.header-close { width: 32px; height: 32px; min-width: 32px; font-size: 14px; }
.view-title { font-size: 18px; }
.card { padding: 16px; }
.form-row { grid-template-columns: 1fr; }
.voice-filters select { min-width: calc(50% - 4px); }
.stats-card { flex-direction: column; align-items: stretch; }
.stats-group { justify-content: space-around; }
.voice-add-row { flex-wrap: wrap; }
.voice-add-row .form-group { min-width: calc(50% - 4px); }
.preset-save-row { flex-wrap: wrap; }
.preset-save-row input { min-width: 100%; margin-bottom: 8px; }
.voice-tabs { flex-wrap: wrap; }
.voice-tab { min-width: calc(33% - 3px); font-size: 11px; padding: 8px 6px; }
}
@media (max-width: 400px) {
.app-header { padding: 8px 10px; }
.mobile-nav-item { min-width: 48px; font-size: 9px; }
.mobile-nav-item i { font-size: 16px; }
}
@media (hover: none) and (pointer: coarse) {
.btn { min-height: 44px; }
.input { min-height: 44px; padding: 12px; }
.nav-item { min-height: 44px; }
.header-close { width: 44px; height: 44px; min-width: 44px; }
.mobile-nav-item { min-height: 44px; }
}
2026-01-18 02:55:49 +08:00
/* <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
Scrollbar
<20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T */
2026-01-18 01:48:30 +08:00
2026-01-17 16:34:39 +08:00
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
2026-01-18 01:48:30 +08:00
::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.08);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.15);
}
2026-01-17 16:34:39 +08:00
.hidden { display: none !important; }
< / style >
< / head >
< body >
< div class = "app-container" >
< header class = "app-header" >
2026-01-18 02:55:49 +08:00
< div class = "header-logo" > < i class = "fa-solid fa-microphone" > < / i > < span > TTS <20> <> <EFBFBD> <EFBFBD> < / span > < / div >
2026-01-17 16:34:39 +08:00
< div class = "header-status" >
2026-01-18 02:55:49 +08:00
< div id = "badge_trial" class = "header-badge active" > < i class = "fa-solid fa-circle" > < / i > < span > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > < / div >
< div id = "badge_auth" class = "header-badge" > < i class = "fa-solid fa-circle" > < / i > < span > <EFBFBD> <EFBFBD> Ȩ< / span > < / div >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "header-spacer" > < / div >
2026-01-18 02:55:49 +08:00
< button id = "tts_close" class = "header-close" > ?< / button >
2026-01-17 16:34:39 +08:00
< / header >
< div class = "app-body" >
< nav class = "app-sidebar" >
2026-01-18 02:55:49 +08:00
< div class = "nav-item active" data-view = "config" > < i class = "fa-solid fa-sliders" > < / i > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
< div class = "nav-item" data-view = "voice" > < i class = "fa-solid fa-user-astronaut" > < / i > <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div class = "nav-divider" > < / div >
2026-01-18 02:55:49 +08:00
< div class = "nav-item" data-view = "advanced" > < i class = "fa-solid fa-gear" > < / i > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
< div class = "nav-item" data-view = "cache" > < i class = "fa-solid fa-database" > < / i > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div class = "nav-divider" > < / div >
2026-01-18 02:55:49 +08:00
< div class = "nav-item" data-view = "guide" > < i class = "fa-solid fa-circle-question" > < / i > ʹ <EFBFBD> <EFBFBD> ˵<EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< / nav >
< main class = "app-main" >
2026-01-18 02:55:49 +08:00
<!-- <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> -->
2026-01-17 16:34:39 +08:00
< div id = "view-config" class = "view active" >
< div class = "view-header" >
2026-01-18 02:55:49 +08:00
< h2 class = "view-title" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / h2 >
< p class = "view-desc" > TTS <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʶ<EFBFBD> <CAB6> <EFBFBD> <EFBFBD> <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "tip-box" style = "margin-bottom: 16px;" >
< i class = "fa-solid fa-info-circle" > < / i >
< div >
2026-01-18 02:55:49 +08:00
< strong > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ< / strong > <20> <> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ã<EFBFBD> ʹ <EFBFBD> ò<EFBFBD> <C3B2> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 11<31> <31> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> < br >
< strong > <EFBFBD> <EFBFBD> Ȩ<EFBFBD> <EFBFBD> ɫ< / strong > <20> <> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> ɽ<EFBFBD> <C9BD> <EFBFBD> <EFBFBD> API<50> <49> 200+ <20> <> ɫ + <20> <> <EFBFBD> ̣<EFBFBD>
< / div >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "card" >
2026-01-18 02:55:49 +08:00
< div class = "card-title" > <EFBFBD> <EFBFBD> Ȩ<EFBFBD> <EFBFBD> <EFBFBD> ã<EFBFBD> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div id = "apiStatusBox" class = "api-status-box not-configured" >
2026-01-18 01:48:30 +08:00
< div class = "api-status-icon" > < i class = "fa-solid fa-link-slash" > < / i > < / div >
2026-01-17 16:34:39 +08:00
< div class = "api-status-info" >
2026-01-18 02:55:49 +08:00
< div class = "api-status-title" > δ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
< div class = "api-status-desc" > <EFBFBD> <EFBFBD> <EFBFBD> ú<EFBFBD> <EFBFBD> <EFBFBD> ʹ <EFBFBD> <EFBFBD> Ԥ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ< / div >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< div class = "form-group" >
< label class = "form-label" > AppID< / label >
2026-01-18 02:55:49 +08:00
< input type = "text" id = "appId" class = "input" placeholder = "<22> <> ɽ<EFBFBD> <C9BD> <EFBFBD> <EFBFBD> AppID" >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "form-group" >
< label class = "form-label" > Access Token< / label >
< div class = "input-row" >
2026-01-18 02:55:49 +08:00
< input type = "password" id = "accessKey" class = "input" placeholder = "<22> <> ɽ<EFBFBD> <C9BD> <EFBFBD> <EFBFBD> Access Token" >
2026-01-17 16:34:39 +08:00
< button id = "toggleKey" class = "btn btn-icon" > < i class = "fa-solid fa-eye" > < / i > < / button >
< / div >
2026-01-18 02:55:49 +08:00
< p class = "form-hint" > <EFBFBD> <EFBFBD> ȡ<EFBFBD> <EFBFBD> ʽ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʹ <EFBFBD> <EFBFBD> ˵<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ҳ< / p >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< div class = "card" >
2026-01-18 02:55:49 +08:00
< div class = "card-title" > <EFBFBD> ʶ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div class = "checkbox-row" >
< input type = "checkbox" id = "autoSpeak" checked >
2026-01-18 02:55:49 +08:00
< label for = "autoSpeak" > AI <20> ظ<EFBFBD> <D8B8> <EFBFBD> <EFBFBD> Զ<EFBFBD> <D4B6> ʶ<EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< div class = "slider-row" >
< input type = "range" id = "speechRate" min = "0.5" max = "2.0" step = "0.1" value = "1.0" >
< span class = "slider-val" id = "speechRateValue" > 1.0x< / span >
< / div >
< / div >
< / div >
< div class = "card" >
2026-01-18 02:55:49 +08:00
< div class = "card-title" > <EFBFBD> ı <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / label >
< p class = "form-hint" style = "margin-bottom: 8px;" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʼ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֱ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʼ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɵ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ա <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
< div class = "rules-editor" >
< div class = "rules-header" >
2026-01-18 02:55:49 +08:00
< span class = "rules-header-title" > <EFBFBD> <EFBFBD> ǰ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span >
< button class = "btn btn-sm" id = "addSkipRule" > < i class = "fa-solid fa-plus" > < / i > <20> <> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "rules-list" id = "skipRulesList" > < / div >
< / div >
< / div >
< div class = "form-group" >
< div class = "checkbox-row" style = "margin-bottom: 8px;" >
< input type = "checkbox" id = "readRangesEnabled" >
2026-01-18 02:55:49 +08:00
< label for = "readRangesEnabled" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֻ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 䣨<EFBFBD> <EFBFBD> <EFBFBD> ʶ<EFBFBD> ƥ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ݣ<EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< / div >
2026-01-18 02:55:49 +08:00
< p class = "form-hint" style = "margin-bottom: 8px;" > <EFBFBD> <EFBFBD> ʼ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɵ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ա <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
< div class = "rules-editor" >
< div class = "rules-header" >
2026-01-18 02:55:49 +08:00
< span class = "rules-header-title" > ֻ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span >
< button class = "btn btn-sm" id = "addReadRule" > < i class = "fa-solid fa-plus" > < / i > <20> <> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "rules-list" id = "readRulesList" > < / div >
< / div >
< / div >
< / div >
2026-01-18 02:55:49 +08:00
< button id = "saveConfigBtn" class = "btn btn-primary" > < i class = "fa-solid fa-floppy-disk" > < / i > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
2026-01-18 02:55:49 +08:00
<!-- <20> <> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> -->
2026-01-17 16:34:39 +08:00
< div id = "view-voice" class = "view" >
< div class = "view-header" >
2026-01-18 02:55:49 +08:00
< h2 class = "view-title" > <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / h2 >
< p class = "view-desc" > <EFBFBD> <EFBFBD> ϲ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 롾<EFBFBD> ҵ<EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "current-voice-card" id = "currentVoiceCard" >
2026-01-18 02:55:49 +08:00
< div class = "current-voice-label" > <EFBFBD> <EFBFBD> ǰĬ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ< / div >
2026-01-17 16:34:39 +08:00
< div class = "current-voice-display" >
< div class = "current-voice-icon" > < i class = "fa-solid fa-microphone-lines" > < / i > < / div >
< div class = "current-voice-info" >
2026-01-18 02:55:49 +08:00
< div class = "current-voice-name" id = "currentVoiceName" > δѡ <EFBFBD> <EFBFBD> < / div >
< div class = "current-voice-source" id = "currentVoiceSource" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ·<EFBFBD> ѡ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ< / div >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< / div >
< div class = "voice-tabs" >
< button class = "voice-tab active" data-panel = "myVoice" >
2026-01-18 02:55:49 +08:00
< i class = "fa-solid fa-star" > < / i > <20> ҵ<EFBFBD>
2026-01-17 16:34:39 +08:00
< span class = "voice-tab-count" id = "myVoiceCount" > 0< / span >
< / button >
< button class = "voice-tab" data-panel = "trialVoice" >
2026-01-18 02:55:49 +08:00
< i class = "fa-solid fa-flask" > < / i > <20> <> <EFBFBD> <EFBFBD>
2026-01-17 16:34:39 +08:00
< / button >
< button class = "voice-tab" data-panel = "authVoice" >
2026-01-18 02:55:49 +08:00
< i class = "fa-solid fa-list" > < / i > Ԥ<> <D4A4> <EFBFBD> <EFBFBD>
< / button >
2026-01-17 16:34:39 +08:00
< / div >
2026-01-18 02:55:49 +08:00
<!-- <20> ҵ<EFBFBD> <D2B5> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> -->
2026-01-17 16:34:39 +08:00
< div class = "voice-panel active" id = "panel-myVoice" >
< div class = "card" >
< div class = "test-voice-box" >
< div class = "test-voice-row" >
2026-01-18 02:55:49 +08:00
< input type = "text" id = "testTextMy" class = "input" value = "♪ <20> <> <EFBFBD> <EFBFBD> <EFBFBD> 뵽<EFBFBD> <EBB5BD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ~♪" placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ı <EFBFBD> " >
< button class = "btn btn-primary" id = "testMyVoiceBtn" > < i class = "fa-solid fa-play" > < / i > <20> <> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "test-voice-status" id = "testMyStatus" > < / div >
< / div >
< p class = "form-hint" style = "margin-bottom: 12px;" >
2026-01-18 02:55:49 +08:00
<20> <> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <D1A1> <EFBFBD> <EFBFBD> ΪĬ<CEAA> ϡ<EFBFBD> < span class = "source-badge trial" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ã<EFBFBD> < span class = "source-badge auth" > <EFBFBD> <EFBFBD> Ȩ< / span > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> API
2026-01-17 16:34:39 +08:00
< / p >
< div class = "voice-list" id = "myVoiceList" > < / div >
< div id = "myVoiceEmpty" class = "rules-empty" >
2026-01-18 01:48:30 +08:00
< i class = "fa-solid fa-inbox" style = "font-size: 24px; margin-bottom: 8px; display: block; opacity: 0.5;" > < / i >
2026-01-18 02:55:49 +08:00
<20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> <EFBFBD> ӡ<EFBFBD> <D3A1> <EFBFBD> <EFBFBD> á<EFBFBD> <C3A1> <EFBFBD> <EFBFBD> <EFBFBD> Ԥ<EFBFBD> <D4A4> <EFBFBD> ⡹<EFBFBD> <E2A1B9> <EFBFBD> <EFBFBD>
< / div >
2026-01-17 16:34:39 +08:00
< div class = "voice-add-form" >
2026-01-18 02:55:49 +08:00
< div class = "form-label" style = "margin-bottom: 8px;" > <EFBFBD> ֶ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ӹ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ < span class = "source-badge auth" > <EFBFBD> <EFBFBD> Ȩ< / span > < / div >
2026-01-17 16:34:39 +08:00
< div class = "voice-add-row" >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> ɫ ID< / label >
< input type = "text" id = "newVoiceId" class = "input" placeholder = "<22> <> S_xxx" >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / label >
< input type = "text" id = "newVoiceName" class = "input" placeholder = "<22> <> ʾ <EFBFBD> <CABE> <EFBFBD> <EFBFBD> " >
2026-01-17 16:34:39 +08:00
< / div >
< button class = "btn btn-primary" id = "addMySpeakerBtn" style = "margin-top: 18px;" > < i class = "fa-solid fa-plus" > < / i > < / button >
< / div >
< / div >
< / div >
< / div >
2026-01-18 02:55:49 +08:00
<!-- <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> -->
2026-01-17 16:34:39 +08:00
< div class = "voice-panel" id = "panel-trialVoice" >
< div class = "card" >
< div class = "test-voice-box" >
< div class = "test-voice-row" >
2026-01-18 02:55:49 +08:00
< input type = "text" id = "testTextTrial" class = "input" value = "<22> <> ~♪ף<> <D7A3> <EFBFBD> <EFBFBD> <EFBFBD> տ<EFBFBD> <D5BF> <EFBFBD> ~" placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ı <EFBFBD> " >
< button class = "btn btn-primary" id = "testTrialVoiceBtn" > < i class = "fa-solid fa-play" > < / i > <20> <> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "test-voice-status" id = "testTrialStatus" > < / div >
< / div >
< div class = "voice-list" id = "trialVoiceList" > < / div >
< div class = "preset-save-row" >
2026-01-18 02:55:49 +08:00
< input type = "text" id = "saveAsNameTrial" class = "input" placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ƣ<EFBFBD> <C6A3> <EFBFBD> ѡ <EFBFBD> <D1A1> " >
< button class = "btn btn-primary" id = "saveToMyVoiceTrialBtn" > < i class = "fa-solid fa-plus" > < / i > <20> <> <EFBFBD> ӵ<EFBFBD> <D3B5> ҵ<EFBFBD> <D2B5> <EFBFBD> ɫ< / button >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< / div >
2026-01-18 02:55:49 +08:00
<!-- Ԥ<> <D4A4> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> -->
2026-01-17 16:34:39 +08:00
< div class = "voice-panel" id = "panel-authVoice" >
< div class = "card" >
< div id = "authVoiceNotice" class = "tip-box warning" style = "margin-bottom: 16px;" >
< i class = "fa-solid fa-exclamation-triangle" > < / i >
2026-01-18 02:55:49 +08:00
< div > ʹ <EFBFBD> <EFBFBD> Ԥ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ҫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ü<EFBFBD> Ȩ API<50> <49> <EFBFBD> <EFBFBD> ǰ<EFBFBD> <C7B0> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> á<EFBFBD> ҳ<EFBFBD> <D2B3> <EFBFBD> <EFBFBD> <EFBFBD> á<EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "test-voice-box" >
< div class = "test-voice-row" >
2026-01-18 02:55:49 +08:00
< input type = "text" id = "testTextAuth" class = "input" value = "Ԥ<> <D4A4> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֻ<EFBFBD> ϻ<EFBFBD> <CFBB> <EFBFBD> <EFBFBD> <EFBFBD> ֻ<EFBFBD> ϻ<EFBFBD> ..." placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ı <EFBFBD> " >
< button class = "btn btn-primary" id = "testAuthVoiceBtn" > < i class = "fa-solid fa-play" > < / i > <20> <> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "test-voice-status" id = "testAuthStatus" > < / div >
< / div >
< div class = "voice-search" >
2026-01-18 02:55:49 +08:00
< input type = "text" id = "voiceSearchInput" class = "input" placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> ..." >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "voice-filters" >
< select id = "voiceGenderFilter" >
2026-01-18 02:55:49 +08:00
< option value = "all" > ȫ<EFBFBD> <EFBFBD> <EFBFBD> Ա<EFBFBD> < / option >
< option value = "female" > Ů<EFBFBD> <EFBFBD> < / option >
< option value = "male" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option >
< option value = "other" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option >
2026-01-17 16:34:39 +08:00
< / select >
< select id = "voiceModelFilter" >
2026-01-18 02:55:49 +08:00
< option value = "all" > ȫ<EFBFBD> <EFBFBD> ģ<EFBFBD> <EFBFBD> < / option >
2026-01-17 16:34:39 +08:00
< option value = "tts2" > 2.0< / option >
< option value = "tts1" > 1.0< / option >
< / select >
< select id = "voiceLangFilter" >
2026-01-18 02:55:49 +08:00
< option value = "all" > ȫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option >
< option value = "zh" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option >
< option value = "en" > Ӣ<EFBFBD> <EFBFBD> < / option >
< option value = "other" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option >
< option value = "multi" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option >
2026-01-17 16:34:39 +08:00
< / select >
< select id = "voiceSceneFilter" >
2026-01-18 02:55:49 +08:00
< option value = "all" > ȫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option >
2026-01-17 16:34:39 +08:00
< / select >
< / div >
< div class = "voice-list" id = "authVoiceList" > < / div >
< div class = "preset-save-row" >
2026-01-18 02:55:49 +08:00
< input type = "text" id = "saveAsNameAuth" class = "input" placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ƣ<EFBFBD> <C6A3> <EFBFBD> ѡ <EFBFBD> <D1A1> " >
< button class = "btn btn-primary" id = "saveToMyVoiceAuthBtn" > < i class = "fa-solid fa-plus" > < / i > <20> <> <EFBFBD> ӵ<EFBFBD> <D3B5> ҵ<EFBFBD> <D2B5> <EFBFBD> ɫ< / button >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< / div >
2026-01-18 02:55:49 +08:00
< button class = "btn btn-primary" id = "saveVoiceBtn" style = "margin-top: 8px;" > < i class = "fa-solid fa-floppy-disk" > < / i > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
2026-01-18 02:55:49 +08:00
<!-- <20> <EFBFBD> <DFBC> <EFBFBD> <EFBFBD> <EFBFBD> -->
2026-01-17 16:34:39 +08:00
< div id = "view-advanced" class = "view" >
< div class = "view-header" >
2026-01-18 02:55:49 +08:00
< h2 class = "view-title" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / h2 >
< p class = "view-desc" > <EFBFBD> Ʒ ѡ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <EFBFBD> <EFBFBD> Ȩģʽ <EFBFBD> <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "card" >
2026-01-18 02:55:49 +08:00
< div class = "card-title" > <EFBFBD> Ʒ <EFBFBD> <EFBFBD> 뻺<EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div class = "checkbox-row" >
< input type = "checkbox" id = "usageReturn" >
2026-01-18 02:55:49 +08:00
< label for = "usageReturn" > <EFBFBD> <EFBFBD> <EFBFBD> ؼƷ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> text_words<EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "checkbox-row" >
< input type = "checkbox" id = "serverCacheEnabled" >
2026-01-18 02:55:49 +08:00
< label for = "serverCacheEnabled" > <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> ɽ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ˻<EFBFBD> <EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< div class = "card" >
2026-01-18 02:55:49 +08:00
< div class = "card-title" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʶ<EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div class = "checkbox-row" >
< input type = "checkbox" id = "disableMarkdownFilter" >
2026-01-18 02:55:49 +08:00
< label for = "disableMarkdownFilter" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Markdown <20> <> <EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "checkbox-row" >
< input type = "checkbox" id = "useTts11" checked >
2026-01-18 02:55:49 +08:00
< label for = "useTts11" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 1.1 ģ<> ͣ<EFBFBD> <CDA3> <EFBFBD> <EFBFBD> <EFBFBD> seed-tts-1.0 <20> <> Ч<EFBFBD> <D0A7> < / label >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "checkbox-row" >
< input type = "checkbox" id = "disableEmojiFilter" >
2026-01-18 02:55:49 +08:00
< label for = "disableEmojiFilter" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Emoji< / label >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "checkbox-row" >
< input type = "checkbox" id = "enableLanguageDetector" >
2026-01-18 02:55:49 +08:00
< label for = "enableLanguageDetector" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Զ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʶ<EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "form-group" style = "margin-top: 12px;" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > ָ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / label >
< input type = "text" id = "explicitLanguage" class = "input" placeholder = "<22> 磺zh-cn / en / crosslingual" >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "form-row" style = "margin-top: 12px;" >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> Ź<EFBFBD> <EFBFBD> ˳<EFBFBD> <EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< input type = "number" id = "maxLengthToFilterParenthesis" class = "input" min = "0" max = "500" >
< / div >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> ߵ <EFBFBD> <EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< input type = "number" id = "postProcessPitch" class = "input" min = "-12" max = "12" >
< / div >
< / div >
< / div >
2026-01-18 02:55:49 +08:00
< button class = "btn btn-primary" id = "saveAdvancedBtn" > < i class = "fa-solid fa-floppy-disk" > < / i > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <DFBC> <EFBFBD> <EFBFBD> <EFBFBD> < / button >
2026-01-17 16:34:39 +08:00
< / div >
2026-01-18 02:55:49 +08:00
<!-- <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> -->
2026-01-17 16:34:39 +08:00
< div id = "view-cache" class = "view" >
< div class = "view-header" >
2026-01-18 02:55:49 +08:00
< h2 class = "view-title" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / h2 >
< p class = "view-desc" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ƶ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͳ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "card" >
< div class = "stats-card" >
< div class = "stats-group" >
< div class = "stats-item" >
< div class = "stats-value" id = "cacheCount" > 0< / div >
2026-01-18 02:55:49 +08:00
< div class = "stats-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "stats-item" >
< div class = "stats-value" id = "cacheSize" > 0 MB< / div >
2026-01-18 02:55:49 +08:00
< div class = "stats-label" > ռ <EFBFBD> ÿռ <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "stats-item" >
< div class = "stats-value" id = "cacheHits" > 0< / div >
2026-01-18 02:55:49 +08:00
< div class = "stats-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "stats-item" >
< div class = "stats-value" id = "cacheMisses" > 0< / div >
2026-01-18 02:55:49 +08:00
< div class = "stats-label" > δ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< / div >
< / div >
< div class = "card" >
2026-01-18 02:55:49 +08:00
< div class = "card-title" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< div class = "form-row" >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< input type = "number" id = "cacheDays" class = "input" min = "1" max = "30" >
< / div >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / label >
2026-01-17 16:34:39 +08:00
< input type = "number" id = "cacheMaxEntries" class = "input" min = "10" max = "5000" >
< / div >
< div class = "form-group" >
2026-01-18 02:55:49 +08:00
< label class = "form-label" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> (MB)< / label >
2026-01-17 16:34:39 +08:00
< input type = "number" id = "cacheMaxMB" class = "input" min = "10" max = "5000" >
< / div >
< / div >
< / div >
< div class = "btn-group" >
2026-01-18 02:55:49 +08:00
< button class = "btn btn-primary" id = "saveCacheBtn" > < i class = "fa-solid fa-floppy-disk" > < / i > <20> <> <EFBFBD> <EFBFBD> < / button >
< button class = "btn" id = "cacheRefreshBtn" > < i class = "fa-solid fa-arrows-rotate" > < / i > ˢ<> <CBA2> < / button >
< button class = "btn" id = "cacheClearExpiredBtn" > < i class = "fa-solid fa-broom" > < / i > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / button >
< button class = "btn btn-danger" id = "cacheClearAllBtn" > < i class = "fa-solid fa-trash" > < / i > <20> <> <EFBFBD> <EFBFBD> ȫ<EFBFBD> <C8AB> < / button >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
2026-01-18 02:55:49 +08:00
<!-- ʹ <> <CAB9> ˵<EFBFBD> <CBB5> -->
2026-01-17 16:34:39 +08:00
< div id = "view-guide" class = "view" >
< div class = "view-header" >
2026-01-18 02:55:49 +08:00
< h2 class = "view-title" > ʹ <EFBFBD> <EFBFBD> ˵<EFBFBD> <EFBFBD> < / h2 >
< p class = "view-desc" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ָ<EFBFBD> <EFBFBD> <EFBFBD> 뿪ͨ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "guide-box" >
2026-01-18 02:55:49 +08:00
< h3 > < i class = "fa-solid fa-terminal" > < / i > <20> <> <EFBFBD> <EFBFBD> ָ<EFBFBD> <D6B8> < / h3 >
< p > <EFBFBD> <EFBFBD> ʽ <EFBFBD> <EFBFBD> < code > [tts:speaker=<3D> <> ɫ<EFBFBD> <C9AB> ;emotion=<3D> <> <EFBFBD> <EFBFBD> ;context=<3D> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʾ ]< / code > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ǰһ <C7B0> <D2BB> < / p >
< p > speaker<EFBFBD> <EFBFBD> emotion<EFBFBD> <EFBFBD> context <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ϡ<EFBFBD> <CFA1> <EFBFBD> <EFBFBD> <EFBFBD> ˳<EFBFBD> <CBB3> <EFBFBD> <EFBFBD> <EFBFBD> ÷ֺŷָ<C5B7> < / p >
< p > ÿ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> һ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < code > [tts:...]< / code > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ֶ<EFBFBD> <D6B6> ʶ<EFBFBD> <CAB6> <EFBFBD> <EFBFBD> <EFBFBD> ˳<EFBFBD> <EFBFBD> < / p >
< p > δд < code > speaker=< / code > <20> Ŀ<EFBFBD> ʹ <EFBFBD> õ<EFBFBD> ǰѡ <C7B0> е <EFBFBD> Ĭ<EFBFBD> <C4AC> <EFBFBD> <EFBFBD> ɫ< / p >
2026-01-17 16:34:39 +08:00
2026-01-18 02:55:49 +08:00
< p style = "margin-top: 12px;" > < strong > <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> speaker<EFBFBD> <EFBFBD> < / strong > < / p >
< p > ֻ<EFBFBD> <EFBFBD> ָ<EFBFBD> <EFBFBD> "<22> ҵ<EFBFBD> <D2B5> <EFBFBD> ɫ"<22> б <EFBFBD> <D0B1> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ơ<EFBFBD> <C6A1> <EFBFBD> <EFBFBD> 籣<EFBFBD> <E7B1A3> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ϊ"С <> <D0A1> "<22> <> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < code > speaker=С <> <D0A1> < / code > <EFBFBD> <EFBFBD> < / p >
2026-01-17 16:34:39 +08:00
2026-01-18 02:55:49 +08:00
< p style = "margin-top: 12px;" > < strong > <EFBFBD> <EFBFBD> <EFBFBD> У <EFBFBD> emotion<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ֵ<EFBFBD> <EFBFBD> < / strong > < / p >
< pre > <EFBFBD> <EFBFBD> <EFBFBD> ģ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ġ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ˡ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ȡ<EFBFBD> <EFBFBD> ־塢<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Į<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ԡ<EFBFBD> <EFBFBD> <EFBFBD> ɥ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ߡ<EFBFBD> <EFBFBD> <EFBFBD> ο <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ᡢ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ¡<EFBFBD> <EFBFBD> <EFBFBD> Ȼ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> е <EFBFBD> ̨<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ԡ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ӫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ų<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ְ<EFBFBD> <EFBFBD> ԡ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ԡ<EFBFBD> <EFBFBD> Ի<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ġ<EFBFBD> <EFBFBD> <EFBFBD> ů<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 顢Ȩ<EFBFBD> <EFBFBD>
Ӣ<EFBFBD> ģ<EFBFBD> happy, sad, angry, surprised, fear, hate, excited, coldness, neutral, depressed, lovey-dovey, shy, comfort, tension, tender, storytelling, radio, magnetic, advertising, vocal-fry, asmr, news, entertainment, dialect, chat, warm, affectionate, authoritative< / pre >
2026-01-17 16:34:39 +08:00
2026-01-18 02:55:49 +08:00
< p style = "margin-top: 12px;" > < strong > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʾ <EFBFBD> <EFBFBD> context<EFBFBD> <EFBFBD> < / strong > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> seed-tts-2.0 <20> <> Ч<EFBFBD> <D0A7> < / p >
< p > <EFBFBD> <EFBFBD> <EFBFBD> 磺"<22> ø<EFBFBD> ί<EFBFBD> <CEAF> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> "<22> <> "<22> <> <EFBFBD> <EFBFBD> һ <EFBFBD> 㣬ѹ<E3A3AC> <D1B9> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> "< / p >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "guide-box" >
2026-01-18 02:55:49 +08:00
< h3 > < i class = "fa-solid fa-user-plus" > < / i > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫʹ <C9AB> <CAB9> < / h3 >
2026-01-17 16:34:39 +08:00
< ol >
2026-01-18 02:55:49 +08:00
< li > <EFBFBD> ڻ<EFBFBD> ɽ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ< / li >
< li > <EFBFBD> <EFBFBD> ȡ<EFBFBD> <EFBFBD> ɫID<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʽ < code > S_xxxxxxxx< / code > <EFBFBD> <EFBFBD> < / li >
< li > <EFBFBD> <EFBFBD> "<22> <> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> " <20> <> "<22> ҵ<EFBFBD> <D2B5> <EFBFBD> ɫ"<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / li >
2026-01-17 16:34:39 +08:00
< / ol >
< / div >
< div class = "tip-box warning" style = "margin-bottom: 16px;" >
< i class = "fa-solid fa-exclamation-triangle" > < / i >
2026-01-18 02:55:49 +08:00
< div > < strong > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ǽ<EFBFBD> Ȩģʽ <EFBFBD> Ŀ<EFBFBD> ͨ<EFBFBD> ̳<EFBFBD> < / strong > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> á<EFBFBD> < / div >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "guide-box" >
2026-01-18 02:55:49 +08:00
< h3 > < i class = "fa-solid fa-server" > < / i > <20> <> <EFBFBD> <EFBFBD> CORS <20> <> <EFBFBD> <EFBFBD> < / h3 >
2026-01-17 16:34:39 +08:00
< ol >
2026-01-18 02:55:49 +08:00
< li > <EFBFBD> ƹ<EFBFBD> Ŀ¼<EFBFBD> <EFBFBD> config.yaml< / li >
< li > <EFBFBD> <EFBFBD> enableCorsProxy <20> <> Ϊ true <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / li >
< li > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ƹݣ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> /<2F> <> <EFBFBD> ̣<EFBFBD> <CCA3> <EFBFBD> <EFBFBD> <EFBFBD> F5 ˢ<> £<EFBFBD> < / li >
2026-01-17 16:34:39 +08:00
< / ol >
< / div >
< div class = "guide-box" >
2026-01-18 02:55:49 +08:00
< h3 > < i class = "fa-solid fa-check-circle" > < / i > <20> <> ͨ<EFBFBD> <CDA8> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ƽ <EFBFBD> һ <EFBFBD> <D2BB> <EFBFBD> Կ<EFBFBD> ͨȫ<CDA8> <C8AB> <EFBFBD> <EFBFBD> < / h3 >
2026-01-17 16:34:39 +08:00
< div class = "guide-link" >
< a href = "https://console.volcengine.com/speech/new/setting/activate" target = "_blank" > https://console.volcengine.com/speech/new/setting/activate< / a >
< / div >
2026-01-18 02:55:49 +08:00
< img class = "guide-image" src = "<22> <> ͨ<EFBFBD> <CDA8> <EFBFBD> <EFBFBD> .png" alt = "<22> <> ͨ<EFBFBD> <CDA8> <EFBFBD> <EFBFBD> " >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "guide-box" >
2026-01-18 02:55:49 +08:00
< h3 > < i class = "fa-solid fa-key" > < / i > <20> <> ȡ Access Token / AppID< / h3 >
2026-01-17 16:34:39 +08:00
< div class = "guide-link" >
< a href = "https://console.volcengine.com/speech/service/8" target = "_blank" > https://console.volcengine.com/speech/service/8< / a >
< / div >
2026-01-18 02:55:49 +08:00
< img class = "guide-image" src = "<22> <> ȡID<49> <44> KEY.png" alt = "<22> <> ȡID<49> <44> KEY" >
2026-01-17 16:34:39 +08:00
< / div >
< div class = "guide-box" >
2026-01-18 02:55:49 +08:00
< h3 > < i class = "fa-solid fa-microphone-lines" > < / i > <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> (<28> <> <EFBFBD> ̺<EFBFBD> ȥ<EFBFBD> <C8A5> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> ID)< / h3 >
2026-01-17 16:34:39 +08:00
< div class = "guide-link" >
< a href = "https://console.volcengine.com/speech/new/experience/clone" target = "_blank" > https://console.volcengine.com/speech/new/experience/clone< / a >
< / div >
2026-01-18 02:55:49 +08:00
< img class = "guide-image" src = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> .png" alt = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> " >
2026-01-17 16:34:39 +08:00
< / div >
< / div >
< / main >
< / div >
< nav class = "mobile-nav" >
< div class = "mobile-nav-inner" >
2026-01-18 02:55:49 +08:00
< div class = "mobile-nav-item active" data-view = "config" > < i class = "fa-solid fa-sliders" > < / i > < span > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > < / div >
< div class = "mobile-nav-item" data-view = "voice" > < i class = "fa-solid fa-user-astronaut" > < / i > < span > <EFBFBD> <EFBFBD> ɫ< / span > < / div >
< div class = "mobile-nav-item" data-view = "advanced" > < i class = "fa-solid fa-gear" > < / i > < span > <EFBFBD> <EFBFBD> < / span > < / div >
< div class = "mobile-nav-item" data-view = "cache" > < i class = "fa-solid fa-database" > < / i > < span > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > < / div >
< div class = "mobile-nav-item" data-view = "guide" > < i class = "fa-solid fa-circle-question" > < / i > < span > ˵<EFBFBD> <EFBFBD> < / span > < / div >
2026-01-17 16:34:39 +08:00
< / div >
< / nav >
< / div >
< script src = "tts-voices.js" > < / script >
< script >
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ״̬
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
const PARENT_ORIGIN = (() => {
try { return new URL(document.referrer).origin; } catch { return window.location.origin; }
})();
const post = (type, payload) => parent.postMessage({ type, payload }, PARENT_ORIGIN);
let config = {};
let mySpeakers = [];
let selectedVoiceValue = '';
let selectedTrialVoiceValue = '';
let selectedAuthVoiceValue = '';
let editingVoiceValue = null;
let activeSaveBtn = null;
const TRIAL_VOICES = [
2026-01-18 02:55:49 +08:00
{ key: 'female_1', name: '<27> <> ز', tag: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ', gender: 'female' },
{ key: 'female_2', name: '˪<> <CBAA> ', tag: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ', gender: 'female' },
{ key: 'female_3', name: '<27> ˽<EFBFBD> ', tag: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɤ', gender: 'female' },
{ key: 'female_4', name: '<27> շ<EFBFBD> ', tag: '<27> <> <EFBFBD> <EFBFBD> ֪<EFBFBD> <D6AA> ', gender: 'female' },
{ key: 'female_5', name: '<27> <> <EFBFBD> <EFBFBD> ', tag: '<27> ۷ <EFBFBD> <DBB7> <EFBFBD> <EFBFBD> <EFBFBD> ', gender: 'female' },
{ key: 'female_6', name: '<27> <> ÷', tag: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ', gender: 'female' },
{ key: 'female_7', name: '<27> <> <EFBFBD> <EFBFBD> ', tag: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ', gender: 'female' },
{ key: 'male_1', name: 'ҹ<> <D2B9> ', tag: '<27> <> <EFBFBD> Ե<EFBFBD> <D4B5> <EFBFBD> ', gender: 'male' },
{ key: 'male_2', name: '<27> <> <EFBFBD> <EFBFBD> ', tag: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ', gender: 'male' },
{ key: 'male_3', name: '<27> <> <EFBFBD> <EFBFBD> ', tag: '<27> <> <EFBFBD> <EFBFBD> ů<EFBFBD> <C5AF> ', gender: 'male' },
{ key: 'male_4', name: '<27> <> <EFBFBD> <EFBFBD> ', tag: '<27> ഺ<EFBFBD> <E0B4BA> <EFBFBD> <EFBFBD> ', gender: 'male' },
2026-01-17 16:34:39 +08:00
];
const TRIAL_VOICE_KEYS = new Set(TRIAL_VOICES.map(v => v.key));
const AUTH_VOICE_DATA = Array.isArray(window.XB_TTS_VOICE_DATA) ? window.XB_TTS_VOICE_DATA : [];
const TTS2_VOICES = new Set((window.XB_TTS_TTS2_VOICE_INFO || []).map(item => item.value));
let authVoiceList = [];
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> <EFBFBD> ߺ <EFBFBD> <DFBA> <EFBFBD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
const $ = id => document.getElementById(id);
const $$ = sel => document.querySelectorAll(sel);
function escapeHtml(str) {
return String(str || '').replace(/& /g, '& ').replace(/< /g, '< ').replace(/>/g, '> ').replace(/"/g, '" ');
}
function switchView(viewId) {
$$('.view').forEach(v => v.classList.remove('active'));
$$('.nav-item, .mobile-nav-item').forEach(n => n.classList.remove('active'));
$(`view-${viewId}`)?.classList.add('active');
$$(`[data-view="${viewId}"]`).forEach(n => n.classList.add('active'));
}
function setSavingState(btn) {
if (!btn) return;
activeSaveBtn = btn;
const i = btn.querySelector('i');
if (i) { btn._origIcon = i.className; i.className = 'fa-solid fa-spinner fa-spin'; }
btn.classList.add('saving');
}
function handleSaveResult(success) {
if (!activeSaveBtn) return;
const btn = activeSaveBtn;
activeSaveBtn = null;
btn.classList.remove('saving');
const i = btn.querySelector('i');
if (success & & i) {
i.className = 'fa-solid fa-check';
btn.classList.add('save-success');
setTimeout(() => { btn.classList.remove('save-success'); i.className = btn._origIcon || 'fa-solid fa-floppy-disk'; }, 1500);
} else if (i) {
i.className = btn._origIcon || 'fa-solid fa-floppy-disk';
}
}
function setTestStatus(elId, status, text) {
const el = $(elId);
if (!el) return;
el.textContent = text;
el.className = 'test-voice-status' + (status ? ' ' + status : '');
}
function getVoiceSource(value) {
if (!value) return 'free';
if (TRIAL_VOICE_KEYS.has(value)) return 'free';
return 'auth';
}
function isAuthConfigured() {
return !!(config?.volc?.appId & & config?.volc?.accessKey);
}
function isInMyList(value) {
return mySpeakers.some(s => s.value === value);
}
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ༭<EFBFBD> <E0BCAD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
function renderRulesList(listEl, rules, type) {
if (!rules?.length) {
2026-01-18 02:55:49 +08:00
listEl.innerHTML = `< div class = "rules-empty" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / div > `;
2026-01-17 16:34:39 +08:00
return;
}
listEl.innerHTML = rules.map((rule, idx) => `
< div class = "rule-item" data-idx = "${idx}" >
2026-01-18 02:55:49 +08:00
< input type = "text" class = "rule-input rule-start" value = "${escapeHtml(rule.start || '')}" placeholder = "<22> <> ʼ <EFBFBD> <CABC> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> գ <EFBFBD> " >
< span class = "rule-arrow" > <EFBFBD> <EFBFBD> < / span >
< input type = "text" class = "rule-input rule-end" value = "${escapeHtml(rule.end || '')}" placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> գ <EFBFBD> " >
2026-01-17 16:34:39 +08:00
< button class = "rule-delete" data-type = "${type}" data-idx = "${idx}" > < i class = "fa-solid fa-times" > < / i > < / button >
< / div >
`).join('');
}
function collectRules(listEl) {
const rules = [];
listEl.querySelectorAll('.rule-item').forEach(item => {
const start = item.querySelector('.rule-start')?.value?.trim() || '';
const end = item.querySelector('.rule-end')?.value?.trim() || '';
if (start || end) rules.push({ start, end });
});
return rules;
}
function initRulesEditors() {
$('addSkipRule').addEventListener('click', () => {
const rules = collectRules($('skipRulesList'));
rules.push({ start: '', end: '' });
renderRulesList($('skipRulesList'), rules, 'skip');
});
$('addReadRule').addEventListener('click', () => {
const rules = collectRules($('readRulesList'));
rules.push({ start: '', end: '' });
renderRulesList($('readRulesList'), rules, 'read');
});
document.addEventListener('click', e => {
const deleteBtn = e.target.closest('.rule-delete');
if (!deleteBtn) return;
const type = deleteBtn.dataset.type;
const idx = parseInt(deleteBtn.dataset.idx);
const listEl = type === 'skip' ? $('skipRulesList') : $('readRulesList');
const rules = collectRules(listEl);
rules.splice(idx, 1);
renderRulesList(listEl, rules, type);
});
}
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
function detectGenderByValue(value) {
const v = String(value || '').toLowerCase();
if (v.includes('female')) return 'female';
if (v.includes('male')) return 'male';
return 'other';
}
function detectModel(value) { return TTS2_VOICES.has(String(value).trim()) ? 'tts2' : 'tts1'; }
function detectTags(value, model) {
const tags = [];
if (model === 'tts2') tags.push('2.0');
if (model === 'tts1') tags.push('1.0');
2026-01-18 02:55:49 +08:00
if (String(value).includes('emo')) tags.push('<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ');
2026-01-17 16:34:39 +08:00
return tags;
}
function buildAuthVoiceList() {
authVoiceList = AUTH_VOICE_DATA.map(item => {
const value = item.value;
const name = item.name || value;
const gender = detectGenderByValue(value);
2026-01-18 02:55:49 +08:00
const genderLabel = gender === 'female' ? 'Ů' : gender === 'male' ? '<27> <> ' : '<27> <> <EFBFBD> <EFBFBD> ';
2026-01-17 16:34:39 +08:00
const model = detectModel(value);
const scene = String(item.scene || '').trim();
const tags = detectTags(value, model);
if (scene & & !tags.includes(scene)) tags.push(scene);
2026-01-18 01:48:30 +08:00
let language;
if (model === 'tts2') {
language = 'multi';
} else if (value.startsWith('en_')) {
language = 'en';
} else if (value.startsWith('multi_')) {
language = 'other';
} else {
language = 'zh';
}
2026-01-17 16:34:39 +08:00
return { value, name, gender, genderLabel, model, language, scene, tags };
});
}
function formatTagLabel(tags) { return tags.length ? ` [${tags.join('/')}]` : ''; }
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// UI <20> <> <EFBFBD> <EFBFBD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
function updateApiStatus() {
const configured = isAuthConfigured();
const box = $('apiStatusBox');
const icon = box.querySelector('.api-status-icon i');
const title = box.querySelector('.api-status-title');
const desc = box.querySelector('.api-status-desc');
const badge = $('badge_auth');
if (configured) {
box.className = 'api-status-box configured';
2026-01-18 01:48:30 +08:00
icon.className = 'fa-solid fa-link';
2026-01-18 02:55:49 +08:00
title.textContent = '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ';
desc.textContent = '<27> <> ʹ <EFBFBD> <CAB9> Ԥ<EFBFBD> <D4A4> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> <CDB8> <EFBFBD> <EFBFBD> <EFBFBD> ɫ';
2026-01-18 01:48:30 +08:00
badge.className = 'header-badge active';
2026-01-17 16:34:39 +08:00
$('authVoiceNotice').style.display = 'none';
} else {
box.className = 'api-status-box not-configured';
2026-01-18 01:48:30 +08:00
icon.className = 'fa-solid fa-link-slash';
2026-01-18 02:55:49 +08:00
title.textContent = 'δ<> <CEB4> <EFBFBD> <EFBFBD> ';
desc.textContent = '<27> <> <EFBFBD> ú<EFBFBD> <C3BA> <EFBFBD> ʹ <EFBFBD> <CAB9> Ԥ<EFBFBD> <D4A4> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> <CDB8> <EFBFBD> <EFBFBD> <EFBFBD> ɫ';
2026-01-17 16:34:39 +08:00
badge.className = 'header-badge';
$('authVoiceNotice').style.display = 'flex';
}
}
function updateCurrentVoiceDisplay() {
const nameEl = $('currentVoiceName');
const sourceEl = $('currentVoiceSource');
if (!selectedVoiceValue) {
2026-01-18 02:55:49 +08:00
nameEl.innerHTML = 'δѡ <CEB4> <D1A1> ';
sourceEl.textContent = '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> ·<EFBFBD> ѡ <EFBFBD> <D1A1> <EFBFBD> <EFBFBD> ɫ';
2026-01-17 16:34:39 +08:00
return;
}
const myVoice = mySpeakers.find(s => s.value === selectedVoiceValue);
const source = myVoice?.source || getVoiceSource(selectedVoiceValue);
const sourceBadge = source === 'free'
2026-01-18 02:55:49 +08:00
? '< span class = "source-badge trial" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > '
: '< span class = "source-badge auth" > <EFBFBD> <EFBFBD> Ȩ< / span > ';
2026-01-17 16:34:39 +08:00
if (myVoice) {
nameEl.innerHTML = `${escapeHtml(myVoice.name)} ${sourceBadge}`;
2026-01-18 02:55:49 +08:00
sourceEl.textContent = '<27> ҵ<EFBFBD> <D2B5> <EFBFBD> ɫ';
2026-01-17 16:34:39 +08:00
} else if (source === 'free') {
const tv = TRIAL_VOICES.find(v => v.key === selectedVoiceValue);
nameEl.innerHTML = `${escapeHtml(tv?.name || selectedVoiceValue)} ${sourceBadge}`;
2026-01-18 02:55:49 +08:00
sourceEl.textContent = tv?.tag || '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ';
2026-01-17 16:34:39 +08:00
} else {
const av = authVoiceList.find(v => v.value === selectedVoiceValue);
nameEl.innerHTML = `${escapeHtml(av?.name || selectedVoiceValue)} ${sourceBadge}`;
2026-01-18 02:55:49 +08:00
sourceEl.textContent = 'Ԥ<> <D4A4> <EFBFBD> <EFBFBD> ɫ<EFBFBD> <C9AB> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ӵ<EFBFBD> <D3B5> ҵ<EFBFBD> <D2B5> <EFBFBD> ɫ<EFBFBD> <C9AB> ';
2026-01-17 16:34:39 +08:00
}
}
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> Ⱦ<EFBFBD> <C8BE> ɫ<EFBFBD> б <EFBFBD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
function renderMyVoiceList() {
const listEl = $('myVoiceList');
const emptyEl = $('myVoiceEmpty');
$('myVoiceCount').textContent = mySpeakers.length;
if (!mySpeakers.length) {
listEl.innerHTML = '';
listEl.style.display = 'none';
emptyEl.style.display = 'block';
return;
}
listEl.style.display = 'flex';
emptyEl.style.display = 'none';
const authOk = isAuthConfigured();
listEl.innerHTML = mySpeakers.map(s => {
const isSelected = s.value === selectedVoiceValue;
const isEditing = s.value === editingVoiceValue;
const source = s.source || getVoiceSource(s.value);
const canPlay = source === 'free' || authOk;
const sourceBadge = source === 'free'
2026-01-18 02:55:49 +08:00
? '< span class = "source-badge trial" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > '
: '< span class = "source-badge auth" > <EFBFBD> <EFBFBD> Ȩ< / span > ';
2026-01-17 16:34:39 +08:00
return `
< div class = "voice-item${isSelected ? ' selected' : ''}${isEditing ? ' editing' : ''}${!canPlay ? ' disabled' : ''}"
data-value="${escapeHtml(s.value)}" data-source="${source}">
< div class = "voice-item-radio" > < / div >
< div class = "voice-item-info" >
< div class = "voice-item-name" > ${escapeHtml(s.name || s.value)} ${sourceBadge}< / div >
2026-01-18 02:55:49 +08:00
< div class = "voice-item-meta" > ${!canPlay ? '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> ü<EFBFBD> Ȩ' : escapeHtml(s.value.slice(0, 25))}< / div >
2026-01-17 16:34:39 +08:00
< div class = "voice-item-edit-form" >
2026-01-18 02:55:49 +08:00
< input type = "text" class = "input voice-edit-input" value = "${escapeHtml(s.name || '')}" placeholder = "<22> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> " >
2026-01-17 16:34:39 +08:00
< button class = "btn btn-xs btn-primary voice-edit-save" > < i class = "fa-solid fa-check" > < / i > < / button >
< button class = "btn btn-xs voice-edit-cancel" > < i class = "fa-solid fa-times" > < / i > < / button >
< / div >
< / div >
< div class = "voice-item-actions" >
2026-01-18 02:55:49 +08:00
< button class = "btn btn-xs voice-rename-btn" data-value = "${escapeHtml(s.value)}" title = "<22> <> <EFBFBD> <EFBFBD> " >
2026-01-17 16:34:39 +08:00
< i class = "fa-solid fa-pen" > < / i >
< / button >
2026-01-18 02:55:49 +08:00
< button class = "btn btn-xs btn-danger voice-delete-btn" data-value = "${escapeHtml(s.value)}" title = "ɾ<> <C9BE> " >
2026-01-17 16:34:39 +08:00
< i class = "fa-solid fa-trash" > < / i >
< / button >
< / div >
< / div > `;
}).join('');
bindMyVoiceEvents(listEl);
}
function bindMyVoiceEvents(listEl) {
listEl.querySelectorAll('.voice-item').forEach(item => {
item.addEventListener('click', e => {
if (e.target.closest('button') || e.target.closest('input')) return;
if (item.classList.contains('disabled')) {
2026-01-18 02:55:49 +08:00
post('xb-tts:toast', { type: 'error', message: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ü<EFBFBD> Ȩ API' });
2026-01-17 16:34:39 +08:00
return;
}
if (editingVoiceValue) return;
selectedVoiceValue = item.dataset.value;
renderMyVoiceList();
updateCurrentVoiceDisplay();
});
});
listEl.querySelectorAll('.voice-rename-btn').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
editingVoiceValue = btn.dataset.value;
renderMyVoiceList();
listEl.querySelector('.voice-edit-input')?.focus();
});
});
listEl.querySelectorAll('.voice-edit-save').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const item = mySpeakers.find(s => s.value === editingVoiceValue);
const input = btn.closest('.voice-item').querySelector('.voice-edit-input');
if (item & & input?.value?.trim()) {
item.name = input.value.trim();
post('xb-tts:save-config', collectForm());
}
editingVoiceValue = null;
renderMyVoiceList();
updateCurrentVoiceDisplay();
});
});
listEl.querySelectorAll('.voice-edit-cancel').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
editingVoiceValue = null;
renderMyVoiceList();
});
});
listEl.querySelectorAll('.voice-delete-btn').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const value = btn.dataset.value;
const item = mySpeakers.find(s => s.value === value);
2026-01-18 02:55:49 +08:00
if (confirm(`ɾ<> <C9BE> <EFBFBD> <EFBFBD> ${item?.name || value}<7D> <> <EFBFBD> <EFBFBD> `)) {
2026-01-17 16:34:39 +08:00
mySpeakers = mySpeakers.filter(s => s.value !== value);
if (selectedVoiceValue === value) {
selectedVoiceValue = mySpeakers[0]?.value || '';
}
renderMyVoiceList();
renderTrialVoiceList();
renderAuthVoiceList();
updateCurrentVoiceDisplay();
post('xb-tts:save-config', collectForm());
}
});
});
}
function renderTrialVoiceList() {
const listEl = $('trialVoiceList');
listEl.innerHTML = TRIAL_VOICES.map(v => {
const isSelected = v.key === selectedTrialVoiceValue;
const inMy = isInMyList(v.key);
return `
< div class = "voice-item${isSelected ? ' selected' : ''}${inMy ? ' in-my-list' : ''}" data-value = "${v.key}" >
< div class = "voice-item-radio" > < / div >
< div class = "voice-item-info" >
2026-01-18 02:55:49 +08:00
< div class = "voice-item-name" > ${escapeHtml(v.name)}${inMy ? ' < span class = "source-badge trial" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > ' : ''}< / div >
< div class = "voice-item-meta" > ${escapeHtml(v.tag)} <20> <> ${v.gender === 'female' ? 'Ů' : '<27> <> '}< / div >
2026-01-17 16:34:39 +08:00
< / div >
< / div > `;
}).join('');
listEl.querySelectorAll('.voice-item').forEach(item => {
item.addEventListener('click', () => {
selectedTrialVoiceValue = item.dataset.value;
renderTrialVoiceList();
});
});
}
function renderAuthVoiceList() {
const gender = $('voiceGenderFilter')?.value || 'all';
const model = $('voiceModelFilter')?.value || 'all';
const language = $('voiceLangFilter')?.value || 'all';
const scene = $('voiceSceneFilter')?.value || 'all';
const search = ($('voiceSearchInput')?.value || '').toLowerCase().trim();
const list = authVoiceList.filter(item => {
if (gender !== 'all' & & item.gender !== gender) return false;
if (model !== 'all' & & item.model !== model) return false;
if (language !== 'all' & & item.language !== language) return false;
if (scene !== 'all' & & item.scene !== scene) return false;
if (search & & !item.name.toLowerCase().includes(search) & & !item.value.toLowerCase().includes(search)) return false;
return true;
}).sort((a, b) => a.name.localeCompare(b.name, 'zh-Hans-CN'));
const listEl = $('authVoiceList');
if (!list.length) {
2026-01-18 02:55:49 +08:00
listEl.innerHTML = '< div class = "rules-empty" > û<EFBFBD> <EFBFBD> ƥ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫ< / div > ';
2026-01-17 16:34:39 +08:00
return;
}
listEl.innerHTML = list.map(item => {
const isSelected = item.value === selectedAuthVoiceValue;
const inMy = isInMyList(item.value);
return `
< div class = "voice-item${isSelected ? ' selected' : ''}${inMy ? ' in-my-list' : ''}" data-value = "${escapeHtml(item.value)}" >
< div class = "voice-item-radio" > < / div >
< div class = "voice-item-info" >
2026-01-18 02:55:49 +08:00
< div class = "voice-item-name" > ${escapeHtml(item.name)}${inMy ? ' < span class = "source-badge auth" > <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / span > ' : ''}< / div >
2026-01-17 16:34:39 +08:00
< div class = "voice-item-meta" > ${escapeHtml(item.genderLabel)}${formatTagLabel(item.tags)}< / div >
< / div >
< / div > `;
}).join('');
listEl.querySelectorAll('.voice-item').forEach(item => {
item.addEventListener('click', () => {
selectedAuthVoiceValue = item.dataset.value;
renderAuthVoiceList();
});
});
}
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> <EFBFBD> ݴ<EFBFBD> <DDB4> <EFBFBD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
function normalizeMySpeakers(list) {
if (!Array.isArray(list)) return [];
return list.map(item => ({
name: String(item?.name || '').trim(),
value: String(item?.value || '').trim(),
source: item?.source || getVoiceSource(item?.value || ''),
})).filter(item => item.value);
}
function applyCacheStats(stats = {}) {
$('cacheCount').textContent = stats.count ?? 0;
$('cacheSize').textContent = `${stats.sizeMB ?? 0} MB`;
$('cacheHits').textContent = stats.hits ?? 0;
$('cacheMisses').textContent = stats.misses ?? 0;
}
function fillForm(cfg) {
config = cfg || {};
mySpeakers = normalizeMySpeakers(cfg.volc?.mySpeakers);
selectedVoiceValue = cfg.volc?.defaultSpeaker || '';
$('appId').value = cfg.volc?.appId || '';
$('accessKey').value = cfg.volc?.accessKey || '';
$('autoSpeak').checked = cfg.autoSpeak !== false;
const speechRate = Number.isFinite(cfg.volc?.speechRate) ? cfg.volc.speechRate : 1.0;
$('speechRate').value = speechRate;
$('speechRateValue').textContent = `${speechRate.toFixed(1)}x`;
renderRulesList($('skipRulesList'), cfg.skipRanges || [], 'skip');
renderRulesList($('readRulesList'), cfg.readRanges || [], 'read');
$('readRangesEnabled').checked = cfg.readRangesEnabled === true;
$('usageReturn').checked = cfg.volc?.usageReturn === true;
$('serverCacheEnabled').checked = cfg.volc?.serverCacheEnabled === true;
$('disableMarkdownFilter').checked = cfg.volc?.disableMarkdownFilter !== false;
$('useTts11').checked = cfg.volc?.useTts11 !== false;
$('disableEmojiFilter').checked = cfg.volc?.disableEmojiFilter === true;
$('enableLanguageDetector').checked = cfg.volc?.enableLanguageDetector === true;
$('explicitLanguage').value = cfg.volc?.explicitLanguage || '';
$('maxLengthToFilterParenthesis').value = cfg.volc?.maxLengthToFilterParenthesis ?? 100;
$('postProcessPitch').value = cfg.volc?.postProcessPitch ?? 0;
$('cacheDays').value = cfg.volc?.cacheDays ?? 7;
$('cacheMaxEntries').value = cfg.volc?.cacheMaxEntries ?? 200;
$('cacheMaxMB').value = cfg.volc?.cacheMaxMB ?? 200;
applyCacheStats(cfg.cacheStats || {});
updateApiStatus();
renderMyVoiceList();
renderTrialVoiceList();
renderAuthVoiceList();
updateCurrentVoiceDisplay();
}
function inferResourceIdBySpeaker(value) {
const v = (value || '').trim().toLowerCase();
if (v.startsWith('icl_') || v.startsWith('s_')) return 'seed-icl-2.0';
if (TTS2_VOICES.has(value)) return 'seed-tts-2.0';
return 'seed-tts-1.0';
}
function collectForm() {
const speaker = selectedVoiceValue;
const source = getVoiceSource(speaker);
return {
volc: {
appId: $('appId').value.trim(),
accessKey: $('accessKey').value.trim(),
defaultResourceId: source === 'auth' ? inferResourceIdBySpeaker(speaker) : '',
defaultSpeaker: speaker,
mySpeakers: mySpeakers,
usageReturn: $('usageReturn').checked,
serverCacheEnabled: $('serverCacheEnabled').checked,
disableMarkdownFilter: $('disableMarkdownFilter').checked,
useTts11: $('useTts11').checked,
disableEmojiFilter: $('disableEmojiFilter').checked,
enableLanguageDetector: $('enableLanguageDetector').checked,
explicitLanguage: $('explicitLanguage').value.trim(),
maxLengthToFilterParenthesis: Number($('maxLengthToFilterParenthesis').value) || 0,
postProcessPitch: Number($('postProcessPitch').value) || 0,
speechRate: Number($('speechRate').value) || 1.0,
localCacheEnabled: true,
cacheDays: Math.max(1, Number($('cacheDays').value) || 7),
cacheMaxEntries: Math.max(10, Number($('cacheMaxEntries').value) || 200),
cacheMaxMB: Math.max(10, Number($('cacheMaxMB').value) || 200),
},
autoSpeak: $('autoSpeak').checked,
skipRanges: collectRules($('skipRulesList')),
readRanges: collectRules($('readRulesList')),
readRangesEnabled: $('readRangesEnabled').checked,
};
}
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
function doTestVoice(speaker, source, textElId, statusElId) {
2026-01-18 02:55:49 +08:00
const text = $(textElId)?.value?.trim() || '<27> <> <EFBFBD> ã<EFBFBD> <C3A3> <EFBFBD> <EFBFBD> <EFBFBD> һ <EFBFBD> β<EFBFBD> <CEB2> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ';
2026-01-17 16:34:39 +08:00
if (!speaker) {
2026-01-18 02:55:49 +08:00
setTestStatus(statusElId, 'error', '<27> <> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <D1A1> һ <EFBFBD> <D2BB> <EFBFBD> <EFBFBD> ɫ');
2026-01-17 16:34:39 +08:00
return;
}
if (source === 'auth' & & !isAuthConfigured()) {
2026-01-18 02:55:49 +08:00
setTestStatus(statusElId, 'error', '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ü<EFBFBD> Ȩ API');
2026-01-17 16:34:39 +08:00
return;
}
2026-01-18 02:55:49 +08:00
setTestStatus(statusElId, 'playing', '<27> <> <EFBFBD> ںϳ <DABA> ...');
2026-01-17 16:34:39 +08:00
post('xb-tts:test-speak', {
text,
speaker,
source,
resourceId: source === 'auth' ? inferResourceIdBySpeaker(speaker) : '',
});
}
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> Ϣ<EFBFBD> <CFA2> <EFBFBD> <EFBFBD>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
window.addEventListener('message', ev => {
if (ev.origin !== PARENT_ORIGIN || ev.source !== parent) return;
if (!ev.data?.type?.startsWith('xb-tts:')) return;
const { type, payload } = ev.data;
switch (type) {
case 'xb-tts:config':
fillForm(payload);
break;
case 'xb-tts:config-saved':
fillForm(payload);
handleSaveResult(true);
2026-01-18 02:55:49 +08:00
post('xb-tts:toast', { type: 'success', message: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> ѱ<EFBFBD> <D1B1> <EFBFBD> ' });
2026-01-17 16:34:39 +08:00
break;
case 'xb-tts:config-save-error':
handleSaveResult(false);
2026-01-18 02:55:49 +08:00
post('xb-tts:toast', { type: 'error', message: payload?.message || '<27> <> <EFBFBD> <EFBFBD> ʧ<EFBFBD> <CAA7> ' });
2026-01-17 16:34:39 +08:00
break;
case 'xb-tts:test-done':
2026-01-18 02:55:49 +08:00
['testMyStatus', 'testTrialStatus', 'testAuthStatus'].forEach(id => setTestStatus(id, 'playing', '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ...'));
2026-01-17 16:34:39 +08:00
setTimeout(() => ['testMyStatus', 'testTrialStatus', 'testAuthStatus'].forEach(id => setTestStatus(id, '', '')), 3000);
break;
case 'xb-tts:test-error':
2026-01-18 02:55:49 +08:00
const errMsg = 'ʧ<> <CAA7> : ' + (payload || 'δ֪<CEB4> <D6AA> <EFBFBD> <EFBFBD> ');
2026-01-17 16:34:39 +08:00
['testMyStatus', 'testTrialStatus', 'testAuthStatus'].forEach(id => setTestStatus(id, 'error', errMsg));
break;
case 'xb-tts:cache-stats':
applyCacheStats(payload || {});
break;
}
});
2026-01-18 02:55:49 +08:00
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
// <20> <> ʼ <EFBFBD> <CABC>
// <20> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T<EFBFBD> T
2026-01-17 16:34:39 +08:00
document.addEventListener('DOMContentLoaded', () => {
buildAuthVoiceList();
initRulesEditors();
const scenes = new Set();
authVoiceList.forEach(item => { if (item.scene) scenes.add(item.scene); });
2026-01-18 02:55:49 +08:00
$('voiceSceneFilter').innerHTML = '< option value = "all" > ȫ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> < / option > ' +
2026-01-17 16:34:39 +08:00
Array.from(scenes).sort((a, b) => a.localeCompare(b, 'zh-Hans-CN')).map(s => `< option value = "${s}" > ${s}< / option > `).join('');
$$('.nav-item, .mobile-nav-item').forEach(item => item.addEventListener('click', () => switchView(item.dataset.view)));
$('tts_close').addEventListener('click', () => post('xb-tts:close'));
$('toggleKey').addEventListener('click', () => {
const input = $('accessKey');
const icon = $('toggleKey').querySelector('i');
input.type = input.type === 'password' ? 'text' : 'password';
icon.className = input.type === 'password' ? 'fa-solid fa-eye' : 'fa-solid fa-eye-slash';
});
['appId', 'accessKey'].forEach(id => {
$(id).addEventListener('input', () => {
config.volc = config.volc || {};
config.volc.appId = $('appId').value.trim();
config.volc.accessKey = $('accessKey').value.trim();
updateApiStatus();
renderMyVoiceList();
});
});
$('speechRate').addEventListener('input', e => {
$('speechRateValue').textContent = `${Number(e.target.value).toFixed(1)}x`;
});
['voiceGenderFilter', 'voiceModelFilter', 'voiceLangFilter', 'voiceSceneFilter'].forEach(id => {
$(id)?.addEventListener('change', renderAuthVoiceList);
});
$('voiceSearchInput')?.addEventListener('input', renderAuthVoiceList);
$$('.voice-tab').forEach(tab => {
tab.addEventListener('click', () => {
$$('.voice-tab').forEach(t => t.classList.remove('active'));
$$('.voice-panel').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
$(`panel-${tab.dataset.panel}`)?.classList.add('active');
});
});
$('testMyVoiceBtn').addEventListener('click', () => {
const my = mySpeakers.find(s => s.value === selectedVoiceValue);
const source = my?.source || getVoiceSource(selectedVoiceValue);
doTestVoice(selectedVoiceValue, source, 'testTextMy', 'testMyStatus');
});
$('testTrialVoiceBtn').addEventListener('click', () => {
doTestVoice(selectedTrialVoiceValue, 'free', 'testTextTrial', 'testTrialStatus');
});
$('testAuthVoiceBtn').addEventListener('click', () => {
doTestVoice(selectedAuthVoiceValue, 'auth', 'testTextAuth', 'testAuthStatus');
});
$('saveToMyVoiceTrialBtn').addEventListener('click', () => {
2026-01-18 02:55:49 +08:00
if (!selectedTrialVoiceValue) { post('xb-tts:toast', { type: 'error', message: '<27> <> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <D1A1> һ <EFBFBD> <D2BB> <EFBFBD> <EFBFBD> ɫ' }); return; }
2026-01-17 16:34:39 +08:00
const tv = TRIAL_VOICES.find(v => v.key === selectedTrialVoiceValue);
const name = $('saveAsNameTrial').value.trim() || tv?.name || selectedTrialVoiceValue;
if (!isInMyList(selectedTrialVoiceValue)) {
mySpeakers.push({ name, value: selectedTrialVoiceValue, source: 'free' });
}
selectedVoiceValue = selectedTrialVoiceValue;
$('saveAsNameTrial').value = '';
renderMyVoiceList();
renderTrialVoiceList();
updateCurrentVoiceDisplay();
$$('.voice-tab').forEach(t => t.classList.remove('active'));
$$('.voice-panel').forEach(p => p.classList.remove('active'));
$$('.voice-tab')[0].classList.add('active');
$('panel-myVoice').classList.add('active');
post('xb-tts:save-config', collectForm());
2026-01-18 02:55:49 +08:00
post('xb-tts:toast', { type: 'success', message: `<60> <> <EFBFBD> <EFBFBD> <EFBFBD> ӣ<EFBFBD> ${name}` });
2026-01-17 16:34:39 +08:00
});
$('saveToMyVoiceAuthBtn').addEventListener('click', () => {
2026-01-18 02:55:49 +08:00
if (!selectedAuthVoiceValue) { post('xb-tts:toast', { type: 'error', message: '<27> <> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <D1A1> һ <EFBFBD> <D2BB> <EFBFBD> <EFBFBD> ɫ' }); return; }
2026-01-17 16:34:39 +08:00
const av = authVoiceList.find(v => v.value === selectedAuthVoiceValue);
const name = $('saveAsNameAuth').value.trim() || av?.name || selectedAuthVoiceValue;
if (!isInMyList(selectedAuthVoiceValue)) {
mySpeakers.push({ name, value: selectedAuthVoiceValue, source: 'auth' });
}
selectedVoiceValue = selectedAuthVoiceValue;
$('saveAsNameAuth').value = '';
renderMyVoiceList();
renderAuthVoiceList();
updateCurrentVoiceDisplay();
$$('.voice-tab').forEach(t => t.classList.remove('active'));
$$('.voice-panel').forEach(p => p.classList.remove('active'));
$$('.voice-tab')[0].classList.add('active');
$('panel-myVoice').classList.add('active');
post('xb-tts:save-config', collectForm());
2026-01-18 02:55:49 +08:00
post('xb-tts:toast', { type: 'success', message: `<60> <> <EFBFBD> <EFBFBD> <EFBFBD> ӣ<EFBFBD> ${name}` });
2026-01-17 16:34:39 +08:00
});
$('addMySpeakerBtn').addEventListener('click', () => {
const id = $('newVoiceId').value.trim();
const name = $('newVoiceName').value.trim();
2026-01-18 02:55:49 +08:00
if (!id) { post('xb-tts:toast', { type: 'error', message: '<27> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ɫID' }); return; }
2026-01-17 16:34:39 +08:00
if (!isInMyList(id)) {
mySpeakers.push({ name: name || id, value: id, source: 'auth' });
}
selectedVoiceValue = id;
$('newVoiceId').value = '';
$('newVoiceName').value = '';
renderMyVoiceList();
updateCurrentVoiceDisplay();
post('xb-tts:save-config', collectForm());
2026-01-18 02:55:49 +08:00
post('xb-tts:toast', { type: 'success', message: `<60> <> <EFBFBD> <EFBFBD> <EFBFBD> ӣ<EFBFBD> ${name || id}` });
2026-01-17 16:34:39 +08:00
});
['saveConfigBtn', 'saveVoiceBtn', 'saveAdvancedBtn', 'saveCacheBtn'].forEach(id => {
$(id)?.addEventListener('click', () => { setSavingState($(id)); post('xb-tts:save-config', collectForm()); });
});
$('cacheRefreshBtn').addEventListener('click', () => post('xb-tts:cache-refresh'));
$('cacheClearExpiredBtn').addEventListener('click', () => post('xb-tts:cache-clear-expired'));
$('cacheClearAllBtn').addEventListener('click', () => post('xb-tts:cache-clear-all'));
post('xb-tts:ready');
});
< / script >
< / body >
2026-01-18 02:55:49 +08:00
< / html >