jscmp4 commited on
Commit
9aa9904
·
verified ·
1 Parent(s): 9ddcbbe
Files changed (1) hide show
  1. index.html +115 -43
index.html CHANGED
@@ -2,69 +2,141 @@
2
  <html lang="zh-CN">
3
  <head>
4
  <meta charset="UTF-8">
5
- <title>WebGPU 冒烟测试</title>
 
6
  <style>
7
- body { font-family: sans-serif; padding: 20px; line-height: 1.6; }
8
- .box { background: #f0f0f0; padding: 15px; border-radius: 8px; margin-bottom: 10px; }
9
- .success { color: green; font-weight: bold; }
10
- .error { color: red; font-weight: bold; }
11
- button { font-size: 16px; padding: 10px 20px; cursor: pointer; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  </style>
13
  </head>
14
  <body>
15
 
16
- <h1>🔥 冒烟测试 (Smoke Test)</h1>
17
- <p>如果下面两步都显示绿色说明你的“地基”已经搭好了。</p>
18
 
19
- <div class="box">
20
- <h3>1. 环境检查</h3>
21
- <div id="env-status">正在检查浏览器环境...</div>
22
- </div>
23
 
24
- <div class="box">
25
- <h3>2. 模型加载测试 (Whisper-Tiny)</h3>
26
- <p>点击按钮,测试是否能从 Hugging Face 拉取模型到本地。</p>
27
- <button id="load-btn">开始加载模型</button>
28
- <div id="model-status" style="margin-top:10px; color:#666;">等待指令...</div>
 
 
 
 
29
  </div>
30
 
31
  <script type="module">
32
- // 引入库
33
  import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2';
34
 
35
- const envStatus = document.getElementById('env-status');
36
- const modelStatus = document.getElementById('model-status');
37
- const btn = document.getElementById('load-btn');
38
-
39
- // 1. 环境检查逻辑
40
- // 简单检查 navigator.gpu 是否存在 (这是 WebGPU 的标志)
41
- if ("gpu" in navigator) {
42
- envStatus.innerHTML = "✅ 检测到 WebGPU 支持!你的浏览器很棒。";
43
- envStatus.className = "success";
44
- } else {
45
- envStatus.innerHTML = "⚠️ 未检测到 WebGPU,将回退到 WASM (CPU) 模式,速度会慢一些,但也能用。";
46
- envStatus.className = "error"; // 其实不算致命错误,只是性能会差
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
 
49
- // 2. 模型加载逻辑
50
- btn.addEventListener('click', async () => {
51
- btn.disabled = true;
52
- modelStatus.innerText = "⏳ 正在连接 HF Hub 下载模型 (约 40MB)...";
 
 
 
 
 
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  try {
55
- // 尝试加载最小的语音识别模型
56
- let pipe = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny');
57
-
58
- modelStatus.innerHTML = "✅ 模型加载成功!引擎已就绪。";
59
- modelStatus.className = "success";
60
- console.log(pipe); // 在控制台打印模型对象
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  } catch (err) {
62
- modelStatus.innerHTML = "❌ 加载失败: " + err.message;
63
- modelStatus.className = "error";
64
  console.error(err);
65
- btn.disabled = false;
 
 
 
66
  }
67
  });
 
 
 
 
68
  </script>
69
  </body>
70
  </html>
 
2
  <html lang="zh-CN">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Web AI - MP3 转文字 (MVP版)</title>
7
  <style>
8
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; color: #333; }
9
+ h1 { border-bottom: 2px solid #eee; padding-bottom: 10px; }
10
+
11
+ /* 布局容器 */
12
+ .container { background: #f9f9f9; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
13
+
14
+ /* 控件样式 */
15
+ .controls { margin: 20px 0; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
16
+ input[type="file"] { padding: 10px; border: 1px solid #ddd; border-radius: 6px; background: white; }
17
+ button { background: #000; color: #fff; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: bold; transition: opacity 0.2s; }
18
+ button:disabled { background: #ccc; cursor: not-allowed; }
19
+ button:hover:not(:disabled) { opacity: 0.8; }
20
+
21
+ /* 状态和结果区域 */
22
+ #status { color: #666; margin-bottom: 10px; font-size: 0.9em; }
23
+ #audio-player { width: 100%; margin: 10px 0; display: none; } /* 默认隐藏播放器 */
24
+
25
+ #result-area {
26
+ width: 100%; height: 200px;
27
+ padding: 15px; border: 1px solid #ddd; border-radius: 6px;
28
+ font-family: monospace; line-height: 1.5; resize: vertical;
29
+ background: #fff; box-sizing: border-box;
30
+ }
31
  </style>
32
  </head>
33
  <body>
34
 
35
+ <h1>🎙️ 本地 MP3 转文字 (Whisper)</h1>
36
+ <p>选择一个音频文件利用浏览器算力将其转换为文字。</p>
37
 
38
+ <div class="container">
39
+ <div id="status">🔵 正在初始化引擎...</div>
 
 
40
 
41
+ <div class="controls">
42
+ <input type="file" id="file-upload" accept="audio/*,video/*">
43
+ <button id="run-btn" disabled>开始转换</button>
44
+ </div>
45
+
46
+ <audio id="audio-player" controls></audio>
47
+
48
+ <h3>转换结果:</h3>
49
+ <textarea id="result-area" placeholder="识别出的文字将显示在这里..."></textarea>
50
  </div>
51
 
52
  <script type="module">
 
53
  import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2';
54
 
55
+ // 允许本地缓存模型,下次刷新不用重新下载
56
+ env.allowLocalModels = false;
57
+ env.useBrowserCache = true;
58
+
59
+ // 获取 DOM 元素
60
+ const statusEl = document.getElementById('status');
61
+ const fileInput = document.getElementById('file-upload');
62
+ const runBtn = document.getElementById('run-btn');
63
+ const audioPlayer = document.getElementById('audio-player');
64
+ const resultArea = document.getElementById('result-area');
65
+
66
+ let transcriber = null;
67
+
68
+ // --- 核心步骤 1: 加载模型 ---
69
+ async function initModel() {
70
+ statusEl.innerText = "⏳ 正在加载 Whisper 模型 (首次需下载 ~40MB)...";
71
+ try {
72
+ // 使用 whisper-tiny 模型 (速度最快)
73
+ // 如果想要更高精度,把下面改成 'Xenova/whisper-base'
74
+ transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny');
75
+
76
+ statusEl.innerText = "✅ 模型就绪!请上传音频。";
77
+ runBtn.disabled = false; // 只有模型加载完了,按钮才能点
78
+ } catch (err) {
79
+ statusEl.innerText = "❌ 模型加载失败: " + err.message;
80
+ }
81
  }
82
 
83
+ // --- 核心步骤 2: 处理文件上传 ---
84
+ fileInput.addEventListener('change', (e) => {
85
+ const file = e.target.files[0];
86
+ if (!file) return;
87
+
88
+ // 创建一个临时的 URL 让播放器能播放
89
+ const url = URL.createObjectURL(file);
90
+ audioPlayer.src = url;
91
+ audioPlayer.style.display = 'block'; // 显示播放器
92
 
93
+ resultArea.value = ""; // 清空上次结果
94
+ statusEl.innerText = "📂 文件已就绪,点击“开始转换”";
95
+ });
96
+
97
+ // --- 核心步骤 3: 执行转换 ---
98
+ runBtn.addEventListener('click', async () => {
99
+ const file = fileInput.files[0];
100
+ if (!file) { alert("请先选择文件!"); return; }
101
+
102
+ // UI 状态更新
103
+ runBtn.disabled = true;
104
+ statusEl.innerText = "🚀 正在转换中... (长音频请耐心等待)";
105
+ const startTime = performance.now();
106
+
107
  try {
108
+ // 将文件转为 Blob URL
109
+ const url = URL.createObjectURL(file);
110
+
111
+ // === 关键逻辑 ===
112
+ // 调用模型进行推理
113
+ const output = await transcriber(url, {
114
+ chunk_length_s: 30, // 关键:每30秒切一片,处理长音频必备
115
+ stride_length_s: 5, // 切片重叠长度,防止切断句子
116
+ language: 'chinese', // 强制中文模式 (如果全是英文可以改成 english)
117
+ task: 'transcribe', // 任务类型:转录
118
+ });
119
+ // ===============
120
+
121
+ const endTime = performance.now();
122
+ const timeCost = ((endTime - startTime) / 1000).toFixed(2);
123
+
124
+ // 显示结果
125
+ resultArea.value = output.text;
126
+ statusEl.innerText = `✅ 转换完成!耗时: ${timeCost}秒`;
127
+
128
  } catch (err) {
 
 
129
  console.error(err);
130
+ statusEl.innerText = "❌ 转换出错,请查看控制台(F12)";
131
+ resultArea.value = "错误详情:\n" + err.message;
132
+ } finally {
133
+ runBtn.disabled = false; // 恢复按钮
134
  }
135
  });
136
+
137
+ // 页面加载时自动启动模型下载
138
+ initModel();
139
+
140
  </script>
141
  </body>
142
  </html>