AKIRA commited on
Commit
b3b0b53
·
1 Parent(s): ad71343

Finalize all local changes

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .DS_Store +0 -0
  2. .gitignore +2 -0
  3. app.js +6 -0
  4. app.json +25 -0
  5. app.py +147 -113
  6. asr.py +31 -0
  7. asr_server.py +78 -0
  8. ecosystem.config.js +8 -0
  9. pages/index/.DS_Store +0 -0
  10. pages/index/index-original-全部依托于HF进行识别翻译的版本-可以运行.js +174 -0
  11. pages/index/index.js +42 -27
  12. pages/index/index.json +1 -0
  13. pages/index/index.wxml +49 -0
  14. pages/index/index.wxss +112 -0
  15. pages/index/index_v10_old.js +160 -0
  16. pages/index/index_v11_old.js +79 -0
  17. pages/index/index_v12_old.js +83 -0
  18. pages/index/index_v13_old.js +79 -0
  19. pages/index/index_v14_同声传译API_only-可以运行_old.js +223 -0
  20. pages/index/index_v15_old.js +192 -0
  21. pages/index/index_v16_old.js +192 -0
  22. pages/index/index_v17_old.js +222 -0
  23. pages/index/index_v18_old.js +231 -0
  24. pages/index/index_v19_old.js +234 -0
  25. pages/index/index_v20_old.js +179 -0
  26. pages/index/index_v21_old.js +177 -0
  27. pages/index/index_v22_old.js +197 -0
  28. pages/index/index_v24_old.js +197 -0
  29. pages/index/index_v25_old.js +203 -0
  30. pages/index/index_v26_old.js +150 -0
  31. pages/index/index_v27_old.js +159 -0
  32. pages/index/index_v28_old.js +139 -0
  33. pages/index/index_v2_old.js +232 -0
  34. pages/index/index_v3_old.js +173 -0
  35. pages/index/index_v4_old.js +169 -0
  36. pages/index/index_v5_old.js +162 -0
  37. pages/index/index_v6_old.js +150 -0
  38. pages/index/index_v7_old.js +157 -0
  39. pages/index/index_v8_old.js +153 -0
  40. pages/index/index_v9_old.js +75 -0
  41. pages/index/indexz_v23_old.js +180 -0
  42. project.config.json +68 -0
  43. project.private.config.json +22 -0
  44. requirements.txt +1 -2
  45. server/package-lock.json +917 -0
  46. server/package.json +19 -0
  47. server/server.js +97 -0
  48. server/server.js.old +64 -0
  49. start_asr.sh +30 -0
  50. translate.py +51 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Ignore local environment variables and secrets
2
+ .env
app.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ // app.js
2
+ App({
3
+ onLaunch() {
4
+ // 小程序启动时执行的逻辑
5
+ }
6
+ })
app.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "pages": [
3
+ "pages/index/index"
4
+ ],
5
+ "permission": {
6
+ "scope.record": {
7
+ "desc": "用于实时语音识别"
8
+ }
9
+ },
10
+ "window": {
11
+ "backgroundTextStyle": "light",
12
+ "navigationBarBackgroundColor": "#fff",
13
+ "navigationBarTitleText": "实时翻译",
14
+ "navigationBarTextStyle": "black"
15
+ },
16
+ "networkTimeout": {
17
+ "request": 10000,
18
+ "connectSocket": 10000,
19
+ "uploadFile": 10000,
20
+ "downloadFile": 10000
21
+ },
22
+ "requiredBackgroundModes": ["audio"],
23
+ "style": "v2",
24
+ "sitemapLocation": "sitemap.json"
25
+ }
app.py CHANGED
@@ -1,5 +1,4 @@
1
- import gradio as gr
2
- from transformers import pipeline, AutoProcessor
3
  from optimum.onnxruntime import ORTModelForSpeechSeq2Seq
4
  import torch
5
  import os
@@ -11,6 +10,10 @@ import uvicorn
11
  import deepl
12
  from dotenv import load_dotenv
13
  import soundfile as sf
 
 
 
 
14
 
15
  # --- Load environment variables and initialize DeepL ---
16
  load_dotenv()
@@ -20,157 +23,188 @@ deepl_translator = None
20
  if DEEPL_AUTH_KEY:
21
  try:
22
  deepl_translator = deepl.Translator(DEEPL_AUTH_KEY)
23
- print("DeepL translator initialized successfully.")
24
  except Exception as e:
25
- print(f"Error initializing DeepL translator: {e}")
26
- print("DeepL will be unavailable.")
27
  else:
28
- print("DEEPL_AUTH_KEY not found. DeepL will be unavailable.")
29
- # --- End ---
30
-
31
 
32
- # 1. Load Models
33
- print("Loading all models... This will take some time on startup.")
34
 
35
- # ASR Model - Using a CPU-optimized ONNX model for speed
36
- print("Loading optimized ASR model...")
37
  asr_model_id = "openai/whisper-base"
38
-
39
- # Load the model and processor using Optimum for ONNX runtime acceleration
40
- asr_model = ORTModelForSpeechSeq2Seq.from_pretrained(asr_model_id, provider="CPUExecutionProvider")
41
- asr_processor = AutoProcessor.from_pretrained(asr_model_id)
42
- print("Optimized ASR model loaded.")
43
-
44
-
45
- # Translation Pipelines - Reverting to the 6 core, absolutely reliable models
46
- translators = {
47
- "en-zh": pipeline("translation", model="Helsinki-NLP/opus-mt-en-zh"),
48
- "zh-en": pipeline("translation", model="Varine/opus-mt-zh-en-model"),
49
- "en-ja": pipeline("translation", model="staka/fugumt-en-ja"),
50
- "ja-en": pipeline("translation", model="Helsinki-NLP/opus-mt-ja-en"),
51
- "en-ko": pipeline("translation", model="Helsinki-NLP/opus-mt-tc-big-en-ko"),
52
- "ko-en": pipeline("translation", model="Helsinki-NLP/opus-mt-ko-en"),
53
- }
54
-
55
- print("All models loaded successfully.")
56
-
57
- # 2. Define Core Logic Functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def transcribe_audio(audio_bytes):
 
 
59
  try:
60
- # Use a temporary file to handle the audio bytes
61
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
62
  tmp_file.write(audio_bytes)
63
  audio_path = tmp_file.name
64
 
65
- # Read the audio file and process it
66
  audio_input, sample_rate = sf.read(audio_path)
67
-
68
- # Ensure the audio is in the correct format (mono, 16kHz)
69
  if audio_input.ndim > 1:
70
- audio_input = audio_input.mean(axis=1) # to mono
71
- if sample_rate != 16000:
72
- # This is a placeholder for resampling. For now, we assume frontend sends 16kHz.
73
- pass
74
 
75
- # Process audio and generate token IDs
76
  input_features = asr_processor(audio_input, sampling_rate=16000, return_tensors="pt").input_features
77
- predicted_ids = asr_model.generate(input_features)
78
 
79
- # Decode the token IDs to text
 
 
 
80
  text = asr_processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
 
 
81
 
82
  os.remove(audio_path)
83
  return text, None
84
  except Exception as e:
85
- # Clean up the temp file in case of an error
86
  if 'audio_path' in locals() and os.path.exists(audio_path):
87
  os.remove(audio_path)
88
  return None, str(e)
89
 
90
  def translate_text(text, source_lang, target_lang):
91
- if not text or not source_lang or not target_lang:
92
- return "", None
93
- if source_lang == target_lang:
94
- return text, None
95
-
96
- # --- DeepL Hybrid Logic ---
97
- if source_lang == 'zh' and target_lang == 'ja' and deepl_translator:
98
- print("Attempting translation with DeepL for zh -> ja")
99
  try:
100
- result = deepl_translator.translate_text(text, target_lang="JA")
 
 
101
  return result.text, None
102
  except Exception as e:
103
- print(f"DeepL API call failed: {e}. Falling back to Hugging Face model.")
104
- # --- End ---
105
-
106
- key = f"{source_lang}-{target_lang}"
107
- try:
108
- if key in translators:
109
- return translators[key](text)[0]['translation_text'], None
110
-
111
- elif source_lang != 'en' and target_lang != 'en':
112
- if f"{source_lang}-en" not in translators or f"en-{target_lang}" not in translators:
113
- return None, f"Bridge translation route not supported: {source_lang}-en or en-{target_lang}"
114
-
115
- print(f"Performing bridge translation: {source_lang} -> en -> {target_lang}")
116
- english_text = translators[f"{source_lang}-en"](text)[0]['translation_text']
117
- return translators[f"en-{target_lang}"](english_text)[0]['translation_text'], None
118
- else:
119
- return None, f"Translation route not supported: {key}"
120
- except Exception as e:
121
- return None, str(e)
122
 
123
- # 3. Create FastAPI App
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  app = FastAPI()
125
 
126
- # 4. Define API Endpoints with FastAPI
 
 
 
127
  @app.post("/api/asr")
128
  async def api_asr(request: Request):
129
- json_data = await request.json()
130
- audio_data_uri = json_data.get('audio_data_uri')
131
- if not audio_data_uri:
132
- return JSONResponse(status_code=400, content={"error": "No audio_data_uri provided"})
133
  try:
134
- header, encoded = audio_data_uri.split(",", 1)
135
- audio_bytes = base64.b64decode(encoded)
136
- transcript, error = transcribe_audio(audio_bytes)
 
 
 
 
 
 
 
137
  if error:
 
138
  return JSONResponse(status_code=500, content={"error": f"ASR Error: {error}"})
139
- return JSONResponse(status_code=200, content={"transcript": transcript})
 
 
 
 
140
  except Exception as e:
141
- return JSONResponse(status_code=500, content={"error": f"Server error: {e}"})
 
142
 
143
  @app.post("/api/translate")
144
  async def api_translate(request: Request):
145
- json_data = await request.json()
146
- text = json_data.get('text')
147
- source_lang = json_data.get('source_lang')
148
- target_lang = json_data.get('target_lang')
149
- if not all([text, source_lang, target_lang]):
150
- return JSONResponse(status_code=400, content={"error": "Missing parameters"})
151
-
152
- translated_text, error = translate_text(text, source_lang, target_lang)
153
- if error:
154
- return JSONResponse(status_code=500, content={"error": error})
155
- return JSONResponse(status_code=200, content={"translated_text": translated_text})
156
-
157
- # 5. Create a simple Gradio UI for debugging (Optional)
158
- def gradio_asr(audio_file):
159
- if audio_file is None:
160
- return ""
161
- # Gradio provides a file object, read its bytes
162
- audio_input, sample_rate = sf.read(audio_file.name)
163
- # Process audio and generate token IDs
164
- input_features = asr_processor(audio_input, sampling_rate=sample_rate, return_tensors="pt").input_features
165
- predicted_ids = asr_model.generate(input_features)
166
- # Decode the token IDs to text
167
- transcript = asr_processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
168
- return transcript
169
-
170
- gradio_ui = gr.Interface(fn=gradio_asr, inputs=gr.Audio(type="filepath"), outputs="text", title="ASR Debugger")
171
-
172
- # 6. Mount Gradio app onto FastAPI
173
- app = gr.mount_gradio_app(app, gradio_ui, path="/")
174
 
 
175
  if __name__ == "__main__":
176
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ from transformers import AutoProcessor
 
2
  from optimum.onnxruntime import ORTModelForSpeechSeq2Seq
3
  import torch
4
  import os
 
10
  import deepl
11
  from dotenv import load_dotenv
12
  import soundfile as sf
13
+ import logging
14
+
15
+ # --- Basic Configuration ---
16
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
 
18
  # --- Load environment variables and initialize DeepL ---
19
  load_dotenv()
 
23
  if DEEPL_AUTH_KEY:
24
  try:
25
  deepl_translator = deepl.Translator(DEEPL_AUTH_KEY)
26
+ logging.info("DeepL translator initialized successfully.")
27
  except Exception as e:
28
+ logging.error(f"Error initializing DeepL translator: {e}")
 
29
  else:
30
+ logging.warning("DEEPL_AUTH_KEY not found. DeepL will be unavailable.")
 
 
31
 
32
+ # --- Load Models ---
33
+ logging.info("Loading all models...")
34
 
35
+ # ASR Model
 
36
  asr_model_id = "openai/whisper-base"
37
+ asr_model = None
38
+ asr_processor = None
39
+ try:
40
+ asr_model = ORTModelForSpeechSeq2Seq.from_pretrained(asr_model_id, provider="CPUExecutionProvider")
41
+ asr_processor = AutoProcessor.from_pretrained(asr_model_id)
42
+
43
+ # FINAL, CRITICAL FIX: The model's default config has a conflicting 'forced_decoder_ids'
44
+ # that clashes with the latest library versions. The library both requires this attribute
45
+ # to exist, but also requires it to be None to avoid a conflict.
46
+ if hasattr(asr_model.config, 'forced_decoder_ids'):
47
+ logging.info("Found conflicting 'forced_decoder_ids' in model config. Setting to None.")
48
+ asr_model.config.forced_decoder_ids = None
49
+
50
+ if hasattr(asr_model.generation_config, 'forced_decoder_ids'):
51
+ logging.info("Found conflicting 'forced_decoder_ids' in generation_config. Setting to None.")
52
+ asr_model.generation_config.forced_decoder_ids = None
53
+
54
+ logging.info("ASR model and processor loaded and configured successfully.")
55
+ except Exception as e:
56
+ logging.error(f"Fatal error loading ASR model: {e}", exc_info=True)
57
+
58
+ # Translation Pipelines
59
+ from transformers import pipeline
60
+ translators = {}
61
+ try:
62
+ translators = {
63
+ "en-zh": pipeline("translation", model="Helsinki-NLP/opus-mt-en-zh"),
64
+ "zh-en": pipeline("translation", model="Helsinki-NLP/opus-mt-zh-en"),
65
+ "en-ja": pipeline("translation", model="staka/fugumt-en-ja"),
66
+ "ja-en": pipeline("translation", model="Helsinki-NLP/opus-mt-ja-en"),
67
+ "en-ko": pipeline("translation", model="Helsinki-NLP/opus-mt-tc-big-en-ko"),
68
+ "ko-en": pipeline("translation", model="Helsinki-NLP/opus-mt-ko-en"),
69
+ }
70
+ logging.info("Translation models loaded successfully.")
71
+ except Exception as e:
72
+ logging.error(f"Failed to load translation models: {e}")
73
+
74
+ # --- Core Logic Functions ---
75
  def transcribe_audio(audio_bytes):
76
+ if not asr_model or not asr_processor:
77
+ return None, "ASR model or processor is not available."
78
  try:
 
79
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
80
  tmp_file.write(audio_bytes)
81
  audio_path = tmp_file.name
82
 
 
83
  audio_input, sample_rate = sf.read(audio_path)
 
 
84
  if audio_input.ndim > 1:
85
+ audio_input = audio_input.mean(axis=1)
 
 
 
86
 
 
87
  input_features = asr_processor(audio_input, sampling_rate=16000, return_tensors="pt").input_features
 
88
 
89
+ # By setting forced_decoder_ids to None in the config, we can now safely
90
+ # let the generate function handle the task without conflicts.
91
+ predicted_ids = asr_model.generate(input_features, task="transcribe")
92
+
93
  text = asr_processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
94
+
95
+ logging.info(f"ASR transcribed text: '{text}'")
96
 
97
  os.remove(audio_path)
98
  return text, None
99
  except Exception as e:
100
+ logging.error(f"ASR transcription failed: {e}", exc_info=True)
101
  if 'audio_path' in locals() and os.path.exists(audio_path):
102
  os.remove(audio_path)
103
  return None, str(e)
104
 
105
  def translate_text(text, source_lang, target_lang):
106
+ # Priority 1: Use DeepL for specific, high-quality pairs if available
107
+ if deepl_translator and ((source_lang == 'zh' and target_lang == 'ja') or (source_lang == 'en' and target_lang == 'ja')):
 
 
 
 
 
 
108
  try:
109
+ dl_source_lang = "ZH" if source_lang == 'zh' else "EN"
110
+ logging.info(f"Attempting DeepL translation for {source_lang} -> {target_lang}")
111
+ result = deepl_translator.translate_text(text, source_lang=dl_source_lang, target_lang="JA")
112
  return result.text, None
113
  except Exception as e:
114
+ logging.error(f"DeepL failed: {e}. Falling back to HF models.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ # Priority 2: Try direct HF translation
117
+ model_key = f"{source_lang}-{target_lang}"
118
+ translator = translators.get(model_key)
119
+ if translator:
120
+ try:
121
+ logging.info(f"Attempting direct HF translation for {model_key}")
122
+ translated_text = translator(text, max_length=512)[0]['translation_text']
123
+ return translated_text, None
124
+ except Exception as e:
125
+ logging.error(f"Direct HF translation for {model_key} failed: {e}", exc_info=True)
126
+ # Don't return here, allow fallback to pivot
127
+
128
+ # Priority 3: Try pivot translation via English
129
+ if source_lang != 'en' and target_lang != 'en':
130
+ to_en_key = f"{source_lang}-en"
131
+ from_en_key = f"en-{target_lang}"
132
+ translator_to_en = translators.get(to_en_key)
133
+ translator_from_en = translators.get(from_en_key)
134
+
135
+ if translator_to_en and translator_from_en:
136
+ try:
137
+ logging.info(f"Attempting pivot translation for {source_lang} -> en -> {target_lang}")
138
+ # Step 1: Source to English
139
+ english_text = translator_to_en(text, max_length=512)[0]['translation_text']
140
+ logging.info(f"Pivot step (to en) result: '{english_text}'")
141
+ # Step 2: English to Target
142
+ final_text = translator_from_en(english_text, max_length=512)[0]['translation_text']
143
+ logging.info(f"Pivot step (from en) result: '{final_text}'")
144
+ return final_text, None
145
+ except Exception as e:
146
+ logging.error(f"Pivot translation failed: {e}", exc_info=True)
147
+
148
+ # If all else fails
149
+ logging.warning(f"No translation path found for {source_lang} -> {target_lang}")
150
+ return None, f"No model available for {source_lang} to {target_lang}"
151
+
152
+ # --- FastAPI App ---
153
  app = FastAPI()
154
 
155
+ @app.get("/")
156
+ def root():
157
+ return {"status": "ok", "message": "Translator API is running."}
158
+
159
  @app.post("/api/asr")
160
  async def api_asr(request: Request):
 
 
 
 
161
  try:
162
+ body = await request.json()
163
+ audio_b64 = body.get('audio_base64')
164
+ if not audio_b64:
165
+ logging.error("Request is missing 'audio_base64'")
166
+ return JSONResponse(status_code=400, content={"error": "No audio_base64 found in request"})
167
+
168
+ audio_bytes = base64.b64decode(audio_b64)
169
+
170
+ text, error = transcribe_audio(audio_bytes)
171
+
172
  if error:
173
+ logging.error(f"ASR transcription function returned an error: {error}")
174
  return JSONResponse(status_code=500, content={"error": f"ASR Error: {error}"})
175
+
176
+ response_data = {"text": text}
177
+ logging.info(f"Returning ASR response: {response_data}")
178
+ return JSONResponse(content=response_data)
179
+
180
  except Exception as e:
181
+ logging.error(f"Critical error in /api/asr endpoint: {e}", exc_info=True)
182
+ return JSONResponse(status_code=500, content={"error": str(e)})
183
 
184
  @app.post("/api/translate")
185
  async def api_translate(request: Request):
186
+ try:
187
+ body = await request.json()
188
+ text = body.get('text')
189
+ source_lang = body.get('source_lang')
190
+ target_lang = body.get('target_lang')
191
+
192
+ if not all([text, source_lang, target_lang]):
193
+ return JSONResponse(status_code=400, content={"error": "Missing parameters: text, source_lang, or target_lang"})
194
+
195
+ translated_text, error = translate_text(text, source_lang, target_lang)
196
+
197
+ if error:
198
+ return JSONResponse(status_code=500, content={"error": f"Translation Error: {error}"})
199
+
200
+ response_data = {"translated_text": translated_text}
201
+ logging.info(f"Returning translation response: {response_data}")
202
+ return JSONResponse(content=response_data)
203
+
204
+ except Exception as e:
205
+ logging.error(f"Error in /api/translate endpoint: {e}", exc_info=True)
206
+ return JSONResponse(status_code=500, content={"error": str(e)})
 
 
 
 
 
 
 
 
207
 
208
+ # --- Main Execution ---
209
  if __name__ == "__main__":
210
+ uvicorn.run(app, host="0.0.0.0", port=7860)
asr.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import whisper
3
+
4
+ def transcribe_audio(file_path):
5
+ """
6
+ Transcribes an audio file using Whisper.
7
+ """
8
+ try:
9
+ # Load the base model. You can change this to 'tiny', 'small', 'medium', or 'large'
10
+ # depending on your server's performance and desired accuracy.
11
+ # 'base' is a good starting point.
12
+ model = whisper.load_model("base")
13
+
14
+ # Transcribe the audio file
15
+ result = model.transcribe(file_path)
16
+
17
+ # Return the transcribed text
18
+ return result["text"]
19
+ except Exception as e:
20
+ # Return the error message if something goes wrong
21
+ return f"Error during transcription: {str(e)}"
22
+
23
+ if __name__ == "__main__":
24
+ # The script expects exactly one argument: the audio file path
25
+ if len(sys.argv) != 2:
26
+ print("Usage: python asr.py <audio_file_path>")
27
+ sys.exit(1)
28
+
29
+ audio_file = sys.argv[1]
30
+ transcribed_text = transcribe_audio(audio_file)
31
+ print(transcribed_text)
asr_server.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import flask
2
+ from flask import request, jsonify
3
+ import logging
4
+ import numpy as np
5
+ import ffmpeg
6
+ from faster_whisper import WhisperModel
7
+ import os
8
+
9
+ # --- Configuration from Environment Variables ---
10
+ MODEL_NAME = os.getenv("MODEL_NAME", "small")
11
+ CPU_THREADS = int(os.getenv("CPU_THREADS", "2"))
12
+
13
+ # --- Setup ---
14
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
+
16
+ # --- Load Model with Performance Optimizations ---
17
+ model = None
18
+ try:
19
+ logging.info(f"Loading faster-whisper model '{MODEL_NAME}' with {CPU_THREADS} threads...")
20
+ # 回退到兼容性最好的 int8 计算模式
21
+ model = WhisperModel(MODEL_NAME, device="cpu", compute_type="int8", cpu_threads=CPU_THREADS, num_workers=1)
22
+ logging.info(f"Whisper '{MODEL_NAME}' model loaded successfully.")
23
+ except Exception as e:
24
+ logging.error(f"Fatal error: Could not load model. {e}")
25
+ exit(1)
26
+
27
+ # --- Initialize Flask App ---
28
+ app = flask.Flask(__name__)
29
+
30
+ def load_audio_from_buffer(buffer, sr=16000):
31
+ """Decodes audio from an in-memory buffer using ffmpeg."""
32
+ try:
33
+ out, _ = (
34
+ ffmpeg.input('pipe:', threads=0)
35
+ .output('pipe:', format='s16le', acodec='pcm_s16le', ac=1, ar=sr)
36
+ .run(input=buffer, capture_stdout=True, capture_stderr=True)
37
+ )
38
+ except ffmpeg.Error as e:
39
+ raise RuntimeError(f"Failed to load audio with ffmpeg: {e.stderr.decode()}") from e
40
+ return np.frombuffer(out, np.int16).flatten().astype(np.float32) / 32768.0
41
+
42
+ @app.route('/transcribe', methods=['POST'])
43
+ def transcribe_audio():
44
+ if 'audio' not in request.files:
45
+ return jsonify({"error": "No audio file part in the request"}), 400
46
+
47
+ file = request.files['audio']
48
+ if file.filename == '':
49
+ return jsonify({"error": "No selected file"}), 400
50
+
51
+ try:
52
+ audio_buffer = file.read()
53
+ audio_np = load_audio_from_buffer(audio_buffer)
54
+
55
+ source_lang_full = request.form.get('sourceLang')
56
+ lang_code = None
57
+ if source_lang_full:
58
+ lang_code = source_lang_full.split('-')[0]
59
+ logging.info(f"Client requested language: {lang_code}")
60
+
61
+ # 保留有���的性能优化: 减小 beam_size 并调整 VAD
62
+ segments, info = model.transcribe(
63
+ audio_np,
64
+ language=lang_code,
65
+ beam_size=2, # 从默认的5减小到2,显著降低计算量
66
+ vad_filter=True,
67
+ vad_parameters=dict(min_silence_duration_ms=700) # 稍微增加静音检测时长
68
+ )
69
+
70
+ transcript = "".join(segment.text for segment in segments).strip()
71
+
72
+ logging.info(f"Detected language '{info.language}' with probability {info.language_probability:.2f}")
73
+ logging.info(f"Transcription result: '{transcript}'")
74
+ return jsonify({"transcript": transcript})
75
+
76
+ except Exception as e:
77
+ logging.error(f"Error during transcription: {e}", exc_info=True)
78
+ return jsonify({"error": f"Transcription failed: {str(e)}"}), 500
ecosystem.config.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ apps: [
3
+ {
4
+ name: 'translator-server',
5
+ script: './server/server.js',
6
+ },
7
+ ],
8
+ };
pages/index/.DS_Store ADDED
Binary file (6.15 kB). View file
 
pages/index/index-original-全部依托于HF进行识别翻译的版本-可以运行.js ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Page({
2
+ data: {
3
+ languages: {
4
+ 'zh': { name: '中文', flag: 'cn' },
5
+ 'en': { name: 'English', flag: 'us' },
6
+ 'ja': { name: '日本語', flag: 'jp' },
7
+ 'ko': { name: '한국어', flag: 'kr' }
8
+ },
9
+ langCodes: ['zh', 'en', 'ja', 'ko'],
10
+ sourceLang: 'zh',
11
+ targetLang: 'en',
12
+ transcript: '',
13
+ outputText: '',
14
+ isRecording: false,
15
+ sourceLanguages: [],
16
+ targetLanguages: [],
17
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
18
+ },
19
+
20
+ onLoad: function () {
21
+ this.initializeLanguages();
22
+ this.recorderManager = wx.getRecorderManager();
23
+ this.initRecorderManager();
24
+ },
25
+
26
+ initializeLanguages: function () {
27
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
28
+ const sourceLanguages = langCodes.map(code => ({
29
+ langCode: code,
30
+ name: languages[code].name,
31
+ flag: languages[code].flag,
32
+ selected: code === sourceLang
33
+ }));
34
+ const targetLanguages = langCodes.map(code => ({
35
+ langCode: code,
36
+ name: languages[code].name,
37
+ flag: languages[code].flag,
38
+ selected: code === targetLang
39
+ }));
40
+ this.setData({ sourceLanguages, targetLanguages });
41
+ },
42
+
43
+ selectSourceLanguage: function (e) {
44
+ const newSourceLang = e.currentTarget.dataset.langCode;
45
+ this.setData({ sourceLang: newSourceLang }, () => {
46
+ this.initializeLanguages();
47
+ if (this.data.transcript.trim() && this.data.transcript !== '正在聆听...' && this.data.transcript !== '未能识别到语音') {
48
+ this.translate(this.data.transcript);
49
+ }
50
+ });
51
+ },
52
+
53
+ selectTargetLanguage: function (e) {
54
+ const newTargetLang = e.currentTarget.dataset.langCode;
55
+ this.setData({ targetLang: newTargetLang }, () => {
56
+ this.initializeLanguages();
57
+ if (this.data.transcript.trim() && this.data.transcript !== '正在聆听...' && this.data.transcript !== '未能识别到语音') {
58
+ this.translate(this.data.transcript);
59
+ }
60
+ });
61
+ },
62
+
63
+ swapLanguages: function () {
64
+ let { sourceLang, targetLang, transcript, outputText } = this.data;
65
+ const tempLang = sourceLang;
66
+ sourceLang = targetLang;
67
+ targetLang = tempLang;
68
+ const tempText = transcript;
69
+ transcript = outputText;
70
+ outputText = tempText;
71
+ this.setData({ sourceLang, targetLang, transcript, outputText }, () => {
72
+ this.initializeLanguages();
73
+ if (this.data.transcript.trim() && this.data.transcript !== '正在聆听...' && this.data.transcript !== '未能识别到语音') {
74
+ this.translate(this.data.transcript);
75
+ }
76
+ });
77
+ },
78
+
79
+ initRecorderManager: function () {
80
+ this.recorderManager.onStart(() => {
81
+ this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
82
+ });
83
+
84
+ this.recorderManager.onStop((res) => {
85
+ this.setData({ isRecording: false });
86
+ if (res.tempFilePath) {
87
+ this.uploadAudioForASR(res.tempFilePath);
88
+ } else {
89
+ this.setData({ transcript: '录音文件创建失败' });
90
+ }
91
+ });
92
+
93
+ this.recorderManager.onError(() => {
94
+ this.setData({ isRecording: false, transcript: '语音识别出错' });
95
+ });
96
+ },
97
+
98
+ startRecording: function () {
99
+ this.recorderManager.start({ duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: 'mp3' });
100
+ },
101
+
102
+ stopRecording: function () {
103
+ this.recorderManager.stop();
104
+ },
105
+
106
+ uploadAudioForASR: function (filePath) {
107
+ this.setData({ transcript: '正在识别...' });
108
+ const fileSystemManager = wx.getFileSystemManager();
109
+ fileSystemManager.readFile({
110
+ filePath: filePath,
111
+ encoding: 'base64',
112
+ success: (res) => {
113
+ const base64Data = res.data;
114
+ const dataUri = 'data:audio/mp3;base64,' + base64Data;
115
+
116
+ wx.request({
117
+ url: `${this.data.hfSpaceUrl}/api/asr`,
118
+ method: 'POST',
119
+ header: { 'Content-Type': 'application/json' },
120
+ data: { "audio_data_uri": dataUri },
121
+ timeout: 60000,
122
+ success: (res) => {
123
+ if (res.statusCode === 200 && res.data && res.data.transcript) {
124
+ const transcript = res.data.transcript;
125
+ this.setData({ transcript: transcript });
126
+ this.translate(transcript);
127
+ } else {
128
+ this.setData({ transcript: '语音识别失败' });
129
+ console.error('ASR Error:', res.data.error);
130
+ }
131
+ },
132
+ fail: (err) => {
133
+ this.setData({ transcript: '语音识别请求失败' });
134
+ }
135
+ });
136
+ },
137
+ fail: (err) => {
138
+ this.setData({ transcript: '读取音频文件失败' });
139
+ }
140
+ });
141
+ },
142
+
143
+ translate: function (text) {
144
+ if (!text) return;
145
+ const { sourceLang, targetLang } = this.data;
146
+ if (sourceLang === targetLang) {
147
+ this.setData({ outputText: text });
148
+ return;
149
+ }
150
+ this.setData({ outputText: '正在翻译...' });
151
+ wx.request({
152
+ url: `${this.data.hfSpaceUrl}/api/translate`,
153
+ method: 'POST',
154
+ header: { 'Content-Type': 'application/json' },
155
+ data: {
156
+ "text": text,
157
+ "source_lang": sourceLang,
158
+ "target_lang": targetLang
159
+ },
160
+ timeout: 30000,
161
+ success: (res) => {
162
+ if (res.statusCode === 200 && res.data && res.data.translated_text) {
163
+ this.setData({ outputText: res.data.translated_text });
164
+ } else {
165
+ this.setData({ outputText: '翻译失败' });
166
+ console.error('Translate Error:', res.data.error);
167
+ }
168
+ },
169
+ fail: (err) => {
170
+ this.setData({ outputText: '翻译出错' });
171
+ }
172
+ });
173
+ }
174
+ });
pages/index/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // FINAL VERSION: v29 - Based on the user's working original file
2
 
