Files
onyx/cli/internal/tui/commands.go
T

204 lines
4.8 KiB
Go

package tui
import (
"context"
"fmt"
"strconv"
"strings"
tea "charm.land/bubbletea/v2"
"github.com/onyx-dot-app/onyx/cli/internal/api"
"github.com/onyx-dot-app/onyx/cli/internal/browser"
"github.com/onyx-dot-app/onyx/cli/internal/config"
"github.com/onyx-dot-app/onyx/cli/internal/models"
)
// handleSlashCommand dispatches slash commands and returns updated model + cmd.
func handleSlashCommand(m Model, text string) (Model, tea.Cmd) {
parts := strings.SplitN(text, " ", 2)
command := strings.ToLower(parts[0])
arg := ""
if len(parts) > 1 {
arg = parts[1]
}
switch command {
case "/help":
m.viewport.addInfo(helpText)
return m, nil
case "/agent":
if arg != "" {
return cmdSelectAgent(m, arg)
}
return cmdShowAgents(m)
case "/attach":
return cmdAttach(m, arg)
case "/sessions", "/resume":
if strings.TrimSpace(arg) != "" {
return cmdResume(m, arg)
}
return cmdSessions(m)
case "/configure":
return enterConfigureMode(m)
case "/clear", "/new":
return cmdNew(m)
case "/connectors":
url := strings.TrimRight(m.config.ServerURL, "/") + "/admin/indexing/status"
if browser.OpenBrowser(url) {
m.viewport.addInfo("Opened " + url + " in browser")
} else {
m.viewport.addWarning("Failed to open browser. Visit: " + url)
}
return m, nil
case "/settings":
url := strings.TrimRight(m.config.ServerURL, "/") + "/app/settings/general"
if browser.OpenBrowser(url) {
m.viewport.addInfo("Opened " + url + " in browser")
} else {
m.viewport.addWarning("Failed to open browser. Visit: " + url)
}
return m, nil
case "/experiments":
m.viewport.addInfo(config.ExperimentsText(m.config.Features))
return m, nil
case "/quit":
return m, tea.Quit
default:
m.viewport.addWarning(fmt.Sprintf("Unknown command: %s. Type /help for available commands.", command))
return m, nil
}
}
func cmdNew(m Model) (Model, tea.Cmd) {
if m.isStreaming {
m, _ = m.cancelStream()
}
m.chatSessionID = nil
parentID := -1
m.parentMessageID = &parentID
m.needsRename = false
m.citations = nil
m.viewport.clearAll()
// Re-add splash as a scrollable entry
viewportHeight := m.viewportHeight()
if viewportHeight < 1 {
viewportHeight = m.height
}
m.viewport.addSplash(viewportHeight)
m.status.setSession("")
return m, nil
}
func cmdShowAgents(m Model) (Model, tea.Cmd) {
m.viewport.addInfo("Loading agents...")
client := m.client
return m, func() tea.Msg {
agents, err := client.ListAgents(context.Background())
return AgentsLoadedMsg{Agents: agents, Err: err}
}
}
func cmdSelectAgent(m Model, idStr string) (Model, tea.Cmd) {
pid, err := strconv.Atoi(strings.TrimSpace(idStr))
if err != nil {
m.viewport.addWarning("Invalid agent ID. Use a number.")
return m, nil
}
var target *models.AgentSummary
for i := range m.agents {
if m.agents[i].ID == pid {
target = &m.agents[i]
break
}
}
if target == nil {
m.viewport.addWarning(fmt.Sprintf("Agent %d not found. Use /agent to see available agents.", pid))
return m, nil
}
m.agentID = target.ID
m.agentName = target.Name
m.status.setAgent(target.Name)
m.viewport.addInfo("Switched to agent: " + target.Name)
// Save preference
m.config.DefaultAgentID = target.ID
_ = config.Save(m.config)
return m, nil
}
func cmdAttach(m Model, pathStr string) (Model, tea.Cmd) {
if pathStr == "" {
m.viewport.addWarning("Usage: /attach <file_path>")
return m, nil
}
m.viewport.addInfo("Uploading " + pathStr + "...")
client := m.client
return m, func() tea.Msg {
fd, err := client.UploadFile(context.Background(), pathStr)
if err != nil {
return FileUploadedMsg{Err: err, FileName: pathStr}
}
return FileUploadedMsg{Descriptor: fd, FileName: pathStr}
}
}
func cmdSessions(m Model) (Model, tea.Cmd) {
m.viewport.addInfo("Loading sessions...")
client := m.client
return m, func() tea.Msg {
sessions, err := client.ListChatSessions(context.Background())
return SessionsLoadedMsg{Sessions: sessions, Err: err}
}
}
func cmdResume(m Model, sessionIDStr string) (Model, tea.Cmd) {
client := m.client
return m, func() tea.Msg {
targetID := sessionIDStr
// Short prefix — scan the list for a match
if len(sessionIDStr) < 36 {
sessions, err := client.ListChatSessions(context.Background())
if err != nil {
return SessionResumedMsg{Err: err}
}
for _, s := range sessions {
if strings.HasPrefix(s.ID, sessionIDStr) {
targetID = s.ID
break
}
}
}
detail, err := client.GetChatSession(context.Background(), targetID)
if err != nil {
return SessionResumedMsg{Err: fmt.Errorf("session not found: %s", sessionIDStr)}
}
return SessionResumedMsg{Detail: detail}
}
}
// loadAgentsCmd returns a tea.Cmd that loads agents from the API.
func loadAgentsCmd(client api.ClientAPI) tea.Cmd {
return func() tea.Msg {
agents, err := client.ListAgents(context.Background())
return InitDoneMsg{Agents: agents, Err: err}
}
}