@minimalcorp/tsunagi 0.4.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  2. package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
  3. package/.next/standalone/apps/web/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
  5. package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  13. package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +2 -2
  14. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  17. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
  21. package/.next/standalone/apps/web/.next/server/app/index.rsc +3 -3
  22. package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  23. package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +3 -3
  24. package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  25. package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  26. package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  27. package/.next/standalone/apps/web/.next/server/app/page.js.nft.json +1 -1
  28. package/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  29. package/.next/standalone/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  30. package/.next/standalone/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  31. package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
  32. package/.next/standalone/apps/web/.next/server/app/settings.rsc +3 -3
  33. package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  34. package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  35. package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  36. package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  37. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  38. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  39. package/.next/standalone/apps/web/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
  40. package/.next/standalone/apps/web/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0qjafjt._.js +1 -1
  42. package/.next/standalone/apps/web/.next/server/chunks/ssr/{_13w5ytx._.js → _01~tp-i._.js} +2 -2
  43. package/.next/standalone/apps/web/.next/server/chunks/ssr/{_0pw3_r_._.js → _03lm.~7._.js} +3 -3
  44. package/.next/standalone/apps/web/.next/server/chunks/ssr/{_0rb6-mo._.js → _09br4yx._.js} +3 -3
  45. package/.next/standalone/apps/web/.next/server/chunks/ssr/_0h-u~an._.js +7 -0
  46. package/.next/standalone/apps/web/.next/server/chunks/ssr/{_139_l.u._.js → _0kkt7a_._.js} +2 -2
  47. package/.next/standalone/apps/web/.next/server/chunks/ssr/_13ne7ej._.js +3 -0
  48. package/.next/standalone/apps/web/.next/server/chunks/ssr/apps_web_src_app_settings_page_tsx_0i6itlb._.js +1 -3
  49. package/.next/standalone/apps/web/.next/server/chunks/ssr/apps_web_src_app_tasks_[id]_page_tsx_0jshsm2._.js +2 -2
  50. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0nzbb9~._.js → node_modules_0ac2-~z._.js} +1 -1
  51. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0e8uni7._.js → node_modules_0gglkx9._.js} +1 -1
  52. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0zi-gfw._.js → node_modules_0i5v48e._.js} +1 -1
  53. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0xzmx-x._.js → node_modules_0k3x6_w._.js} +1 -1
  54. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0~aiu12._.js → node_modules_0rnsluy._.js} +1 -1
  55. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0vj~e_v._.js → node_modules_0t-jssg._.js} +1 -1
  56. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0r6t2tu._.js → node_modules_0ut2-yw._.js} +1 -1
  57. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_0925te2._.js → node_modules_0w6fm3f._.js} +2 -2
  58. package/.next/standalone/apps/web/.next/server/chunks/ssr/{node_modules_03-rvdo._.js → node_modules_0zi0w9_._.js} +1 -1
  59. package/.next/standalone/apps/web/.next/server/chunks/ssr/{_0w2dm3r._.js → node_modules_lucide-react_dist_esm_icons_0zerlct._.js} +2 -2
  60. package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
  61. package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  62. package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  63. package/.next/standalone/apps/web/.next/server/server-reference-manifest.js +1 -1
  64. package/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
  65. package/.next/standalone/apps/web/.next/static/chunks/{0l83q95s~tih4.js → 08s3o7outb~0z.js} +1 -1
  66. package/.next/standalone/apps/web/.next/static/chunks/0a2u28f44p7t..js +1 -0
  67. package/.next/standalone/apps/web/.next/static/chunks/0dgu~u-1e5b7-.js +5 -0
  68. package/.next/standalone/apps/web/.next/static/chunks/{0whl1y16cvn5q.js → 0e8u7rqrqfiiy.js} +1 -1
  69. package/.next/standalone/apps/web/.next/static/chunks/0n72w4sxrwoj4.js +5 -0
  70. package/.next/standalone/apps/web/.next/static/chunks/0o1bh_qwdvmid.css +4 -0
  71. package/.next/standalone/apps/web/.next/static/chunks/{0smdykrt42e.5.js → 0t3d~cifmhdv9.js} +2 -2
  72. package/.next/standalone/apps/web/.next/static/chunks/{17~93fal1fw_d.js → 0uugzgqr-ug3_.js} +1 -1
  73. package/.next/standalone/apps/web/.next/static/chunks/0ysgl4vmer~v5.js +6 -0
  74. package/.next/standalone/apps/web/.next/static/chunks/13_9zaka0bif_.js +1 -0
  75. package/.next/standalone/apps/web/.next/static/chunks/{1624992q-gpj2.js → 1662w8cu9n67h.js} +2 -2
  76. package/.next/standalone/apps/web/package.json +0 -3
  77. package/dist/app.js +0 -1
  78. package/dist/server/index.js +0 -5
  79. package/dist/server/lib/agent-profiles.d.ts +1 -0
  80. package/dist/server/lib/agent-profiles.js +5 -2
  81. package/dist/server/pty-manager.d.ts +28 -2
  82. package/dist/server/pty-manager.js +71 -4
  83. package/dist/server/routes/terminal.js +24 -48
  84. package/npm-shrinkwrap.json +227 -226
  85. package/package.json +1 -1
  86. package/.next/standalone/apps/web/.next/server/chunks/ssr/[root-of-the-server]__11y.qj~._.js +0 -3
  87. package/.next/standalone/apps/web/.next/server/chunks/ssr/_03vftq7._.js +0 -7
  88. package/.next/standalone/apps/web/.next/server/chunks/ssr/_05n88to._.js +0 -3
  89. package/.next/standalone/apps/web/.next/static/chunks/0.gcmzq82~36i.js +0 -1
  90. package/.next/standalone/apps/web/.next/static/chunks/06gfh91..uydi.js +0 -5
  91. package/.next/standalone/apps/web/.next/static/chunks/09b_3duxnyfto.js +0 -7
  92. package/.next/standalone/apps/web/.next/static/chunks/0i9h-39w91ybd.js +0 -6
  93. package/.next/standalone/apps/web/.next/static/chunks/0makl-d3f0_qh.js +0 -2878
  94. package/.next/standalone/apps/web/.next/static/chunks/0pm0knaijueoc.css +0 -4
  95. package/.next/standalone/apps/web/.next/static/chunks/14zh2vf8ljbxo.js +0 -1
  96. package/.next/standalone/apps/web/public/vad/ort-wasm-simd-threaded.mjs +0 -59
  97. package/.next/standalone/apps/web/public/vad/ort-wasm-simd-threaded.wasm +0 -0
  98. package/.next/standalone/apps/web/public/vad/silero_vad_v5.onnx +0 -0
  99. package/.next/standalone/apps/web/public/vad/vad.worklet.bundle.min.js +0 -1
  100. package/dist/server/lib/asr-models.d.ts +0 -7
  101. package/dist/server/lib/asr-models.js +0 -51
  102. package/dist/server/lib/process-port.d.ts +0 -1
  103. package/dist/server/lib/process-port.js +0 -28
  104. package/dist/server/lib/python-venv.d.ts +0 -4
  105. package/dist/server/lib/python-venv.js +0 -66
  106. package/dist/server/lib/voice-debug-log.d.ts +0 -9
  107. package/dist/server/lib/voice-debug-log.js +0 -28
  108. package/dist/server/lib/whisper-process.d.ts +0 -40
  109. package/dist/server/lib/whisper-process.js +0 -333
  110. package/dist/server/routes/whisper.d.ts +0 -2
  111. package/dist/server/routes/whisper.js +0 -103
  112. package/dist/whisper-server/README.md +0 -31
  113. package/dist/whisper-server/download_model.py +0 -19
  114. package/dist/whisper-server/requirements.txt +0 -8
  115. package/dist/whisper-server/run.sh +0 -62
  116. package/dist/whisper-server/server.py +0 -147
  117. /package/.next/standalone/apps/web/.next/static/{ZcOuTrikuO_ZLaERk7FsW → h5xwYydO3EbBSaceTFcLG}/_buildManifest.js +0 -0
  118. /package/.next/standalone/apps/web/.next/static/{ZcOuTrikuO_ZLaERk7FsW → h5xwYydO3EbBSaceTFcLG}/_clientMiddlewareManifest.js +0 -0
  119. /package/.next/standalone/apps/web/.next/static/{ZcOuTrikuO_ZLaERk7FsW → h5xwYydO3EbBSaceTFcLG}/_ssgManifest.js +0 -0
