web-index/doubao-asr-demo/index.html

249 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="filename" content="audio-to-text-tool.html">
<title>录音文件转文字工具</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<!-- 使用页面内样式,避免旧样式冲突 -->
<style>
/* 省略:保留你提供的全部样式,未改动 */
:root { --primary: #4361ee; --primary-light: #5a9cff; --primary-dark: #3a0ca3; --success: #41d6a4; --warning: #ffb020; --error: #ff4d4f; --bg-light: #f9fafc; --bg-dark: #101827; --text-light: #111827; --text-dark: #e2e8f0; --card-bg-light: #ffffff; --card-bg-dark: #1e293b; --border-light: #e2e8f0; --border-dark: #334155; --gray-100: #f1f5f9; --gray-200: #e2e8f0; --gray-300: #cbd5e1; --gray-700: #334155; --font-main: 'Poppins', 'Noto Sans SC', system-ui, sans-serif; --font-secondary: 'SF Pro', 'Helvetica Neue', sans-serif; --radius: 16px; --shadow: 0 10px 20px rgba(0, 0, 0, 0.08); --shadow-dark: 0 10px 20px rgba(0, 0, 0, 0.2); --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }
* { margin:0; padding:0; box-sizing:border-box; }
body { background: linear-gradient(135deg, #f0f4ff 0%, #e6f0ff 100%); color: var(--text-light); font-family: var(--font-main); margin: 0; padding: 0; display: flex; justify-content: center; min-height: 100vh; transition: var(--transition); }
body.dark-mode { background: linear-gradient(135deg, #0c142a 0%, #101e3c 100%); color: var(--text-dark); }
::-webkit-scrollbar { width:8px; } ::-webkit-scrollbar-track { background: var(--gray-100); border-radius:4px; } body.dark-mode ::-webkit-scrollbar-track { background: var(--gray-700);} ::-webkit-scrollbar-thumb { background: var(--primary); border-radius:4px; }
.app-container { width:100%; max-width:1400px; margin:2rem; }
.navbar { display:flex; justify-content:space-between; align-items:center; margin-bottom:2.5rem; padding:0.5rem 1rem; background: var(--card-bg-light); border-radius: var(--radius); box-shadow: var(--shadow); transition: var(--transition); }
body.dark-mode .navbar { background: var(--card-bg-dark); box-shadow: var(--shadow-dark); }
.brand { display:flex; align-items:center; gap:16px; }
.logo-svg { height:44px; width:44px; background: linear-gradient(135deg, var(--primary), var(--primary-dark)); border-radius:12px; display:flex; align-items:center; justify-content:center; }
.logo-svg svg { width:24px; height:24px; fill:white; }
.brand-info h1 { font-size:1.8rem; font-weight:700; margin-bottom:4px; color: var(--primary); letter-spacing:-0.5px; }
.brand-info p { font-size:0.9rem; color: var(--gray-700); }
body.dark-mode .brand-info p { color: var(--gray-300);}
.navbar-controls { display:flex; align-items:center; gap:1.5rem; }
.language-select { padding:0.8rem 1.4rem; background: var(--card-bg-light); border:1px solid var(--border-light); border-radius:12px; cursor:pointer; display:flex; align-items:center; gap:12px; transition: var(--transition); font-weight:500; }
body.dark-mode .language-select { background: var(--card-bg-dark); border-color: var(--border-dark);}
.language-select:hover { border-color: var(--primary);}
.theme-toggle { background: var(--card-bg-light); border:1px solid var(--border-light); height:50px; width:50px; border-radius:50%; display:flex; align-items:center; justify-content:center; cursor:pointer; transition: var(--transition);}
body.dark-mode .theme-toggle { background: var(--card-bg-dark); border-color: var(--border-dark);}
.theme-toggle:hover { background: var(--gray-100); transform: rotate(20deg);} body.dark-mode .theme-toggle:hover { background: rgba(255,255,255,0.1);}
.content-grid { display:grid; grid-template-columns: 1fr 1fr 1fr; gap:2rem; margin-bottom:2rem; }
@media (max-width:1200px){ .content-grid { grid-template-columns: 1fr 1fr; } } @media (max-width:768px){ .content-grid { grid-template-columns: 1fr; } }
.card { border-radius: var(--radius); overflow:hidden; box-shadow: var(--shadow); background: var(--card-bg-light); transition: var(--transition); position:relative; margin-bottom:2rem; }
body.dark-mode .card { background: var(--card-bg-dark); box-shadow: var(--shadow-dark);}
.card:hover { transform: translateY(-5px); box-shadow: 0 15px 30px rgba(0,0,0,0.15);} body.dark-mode .card:hover { box-shadow: 0 15px 30px rgba(0,0,0,0.3);}
.card-header { padding:1.8rem 1.8rem 1.2rem; display:flex; align-items:center; font-weight:600; gap:0.75rem; border-bottom:1px solid var(--border-light); font-size:1.2rem; color: var(--primary);} body.dark-mode .card-header { border-bottom:1px solid var(--border-dark);}
.card-body { padding:2rem; }
.upload-area { display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:4rem 2rem; border:2px dashed var(--border-light); border-radius: var(--radius); cursor:pointer; transition: var(--transition); position:relative; background: rgba(67,97,238,0.03);} body.dark-mode .upload-area { border-color: var(--border-dark); background: rgba(33,66,164,0.05);} .upload-area:hover { border-color: var(--primary); background: rgba(67,97,238,0.08);} .upload-area.active { background: rgba(67,97,238,0.1); border-color: var(--primary); border-style: solid; }
.upload-icon { font-size:4.5rem; color: var(--primary); margin-bottom:1.5rem; display:flex; align-items:center; justify-content:center; }
.upload-text { font-size:1.4rem; margin-bottom:1.2rem; font-weight:500; color: var(--text-light);} body.dark-mode .upload-text { color: var(--text-dark);}
.upload-info { color: var(--gray-700); font-size:1rem; margin-bottom:1.5rem; max-width:90%; } body.dark-mode .upload-info { color: var(--gray-300);}
.file-input { opacity:0; position:absolute; top:0; left:0; width:100%; height:100%; cursor:pointer; z-index:2; }
.btn { padding:1.1rem 3rem; border-radius: var(--radius); border:none; cursor:pointer; font-weight:600; transition: var(--transition); display:inline-flex; align-items:center; justify-content:center; gap:12px; font-size:1.1rem; position:relative; overflow:hidden; z-index:1; }
.btn::after { content:''; position:absolute; width:100%; height:0; top:50%; left:50%; background: rgba(255,255,255,0.2); opacity:0; transform: translateX(-50%) translateY(-50%) rotate(0deg); transition: all .5s; z-index:-1; } .btn:hover::after { height:350%; opacity:1;} .btn:active::after { height:500%; opacity:0; }
.btn-primary { background: linear-gradient(90deg, var(--primary), var(--primary-light)); color:white; box-shadow: 0 8px 20px rgba(67,97,238,0.3);} .btn-primary:hover { background: linear-gradient(90deg, var(--primary-dark), var(--primary)); box-shadow: 0 12px 25px rgba(58,12,163,0.4);}
.btn-secondary { background: var(--gray-100); color: var(--text-light);} body.dark-mode .btn-secondary { background: var(--gray-700); color: var(--text-dark);}
.btn-success { background: var(--success); color:white; box-shadow: 0 8px 20px rgba(65,214,164,0.3);} .btn-success:hover { background:#2a9d7d; box-shadow: 0 12px 25px rgba(42,157,125,0.4);}
.status-grid { display:grid; grid-template-columns: 1fr 1fr; gap:2rem; margin-bottom:2rem; }
.progress-container { width:100%; display:flex; flex-direction:column; justify-content:center; }
.progress-header { display:flex; justify-content:space-between; margin-bottom:1.5rem; }
.progress-label { font-size:1.25rem; font-weight:500; color: var(--text-light);} body.dark-mode .progress-label { color: var(--text-dark);}
.progress-percent { font-weight:700; color: var(--primary); font-size:1.4rem; }
.progress-bar { height:16px; background-color: var(--gray-200); border-radius:8px; overflow:hidden; } body.dark-mode .progress-bar { background-color: var(--gray-700);}
.progress-fill { height:100%; background: linear-gradient(90deg, var(--primary), var(--primary-light)); border-radius:8px; width:0%; transition: width 0.4s ease; box-shadow: 2px 0 10px rgba(67,97,238,0.4);}
.progress-info { display:flex; justify-content:space-between; margin-top:1rem; font-size:1rem; color: var(--gray-700);} body.dark-mode .progress-info { color: var(--gray-300);}
.status-animation { display:flex; align-items:center; justify-content:center; height:160px;} .sound-wave { display:flex; align-items:center; height:130px; gap:12px;} .sound-bar { width:10px; background-color: var(--primary); border-radius:5px; animation: sound-wave 1.2s ease-in-out infinite; box-shadow: 0 0 10px rgba(67,97,238,0.3);} @keyframes sound-wave { 0%,100%{ transform: scaleY(0.7) translateY(10px); background-color: var(--primary-light);} 50%{ transform: scaleY(1.5) translateY(0); background-color: var(--primary);} } .sound-bar:nth-child(1){ height:30px; animation-delay:0s;} .sound-bar:nth-child(2){ height:50px; animation-delay:0.1s;} .sound-bar:nth-child(3){ height:70px; animation-delay:0.2s;} .sound-bar:nth-child(4){ height:100px; animation-delay:0.3s;} .sound-bar:nth-child(5){ height:70px; animation-delay:0.4s;} .sound-bar:nth-child(6){ height:50px; animation-delay:0.5s;} .sound-bar:nth-child(7){ height:30px; animation-delay:0.6s;}
.status-detail { text-align:center; margin-top:2rem;} .status-detail p { font-size:1.2rem; margin-bottom:0.5rem; line-height:1.7;} .highlight-text { color: var(--primary); font-weight:700; display:inline-block; }
.result-container { display:grid; grid-template-columns: 1fr 1fr; gap:2rem; } @media (max-width:1200px){ .result-container { grid-template-columns: 1fr; } }
.audio-player-section { display:flex; flex-direction:column; gap:2rem; }
.audio-cover { width:100%; height:280px; border-radius: var(--radius); background-color: var(--gray-200); background-image: url('https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80'); background-size: cover; background-position:center; position:relative; overflow:hidden; box-shadow: inset 0 -60px 40px -20px rgba(0,0,0,0.7);}
.audio-info { position:absolute; bottom:0; left:0; right:0; padding:2rem; color:white; } .audio-info h3 { font-size:1.8rem; margin-bottom:0.7rem; font-weight:700; text-shadow: 0 2px 4px rgba(0,0,0,0.5);} .audio-info p { opacity:0.9; font-size:1.1rem; text-shadow: 0 1px 2px rgba(0,0,0,0.5);}
.audio-controls { background: var(--card-bg-light); border-radius: var(--radius); padding:2rem; display:flex; flex-direction:column; gap:1.5rem; box-shadow: var(--shadow);} body.dark-mode .audio-controls { background: var(--card-bg-dark); box-shadow: var(--shadow-dark);}
.control-bar { display:flex; align-items:center; gap:1.8rem; } .play-btn { width:60px; height:60px; border-radius:50%; background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color:white; display:flex; align-items:center; justify-content:center; border:none; cursor:pointer; transition: var(--transition); box-shadow: 0 6px 15px rgba(58,12,163,0.5);} .play-btn:hover { transform: scale(1.08); box-shadow: 0 8px 20px rgba(58,12,163,0.7);}
.progress-audio { flex:1; position:relative; } .progress-seek { width:100%; height:8px; background: var(--gray-200); border-radius:4px; cursor:pointer; position:relative;} body.dark-mode .progress-seek { background: var(--gray-700);} .progress-current { height:100%; background: linear-gradient(90deg, var(--primary), var(--primary-light)); border-radius:4px; position:absolute; top:0; left:0; width:0%; transition: width 0.2s ease;}
.time-indicators { display:flex; justify-content:space-between; margin-top:0.8rem; font-size:1rem; font-weight:500; color: var(--gray-700);} body.dark-mode .time-indicators { color: var(--gray-300);}
.secondary-controls { display:flex; justify-content:space-between; align-items:center;} .control-group { display:flex; align-items:center; gap:1.5rem;} .control-btn { background: rgba(67,97,238,0.1); border:none; width:42px; height:42px; border-radius:12px; display:flex; align-items:center; justify-content:center; cursor:pointer; color: var(--primary); transition: var(--transition);} body.dark-mode .control-btn { background: rgba(67,97,238,0.15);} .control-btn:hover { background: rgba(67,97,238,0.2); transform: translateY(-3px);} body.dark-mode .control-btn:hover { background: rgba(67,97,238,0.3);}
.dropdown { background: var(--card-bg-light); border:1px solid var(--border-light); border-radius:12px; padding:0.6rem 1.2rem; font-weight:500; font-size:1rem;} body.dark-mode .dropdown { background: var(--card-bg-dark); border:1px solid var(--border-dark);}
.transcript-area { display:flex; flex-direction:column; height:100%; gap:1.5rem;}
.transcript-content { flex:1; background: var(--card-bg-light); border-radius: var(--radius); padding:2.2rem; overflow-y:auto; box-shadow: var(--shadow);} body.dark-mode .transcript-content { background: var(--card-bg-dark); box-shadow: var(--shadow-dark);}
.transcript-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:1.8rem; padding-bottom:1.2rem; border-bottom:1px solid var(--border-light);} body.dark-mode .transcript-header { border-bottom-color: var(--border-dark);}
.speaker-tags { display:flex; gap:1rem; flex-wrap:wrap; }
.speaker-tag { background: var(--primary); color:white; padding:0.45rem 1.3rem; border-radius:24px; font-size:1rem; font-weight:600; box-shadow: 0 4px 8px rgba(0,0,0,0.1);}
.tag-1 { background: linear-gradient(90deg, #4361ee, #4895ef);} .tag-2 { background: linear-gradient(90deg, #41d6a4, #2e9d7d);} .tag-3 { background: linear-gradient(90deg, #f15bb5, #ae3f85);}
.tool-btn { background: rgba(67,97,238,0.1); border:none; padding:0.7rem 1.2rem; border-radius:8px; cursor:pointer; color: var(--primary); font-weight:500; transition: var(--transition);} .tool-btn:hover { background: rgba(67,97,238,0.2);}
.transcript-lines { display:flex; flex-direction:column; gap:1.5rem; }
.transcript-line { padding:1.5rem; border-radius:14px; transition: var(--transition); position:relative; cursor:pointer; }
body.dark-mode .transcript-line { background: rgba(255,255,255,0.03);} .transcript-line:not(.active):hover { background: rgba(67,97,238,0.05);} .transcript-line.active { background: rgba(67,97,238,0.1); border-left:5px solid var(--primary); box-shadow: 0 4px 12px rgba(67,97,238,0.1);}
.timestamp { font-size:0.9rem; font-weight:600; color: var(--primary); margin-bottom:0.5rem; display:block; background: rgba(67,97,238,0.1); padding:0.3rem 0.8rem; border-radius:20px; display:inline-block; transition: var(--transition);} body.dark-mode .timestamp { color: var(--primary-light);} .timestamp:hover { background: rgba(67,97,238,0.2);}
.speaker { font-weight:700; font-size:1.1rem; margin-right:0.5rem; display:inline-block; padding:0.3rem 1rem; border-radius:18px; color:white; }
.text-content { line-height:1.75; margin-top:0.8rem; font-size:1.05rem; }
.action-bar { display:flex; justify-content:flex-end; gap:1.5rem; padding:1.5rem 0 0; }
.action-btn { padding:1.1rem 2.2rem; border-radius: var(--radius); border:none; cursor:pointer; font-weight:600; transition: var(--transition); display:inline-flex; align-items:center; gap:10px; font-size:1.1rem; }
.action-success { background: var(--success); color:white; box-shadow: 0 8px 20px rgba(65,214,164,0.3);} .action-secondary { background: var(--gray-100); color: var(--text-light);} body.dark-mode .action-secondary { background: var(--gray-700); color: var(--text-dark);} .action-secondary:hover { background: var(--gray-200);} body.dark-mode .action-secondary:hover { background: var(--gray-600);}
.footer { text-align:center; padding:2rem; color: var(--gray-700); font-size:0.95rem; margin-top:2rem; border-top:1px solid var(--border-light);} body.dark-mode .footer { color: var(--gray-300); border-top-color: var(--border-dark);}
</style>
</head>
<body>
<div class="app-container">
<div class="navbar">
<div class="brand">
<div class="logo-svg">
<svg viewBox="0 0 24 24"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>
</div>
<div class="brand-info">
<h1>AudioToText Pro</h1>
<p>智能语音识别与转换解决方案</p>
</div>
</div>
<div class="navbar-controls">
<div class="language-select">
<i class="fas fa-globe"></i>
<span>自动检测语言</span>
<i class="fas fa-chevron-down"></i>
</div>
<button class="theme-toggle" id="themeToggle"><i class="fas fa-moon"></i></button>
</div>
</div>
<div class="content-grid">
<div class="card">
<div class="card-header"><i class="fas fa-file-upload"></i><span>上传录音文件</span></div>
<div class="card-body">
<div class="upload-area" id="uploadArea">
<div class="upload-icon"><i class="fas fa-cloud-upload-alt"></i></div>
<p class="upload-text">拖拽录音文件到这里或点击上传</p>
<p class="upload-info">支持 MP3, WAV, FLAC, AAC, M4A 等格式最大500MB</p>
<button class="btn btn-primary" onclick="document.getElementById('fileInput').click()"><i class="fas fa-upload"></i> 选择文件</button>
<input type="file" class="file-input" id="fileInput" accept="audio/*">
</div>
</div>
</div>
<div class="card">
<div class="card-header"><i class="fas fa-arrow-up"></i><span>上传状态</span></div>
<div class="card-body">
<div class="progress-container">
<div class="progress-header"><span class="progress-label">文件上传进度</span><span class="progress-percent" id="uploadPercent">0%</span></div>
<div class="progress-bar"><div class="progress-fill" id="uploadFill"></div></div>
<div class="progress-info"><span id="uploadFileName">未选择文件</span><span id="uploadEta">准备就绪</span></div>
</div>
</div>
</div>
<div class="card" id="analysisCard">
<div class="card-header"><i class="fas fa-cogs"></i><span>转写分析</span></div>
<div class="card-body">
<div class="status-animation"><div class="sound-wave"><div class="sound-bar"></div><div class="sound-bar"></div><div class="sound-bar"></div><div class="sound-bar"></div><div class="sound-bar"></div><div class="sound-bar"></div><div class="sound-bar"></div></div></div>
<div class="status-detail">
<p>AI正在分析音频内容识别不同发言者</p>
<p>转写用时 <span class="highlight-text" id="analysisProgress">00:00</span></p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="result-container">
<div class="audio-player-section">
<div class="audio-cover"><div class="audio-info"><h3 id="audioTitle">录音文件</h3><p id="audioMeta"></p></div></div>
<div class="audio-controls">
<div class="control-bar">
<button class="play-btn" id="playBtn"><i class="fas fa-play"></i></button>
<div class="progress-audio">
<div class="progress-seek"><div class="progress-current" id="audioProgress"></div></div>
<div class="time-indicators"><span id="currentTime">0:00</span><span id="totalTime"></span></div>
</div>
</div>
<div class="secondary-controls">
<div class="control-group">
<button class="control-btn" id="stepBack"><i class="fas fa-step-backward"></i></button>
<button class="control-btn" id="stepForward"><i class="fas fa-step-forward"></i></button>
<button class="control-btn" id="redoBtn"><i class="fas fa-redo"></i></button>
</div>
<div class="control-group">
<button class="control-btn"><i class="fas fa-volume-up"></i></button>
<div class="dropdown"><select id="speedSelect"><option>1.0x</option><option>0.75x</option><option>1.25x</option><option>1.5x</option><option>2.0x</option></select></div>
</div>
</div>
</div>
</div>
<div class="transcript-area">
<div class="transcript-header">
<div class="speaker-tags" id="speakerTags"></div>
<button class="tool-btn" id="editToggle"><i class="fas fa-edit"></i> 编辑</button>
<button class="tool-btn" id="saveSpkBtn" style="display:none"><i class="fas fa-save"></i> 保存</button>
</div>
<div class="transcript-content">
<div class="transcript-lines" id="transcriptLines"></div>
</div>
</div>
</div>
<div class="action-bar">
<button class="action-secondary action-btn" id="convertBtn"><i class="fas fa-redo"></i> 重新转换</button>
<button class="action-secondary action-btn" id="copyAllBtn"><i class="fas fa-copy"></i> 复制全文</button>
<button class="action-success action-btn" id="exportBtn"><i class="fas fa-download"></i> 导出文件</button>
</div>
</div>
</div>
<!-- 调试日志卡片(置底) -->
<div class="card">
<div class="card-header"><i class="fas fa-terminal"></i><span>调试日志</span></div>
<div class="card-body">
<div class="action-bar" style="justify-content: flex-end; padding: 0; margin-bottom: 1rem;">
<button class="tool-btn" id="copyDebugBtn"><i class="fas fa-copy"></i> 复制日志全文</button>
</div>
<div class="transcript-content" style="max-height: 40vh;">
<pre id="debugLog">等待提交…</pre>
</div>
</div>
</div>
<div class="footer"><p>AudioToText Pro &copy; 2025 | 高精度语音识别解决方案</p></div>
</div>
<!-- 主题与上传交互保留;识别、渲染由 main.js 接管 -->
<script>
const themeToggle = document.getElementById('themeToggle');
const body = document.body;
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') { body.classList.add('dark-mode'); themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; }
themeToggle.addEventListener('click', () => {
body.classList.toggle('dark-mode');
if (body.classList.contains('dark-mode')) { themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; localStorage.setItem('theme', 'dark'); }
else { themeToggle.innerHTML = '<i class="fas fa-moon"></i>'; localStorage.setItem('theme', 'light'); }
});
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('active'); });
uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('active'); });
function startAnalysisTimer(){ const el=document.getElementById('analysisProgress'); const started=Date.now(); if (!startAnalysisTimer._timer){ startAnalysisTimer._timer=setInterval(()=>{ const ms=Date.now()-started; const mm=Math.floor(ms/60000).toString().padStart(2,'0'); const ss=Math.floor((ms%60000)/1000).toString().padStart(2,'0'); el.textContent=`${mm}:${ss}`; },1000);} }
uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('active'); if (e.dataTransfer.files.length) { fileInput.files = e.dataTransfer.files; const f=e.dataTransfer.files[0]; document.getElementById('uploadFileName').textContent=f.name; document.getElementById('uploadPercent').textContent='100%'; document.getElementById('uploadFill').style.width='100%'; startAnalysisTimer(); } });
fileInput.addEventListener('change', () => { if (fileInput.files.length){ const f=fileInput.files[0]; document.getElementById('uploadFileName').textContent=f.name; document.getElementById('uploadPercent').textContent='100%'; document.getElementById('uploadFill').style.width='100%'; startAnalysisTimer(); } });
const copyDebugBtn = document.getElementById('copyDebugBtn');
copyDebugBtn?.addEventListener('click', async () => { try { const txt = document.getElementById('debugLog')?.textContent || ''; await navigator.clipboard.writeText(txt); } catch(e){} });
const playBtn = document.getElementById('playBtn');
playBtn.addEventListener('click', () => { const icon=playBtn.querySelector('i'); icon.classList.toggle('fa-play'); icon.classList.toggle('fa-pause'); });
</script>
<script>
(function(){
function wlog(msg){
try{var el=document.getElementById('debugLog'); if(el){ var ts=new Date().toISOString(); el.textContent = (el.textContent&&el.textContent!=='等待提交…'? (el.textContent+'\n'): '') + '['+ts+'] '+msg; }}catch(e){}
}
window.addEventListener('error', function(e){ wlog('全局错误: '+ (e.message||e.error||e.filename||'unknown')); });
window.addEventListener('unhandledrejection', function(e){ wlog('未捕获的Promise拒绝: '+ (e.reason&&e.reason.message?e.reason.message:JSON.stringify(e.reason||{}))); });
wlog('调试桥已安装');
})();
</script>
<script src="main.js?v=3"></script>
</body>
</html>