feat: bash commands
This commit is contained in:
@@ -49,6 +49,7 @@ type App struct {
|
||||
InitialSession *string
|
||||
compactCancel context.CancelFunc
|
||||
IsLeaderSequence bool
|
||||
IsBashMode bool
|
||||
}
|
||||
|
||||
func (a *App) Agent() *opencode.Agent {
|
||||
@@ -79,6 +80,9 @@ type AgentSelectedMsg struct {
|
||||
type SessionClearedMsg struct{}
|
||||
type CompactSessionMsg struct{}
|
||||
type SendPrompt = Prompt
|
||||
type SendBash = struct {
|
||||
Command string
|
||||
}
|
||||
type SetEditorContentMsg struct {
|
||||
Text string
|
||||
}
|
||||
@@ -296,23 +300,41 @@ func (a *App) CycleRecentModel() (*App, tea.Cmd) {
|
||||
}
|
||||
nextIndex := 0
|
||||
for i, recentModel := range recentModels {
|
||||
if a.Provider != nil && a.Model != nil && recentModel.ProviderID == a.Provider.ID && recentModel.ModelID == a.Model.ID {
|
||||
if a.Provider != nil && a.Model != nil && recentModel.ProviderID == a.Provider.ID &&
|
||||
recentModel.ModelID == a.Model.ID {
|
||||
nextIndex = (i + 1) % len(recentModels)
|
||||
break
|
||||
}
|
||||
}
|
||||
for range recentModels {
|
||||
currentRecentModel := recentModels[nextIndex%len(recentModels)]
|
||||
provider, model := findModelByProviderAndModelID(a.Providers, currentRecentModel.ProviderID, currentRecentModel.ModelID)
|
||||
provider, model := findModelByProviderAndModelID(
|
||||
a.Providers,
|
||||
currentRecentModel.ProviderID,
|
||||
currentRecentModel.ModelID,
|
||||
)
|
||||
if provider != nil && model != nil {
|
||||
a.Provider, a.Model = provider, model
|
||||
a.State.AgentModel[a.Agent().Name] = AgentModel{ProviderID: provider.ID, ModelID: model.ID}
|
||||
return a, tea.Sequence(a.SaveState(), toast.NewSuccessToast(fmt.Sprintf("Switched to %s (%s)", model.Name, provider.Name)))
|
||||
a.State.AgentModel[a.Agent().Name] = AgentModel{
|
||||
ProviderID: provider.ID,
|
||||
ModelID: model.ID,
|
||||
}
|
||||
return a, tea.Sequence(
|
||||
a.SaveState(),
|
||||
toast.NewSuccessToast(
|
||||
fmt.Sprintf("Switched to %s (%s)", model.Name, provider.Name),
|
||||
),
|
||||
)
|
||||
}
|
||||
recentModels = append(recentModels[:nextIndex%len(recentModels)], recentModels[nextIndex%len(recentModels)+1:]...)
|
||||
recentModels = append(
|
||||
recentModels[:nextIndex%len(recentModels)],
|
||||
recentModels[nextIndex%len(recentModels)+1:]...)
|
||||
if len(recentModels) < 2 {
|
||||
a.State.RecentlyUsedModels = recentModels
|
||||
return a, tea.Sequence(a.SaveState(), toast.NewInfoToast("Not enough valid recent models to cycle"))
|
||||
return a, tea.Sequence(
|
||||
a.SaveState(),
|
||||
toast.NewInfoToast("Not enough valid recent models to cycle"),
|
||||
)
|
||||
}
|
||||
}
|
||||
a.State.RecentlyUsedModels = recentModels
|
||||
@@ -464,10 +486,19 @@ func (a *App) InitializeProvider() tea.Cmd {
|
||||
|
||||
// Priority 3: Current agent's preferred model
|
||||
if selectedProvider == nil && a.Agent().Model.ModelID != "" {
|
||||
if provider, model := findModelByProviderAndModelID(providers, a.Agent().Model.ProviderID, a.Agent().Model.ModelID); provider != nil && model != nil {
|
||||
if provider, model := findModelByProviderAndModelID(providers, a.Agent().Model.ProviderID, a.Agent().Model.ModelID); provider != nil &&
|
||||
model != nil {
|
||||
selectedProvider = provider
|
||||
selectedModel = model
|
||||
slog.Debug("Selected model from current agent", "provider", provider.ID, "model", model.ID, "agent", a.Agent().Name)
|
||||
slog.Debug(
|
||||
"Selected model from current agent",
|
||||
"provider",
|
||||
provider.ID,
|
||||
"model",
|
||||
model.ID,
|
||||
"agent",
|
||||
a.Agent().Name,
|
||||
)
|
||||
} else {
|
||||
slog.Debug("Agent model not found", "provider", a.Agent().Model.ProviderID, "model", a.Agent().Model.ModelID, "agent", a.Agent().Name)
|
||||
}
|
||||
@@ -724,6 +755,38 @@ func (a *App) SendPrompt(ctx context.Context, prompt Prompt) (*App, tea.Cmd) {
|
||||
return a, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (a *App) SendBash(ctx context.Context, command string) (*App, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
if a.Session.ID == "" {
|
||||
session, err := a.CreateSession(ctx)
|
||||
if err != nil {
|
||||
return a, toast.NewErrorToast(err.Error())
|
||||
}
|
||||
a.Session = session
|
||||
cmds = append(cmds, util.CmdHandler(SessionCreatedMsg{Session: session}))
|
||||
}
|
||||
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
_, err := a.Client.Session.Bash(
|
||||
context.Background(),
|
||||
a.Session.ID,
|
||||
opencode.SessionBashParams{
|
||||
Agent: opencode.F(a.Agent().Name),
|
||||
Command: opencode.F(command),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("Failed to submit bash command", "error", err)
|
||||
return toast.NewErrorToast("Failed to submit bash command")()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// The actual response will come through SSE
|
||||
// For now, just return success
|
||||
return a, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (a *App) Cancel(ctx context.Context, sessionID string) error {
|
||||
// Cancel any running compact operation
|
||||
if a.compactCancel != nil {
|
||||
|
||||
@@ -39,6 +39,7 @@ type EditorComponent interface {
|
||||
Focus() (tea.Model, tea.Cmd)
|
||||
Blur()
|
||||
Submit() (tea.Model, tea.Cmd)
|
||||
SubmitBash() (tea.Model, tea.Cmd)
|
||||
Clear() (tea.Model, tea.Cmd)
|
||||
Paste() (tea.Model, tea.Cmd)
|
||||
Newline() (tea.Model, tea.Cmd)
|
||||
@@ -342,6 +343,14 @@ func (m *editorComponent) Content() string {
|
||||
Padding(0, 0, 0, 1).
|
||||
Bold(true)
|
||||
prompt := promptStyle.Render(">")
|
||||
borderForeground := t.Border()
|
||||
if m.app.IsLeaderSequence {
|
||||
borderForeground = t.Accent()
|
||||
}
|
||||
if m.app.IsBashMode {
|
||||
borderForeground = t.Secondary()
|
||||
prompt = promptStyle.Render("!")
|
||||
}
|
||||
|
||||
m.textarea.SetWidth(width - 6)
|
||||
textarea := lipgloss.JoinHorizontal(
|
||||
@@ -349,10 +358,6 @@ func (m *editorComponent) Content() string {
|
||||
prompt,
|
||||
m.textarea.View(),
|
||||
)
|
||||
borderForeground := t.Border()
|
||||
if m.app.IsLeaderSequence {
|
||||
borderForeground = t.Accent()
|
||||
}
|
||||
textarea = styles.NewStyle().
|
||||
Background(t.BackgroundElement()).
|
||||
Width(width).
|
||||
@@ -489,6 +494,16 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *editorComponent) SubmitBash() (tea.Model, tea.Cmd) {
|
||||
command := m.textarea.Value()
|
||||
var cmds []tea.Cmd
|
||||
updated, cmd := m.Clear()
|
||||
m = updated.(*editorComponent)
|
||||
cmds = append(cmds, cmd)
|
||||
cmds = append(cmds, util.CmdHandler(app.SendBash{Command: command}))
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *editorComponent) Clear() (tea.Model, tea.Cmd) {
|
||||
m.textarea.Reset()
|
||||
m.historyIndex = -1
|
||||
|
||||
@@ -151,6 +151,23 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
if a.app.IsBashMode {
|
||||
if keyString == "backspace" && a.editor.Length() == 0 {
|
||||
a.app.IsBashMode = false
|
||||
return a, nil
|
||||
}
|
||||
|
||||
if keyString == "enter" || keyString == "esc" || keyString == "ctrl+c" {
|
||||
a.app.IsBashMode = false
|
||||
if keyString == "enter" {
|
||||
updated, cmd := a.editor.SubmitBash()
|
||||
a.editor = updated.(chat.EditorComponent)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
return a, tea.Batch(cmds...)
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Handle active modal
|
||||
if a.modal != nil {
|
||||
switch keyString {
|
||||
@@ -189,7 +206,8 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// 3. Handle completions trigger
|
||||
if keyString == "/" &&
|
||||
!a.showCompletionDialog &&
|
||||
a.editor.Value() == "" {
|
||||
a.editor.Value() == "" &&
|
||||
!a.app.IsBashMode {
|
||||
a.showCompletionDialog = true
|
||||
|
||||
updated, cmd := a.editor.Update(msg)
|
||||
@@ -207,7 +225,8 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
// Handle file completions trigger
|
||||
if keyString == "@" &&
|
||||
!a.showCompletionDialog {
|
||||
!a.showCompletionDialog &&
|
||||
!a.app.IsBashMode {
|
||||
a.showCompletionDialog = true
|
||||
|
||||
updated, cmd := a.editor.Update(msg)
|
||||
@@ -223,6 +242,11 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return a, tea.Sequence(cmds...)
|
||||
}
|
||||
|
||||
if keyString == "!" && a.editor.Value() == "" {
|
||||
a.app.IsBashMode = true
|
||||
return a, nil
|
||||
}
|
||||
|
||||
if a.showCompletionDialog {
|
||||
switch keyString {
|
||||
case "tab", "enter", "esc", "ctrl+c", "up", "down", "ctrl+p", "ctrl+n":
|
||||
@@ -378,6 +402,9 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
a.showCompletionDialog = false
|
||||
a.app, cmd = a.app.SendPrompt(context.Background(), msg)
|
||||
cmds = append(cmds, cmd)
|
||||
case app.SendBash:
|
||||
a.app, cmd = a.app.SendBash(context.Background(), msg.Command)
|
||||
cmds = append(cmds, cmd)
|
||||
case app.SetEditorContentMsg:
|
||||
// Set the editor content without sending
|
||||
a.editor.SetValueWithAttachments(msg.Text)
|
||||
|
||||
Reference in New Issue
Block a user