diff --git a/internal/client/client.go b/internal/client/client.go index 96ff1c6..f37ae24 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -2,6 +2,7 @@ package client import ( "bufio" + "crypto/rand" "encoding/json" "fmt" "net" @@ -84,9 +85,19 @@ func (c *Conn) Send(env Envelope) error { return c.encoder.Encode(env) } +func uuidV4() string { + b := make([]byte, 16) + rand.Read(b) + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", + b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) +} + func (c *Conn) SendMessage(msgType string, payload interface{}) error { env := Envelope{ Type: msgType, + ID: uuidV4(), Timestamp: time.Now().UTC().Format(time.RFC3339), From: "cli", } @@ -108,6 +119,16 @@ func (c *Conn) SendInput(content string) error { }) } +func (c *Conn) SendVoice() error { + return c.SendMessage("input_forward", map[string]interface{}{ + "mode": "voice", + "content": "", + "context": map[string]interface{}{ + "session_id": fmt.Sprintf("sess_%d", time.Now().UnixNano()), + }, + }) +} + func (c *Conn) SendSystemCode(code string, unlockCode string) error { payload := map[string]interface{}{ "code": code, diff --git a/internal/tui/keybind.go b/internal/tui/keybind.go index 922d578..9ca6bf4 100644 --- a/internal/tui/keybind.go +++ b/internal/tui/keybind.go @@ -29,11 +29,8 @@ func (m Model) handleIdleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "ctrl+c", "ctrl+d": return m, tea.Quit - case "ctrl+alt+s": - m.conn.SendSystemCode("security", "") - return m, nil case "/": - m.conn.SendInput("[voice input]") + m.conn.SendVoice() return m, nil case "esc": return m, nil @@ -104,7 +101,7 @@ func (m Model) handleListeningKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, tea.ClearScreen case "/": - m.conn.SendInput("[voice input]") + m.conn.SendVoice() return m, nil default: diff --git a/internal/tui/model.go b/internal/tui/model.go index d2c0caa..d579da5 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -51,7 +51,8 @@ type Model struct { type outputMsg string type statusMsg string -type connStatusMsg connectionStatus + type connStatusMsg connectionStatus +type reconnectMsg struct{} func NewModel(conn *client.Conn) Model { return Model{ @@ -71,14 +72,28 @@ func (m Model) Init() tea.Cmd { func connectCmd(conn *client.Conn) tea.Cmd { return func() tea.Msg { - for i := 0; i < 30; i++ { + for i := 0; ; i++ { + if err := conn.Connect(); err == nil { + conn.RequestStatus() + return connStatusMsg(ConnConnected) + } + if i >= 30 { + return connStatusMsg(ConnFailed) + } + time.Sleep(time.Second) + } + } +} + +func reconnectCmd(conn *client.Conn) tea.Cmd { + return func() tea.Msg { + for { if err := conn.Connect(); err == nil { conn.RequestStatus() return connStatusMsg(ConnConnected) } time.Sleep(time.Second) } - return connStatusMsg(ConnFailed) } } @@ -106,8 +121,21 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.connStatus == ConnConnected { return m, listenCmd(m.conn) } + if m.connStatus == ConnFailed { + return m, reconnectCmd(m.conn) + } return m, nil + case reconnectMsg: + if m.connStatus == ConnFailed || m.connStatus == ConnDisconnected { + if err := m.conn.Connect(); err == nil { + m.conn.RequestStatus() + m.connStatus = ConnConnected + return m, listenCmd(m.conn) + } + } + return m, reconnectCmd(m.conn) + case outputMsg: m.output.Reset() m.output.WriteString(string(msg)) @@ -137,7 +165,7 @@ func listenCmd(conn *client.Conn) tea.Cmd { select { case env, ok := <-conn.Messages: if !ok { - return connStatusMsg(ConnDisconnected) + return reconnectMsg{} } switch env.Type { case "output_deliver": @@ -165,8 +193,11 @@ func (m Model) View() string { return "\n Loading..." } + if m.connStatus == ConnConnecting { + return "\n\n Connecting...\n" + } if m.connStatus == ConnFailed { - return m.renderError("Daemon unavailable. Check system.") + return "\n\n ⚠ Daemon unavailable. Check system.\n\n Retrying connection...\n" } switch m.state {