File size: 4,756 Bytes
2318eae
 
 
 
0d6899b
516e9a4
2318eae
 
 
 
 
 
 
 
 
 
 
 
 
1d28d11
2318eae
1d28d11
2318eae
454a10d
 
7c3f2af
 
2318eae
 
7c3f2af
1d28d11
 
454a10d
1d28d11
 
 
454a10d
 
 
 
516e9a4
 
 
 
1d28d11
 
 
 
 
7c3f2af
 
 
0d6899b
1d28d11
 
 
7c3f2af
1d28d11
454a10d
 
 
 
 
 
 
 
 
 
 
 
 
 
7c3f2af
1d28d11
 
 
 
 
 
 
 
 
 
7c3f2af
 
1d28d11
 
 
7c3f2af
1d28d11
 
 
 
7c3f2af
 
1d28d11
7c3f2af
 
1d28d11
454a10d
 
516e9a4
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from fastapi import FastAPI, WebSocket
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
from app.asr_worker import create_recognizer, stream_audio, finalize_stream
import json
from starlette.websockets import WebSocketDisconnect

app = FastAPI()

app.mount("/static", StaticFiles(directory="app/static"), name="static")

@app.get("/")
async def root():
    with open("app/static/index.html") as f:
        return HTMLResponse(f.read())


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    print("[DEBUG main] ▶ Attempting to accept WebSocket…")
    await websocket.accept()
    print("[DEBUG main] ▶ WebSocket.accept() returned → client is connected!")

    recognizer = None
    stream = None
    orig_sr = 48000  # default fallback

    try:
        while True:
            data = await websocket.receive()
            kind = data.get("type")

            # Handle control frames
            if kind not in ("websocket.receive", "websocket.receive_bytes"):
                print(f"[DEBUG main] Received control/frame: {data}")
                if kind == "websocket.disconnect":
                    # On client disconnect, flush final transcript if possible
                    if stream and recognizer:
                        print(f"[INFO main] Client disconnected (code={data.get('code')}). Flushing final transcript...")
                        final = finalize_stream(stream, recognizer)
                        try:
                            await websocket.send_json({"final": final})
                        except (WebSocketDisconnect, RuntimeError):
                            pass
                    break
                continue

            # Handle text (config) frame
            if kind == "websocket.receive" and "text" in data:
                raw = data["text"]
                try:
                    config_msg = json.loads(raw)
                except Exception as e:
                    print(f"[ERROR main] JSON parse failed: {e}")
                    continue
                if config_msg.get("type") == "config":
                    orig_sr = int(config_msg["sampleRate"])
                    print(f"[INFO main] Set original sample rate to {orig_sr}")

                    # New: dynamic model & precision
                    model_id = config_msg.get("model")
                    precision = config_msg.get("precision")
                    print(f"[INFO main] Selected model: {model_id}, precision: {precision}")

                    recognizer = create_recognizer(model_id, precision)
                    stream = recognizer.create_stream()
                    print("[INFO main] WebSocket connection accepted; created a streaming context.")
                continue

            # Don't process audio until after config
            if recognizer is None or stream is None:
                continue

            # If it’s a text payload but with bytes (some FastAPI versions put audio under 'text'!)  
            if kind == "websocket.receive" and "bytes" in data:
                raw_audio = data["bytes"]
                print(f"[INFO main] (text+bytes) Received audio chunk: {len(raw_audio)} bytes")
                result, rms = stream_audio(raw_audio, stream, recognizer, orig_sr)
                vol_to_send = min(rms * 20.0, 1.0)
                print(f"[INFO main] Sending → partial='{result[:30]}…', volume={vol_to_send:.4f}")
                await websocket.send_json({"partial": result, "volume": vol_to_send})
                continue

            elif isinstance(data, dict) and data.get("type") == "websocket.receive_bytes":
                raw_audio = data["bytes"]
                print(f"[INFO main] Received audio chunk: {len(raw_audio)} bytes")

                # This will also print its own debug info (see asr_worker.py)
                result, rms = stream_audio(raw_audio, stream, recognizer, orig_sr)

                vol_to_send = min(rms * 20.0, 1.0)
                print(f"[INFO main] Sending → partial='{result[:30]}…', volume={vol_to_send:.4f}")

                await websocket.send_json({
                    "partial": result,
                    "volume": vol_to_send
                })
    except Exception as e:
        print(f"[ERROR main] Unexpected exception: {e}")
        if stream and recognizer:
            final = finalize_stream(stream, recognizer)
            try:
                await websocket.send_json({"final": final})
            except (WebSocketDisconnect, RuntimeError):
                pass
        # Ensure connection is closed
        try:
            await websocket.close()
        except:
            pass
        print("[INFO main] WebSocket closed, cleanup complete.")