diff --git a/internal/ws/protocol.go b/internal/ws/protocol.go index dbbef09..12674dc 100644 --- a/internal/ws/protocol.go +++ b/internal/ws/protocol.go @@ -8,15 +8,22 @@ import "encoding/json" type MsgType string const ( - MsgStart MsgType = "start" // Begin recording session - MsgStop MsgType = "stop" // End recording session - MsgPaste MsgType = "paste" // Re-paste a history item + MsgHello MsgType = "hello" + MsgStart MsgType = "start" + MsgStop MsgType = "stop" + MsgPaste MsgType = "paste" + MsgPing MsgType = "ping" + MsgPong MsgType = "pong" ) // ClientMsg is a JSON control message from the phone. type ClientMsg struct { - Type MsgType `json:"type"` - Text string `json:"text,omitempty"` // Only for "paste" + Type MsgType `json:"type"` + SessionID string `json:"sessionId,omitempty"` + Seq int64 `json:"seq,omitempty"` + Text string `json:"text,omitempty"` // Only for "paste" + Version int `json:"version,omitempty"` + TS int64 `json:"ts,omitempty"` // Future extension: dynamic hotwords (Phase 2) // Hotwords []string `json:"hotwords,omitempty"` } @@ -24,17 +31,33 @@ type ClientMsg struct { // ── Server → Client messages ── const ( - MsgPartial MsgType = "partial" // Interim ASR result - MsgFinal MsgType = "final" // Final ASR result - MsgPasted MsgType = "pasted" // Paste confirmed - MsgError MsgType = "error" // Error notification + MsgReady MsgType = "ready" + MsgState MsgType = "state" + MsgStartAck MsgType = "start_ack" + MsgStopAck MsgType = "stop_ack" + MsgPartial MsgType = "partial" + MsgFinal MsgType = "final" + MsgPasted MsgType = "pasted" + MsgError MsgType = "error" +) + +const ( + StateIdle = "idle" + StateRecording = "recording" + StateStopping = "stopping" ) // ServerMsg is a JSON message sent to the phone. type ServerMsg struct { - Type MsgType `json:"type"` - Text string `json:"text,omitempty"` - Message string `json:"message,omitempty"` // For errors + Type MsgType `json:"type"` + State string `json:"state,omitempty"` + SessionID string `json:"sessionId,omitempty"` + Seq int64 `json:"seq,omitempty"` + Text string `json:"text,omitempty"` + Message string `json:"message,omitempty"` // For errors + Code string `json:"code,omitempty"` + Retryable bool `json:"retryable,omitempty"` + TS int64 `json:"ts,omitempty"` } func (m ServerMsg) Bytes() []byte { diff --git a/web/src/protocol.ts b/web/src/protocol.ts new file mode 100644 index 0000000..5dd90b0 --- /dev/null +++ b/web/src/protocol.ts @@ -0,0 +1,35 @@ +export type ClientMsgType = "hello" | "start" | "stop" | "paste" | "ping"; + +export type ServerMsgType = + | "ready" + | "state" + | "start_ack" + | "stop_ack" + | "partial" + | "final" + | "pasted" + | "error" + | "pong"; + +export type SessionState = "idle" | "recording" | "stopping"; + +export interface ClientMsg { + type: ClientMsgType; + sessionId?: string; + seq?: number; + text?: string; + version?: number; + ts?: number; +} + +export interface ServerMsg { + type: ServerMsgType; + state?: SessionState; + sessionId?: string; + seq?: number; + text?: string; + message?: string; + code?: string; + retryable?: boolean; + ts?: number; +} diff --git a/web/src/stores/app-store.ts b/web/src/stores/app-store.ts index b381520..d782f1a 100644 --- a/web/src/stores/app-store.ts +++ b/web/src/stores/app-store.ts @@ -13,9 +13,13 @@ type ConnectionStatus = "connected" | "disconnected" | "connecting"; interface AppState { // Connection connectionStatus: ConnectionStatus; + weakNetwork: boolean; // Recording recording: boolean; pendingStart: boolean; + stopping: boolean; + micReady: boolean; + activeSessionId: string | null; // Preview previewText: string; previewActive: boolean; @@ -24,8 +28,12 @@ interface AppState { // Actions setConnectionStatus: (status: ConnectionStatus) => void; + setWeakNetwork: (weak: boolean) => void; setRecording: (recording: boolean) => void; setPendingStart: (pending: boolean) => void; + setStopping: (stopping: boolean) => void; + setMicReady: (ready: boolean) => void; + setActiveSessionId: (sessionId: string | null) => void; setPreview: (text: string, isFinal: boolean) => void; clearPreview: () => void; addHistory: (text: string) => void; @@ -36,15 +44,23 @@ export const useAppStore = create()( persist( (set, get) => ({ connectionStatus: "connecting", + weakNetwork: false, recording: false, pendingStart: false, + stopping: false, + micReady: false, + activeSessionId: null, previewText: "", previewActive: false, history: [], setConnectionStatus: (connectionStatus) => set({ connectionStatus }), + setWeakNetwork: (weakNetwork) => set({ weakNetwork }), setRecording: (recording) => set({ recording }), setPendingStart: (pendingStart) => set({ pendingStart }), + setStopping: (stopping) => set({ stopping }), + setMicReady: (micReady) => set({ micReady }), + setActiveSessionId: (activeSessionId) => set({ activeSessionId }), setPreview: (text, isFinal) => set({