Lashtw commited on
Commit
3c8ce5d
·
verified ·
1 Parent(s): 1713756

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +81 -47
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>單字閃卡 (手寫測試版)</title>
7
  <!-- 載入 Tailwind CSS CDN -->
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <!-- 載入彩帶效果庫 -->
@@ -434,7 +434,7 @@
434
  </button>
435
  <button id="hw-recognize-btn" class="flex-[2] py-3 bg-indigo-600 text-white rounded-xl font-bold hover:bg-indigo-700 shadow-md transition-colors flex items-center justify-center gap-1">
436
  <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
437
- 辨識輸入
438
  </button>
439
  </div>
440
  </div>
@@ -668,6 +668,10 @@
668
  let currentStrokes = []; // 存放所有筆畫: [ [[x], [y], [t]], [[x], [y], [t]], ... ]
669
  let currentStroke = {x: [], y: [], t: []}; // 暫存當前筆畫
670
  let canvasCtx = null;
 
 
 
 
671
 
672
  document.addEventListener('DOMContentLoaded', () => {
673
  // DOM 元素
@@ -900,7 +904,7 @@
900
 
901
  // 3. 設定 Context
902
  canvasCtx = canvas.getContext('2d');
903
- canvasCtx.lineWidth = 5;
904
  canvasCtx.lineCap = 'round';
905
  canvasCtx.lineJoin = 'round';
906
  canvasCtx.strokeStyle = '#000000';
@@ -1032,67 +1036,91 @@
1032
  canvasCtx.stroke();
1033
  }
1034
 
1035
- // [修正] 辨識手寫 (呼叫 Google API) - 修正 INVALID_INPUT_METHOD_NAME
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1036
  async function recognizeHandwriting() {
 
1037
  if (currentStrokes.length === 0) {
1038
  showToast("請先手寫內容!");
1039
  return;
1040
  }
1041
 
1042
- hwRecognizeBtn.disabled = true;
1043
- hwRecognizeBtn.textContent = "辨識中...";
1044
-
1045
- const canvas = document.getElementById('handwriting-canvas');
1046
- const width = canvas.width;
1047
- const height = canvas.height;
1048
 
1049
- // [修正] 移除 language 欄位,避免參數衝突
1050
- const payload = {
1051
- "options": "enable_pre_space",
1052
- "requests": [{
1053
- "writing_guide": {
1054
- "writing_area_width": width,
1055
- "writing_area_height": height
1056
- },
1057
- "ink": currentStrokes
1058
- }]
1059
- };
1060
 
1061
  try {
1062
- // [修正] 改用最通用的 www.google.com 端點,並將 app 參數改為 translate
1063
- const response = await fetch('https://www.google.com/inputtools/request?itc=en-t-i0-handwriting&app=translate', {
1064
- method: 'POST',
1065
- headers: { 'Content-Type': 'application/json' },
1066
- body: JSON.stringify(payload)
1067
- });
1068
 
1069
- const result = await response.json();
 
 
1070
 
1071
- // 解析回傳資料: ["SUCCESS", [[["candidate1", ...], ...]]]
1072
- if (result[0] === 'SUCCESS' && result[1] && result[1][0] && result[1][0][1]) {
1073
- const candidates = result[1][0][1];
1074
-
1075
- // 1. 填入第一個候選字
1076
- answerInput.value = candidates[0];
1077
-
1078
- // 2. 顯示候選字列
1079
- showCandidates(candidates);
1080
 
1081
- // 視覺回饋
1082
  answerInput.classList.add('border-green-500', 'bg-green-50');
1083
  setTimeout(() => answerInput.classList.remove('border-green-500', 'bg-green-50'), 500);
 
 
 
 
1084
  } else {
1085
- console.warn("API 回傳格式不如預期", result);
1086
- if(result[0] === 'INVALID_INPUT_METHOD_NAME') {
1087
- showToast("API 錯誤:輸入法名稱無效,請確認網路環境或稍後再試。");
1088
- }
1089
  }
 
1090
  } catch (error) {
1091
- console.error("手寫辨識失敗:", error);
1092
- showToast("辨識失敗,請檢查網路連線。");
1093
  } finally {
1094
  hwRecognizeBtn.disabled = false;
1095
- hwRecognizeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg> 辨識輸入`;
1096
  }
1097
  }
1098
 
@@ -1137,12 +1165,18 @@
1137
  // [關鍵] 延遲時間設為 400ms,配合 CSS transition
1138
  setTimeout(() => initHandwritingBoard(), 400);
1139
 
1140
- // 3. 鎖定輸入框
 
 
 
 
 
 
1141
  answerInput.readOnly = true;
1142
  answerInput.placeholder = "請在下方手寫英文...";
1143
  answerInput.classList.add('handwriting-mode');
1144
 
1145
- // 4. 嘗試讓輸入框失去焦點,避免鍵盤彈出
1146
  if (document.activeElement === answerInput) {
1147
  answerInput.blur();
1148
  }
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>單字閃卡 (TrOCR AI 版)</title>
7
  <!-- 載入 Tailwind CSS CDN -->
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <!-- 載入彩帶效果庫 -->
 
434
  </button>
435
  <button id="hw-recognize-btn" class="flex-[2] py-3 bg-indigo-600 text-white rounded-xl font-bold hover:bg-indigo-700 shadow-md transition-colors flex items-center justify-center gap-1">
436
  <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
437
+ 辨識 (TrOCR AI)
438
  </button>
439
  </div>
440
  </div>
 
668
  let currentStrokes = []; // 存放所有筆畫: [ [[x], [y], [t]], [[x], [y], [t]], ... ]
669
  let currentStroke = {x: [], y: [], t: []}; // 暫存當前筆畫
670
  let canvasCtx = null;
671
+
672
+ // [新增] TrOCR 相關變數
673
+ let ocrPipeline = null; // 存放載入好的模型
674
+ let isModelLoading = false;
675
 
676
  document.addEventListener('DOMContentLoaded', () => {
677
  // DOM 元素
 
904
 
905
  // 3. 設定 Context
906
  canvasCtx = canvas.getContext('2d');
907
+ canvasCtx.lineWidth = 10; // [修改] 增加線條寬度以利 OCR 辨識
908
  canvasCtx.lineCap = 'round';
909
  canvasCtx.lineJoin = 'round';
910
  canvasCtx.strokeStyle = '#000000';
 
1036
  canvasCtx.stroke();
1037
  }
1038
 
1039
+ // [新增] 載入 TrOCR AI 模型 (使用 dynamic import)
1040
+ async function initOCR() {
1041
+ if (ocrPipeline) return; // 已載入
1042
+ if (isModelLoading) return; // 載入中
1043
+
1044
+ isModelLoading = true;
1045
+ showToast("正在下載 AI 手寫模型 (首次需約 200MB)...");
1046
+
1047
+ try {
1048
+ // 使用動態導入載入 Transformers.js
1049
+ const { pipeline, env } = await import('https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2');
1050
+
1051
+ // 設定環境:不使用本地模型 (因為是網頁版),強制使用 Browser Cache
1052
+ env.allowLocalModels = false;
1053
+ env.useBrowserCache = true;
1054
+
1055
+ // 載入 TrOCR 模型 (手寫文字辨識)
1056
+ // Xenova/trocr-small-handwritten 是量化後的版本,適合瀏覽器
1057
+ ocrPipeline = await pipeline('image-to-text', 'Xenova/trocr-small-handwritten', {
1058
+ progress_callback: (data) => {
1059
+ if (data.status === 'progress') {
1060
+ console.log(`Model loading: ${Math.round(data.progress || 0)}%`);
1061
+ }
1062
+ }
1063
+ });
1064
+
1065
+ showToast("AI 模型載入完成!");
1066
+ isModelLoading = false;
1067
+
1068
+ } catch (error) {
1069
+ console.error("AI 模型載入失敗:", error);
1070
+ showToast("模型載入失敗,請檢查網路連線");
1071
+ isModelLoading = false;
1072
+ }
1073
+ }
1074
+
1075
+ // [修正] 辨識手寫 (改用 TrOCR)
1076
  async function recognizeHandwriting() {
1077
+ // 1. 檢查是否有筆跡
1078
  if (currentStrokes.length === 0) {
1079
  showToast("請先手寫內容!");
1080
  return;
1081
  }
1082
 
1083
+ // 2. 確保模型已載入
1084
+ if (!ocrPipeline) {
1085
+ await initOCR();
1086
+ if (!ocrPipeline) return; // 載入失敗
1087
+ }
 
1088
 
1089
+ hwRecognizeBtn.disabled = true;
1090
+ hwRecognizeBtn.textContent = "AI 辨識中...";
 
 
 
 
 
 
 
 
 
1091
 
1092
  try {
1093
+ const canvas = document.getElementById('handwriting-canvas');
1094
+ // Canvas 轉為 Data URL
1095
+ const imageData = canvas.toDataURL("image/png");
 
 
 
1096
 
1097
+ // 呼叫 pipeline 進行辨識
1098
+ const output = await ocrPipeline(imageData);
1099
+ // 輸出格式通常是 [{ generated_text: "hello" }]
1100
 
1101
+ const text = output[0]?.generated_text?.trim();
1102
+
1103
+ if (text) {
1104
+ // TrOCR 可能會回傳句點,如果是單字練習可以移除
1105
+ const cleanedText = text.replace(/\.$/, '');
 
 
 
 
1106
 
1107
+ answerInput.value = cleanedText;
1108
  answerInput.classList.add('border-green-500', 'bg-green-50');
1109
  setTimeout(() => answerInput.classList.remove('border-green-500', 'bg-green-50'), 500);
1110
+ showToast("辨識結果:" + cleanedText);
1111
+
1112
+ // 也可以把原文當作候選字 (TrOCR 通常只回傳最可能的結果,不像 API 有多個候選)
1113
+ showCandidates([cleanedText]);
1114
  } else {
1115
+ showToast("未能辨識出文字");
 
 
 
1116
  }
1117
+
1118
  } catch (error) {
1119
+ console.error("AI 辨識失敗:", error);
1120
+ showToast("辨識發生錯誤: " + error.message);
1121
  } finally {
1122
  hwRecognizeBtn.disabled = false;
1123
+ hwRecognizeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /></svg> 辨識 (TrOCR)`;
1124
  }
1125
  }
1126
 
 
1165
  // [關鍵] 延遲時間設為 400ms,配合 CSS transition
1166
  setTimeout(() => initHandwritingBoard(), 400);
1167
 
1168
+ // 3. 觸發模型預載 (Lazy Load)
1169
+ // 當使用者切換到英文輸入模式時,就開始在背景下載模型
1170
+ if (!ocrPipeline && !isModelLoading) {
1171
+ initOCR();
1172
+ }
1173
+
1174
+ // 4. 鎖定輸入框
1175
  answerInput.readOnly = true;
1176
  answerInput.placeholder = "請在下方手寫英文...";
1177
  answerInput.classList.add('handwriting-mode');
1178
 
1179
+ // 5. 嘗試讓輸入框失去焦點,避免鍵盤彈出
1180
  if (document.activeElement === answerInput) {
1181
  answerInput.blur();
1182
  }