3
  // Helper function to show detailed errors
4
  function showDetailedError(title, content) {
@@ -26,28 +26,24 @@ Page({
26
  },
27
 
28
  onLoad: function () {
29
- // Use the working pattern: attach recorderManager to `this`
30
  this.recorderManager = wx.getRecorderManager();
31
  this.initRecorderManager();
32
-
33
- // Use the improved, simpler language list setup
34
  this.setData({
35
  sourceLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key })),
36
  targetLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key }))
37
  });
38
  },
39
 
40
- // --- Language Selection & UI (Simplified) ---
41
  selectSourceLanguage: function (e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }); },
42
  selectTargetLanguage: function (e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }); },
43
  swapLanguages: function () {
44
  this.setData({ sourceLang: this.data.targetLang, targetLang: this.data.sourceLang, transcript: this.data.outputText, outputText: this.data.transcript });
45
  },
46
 
47
- // --- Unified Native Recorder Initialization (Correct Pattern) ---
48
  initRecorderManager: function () {
49
  this.recorderManager.onStart(() => {
50
- // Correct pattern: Set UI state *inside* the onStart callback
51
  this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
52
  });
53
 
@@ -56,7 +52,6 @@ Page({
56
  if (res.tempFilePath) {
57
  this.uploadAudioForASR(res.tempFilePath);
58
  } else {
59
- // This case might happen if recording is too short
60
  this.setData({ transcript: '录音时间太短或无效' });
61
  }
62
  });
@@ -67,7 +62,7 @@ Page({
67
  });
68
  },
69
 
70
- // --- Main Record Button Handler (with Permissions) ---
71
  handleRecordToggle: function() {
72
  if (this.data.isRecording) {
73
  this.stopRecording();
@@ -85,14 +80,14 @@ Page({
85
  });
86
  },
87
 
88
- // --- Unified Start/Stop Recording ---
89
  startRecording: function () {
90
  const options = {
91
- duration: 60000, // Max recording duration: 60s
92
- sampleRate: 16000, // For ASR, 16kHz is the standard
93
- numberOfChannels: 1, // Mono audio is sufficient
94
- encodeBitRate: 48000, // 48kbps is a good balance for speech
95
- format: 'mp3' // Use mp3 format
96
  };
97
  this.recorderManager.start(options);
98
  },
@@ -101,25 +96,41 @@ Page({
101
  this.recorderManager.stop();
102
  },
103
 
104
- // --- Unified Backend ASR & Translation Flow ---
105
  uploadAudioForASR: function (filePath) {
106
  this.setData({ transcript: '正在识别...' });
107
  wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
108
  wx.request({
109
  url: `${this.data.hfSpaceUrl}/api/asr`,
110
  method: 'POST',
111
- data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
112
- timeout: 60000,
113
  success: (asrRes) => {
114
- if (asrRes.statusCode === 200 && asrRes.data.transcript) {
115
- const transcript = asrRes.data.transcript;
116
- this.setData({ transcript });
117
- this.translate(transcript);
 
 
 
 
 
 
 
118
  } else {
119
- showDetailedError('语音识别失败', asrRes.data);
 
 
120
  }
121
  },
122
- fail: (err) => showDetailedError('识别请求失败', err)
 
 
 
 
 
 
 
123
  });
124
  }});
125
  },
@@ -137,13 +148,17 @@ Page({
137
  data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
138
  timeout: 45000,
139
  success: (res) => {
140
- if (res.statusCode === 200 && res.data.translated_text) {
141
  this.setData({ outputText: res.data.translated_text });
142
  } else {
143
- showDetailedError('翻译失败', res.data);
 
144
  }
145
  },
146
- fail: (err) => showDetailedError('翻译请求失败', err)
 
 
 
147
  });
148
  }
149
  });
 
1
+ // FINAL VERSION: v30 - Robust error handling and UI updates
2
 
3
  // Helper function to show detailed errors
4
  function showDetailedError(title, content) {
 
26
  },
27
 
28
  onLoad: function () {
 
29
  this.recorderManager = wx.getRecorderManager();
30
  this.initRecorderManager();
 
 
31
  this.setData({
32
  sourceLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key })),
33
  targetLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key }))
34
  });
35
  },
36
 
37
+ // --- Language Selection & UI ---
38
  selectSourceLanguage: function (e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }); },
39
  selectTargetLanguage: function (e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }); },
40
  swapLanguages: function () {
41
  this.setData({ sourceLang: this.data.targetLang, targetLang: this.data.sourceLang, transcript: this.data.outputText, outputText: this.data.transcript });
42
  },
43
 
44
+ // --- Recorder Initialization ---
45
  initRecorderManager: function () {
46
  this.recorderManager.onStart(() => {
 
47
  this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
48
  });
49
 
 
52
  if (res.tempFilePath) {
53
  this.uploadAudioForASR(res.tempFilePath);
54
  } else {
 
55
  this.setData({ transcript: '录音时间太短或无效' });
56
  }
57
  });
 
62
  });
63
  },
64
 
65
+ // --- Record Button Handler ---
66
  handleRecordToggle: function() {
67
  if (this.data.isRecording) {
68
  this.stopRecording();
 
80
  });
81
  },
82
 
83
+ // --- Start/Stop Recording ---
84
  startRecording: function () {
85
  const options = {
86
+ duration: 60000,
87
+ sampleRate: 16000,
88
+ numberOfChannels: 1,
89
+ encodeBitRate: 48000,
90
+ format: 'mp3'
91
  };
92
  this.recorderManager.start(options);
93
  },
 
96
  this.recorderManager.stop();
97
  },
98
 
99
+ // --- ASR & Translation Flow ---
100
  uploadAudioForASR: function (filePath) {
101
  this.setData({ transcript: '正在识别...' });
102
  wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
103
  wx.request({
104
  url: `${this.data.hfSpaceUrl}/api/asr`,
105
  method: 'POST',
106
+ data: { "audio_base64": res.data },
107
+ timeout: 120000,
108
  success: (asrRes) => {
109
+ console.log("ASR Response:", asrRes); // Log for debugging
110
+
111
+ if (asrRes.statusCode === 200 && asrRes.data && typeof asrRes.data.text !== 'undefined') {
112
+ const transcript = asrRes.data.text;
113
+ if (transcript) {
114
+ this.setData({ transcript });
115
+ this.translate(transcript);
116
+ } else {
117
+ // Handle successful response with empty transcript
118
+ this.setData({ transcript: '未能识别到语音,请重试。' });
119
+ }
120
  } else {
121
+ // Handle non-200 responses or malformed data
122
+ this.setData({ transcript: '识别失败,请重试。' });
123
+ showDetailedError('语音识别失败', asrRes.data || '服务器返回异常');
124
  }
125
  },
126
+ fail: (err) => {
127
+ this.setData({ transcript: '识别请求失败。' });
128
+ if (err.errMsg && err.errMsg.includes('timeout')) {
129
+ showDetailedError('识别超时', '服务器处理时间过长,请稍后再试或尝试更短的语音。');
130
+ } else {
131
+ showDetailedError('识别请求失败', err);
132
+ }
133
+ }
134
  });
135
  }});
136
  },
 
148
  data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
149
  timeout: 45000,
150
  success: (res) => {
151
+ if (res.statusCode === 200 && res.data && res.data.translated_text) {
152
  this.setData({ outputText: res.data.translated_text });
153
  } else {
154
+ this.setData({ outputText: '翻译失败' });
155
+ showDetailedError('翻译失败', res.data || '服务器返回异常');
156
  }
157
  },
158
+ fail: (err) => {
159
+ this.setData({ outputText: '翻译请求失败' });
160
+ showDetailedError('翻译请求失败', err);
161
+ }
162
  });
163
  }
164
  });