@@ -1,147 +0,0 @@
1
- """ローカル音声認識サーバー (Apple Silicon GPU)。
2
-
3
- 読み込むモデルは起動時の環境変数で切り替える(tsunagiのSettingsで選択したものが渡される)。
4
- - TSUNAGI_ASR_ENGINE: "whisper"(mlx-whisper) / "qwen3-asr"(mlx-qwen3-asr)
5
- - TSUNAGI_ASR_MODEL: Hugging FaceのリポジトリID
6
-
7
- ユーザーが手動でセットアップ・起動する常駐プロセス。モデルをメモリに保持し続け
8
- リクエスト毎のロード待ちを避ける。tsunagi本体(Fastify)からHTTPでプロキシされる。
9
-
10
- 音声はブラウザ側のVAD(Silero VAD)で発話区間だけを切り出した16kHzモノラルWAVを受け取る。
11
- デコードにはPyAV(`av`)を使う(WAV以外の形式もそのままデコードできる)。ffmpegの内部コーデックをホイールに同梱しており、
12
- システムにffmpegバイナリをインストールする必要はない。
13
- """
14
-
15
- import io
16
- import os
17
- import re
18
- import threading
19
- from typing import Optional
20
-
21
- import av
22
- import numpy as np
23
- from fastapi import FastAPI, File, Form, UploadFile
24
-
25
- ENGINE = os.environ.get("TSUNAGI_ASR_ENGINE", "whisper")
26
- MODEL = os.environ.get("TSUNAGI_ASR_MODEL", "mlx-community/whisper-large-v3-turbo")
27
- if ENGINE not in ("whisper", "qwen3-asr"):
28
- raise SystemExit(f"Unknown TSUNAGI_ASR_ENGINE: {ENGINE}")
29
-
30
- if ENGINE == "whisper":
31
- import mlx_whisper
32
- else:
33
- from mlx_qwen3_asr import Session
34
-
35
- # Qwen3-ASRはモデルを保持するSessionを起動時に作り、リクエスト毎のロード待ちを避ける。
36
- # 読み込み完了まで/healthが応答しないため、tsunagi側は読み込み完了を起動完了とみなせる。
37
- _qwen_session = Session(model=MODEL)
38
- # 無音・雑音区間で「ご視聴ありがとうございました」等の無関係な文章を自信満々に
39
- # 生成してしまう(Whisper系モデルで知られたハルシネーション挙動)ことがあるため、
40
- # no_speech_prob(無音である確率)がこの値を超えるセグメントは出力から除外する。
41
- NO_SPEECH_THRESHOLD = 0.6
42
- # ただしno_speech_probは30秒の窓ごとに1回しか計算されず、窓内の全セグメントに同じ値が
43
- # コピーされる。そのため「発話+末尾の無音」を含む窓では、無音部分から生成された定型文も
44
- # 発話ありと判定されて素通りする。これを補うため、発話より後ろ(または前)の無音区間に
45
- # 現れた異常セグメントを読み飛ばすmlx_whisper組込みの判定(要word_timestamps)を有効にする。
46
- HALLUCINATION_SILENCE_THRESHOLD = 2.0
47
- # それでも残った場合の最終防衛線。YouTube字幕由来の典型的なハルシネーションで、
48
- # セグメント全体が(句読点・空白を除いて)これらと完全一致する場合のみ除外する。
49
- # 部分一致にしないのは、ユーザーが実際にこれらの語を含む文を話すケースを壊さないため。
50
- HALLUCINATION_PHRASES = {
51
- "ご視聴ありがとうございました",
52
- "ご視聴ありがとうございます",
53
- "最後までご視聴いただきありがとうございました",
54
- "最後までご視聴ありがとうございました",
55
- "チャンネル登録お願いします",
56
- "チャンネル登録をお願いします",
57
- "チャンネル登録よろしくお願いします",
58
- "次回もお楽しみに",
59
- "次回予告",
60
- "おやすみなさい",
61
- }
62
- _NORMALIZE_RE = re.compile(r"[\s、。,.,.!!??・…「」『』()()\[\]〜~♪]+")
63
-
64
- # MLXの推論は同時実行を想定していないため直列化する。エンドポイント自体は
65
- # 同期関数にしてスレッドプールで実行させ、推論中も/healthが応答できるようにする
66
- # (途中経過の表示で文字起こしリクエストが連続しても、起動状態のポーリングが
67
- # タイムアウトしてUIが「未起動」扱いになるのを防ぐ)。
68
- _transcribe_lock = threading.Lock()
69
-
70
- app = FastAPI()
71
-
72
-
73
- @app.get("/health")
74
- async def health() -> dict:
75
- return {"status": "ok", "engine": ENGINE, "model": MODEL}
76
-
77
-
78
- def decode_to_16k_mono(data: bytes) -> np.ndarray:
79
- container = av.open(io.BytesIO(data))
80
- stream = container.streams.audio[0]
81
- resampler = av.AudioResampler(format="s16", layout="mono", rate=16000)
82
-
83
- chunks = []
84
- for frame in container.decode(stream):
85
- for resampled in resampler.resample(frame):
86
- chunks.append(resampled.to_ndarray().reshape(-1))
87
- container.close()
88
-
89
- if not chunks:
90
- return np.zeros(0, dtype=np.float32)
91
- samples = np.concatenate(chunks).astype(np.float32) / 32768.0
92
- return samples
93
-
94
-
95
- def is_hallucination_phrase(text: str) -> bool:
96
- return _NORMALIZE_RE.sub("", text) in HALLUCINATION_PHRASES
97
-
98
-
99
- def transcribe_whisper(audio: np.ndarray, prompt: Optional[str]) -> str:
100
- # initial_promptは文字起こしのスタイル(表記ゆれ・句読点・固有名詞など)を
101
- # 誘導するヒントで、tsunagiのSettingsからユーザーが自由に設定できる。
102
- result = mlx_whisper.transcribe(
103
- audio,
104
- path_or_hf_repo=MODEL,
105
- language="ja",
106
- initial_prompt=prompt or None,
107
- # 前の窓の出力を次の窓のプロンプトに引き継ぐと、一度出たハルシネーションが
108
- # 後続の窓へ連鎖しやすくなるため無効化する。
109
- condition_on_previous_text=False,
110
- word_timestamps=True,
111
- hallucination_silence_threshold=HALLUCINATION_SILENCE_THRESHOLD,
112
- )
113
-
114
- # result["text"]はno_speech_prob等のフィルタを経ずに全セグメントを結合した
115
- # ものなので使わず、セグメント単位でno_speech_probを見て自前で組み立て直す。
116
- segments = result.get("segments")
117
- if segments:
118
- return "".join(
119
- seg["text"]
120
- for seg in segments
121
- if seg.get("no_speech_prob", 0.0) <= NO_SPEECH_THRESHOLD
122
- and not is_hallucination_phrase(seg["text"])
123
- )
124
- text = result.get("text", "")
125
- return "" if is_hallucination_phrase(text) else text
126
-
127
-
128
- def transcribe_qwen3_asr(audio: np.ndarray, prompt: Optional[str]) -> str:
129
- # contextはシステムプロンプトに差し込まれる語彙ヒント(スペース区切りの単語列を想定)。
130
- # SettingsのプロンプトをWhisperのinitial_promptと共通で使う。
131
- result = _qwen_session.transcribe(audio, context=prompt or "", language="Japanese")
132
- # Whisperのようなno_speech_probは無いため、定型文の完全一致除外のみ行う。
133
- return "" if is_hallucination_phrase(result.text) else result.text
134
-
135
-
136
- @app.post("/transcribe")
137
- def transcribe(file: UploadFile = File(...), prompt: Optional[str] = Form(None)) -> dict:
138
- data = file.file.read()
139
- audio = decode_to_16k_mono(data)
140
- if audio.size == 0:
141
- return {"text": "", "model": MODEL}
142
- with _transcribe_lock:
143
- if ENGINE == "whisper":
144
- text = transcribe_whisper(audio, prompt)
145
- else:
146
- text = transcribe_qwen3_asr(audio, prompt)
147
- return {"text": text.strip(), "model": MODEL}