diff --git a/cmd/tui/main.go b/cmd/tui/main.go new file mode 100644 index 0000000..3ecc2f4 --- /dev/null +++ b/cmd/tui/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + + tea "github.com/charmbracelet/bubbletea" +) + +// debugLog writes to a debug file +var debugFile *os.File + +func debugLog(format string, args ...any) { + if debugFile != nil { + fmt.Fprintf(debugFile, format+"\n", args...) + debugFile.Sync() + } +} + +func main() { + serverAddr := flag.String("server", "127.0.0.1:9987", "TeamSpeak 3 Server Address") + nickname := flag.String("nickname", "TUI-User", "Your nickname") + debug := flag.Bool("debug", false, "Enable debug logging to tui-debug.log") + flag.Parse() + + // Disable log output completely to prevent TUI corruption + log.SetOutput(io.Discard) + + // Enable debug file logging if requested + if *debug { + var err error + debugFile, err = os.Create("tui-debug.log") + if err == nil { + defer debugFile.Close() + debugLog("TUI Debug started") + } + } + + // Create the TUI model + m := NewModel(*serverAddr, *nickname) + + // Create Bubble Tea program + p := tea.NewProgram(m, tea.WithAltScreen()) + + // Run + if _, err := p.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/cmd/tui/model.go b/cmd/tui/model.go new file mode 100644 index 0000000..c5266e7 --- /dev/null +++ b/cmd/tui/model.go @@ -0,0 +1,476 @@ +package main + +import ( + "fmt" + "sort" + "time" + + "go-ts/pkg/ts3client" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +// Focus indicates which panel has focus +type Focus int + +const ( + FocusChannels Focus = iota + FocusChat + FocusInput +) + +// ChatMessage represents a message in the chat +type ChatMessage struct { + Time time.Time + Sender string + Content string +} + +// ChannelNode represents a channel in the tree +type ChannelNode struct { + ID uint64 + Name string + Users []UserNode + Expanded bool + Selected bool +} + +// UserNode represents a user in a channel +type UserNode struct { + ID uint16 + Nickname string + Talking bool + Muted bool + IsMe bool +} + +// Model is the Bubble Tea model for our TUI +type Model struct { + // Connection + serverAddr string + nickname string + client *ts3client.Client + connected bool + connecting bool + serverName string + selfID uint16 + ping int + + // UI State + focus Focus + width, height int + channels []ChannelNode + selectedIdx int + chatMessages []ChatMessage + logMessages []string // Debug logs shown in chat panel + inputText string + inputActive bool + + // Error handling + lastError string +} + +// addLog adds a message to the log panel +func (m *Model) addLog(format string, args ...any) { + msg := fmt.Sprintf(format, args...) + m.logMessages = append(m.logMessages, msg) + // Keep last 50 messages + if len(m.logMessages) > 50 { + m.logMessages = m.logMessages[1:] + } +} + +// NewModel creates a new TUI model +func NewModel(serverAddr, nickname string) *Model { + return &Model{ + serverAddr: serverAddr, + nickname: nickname, + focus: FocusChannels, + channels: []ChannelNode{}, + chatMessages: []ChatMessage{}, + logMessages: []string{"Starting..."}, + } +} + +// Init is called when the program starts +func (m *Model) Init() tea.Cmd { + return tea.Batch( + m.connectToServer(), + tea.SetWindowTitle("TeamSpeak TUI"), + ) +} + +// TS3Event wraps events from the TS3 client +type TS3Event struct { + Type string + Data map[string]any +} + +// connectToServer initiates connection to TeamSpeak +func (m *Model) connectToServer() tea.Cmd { + return func() tea.Msg { + m.connecting = true + + client := ts3client.New(m.serverAddr, ts3client.Config{ + Nickname: m.nickname, + }) + + return ts3ClientMsg{client: client} + } +} + +type ts3ClientMsg struct { + client *ts3client.Client +} + +type connectedMsg struct { + clientID uint16 + serverName string +} + +type channelListMsg struct { + channels []*ts3client.Channel +} + +type clientEnterMsg struct { + clientID uint16 + nickname string + channelID uint64 +} + +type clientLeftMsg struct { + clientID uint16 +} + +type chatMsg struct { + senderID uint16 + senderName string + message string +} + +type errorMsg struct { + err string +} + +type tickMsg time.Time + +// Update handles messages and user input +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil + + case tea.KeyMsg: + return m.handleKeyPress(msg) + + case ts3ClientMsg: + m.client = msg.client + m.connecting = true + + // Connect asynchronously + m.client.ConnectAsync() + + // Set up a ticker to poll for state changes + return m, tea.Tick(500*time.Millisecond, func(t time.Time) tea.Msg { + return tickMsg(t) + }) + + case tickMsg: + // Poll client state + if m.client != nil { + // Check if connected + if !m.connected { + info := m.client.GetSelfInfo() + serverInfo := m.client.GetServerInfo() + m.addLog("Tick: selfInfo=%v, serverInfo=%v", info != nil, serverInfo != nil) + if info != nil && serverInfo != nil { + m.connected = true + m.selfID = info.ClientID + m.serverName = serverInfo.Name + m.addLog("Connected! ClientID=%d, Server=%s", m.selfID, m.serverName) + + // Get channels + channels := m.client.GetChannels() + m.addLog("Got %d channels", len(channels)) + m.updateChannelList(channels) + } + } else { + // Update channel list periodically + channels := m.client.GetChannels() + if len(channels) != len(m.channels) { + m.addLog("Channel update: got %d channels (had %d)", len(channels), len(m.channels)) + } + m.updateChannelList(channels) + } + } + + // Continue ticking + return m, tea.Tick(500*time.Millisecond, func(t time.Time) tea.Msg { + return tickMsg(t) + }) + + case connectedMsg: + m.connected = true + m.connecting = false + m.selfID = msg.clientID + m.serverName = msg.serverName + return m, nil + + case channelListMsg: + m.updateChannelList(msg.channels) + return m, nil + + case errorMsg: + m.lastError = msg.err + return m, nil + } + + return m, nil +} + +func (m *Model) updateChannelList(channels []*ts3client.Channel) { + // Sort channels by ID for stable ordering + sortedChannels := make([]*ts3client.Channel, len(channels)) + copy(sortedChannels, channels) + sort.Slice(sortedChannels, func(i, j int) bool { + return sortedChannels[i].ID < sortedChannels[j].ID + }) + + m.channels = make([]ChannelNode, 0, len(sortedChannels)) + for _, ch := range sortedChannels { + node := ChannelNode{ + ID: ch.ID, + Name: ch.Name, + Users: []UserNode{}, + Expanded: true, + } + + // Get users in this channel + for _, cl := range m.client.GetClients() { + if cl.ChannelID == ch.ID { + node.Users = append(node.Users, UserNode{ + ID: cl.ID, + Nickname: cl.Nickname, + IsMe: cl.ID == m.selfID, + }) + } + } + + m.channels = append(m.channels, node) + } +} + +func (m *Model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) { + // Global keys + switch msg.String() { + case "ctrl+c", "q": + if m.client != nil { + m.client.Disconnect() + } + return m, tea.Quit + + case "tab": + // Cycle focus + m.focus = (m.focus + 1) % 3 + return m, nil + + case "m": + // Toggle mute (would need to implement) + return m, nil + } + + // Focus-specific keys + switch m.focus { + case FocusChannels: + return m.handleChannelKeys(msg) + case FocusInput: + return m.handleInputKeys(msg) + } + + return m, nil +} + +func (m *Model) handleChannelKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { + switch msg.String() { + case "up", "k": + if m.selectedIdx > 0 { + m.selectedIdx-- + } + case "down", "j": + if m.selectedIdx < len(m.channels)-1 { + m.selectedIdx++ + } + case "enter": + // Join selected channel + if m.selectedIdx < len(m.channels) && m.client != nil { + ch := m.channels[m.selectedIdx] + m.client.JoinChannel(ch.ID) + } + } + return m, nil +} + +func (m *Model) handleInputKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { + switch msg.String() { + case "enter": + if m.inputText != "" && m.client != nil { + m.client.SendChannelMessage(m.inputText) + m.inputText = "" + } + case "esc": + m.focus = FocusChannels + m.inputText = "" + case "backspace": + if len(m.inputText) > 0 { + m.inputText = m.inputText[:len(m.inputText)-1] + } + default: + // Add character to input + if len(msg.String()) == 1 { + m.inputText += msg.String() + } + } + return m, nil +} + +// View renders the UI +func (m *Model) View() string { + if m.width == 0 { + return "Loading..." + } + + // Styles + headerStyle := lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("229")). + Background(lipgloss.Color("57")). + Padding(0, 1). + Width(m.width) + + channelPanelStyle := lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color("63")). + Padding(0, 1). + Width(m.width/3 - 2). + Height(m.height - 6) + + chatPanelStyle := lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color("63")). + Padding(0, 1). + Width(m.width*2/3 - 2). + Height(m.height - 6) + + inputStyle := lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("63")). + Padding(0, 1). + Width(m.width - 4) + + // Header + status := "Connecting..." + if m.connected { + status = fmt.Sprintf("Server: %s │ You: %s (ID: %d)", m.serverName, m.nickname, m.selfID) + } + header := headerStyle.Render(status) + + // Channel panel + channelContent := m.renderChannels() + if m.focus == FocusChannels { + channelPanelStyle = channelPanelStyle.BorderForeground(lipgloss.Color("212")) + } + channelPanel := channelPanelStyle.Render(channelContent) + + // Chat panel + chatContent := m.renderChat() + if m.focus == FocusChat { + chatPanelStyle = chatPanelStyle.BorderForeground(lipgloss.Color("212")) + } + chatPanel := chatPanelStyle.Render(chatContent) + + // Input + inputContent := "> " + m.inputText + if m.focus == FocusInput { + inputStyle = inputStyle.BorderForeground(lipgloss.Color("212")) + inputContent += "█" + } + input := inputStyle.Render(inputContent) + + // Footer help + help := lipgloss.NewStyle().Faint(true).Render("↑↓ navigate │ Enter join │ Tab switch │ q quit") + + // Combine panels + panels := lipgloss.JoinHorizontal(lipgloss.Top, channelPanel, chatPanel) + + return lipgloss.JoinVertical(lipgloss.Left, + header, + panels, + input, + help, + ) +} + +func (m *Model) renderChannels() string { + if len(m.channels) == 0 { + return "No channels..." + } + + var lines []string + lines = append(lines, lipgloss.NewStyle().Bold(true).Render("CHANNELS")) + lines = append(lines, "") + + for i, ch := range m.channels { + prefix := " " + if i == m.selectedIdx { + prefix = "► " + } + + style := lipgloss.NewStyle() + if i == m.selectedIdx { + style = style.Bold(true).Foreground(lipgloss.Color("212")) + } + + lines = append(lines, style.Render(prefix+ch.Name)) + + // Show users in channel + for _, user := range ch.Users { + userPrefix := " └─ " + userStyle := lipgloss.NewStyle().Faint(true) + if user.IsMe { + userStyle = userStyle.Foreground(lipgloss.Color("82")) + userPrefix = " └─ ► " + } + lines = append(lines, userStyle.Render(userPrefix+user.Nickname)) + } + } + + return lipgloss.JoinVertical(lipgloss.Left, lines...) +} + +func (m *Model) renderChat() string { + var lines []string + lines = append(lines, lipgloss.NewStyle().Bold(true).Render("LOG")) + lines = append(lines, "") + + if len(m.logMessages) == 0 { + lines = append(lines, lipgloss.NewStyle().Faint(true).Render("No logs yet...")) + } else { + // Limit to last N messages that fit in the panel + maxLines := m.height - 10 + if maxLines < 5 { + maxLines = 5 + } + start := 0 + if len(m.logMessages) > maxLines { + start = len(m.logMessages) - maxLines + } + for _, msg := range m.logMessages[start:] { + lines = append(lines, lipgloss.NewStyle().Faint(true).Render(msg)) + } + } + + return lipgloss.JoinVertical(lipgloss.Left, lines...) +} diff --git a/go.mod b/go.mod index 40e8583..9ebdef0 100644 --- a/go.mod +++ b/go.mod @@ -11,4 +11,26 @@ require ( require gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 -require github.com/gorilla/websocket v1.5.3 // indirect +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbles v0.21.0 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.3.8 // indirect +) diff --git a/go.sum b/go.sum index b45c987..99cc97a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,51 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= +github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/dgryski/go-quicklz v0.0.0-20151014073603-d7042a82d57e h1:MhBotBstN1h/GeA7lx7xstbFB8avummjt+nzOi2cY7Y= github.com/dgryski/go-quicklz v0.0.0-20151014073603-d7042a82d57e/go.mod h1:XLmYwGWgVzMPLlMmcNcWt3b5ixRabPLstWnPVEDRhzc= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 h1:xeVptzkP8BuJhoIjNizd2bRHfq9KB9HfOLZu90T04XM= gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g= diff --git a/pkg/ts3client/client.go b/pkg/ts3client/client.go index 14291fa..ff560e3 100644 --- a/pkg/ts3client/client.go +++ b/pkg/ts3client/client.go @@ -237,6 +237,7 @@ func (c *Client) handleInternalEvent(eventType string, data map[string]any) { serverName = v } c.selfInfo = &SelfInfo{ClientID: clientID, Nickname: c.config.Nickname} + c.serverInfo = &ServerInfo{Name: serverName} c.emit(EventConnected, &ConnectedEvent{ ClientID: clientID, ServerName: serverName, diff --git a/teamspeak-tui.exe b/teamspeak-tui.exe new file mode 100644 index 0000000..656594e Binary files /dev/null and b/teamspeak-tui.exe differ diff --git a/tui.ps1 b/tui.ps1 new file mode 100644 index 0000000..e1f1775 --- /dev/null +++ b/tui.ps1 @@ -0,0 +1,12 @@ +# Fix UTF-8 encoding for PowerShell +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +$OutputEncoding = [System.Text.Encoding]::UTF8 +chcp 65001 > $null + +$env:PATH = "D:\esto_al_path\msys64\mingw64\bin;$env:PATH" +$env:PKG_CONFIG_PATH = "D:\esto_al_path\msys64\mingw64\lib\pkgconfig" + +$env:XAI_API_KEY = "xai-TyecBoTLlFNL0Qxwnb0eRainG8hKTpJGtnCziMhm1tTyB1FrLpZm0gHNYA9qqqX21JsXStN1f9DseLdJ" +# go run ./cmd/voicebot --server localhost:9987 --nickname Adam --voice Rex --greeting " " --room "test" + +go run ./cmd/tui --server localhost:9987 --nickname Adam \ No newline at end of file