pages/index/index.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
pages/index/index.wxml ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- wechat-miniprogram-translator/index.wxml -->
2
+ <view class="container">
3
+ <!-- IO Boxes -->
4
+ <view class="io-container">
5
+ <!-- Source Box -->
6
+ <view class="io-box" id="source-box">
7
+ <view class="language-panel">
8
+ <view class="panel-label">您说的话 (选择语言)</view>
9
+ <view class="language-buttons">
10
+ <block wx:for="{{sourceLanguages}}" wx:key="langCode">
11
+ <view class="lang-button {{sourceLang === item.langCode ? 'selected' : ''}}" data-lang-code="{{item.langCode}}" bindtap="selectSourceLanguage">
12
+ <image src="https://flagcdn.com/w40/{{item.flag}}.png" alt="{{item.name}}" class="flag-icon"></image>
13
+ </view>
14
+ </block>
15
+ </view>
16
+ </view>
17
+ <scroll-view scroll-y class="text-content" id="transcript">{{transcript}}</scroll-view>
18
+ </view>
19
+
20
+ <!-- Swap Button -->
21
+ <view class="swap-controls">
22
+ <button id="swap-btn" title="交换语言" bindtap="swapLanguages">
23
+ <text>🔄</text> <!-- Swap icon -->
24
+ </button>
25
+ </view>
26
+
27
+ <!-- Target Box -->
28
+ <view class="io-box" id="target-box">
29
+ <view class="language-panel">
30
+ <view class="panel-label">翻译成 (选择语言)</view>
31
+ <view class="language-buttons">
32
+ <block wx:for="{{targetLanguages}}" wx:key="langCode">
33
+ <view class="lang-button {{targetLang === item.langCode ? 'selected' : ''}}" data-lang-code="{{item.langCode}}" bindtap="selectTargetLanguage">
34
+ <image src="https://flagcdn.com/w40/{{item.flag}}.png" alt="{{item.name}}" class="flag-icon"></image>
35
+ </view>
36
+ </block>
37
+ </view>
38
+ </view>
39
+ <scroll-view scroll-y class="text-content" id="output-text">{{outputText}}</scroll-view>
40
+ </view>
41
+ </view>
42
+ </view>
43
+
44
+ <!-- Floating Mic Button -->
45
+ <view class="mic-container">
46
+ <button id="mic-btn" title="按住说话" class="{{isRecording ? 'recording' : ''}}" bindtouchstart="handleRecordToggle" bindtouchend="stopRecording">
47
+ <text>🎤</text> <!-- Microphone icon -->
48
+ </button>
49
+ </view>
pages/index/index.wxss ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* wechat-miniprogram-translator/index.wxss */
2
+ :root {
3
+ --background-color: #f0f2f5;
4
+ --card-bg: #ffffff;
5
+ --primary-text: #1c1e21;
6
+ --secondary-text: #606770;
7
+ --border-color: #e0e0e0;
8
+ --accent-color: #007bff;
9
+ --mic-button-bg: #28a745;
10
+ --mic-recording-bg: #dc3545;
11
+ /* Note: WeChat Mini Program does not fully support custom font families directly via CSS import. */
12
+ --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
13
+ }
14
+ page {
15
+ height: 100%;
16
+ background-color: var(--background-color);
17
+ }
18
+ body {
19
+ /* In Mini Program, use 'page' for root styling, 'body' is not applicable */
20
+ }
21
+ .container {
22
+ width: 100%;
23
+ max-width: 500px; /* Optimized for mobile width */
24
+ margin: 0 auto;
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 1rem;
28
+ height: 100vh; /* Use 100vh for full height in Mini Program */
29
+ padding: 1rem;
30
+ box-sizing: border-box;
31
+ }
32
+
33
+ .io-container {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 100rpx; /* Adjust this value as needed */
37
+ }
38
+ .language-panel {
39
+ background-color: var(--card-bg);
40
+ padding: 0.75rem;
41
+ border-radius: 12px;
42
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
43
+ }
44
+ .panel-label {
45
+ font-size: 0.9rem;
46
+ font-weight: 600;
47
+ margin-bottom: 0.75rem;
48
+ text-align: center;
49
+ color: var(--primary-text);
50
+ }
51
+ .language-buttons {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ justify-content: center;
55
+ gap: 0.5rem;
56
+ }
57
+ .lang-button {
58
+ display: inline-flex; /* Use inline-flex to allow spacing */
59
+ flex-direction: column;
60
+ align-items: center;
61
+ justify-content: center;
62
+ width: 44px; /* Set a fixed width for the button */
63
+ height: 44px; /* Set a fixed height to make it a square */
64
+ margin: 5px;
65
+ padding: 0;
66
+ border: 2px solid transparent; /* Start with a transparent border */
67
+ border-radius: 50%; /* Make it a circle */
68
+ background-color: #f0f0f0;
69
+ transition: all 0.2s ease-in-out;
70
+ box-sizing: border-box; /* Ensure padding and border are inside the width/height */
71
+ }
72
+
73
+ .lang-button.selected {
74
+ border-color: #007aff; /* Blue border for selected state */
75
+ transform: scale(1.1); /* Slightly enlarge the selected button */
76
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
77
+ }
78
+
79
+ .flag-icon {
80
+ width: 32px; /* Adjust flag size to fit inside the circle */
81
+ height: 32px;
82
+ border-radius: 50%; /* Make the flag image itself circular */
83
+ object-fit: cover; /* Ensure the image covers the area nicely */
84
+ }
85
+
86
+ .mic-container {
87
+ position: fixed;
88
+ bottom: 30px;
89
+ left: 50%;
90
+ transform: translateX(-50%);
91
+ z-index: 100;
92
+ }
93
+
94
+ #mic-btn {
95
+ width: 70px;
96
+ height: 70px;
97
+ border-radius: 50%;
98
+ background-color: #007aff;
99
+ color: white;
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ font-size: 30px;
104
+ box-shadow: 0 4px 12px rgba(0, 122, 255, 0.4);
105
+ transition: all 0.2s ease;
106
+ }
107
+
108
+ #mic-btn.recording {
109
+ background-color: #ff3b30; /* Red color when recording */
110
+ transform: scale(1.1);
111
+ box-shadow: 0 6px 16px rgba(255, 59, 48, 0.5);
112
+ }
pages/index/index_v10_old.js ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+
3
+ Page({
4
+ data: {
5
+ languages: {
6
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
7
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
8
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
9
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
10
+ },
11
+ langCodes: ['zh', 'en', 'ja', 'ko'],
12
+ sourceLang: 'zh',
13
+ targetLang: 'en',
14
+ transcript: '',
15
+ outputText: '',
16
+ isRecording: false,
17
+ sourceLanguages: [],
18
+ targetLanguages: [],
19
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
20
+ },
21
+
22
+ onLoad: function () {
23
+ this.initializeLanguages();
24
+ this.initRecorderManager(); // Correctly initialize the manager within the page lifecycle
25
+ },
26
+
27
+ // --- Language Selection Logic ---
28
+ initializeLanguages: function () {
29
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
30
+ this.setData({
31
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
32
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
33
+ });
34
+ },
35
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
36
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
37
+ swapLanguages: function() {
38
+ this.setData({
39
+ sourceLang: this.data.targetLang,
40
+ targetLang: this.data.sourceLang,
41
+ transcript: this.data.outputText,
42
+ outputText: this.data.transcript
43
+ }, this.initializeLanguages);
44
+ },
45
+
46
+ // --- CORRECTED: Unified, Lifecycle-Aware Recording Logic ---
47
+ initRecorderManager: function() {
48
+ const manager = plugin.getRecordRecognitionManager();
49
+
50
+ manager.onStart = () => {
51
+ this.setData({ transcript: '正在聆听...', outputText: '' });
52
+ };
53
+
54
+ // CRITICAL: Only use onStop for final results to prevent race conditions.
55
+ manager.onStop = (res) => {
56
+ this.setData({ isRecording: false });
57
+ const { sourceLang, targetLang } = this.data;
58
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
59
+
60
+ if (isChineseEnglish) {
61
+ // Mode 1: Plugin handles both ASR and Translation
62
+ if (res.result) {
63
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
64
+ } else {
65
+ this.setData({ transcript: '识别结果为空', outputText: '' });
66
+ }
67
+ } else {
68
+ // Mode 2: Plugin handles ASR, then HF handles translation
69
+ if (res.tempFilePath) {
70
+ this.uploadAudioForASR(res.tempFilePath);
71
+ } else {
72
+ this.setData({ transcript: '录音文件获取失败' });
73
+ }
74
+ }
75
+ };
76
+
77
+ manager.onError = (res) => {
78
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
79
+ };
80
+
81
+ // CRITICAL: Save the manager instance to the page context
82
+ this.manager = manager;
83
+ },
84
+
85
+ startRecording: function () {
86
+ const { sourceLang, targetLang } = this.data;
87
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
88
+
89
+ this.setData({ isRecording: true });
90
+
91
+ if (isChineseEnglish) {
92
+ this.manager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
93
+ } else {
94
+ this.manager.start({ lang: this.data.languages[sourceLang].code, translate: false });
95
+ }
96
+ },
97
+
98
+ stopRecording: function () {
99
+ this.manager.stop();
100
+ },
101
+
102
+ // --- HF Bridge Translation Flow (This part remains the same) ---
103
+ uploadAudioForASR: function (filePath) {
104
+ this.setData({ transcript: '正在识别 (1/3)...' });
105
+ const fileSystemManager = wx.getFileSystemManager();
106
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
107
+ wx.request({
108
+ url: `${this.data.hfSpaceUrl}/api/asr`,
109
+ method: 'POST',
110
+ header: { 'Content-Type': 'application/json' },
111
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
112
+ timeout: 60000,
113
+ success: (asrRes) => {
114
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
115
+ const transcript = asrRes.data.transcript;
116
+ this.setData({ transcript });
117
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
118
+ } else { this.setData({ transcript: 'HF识别失败' }); }
119
+ },
120
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
121
+ });
122
+ }});
123
+ },
124
+
125
+ fullBackendBridge: function(text, sourceLang, targetLang) {
126
+ this.setData({ outputText: '翻译中 (2/3)..' });
127
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
128
+ if (englishResult) {
129
+ this.setData({ outputText: '翻译中 (3/3)..' });
130
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
131
+ if (finalResult) {
132
+ this.setData({ outputText: finalResult });
133
+ }
134
+ });
135
+ }
136
+ });
137
+ },
138
+
139
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
140
+ wx.request({
141
+ url: `${this.data.hfSpaceUrl}/api/translate`,
142
+ method: 'POST',
143
+ header: { 'Content-Type': 'application/json' },
144
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
145
+ timeout: 30000,
146
+ success: (res) => {
147
+ if (res.statusCode === 200 && res.data.translated_text) {
148
+ callback(res.data.translated_text);
149
+ } else {
150
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
151
+ callback(null);
152
+ }
153
+ },
154
+ fail: () => {
155
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
156
+ callback(null);
157
+ }
158
+ });
159
+ }
160
+ });
pages/index/index_v11_old.js ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+
3
+ Page({
4
+ data: {
5
+ languages: {
6
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
7
+ 'en': { name: 'English', flag: 'us', code: 'en_US' }
8
+ },
9
+ langCodes: ['zh', 'en'], // Only CN and EN for this test version
10
+ sourceLang: 'zh',
11
+ targetLang: 'en',
12
+ transcript: '',
13
+ outputText: '',
14
+ isRecording: false,
15
+ sourceLanguages: [],
16
+ targetLanguages: []
17
+ },
18
+
19
+ onLoad: function () {
20
+ this.initializeLanguages();
21
+ this.initRecorderManager();
22
+ },
23
+
24
+ // --- Language Selection Logic (Simplified for CN/EN only) ---
25
+ initializeLanguages: function () {
26
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
27
+ this.setData({
28
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
29
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
30
+ });
31
+ },
32
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
33
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
34
+ swapLanguages: function() {
35
+ this.setData({
36
+ sourceLang: this.data.targetLang,
37
+ targetLang: this.data.sourceLang,
38
+ transcript: this.data.outputText,
39
+ outputText: this.data.transcript
40
+ }, this.initializeLanguages);
41
+ },
42
+
43
+ // --- Recorder Manager Initialization (Pure Plugin Mode) ---
44
+ initRecorderManager: function() {
45
+ const manager = plugin.getRecordRecognitionManager();
46
+
47
+ manager.onStart = () => {
48
+ this.setData({ transcript: '正在聆听...', outputText: '' });
49
+ };
50
+
51
+ // Only onStop is used for final results
52
+ manager.onStop = (res) => {
53
+ this.setData({ isRecording: false });
54
+ if (res.result) {
55
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
56
+ } else {
57
+ this.setData({ transcript: '识别结果为空', outputText: '' });
58
+ }
59
+ };
60
+
61
+ manager.onError = (res) => {
62
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
63
+ };
64
+
65
+ // CRITICAL: Save the manager instance to the page context
66
+ this.manager = manager;
67
+ },
68
+
69
+ // --- Recording Start/Stop (Pure Plugin Mode) ---
70
+ startRecording: function () {
71
+ const { sourceLang, targetLang } = this.data;
72
+ this.setData({ isRecording: true });
73
+ this.manager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
74
+ },
75
+
76
+ stopRecording: function () {
77
+ this.manager.stop();
78
+ }
79
+ });
pages/index/index_v12_old.js ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+
3
+ Page({
4
+ data: {
5
+ languages: {
6
+ 'zh_CN': { name: '中文', flag: 'cn' },
7
+ 'en_US': { name: 'English', flag: 'us' }
8
+ },
9
+ langCodes: ['zh_CN', 'en_US'], // Only CN and EN for this test version
10
+ sourceLang: 'zh_CN',
11
+ targetLang: 'en_US',
12
+ transcript: '',
13
+ outputText: '',
14
+ isRecording: false,
15
+ sourceLanguages: [],
16
+ targetLanguages: []
17
+ },
18
+
19
+ onLoad: function () {
20
+ this.initializeLanguages();
21
+ this.initRecorderManager();
22
+ },
23
+
24
+ // --- Language Selection Logic (Simplified for CN/EN only) ---
25
+ initializeLanguages: function () {
26
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
27
+ this.setData({
28
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
29
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
30
+ });
31
+ },
32
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
33
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
34
+ swapLanguages: function() {
35
+ this.setData({
36
+ sourceLang: this.data.targetLang,
37
+ targetLang: this.data.sourceLang,
38
+ transcript: this.data.outputText,
39
+ outputText: this.data.transcript
40
+ }, this.initializeLanguages);
41
+ },
42
+
43
+ // --- Recorder Manager Initialization (Pure Plugin Mode) ---
44
+ initRecorderManager: function() {
45
+ const manager = plugin.getRecordRecognitionManager();
46
+
47
+ manager.onStart = () => {
48
+ this.setData({ transcript: '正在聆听...', outputText: '' });
49
+ };
50
+
51
+ // Only onStop is used for final results
52
+ manager.onStop = (res) => {
53
+ this.setData({ isRecording: false });
54
+ if (res.result) {
55
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
56
+ } else {
57
+ this.setData({ transcript: '识别结果为空', outputText: '' });
58
+ }
59
+ };
60
+
61
+ manager.onError = (res) => {
62
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
63
+ };
64
+
65
+ // CRITICAL: Save the manager instance to the page context
66
+ this.manager = manager;
67
+ },
68
+
69
+ // --- Recording Start/Stop (Pure Plugin Mode) ---
70
+ startRecording: function () {
71
+ const { sourceLang, targetLang } = this.data;
72
+ this.setData({ isRecording: true });
73
+ // CORRECTED: Use trans_lang instead of lto
74
+ this.manager.start({
75
+ lang: this.data.languages[sourceLang].code,
76
+ trans_lang: this.data.languages[targetLang].code,
77
+ });
78
+ },
79
+
80
+ stopRecording: function () {
81
+ this.manager.stop();
82
+ }
83
+ });
pages/index/index_v13_old.js ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+ const manager = plugin.getRecordRecognitionManager(); // CRITICAL: Global creation
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' }
9
+ },
10
+ langCodes: ['zh', 'en'], // Only CN and EN for this test version
11
+ sourceLang: 'zh',
12
+ targetLang: 'en',
13
+ transcript: '',
14
+ outputText: '',
15
+ isRecording: false,
16
+ sourceLanguages: [],
17
+ targetLanguages: []
18
+ },
19
+
20
+ onLoad: function () {
21
+ this.initializeLanguages();
22
+ this.initRecorderManager(); // Set up event listeners for the globally created manager
23
+ },
24
+
25
+ // --- Language Selection Logic (Simplified for CN/EN only) ---
26
+ initializeLanguages: function () {
27
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
28
+ this.setData({
29
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
30
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
31
+ });
32
+ },
33
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
34
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
35
+ swapLanguages: function() {
36
+ this.setData({
37
+ sourceLang: this.data.targetLang,
38
+ targetLang: this.data.sourceLang,
39
+ transcript: this.data.outputText,
40
+ outputText: this.data.transcript
41
+ }, this.initializeLanguages);
42
+ },
43
+
44
+ // --- Recorder Manager Initialization (Pure Plugin Mode) ---
45
+ initRecorderManager: function() {
46
+ // Event listeners are set on the globally created manager
47
+ manager.onStart = () => {
48
+ this.setData({ transcript: '正在聆听...', outputText: '' });
49
+ };
50
+
51
+ // Only onStop is used for final results
52
+ manager.onStop = (res) => {
53
+ this.setData({ isRecording: false });
54
+ if (res.result) {
55
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
56
+ } else {
57
+ this.setData({ transcript: '识别结果为空', outputText: '' });
58
+ }
59
+ };
60
+
61
+ manager.onError = (res) => {
62
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
63
+ };
64
+ },
65
+
66
+ // --- Recording Start/Stop (Pure Plugin Mode) ---
67
+ startRecording: function () {
68
+ const { sourceLang, targetLang } = this.data;
69
+ this.setData({ isRecording: true });
70
+ manager.start({
71
+ lang: this.data.languages[sourceLang].code,
72
+ trans_lang: this.data.languages[targetLang].code,
73
+ });
74
+ },
75
+
76
+ stopRecording: function () {
77
+ manager.stop();
78
+ }
79
+ });
pages/index/index_v14_同声传译API_only-可以运行_old.js ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ const plugin = requirePlugin("WechatSI");
3
+ const manager = plugin.getRecordRecognitionManager();
4
+
5
+ Page({
6
+ data: {
7
+ languages: {
8
+ 'zh_CN': { name: '中文', flag: 'cn' },
9
+ 'en_US': { name: 'English', flag: 'us' },
10
+ 'ja_JP': { name: '日本語', flag: 'jp' },
11
+ 'ko_KR': { name: '한국어', flag: 'kr' }
12
+ },
13
+ langCodes: ['zh_CN', 'en_US', 'ja_JP', 'ko_KR'],
14
+ sourceLang: 'zh_CN',
15
+ targetLang: 'en_US',
16
+ transcript: '',
17
+ outputText: '',
18
+ isRecording: false,
19
+ sourceLanguages: [],
20
+ targetLanguages: []
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initRecord();
26
+ },
27
+
28
+ initializeLanguages: function () {
29
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
30
+ const sourceLanguages = langCodes.map(code => ({
31
+ langCode: code,
32
+ name: languages[code].name,
33
+ flag: languages[code].flag,
34
+ selected: code === sourceLang
35
+ }));
36
+ const targetLanguages = langCodes.map(code => ({
37
+ langCode: code,
38
+ name: languages[code].name,
39
+ flag: languages[code].flag,
40
+ selected: code === targetLang
41
+ }));
42
+ this.setData({ sourceLanguages, targetLanguages });
43
+ },
44
+
45
+ selectSourceLanguage: function (e) {
46
+ const newSourceLang = e.currentTarget.dataset.langCode;
47
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
48
+ },
49
+
50
+ selectTargetLanguage: function (e) {
51
+ const newTargetLang = e.currentTarget.dataset.langCode;
52
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
53
+ },
54
+
55
+ swapLanguages: function () {
56
+ const { sourceLang, targetLang } = this.data;
57
+ this.setData({
58
+ sourceLang: targetLang,
59
+ targetLang: sourceLang,
60
+ transcript: this.data.outputText,
61
+ outputText: this.data.transcript
62
+ }, this.initializeLanguages);
63
+ },
64
+
65
+ initRecord: function () {
66
+ manager.onStart = (res) => {
67
+ console.log("成功开始录音识别", res);
68
+ this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
69
+ };
70
+
71
+ manager.onRecognize = (res) => {
72
+ this.setData({ transcript: res.result });
73
+ };
74
+
75
+ manager.onStop = (res) => {
76
+ console.log("录音识别结束", res);
77
+ this.setData({ isRecording: false, transcript: res.result });
78
+ if (res.result) {
79
+ this.translate(res.result);
80
+ } else {
81
+ this.setData({ transcript: '未能识别到语音' });
82
+ }
83
+ };
84
+
85
+ manager.onError = (res) => {
86
+ console.error("录音识别错误", res);
87
+ this.setData({ isRecording: false, transcript: '语音识别出错' });
88
+ };
89
+ },
90
+
91
+ startRecording: function () {
92
+ const { sourceLang, targetLang } = this.data;
93
+
94
+ // 插件仅支持中英文
95
+ if ((sourceLang !== 'zh_CN' && sourceLang !== 'en_US') || (targetLang !== 'zh_CN' && targetLang !== 'en_US')) {
96
+ wx.showToast({
97
+ title: '插件仅支持中英文互译',
98
+ icon: 'none'
99
+ });
100
+ return;
101
+ }
102
+
103
+ manager.start({
104
+ lang: sourceLang,
105
+ trans_lang: targetLang,
106
+ });
107
+ },
108
+
109
+ stopRecording: function () {
110
+ manager.stop();
111
+ },
112
+
113
+ translate: function (text) {
114
+ const { sourceLang, targetLang } = this.data;
115
+ if (sourceLang === targetLang) {
116
+ this.setData({ outputText: text });
117
+ return;
118
+ }
119
+
120
+ this.setData({ outputText: '正在翻译...' });
121
+
122
+ plugin.translate({
123
+ lfrom: sourceLang,
124
+ lto: targetLang,
125
+ content: text,
126
+ success: (res) => {
127
+ if (res.retcode === 0) {
128
+ this.setData({ outputText: res.result });
129
+ } else {
130
+ console.error('翻译失败', res);
131
+ this.setData({ outputText: '翻译失败' });
132
+ }
133
+ },
134
+ fail: (err) => {
135
+ console.error('翻译接口调用失败', err);
136
+ this.setData({ outputText: '翻译出错' });
137
+ }
138
+ });
139
+ }
140
+ });
141
+
142
+ /*
143
+ --- Original index.v13.js content (commented out) ---
144
+ const plugin = requirePlugin('WechatSI');
145
+ const manager = plugin.getRecordRecognitionManager(); // CRITICAL: Global creation
146
+
147
+ Page({
148
+ data: {
149
+ languages: {
150
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
151
+ 'en': { name: 'English', flag: 'us', code: 'en_US' }
152
+ },
153
+ langCodes: ['zh', 'en'], // Only CN and EN for this test version
154
+ sourceLang: 'zh',
155
+ targetLang: 'en',
156
+ transcript: '',
157
+ outputText: '',
158
+ isRecording: false,
159
+ sourceLanguages: [],
160
+ targetLanguages: []
161
+ },
162
+
163
+ onLoad: function () {
164
+ this.initializeLanguages();
165
+ this.initRecorderManager(); // Set up event listeners for the globally created manager
166
+ },
167
+
168
+ // --- Language Selection Logic (Simplified for CN/EN only) ---
169
+ initializeLanguages: function () {
170
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
171
+ this.setData({
172
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
173
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
174
+ });
175
+ },
176
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
177
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
178
+ swapLanguages: function() {
179
+ this.setData({
180
+ sourceLang: this.data.targetLang,
181
+ targetLang: this.data.sourceLang,
182
+ transcript: this.data.outputText,
183
+ outputText: this.data.transcript
184
+ }, this.initializeLanguages);
185
+ },
186
+
187
+ // --- Recorder Manager Initialization (Pure Plugin Mode) ---
188
+ initRecorderManager: function() {
189
+ // Event listeners are set on the globally created manager
190
+ manager.onStart = () => {
191
+ this.setData({ transcript: '正在聆听...', outputText: '' });
192
+ };
193
+
194
+ // Only onStop is used for final results
195
+ manager.onStop = (res) => {
196
+ this.setData({ isRecording: false });
197
+ if (res.result) {
198
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
199
+ } else {
200
+ this.setData({ transcript: '识别结果为空', outputText: '' });
201
+ }
202
+ };
203
+
204
+ manager.onError = (res) => {
205
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
206
+ };
207
+ },
208
+
209
+ // --- Recording Start/Stop (Pure Plugin Mode) ---
210
+ startRecording: function () {
211
+ const { sourceLang, targetLang } = this.data;
212
+ this.setData({ isRecording: true });
213
+ manager.start({
214
+ lang: this.data.languages[sourceLang].code,
215
+ trans_lang: this.data.languages[targetLang].code,
216
+ });
217
+ },
218
+
219
+ stopRecording: function () {
220
+ manager.stop();
221
+ }
222
+ });
223
+ */
pages/index/index_v15_old.js ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const manager = plugin.getRecordRecognitionManager();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'], // Use short codes for internal logic
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initRecordManager();
26
+ },
27
+
28
+ // --- Language Selection Logic ---
29
+ initializeLanguages: function () {
30
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
31
+ this.setData({
32
+ sourceLanguages: langCodes.map(c => ({
33
+ langCode: c,
34
+ name: languages[c].name,
35
+ flag: languages[c].flag,
36
+ selected: c === sourceLang
37
+ })),
38
+ targetLanguages: langCodes.map(c => ({
39
+ langCode: c,
40
+ name: languages[c].name,
41
+ flag: languages[c].flag,
42
+ selected: c === targetLang
43
+ }))
44
+ });
45
+ },
46
+ selectSourceLanguage: function (e) {
47
+ const newSourceLang = e.currentTarget.dataset.langCode;
48
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
49
+ },
50
+ selectTargetLanguage: function (e) {
51
+ const newTargetLang = e.currentTarget.dataset.langCode;
52
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
53
+ },
54
+ swapLanguages: function () {
55
+ const { sourceLang, targetLang } = this.data;
56
+ this.setData({
57
+ sourceLang: targetLang,
58
+ targetLang: sourceLang,
59
+ transcript: this.data.outputText,
60
+ outputText: this.data.transcript
61
+ }, this.initializeLanguages);
62
+ },
63
+
64
+ // --- Recorder Manager Initialization (Hybrid Mode) ---
65
+ initRecordManager: function () {
66
+ manager.onStart = () => {
67
+ this.setData({ transcript: '正在聆听...', outputText: '' });
68
+ };
69
+
70
+ manager.onRecognize = (res) => {
71
+ // For plugin mode, this updates transcript in real-time
72
+ // For HF mode, we only care about the final result in onStop
73
+ const { sourceLang, targetLang } = this.data;
74
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
75
+ if (isChineseEnglish) {
76
+ this.setData({ transcript: res.result });
77
+ }
78
+ };
79
+
80
+ manager.onStop = (res) => {
81
+ this.setData({ isRecording: false });
82
+ const { sourceLang, targetLang } = this.data;
83
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
84
+
85
+ if (isChineseEnglish) {
86
+ // Mode 1: Plugin handles both ASR and Translation
87
+ if (res.result) {
88
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
89
+ } else {
90
+ this.setData({ transcript: '识别结果为空', outputText: '' });
91
+ }
92
+ } else {
93
+ // Mode 2: Plugin handles ASR, then HF handles translation
94
+ if (res.tempFilePath) {
95
+ this.uploadAudioForASR(res.tempFilePath);
96
+ } else {
97
+ this.setData({ transcript: '录音文件获取失败' });
98
+ }
99
+ }
100
+ };
101
+
102
+ manager.onError = (res) => {
103
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
104
+ };
105
+ },
106
+
107
+ startRecording: function () {
108
+ const { sourceLang, targetLang } = this.data;
109
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
110
+
111
+ this.setData({ isRecording: true });
112
+
113
+ if (isChineseEnglish) {
114
+ // Use plugin for ASR and Translation
115
+ this.manager.start({
116
+ lang: this.data.languages[sourceLang].code,
117
+ trans_lang: this.data.languages[targetLang].code,
118
+ });
119
+ } else {
120
+ // Use plugin for ASR only, then HF for translation
121
+ this.manager.start({
122
+ lang: this.data.languages[sourceLang].code,
123
+ // No trans_lang here, as HF will handle translation
124
+ });
125
+ }
126
+ },
127
+
128
+ stopRecording: function () {
129
+ this.manager.stop();
130
+ },
131
+
132
+ // --- HF Bridge Translation Flow (for non-CN/EN pairs) ---
133
+ uploadAudioForASR: function (filePath) {
134
+ this.setData({ transcript: '正在识别 (1/3)...' });
135
+ const fileSystemManager = wx.getFileSystemManager();
136
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
137
+ wx.request({
138
+ url: `${this.data.hfSpaceUrl}/api/asr`,
139
+ method: 'POST',
140
+ header: { 'Content-Type': 'application/json' },
141
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
142
+ timeout: 60000,
143
+ success: (asrRes) => {
144
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
145
+ const transcript = asrRes.data.transcript;
146
+ this.setData({ transcript });
147
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
148
+ } else { this.setData({ transcript: 'HF识别失败' }); }
149
+ },
150
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
151
+ });
152
+ }});
153
+ },
154
+
155
+ fullBackendBridge: function(text, sourceLang, targetLang) {
156
+ this.setData({ outputText: '翻译中 (2/3)..' });
157
+ // Step 1: Translate source (e.g., JA) to English via HF
158
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
159
+ if (englishResult) {
160
+ // Step 2: Translate English result to final target (e.g., ZH) via HF
161
+ this.setData({ outputText: '翻译中 (3/3)..' });
162
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
163
+ if (finalResult) {
164
+ this.setData({ outputText: finalResult });
165
+ }
166
+ });
167
+ }
168
+ });
169
+ },
170
+
171
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
172
+ wx.request({
173
+ url: `${this.data.hfSpaceUrl}/api/translate`,
174
+ method: 'POST',
175
+ header: { 'Content-Type': 'application/json' },
176
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
177
+ timeout: 30000,
178
+ success: (res) => {
179
+ if (res.statusCode === 200 && res.data.translated_text) {
180
+ callback(res.data.translated_text);
181
+ } else {
182
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
183
+ callback(null);
184
+ }
185
+ },
186
+ fail: () => {
187
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
188
+ callback(null);
189
+ }
190
+ });
191
+ }
192
+ });
pages/index/index_v16_old.js ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const manager = plugin.getRecordRecognitionManager();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'], // Use short codes for internal logic
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initRecordManager();
26
+ },
27
+
28
+ // --- Language Selection Logic ---
29
+ initializeLanguages: function () {
30
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
31
+ this.setData({
32
+ sourceLanguages: langCodes.map(c => ({
33
+ langCode: c,
34
+ name: languages[c].name,
35
+ flag: languages[c].flag,
36
+ selected: c === sourceLang
37
+ })),
38
+ targetLanguages: langCodes.map(c => ({
39
+ langCode: c,
40
+ name: languages[c].name,
41
+ flag: languages[c].flag,
42
+ selected: c === targetLang
43
+ }))
44
+ });
45
+ },
46
+ selectSourceLanguage: function (e) {
47
+ const newSourceLang = e.currentTarget.dataset.langCode;
48
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
49
+ },
50
+ selectTargetLanguage: function (e) {
51
+ const newTargetLang = e.currentTarget.dataset.langCode;
52
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
53
+ },
54
+ swapLanguages: function () {
55
+ const { sourceLang, targetLang } = this.data;
56
+ this.setData({
57
+ sourceLang: targetLang,
58
+ targetLang: sourceLang,
59
+ transcript: this.data.outputText,
60
+ outputText: this.data.transcript
61
+ }, this.initializeLanguages);
62
+ },
63
+
64
+ // --- Recorder Manager Initialization (Hybrid Mode) ---
65
+ initRecordManager: function () {
66
+ manager.onStart = () => {
67
+ this.setData({ transcript: '正在聆听...', outputText: '' });
68
+ };
69
+
70
+ manager.onRecognize = (res) => {
71
+ // For plugin mode, this updates transcript in real-time
72
+ // For HF mode, we only care about the final result in onStop
73
+ const { sourceLang, targetLang } = this.data;
74
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
75
+ if (isChineseEnglish) {
76
+ this.setData({ transcript: res.result });
77
+ }
78
+ };
79
+
80
+ manager.onStop = (res) => {
81
+ this.setData({ isRecording: false });
82
+ const { sourceLang, targetLang } = this.data;
83
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
84
+
85
+ if (isChineseEnglish) {
86
+ // Mode 1: Plugin handles both ASR and Translation
87
+ if (res.result) {
88
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
89
+ } else {
90
+ this.setData({ transcript: '识别结果为空', outputText: '' });
91
+ }
92
+ } else {
93
+ // Mode 2: Plugin handles ASR, then HF handles translation
94
+ if (res.tempFilePath) {
95
+ this.uploadAudioForASR(res.tempFilePath);
96
+ } else {
97
+ this.setData({ transcript: '录音文件获取失败' });
98
+ }
99
+ }
100
+ };
101
+
102
+ manager.onError = (res) => {
103
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
104
+ };
105
+ },
106
+
107
+ startRecording: function () {
108
+ const { sourceLang, targetLang } = this.data;
109
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
110
+
111
+ this.setData({ isRecording: true });
112
+
113
+ if (isChineseEnglish) {
114
+ // Use plugin for ASR and Translation
115
+ manager.start({
116
+ lang: this.data.languages[sourceLang].code,
117
+ trans_lang: this.data.languages[targetLang].code,
118
+ });
119
+ } else {
120
+ // Use plugin for ASR only, then HF for translation
121
+ manager.start({
122
+ lang: this.data.languages[sourceLang].code,
123
+ // No trans_lang here, as HF will handle translation
124
+ });
125
+ }
126
+ },
127
+
128
+ stopRecording: function () {
129
+ manager.stop();
130
+ },
131
+
132
+ // --- HF Bridge Translation Flow (for non-CN/EN pairs) ---
133
+ uploadAudioForASR: function (filePath) {
134
+ this.setData({ transcript: '正在识别 (1/3)...' });
135
+ const fileSystemManager = wx.getFileSystemManager();
136
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
137
+ wx.request({
138
+ url: `${this.data.hfSpaceUrl}/api/asr`,
139
+ method: 'POST',
140
+ header: { 'Content-Type': 'application/json' },
141
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
142
+ timeout: 60000,
143
+ success: (asrRes) => {
144
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
145
+ const transcript = asrRes.data.transcript;
146
+ this.setData({ transcript });
147
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
148
+ } else { this.setData({ transcript: 'HF识别失败' }); }
149
+ },
150
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
151
+ });
152
+ }});
153
+ },
154
+
155
+ fullBackendBridge: function(text, sourceLang, targetLang) {
156
+ this.setData({ outputText: '翻译中 (2/3)..' });
157
+ // Step 1: Translate source (e.g., JA) to English via HF
158
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
159
+ if (englishResult) {
160
+ // Step 2: Translate English result to final target (e.g., ZH) via HF
161
+ this.setData({ outputText: '翻译中 (3/3)..' });
162
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
163
+ if (finalResult) {
164
+ this.setData({ outputText: finalResult });
165
+ }
166
+ });
167
+ }
168
+ });
169
+ },
170
+
171
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
172
+ wx.request({
173
+ url: `${this.data.hfSpaceUrl}/api/translate`,
174
+ method: 'POST',
175
+ header: { 'Content-Type': 'application/json' },
176
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
177
+ timeout: 30000,
178
+ success: (res) => {
179
+ if (res.statusCode === 200 && res.data.translated_text) {
180
+ callback(res.data.translated_text);
181
+ } else {
182
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
183
+ callback(null);
184
+ }
185
+ },
186
+ fail: () => {
187
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
188
+ callback(null);
189
+ }
190
+ });
191
+ }
192
+ });
pages/index/index_v17_old.js ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const manager = plugin.getRecordRecognitionManager();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'], // Use short codes for internal logic
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initRecordManager();
26
+ },
27
+
28
+ // --- Language Selection Logic ---
29
+ initializeLanguages: function () {
30
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
31
+ this.setData({
32
+ sourceLanguages: langCodes.map(c => ({
33
+ langCode: c,
34
+ name: languages[c].name,
35
+ flag: languages[c].flag,
36
+ selected: c === sourceLang
37
+ })),
38
+ targetLanguages: langCodes.map(c => ({
39
+ langCode: c,
40
+ name: languages[c].name,
41
+ flag: languages[c].flag,
42
+ selected: c === targetLang
43
+ }))
44
+ });
45
+ },
46
+ selectSourceLanguage: function (e) {
47
+ const newSourceLang = e.currentTarget.dataset.langCode;
48
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
49
+ },
50
+ selectTargetLanguage: function (e) {
51
+ const newTargetLang = e.currentTarget.dataset.langCode;
52
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
53
+ },
54
+ swapLanguages: function () {
55
+ const { sourceLang, targetLang } = this.data;
56
+ this.setData({
57
+ sourceLang: targetLang,
58
+ targetLang: sourceLang,
59
+ transcript: this.data.outputText,
60
+ outputText: this.data.transcript
61
+ }, this.initializeLanguages);
62
+ },
63
+
64
+ // --- Recorder Manager Initialization (Hybrid Mode) ---
65
+ initRecordManager: function () {
66
+ manager.onStart = () => {
67
+ this.setData({ transcript: '正在聆听...', outputText: '' });
68
+ };
69
+
70
+ manager.onRecognize = (res) => {
71
+ // For plugin mode, this updates transcript in real-time
72
+ // For HF mode, we only care about the final result in onStop
73
+ const { sourceLang, targetLang } = this.data;
74
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
75
+ if (isChineseEnglish) {
76
+ this.setData({ transcript: res.result });
77
+ }
78
+ };
79
+
80
+ manager.onStop = (res) => {
81
+ this.setData({ isRecording: false });
82
+ const { sourceLang, targetLang } = this.data;
83
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
84
+
85
+ if (isChineseEnglish) {
86
+ // Mode 1: Plugin handles ASR, then we explicitly call plugin.translate
87
+ if (res.result) {
88
+ this.setData({ transcript: res.result });
89
+ this.translate(res.result); // CRITICAL: Explicitly call translate
90
+ } else {
91
+ this.setData({ transcript: '识别结果为空', outputText: '' });
92
+ }
93
+ } else {
94
+ // Mode 2: Plugin handles ASR, then HF handles translation
95
+ if (res.tempFilePath) {
96
+ this.uploadAudioForASR(res.tempFilePath);
97
+ } else {
98
+ this.setData({ transcript: '录音文件获取失败' });
99
+ }
100
+ }
101
+ };
102
+
103
+ manager.onError = (res) => {
104
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
105
+ };
106
+ },
107
+
108
+ startRecording: function () {
109
+ const { sourceLang, targetLang } = this.data;
110
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
111
+
112
+ this.setData({ isRecording: true });
113
+
114
+ if (isChineseEnglish) {
115
+ // Use plugin for ASR only (no trans_lang here), then explicitly call plugin.translate
116
+ manager.start({
117
+ lang: this.data.languages[sourceLang].code,
118
+ // No trans_lang here, as we will call plugin.translate explicitly
119
+ });
120
+ } else {
121
+ // Use plugin for ASR only, then HF for translation
122
+ manager.start({
123
+ lang: this.data.languages[sourceLang].code,
124
+ // No trans_lang here, as HF will handle translation
125
+ });
126
+ }
127
+ },
128
+
129
+ stopRecording: function () {
130
+ manager.stop();
131
+ },
132
+
133
+ // --- CRITICAL: Re-introduced translate function for plugin mode ---
134
+ translate: function (text) {
135
+ const { sourceLang, targetLang } = this.data;
136
+ if (sourceLang === targetLang) {
137
+ this.setData({ outputText: text });
138
+ return;
139
+ }
140
+
141
+ this.setData({ outputText: '正在翻译...' });
142
+
143
+ plugin.translate({
144
+ lfrom: this.data.languages[sourceLang].code,
145
+ lto: this.data.languages[targetLang].code,
146
+ content: text,
147
+ success: (res) => {
148
+ if (res.retcode === 0) {
149
+ this.setData({ outputText: res.result });
150
+ } else {
151
+ console.error('翻译失败', res);
152
+ this.setData({ outputText: '翻译失败' });
153
+ }
154
+ },
155
+ fail: (err) => {
156
+ console.error('翻译接口调用失败', err);
157
+ this.setData({ outputText: '翻译出错' });
158
+ }
159
+ });
160
+ },
161
+
162
+ // --- HF Bridge Translation Flow (for non-CN/EN pairs) ---
163
+ uploadAudioForASR: function (filePath) {
164
+ this.setData({ transcript: '正在识别 (1/3)...' });
165
+ const fileSystemManager = wx.getFileSystemManager();
166
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
167
+ wx.request({
168
+ url: `${this.data.hfSpaceUrl}/api/asr`,
169
+ method: 'POST',
170
+ header: { 'Content-Type': 'application/json' },
171
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
172
+ timeout: 60000,
173
+ success: (asrRes) => {
174
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
175
+ const transcript = asrRes.data.transcript;
176
+ this.setData({ transcript });
177
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
178
+ } else { this.setData({ transcript: 'HF识别失败' }); }
179
+ },
180
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
181
+ });
182
+ }});
183
+ },
184
+
185
+ fullBackendBridge: function(text, sourceLang, targetLang) {
186
+ this.setData({ outputText: '翻译中 (2/3)..' });
187
+ // Step 1: Translate source (e.g., JA) to English via HF
188
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
189
+ if (englishResult) {
190
+ // Step 2: Translate English result to final target (e.g., ZH) via HF
191
+ this.setData({ outputText: '翻译中 (3/3)..' });
192
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
193
+ if (finalResult) {
194
+ this.setData({ outputText: finalResult });
195
+ }
196
+ });
197
+ }
198
+ });
199
+ },
200
+
201
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
202
+ wx.request({
203
+ url: `${this.data.hfSpaceUrl}/api/translate`,
204
+ method: 'POST',
205
+ header: { 'Content-Type': 'application/json' },
206
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
207
+ timeout: 30000,
208
+ success: (res) => {
209
+ if (res.statusCode === 200 && res.data.translated_text) {
210
+ callback(res.data.translated_text);
211
+ } else {
212
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
213
+ callback(null);
214
+ }
215
+ },
216
+ fail: () => {
217
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
218
+ callback(null);
219
+ }
220
+ });
221
+ }
222
+ });
pages/index/index_v18_old.js ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const manager = plugin.getRecordRecognitionManager();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'], // Use short codes for internal logic
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initRecordManager();
26
+ },
27
+
28
+ // --- Language Selection Logic ---
29
+ initializeLanguages: function () {
30
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
31
+ this.setData({
32
+ sourceLanguages: langCodes.map(c => ({
33
+ langCode: c,
34
+ name: languages[c].name,
35
+ flag: languages[c].flag,
36
+ selected: c === sourceLang
37
+ })),
38
+ targetLanguages: langCodes.map(c => ({
39
+ langCode: c,
40
+ name: languages[c].name,
41
+ flag: languages[c].flag,
42
+ selected: c === targetLang
43
+ }))
44
+ });
45
+ },
46
+ selectSourceLanguage: function (e) {
47
+ const newSourceLang = e.currentTarget.dataset.langCode;
48
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
49
+ },
50
+ selectTargetLanguage: function (e) {
51
+ const newTargetLang = e.currentTarget.dataset.langCode;
52
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
53
+ },
54
+ swapLanguages: function () {
55
+ const { sourceLang, targetLang } = this.data;
56
+ this.setData({
57
+ sourceLang: targetLang,
58
+ targetLang: sourceLang,
59
+ transcript: this.data.outputText,
60
+ outputText: this.data.transcript
61
+ }, this.initializeLanguages);
62
+ },
63
+
64
+ // --- Recorder Manager Initialization (Hybrid Mode) ---
65
+ initRecordManager: function () {
66
+ manager.onStart = () => {
67
+ this.setData({ transcript: '正在聆听...', outputText: '' });
68
+ };
69
+
70
+ manager.onRecognize = (res) => {
71
+ // For plugin mode, this updates transcript in real-time
72
+ // For HF mode, we only care about the final result in onStop
73
+ const { sourceLang, targetLang } = this.data;
74
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
75
+ if (isChineseEnglish) {
76
+ this.setData({ transcript: res.result });
77
+ }
78
+ };
79
+
80
+ manager.onStop = (res) => {
81
+ this.setData({ isRecording: false });
82
+ const { sourceLang, targetLang } = this.data;
83
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
84
+
85
+ if (isChineseEnglish) {
86
+ // Mode 1: Plugin handles ASR, then we explicitly call plugin.translate
87
+ if (res.result) {
88
+ this.setData({ transcript: res.result });
89
+ this.translate(res.result); // CRITICAL: Explicitly call translate
90
+ } else {
91
+ this.setData({ transcript: '识别结果为空', outputText: '' });
92
+ }
93
+ } else {
94
+ // Mode 2: Plugin handles ASR, then HF handles translation
95
+ if (res.tempFilePath) {
96
+ this.uploadAudioForASR(res.tempFilePath);
97
+ } else {
98
+ this.setData({ transcript: '录音文件获取失败' });
99
+ }
100
+ }
101
+ };
102
+
103
+ manager.onError = (res) => {
104
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
105
+ };
106
+ },
107
+
108
+ startRecording: function () {
109
+ const { sourceLang, targetLang } = this.data;
110
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
111
+
112
+ this.setData({ isRecording: true });
113
+
114
+ if (isChineseEnglish) {
115
+ // Use plugin for ASR only (no trans_lang here), then explicitly call plugin.translate
116
+ manager.start({
117
+ lang: this.data.languages[sourceLang].code,
118
+ // No trans_lang here, as we will call plugin.translate explicitly
119
+ });
120
+ } else {
121
+ // CRITICAL: Check if source language is supported by plugin for ASR
122
+ if (sourceLang !== 'zh' && sourceLang !== 'en') {
123
+ wx.showToast({
124
+ title: '当前源语言不支持插件ASR',
125
+ icon: 'none'
126
+ });
127
+ this.setData({ isRecording: false, transcript: '请选择中文或英文作为源语言' });
128
+ return;
129
+ }
130
+ // Use plugin for ASR only, then HF for translation
131
+ manager.start({
132
+ lang: this.data.languages[sourceLang].code,
133
+ // No trans_lang here, as HF will handle translation
134
+ });
135
+ }
136
+ },
137
+
138
+ stopRecording: function () {
139
+ manager.stop();
140
+ },
141
+
142
+ // --- CRITICAL: Re-introduced translate function for plugin mode ---
143
+ translate: function (text) {
144
+ const { sourceLang, targetLang } = this.data;
145
+ if (sourceLang === targetLang) {
146
+ this.setData({ outputText: text });
147
+ return;
148
+ }
149
+
150
+ this.setData({ outputText: '正在翻译...' });
151
+
152
+ plugin.translate({
153
+ lfrom: this.data.languages[sourceLang].code,
154
+ lto: this.data.languages[targetLang].code,
155
+ content: text,
156
+ success: (res) => {
157
+ if (res.retcode === 0) {
158
+ this.setData({ outputText: res.result });
159
+ } else {
160
+ console.error('翻译失败', res);
161
+ this.setData({ outputText: '翻译失败' });
162
+ }
163
+ },
164
+ fail: (err) => {
165
+ console.error('翻译接口调用失败', err);
166
+ this.setData({ outputText: '翻译出错' });
167
+ }
168
+ });
169
+ },
170
+
171
+ // --- HF Bridge Translation Flow (for non-CN/EN pairs) ---
172
+ uploadAudioForASR: function (filePath) {
173
+ this.setData({ transcript: '正在识别 (1/3)...' });
174
+ const fileSystemManager = wx.getFileSystemManager();
175
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
176
+ wx.request({
177
+ url: `${this.data.hfSpaceUrl}/api/asr`,
178
+ method: 'POST',
179
+ header: { 'Content-Type': 'application/json' },
180
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
181
+ timeout: 60000,
182
+ success: (asrRes) => {
183
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
184
+ const transcript = asrRes.data.transcript;
185
+ this.setData({ transcript });
186
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
187
+ } else { this.setData({ transcript: 'HF识别失败' }); }
188
+ },
189
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
190
+ });
191
+ }});
192
+ },
193
+
194
+ fullBackendBridge: function(text, sourceLang, targetLang) {
195
+ this.setData({ outputText: '翻译中 (2/3)..' });
196
+ // Step 1: Translate source (e.g., JA) to English via HF
197
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
198
+ if (englishResult) {
199
+ // Step 2: Translate English result to final target (e.g., ZH) via HF
200
+ this.setData({ outputText: '翻译中 (3/3)..' });
201
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
202
+ if (finalResult) {
203
+ this.setData({ outputText: finalResult });
204
+ }
205
+ });
206
+ }
207
+ });
208
+ },
209
+
210
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
211
+ wx.request({
212
+ url: `${this.data.hfSpaceUrl}/api/translate`,
213
+ method: 'POST',
214
+ header: { 'Content-Type': 'application/json' },
215
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
216
+ timeout: 30000,
217
+ success: (res) => {
218
+ if (res.statusCode === 200 && res.data.translated_text) {
219
+ callback(res.data.translated_text);
220
+ } else {
221
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
222
+ callback(null);
223
+ }
224
+ },
225
+ fail: () => {
226
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
227
+ callback(null);
228
+ }
229
+ });
230
+ }
231
+ });
pages/index/index_v19_old.js ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const pluginManager = plugin.getRecordRecognitionManager(); // For CN/EN ASR & Translation
3
+ const nativeRecorderManager = wx.getRecorderManager(); // For JA/KO ASR (recording only)
4
+
5
+ Page({
6
+ data: {
7
+ languages: {
8
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
9
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
10
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
11
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
12
+ },
13
+ langCodes: ['zh', 'en', 'ja', 'ko'], // Use short codes for internal logic
14
+ sourceLang: 'zh',
15
+ targetLang: 'en',
16
+ transcript: '',
17
+ outputText: '',
18
+ isRecording: false,
19
+ sourceLanguages: [],
20
+ targetLanguages: [],
21
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
22
+ currentActiveManager: null // To track which manager is active
23
+ },
24
+
25
+ onLoad: function () {
26
+ this.initializeLanguages();
27
+ this.initPluginManager();
28
+ this.initNativeRecorderManager();
29
+ },
30
+
31
+ // --- Language Selection Logic ---
32
+ initializeLanguages: function () {
33
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
34
+ this.setData({
35
+ sourceLanguages: langCodes.map(c => ({
36
+ langCode: c,
37
+ name: languages[c].name,
38
+ flag: languages[c].flag,
39
+ selected: c === sourceLang
40
+ })),
41
+ targetLanguages: langCodes.map(c => ({
42
+ langCode: c,
43
+ name: languages[c].name,
44
+ flag: languages[c].flag,
45
+ selected: c === targetLang
46
+ }))
47
+ });
48
+ },
49
+ selectSourceLanguage: function (e) {
50
+ const newSourceLang = e.currentTarget.dataset.langCode;
51
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
52
+ },
53
+ selectTargetLanguage: function (e) {
54
+ const newTargetLang = e.currentTarget.dataset.langCode;
55
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
56
+ },
57
+ swapLanguages: function () {
58
+ const { sourceLang, targetLang } = this.data;
59
+ this.setData({
60
+ sourceLang: targetLang,
61
+ targetLang: sourceLang,
62
+ transcript: this.data.outputText,
63
+ outputText: this.data.transcript
64
+ }, this.initializeLanguages);
65
+ },
66
+
67
+ // --- Plugin Manager Initialization (for CN/EN) ---
68
+ initPluginManager: function () {
69
+ pluginManager.onStart = () => {
70
+ this.setData({ transcript: '正在聆听...', outputText: '' });
71
+ };
72
+
73
+ pluginManager.onRecognize = (res) => {
74
+ this.setData({ transcript: res.result });
75
+ };
76
+
77
+ pluginManager.onStop = (res) => {
78
+ this.setData({ isRecording: false });
79
+ if (res.result) {
80
+ this.setData({ transcript: res.result });
81
+ this.translate(res.result); // Explicitly call translate for plugin mode
82
+ } else {
83
+ this.setData({ transcript: '识别结果为空', outputText: '' });
84
+ }
85
+ };
86
+
87
+ pluginManager.onError = (res) => {
88
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
89
+ };
90
+ },
91
+
92
+ // --- Native Recorder Manager Initialization (for JA/KO recording) ---
93
+ initNativeRecorderManager: function () {
94
+ nativeRecorderManager.onStart = () => {
95
+ this.setData({ transcript: '正在聆听...', outputText: '' });
96
+ };
97
+
98
+ nativeRecorderManager.onStop = (res) => {
99
+ this.setData({ isRecording: false });
100
+ if (res.tempFilePath) {
101
+ this.uploadAudioForASR(res.tempFilePath);
102
+ } else {
103
+ this.setData({ transcript: '录音文件获取失败' });
104
+ }
105
+ };
106
+
107
+ nativeRecorderManager.onError = (res) => {
108
+ this.setData({ isRecording: false, transcript: '录音失败', outputText: res.errMsg });
109
+ };
110
+ },
111
+
112
+ // --- Unified Start Recording ---
113
+ startRecording: function () {
114
+ const { sourceLang, targetLang } = this.data;
115
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
116
+
117
+ this.setData({ isRecording: true });
118
+
119
+ if (isChineseEnglish) {
120
+ this.setData({ currentActiveManager: 'plugin' });
121
+ pluginManager.start({
122
+ lang: this.data.languages[sourceLang].code,
123
+ trans_lang: this.data.languages[targetLang].code,
124
+ });
125
+ } else {
126
+ this.setData({ currentActiveManager: 'native' });
127
+ nativeRecorderManager.start({
128
+ format: 'mp3', // Ensure format is compatible with HF backend
129
+ sampleRate: 16000,
130
+ numberOfChannels: 1,
131
+ encodeBitRate: 96000,
132
+ });
133
+ }
134
+ },
135
+
136
+ // --- Unified Stop Recording ---
137
+ stopRecording: function () {
138
+ if (this.data.currentActiveManager === 'plugin') {
139
+ pluginManager.stop();
140
+ } else if (this.data.currentActiveManager === 'native') {
141
+ nativeRecorderManager.stop();
142
+ }
143
+ },
144
+
145
+ // --- Plugin Translation Function (for CN/EN) ---
146
+ translate: function (text) {
147
+ const { sourceLang, targetLang } = this.data;
148
+ if (sourceLang === targetLang) {
149
+ this.setData({ outputText: text });
150
+ return;
151
+ }
152
+
153
+ this.setData({ outputText: '正在翻译...' });
154
+
155
+ plugin.translate({
156
+ lfrom: this.data.languages[sourceLang].code,
157
+ lto: this.data.languages[targetLang].code,
158
+ content: text,
159
+ success: (res) => {
160
+ if (res.retcode === 0) {
161
+ this.setData({ outputText: res.result });
162
+ } else {
163
+ console.error('翻译失败', res);
164
+ this.setData({ outputText: '翻译失败' });
165
+ }
166
+ },
167
+ fail: (err) => {
168
+ console.error('翻译接口调用失败', err);
169
+ this.setData({ outputText: '翻译出错' });
170
+ }
171
+ });
172
+ },
173
+
174
+ // --- HF Bridge Translation Flow (for non-CN/EN pairs) ---
175
+ uploadAudioForASR: function (filePath) {
176
+ this.setData({ transcript: '正在识别 (1/3)...' });
177
+ const fileSystemManager = wx.getFileSystemManager();
178
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
179
+ wx.request({
180
+ url: `${this.data.hfSpaceUrl}/api/asr`,
181
+ method: 'POST',
182
+ header: { 'Content-Type': 'application/json' },
183
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
184
+ timeout: 60000,
185
+ success: (asrRes) => {
186
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
187
+ const transcript = asrRes.data.transcript;
188
+ this.setData({ transcript });
189
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
190
+ } else { this.setData({ transcript: 'HF识别失败' }); }
191
+ },
192
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
193
+ });
194
+ }});
195
+ },
196
+
197
+ fullBackendBridge: function(text, sourceLang, targetLang) {
198
+ this.setData({ outputText: '翻译中 (2/3)..' });
199
+ // Step 1: Translate source (e.g., JA) to English via HF
200
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
201
+ if (englishResult) {
202
+ // Step 2: Translate English result to final target (e.g., ZH) via HF
203
+ this.setData({ outputText: '翻译中 (3/3)..' });
204
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
205
+ if (finalResult) {
206
+ this.setData({ outputText: finalResult });
207
+ }
208
+ });
209
+ }
210
+ });
211
+ },
212
+
213
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
214
+ wx.request({
215
+ url: `${this.data.hfSpaceUrl}/api/translate`,
216
+ method: 'POST',
217
+ header: { 'Content-Type': 'application/json' },
218
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
219
+ timeout: 30000,
220
+ success: (res) => {
221
+ if (res.statusCode === 200 && res.data.translated_text) {
222
+ callback(res.data.translated_text);
223
+ } else {
224
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
225
+ callback(null);
226
+ }
227
+ },
228
+ fail: () => {
229
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
230
+ callback(null);
231
+ }
232
+ });
233
+ }
234
+ });
pages/index/index_v20_old.js ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const manager = plugin.getRecordRecognitionManager(); // Only one manager
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'], // Use short codes for internal logic
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initRecordManager();
26
+ },
27
+
28
+ // --- Language Selection Logic ---
29
+ initializeLanguages: function () {
30
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
31
+ this.setData({
32
+ sourceLanguages: langCodes.map(c => ({
33
+ langCode: c,
34
+ name: languages[c].name,
35
+ flag: languages[c].flag,
36
+ selected: c === sourceLang
37
+ })),
38
+ targetLanguages: langCodes.map(c => ({
39
+ langCode: c,
40
+ name: languages[c].name,
41
+ flag: languages[c].flag,
42
+ selected: c === targetLang
43
+ }))
44
+ });
45
+ },
46
+ selectSourceLanguage: function (e) {
47
+ const newSourceLang = e.currentTarget.dataset.langCode;
48
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
49
+ },
50
+ selectTargetLanguage: function (e) {
51
+ const newTargetLang = e.currentTarget.dataset.langCode;
52
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
53
+ },
54
+ swapLanguages: function () {
55
+ const { sourceLang, targetLang } = this.data;
56
+ this.setData({
57
+ sourceLang: targetLang,
58
+ targetLang: sourceLang,
59
+ transcript: this.data.outputText,
60
+ outputText: this.data.transcript
61
+ }, this.initializeLanguages);
62
+ },
63
+
64
+ // --- Recorder Manager Initialization (Single Manager Mode) ---
65
+ initRecordManager: function () {
66
+ manager.onStart = () => {
67
+ this.setData({ transcript: '正在聆听...', outputText: '' });
68
+ };
69
+
70
+ manager.onRecognize = (res) => {
71
+ // Only update transcript for plugin-handled ASR (CN/EN)
72
+ const { sourceLang } = this.data;
73
+ if (sourceLang === 'zh' || sourceLang === 'en') {
74
+ this.setData({ transcript: res.result });
75
+ }
76
+ };
77
+
78
+ manager.onStop = (res) => {
79
+ this.setData({ isRecording: false });
80
+ const { sourceLang, targetLang } = this.data;
81
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
82
+
83
+ if (isChineseEnglish) {
84
+ // Mode 1: Plugin handles both ASR and Translation
85
+ if (res.result) {
86
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
87
+ } else {
88
+ this.setData({ transcript: '识别结果为空', outputText: '' });
89
+ }
90
+ } else {
91
+ // Mode 2: Plugin handles ASR (if CN/EN), then HF handles translation
92
+ if (res.result) { // res.result is the ASR result from plugin
93
+ this.setData({ transcript: res.result });
94
+ this.fullBackendBridge(res.result, this.data.sourceLang, this.data.targetLang);
95
+ } else {
96
+ this.setData({ transcript: '识别结果为空' });
97
+ }
98
+ }
99
+ };
100
+
101
+ manager.onError = (res) => {
102
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
103
+ };
104
+ },
105
+
106
+ startRecording: function () {
107
+ const { sourceLang, targetLang } = this.data;
108
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
109
+
110
+ this.setData({ isRecording: true });
111
+
112
+ // CRITICAL: Check if source language is supported by plugin for ASR
113
+ if (sourceLang !== 'zh' && sourceLang !== 'en') {
114
+ wx.showToast({
115
+ title: '当前源语言不支持语音输入',
116
+ icon: 'none'
117
+ });
118
+ this.setData({ isRecording: false, transcript: '请选择中文或英文作为源语言' });
119
+ return;
120
+ }
121
+
122
+ if (isChineseEnglish) {
123
+ // Use plugin for ASR and Translation
124
+ manager.start({
125
+ lang: this.data.languages[sourceLang].code,
126
+ trans_lang: this.data.languages[targetLang].code,
127
+ });
128
+ } else {
129
+ // Use plugin for ASR only, then HF for translation
130
+ manager.start({
131
+ lang: this.data.languages[sourceLang].code,
132
+ // No trans_lang here, as HF will handle translation
133
+ });
134
+ }
135
+ },
136
+
137
+ stopRecording: function () {
138
+ manager.stop();
139
+ },
140
+
141
+ // --- HF Bridge Translation Flow (for non-CN/EN pairs) ---
142
+ fullBackendBridge: function(text, sourceLang, targetLang) {
143
+ this.setData({ outputText: '翻译中 (1/2)..' }); // Simplified steps
144
+ // Step 1: Translate source (e.g., ZH) to English via HF
145
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
146
+ if (englishResult) {
147
+ // Step 2: Translate English result to final target (e.g., JA) via HF
148
+ this.setData({ outputText: '翻译中 (2/2)..' });
149
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
150
+ if (finalResult) {
151
+ this.setData({ outputText: finalResult });
152
+ }
153
+ });
154
+ }
155
+ });
156
+ },
157
+
158
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
159
+ wx.request({
160
+ url: `${this.data.hfSpaceUrl}/api/translate`,
161
+ method: 'POST',
162
+ header: { 'Content-Type': 'application/json' },
163
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
164
+ timeout: 30000,
165
+ success: (res) => {
166
+ if (res.statusCode === 200 && res.data.translated_text) {
167
+ callback(res.data.translated_text);
168
+ } else {
169
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
170
+ callback(null);
171
+ }
172
+ },
173
+ fail: () => {
174
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
175
+ callback(null);
176
+ }
177
+ });
178
+ }
179
+ });
pages/index/index_v21_old.js ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const manager = plugin.getRecordRecognitionManager();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'],
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
21
+ // This flag will determine if we need to call our backend
22
+ useHfBridge: false
23
+ },
24
+
25
+ onLoad: function () {
26
+ this.initializeLanguages();
27
+ this.initManager();
28
+ },
29
+
30
+ // --- Language Selection Logic ---
31
+ initializeLanguages: function () {
32
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
33
+ this.setData({
34
+ sourceLanguages: langCodes.map(c => ({
35
+ langCode: c,
36
+ name: languages[c].name,
37
+ flag: languages[c].flag,
38
+ selected: c === sourceLang
39
+ })),
40
+ targetLanguages: langCodes.map(c => ({
41
+ langCode: c,
42
+ name: languages[c].name,
43
+ flag: languages[c].flag,
44
+ selected: c === targetLang
45
+ }))
46
+ });
47
+ },
48
+ selectSourceLanguage: function (e) {
49
+ const newSourceLang = e.currentTarget.dataset.langCode;
50
+ this.setData({ sourceLang: newSourceLang }, this.initializeLanguages);
51
+ },
52
+ selectTargetLanguage: function (e) {
53
+ const newTargetLang = e.currentTarget.dataset.langCode;
54
+ this.setData({ targetLang: newTargetLang }, this.initializeLanguages);
55
+ },
56
+ swapLanguages: function () {
57
+ const { sourceLang, targetLang } = this.data;
58
+ this.setData({
59
+ sourceLang: targetLang,
60
+ targetLang: sourceLang,
61
+ transcript: this.data.outputText,
62
+ outputText: this.data.transcript
63
+ }, this.initializeLanguages);
64
+ },
65
+
66
+ // --- Unified Plugin Manager Initialization ---
67
+ initManager: function () {
68
+ manager.onStart = (res) => {
69
+ this.setData({ transcript: '正在聆听...', outputText: '' });
70
+ };
71
+
72
+ manager.onRecognize = (res) => {
73
+ // Live recognition feedback
74
+ this.setData({ transcript: res.result });
75
+ };
76
+
77
+ manager.onStop = (res) => {
78
+ this.setData({ isRecording: false });
79
+
80
+ if (res.result) {
81
+ this.setData({ transcript: res.result });
82
+ // If using HF Bridge, the plugin only provides the transcript.
83
+ // We then manually call our backend for translation.
84
+ if (this.data.useHfBridge) {
85
+ this.translateViaHfBridge(res.result);
86
+ } else {
87
+ // Otherwise, the plugin provides the translation directly.
88
+ this.setData({ outputText: res.translateResult || '翻译结果为空' });
89
+ }
90
+ } else {
91
+ this.setData({ transcript: '识别结果为空', outputText: '' });
92
+ }
93
+ };
94
+
95
+ manager.onError = (res) => {
96
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
97
+ };
98
+ },
99
+
100
+ // --- Unified Start/Stop Recording ---
101
+ startRecording: function () {
102
+ const { sourceLang, targetLang, languages } = this.data;
103
+
104
+ // Block recording if source is not CN or EN
105
+ if (sourceLang !== 'zh' && sourceLang !== 'en') {
106
+ wx.showToast({
107
+ title: '语音输入暂仅支持中文和英文',
108
+ icon: 'none',
109
+ duration: 2000
110
+ });
111
+ return;
112
+ }
113
+
114
+ const isChineseEnglishPair = (sourceLang === 'zh' || sourceLang === 'en') && (targetLang === 'zh' || targetLang === 'en');
115
+ const useHfBridge = !isChineseEnglishPair;
116
+
117
+ this.setData({
118
+ isRecording: true,
119
+ useHfBridge: useHfBridge,
120
+ transcript: '',
121
+ outputText: ''
122
+ });
123
+
124
+ manager.start({
125
+ lang: languages[sourceLang].code,
126
+ trans_lang: languages[targetLang].code,
127
+ // Critical: Only enable plugin's internal translation for direct CN/EN pairs
128
+ translate: !useHfBridge
129
+ });
130
+ },
131
+
132
+ stopRecording: function () {
133
+ manager.stop();
134
+ },
135
+
136
+ // --- HF Bridge Translation Flow ---
137
+ translateViaHfBridge: function(text) {
138
+ const { sourceLang, targetLang } = this.data;
139
+ this.setData({ outputText: '翻译中 (HF)...' });
140
+
141
+ // The plugin has already done the ASR (e.g., EN).
142
+ // Now we translate it to the final target (e.g., JA).
143
+ this.translateViaHF(text, sourceLang, targetLang, (finalResult) => {
144
+ if (finalResult) {
145
+ this.setData({ outputText: finalResult });
146
+ }
147
+ // Error message is set within translateViaHF
148
+ });
149
+ },
150
+
151
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
152
+ wx.request({
153
+ url: `${this.data.hfSpaceUrl}/api/translate`,
154
+ method: 'POST',
155
+ header: { 'Content-Type': 'application/json' },
156
+ data: {
157
+ "text": text,
158
+ "source_lang": sourceLang,
159
+ "target_lang": targetLang
160
+ },
161
+ timeout: 45000,
162
+ success: (res) => {
163
+ if (res.statusCode === 200 && res.data.translated_text) {
164
+ callback(res.data.translated_text);
165
+ } else {
166
+ const errorMsg = res.data.error || `HF翻译失败 (${sourceLang}->${targetLang})`;
167
+ this.setData({ outputText: errorMsg });
168
+ callback(null);
169
+ }
170
+ },
171
+ fail: (err) => {
172
+ this.setData({ outputText: `HF请求失败 (${sourceLang}->${targetLang})` });
173
+ callback(null);
174
+ }
175
+ });
176
+ }
177
+ });
pages/index/index_v22_old.js ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const pluginManager = plugin.getRecordRecognitionManager(); // For CN/EN ASR
3
+ const nativeRecorderManager = wx.getRecorderManager(); // For JA/KO Recording
4
+
5
+ Page({
6
+ data: {
7
+ languages: {
8
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN', native: false },
9
+ 'en': { name: 'English', flag: 'us', code: 'en_US', native: false },
10
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP', native: true },
11
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR', native: true }
12
+ },
13
+ langCodes: ['zh', 'en', 'ja', 'ko'],
14
+ sourceLang: 'zh',
15
+ targetLang: 'en',
16
+ transcript: '',
17
+ outputText: '',
18
+ isRecording: false,
19
+ sourceLanguages: [],
20
+ targetLanguages: [],
21
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
22
+ currentManager: null // 'plugin' or 'native'
23
+ },
24
+
25
+ onLoad: function () {
26
+ this.initializeLanguages();
27
+ this.initPluginManager();
28
+ this.initNativeRecorderManager();
29
+ },
30
+
31
+ // --- Language Selection Logic ---
32
+ initializeLanguages: function () {
33
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
34
+ this.setData({
35
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
36
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
37
+ });
38
+ },
39
+ selectSourceLanguage: function (e) {
40
+ this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages);
41
+ },
42
+ selectTargetLanguage: function (e) {
43
+ this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages);
44
+ },
45
+ swapLanguages: function () {
46
+ this.setData({
47
+ sourceLang: this.data.targetLang,
48
+ targetLang: this.data.sourceLang,
49
+ transcript: this.data.outputText,
50
+ outputText: this.data.transcript
51
+ }, this.initializeLanguages);
52
+ },
53
+
54
+ // --- Manager Initializations ---
55
+ initPluginManager: function () {
56
+ pluginManager.onStart = () => this.setData({ transcript: '正在聆听 (插件)...', outputText: '' });
57
+ pluginManager.onRecognize = (res) => this.setData({ transcript: res.result });
58
+ pluginManager.onStop = (res) => {
59
+ this.setData({ isRecording: false });
60
+ if (!res.result) {
61
+ this.setData({ transcript: '识别结果为空', outputText: '' });
62
+ return;
63
+ }
64
+ this.setData({ transcript: res.result });
65
+ const { sourceLang, targetLang } = this.data;
66
+ const isChineseEnglishPair = (sourceLang === 'zh' || sourceLang === 'en') && (targetLang === 'zh' || targetLang === 'en');
67
+
68
+ if (isChineseEnglishPair) {
69
+ this.translateViaPlugin(res.result);
70
+ } else {
71
+ this.translateViaHfBridge(res.result, sourceLang, targetLang);
72
+ }
73
+ };
74
+ pluginManager.onError = (res) => this.setData({ isRecording: false, transcript: '插件识别失败', outputText: res.msg });
75
+ },
76
+
77
+ initNativeRecorderManager: function () {
78
+ nativeRecorderManager.onStart = () => this.setData({ transcript: '正在聆听 (原生)...', outputText: '' });
79
+ nativeRecorderManager.onStop = (res) => {
80
+ this.setData({ isRecording: false });
81
+ if (res.tempFilePath) {
82
+ this.uploadAudioForASR(res.tempFilePath);
83
+ } else {
84
+ this.setData({ transcript: '录音文件获取失败' });
85
+ }
86
+ };
87
+ nativeRecorderManager.onError = (res) => this.setData({ isRecording: false, transcript: '原生录音失败', outputText: res.errMsg });
88
+ },
89
+
90
+ // --- Recording Logic ---
91
+ startRecording: function () {
92
+ const { sourceLang, languages } = this.data;
93
+ const shouldUseNative = languages[sourceLang].native;
94
+
95
+ this.setData({ isRecording: true, currentManager: shouldUseNative ? 'native' : 'plugin' });
96
+
97
+ if (shouldUseNative) {
98
+ nativeRecorderManager.start({ format: 'mp3', sampleRate: 16000, numberOfChannels: 1 });
99
+ } else {
100
+ pluginManager.start({ lang: languages[sourceLang].code, translate: false });
101
+ }
102
+ },
103
+
104
+ stopRecording: function () {
105
+ if (this.data.currentManager === 'native') {
106
+ nativeRecorderManager.stop();
107
+ } else {
108
+ pluginManager.stop();
109
+ }
110
+ },
111
+
112
+ // --- Translation Flows ---
113
+ translateViaPlugin: function(text) {
114
+ const { sourceLang, targetLang, languages } = this.data;
115
+ if (sourceLang === targetLang) {
116
+ this.setData({ outputText: text });
117
+ return;
118
+ }
119
+ this.setData({ outputText: '翻译中 (插件)...' });
120
+ plugin.translate({
121
+ lfrom: languages[sourceLang].code,
122
+ lto: languages[targetLang].code,
123
+ content: text,
124
+ success: (res) => {
125
+ if (res.retcode === 0) {
126
+ this.setData({ outputText: res.result });
127
+ } else {
128
+ this.setData({ outputText: '插件翻译失败' });
129
+ }
130
+ },
131
+ fail: () => this.setData({ outputText: '插件翻译接口调用失败' })
132
+ });
133
+ },
134
+
135
+ uploadAudioForASR: function (filePath) {
136
+ this.setData({ transcript: '正在识别 (HF)...' });
137
+ wx.getFileSystemManager().readFile({
138
+ filePath,
139
+ encoding: 'base64',
140
+ success: (res) => {
141
+ wx.request({
142
+ url: `${this.data.hfSpaceUrl}/api/asr`,
143
+ method: 'POST',
144
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
145
+ timeout: 60000,
146
+ success: (asrRes) => {
147
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
148
+ const transcript = asrRes.data.transcript;
149
+ this.setData({ transcript });
150
+ this.translateViaHfBridge(transcript, this.data.sourceLang, this.data.targetLang);
151
+ } else {
152
+ this.setData({ transcript: 'HF识别失败', outputText: asrRes.data.error || '' });
153
+ }
154
+ },
155
+ fail: () => this.setData({ transcript: 'HF识别请求失败' })
156
+ });
157
+ },
158
+ fail: () => this.setData({ transcript: '读取录音文件失败' })
159
+ });
160
+ },
161
+
162
+ translateViaHfBridge: function(text, source, target) {
163
+ this.setData({ outputText: '翻译中 (HF)...' });
164
+ if (source === target) {
165
+ this.setData({ outputText: text });
166
+ return;
167
+ }
168
+ this.translateViaHF(text, source, target, (result) => {
169
+ if (result) {
170
+ this.setData({ outputText: result });
171
+ }
172
+ // Error message is set within translateViaHF
173
+ });
174
+ },
175
+
176
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
177
+ wx.request({
178
+ url: `${this.data.hfSpaceUrl}/api/translate`,
179
+ method: 'POST',
180
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
181
+ timeout: 45000,
182
+ success: (res) => {
183
+ if (res.statusCode === 200 && res.data.translated_text) {
184
+ callback(res.data.translated_text);
185
+ } else {
186
+ const errorMsg = res.data.error || `HF翻译失败`;
187
+ this.setData({ outputText: errorMsg });
188
+ callback(null);
189
+ }
190
+ },
191
+ fail: () => {
192
+ this.setData({ outputText: `HF翻译请求失败` });
193
+ callback(null);
194
+ }
195
+ });
196
+ }
197
+ });
pages/index/index_v24_old.js ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const pluginManager = plugin.getRecordRecognitionManager();
3
+ const nativeRecorderManager = wx.getRecorderManager();
4
+
5
+ // Helper function to show detailed errors
6
+ function showDetailedError(title, content) {
7
+ wx.showModal({
8
+ title: title,
9
+ content: typeof content === 'object' ? JSON.stringify(content) : String(content),
10
+ showCancel: false
11
+ });
12
+ }
13
+
14
+ Page({
15
+ data: {
16
+ languages: {
17
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN', usePlugin: true },
18
+ 'en': { name: 'English', flag: 'us', code: 'en_US', usePlugin: true },
19
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP', usePlugin: false },
20
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR', usePlugin: false }
21
+ },
22
+ langCodes: ['zh', 'en', 'ja', 'ko'],
23
+ sourceLang: 'zh',
24
+ targetLang: 'en',
25
+ transcript: '',
26
+ outputText: '',
27
+ isRecording: false,
28
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
29
+ currentManagerType: null // 'plugin' or 'native'
30
+ },
31
+
32
+ onLoad: function () {
33
+ this.initializeLanguages();
34
+ this.initPluginManager();
35
+ this.initNativeRecorderManager();
36
+ },
37
+
38
+ // --- Language Selection Logic ---
39
+ initializeLanguages: function () {
40
+ const { languages, sourceLang, targetLang } = this.data;
41
+ this.setData({
42
+ sourceLanguages: Object.keys(languages).map(key => ({ ...languages[key], langCode: key, selected: key === sourceLang })),
43
+ targetLanguages: Object.keys(languages).map(key => ({ ...languages[key], langCode: key, selected: key === targetLang }))
44
+ });
45
+ },
46
+ selectSourceLanguage: function (e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
47
+ selectTargetLanguage: function (e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
48
+ swapLanguages: function () {
49
+ this.setData({
50
+ sourceLang: this.data.targetLang,
51
+ targetLang: this.data.sourceLang,
52
+ transcript: this.data.outputText,
53
+ outputText: this.data.transcript
54
+ }, this.initializeLanguages);
55
+ },
56
+
57
+ // --- Manager Initializations with Detailed Error Popups ---
58
+ initPluginManager: function () {
59
+ pluginManager.onStart = () => this.setData({ transcript: '正在聆听 (插件)...', outputText: '' });
60
+ pluginManager.onRecognize = (res) => this.setData({ transcript: res.result });
61
+ pluginManager.onStop = (res) => {
62
+ this.setData({ isRecording: false });
63
+ if (!res.result) { return this.setData({ transcript: '识别结果为空' }); }
64
+ this.setData({ transcript: res.result });
65
+ const { sourceLang, targetLang } = this.data;
66
+ const isPluginTarget = this.data.languages[targetLang].usePlugin;
67
+ if (isPluginTarget) {
68
+ this.translateViaPlugin(res.result);
69
+ } else {
70
+ this.translateViaHfBridge(res.result, sourceLang, targetLang);
71
+ }
72
+ };
73
+ pluginManager.onError = (res) => {
74
+ this.setData({ isRecording: false });
75
+ showDetailedError('插件录音失败', res);
76
+ };
77
+ },
78
+
79
+ initNativeRecorderManager: function () {
80
+ nativeRecorderManager.onStart = () => this.setData({ transcript: '正在聆听 (原生)...', outputText: '' });
81
+ nativeRecorderManager.onStop = (res) => {
82
+ this.setData({ isRecording: false });
83
+ if (res.tempFilePath) { this.uploadAudioForASR(res.tempFilePath); }
84
+ else { this.setData({ transcript: '原生录音文件获取失败' }); }
85
+ };
86
+ nativeRecorderManager.onError = (res) => {
87
+ this.setData({ isRecording: false });
88
+ showDetailedError('原生录音失败', res);
89
+ };
90
+ },
91
+
92
+ // --- Main Record Button Handler ---
93
+ handleRecordToggle: function() {
94
+ if (this.data.isRecording) {
95
+ this.stopRecording();
96
+ return;
97
+ }
98
+ wx.getSetting({
99
+ success: (res) => {
100
+ if (!res.authSetting['scope.record']) {
101
+ wx.authorize({
102
+ scope: 'scope.record',
103
+ success: this.startRecording,
104
+ fail: (err) => showDetailedError('授权失败', err)
105
+ });
106
+ } else {
107
+ this.startRecording();
108
+ }
109
+ },
110
+ fail: (err) => showDetailedError('无法获取权限设置', err)
111
+ });
112
+ },
113
+
114
+ startRecording: function () {
115
+ // *** DEBUG LOGGING ***
116
+ console.log(`[DEBUG] Attempting to start recording. Source Language: ${this.data.sourceLang}`);
117
+
118
+ const { sourceLang, languages } = this.data;
119
+ const shouldUsePlugin = languages[sourceLang].usePlugin;
120
+ const managerType = shouldUsePlugin ? 'plugin' : 'native';
121
+
122
+ this.setData({ isRecording: true, currentManagerType: managerType });
123
+
124
+ if (shouldUsePlugin) {
125
+ pluginManager.start({ lang: languages[sourceLang].code, translate: false });
126
+ } else {
127
+ nativeRecorderManager.start({ format: 'mp3', sampleRate: 16000, numberOfChannels: 1 });
128
+ }
129
+ },
130
+
131
+ stopRecording: function () {
132
+ if (this.data.currentManagerType === 'native') {
133
+ nativeRecorderManager.stop();
134
+ } else {
135
+ pluginManager.stop();
136
+ }
137
+ },
138
+
139
+ // --- Translation Logic (unchanged) ---
140
+ translateViaPlugin: function(text) {
141
+ const { sourceLang, targetLang, languages } = this.data;
142
+ if (sourceLang === targetLang) { return this.setData({ outputText: text }); }
143
+ this.setData({ outputText: '翻译中 (插件)...' });
144
+ plugin.translate({
145
+ lfrom: languages[sourceLang].code,
146
+ lto: languages[targetLang].code,
147
+ content: text,
148
+ success: (res) => {
149
+ if (res.retcode === 0) { this.setData({ outputText: res.result }); }
150
+ else { showDetailedError('插件翻译失败', res); }
151
+ },
152
+ fail: (err) => showDetailedError('插件翻译接口调用失败', err)
153
+ });
154
+ },
155
+
156
+ uploadAudioForASR: function (filePath) {
157
+ this.setData({ transcript: '正在识别 (HF)...' });
158
+ wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
159
+ wx.request({
160
+ url: `${this.data.hfSpaceUrl}/api/asr`,
161
+ method: 'POST',
162
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
163
+ timeout: 60000,
164
+ success: (asrRes) => {
165
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
166
+ const transcript = asrRes.data.transcript;
167
+ this.setData({ transcript });
168
+ this.translateViaHfBridge(transcript, this.data.sourceLang, this.data.targetLang);
169
+ } else { showDetailedError('HF识别失败', asrRes.data); }
170
+ },
171
+ fail: (err) => showDetailedError('HF识别请求失败', err)
172
+ });
173
+ }});
174
+ },
175
+
176
+ translateViaHfBridge: function(text, source, target) {
177
+ if (source === target) { return this.setData({ outputText: text }); }
178
+ this.setData({ outputText: '翻译中 (HF)...' });
179
+ this.translateViaHF(text, source, target, (result) => {
180
+ if (result) { this.setData({ outputText: result }); }
181
+ });
182
+ },
183
+
184
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
185
+ wx.request({
186
+ url: `${this.data.hfSpaceUrl}/api/translate`,
187
+ method: 'POST',
188
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
189
+ timeout: 45000,
190
+ success: (res) => {
191
+ if (res.statusCode === 200 && res.data.translated_text) { callback(res.data.translated_text); }
192
+ else { showDetailedError('HF翻译失败', res.data); callback(null); }
193
+ },
194
+ fail: (err) => { showDetailedError('HF翻译请求失败', err); callback(null); }
195
+ });
196
+ }
197
+ });
pages/index/index_v25_old.js ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const pluginManager = plugin.getRecordRecognitionManager();
3
+ const nativeRecorderManager = wx.getRecorderManager();
4
+
5
+ // Helper function to show detailed errors
6
+ function showDetailedError(title, content) {
7
+ wx.showModal({
8
+ title: title,
9
+ content: typeof content === 'object' ? JSON.stringify(content) : String(content),
10
+ showCancel: false
11
+ });
12
+ }
13
+
14
+ Page({
15
+ data: {
16
+ // Static language configuration
17
+ languages: {
18
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN', usePlugin: true },
19
+ 'en': { name: 'English', flag: 'us', code: 'en_US', usePlugin: true },
20
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP', usePlugin: false },
21
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR', usePlugin: false }
22
+ },
23
+ // Dynamic data
24
+ sourceLang: 'zh',
25
+ targetLang: 'en',
26
+ transcript: '',
27
+ outputText: '',
28
+ isRecording: false,
29
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
30
+ currentManagerType: null // 'plugin' or 'native'
31
+ },
32
+
33
+ onLoad: function () {
34
+ // Set the full language arrays for the UI to use
35
+ this.setData({
36
+ sourceLanguages: Object.values(this.data.languages).map(lang => ({...lang, langCode: this.getLangCode(lang.code)})),
37
+ targetLanguages: Object.values(this.data.languages).map(lang => ({...lang, langCode: this.getLangCode(lang.code)}))
38
+ });
39
+ this.initPluginManager();
40
+ this.initNativeRecorderManager();
41
+ },
42
+
43
+ getLangCode: function(fullCode) {
44
+ return fullCode.split('_')[0];
45
+ },
46
+
47
+ // --- Language Selection Logic (Simplified and Synchronous) ---
48
+ selectSourceLanguage: function (e) {
49
+ this.setData({ sourceLang: e.currentTarget.dataset.langCode });
50
+ },
51
+ selectTargetLanguage: function (e) {
52
+ this.setData({ targetLang: e.currentTarget.dataset.langCode });
53
+ },
54
+ swapLanguages: function () {
55
+ const { sourceLang, targetLang } = this.data;
56
+ this.setData({
57
+ sourceLang: targetLang,
58
+ targetLang: sourceLang,
59
+ transcript: this.data.outputText,
60
+ outputText: this.data.transcript
61
+ });
62
+ },
63
+
64
+ // --- Manager Initializations ---
65
+ initPluginManager: function () {
66
+ pluginManager.onStart = () => this.setData({ transcript: '正在聆听 (插件)...', outputText: '' });
67
+ pluginManager.onRecognize = (res) => this.setData({ transcript: res.result });
68
+ pluginManager.onStop = (res) => {
69
+ this.setData({ isRecording: false });
70
+ if (!res.result) { return this.setData({ transcript: '识别结果为空' }); }
71
+ this.setData({ transcript: res.result });
72
+ const { sourceLang, targetLang, languages } = this.data;
73
+ const isPluginTarget = languages[targetLang].usePlugin;
74
+ if (sourceLang === targetLang) {
75
+ this.setData({ outputText: res.result });
76
+ } else if (isPluginTarget) {
77
+ this.translateViaPlugin(res.result);
78
+ } else {
79
+ this.translateViaHfBridge(res.result, sourceLang, targetLang);
80
+ }
81
+ };
82
+ pluginManager.onError = (res) => {
83
+ this.setData({ isRecording: false });
84
+ showDetailedError('插件录音失败', res);
85
+ };
86
+ },
87
+
88
+ initNativeRecorderManager: function () {
89
+ nativeRecorderManager.onStart = () => this.setData({ transcript: '正在聆听 (原生)...', outputText: '' });
90
+ nativeRecorderManager.onStop = (res) => {
91
+ this.setData({ isRecording: false });
92
+ if (res.tempFilePath) { this.uploadAudioForASR(res.tempFilePath); }
93
+ else { this.setData({ transcript: '原生录音文件获取失败' }); }
94
+ };
95
+ nativeRecorderManager.onError = (res) => {
96
+ this.setData({ isRecording: false });
97
+ showDetailedError('原生录音失败', res);
98
+ };
99
+ },
100
+
101
+ // --- Main Record Button Handler ---
102
+ handleRecordToggle: function() {
103
+ if (this.data.isRecording) {
104
+ this.stopRecording();
105
+ return;
106
+ }
107
+ wx.getSetting({
108
+ success: (res) => {
109
+ if (!res.authSetting['scope.record']) {
110
+ wx.authorize({
111
+ scope: 'scope.record',
112
+ success: this.startRecording,
113
+ fail: (err) => showDetailedError('授权失败', err)
114
+ });
115
+ } else {
116
+ this.startRecording();
117
+ }
118
+ },
119
+ fail: (err) => showDetailedError('无法获取权限设置', err)
120
+ });
121
+ },
122
+
123
+ startRecording: function () {
124
+ console.log(`[DEBUG] Starting recording. Source: ${this.data.sourceLang}, Target: ${this.data.targetLang}`);
125
+ const { sourceLang, languages } = this.data;
126
+ const shouldUsePlugin = languages[sourceLang].usePlugin;
127
+ const managerType = shouldUsePlugin ? 'plugin' : 'native';
128
+
129
+ this.setData({ isRecording: true, currentManagerType: managerType });
130
+
131
+ if (shouldUsePlugin) {
132
+ pluginManager.start({ lang: languages[sourceLang].code, translate: false });
133
+ } else {
134
+ nativeRecorderManager.start({ format: 'mp3', sampleRate: 16000, numberOfChannels: 1 });
135
+ }
136
+ },
137
+
138
+ stopRecording: function () {
139
+ if (this.data.currentManagerType === 'native') {
140
+ nativeRecorderManager.stop();
141
+ } else {
142
+ pluginManager.stop();
143
+ }
144
+ },
145
+
146
+ // --- Translation Logic ---
147
+ translateViaPlugin: function(text) {
148
+ const { sourceLang, targetLang, languages } = this.data;
149
+ this.setData({ outputText: '翻译中 (插件)...' });
150
+ plugin.translate({
151
+ lfrom: languages[sourceLang].code,
152
+ lto: languages[targetLang].code,
153
+ content: text,
154
+ success: (res) => {
155
+ if (res.retcode === 0) { this.setData({ outputText: res.result }); }
156
+ else { showDetailedError('插件翻译失败', res); }
157
+ },
158
+ fail: (err) => showDetailedError('插件翻译接口调用失败', err)
159
+ });
160
+ },
161
+
162
+ uploadAudioForASR: function (filePath) {
163
+ this.setData({ transcript: '正在识别 (HF)...' });
164
+ wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
165
+ wx.request({
166
+ url: `${this.data.hfSpaceUrl}/api/asr`,
167
+ method: 'POST',
168
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
169
+ timeout: 60000,
170
+ success: (asrRes) => {
171
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
172
+ const transcript = asrRes.data.transcript;
173
+ this.setData({ transcript });
174
+ this.translateViaHfBridge(transcript, this.data.sourceLang, this.data.targetLang);
175
+ } else { showDetailedError('HF识别失败', asrRes.data); }
176
+ },
177
+ fail: (err) => showDetailedError('HF识别请求失败', err)
178
+ });
179
+ }});
180
+ },
181
+
182
+ translateViaHfBridge: function(text, source, target) {
183
+ if (source === target) { return this.setData({ outputText: text }); }
184
+ this.setData({ outputText: '翻译中 (HF)...' });
185
+ this.translateViaHF(text, source, target, (result) => {
186
+ if (result) { this.setData({ outputText: result }); }
187
+ });
188
+ },
189
+
190
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
191
+ wx.request({
192
+ url: `${this.data.hfSpaceUrl}/api/translate`,
193
+ method: 'POST',
194
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
195
+ timeout: 45000,
196
+ success: (res) => {
197
+ if (res.statusCode === 200 && res.data.translated_text) { callback(res.data.translated_text); }
198
+ else { showDetailedError('HF翻译失败', res.data); callback(null); }
199
+ },
200
+ fail: (err) => { showDetailedError('HF翻译请求失败', err); callback(null); }
201
+ });
202
+ }
203
+ });
pages/index/index_v26_old.js ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const pluginManager = plugin.getRecordRecognitionManager();
3
+ const nativeRecorderManager = wx.getRecorderManager();
4
+
5
+ function showDetailedError(title, content) {
6
+ wx.showModal({
7
+ title: title,
8
+ content: typeof content === 'object' ? JSON.stringify(content) : String(content),
9
+ showCancel: false
10
+ });
11
+ }
12
+
13
+ Page({
14
+ data: {
15
+ languages: {
16
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN', usePlugin: true },
17
+ 'en': { name: 'English', flag: 'us', code: 'en_US', usePlugin: true },
18
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP', usePlugin: false },
19
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR', usePlugin: false }
20
+ },
21
+ sourceLang: 'zh',
22
+ targetLang: 'en',
23
+ transcript: '',
24
+ outputText: '',
25
+ isRecording: false,
26
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
27
+ },
28
+
29
+ onLoad: function () {
30
+ this.setData({
31
+ sourceLanguages: Object.values(this.data.languages).map(lang => ({...lang, langCode: lang.code.split('_')[0]})),
32
+ targetLanguages: Object.values(this.data.languages).map(lang => ({...lang, langCode: lang.code.split('_')[0]}))
33
+ });
34
+ this.initPluginManager();
35
+ this.initNativeRecorderManager();
36
+ },
37
+
38
+ // --- Language Selection & UI ---
39
+ selectSourceLanguage: function (e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }); },
40
+ selectTargetLanguage: function (e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }); },
41
+ swapLanguages: function () {
42
+ this.setData({ sourceLang: this.data.targetLang, targetLang: this.data.sourceLang, transcript: this.data.outputText, outputText: this.data.transcript });
43
+ },
44
+
45
+ // --- Manager Initializations ---
46
+ initPluginManager: function () {
47
+ pluginManager.onStart = () => this.setData({ transcript: '正在聆听 (插件)...', outputText: '' });
48
+ pluginManager.onRecognize = (res) => this.setData({ transcript: res.result });
49
+ pluginManager.onStop = (res) => {
50
+ this.setData({ isRecording: false });
51
+ if (!res.result) { return this.setData({ transcript: '识别结果为空' }); }
52
+ this.setData({ transcript: res.result });
53
+ const { sourceLang, targetLang, languages } = this.data;
54
+ if (sourceLang === targetLang) { return this.setData({ outputText: res.result }); }
55
+ const isPluginTarget = languages[targetLang].usePlugin;
56
+ if (isPluginTarget) { this.translateViaPlugin(res.result); }
57
+ else { this.translateViaHfBridge(res.result, sourceLang, targetLang); }
58
+ };
59
+ pluginManager.onError = (res) => { this.setData({ isRecording: false }); showDetailedError('插件录音失败', res); };
60
+ },
61
+
62
+ initNativeRecorderManager: function () {
63
+ nativeRecorderManager.onStart = () => this.setData({ transcript: '正在聆听 (原生)...', outputText: '' });
64
+ nativeRecorderManager.onStop = (res) => {
65
+ this.setData({ isRecording: false });
66
+ if (res.tempFilePath) { this.uploadAudioForASR(res.tempFilePath); }
67
+ else { this.setData({ transcript: '原生录音文件获取失败' }); }
68
+ };
69
+ nativeRecorderManager.onError = (res) => { this.setData({ isRecording: false }); showDetailedError('原生录音失败', res); };
70
+ },
71
+
72
+ // --- Main Record Button Handler ---
73
+ handleRecordToggle: function() {
74
+ if (this.data.isRecording) { this.stopRecording(); return; }
75
+ wx.getSetting({
76
+ success: (res) => {
77
+ if (!res.authSetting['scope.record']) {
78
+ wx.authorize({ scope: 'scope.record', success: this.startRecording, fail: (err) => showDetailedError('授权失败', err) });
79
+ } else { this.startRecording(); }
80
+ },
81
+ fail: (err) => showDetailedError('无法获取权限设置', err)
82
+ });
83
+ },
84
+
85
+ startRecording: function () {
86
+ const { sourceLang, languages } = this.data;
87
+ const langConfig = languages[sourceLang];
88
+ const shouldUsePlugin = langConfig ? langConfig.usePlugin : false;
89
+
90
+ // *** ULTIMATE DEBUG LOG ***
91
+ console.log(`[DEBUG] Start decision: sourceLang=${sourceLang}, langConfig=${JSON.stringify(langConfig)}, shouldUsePlugin=${shouldUsePlugin}`);
92
+
93
+ this.setData({ isRecording: true });
94
+
95
+ if (shouldUsePlugin) {
96
+ pluginManager.start({ lang: langConfig.code, translate: false });
97
+ } else {
98
+ nativeRecorderManager.start({ format: 'mp3', sampleRate: 16000, numberOfChannels: 1 });
99
+ }
100
+ },
101
+
102
+ // --- ROBUST Stop Recording ---
103
+ stopRecording: function () {
104
+ this.setData({ isRecording: false });
105
+ try { pluginManager.stop(); } catch (e) { console.log("Plugin manager wasn't running or failed to stop."); }
106
+ try { nativeRecorderManager.stop(); } catch (e) { console.log("Native manager wasn't running or failed to stop."); }
107
+ },
108
+
109
+ // --- Translation Logic ---
110
+ translateViaPlugin: function(text) {
111
+ const { sourceLang, targetLang, languages } = this.data;
112
+ this.setData({ outputText: '翻译中 (插件)...' });
113
+ plugin.translate({
114
+ lfrom: languages[sourceLang].code, lto: languages[targetLang].code, content: text,
115
+ success: (res) => { if (res.retcode === 0) { this.setData({ outputText: res.result }); } else { showDetailedError('插件翻译失败', res); } },
116
+ fail: (err) => showDetailedError('插件翻译接口调用失败', err)
117
+ });
118
+ },
119
+
120
+ uploadAudioForASR: function (filePath) {
121
+ this.setData({ transcript: '正在识别 (HF)...' });
122
+ wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
123
+ wx.request({
124
+ url: `${this.data.hfSpaceUrl}/api/asr`, method: 'POST', data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` }, timeout: 60000,
125
+ success: (asrRes) => {
126
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
127
+ const transcript = asrRes.data.transcript;
128
+ this.setData({ transcript });
129
+ this.translateViaHfBridge(transcript, this.data.sourceLang, this.data.targetLang);
130
+ } else { showDetailedError('HF识别失败', asrRes.data); }
131
+ },
132
+ fail: (err) => showDetailedError('HF识别请求失败', err)
133
+ });
134
+ }});
135
+ },
136
+
137
+ translateViaHfBridge: function(text, source, target) {
138
+ if (source === target) { return this.setData({ outputText: text }); }
139
+ this.setData({ outputText: '翻译中 (HF)...' });
140
+ this.translateViaHF(text, source, target, (result) => { if (result) { this.setData({ outputText: result }); } });
141
+ },
142
+
143
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
144
+ wx.request({
145
+ url: `${this.data.hfSpaceUrl}/api/translate`, method: 'POST', data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang }, timeout: 45000,
146
+ success: (res) => { if (res.statusCode === 200 && res.data.translated_text) { callback(res.data.translated_text); } else { showDetailedError('HF翻译失败', res.data); callback(null); } },
147
+ fail: (err) => { showDetailedError('HF翻译请求失败', err); callback(null); }
148
+ });
149
+ }
150
+ });
pages/index/index_v27_old.js ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const pluginManager = plugin.getRecordRecognitionManager();
3
+ const nativeRecorderManager = wx.getRecorderManager();
4
+
5
+ function showDetailedError(title, content) {
6
+ wx.showModal({
7
+ title: title,
8
+ content: typeof content === 'object' ? JSON.stringify(content) : String(content),
9
+ showCancel: false
10
+ });
11
+ }
12
+
13
+ Page({
14
+ data: {
15
+ languages: {
16
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN', usePlugin: true },
17
+ 'en': { name: 'English', flag: 'us', code: 'en_US', usePlugin: true },
18
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP', usePlugin: false },
19
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR', usePlugin: false }
20
+ },
21
+ sourceLang: 'zh',
22
+ targetLang: 'en',
23
+ transcript: '',
24
+ outputText: '',
25
+ isRecording: false,
26
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
27
+ },
28
+
29
+ onLoad: function () {
30
+ this.setData({
31
+ sourceLanguages: Object.values(this.data.languages).map(lang => ({...lang, langCode: lang.code.split('_')[0]})),
32
+ targetLanguages: Object.values(this.data.languages).map(lang => ({...lang, langCode: lang.code.split('_')[0]}))
33
+ });
34
+ this.initPluginManager();
35
+ this.initNativeRecorderManager();
36
+ },
37
+
38
+ // --- Language Selection & UI with DEBUG ---
39
+ selectSourceLanguage: function (e) {
40
+ const newLang = e.currentTarget.dataset.langCode;
41
+ console.log(`[DEBUG] Language button tapped. Changing sourceLang to: ${newLang}`);
42
+ this.setData({ sourceLang: newLang });
43
+ },
44
+ selectTargetLanguage: function (e) {
45
+ const newLang = e.currentTarget.dataset.langCode;
46
+ console.log(`[DEBUG] Language button tapped. Changing targetLang to: ${newLang}`);
47
+ this.setData({ targetLang: newLang });
48
+ },
49
+ swapLanguages: function () {
50
+ this.setData({ sourceLang: this.data.targetLang, targetLang: this.data.sourceLang, transcript: this.data.outputText, outputText: this.data.transcript });
51
+ },
52
+
53
+ // --- Manager Initializations ---
54
+ initPluginManager: function () {
55
+ pluginManager.onStart = () => this.setData({ transcript: '正在聆听 (插件)...', outputText: '' });
56
+ pluginManager.onRecognize = (res) => this.setData({ transcript: res.result });
57
+ pluginManager.onStop = (res) => {
58
+ this.setData({ isRecording: false });
59
+ if (!res.result) { return this.setData({ transcript: '识别结果为空' }); }
60
+ this.setData({ transcript: res.result });
61
+ const { sourceLang, targetLang, languages } = this.data;
62
+ if (sourceLang === targetLang) { return this.setData({ outputText: res.result }); }
63
+ const isPluginTarget = languages[targetLang].usePlugin;
64
+ if (isPluginTarget) { this.translateViaPlugin(res.result); }
65
+ else { this.translateViaHfBridge(res.result, sourceLang, targetLang); }
66
+ };
67
+ pluginManager.onError = (res) => { this.setData({ isRecording: false }); showDetailedError('插件录音失败', res); };
68
+ },
69
+
70
+ initNativeRecorderManager: function () {
71
+ nativeRecorderManager.onStart = () => this.setData({ transcript: '正在聆听 (原生)...', outputText: '' });
72
+ nativeRecorderManager.onStop = (res) => {
73
+ this.setData({ isRecording: false });
74
+ if (res.tempFilePath) { this.uploadAudioForASR(res.tempFilePath); }
75
+ else { this.setData({ transcript: '原生录音文件获取失败' }); }
76
+ };
77
+ nativeRecorderManager.onError = (res) => { this.setData({ isRecording: false }); showDetailedError('原生录音失败', res); };
78
+ },
79
+
80
+ // --- Main Record Button Handler ---
81
+ handleRecordToggle: function() {
82
+ if (this.data.isRecording) { this.stopRecording(); return; }
83
+ wx.getSetting({
84
+ success: (res) => {
85
+ if (!res.authSetting['scope.record']) {
86
+ wx.authorize({ scope: 'scope.record', success: this.startRecording, fail: (err) => showDetailedError('授权失败', err) });
87
+ } else { this.startRecording(); }
88
+ },
89
+ fail: (err) => showDetailedError('无法获取权限设置', err)
90
+ });
91
+ },
92
+
93
+ startRecording: function () {
94
+ const { sourceLang, languages } = this.data;
95
+ const langConfig = languages[sourceLang];
96
+ // Fallback to a default config if something goes wrong, to prevent crashes
97
+ const shouldUsePlugin = langConfig ? langConfig.usePlugin : false;
98
+
99
+ // *** ULTIMATE DEBUG LOG ***
100
+ console.log(`[DEBUG] Start decision: sourceLang=${sourceLang}, langConfig=${JSON.stringify(langConfig)}, shouldUsePlugin=${shouldUsePlugin}`);
101
+
102
+ this.setData({ isRecording: true });
103
+
104
+ if (shouldUsePlugin) {
105
+ pluginManager.start({ lang: (langConfig || {code: 'zh_CN'}).code, translate: false });
106
+ } else {
107
+ nativeRecorderManager.start({ format: 'mp3', sampleRate: 16000, numberOfChannels: 1 });
108
+ }
109
+ },
110
+
111
+ // --- ROBUST Stop Recording ---
112
+ stopRecording: function () {
113
+ this.setData({ isRecording: false });
114
+ try { pluginManager.stop(); } catch (e) { /* Ignore error */ }
115
+ try { nativeRecorderManager.stop(); } catch (e) { /* Ignore error */ }
116
+ },
117
+
118
+ // --- Translation Logic ---
119
+ translateViaPlugin: function(text) {
120
+ const { sourceLang, targetLang, languages } = this.data;
121
+ this.setData({ outputText: '翻译中 (插件)...' });
122
+ plugin.translate({
123
+ lfrom: languages[sourceLang].code, lto: languages[targetLang].code, content: text,
124
+ success: (res) => { if (res.retcode === 0) { this.setData({ outputText: res.result }); } else { showDetailedError('插件翻译失败', res); } },
125
+ fail: (err) => showDetailedError('插件翻译接口调用失败', err)
126
+ });
127
+ },
128
+
129
+ uploadAudioForASR: function (filePath) {
130
+ this.setData({ transcript: '正在识别 (HF)...' });
131
+ wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
132
+ wx.request({
133
+ url: `${this.data.hfSpaceUrl}/api/asr`, method: 'POST', data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` }, timeout: 60000,
134
+ success: (asrRes) => {
135
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
136
+ const transcript = asrRes.data.transcript;
137
+ this.setData({ transcript });
138
+ this.translateViaHfBridge(transcript, this.data.sourceLang, this.data.targetLang);
139
+ } else { showDetailedError('HF识别失败', asrRes.data); }
140
+ },
141
+ fail: (err) => showDetailedError('HF识别请求失败', err)
142
+ });
143
+ }});
144
+ },
145
+
146
+ translateViaHfBridge: function(text, source, target) {
147
+ if (source === target) { return this.setData({ outputText: text }); }
148
+ this.setData({ outputText: '翻译中 (HF)...' });
149
+ this.translateViaHF(text, source, target, (result) => { if (result) { this.setData({ outputText: result }); } });
150
+ },
151
+
152
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
153
+ wx.request({
154
+ url: `${this.data.hfSpaceUrl}/api/translate`, method: 'POST', data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang }, timeout: 45000,
155
+ success: (res) => { if (res.statusCode === 200 && res.data.translated_text) { callback(res.data.translated_text); } else { showDetailedError('HF翻译失败', res.data); callback(null); } },
156
+ fail: (err) => { showDetailedError('HF翻译请求失败', err); callback(null); }
157
+ });
158
+ }
159
+ });
pages/index/index_v28_old.js ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // FINAL VERSION: v28 - Unified Native Recording and HF Backend
2
+ const nativeRecorderManager = wx.getRecorderManager();
3
+
4
+ // Helper function to show detailed errors
5
+ function showDetailedError(title, content) {
6
+ wx.showModal({
7
+ title: title,
8
+ content: typeof content === 'object' ? JSON.stringify(content) : String(content),
9
+ showCancel: false
10
+ });
11
+ }
12
+
13
+ Page({
14
+ data: {
15
+ languages: {
16
+ 'zh': { name: '中文', flag: 'cn' },
17
+ 'en': { name: 'English', flag: 'us' },
18
+ 'ja': { name: '日本語', flag: 'jp' },
19
+ 'ko': { name: '한국어', flag: 'kr' }
20
+ },
21
+ sourceLang: 'zh',
22
+ targetLang: 'en',
23
+ transcript: '',
24
+ outputText: '',
25
+ isRecording: false,
26
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
27
+ },
28
+
29
+ onLoad: function () {
30
+ // Directly create language arrays for the UI from the static config
31
+ this.setData({
32
+ sourceLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key })),
33
+ targetLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key }))
34
+ });
35
+ this.initNativeRecorderManager();
36
+ },
37
+
38
+ // --- Language Selection & UI ---
39
+ selectSourceLanguage: function (e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }); },
40
+ selectTargetLanguage: function (e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }); },
41
+ swapLanguages: function () {
42
+ this.setData({ sourceLang: this.data.targetLang, targetLang: this.data.sourceLang, transcript: this.data.outputText, outputText: this.data.transcript });
43
+ },
44
+
45
+ // --- Unified Native Recorder Initialization ---
46
+ initNativeRecorderManager: function () {
47
+ nativeRecorderManager.onStart = () => {
48
+ this.setData({ transcript: '正在聆听...', outputText: '' });
49
+ };
50
+ nativeRecorderManager.onStop = (res) => {
51
+ this.setData({ isRecording: false });
52
+ if (res.tempFilePath) {
53
+ // ALL recordings go to the HF backend for ASR
54
+ this.uploadAudioForASR(res.tempFilePath);
55
+ } else {
56
+ showDetailedError('录音失败', '未能获取到有效的录音文件路径。');
57
+ }
58
+ };
59
+ nativeRecorderManager.onError = (res) => {
60
+ this.setData({ isRecording: false });
61
+ showDetailedError('录音发生错误', res);
62
+ };
63
+ },
64
+
65
+ // --- Main Record Button Handler ---
66
+ handleRecordToggle: function() {
67
+ if (this.data.isRecording) {
68
+ this.stopRecording();
69
+ return;
70
+ }
71
+ // Check permissions before starting
72
+ wx.getSetting({
73
+ success: (res) => {
74
+ if (!res.authSetting['scope.record']) {
75
+ wx.authorize({ scope: 'scope.record', success: this.startRecording, fail: (err) => showDetailedError('授权失败', err) });
76
+ } else {
77
+ this.startRecording();
78
+ }
79
+ },
80
+ fail: (err) => showDetailedError('无法获取权限设置', err)
81
+ });
82
+ },
83
+
84
+ // --- Unified Start/Stop Recording ---
85
+ startRecording: function () {
86
+ this.setData({ isRecording: true });
87
+ nativeRecorderManager.start({ format: 'mp3', sampleRate: 16000, numberOfChannels: 1 });
88
+ },
89
+
90
+ stopRecording: function () {
91
+ this.setData({ isRecording: false });
92
+ nativeRecorderManager.stop();
93
+ },
94
+
95
+ // --- Unified Backend ASR & Translation Flow ---
96
+ uploadAudioForASR: function (filePath) {
97
+ this.setData({ transcript: '正在识别 (1/2)...' });
98
+ wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
99
+ wx.request({
100
+ url: `${this.data.hfSpaceUrl}/api/asr`,
101
+ method: 'POST',
102
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
103
+ timeout: 60000,
104
+ success: (asrRes) => {
105
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
106
+ const transcript = asrRes.data.transcript;
107
+ this.setData({ transcript });
108
+ // After ASR, ALL translations go to the HF backend
109
+ this.translateViaHfBridge(transcript, this.data.sourceLang, this.data.targetLang);
110
+ } else {
111
+ showDetailedError('语音识别失败', asrRes.data);
112
+ }
113
+ },
114
+ fail: (err) => showDetailedError('识别请求失败', err)
115
+ });
116
+ }});
117
+ },
118
+
119
+ translateViaHfBridge: function(text, source, target) {
120
+ if (source === target) {
121
+ return this.setData({ outputText: text });
122
+ }
123
+ this.setData({ outputText: '正在翻译 (2/2)...' });
124
+ wx.request({
125
+ url: `${this.data.hfSpaceUrl}/api/translate`,
126
+ method: 'POST',
127
+ data: { "text": text, "source_lang": source, "target_lang": target },
128
+ timeout: 45000,
129
+ success: (res) => {
130
+ if (res.statusCode === 200 && res.data.translated_text) {
131
+ this.setData({ outputText: res.data.translated_text });
132
+ } else {
133
+ showDetailedError('翻译失败', res.data);
134
+ }
135
+ },
136
+ fail: (err) => showDetailedError('翻译请求失败', err)
137
+ });
138
+ }
139
+ });
pages/index/index_v2_old.js ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+ const innerAudioContext = wx.createInnerAudioContext();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'],
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.recorderManager = wx.getRecorderManager();
26
+ this.initRecorderManager();
27
+ plugin.getRecordRecognitionManager = wx.getRecordRecognitionManager;
28
+ },
29
+
30
+ initializeLanguages: function () {
31
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
32
+ const sourceLanguages = langCodes.map(code => ({
33
+ langCode: code,
34
+ name: languages[code].name,
35
+ flag: languages[code].flag,
36
+ selected: code === sourceLang
37
+ }));
38
+ const targetLanguages = langCodes.map(code => ({
39
+ langCode: code,
40
+ name: languages[code].name,
41
+ flag: languages[code].flag,
42
+ selected: code === targetLang
43
+ }));
44
+ this.setData({ sourceLanguages, targetLanguages });
45
+ },
46
+
47
+ selectSourceLanguage: function (e) {
48
+ const newSourceLang = e.currentTarget.dataset.langCode;
49
+ this.setData({ sourceLang: newSourceLang }, () => {
50
+ this.initializeLanguages();
51
+ if (this.data.transcript.trim() && this.data.transcript !== '正在聆听...' && this.data.transcript !== '未能识别到语音') {
52
+ this.translate(this.data.transcript);
53
+ }
54
+ });
55
+ },
56
+
57
+ selectTargetLanguage: function (e) {
58
+ const newTargetLang = e.currentTarget.dataset.langCode;
59
+ this.setData({ targetLang: newTargetLang }, () => {
60
+ this.initializeLanguages();
61
+ if (this.data.transcript.trim() && this.data.transcript !== '正在聆听...' && this.data.transcript !== '未能识别到语音') {
62
+ this.translate(this.data.transcript);
63
+ }
64
+ });
65
+ },
66
+
67
+ swapLanguages: function () {
68
+ let { sourceLang, targetLang, transcript, outputText } = this.data;
69
+ const tempLang = sourceLang;
70
+ sourceLang = targetLang;
71
+ targetLang = tempLang;
72
+ const tempText = transcript;
73
+ transcript = outputText;
74
+ outputText = tempText;
75
+ this.setData({ sourceLang, targetLang, transcript, outputText }, () => {
76
+ this.initializeLanguages();
77
+ if (this.data.transcript.trim() && this.data.transcript !== '正在聆听...' && this.data.transcript !== '未能识别到语音') {
78
+ this.translate(this.data.transcript);
79
+ }
80
+ });
81
+ },
82
+
83
+ initRecorderManager: function () {
84
+ this.recorderManager.onStart(() => {
85
+ this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
86
+ });
87
+
88
+ this.recorderManager.onStop((res) => {
89
+ this.setData({ isRecording: false });
90
+ if (res.tempFilePath) {
91
+ this.uploadAudioForASR(res.tempFilePath);
92
+ } else {
93
+ this.setData({ transcript: '录音文件创建失败' });
94
+ }
95
+ });
96
+
97
+ this.recorderManager.onError(() => {
98
+ this.setData({ isRecording: false, transcript: '语音识别出错' });
99
+ });
100
+ },
101
+
102
+ startRecording: function () {
103
+ this.recorderManager.start({ duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: 'mp3' });
104
+ },
105
+
106
+ stopRecording: function () {
107
+ this.recorderManager.stop();
108
+ },
109
+
110
+ uploadAudioForASR: function (filePath) {
111
+ this.setData({ transcript: '正在识别...' });
112
+ const fileSystemManager = wx.getFileSystemManager();
113
+ fileSystemManager.readFile({
114
+ filePath: filePath,
115
+ encoding: 'base64',
116
+ success: (res) => {
117
+ const base64Data = res.data;
118
+ const dataUri = 'data:audio/mp3;base64,' + base64Data;
119
+
120
+ wx.request({
121
+ url: `${this.data.hfSpaceUrl}/api/asr`,
122
+ method: 'POST',
123
+ header: { 'Content-Type': 'application/json' },
124
+ data: { "audio_data_uri": dataUri },
125
+ timeout: 60000,
126
+ success: (res) => {
127
+ if (res.statusCode === 200 && res.data && res.data.transcript) {
128
+ const transcript = res.data.transcript;
129
+ this.setData({ transcript: transcript });
130
+ this.translate(transcript);
131
+ } else {
132
+ this.setData({ transcript: '语音识别失败' });
133
+ }
134
+ },
135
+ fail: () => {
136
+ this.setData({ transcript: '语音识别请求失败' });
137
+ }
138
+ });
139
+ },
140
+ fail: () => {
141
+ this.setData({ transcript: '读取音频文件失败' });
142
+ }
143
+ });
144
+ },
145
+
146
+ translate: function (text) {
147
+ if (!text) return;
148
+ const { sourceLang, targetLang, languages } = this.data;
149
+
150
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
151
+
152
+ if (isChineseEnglish) {
153
+ // --- Scenario 1: Direct CN-EN translation using WeChat Plugin ---
154
+ this.setData({ outputText: '正在翻译 (微信)...' });
155
+ plugin.translate({
156
+ lfrom: languages[sourceLang].code,
157
+ lto: languages[targetLang].code,
158
+ content: text,
159
+ success: (res) => {
160
+ if (res.retcode == 0) {
161
+ this.setData({ outputText: res.result });
162
+ } else {
163
+ this.setData({ outputText: '微信翻译失败' });
164
+ }
165
+ },
166
+ fail: () => {
167
+ this.setData({ outputText: '微信翻译接口调用失败' });
168
+ }
169
+ });
170
+ } else {
171
+ // --- Scenario 2: Hybrid translation for other languages ---
172
+ this.hybridTranslate(text, sourceLang, targetLang);
173
+ }
174
+ },
175
+
176
+ hybridTranslate: function(text, sourceLang, targetLang) {
177
+ // If source is not English, first translate to English using WeChat Plugin
178
+ if (sourceLang !== 'en') {
179
+ this.setData({ outputText: '翻译中 (1/2)...' });
180
+ plugin.translate({
181
+ lfrom: this.data.languages[sourceLang].code,
182
+ lto: 'en_US',
183
+ content: text,
184
+ success: (res) => {
185
+ if (res.retcode == 0) {
186
+ const englishText = res.result;
187
+ // If the final target is English, we are done.
188
+ if (targetLang === 'en') {
189
+ this.setData({ outputText: englishText });
190
+ } else {
191
+ // Otherwise, send the high-quality English text to HF for the second leg
192
+ this.translateViaHF(englishText, 'en', targetLang);
193
+ }
194
+ } else {
195
+ this.setData({ outputText: '混合翻译失败 (1/2)' });
196
+ }
197
+ },
198
+ fail: () => {
199
+ this.setData({ outputText: '混合翻译接口调用失败 (1/2)' });
200
+ }
201
+ });
202
+ } else {
203
+ // If source is already English, directly call HF for translation
204
+ this.translateViaHF(text, sourceLang, targetLang);
205
+ }
206
+ },
207
+
208
+ translateViaHF: function(text, sourceLang, targetLang) {
209
+ this.setData({ outputText: '翻译中 (2/2)..' });
210
+ wx.request({
211
+ url: `${this.data.hfSpaceUrl}/api/translate`,
212
+ method: 'POST',
213
+ header: { 'Content-Type': 'application/json' },
214
+ data: {
215
+ "text": text,
216
+ "source_lang": sourceLang,
217
+ "target_lang": targetLang
218
+ },
219
+ timeout: 30000,
220
+ success: (res) => {
221
+ if (res.statusCode === 200 && res.data && res.data.translated_text) {
222
+ this.setData({ outputText: res.data.translated_text });
223
+ } else {
224
+ this.setData({ outputText: '混合翻译失败 (2/2)' });
225
+ }
226
+ },
227
+ fail: () => {
228
+ this.setData({ outputText: '混合翻译出错 (2/2)' });
229
+ }
230
+ });
231
+ }
232
+ });
pages/index/index_v3_old.js ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+ const innerAudioContext = wx.createInnerAudioContext();
3
+
4
+ // Get the voice recognition manager from the plugin
5
+ const manager = plugin.getRecordRecognitionManager();
6
+
7
+ Page({
8
+ data: {
9
+ languages: {
10
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
11
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
12
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
13
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
14
+ },
15
+ langCodes: ['zh', 'en', 'ja', 'ko'],
16
+ sourceLang: 'zh',
17
+ targetLang: 'en',
18
+ transcript: '',
19
+ outputText: '',
20
+ isRecording: false,
21
+ sourceLanguages: [],
22
+ targetLanguages: [],
23
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
24
+ currentRecordMode: '' // 'plugin' or 'custom'
25
+ },
26
+
27
+ onLoad: function () {
28
+ this.initializeLanguages();
29
+ // Custom recorder (for HF)
30
+ this.customRecorderManager = wx.getRecorderManager();
31
+ this.initCustomRecorderManager();
32
+ // Plugin recorder (for CN/EN)
33
+ this.initPluginRecorderManager();
34
+ },
35
+
36
+ // --- Language Selection Logic ---
37
+ initializeLanguages: function () {
38
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
39
+ this.setData({
40
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
41
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
42
+ });
43
+ },
44
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
45
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
46
+ swapLanguages: function() {
47
+ this.setData({
48
+ sourceLang: this.data.targetLang,
49
+ targetLang: this.data.sourceLang,
50
+ transcript: this.data.outputText,
51
+ outputText: this.data.transcript
52
+ }, this.initializeLanguages);
53
+ },
54
+
55
+ // --- Recording Logic ---
56
+ startRecording: function () {
57
+ const { sourceLang, targetLang } = this.data;
58
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
59
+
60
+ this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
61
+
62
+ if (isChineseEnglish) {
63
+ // Use the ultra-fast WeChat Plugin for both ASR and Translation
64
+ this.setData({ currentRecordMode: 'plugin' });
65
+ manager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
66
+ } else {
67
+ // Use our custom HF backend for ASR for other languages
68
+ this.setData({ currentRecordMode: 'custom' });
69
+ this.customRecorderManager.start({ duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: 'mp3' });
70
+ }
71
+ },
72
+
73
+ stopRecording: function () {
74
+ if (this.data.currentRecordMode === 'plugin') {
75
+ manager.stop();
76
+ } else if (this.data.currentRecordMode === 'custom') {
77
+ this.customRecorderManager.stop();
78
+ }
79
+ this.setData({ isRecording: false });
80
+ },
81
+
82
+ // --- Result Handlers ---
83
+ initPluginRecorderManager: function() {
84
+ manager.onStart = (res) => {
85
+ this.setData({ transcript: '正在识别和翻译...' });
86
+ };
87
+ manager.onRecognize = (res) => {
88
+ this.setData({ transcript: res.result });
89
+ };
90
+ manager.onStop = (res) => {
91
+ this.setData({ transcript: res.result, outputText: res.translateResult });
92
+ };
93
+ manager.onError = (res) => {
94
+ this.setData({ transcript: '微信插件识别失败', outputText: res.msg });
95
+ };
96
+ },
97
+
98
+ initCustomRecorderManager: function() {
99
+ this.customRecorderManager.onStop = (res) => {
100
+ if (res.tempFilePath) {
101
+ this.uploadAudioForASR(res.tempFilePath);
102
+ } else {
103
+ this.setData({ transcript: '录音文件创建失败' });
104
+ }
105
+ };
106
+ this.customRecorderManager.onError = () => {
107
+ this.setData({ transcript: '语音识别出错' });
108
+ };
109
+ },
110
+
111
+ // --- Custom Translation Flow (for non-CN/EN pairs) ---
112
+ uploadAudioForASR: function (filePath) {
113
+ this.setData({ transcript: '正在识别 (HF)...' });
114
+ const fileSystemManager = wx.getFileSystemManager();
115
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
116
+ wx.request({
117
+ url: `${this.data.hfSpaceUrl}/api/asr`,
118
+ method: 'POST',
119
+ header: { 'Content-Type': 'application/json' },
120
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
121
+ timeout: 60000,
122
+ success: (asrRes) => {
123
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
124
+ const transcript = asrRes.data.transcript;
125
+ this.setData({ transcript });
126
+ this.hybridTranslate(transcript, this.data.sourceLang, this.data.targetLang);
127
+ } else { this.setData({ transcript: 'HF识别失败' }); }
128
+ },
129
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
130
+ });
131
+ }});
132
+ },
133
+
134
+ hybridTranslate: function(text, sourceLang, targetLang) {
135
+ // If source is ZH or EN, use the fast plugin for the first leg of the bridge
136
+ if (sourceLang === 'zh' || sourceLang === 'en') {
137
+ this.setData({ outputText: '翻译中 (1/2)...' });
138
+ plugin.translate({ lfrom: this.data.languages[sourceLang].code, lto: 'en_US', content: text, success: (res) => {
139
+ if (res.retcode === 0) {
140
+ this.translateViaHF(res.result, 'en', targetLang);
141
+ } else { this.setData({ outputText: '混合翻译失败 (1/2)' }); }
142
+ }});
143
+ } else {
144
+ // If source is JA or KO, we MUST use our HF backend for the first leg
145
+ this.setData({ outputText: '翻译中 (1/2 - HF)..' });
146
+ this.translateViaHF(text, sourceLang, 'en', (englishText) => {
147
+ // This is a callback to run after the first leg is complete
148
+ this.translateViaHF(englishText, 'en', targetLang);
149
+ });
150
+ }
151
+ },
152
+
153
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
154
+ this.setData({ outputText: this.data.outputText + ' (2/2)..' });
155
+ wx.request({
156
+ url: `${this.data.hfSpaceUrl}/api/translate`,
157
+ method: 'POST',
158
+ header: { 'Content-Type': 'application/json' },
159
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
160
+ timeout: 30000,
161
+ success: (res) => {
162
+ if (res.statusCode === 200 && res.data.translated_text) {
163
+ if (callback) {
164
+ callback(res.data.translated_text);
165
+ } else {
166
+ this.setData({ outputText: res.data.translated_text });
167
+ }
168
+ } else { this.setData({ outputText: 'HF翻译失败' }); }
169
+ },
170
+ fail: () => { this.setData({ outputText: 'HF翻译请求失败' }); }
171
+ });
172
+ }
173
+ });
pages/index/index_v4_old.js ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+ const innerAudioContext = wx.createInnerAudioContext();
3
+ const pluginManager = plugin.getRecordRecognitionManager();
4
+
5
+ Page({
6
+ data: {
7
+ languages: {
8
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
9
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
10
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
11
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
12
+ },
13
+ langCodes: ['zh', 'en', 'ja', 'ko'],
14
+ sourceLang: 'zh',
15
+ targetLang: 'en',
16
+ transcript: '',
17
+ outputText: '',
18
+ isRecording: false,
19
+ sourceLanguages: [],
20
+ targetLanguages: [],
21
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
22
+ currentRecordMode: '' // 'plugin' or 'custom'
23
+ },
24
+
25
+ onLoad: function () {
26
+ this.initializeLanguages();
27
+ this.customRecorderManager = wx.getRecorderManager();
28
+ this.initCustomRecorderManager();
29
+ this.initPluginRecorderManager();
30
+ },
31
+
32
+ // --- Language Selection Logic ---
33
+ initializeLanguages: function () {
34
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
35
+ this.setData({
36
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
37
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
38
+ });
39
+ },
40
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
41
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
42
+ swapLanguages: function() {
43
+ this.setData({
44
+ sourceLang: this.data.targetLang,
45
+ targetLang: this.data.sourceLang,
46
+ transcript: this.data.outputText,
47
+ outputText: this.data.transcript
48
+ }, this.initializeLanguages);
49
+ },
50
+
51
+ // --- Recording Logic ---
52
+ startRecording: function () {
53
+ const { sourceLang, targetLang } = this.data;
54
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
55
+
56
+ this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
57
+
58
+ if (isChineseEnglish) {
59
+ this.setData({ currentRecordMode: 'plugin' });
60
+ pluginManager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
61
+ } else {
62
+ this.setData({ currentRecordMode: 'custom' });
63
+ this.customRecorderManager.start({ duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: 'mp3' });
64
+ }
65
+ },
66
+
67
+ stopRecording: function () {
68
+ if (this.data.currentRecordMode === 'plugin') {
69
+ pluginManager.stop();
70
+ } else if (this.data.currentRecordMode === 'custom') {
71
+ this.customRecorderManager.stop();
72
+ }
73
+ this.setData({ isRecording: false });
74
+ },
75
+
76
+ // --- Result Handlers ---
77
+ initPluginRecorderManager: function() {
78
+ pluginManager.onRecognize = (res) => {
79
+ this.setData({ transcript: res.result });
80
+ };
81
+ // CORRECTED: Use onTranslate to get translation results
82
+ pluginManager.onTranslate = (res) => {
83
+ this.setData({ outputText: res.result });
84
+ };
85
+ pluginManager.onStop = (res) => {
86
+ this.setData({ transcript: res.result, outputText: res.translateResult });
87
+ };
88
+ pluginManager.onError = (res) => {
89
+ this.setData({ transcript: '微信插件识别失败', outputText: res.msg });
90
+ };
91
+ },
92
+
93
+ initCustomRecorderManager: function() {
94
+ this.customRecorderManager.onStop = (res) => {
95
+ if (res.tempFilePath) {
96
+ this.uploadAudioForASR(res.tempFilePath);
97
+ } else { this.setData({ transcript: '录音文件创建失败' }); }
98
+ };
99
+ this.customRecorderManager.onError = () => {
100
+ this.setData({ transcript: '语音识别出错' });
101
+ };
102
+ },
103
+
104
+ // --- Custom Translation Flow (for non-CN/EN pairs) ---
105
+ uploadAudioForASR: function (filePath) {
106
+ this.setData({ transcript: '正在识别 (1/3)...' });
107
+ const fileSystemManager = wx.getFileSystemManager();
108
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
109
+ wx.request({
110
+ url: `${this.data.hfSpaceUrl}/api/asr`,
111
+ method: 'POST',
112
+ header: { 'Content-Type': 'application/json' },
113
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
114
+ timeout: 60000,
115
+ success: (asrRes) => {
116
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
117
+ const transcript = asrRes.data.transcript;
118
+ this.setData({ transcript });
119
+ // Start the full-backend translation bridge
120
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
121
+ } else { this.setData({ transcript: 'HF识别失败' }); }
122
+ },
123
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
124
+ });
125
+ }});
126
+ },
127
+
128
+ fullBackendBridge: function(text, sourceLang, targetLang) {
129
+ // Step 1: Translate source (e.g., JA) to English via HF
130
+ this.setData({ outputText: '翻译中 (2/3)..' });
131
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
132
+ if (englishResult) {
133
+ // Step 2: Translate English result to final target (e.g., ZH) via HF
134
+ this.setData({ outputText: '翻译中 (3/3)..' });
135
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
136
+ if (finalResult) {
137
+ this.setData({ outputText: finalResult });
138
+ }
139
+ });
140
+ }
141
+ });
142
+ },
143
+
144
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
145
+ wx.request({
146
+ url: `${this.data.hfSpaceUrl}/api/translate`,
147
+ method: 'POST',
148
+ header: { 'Content-Type': 'application/json' },
149
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
150
+ timeout: 30000,
151
+ success: (res) => {
152
+ if (res.statusCode === 200 && res.data.translated_text) {
153
+ if (callback) {
154
+ callback(res.data.translated_text);
155
+ } else {
156
+ this.setData({ outputText: res.data.translated_text });
157
+ }
158
+ } else {
159
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
160
+ if (callback) callback(null); // Signal failure
161
+ }
162
+ },
163
+ fail: () => {
164
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
165
+ if (callback) callback(null); // Signal failure
166
+ }
167
+ });
168
+ }
169
+ });
pages/index/index_v5_old.js ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+ const innerAudioContext = wx.createInnerAudioContext();
3
+ const manager = plugin.getRecordRecognitionManager();
4
+
5
+ Page({
6
+ data: {
7
+ languages: {
8
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
9
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
10
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
11
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
12
+ },
13
+ langCodes: ['zh', 'en', 'ja', 'ko'],
14
+ sourceLang: 'zh',
15
+ targetLang: 'en',
16
+ transcript: '',
17
+ outputText: '',
18
+ isRecording: false,
19
+ sourceLanguages: [],
20
+ targetLanguages: [],
21
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
22
+ currentMode: '' // 'plugin-translate' or 'hf-bridge'
23
+ },
24
+
25
+ onLoad: function () {
26
+ this.initializeLanguages();
27
+ this.initRecorderManager();
28
+ },
29
+
30
+ // --- Language Selection Logic ---
31
+ initializeLanguages: function () {
32
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
33
+ this.setData({
34
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
35
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
36
+ });
37
+ },
38
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
39
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
40
+ swapLanguages: function() {
41
+ this.setData({
42
+ sourceLang: this.data.targetLang,
43
+ targetLang: this.data.sourceLang,
44
+ transcript: this.data.outputText,
45
+ outputText: this.data.transcript
46
+ }, this.initializeLanguages);
47
+ },
48
+
49
+ // --- Unified Recording Logic ---
50
+ initRecorderManager: function() {
51
+ manager.onStart = (res) => {
52
+ this.setData({ transcript: '正在聆听...', outputText: '' });
53
+ };
54
+
55
+ manager.onRecognize = (res) => {
56
+ this.setData({ transcript: res.result });
57
+ };
58
+
59
+ manager.onTranslate = (res) => {
60
+ this.setData({ outputText: res.result });
61
+ };
62
+
63
+ manager.onStop = (res) => {
64
+ this.setData({ isRecording: false });
65
+ if (this.data.currentMode === 'plugin-translate') {
66
+ this.setData({ transcript: res.result, outputText: res.translateResult });
67
+ } else if (this.data.currentMode === 'hf-bridge') {
68
+ // For HF mode, we need the audio file to upload.
69
+ if (res.tempFilePath) {
70
+ this.uploadAudioForASR(res.tempFilePath);
71
+ } else {
72
+ this.setData({ transcript: '录音文件获取失败' });
73
+ }
74
+ }
75
+ };
76
+
77
+ manager.onError = (res) => {
78
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
79
+ };
80
+ },
81
+
82
+ startRecording: function () {
83
+ const { sourceLang, targetLang } = this.data;
84
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
85
+
86
+ this.setData({ isRecording: true });
87
+
88
+ if (isChineseEnglish) {
89
+ // Mode 1: Fast, all-in-one ASR and Translation by the plugin
90
+ this.setData({ currentMode: 'plugin-translate' });
91
+ manager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
92
+ } else {
93
+ // Mode 2: HF Bridge. Use plugin for ASR only, then our backend for translation.
94
+ this.setData({ currentMode: 'hf-bridge' });
95
+ // We ask the plugin to record, but NOT to translate.
96
+ manager.start({ lang: this.data.languages[sourceLang].code, translate: false });
97
+ }
98
+ },
99
+
100
+ stopRecording: function () {
101
+ manager.stop();
102
+ },
103
+
104
+ // --- HF Bridge Translation Flow ---
105
+ uploadAudioForASR: function (filePath) {
106
+ this.setData({ transcript: '正在识别 (1/3)...' });
107
+ const fileSystemManager = wx.getFileSystemManager();
108
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
109
+ wx.request({
110
+ url: `${this.data.hfSpaceUrl}/api/asr`,
111
+ method: 'POST',
112
+ header: { 'Content-Type': 'application/json' },
113
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
114
+ timeout: 60000,
115
+ success: (asrRes) => {
116
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
117
+ const transcript = asrRes.data.transcript;
118
+ this.setData({ transcript });
119
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
120
+ } else { this.setData({ transcript: 'HF识别失败' }); }
121
+ },
122
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
123
+ });
124
+ }});
125
+ },
126
+
127
+ fullBackendBridge: function(text, sourceLang, targetLang) {
128
+ this.setData({ outputText: '翻译中 (2/3)..' });
129
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
130
+ if (englishResult) {
131
+ this.setData({ outputText: '翻译中 (3/3)..' });
132
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
133
+ if (finalResult) {
134
+ this.setData({ outputText: finalResult });
135
+ }
136
+ });
137
+ }
138
+ });
139
+ },
140
+
141
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
142
+ wx.request({
143
+ url: `${this.data.hfSpaceUrl}/api/translate`,
144
+ method: 'POST',
145
+ header: { 'Content-Type': 'application/json' },
146
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
147
+ timeout: 30000,
148
+ success: (res) => {
149
+ if (res.statusCode === 200 && res.data.translated_text) {
150
+ callback(res.data.translated_text);
151
+ } else {
152
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
153
+ callback(null);
154
+ }
155
+ },
156
+ fail: () => {
157
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
158
+ callback(null);
159
+ }
160
+ });
161
+ }
162
+ });
pages/index/index_v6_old.js ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+ const manager = plugin.getRecordRecognitionManager();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
9
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
10
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
11
+ },
12
+ langCodes: ['zh', 'en', 'ja', 'ko'],
13
+ sourceLang: 'zh',
14
+ targetLang: 'en',
15
+ transcript: '',
16
+ outputText: '',
17
+ isRecording: false,
18
+ sourceLanguages: [],
19
+ targetLanguages: [],
20
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
21
+ currentMode: '' // 'plugin-translate' or 'hf-bridge'
22
+ },
23
+
24
+ onLoad: function () {
25
+ this.initializeLanguages();
26
+ this.initRecorderManager();
27
+ },
28
+
29
+ // --- Language Selection Logic ---
30
+ initializeLanguages: function () {
31
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
32
+ this.setData({
33
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
34
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
35
+ });
36
+ },
37
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
38
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
39
+ swapLanguages: function() {
40
+ this.setData({
41
+ sourceLang: this.data.targetLang,
42
+ targetLang: this.data.sourceLang,
43
+ transcript: this.data.outputText,
44
+ outputText: this.data.transcript
45
+ }, this.initializeLanguages);
46
+ },
47
+
48
+ // --- Unified, Simplified, and Correct Recording Logic ---
49
+ initRecorderManager: function() {
50
+ manager.onStart = () => {
51
+ this.setData({ transcript: '正在聆听...', outputText: '' });
52
+ };
53
+
54
+ // CORRECTED: Only use onStop for final results to prevent race conditions.
55
+ manager.onStop = (res) => {
56
+ this.setData({ isRecording: false });
57
+ if (this.data.currentMode === 'plugin-translate') {
58
+ this.setData({ transcript: res.result, outputText: res.translateResult });
59
+ } else if (this.data.currentMode === 'hf-bridge') {
60
+ if (res.tempFilePath) {
61
+ this.uploadAudioForASR(res.tempFilePath);
62
+ } else {
63
+ this.setData({ transcript: '录音文件获取失败' });
64
+ }
65
+ }
66
+ };
67
+
68
+ manager.onError = (res) => {
69
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
70
+ };
71
+ },
72
+
73
+ startRecording: function () {
74
+ const { sourceLang, targetLang } = this.data;
75
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
76
+
77
+ this.setData({ isRecording: true });
78
+
79
+ if (isChineseEnglish) {
80
+ this.setData({ currentMode: 'plugin-translate' });
81
+ manager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
82
+ } else {
83
+ this.setData({ currentMode: 'hf-bridge' });
84
+ manager.start({ lang: this.data.languages[sourceLang].code, translate: false });
85
+ }
86
+ },
87
+
88
+ stopRecording: function () {
89
+ manager.stop();
90
+ },
91
+
92
+ // --- HF Bridge Translation Flow ---
93
+ uploadAudioForASR: function (filePath) {
94
+ this.setData({ transcript: '正在识别 (1/3)...' });
95
+ const fileSystemManager = wx.getFileSystemManager();
96
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
97
+ wx.request({
98
+ url: `${this.data.hfSpaceUrl}/api/asr`,
99
+ method: 'POST',
100
+ header: { 'Content-Type': 'application/json' },
101
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
102
+ timeout: 60000,
103
+ success: (asrRes) => {
104
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
105
+ const transcript = asrRes.data.transcript;
106
+ this.setData({ transcript });
107
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
108
+ } else { this.setData({ transcript: 'HF识别失败' }); }
109
+ },
110
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
111
+ });
112
+ }});
113
+ },
114
+
115
+ fullBackendBridge: function(text, sourceLang, targetLang) {
116
+ this.setData({ outputText: '翻译中 (2/3)..' });
117
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
118
+ if (englishResult) {
119
+ this.setData({ outputText: '翻译中 (3/3)..' });
120
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
121
+ if (finalResult) {
122
+ this.setData({ outputText: finalResult });
123
+ }
124
+ });
125
+ }
126
+ });
127
+ },
128
+
129
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
130
+ wx.request({
131
+ url: `${this.data.hfSpaceUrl}/api/translate`,
132
+ method: 'POST',
133
+ header: { 'Content-Type': 'application/json' },
134
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
135
+ timeout: 30000,
136
+ success: (res) => {
137
+ if (res.statusCode === 200 && res.data.translated_text) {
138
+ callback(res.data.translated_text);
139
+ } else {
140
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
141
+ callback(null);
142
+ }
143
+ },
144
+ fail: () => {
145
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
146
+ callback(null);
147
+ }
148
+ });
149
+ }
150
+ });
pages/index/index_v7_old.js ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+
3
+ Page({
4
+ data: {
5
+ languages: {
6
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
7
+ 'en': { name: 'English', flag: 'us', code: 'en_US' },
8
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' },
9
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' }
10
+ },
11
+ langCodes: ['zh', 'en', 'ja', 'ko'],
12
+ sourceLang: 'zh',
13
+ targetLang: 'en',
14
+ transcript: '',
15
+ outputText: '',
16
+ isRecording: false,
17
+ sourceLanguages: [],
18
+ targetLanguages: [],
19
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
20
+ currentMode: '' // 'plugin-translate' or 'hf-bridge'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initRecorderManager(); // Correctly initialize the manager within the page lifecycle
26
+ },
27
+
28
+ // --- Language Selection Logic ---
29
+ initializeLanguages: function () {
30
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
31
+ this.setData({
32
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
33
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
34
+ });
35
+ },
36
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
37
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
38
+ swapLanguages: function() {
39
+ this.setData({
40
+ sourceLang: this.data.targetLang,
41
+ targetLang: this.data.sourceLang,
42
+ transcript: this.data.outputText,
43
+ outputText: this.data.transcript
44
+ }, this.initializeLanguages);
45
+ },
46
+
47
+ // --- CORRECTED: Unified, Lifecycle-Aware Recording Logic ---
48
+ initRecorderManager: function() {
49
+ const manager = plugin.getRecordRecognitionManager();
50
+
51
+ manager.onStart = () => {
52
+ this.setData({ transcript: '正在聆听...', outputText: '' });
53
+ };
54
+
55
+ manager.onStop = (res) => {
56
+ this.setData({ isRecording: false });
57
+ if (this.data.currentMode === 'plugin-translate') {
58
+ if (res.result) {
59
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
60
+ } else {
61
+ this.setData({ transcript: '识别结果为空', outputText: '' });
62
+ }
63
+ } else if (this.data.currentMode === 'hf-bridge') {
64
+ if (res.tempFilePath) {
65
+ this.uploadAudioForASR(res.tempFilePath);
66
+ } else {
67
+ this.setData({ transcript: '录音文件获取失败' });
68
+ }
69
+ }
70
+ };
71
+
72
+ manager.onError = (res) => {
73
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
74
+ };
75
+
76
+ // CRITICAL: Save the manager instance to the page context
77
+ this.manager = manager;
78
+ },
79
+
80
+ startRecording: function () {
81
+ const { sourceLang, targetLang } = this.data;
82
+ const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh');
83
+
84
+ this.setData({ isRecording: true });
85
+
86
+ if (isChineseEnglish) {
87
+ this.setData({ currentMode: 'plugin-translate' });
88
+ this.manager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
89
+ } else {
90
+ this.setData({ currentMode: 'hf-bridge' });
91
+ this.manager.start({ lang: this.data.languages[sourceLang].code, translate: false });
92
+ }
93
+ },
94
+
95
+ stopRecording: function () {
96
+ this.manager.stop();
97
+ },
98
+
99
+ // --- HF Bridge Translation Flow (This part remains the same) ---
100
+ uploadAudioForASR: function (filePath) {
101
+ this.setData({ transcript: '正在识别 (1/3)...' });
102
+ const fileSystemManager = wx.getFileSystemManager();
103
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
104
+ wx.request({
105
+ url: `${this.data.hfSpaceUrl}/api/asr`,
106
+ method: 'POST',
107
+ header: { 'Content-Type': 'application/json' },
108
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
109
+ timeout: 60000,
110
+ success: (asrRes) => {
111
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
112
+ const transcript = asrRes.data.transcript;
113
+ this.setData({ transcript });
114
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
115
+ } else { this.setData({ transcript: 'HF识别失败' }); }
116
+ },
117
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
118
+ });
119
+ }});
120
+ },
121
+
122
+ fullBackendBridge: function(text, sourceLang, targetLang) {
123
+ this.setData({ outputText: '翻译中 (2/3)..' });
124
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
125
+ if (englishResult) {
126
+ this.setData({ outputText: '翻译中 (3/3)..' });
127
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
128
+ if (finalResult) {
129
+ this.setData({ outputText: finalResult });
130
+ }
131
+ });
132
+ }
133
+ });
134
+ },
135
+
136
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
137
+ wx.request({
138
+ url: `${this.data.hfSpaceUrl}/api/translate`,
139
+ method: 'POST',
140
+ header: { 'Content-Type': 'application/json' },
141
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
142
+ timeout: 30000,
143
+ success: (res) => {
144
+ if (res.statusCode === 200 && res.data.translated_text) {
145
+ callback(res.data.translated_text);
146
+ } else {
147
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
148
+ callback(null);
149
+ }
150
+ },
151
+ fail: () => {
152
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
153
+ callback(null);
154
+ }
155
+ });
156
+ }
157
+ });
pages/index/index_v8_old.js ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const recorderManager = wx.getRecorderManager();
2
+
3
+ Page({
4
+ data: {
5
+ languages: {
6
+ 'zh': { name: '中文', flag: 'cn' },
7
+ 'en': { name: 'English', flag: 'us' },
8
+ 'ja': { name: '日本語', flag: 'jp' },
9
+ 'ko': { name: '한국어', flag: 'kr' }
10
+ },
11
+ langCodes: ['zh', 'en', 'ja', 'ko'],
12
+ sourceLang: 'zh',
13
+ targetLang: 'en',
14
+ transcript: '',
15
+ outputText: '',
16
+ isRecording: false,
17
+ sourceLanguages: [],
18
+ targetLanguages: [],
19
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space'
20
+ },
21
+
22
+ onLoad: function () {
23
+ this.initializeLanguages();
24
+ this.initRecorderManager();
25
+ },
26
+
27
+ // --- Language Selection Logic ---
28
+ initializeLanguages: function () {
29
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
30
+ this.setData({
31
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
32
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
33
+ });
34
+ },
35
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
36
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
37
+ swapLanguages: function() {
38
+ this.setData({
39
+ sourceLang: this.data.targetLang,
40
+ targetLang: this.data.sourceLang,
41
+ transcript: this.data.outputText,
42
+ outputText: this.data.transcript
43
+ }, this.initializeLanguages);
44
+ },
45
+
46
+ // --- Simplified, Unified, and Correct Recording Logic ---
47
+ initRecorderManager: function() {
48
+ recorderManager.onStart = () => {
49
+ this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' });
50
+ };
51
+
52
+ recorderManager.onStop = (res) => {
53
+ this.setData({ isRecording: false });
54
+ if (res.tempFilePath) {
55
+ this.uploadAudioForASR(res.tempFilePath);
56
+ } else {
57
+ this.setData({ transcript: '录音文件获取失败' });
58
+ }
59
+ };
60
+
61
+ recorderManager.onError = (res) => {
62
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
63
+ };
64
+ },
65
+
66
+ startRecording: function () {
67
+ recorderManager.start({ duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: 'mp3' });
68
+ },
69
+
70
+ stopRecording: function () {
71
+ recorderManager.stop();
72
+ },
73
+
74
+ // --- Unified Backend Flow ---
75
+ uploadAudioForASR: function (filePath) {
76
+ this.setData({ transcript: '正在识别 (1/3)...' });
77
+ const fileSystemManager = wx.getFileSystemManager();
78
+ fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => {
79
+ wx.request({
80
+ url: `${this.data.hfSpaceUrl}/api/asr`,
81
+ method: 'POST',
82
+ header: { 'Content-Type': 'application/json' },
83
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
84
+ timeout: 60000,
85
+ success: (asrRes) => {
86
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
87
+ const transcript = asrRes.data.transcript;
88
+ this.setData({ transcript });
89
+ // If source and target are the same, no need to translate
90
+ if (this.data.sourceLang === this.data.targetLang) {
91
+ this.setData({ outputText: transcript });
92
+ return;
93
+ }
94
+ this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang);
95
+ } else { this.setData({ transcript: 'HF识别失败' }); }
96
+ },
97
+ fail: () => { this.setData({ transcript: 'HF识别请求失败' }); }
98
+ });
99
+ }});
100
+ },
101
+
102
+ fullBackendBridge: function(text, sourceLang, targetLang) {
103
+ // If source is not English, bridge through English
104
+ if (sourceLang !== 'en') {
105
+ this.setData({ outputText: '翻译中 (2/3)..' });
106
+ this.translateViaHF(text, sourceLang, 'en', (englishResult) => {
107
+ if (englishResult) {
108
+ // If the final target was English, we are done
109
+ if (targetLang === 'en') {
110
+ this.setData({ outputText: englishResult });
111
+ } else {
112
+ this.setData({ outputText: '翻译中 (3/3)..' });
113
+ this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => {
114
+ if (finalResult) {
115
+ this.setData({ outputText: finalResult });
116
+ }
117
+ });
118
+ }
119
+ }
120
+ });
121
+ } else {
122
+ // If source is English, directly translate to target
123
+ this.setData({ outputText: '翻译中 (2/2)..' });
124
+ this.translateViaHF(text, sourceLang, targetLang, (finalResult) => {
125
+ if (finalResult) {
126
+ this.setData({ outputText: finalResult });
127
+ }
128
+ });
129
+ }
130
+ },
131
+
132
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
133
+ wx.request({
134
+ url: `${this.data.hfSpaceUrl}/api/translate`,
135
+ method: 'POST',
136
+ header: { 'Content-Type': 'application/json' },
137
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
138
+ timeout: 30000,
139
+ success: (res) => {
140
+ if (res.statusCode === 200 && res.data.translated_text) {
141
+ callback(res.data.translated_text);
142
+ } else {
143
+ this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` });
144
+ callback(null);
145
+ }
146
+ },
147
+ fail: () => {
148
+ this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` });
149
+ callback(null);
150
+ }
151
+ });
152
+ }
153
+ });
pages/index/index_v9_old.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin('WechatSI');
2
+ const manager = plugin.getRecordRecognitionManager();
3
+
4
+ Page({
5
+ data: {
6
+ languages: {
7
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN' },
8
+ 'en': { name: 'English', flag: 'us', code: 'en_US' }
9
+ },
10
+ langCodes: ['zh', 'en'], // Only CN and EN for this test version
11
+ sourceLang: 'zh',
12
+ targetLang: 'en',
13
+ transcript: '',
14
+ outputText: '',
15
+ isRecording: false,
16
+ sourceLanguages: [],
17
+ targetLanguages: []
18
+ },
19
+
20
+ onLoad: function () {
21
+ this.initializeLanguages();
22
+ this.initRecorderManager();
23
+ },
24
+
25
+ // --- Language Selection Logic (Simplified for CN/EN only) ---
26
+ initializeLanguages: function () {
27
+ const { langCodes, languages, sourceLang, targetLang } = this.data;
28
+ this.setData({
29
+ sourceLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === sourceLang })),
30
+ targetLanguages: langCodes.map(c => ({ ...languages[c], langCode: c, selected: c === targetLang }))
31
+ });
32
+ },
33
+ selectSourceLanguage: function(e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
34
+ selectTargetLanguage: function(e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
35
+ swapLanguages: function() {
36
+ this.setData({
37
+ sourceLang: this.data.targetLang,
38
+ targetLang: this.data.sourceLang,
39
+ transcript: this.data.outputText,
40
+ outputText: this.data.transcript
41
+ }, this.initializeLanguages);
42
+ },
43
+
44
+ // --- Recorder Manager Initialization (Pure Plugin Mode) ---
45
+ initRecorderManager: function() {
46
+ manager.onStart = () => {
47
+ this.setData({ transcript: '正在聆听...', outputText: '' });
48
+ };
49
+
50
+ // Only onStop is used for final results
51
+ manager.onStop = (res) => {
52
+ this.setData({ isRecording: false });
53
+ if (res.result) {
54
+ this.setData({ transcript: res.result, outputText: res.translateResult || '' });
55
+ } else {
56
+ this.setData({ transcript: '识别结果为空', outputText: '' });
57
+ }
58
+ };
59
+
60
+ manager.onError = (res) => {
61
+ this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg });
62
+ };
63
+ },
64
+
65
+ // --- Recording Start/Stop (Pure Plugin Mode) ---
66
+ startRecording: function () {
67
+ const { sourceLang, targetLang } = this.data;
68
+ this.setData({ isRecording: true });
69
+ manager.start({ lang: this.data.languages[sourceLang].code, translate: true, lto: this.data.languages[targetLang].code });
70
+ },
71
+
72
+ stopRecording: function () {
73
+ manager.stop();
74
+ }
75
+ });
pages/index/indexz_v23_old.js ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = requirePlugin("WechatSI");
2
+ const pluginManager = plugin.getRecordRecognitionManager();
3
+ const nativeRecorderManager = wx.getRecorderManager();
4
+
5
+ Page({
6
+ data: {
7
+ languages: {
8
+ 'zh': { name: '中文', flag: 'cn', code: 'zh_CN', usePlugin: true },
9
+ 'en': { name: 'English', flag: 'us', code: 'en_US', usePlugin: true },
10
+ 'ja': { name: '日本語', flag: 'jp', code: 'ja_JP', usePlugin: false },
11
+ 'ko': { name: '한국어', flag: 'kr', code: 'ko_KR', usePlugin: false }
12
+ },
13
+ langCodes: ['zh', 'en', 'ja', 'ko'],
14
+ sourceLang: 'zh',
15
+ targetLang: 'en',
16
+ transcript: '',
17
+ outputText: '',
18
+ isRecording: false,
19
+ hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space',
20
+ currentManagerType: null // 'plugin' or 'native'
21
+ },
22
+
23
+ onLoad: function () {
24
+ this.initializeLanguages();
25
+ this.initPluginManager();
26
+ this.initNativeRecorderManager();
27
+ },
28
+
29
+ // --- Language Selection Logic (Simplified) ---
30
+ initializeLanguages: function () {
31
+ const { languages, sourceLang, targetLang } = this.data;
32
+ this.setData({
33
+ sourceLanguages: Object.keys(languages).map(key => ({ ...languages[key], langCode: key, selected: key === sourceLang })),
34
+ targetLanguages: Object.keys(languages).map(key => ({ ...languages[key], langCode: key, selected: key === targetLang }))
35
+ });
36
+ },
37
+ selectSourceLanguage: function (e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
38
+ selectTargetLanguage: function (e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }, this.initializeLanguages); },
39
+ swapLanguages: function () {
40
+ this.setData({
41
+ sourceLang: this.data.targetLang,
42
+ targetLang: this.data.sourceLang,
43
+ transcript: this.data.outputText,
44
+ outputText: this.data.transcript
45
+ }, this.initializeLanguages);
46
+ },
47
+
48
+ // --- Manager Initializations with Detailed Error Logging ---
49
+ initPluginManager: function () {
50
+ pluginManager.onStart = () => this.setData({ transcript: '正在聆听 (插件)...', outputText: '' });
51
+ pluginManager.onRecognize = (res) => this.setData({ transcript: res.result });
52
+ pluginManager.onStop = (res) => {
53
+ this.setData({ isRecording: false });
54
+ if (!res.result) { return this.setData({ transcript: '识别结果为空' }); }
55
+ this.setData({ transcript: res.result });
56
+ const { sourceLang, targetLang } = this.data;
57
+ const isPluginTarget = this.data.languages[targetLang].usePlugin;
58
+ if (isPluginTarget) {
59
+ this.translateViaPlugin(res.result);
60
+ } else {
61
+ this.translateViaHfBridge(res.result, sourceLang, targetLang);
62
+ }
63
+ };
64
+ pluginManager.onError = (res) => this.setData({ isRecording: false, transcript: `插件错误: ${res.msg}` });
65
+ },
66
+
67
+ initNativeRecorderManager: function () {
68
+ nativeRecorderManager.onStart = () => this.setData({ transcript: '正在聆听 (原生)...', outputText: '' });
69
+ nativeRecorderManager.onStop = (res) => {
70
+ this.setData({ isRecording: false });
71
+ if (res.tempFilePath) { this.uploadAudioForASR(res.tempFilePath); }
72
+ else { this.setData({ transcript: '原生录音文件获取失败' }); }
73
+ };
74
+ nativeRecorderManager.onError = (res) => this.setData({ isRecording: false, transcript: `原生录音失败: ${res.errMsg}` });
75
+ },
76
+
77
+ // --- Main Record Button Handler ---
78
+ handleRecordToggle: function() {
79
+ if (this.data.isRecording) {
80
+ this.stopRecording();
81
+ return;
82
+ }
83
+ // Check permissions before starting a new recording
84
+ wx.getSetting({
85
+ success: (res) => {
86
+ if (!res.authSetting['scope.record']) {
87
+ wx.authorize({
88
+ scope: 'scope.record',
89
+ success: this.startRecording,
90
+ fail: () => wx.showToast({ title: '您拒绝了录音权限', icon: 'none' })
91
+ });
92
+ } else {
93
+ this.startRecording();
94
+ }
95
+ },
96
+ fail: () => wx.showToast({ title: '无法获取权限设置', icon: 'none' })
97
+ });
98
+ },
99
+
100
+ startRecording: function () {
101
+ const { sourceLang, languages } = this.data;
102
+ const shouldUsePlugin = languages[sourceLang].usePlugin;
103
+ const managerType = shouldUsePlugin ? 'plugin' : 'native';
104
+
105
+ this.setData({ isRecording: true, currentManagerType: managerType });
106
+
107
+ if (shouldUsePlugin) {
108
+ pluginManager.start({ lang: languages[sourceLang].code, translate: false });
109
+ } else {
110
+ nativeRecorderManager.start({ format: 'mp3', sampleRate: 16000, numberOfChannels: 1 });
111
+ }
112
+ },
113
+
114
+ stopRecording: function () {
115
+ if (this.data.currentManagerType === 'native') {
116
+ nativeRecorderManager.stop();
117
+ } else {
118
+ pluginManager.stop();
119
+ }
120
+ },
121
+
122
+ // --- Translation Logic ---
123
+ translateViaPlugin: function(text) {
124
+ const { sourceLang, targetLang, languages } = this.data;
125
+ if (sourceLang === targetLang) { return this.setData({ outputText: text }); }
126
+ this.setData({ outputText: '翻译中 (插件)...' });
127
+ plugin.translate({
128
+ lfrom: languages[sourceLang].code,
129
+ lto: languages[targetLang].code,
130
+ content: text,
131
+ success: (res) => {
132
+ if (res.retcode === 0) { this.setData({ outputText: res.result }); }
133
+ else { this.setData({ outputText: `插件翻译失败: ${res.retcode}` }); }
134
+ },
135
+ fail: (err) => this.setData({ outputText: '插件翻译接口调用失败' })
136
+ });
137
+ },
138
+
139
+ uploadAudioForASR: function (filePath) {
140
+ this.setData({ transcript: '正在识别 (HF)...' });
141
+ wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => {
142
+ wx.request({
143
+ url: `${this.data.hfSpaceUrl}/api/asr`,
144
+ method: 'POST',
145
+ data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` },
146
+ timeout: 60000,
147
+ success: (asrRes) => {
148
+ if (asrRes.statusCode === 200 && asrRes.data.transcript) {
149
+ const transcript = asrRes.data.transcript;
150
+ this.setData({ transcript });
151
+ this.translateViaHfBridge(transcript, this.data.sourceLang, this.data.targetLang);
152
+ } else { this.setData({ transcript: 'HF识别失败', outputText: asrRes.data.error || '' }); }
153
+ },
154
+ fail: () => this.setData({ transcript: 'HF识别请求失败' })
155
+ });
156
+ }});
157
+ },
158
+
159
+ translateViaHfBridge: function(text, source, target) {
160
+ if (source === target) { return this.setData({ outputText: text }); }
161
+ this.setData({ outputText: '翻译中 (HF)...' });
162
+ this.translateViaHF(text, source, target, (result) => {
163
+ if (result) { this.setData({ outputText: result }); }
164
+ });
165
+ },
166
+
167
+ translateViaHF: function(text, sourceLang, targetLang, callback) {
168
+ wx.request({
169
+ url: `${this.data.hfSpaceUrl}/api/translate`,
170
+ method: 'POST',
171
+ data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang },
172
+ timeout: 45000,
173
+ success: (res) => {
174
+ if (res.statusCode === 200 && res.data.translated_text) { callback(res.data.translated_text); }
175
+ else { this.setData({ outputText: `HF翻译失败: ${res.data.error || '未知错误'}` }); callback(null); }
176
+ },
177
+ fail: () => { this.setData({ outputText: 'HF翻译请求失败' }); callback(null); }
178
+ });
179
+ }
180
+ });
project.config.json ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "miniprogramRoot": "./",
3
+ "projectname": "wechat-miniprogram-translator",
4
+ "description": "实时翻译工具微信小程序版",
5
+ "appid": "wx4bf4c3f1ca6b34b0",
6
+ "setting": {
7
+ "urlCheck": true,
8
+ "es6": true,
9
+ "enhance": true,
10
+ "postcss": true,
11
+ "minified": true,
12
+ "newFeature": true,
13
+ "coverView": true,
14
+ "nodeModules": true,
15
+ "autoAudits": false,
16
+ "showShadowRootInWxmlPanel": true,
17
+ "scopeDataCheck": false,
18
+ "checkSiteMap": true,
19
+ "enableEngineNative": false,
20
+ "useMultiFrameRuntime": true,
21
+ "useApiHook": true,
22
+ "useApiHostProcess": true,
23
+ "babelSetting": {
24
+ "ignore": [],
25
+ "disablePlugins": [],
26
+ "outputPath": ""
27
+ },
28
+ "condition": {
29
+ "search": {
30
+ "list": []
31
+ },
32
+ "conversation": {
33
+ "list": []
34
+ },
35
+ "plugin": {
36
+ "list": []
37
+ },
38
+ "sitemap": {
39
+ "list": []
40
+ },
41
+ "enableTools": true
42
+ },
43
+ "compileWorklet": false,
44
+ "uglifyFileName": false,
45
+ "uploadWithSourceMap": true,
46
+ "packNpmManually": false,
47
+ "packNpmRelationList": [],
48
+ "minifyWXSS": true,
49
+ "minifyWXML": true,
50
+ "localPlugins": false,
51
+ "disableUseStrict": false,
52
+ "useCompilerPlugins": false,
53
+ "swc": false,
54
+ "disableSWC": true
55
+ },
56
+ "compileType": "miniprogram",
57
+ "libVersion": "3.8.10",
58
+ "srcMiniprogramRoot": "",
59
+ "editorSetting": {
60
+ "tabIndent": "tab",
61
+ "tabSize": 4
62
+ },
63
+ "simulatorPluginLibVersion": {},
64
+ "packOptions": {
65
+ "ignore": [],
66
+ "include": []
67
+ }
68
+ }
project.private.config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "libVersion": "3.8.10",
3
+ "projectname": "%E4%BD%A0%E8%AF%B4%E6%88%91%E8%AF%91project-hf",
4
+ "setting": {
5
+ "urlCheck": false,
6
+ "coverView": true,
7
+ "lazyloadPlaceholderEnable": false,
8
+ "skylineRenderEnable": false,
9
+ "preloadBackgroundData": false,
10
+ "autoAudits": false,
11
+ "useApiHook": true,
12
+ "useApiHostProcess": true,
13
+ "showShadowRootInWxmlPanel": true,
14
+ "useStaticServer": false,
15
+ "useLanDebug": false,
16
+ "showES6CompileOption": false,
17
+ "compileHotReLoad": true,
18
+ "checkInvalidKey": true,
19
+ "ignoreDevUnusedFiles": true,
20
+ "bigPackageSizeSupport": false
21
+ }
22
+ }
requirements.txt CHANGED
@@ -1,11 +1,10 @@
1
  transformers
2
  torch
3
  sentencepiece
4
- gradio
5
  soundfile
6
  sacremoses
7
  fastapi
8
  uvicorn
9
  deepl
10
  python-dotenv
11
- optimum[onnxruntime]
 
1
  transformers
2
  torch
3
  sentencepiece
 
4
  soundfile
5
  sacremoses
6
  fastapi
7
  uvicorn
8
  deepl
9
  python-dotenv
10
+ optimum[onnxruntime]
server/package-lock.json ADDED
@@ -0,0 +1,917 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "server",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "server",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "express": "^5.1.0",
13
+ "node-fetch": "^3.3.2"
14
+ }
15
+ },
16
+ "node_modules/accepts": {
17
+ "version": "2.0.0",
18
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
19
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "mime-types": "^3.0.0",
23
+ "negotiator": "^1.0.0"
24
+ },
25
+ "engines": {
26
+ "node": ">= 0.6"
27
+ }
28
+ },
29
+ "node_modules/body-parser": {
30
+ "version": "2.2.0",
31
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
32
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "bytes": "^3.1.2",
36
+ "content-type": "^1.0.5",
37
+ "debug": "^4.4.0",
38
+ "http-errors": "^2.0.0",
39
+ "iconv-lite": "^0.6.3",
40
+ "on-finished": "^2.4.1",
41
+ "qs": "^6.14.0",
42
+ "raw-body": "^3.0.0",
43
+ "type-is": "^2.0.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ }
48
+ },
49
+ "node_modules/bytes": {
50
+ "version": "3.1.2",
51
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
52
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
53
+ "license": "MIT",
54
+ "engines": {
55
+ "node": ">= 0.8"
56
+ }
57
+ },
58
+ "node_modules/call-bind-apply-helpers": {
59
+ "version": "1.0.2",
60
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
61
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
62
+ "license": "MIT",
63
+ "dependencies": {
64
+ "es-errors": "^1.3.0",
65
+ "function-bind": "^1.1.2"
66
+ },
67
+ "engines": {
68
+ "node": ">= 0.4"
69
+ }
70
+ },
71
+ "node_modules/call-bound": {
72
+ "version": "1.0.4",
73
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
74
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
75
+ "license": "MIT",
76
+ "dependencies": {
77
+ "call-bind-apply-helpers": "^1.0.2",
78
+ "get-intrinsic": "^1.3.0"
79
+ },
80
+ "engines": {
81
+ "node": ">= 0.4"
82
+ },
83
+ "funding": {
84
+ "url": "https://github.com/sponsors/ljharb"
85
+ }
86
+ },
87
+ "node_modules/content-disposition": {
88
+ "version": "1.0.0",
89
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
90
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
91
+ "license": "MIT",
92
+ "dependencies": {
93
+ "safe-buffer": "5.2.1"
94
+ },
95
+ "engines": {
96
+ "node": ">= 0.6"
97
+ }
98
+ },
99
+ "node_modules/content-type": {
100
+ "version": "1.0.5",
101
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
102
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
103
+ "license": "MIT",
104
+ "engines": {
105
+ "node": ">= 0.6"
106
+ }
107
+ },
108
+ "node_modules/cookie": {
109
+ "version": "0.7.2",
110
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
111
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
112
+ "license": "MIT",
113
+ "engines": {
114
+ "node": ">= 0.6"
115
+ }
116
+ },
117
+ "node_modules/cookie-signature": {
118
+ "version": "1.2.2",
119
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
120
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
121
+ "license": "MIT",
122
+ "engines": {
123
+ "node": ">=6.6.0"
124
+ }
125
+ },
126
+ "node_modules/data-uri-to-buffer": {
127
+ "version": "4.0.1",
128
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
129
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
130
+ "license": "MIT",
131
+ "engines": {
132
+ "node": ">= 12"
133
+ }
134
+ },
135
+ "node_modules/debug": {
136
+ "version": "4.4.1",
137
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
138
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
139
+ "license": "MIT",
140
+ "dependencies": {
141
+ "ms": "^2.1.3"
142
+ },
143
+ "engines": {
144
+ "node": ">=6.0"
145
+ },
146
+ "peerDependenciesMeta": {
147
+ "supports-color": {
148
+ "optional": true
149
+ }
150
+ }
151
+ },
152
+ "node_modules/depd": {
153
+ "version": "2.0.0",
154
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
155
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
156
+ "license": "MIT",
157
+ "engines": {
158
+ "node": ">= 0.8"
159
+ }
160
+ },
161
+ "node_modules/dunder-proto": {
162
+ "version": "1.0.1",
163
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
164
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
165
+ "license": "MIT",
166
+ "dependencies": {
167
+ "call-bind-apply-helpers": "^1.0.1",
168
+ "es-errors": "^1.3.0",
169
+ "gopd": "^1.2.0"
170
+ },
171
+ "engines": {
172
+ "node": ">= 0.4"
173
+ }
174
+ },
175
+ "node_modules/ee-first": {
176
+ "version": "1.1.1",
177
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
178
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
179
+ "license": "MIT"
180
+ },
181
+ "node_modules/encodeurl": {
182
+ "version": "2.0.0",
183
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
184
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
185
+ "license": "MIT",
186
+ "engines": {
187
+ "node": ">= 0.8"
188
+ }
189
+ },
190
+ "node_modules/es-define-property": {
191
+ "version": "1.0.1",
192
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
193
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
194
+ "license": "MIT",
195
+ "engines": {
196
+ "node": ">= 0.4"
197
+ }
198
+ },
199
+ "node_modules/es-errors": {
200
+ "version": "1.3.0",
201
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
202
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
203
+ "license": "MIT",
204
+ "engines": {
205
+ "node": ">= 0.4"
206
+ }
207
+ },
208
+ "node_modules/es-object-atoms": {
209
+ "version": "1.1.1",
210
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
211
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
212
+ "license": "MIT",
213
+ "dependencies": {
214
+ "es-errors": "^1.3.0"
215
+ },
216
+ "engines": {
217
+ "node": ">= 0.4"
218
+ }
219
+ },
220
+ "node_modules/escape-html": {
221
+ "version": "1.0.3",
222
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
223
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
224
+ "license": "MIT"
225
+ },
226
+ "node_modules/etag": {
227
+ "version": "1.8.1",
228
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
229
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
230
+ "license": "MIT",
231
+ "engines": {
232
+ "node": ">= 0.6"
233
+ }
234
+ },
235
+ "node_modules/express": {
236
+ "version": "5.1.0",
237
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
238
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
239
+ "license": "MIT",
240
+ "dependencies": {
241
+ "accepts": "^2.0.0",
242
+ "body-parser": "^2.2.0",
243
+ "content-disposition": "^1.0.0",
244
+ "content-type": "^1.0.5",
245
+ "cookie": "^0.7.1",
246
+ "cookie-signature": "^1.2.1",
247
+ "debug": "^4.4.0",
248
+ "encodeurl": "^2.0.0",
249
+ "escape-html": "^1.0.3",
250
+ "etag": "^1.8.1",
251
+ "finalhandler": "^2.1.0",
252
+ "fresh": "^2.0.0",
253
+ "http-errors": "^2.0.0",
254
+ "merge-descriptors": "^2.0.0",
255
+ "mime-types": "^3.0.0",
256
+ "on-finished": "^2.4.1",
257
+ "once": "^1.4.0",
258
+ "parseurl": "^1.3.3",
259
+ "proxy-addr": "^2.0.7",
260
+ "qs": "^6.14.0",
261
+ "range-parser": "^1.2.1",
262
+ "router": "^2.2.0",
263
+ "send": "^1.1.0",
264
+ "serve-static": "^2.2.0",
265
+ "statuses": "^2.0.1",
266
+ "type-is": "^2.0.1",
267
+ "vary": "^1.1.2"
268
+ },
269
+ "engines": {
270
+ "node": ">= 18"
271
+ },
272
+ "funding": {
273
+ "type": "opencollective",
274
+ "url": "https://opencollective.com/express"
275
+ }
276
+ },
277
+ "node_modules/fetch-blob": {
278
+ "version": "3.2.0",
279
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
280
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
281
+ "funding": [
282
+ {
283
+ "type": "github",
284
+ "url": "https://github.com/sponsors/jimmywarting"
285
+ },
286
+ {
287
+ "type": "paypal",
288
+ "url": "https://paypal.me/jimmywarting"
289
+ }
290
+ ],
291
+ "license": "MIT",
292
+ "dependencies": {
293
+ "node-domexception": "^1.0.0",
294
+ "web-streams-polyfill": "^3.0.3"
295
+ },
296
+ "engines": {
297
+ "node": "^12.20 || >= 14.13"
298
+ }
299
+ },
300
+ "node_modules/finalhandler": {
301
+ "version": "2.1.0",
302
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
303
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
304
+ "license": "MIT",
305
+ "dependencies": {
306
+ "debug": "^4.4.0",
307
+ "encodeurl": "^2.0.0",
308
+ "escape-html": "^1.0.3",
309
+ "on-finished": "^2.4.1",
310
+ "parseurl": "^1.3.3",
311
+ "statuses": "^2.0.1"
312
+ },
313
+ "engines": {
314
+ "node": ">= 0.8"
315
+ }
316
+ },
317
+ "node_modules/formdata-polyfill": {
318
+ "version": "4.0.10",
319
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
320
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
321
+ "license": "MIT",
322
+ "dependencies": {
323
+ "fetch-blob": "^3.1.2"
324
+ },
325
+ "engines": {
326
+ "node": ">=12.20.0"
327
+ }
328
+ },
329
+ "node_modules/forwarded": {
330
+ "version": "0.2.0",
331
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
332
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
333
+ "license": "MIT",
334
+ "engines": {
335
+ "node": ">= 0.6"
336
+ }
337
+ },
338
+ "node_modules/fresh": {
339
+ "version": "2.0.0",
340
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
341
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
342
+ "license": "MIT",
343
+ "engines": {
344
+ "node": ">= 0.8"
345
+ }
346
+ },
347
+ "node_modules/function-bind": {
348
+ "version": "1.1.2",
349
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
350
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
351
+ "license": "MIT",
352
+ "funding": {
353
+ "url": "https://github.com/sponsors/ljharb"
354
+ }
355
+ },
356
+ "node_modules/get-intrinsic": {
357
+ "version": "1.3.0",
358
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
359
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
360
+ "license": "MIT",
361
+ "dependencies": {
362
+ "call-bind-apply-helpers": "^1.0.2",
363
+ "es-define-property": "^1.0.1",
364
+ "es-errors": "^1.3.0",
365
+ "es-object-atoms": "^1.1.1",
366
+ "function-bind": "^1.1.2",
367
+ "get-proto": "^1.0.1",
368
+ "gopd": "^1.2.0",
369
+ "has-symbols": "^1.1.0",
370
+ "hasown": "^2.0.2",
371
+ "math-intrinsics": "^1.1.0"
372
+ },
373
+ "engines": {
374
+ "node": ">= 0.4"
375
+ },
376
+ "funding": {
377
+ "url": "https://github.com/sponsors/ljharb"
378
+ }
379
+ },
380
+ "node_modules/get-proto": {
381
+ "version": "1.0.1",
382
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
383
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
384
+ "license": "MIT",
385
+ "dependencies": {
386
+ "dunder-proto": "^1.0.1",
387
+ "es-object-atoms": "^1.0.0"
388
+ },
389
+ "engines": {
390
+ "node": ">= 0.4"
391
+ }
392
+ },
393
+ "node_modules/gopd": {
394
+ "version": "1.2.0",
395
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
396
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
397
+ "license": "MIT",
398
+ "engines": {
399
+ "node": ">= 0.4"
400
+ },
401
+ "funding": {
402
+ "url": "https://github.com/sponsors/ljharb"
403
+ }
404
+ },
405
+ "node_modules/has-symbols": {
406
+ "version": "1.1.0",
407
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
408
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
409
+ "license": "MIT",
410
+ "engines": {
411
+ "node": ">= 0.4"
412
+ },
413
+ "funding": {
414
+ "url": "https://github.com/sponsors/ljharb"
415
+ }
416
+ },
417
+ "node_modules/hasown": {
418
+ "version": "2.0.2",
419
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
420
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
421
+ "license": "MIT",
422
+ "dependencies": {
423
+ "function-bind": "^1.1.2"
424
+ },
425
+ "engines": {
426
+ "node": ">= 0.4"
427
+ }
428
+ },
429
+ "node_modules/http-errors": {
430
+ "version": "2.0.0",
431
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
432
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
433
+ "license": "MIT",
434
+ "dependencies": {
435
+ "depd": "2.0.0",
436
+ "inherits": "2.0.4",
437
+ "setprototypeof": "1.2.0",
438
+ "statuses": "2.0.1",
439
+ "toidentifier": "1.0.1"
440
+ },
441
+ "engines": {
442
+ "node": ">= 0.8"
443
+ }
444
+ },
445
+ "node_modules/http-errors/node_modules/statuses": {
446
+ "version": "2.0.1",
447
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
448
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
449
+ "license": "MIT",
450
+ "engines": {
451
+ "node": ">= 0.8"
452
+ }
453
+ },
454
+ "node_modules/iconv-lite": {
455
+ "version": "0.6.3",
456
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
457
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
458
+ "license": "MIT",
459
+ "dependencies": {
460
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
461
+ },
462
+ "engines": {
463
+ "node": ">=0.10.0"
464
+ }
465
+ },
466
+ "node_modules/inherits": {
467
+ "version": "2.0.4",
468
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
469
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
470
+ "license": "ISC"
471
+ },
472
+ "node_modules/ipaddr.js": {
473
+ "version": "1.9.1",
474
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
475
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
476
+ "license": "MIT",
477
+ "engines": {
478
+ "node": ">= 0.10"
479
+ }
480
+ },
481
+ "node_modules/is-promise": {
482
+ "version": "4.0.0",
483
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
484
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
485
+ "license": "MIT"
486
+ },
487
+ "node_modules/math-intrinsics": {
488
+ "version": "1.1.0",
489
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
490
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
491
+ "license": "MIT",
492
+ "engines": {
493
+ "node": ">= 0.4"
494
+ }
495
+ },
496
+ "node_modules/media-typer": {
497
+ "version": "1.1.0",
498
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
499
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
500
+ "license": "MIT",
501
+ "engines": {
502
+ "node": ">= 0.8"
503
+ }
504
+ },
505
+ "node_modules/merge-descriptors": {
506
+ "version": "2.0.0",
507
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
508
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
509
+ "license": "MIT",
510
+ "engines": {
511
+ "node": ">=18"
512
+ },
513
+ "funding": {
514
+ "url": "https://github.com/sponsors/sindresorhus"
515
+ }
516
+ },
517
+ "node_modules/mime-db": {
518
+ "version": "1.54.0",
519
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
520
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
521
+ "license": "MIT",
522
+ "engines": {
523
+ "node": ">= 0.6"
524
+ }
525
+ },
526
+ "node_modules/mime-types": {
527
+ "version": "3.0.1",
528
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
529
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
530
+ "license": "MIT",
531
+ "dependencies": {
532
+ "mime-db": "^1.54.0"
533
+ },
534
+ "engines": {
535
+ "node": ">= 0.6"
536
+ }
537
+ },
538
+ "node_modules/ms": {
539
+ "version": "2.1.3",
540
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
541
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
542
+ "license": "MIT"
543
+ },
544
+ "node_modules/negotiator": {
545
+ "version": "1.0.0",
546
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
547
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
548
+ "license": "MIT",
549
+ "engines": {
550
+ "node": ">= 0.6"
551
+ }
552
+ },
553
+ "node_modules/node-domexception": {
554
+ "version": "1.0.0",
555
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
556
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
557
+ "deprecated": "Use your platform's native DOMException instead",
558
+ "funding": [
559
+ {
560
+ "type": "github",
561
+ "url": "https://github.com/sponsors/jimmywarting"
562
+ },
563
+ {
564
+ "type": "github",
565
+ "url": "https://paypal.me/jimmywarting"
566
+ }
567
+ ],
568
+ "license": "MIT",
569
+ "engines": {
570
+ "node": ">=10.5.0"
571
+ }
572
+ },
573
+ "node_modules/node-fetch": {
574
+ "version": "3.3.2",
575
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
576
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
577
+ "license": "MIT",
578
+ "dependencies": {
579
+ "data-uri-to-buffer": "^4.0.0",
580
+ "fetch-blob": "^3.1.4",
581
+ "formdata-polyfill": "^4.0.10"
582
+ },
583
+ "engines": {
584
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
585
+ },
586
+ "funding": {
587
+ "type": "opencollective",
588
+ "url": "https://opencollective.com/node-fetch"
589
+ }
590
+ },
591
+ "node_modules/object-inspect": {
592
+ "version": "1.13.4",
593
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
594
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
595
+ "license": "MIT",
596
+ "engines": {
597
+ "node": ">= 0.4"
598
+ },
599
+ "funding": {
600
+ "url": "https://github.com/sponsors/ljharb"
601
+ }
602
+ },
603
+ "node_modules/on-finished": {
604
+ "version": "2.4.1",
605
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
606
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
607
+ "license": "MIT",
608
+ "dependencies": {
609
+ "ee-first": "1.1.1"
610
+ },
611
+ "engines": {
612
+ "node": ">= 0.8"
613
+ }
614
+ },
615
+ "node_modules/once": {
616
+ "version": "1.4.0",
617
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
618
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
619
+ "license": "ISC",
620
+ "dependencies": {
621
+ "wrappy": "1"
622
+ }
623
+ },
624
+ "node_modules/parseurl": {
625
+ "version": "1.3.3",
626
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
627
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
628
+ "license": "MIT",
629
+ "engines": {
630
+ "node": ">= 0.8"
631
+ }
632
+ },
633
+ "node_modules/path-to-regexp": {
634
+ "version": "8.2.0",
635
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
636
+ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
637
+ "license": "MIT",
638
+ "engines": {
639
+ "node": ">=16"
640
+ }
641
+ },
642
+ "node_modules/proxy-addr": {
643
+ "version": "2.0.7",
644
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
645
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
646
+ "license": "MIT",
647
+ "dependencies": {
648
+ "forwarded": "0.2.0",
649
+ "ipaddr.js": "1.9.1"
650
+ },
651
+ "engines": {
652
+ "node": ">= 0.10"
653
+ }
654
+ },
655
+ "node_modules/qs": {
656
+ "version": "6.14.0",
657
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
658
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
659
+ "license": "BSD-3-Clause",
660
+ "dependencies": {
661
+ "side-channel": "^1.1.0"
662
+ },
663
+ "engines": {
664
+ "node": ">=0.6"
665
+ },
666
+ "funding": {
667
+ "url": "https://github.com/sponsors/ljharb"
668
+ }
669
+ },
670
+ "node_modules/range-parser": {
671
+ "version": "1.2.1",
672
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
673
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
674
+ "license": "MIT",
675
+ "engines": {
676
+ "node": ">= 0.6"
677
+ }
678
+ },
679
+ "node_modules/raw-body": {
680
+ "version": "3.0.0",
681
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
682
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
683
+ "license": "MIT",
684
+ "dependencies": {
685
+ "bytes": "3.1.2",
686
+ "http-errors": "2.0.0",
687
+ "iconv-lite": "0.6.3",
688
+ "unpipe": "1.0.0"
689
+ },
690
+ "engines": {
691
+ "node": ">= 0.8"
692
+ }
693
+ },
694
+ "node_modules/router": {
695
+ "version": "2.2.0",
696
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
697
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
698
+ "license": "MIT",
699
+ "dependencies": {
700
+ "debug": "^4.4.0",
701
+ "depd": "^2.0.0",
702
+ "is-promise": "^4.0.0",
703
+ "parseurl": "^1.3.3",
704
+ "path-to-regexp": "^8.0.0"
705
+ },
706
+ "engines": {
707
+ "node": ">= 18"
708
+ }
709
+ },
710
+ "node_modules/safe-buffer": {
711
+ "version": "5.2.1",
712
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
713
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
714
+ "funding": [
715
+ {
716
+ "type": "github",
717
+ "url": "https://github.com/sponsors/feross"
718
+ },
719
+ {
720
+ "type": "patreon",
721
+ "url": "https://www.patreon.com/feross"
722
+ },
723
+ {
724
+ "type": "consulting",
725
+ "url": "https://feross.org/support"
726
+ }
727
+ ],
728
+ "license": "MIT"
729
+ },
730
+ "node_modules/safer-buffer": {
731
+ "version": "2.1.2",
732
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
733
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
734
+ "license": "MIT"
735
+ },
736
+ "node_modules/send": {
737
+ "version": "1.2.0",
738
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
739
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
740
+ "license": "MIT",
741
+ "dependencies": {
742
+ "debug": "^4.3.5",
743
+ "encodeurl": "^2.0.0",
744
+ "escape-html": "^1.0.3",
745
+ "etag": "^1.8.1",
746
+ "fresh": "^2.0.0",
747
+ "http-errors": "^2.0.0",
748
+ "mime-types": "^3.0.1",
749
+ "ms": "^2.1.3",
750
+ "on-finished": "^2.4.1",
751
+ "range-parser": "^1.2.1",
752
+ "statuses": "^2.0.1"
753
+ },
754
+ "engines": {
755
+ "node": ">= 18"
756
+ }
757
+ },
758
+ "node_modules/serve-static": {
759
+ "version": "2.2.0",
760
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
761
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
762
+ "license": "MIT",
763
+ "dependencies": {
764
+ "encodeurl": "^2.0.0",
765
+ "escape-html": "^1.0.3",
766
+ "parseurl": "^1.3.3",
767
+ "send": "^1.2.0"
768
+ },
769
+ "engines": {
770
+ "node": ">= 18"
771
+ }
772
+ },
773
+ "node_modules/setprototypeof": {
774
+ "version": "1.2.0",
775
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
776
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
777
+ "license": "ISC"
778
+ },
779
+ "node_modules/side-channel": {
780
+ "version": "1.1.0",
781
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
782
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
783
+ "license": "MIT",
784
+ "dependencies": {
785
+ "es-errors": "^1.3.0",
786
+ "object-inspect": "^1.13.3",
787
+ "side-channel-list": "^1.0.0",
788
+ "side-channel-map": "^1.0.1",
789
+ "side-channel-weakmap": "^1.0.2"
790
+ },
791
+ "engines": {
792
+ "node": ">= 0.4"
793
+ },
794
+ "funding": {
795
+ "url": "https://github.com/sponsors/ljharb"
796
+ }
797
+ },
798
+ "node_modules/side-channel-list": {
799
+ "version": "1.0.0",
800
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
801
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
802
+ "license": "MIT",
803
+ "dependencies": {
804
+ "es-errors": "^1.3.0",
805
+ "object-inspect": "^1.13.3"
806
+ },
807
+ "engines": {
808
+ "node": ">= 0.4"
809
+ },
810
+ "funding": {
811
+ "url": "https://github.com/sponsors/ljharb"
812
+ }
813
+ },
814
+ "node_modules/side-channel-map": {
815
+ "version": "1.0.1",
816
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
817
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
818
+ "license": "MIT",
819
+ "dependencies": {
820
+ "call-bound": "^1.0.2",
821
+ "es-errors": "^1.3.0",
822
+ "get-intrinsic": "^1.2.5",
823
+ "object-inspect": "^1.13.3"
824
+ },
825
+ "engines": {
826
+ "node": ">= 0.4"
827
+ },
828
+ "funding": {
829
+ "url": "https://github.com/sponsors/ljharb"
830
+ }
831
+ },
832
+ "node_modules/side-channel-weakmap": {
833
+ "version": "1.0.2",
834
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
835
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
836
+ "license": "MIT",
837
+ "dependencies": {
838
+ "call-bound": "^1.0.2",
839
+ "es-errors": "^1.3.0",
840
+ "get-intrinsic": "^1.2.5",
841
+ "object-inspect": "^1.13.3",
842
+ "side-channel-map": "^1.0.1"
843
+ },
844
+ "engines": {
845
+ "node": ">= 0.4"
846
+ },
847
+ "funding": {
848
+ "url": "https://github.com/sponsors/ljharb"
849
+ }
850
+ },
851
+ "node_modules/statuses": {
852
+ "version": "2.0.2",
853
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
854
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
855
+ "license": "MIT",
856
+ "engines": {
857
+ "node": ">= 0.8"
858
+ }
859
+ },
860
+ "node_modules/toidentifier": {
861
+ "version": "1.0.1",
862
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
863
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
864
+ "license": "MIT",
865
+ "engines": {
866
+ "node": ">=0.6"
867
+ }
868
+ },
869
+ "node_modules/type-is": {
870
+ "version": "2.0.1",
871
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
872
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
873
+ "license": "MIT",
874
+ "dependencies": {
875
+ "content-type": "^1.0.5",
876
+ "media-typer": "^1.1.0",
877
+ "mime-types": "^3.0.0"
878
+ },
879
+ "engines": {
880
+ "node": ">= 0.6"
881
+ }
882
+ },
883
+ "node_modules/unpipe": {
884
+ "version": "1.0.0",
885
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
886
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
887
+ "license": "MIT",
888
+ "engines": {
889
+ "node": ">= 0.8"
890
+ }
891
+ },
892
+ "node_modules/vary": {
893
+ "version": "1.1.2",
894
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
895
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
896
+ "license": "MIT",
897
+ "engines": {
898
+ "node": ">= 0.8"
899
+ }
900
+ },
901
+ "node_modules/web-streams-polyfill": {
902
+ "version": "3.3.3",
903
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
904
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
905
+ "license": "MIT",
906
+ "engines": {
907
+ "node": ">= 8"
908
+ }
909
+ },
910
+ "node_modules/wrappy": {
911
+ "version": "1.0.2",
912
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
913
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
914
+ "license": "ISC"
915
+ }
916
+ }
917
+ }
server/package.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "server",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": [],
9
+ "author": "",
10
+ "license": "ISC",
11
+ "description": "",
12
+ "dependencies": {
13
+ "express": "^4.19.2",
14
+ "fluent-ffmpeg": "^2.1.2",
15
+ "form-data": "^4.0.0",
16
+ "multer": "^1.4.5-lts.1",
17
+ "node-fetch": "^2.7.0"
18
+ }
19
+ }
server/server.js ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const multer = require('multer');
3
+ const FormData = require('form-data');
4
+ const axios = require('axios'); // 使用 axios 替换 node-fetch
5
+ const fetch = require('node-fetch'); // 保留用于翻译接口
6
+
7
+ const app = express();
8
+ const port = 3000;
9
+
10
+ const storage = multer.memoryStorage();
11
+ const upload = multer({ storage: storage });
12
+
13
+ app.use(express.json());
14
+ app.use(express.urlencoded({ extended: true }));
15
+ app.use((req, res, next) => {
16
+ res.header('Access-Control-Allow-Origin', '*');
17
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
18
+ next();
19
+ });
20
+
21
+ const PYTHON_ASR_SERVICE_URL = 'http://localhost:5001/transcribe';
22
+
23
+ // ASR Endpoint: 使用 axios 重写,确保超时正确生效
24
+ app.post('/asr', upload.single('audio'), async (req, res) => {
25
+ console.log("Received ASR request, proxying to Python service with axios...");
26
+
27
+ if (!req.file) {
28
+ return res.status(400).json({ error: 'No audio file uploaded.' });
29
+ }
30
+
31
+ try {
32
+ const form = new FormData();
33
+ form.append('audio', req.file.buffer, {
34
+ filename: req.file.originalname,
35
+ contentType: req.file.mimetype,
36
+ });
37
+
38
+ const langCode = req.body.sourceLang.split('-')[0];
39
+ form.append('language', langCode);
40
+ console.log(`Forwarding audio buffer with language '${langCode}'...`);
41
+
42
+ // 使用 axios 发送请求,并设置一个可靠的60秒超时
43
+ const asrResponse = await axios.post(PYTHON_ASR_SERVICE_URL, form, {
44
+ headers: form.getHeaders(),
45
+ timeout: 60000 // 60秒超时,作用于整个请求
46
+ });
47
+
48
+ const asrData = asrResponse.data;
49
+
50
+ console.log(`Transcription result: "${asrData.transcript}"`);
51
+ res.json({ transcript: asrData.transcript });
52
+
53
+ } catch (error) {
54
+ if (error.code === 'ECONNABORTED') {
55
+ console.error("Failed to process ASR request: Axios request timed out.");
56
+ res.status(504).json({ error: 'Request to ASR service timed out.' });
57
+ } else {
58
+ console.error("Failed to process ASR request:", error.message);
59
+ res.status(500).json({ error: error.message });
60
+ }
61
+ }
62
+ });
63
+
64
+ // Translate Endpoint (保持不变)
65
+ app.post('/translate', async (req, res) => {
66
+ const { text, sourceLang, targetLang } = req.body;
67
+ if (!text || !sourceLang || !targetLang) {
68
+ return res.status(400).json({ error: 'Missing text, sourceLang, or targetLang' });
69
+ }
70
+ const source = sourceLang.split('-')[0];
71
+ const target = targetLang.split('-')[0];
72
+ if (source === target) {
73
+ return res.json({ translatedText: text });
74
+ }
75
+ const apiUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${source}&tl=${target}&dt=t&q=${encodeURIComponent(text)}`;
76
+ try {
77
+ const response = await fetch(apiUrl);
78
+ const data = await response.json();
79
+ if (data && data[0] && data[0][0] && data[0][0][0]) {
80
+ const translatedText = data[0].map(segment => segment[0]).join('');
81
+ res.json({ translatedText });
82
+ } else {
83
+ throw new Error('Unexpected API response from translation service');
84
+ }
85
+ } catch (error) {
86
+ console.error('Translation request error:', error.message);
87
+ res.status(500).json({ error: 'Translation failed' });
88
+ }
89
+ });
90
+
91
+ app.get('/', (req, res) => {
92
+ res.status(200).send('Node.js server is running and healthy!');
93
+ });
94
+
95
+ app.listen(port, () => {
96
+ console.log(`Node.js proxy server listening at http://localhost:${port}`);
97
+ });
server/server.js.old ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // wechat-miniprogram-translator/server/server.js
2
+ const express = require('express');
3
+ const fetch = require('node-fetch');
4
+ const app = express();
5
+ const port = 3000;
6
+
7
+ app.use(express.json());
8
+ app.use(express.urlencoded({ extended: true }));
9
+
10
+ // CORS for development (allow requests from any origin)
11
+ app.use((req, res, next) => {
12
+ res.header('Access-Control-Allow-Origin', '*');
13
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
14
+ next();
15
+ });
16
+
17
+ // ASR (Speech-to-Text) Endpoint - Placeholder
18
+ app.post('/asr', (req, res) => {
19
+ // In a real application, you would receive the audio file here (e.g., via multipart/form-data)
20
+ // and send it to a real ASR service (e.g., Tencent Cloud, Baidu AI, Google Speech-to-Text).
21
+ // For this prototype, we'll simulate a response.
22
+ console.log('Received ASR request. Simulating response...');
23
+ const simulatedText = '这是模拟的语音识别结果。'; // You can change this for testing
24
+ res.json({ transcript: simulatedText });
25
+ });
26
+
27
+ // Translation Endpoint - Proxy for Google Translate API
28
+ app.post('/translate', async (req, res) => {
29
+ const { text, sourceLang, targetLang } = req.body;
30
+
31
+ if (!text || !sourceLang || !targetLang) {
32
+ return res.status(400).json({ error: 'Missing text, sourceLang, or targetLang' });
33
+ }
34
+
35
+ const source = sourceLang.split('-')[0];
36
+ const target = targetLang.split('-')[0];
37
+
38
+ if (source === target) {
39
+ return res.json({ translatedText: text });
40
+ }
41
+
42
+ const apiUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${source}&tl=${target}&dt=t&q=${encodeURIComponent(text)}`;
43
+
44
+ try {
45
+ const response = await fetch(apiUrl);
46
+ const data = await response.json();
47
+
48
+ if (data && data[0] && data[0][0] && data[0][0][0]) {
49
+ const translatedText = data[0].map(segment => segment[0]).join('');
50
+ res.json({ translatedText });
51
+ } else {
52
+ console.error('Translation API returned unexpected data:', data);
53
+ res.status(500).json({ error: 'Translation failed: Unexpected API response' });
54
+ }
55
+ } catch (error) {
56
+ console.error('Error calling Google Translate API:', error);
57
+ res.status(500).json({ error: 'Translation failed: Network error or API issue' });
58
+ }
59
+ });
60
+
61
+ app.listen(port, () => {
62
+ console.log(`Backend server listening at http://localhost:${port}`);
63
+ console.log("Remember to start this server before running the Mini Program!");
64
+ });
start_asr.sh ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # This script starts the Gunicorn server for the ASR application.
3
+
4
+ echo "Starting ASR service with Gunicorn..."
5
+
6
+ # Absolute path to the project directory
7
+ PROJECT_DIR="/home/ubuntu/wechat-miniprogram-translator"
8
+
9
+ # Absolute path to the Gunicorn executable within the venv
10
+ VENV_GUNICORN="${PROJECT_DIR}/venv/bin/gunicorn"
11
+
12
+ # Check if the Gunicorn executable exists
13
+ if [ ! -f "$VENV_GUNICORN" ]; then
14
+ echo "Error: Gunicorn executable not found at $VENV_GUNICORN"
15
+ echo "Please make sure you have run 'pip install gunicorn' in your venv."
16
+ exit 1
17
+ fi
18
+
19
+ # Navigate to the directory containing asr_server.py
20
+ cd "$PROJECT_DIR" || exit
21
+
22
+ # Explanation of parameters:
23
+ # --workers 1: Use a single worker process.
24
+ # --max-requests 1: CRITICAL! Restart the worker after every single request to prevent state issues.
25
+ # --timeout 120: Allow each worker up to 120 seconds to process a request.
26
+ # --bind 0.0.0.0:5001: Listen on port 5001 on all network interfaces.
27
+ # asr_server:app: The Flask application instance 'app' from the 'asr_server.py' file.
28
+ # "$@": This is the crucial part. It takes all arguments passed to this script
29
+ # (from ecosystem.config.js, e.g., --model tiny) and passes them to the python script.
30
+ exec "$VENV_GUNICORN" --workers 1 --max-requests 1 --timeout 120 --bind 0.0.0.0:5001 asr_server:app -- "$@"
translate.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from transformers import MarianMTModel, MarianTokenizer
3
+
4
+ def translate_text(text, source_lang, target_lang):
5
+ """
6
+ Translates text from a source language to a target language.
7
+ """
8
+ try:
9
+ # Define the model name based on source and target languages
10
+ # The format is 'Helsinki-NLP/opus-mt-{source}-{target}'
11
+ model_name = f'Helsinki-NLP/opus-mt-{source_lang}-{target_lang}'
12
+
13
+ # Load the tokenizer and model.
14
+ # The first time a model is used, it will be downloaded from Hugging Face.
15
+ # This might take a moment. Subsequent uses will load from cache.
16
+ tokenizer = MarianTokenizer.from_pretrained(model_name)
17
+ model = MarianMTModel.from_pretrained(model_name)
18
+
19
+ # Tokenize the input text
20
+ tokenized_text = tokenizer(text, return_tensors="pt", padding=True)
21
+
22
+ # Generate the translation
23
+ translated_tokens = model.generate(**tokenized_text)
24
+
25
+ # Decode the translated tokens into text
26
+ translated_text = tokenizer.decode(translated_tokens[0], skip_special_tokens=True)
27
+
28
+ return translated_text
29
+ except Exception as e:
30
+ # Handle cases where a direct model doesn't exist (e.g., zh-es)
31
+ # or other errors.
32
+ return f"Error during translation: {str(e)}"
33
+
34
+
35
+ if __name__ == "__main__":
36
+ # The script expects three arguments: text, source_lang, target_lang
37
+ if len(sys.argv) != 4:
38
+ print("Usage: python translate.py <text_to_translate> <source_lang> <target_lang>")
39
+ sys.exit(1)
40
+
41
+ input_text = sys.argv[1]
42
+ source_language = sys.argv[2]
43
+ target_language = sys.argv[3]
44
+
45
+ # The models use 2-letter language codes (e.g., 'en', 'zh', 'es')
46
+ # We take the first part of the lang code (e.g., 'zh-CN' -> 'zh')
47
+ source_code = source_language.split('-')[0]
48
+ target_code = target_language.split('-')[0]
49
+
50
+ translated_output = translate_text(input_text, source_code, target_code)
51
+ print(translated_output)