Browse Source

feat: share and init commands

adamdottv 8 months ago
parent
commit
d6d45bdc63

+ 17 - 1
packages/tui/internal/app/app.go

@@ -170,16 +170,17 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
 	cmds = append(cmds, util.CmdHandler(state.SessionSelectedMsg(session)))
 	cmds = append(cmds, util.CmdHandler(state.SessionSelectedMsg(session)))
 
 
 	go func() {
 	go func() {
-		// TODO: Handle no provider or model setup, yet
 		response, err := a.Client.PostSessionInitialize(ctx, client.PostSessionInitializeJSONRequestBody{
 		response, err := a.Client.PostSessionInitialize(ctx, client.PostSessionInitializeJSONRequestBody{
 			SessionID:  a.Session.Id,
 			SessionID:  a.Session.Id,
 			ProviderID: a.Provider.Id,
 			ProviderID: a.Provider.Id,
 			ModelID:    a.Model.Id,
 			ModelID:    a.Model.Id,
 		})
 		})
 		if err != nil {
 		if err != nil {
+			slog.Error("Failed to initialize project", "error", err)
 			// status.Error(err.Error())
 			// status.Error(err.Error())
 		}
 		}
 		if response != nil && response.StatusCode != 200 {
 		if response != nil && response.StatusCode != 200 {
+			slog.Error("Failed to initialize project", "error", response.StatusCode)
 			// status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode))
 			// status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode))
 		}
 		}
 	}()
 	}()
@@ -187,6 +188,21 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
 	return tea.Batch(cmds...)
 	return tea.Batch(cmds...)
 }
 }
 
 
+func (a *App) CompactSession(ctx context.Context) tea.Cmd {
+	response, err := a.Client.PostSessionSummarizeWithResponse(ctx, client.PostSessionSummarizeJSONRequestBody{
+		SessionID:  a.Session.Id,
+		ProviderID: a.Provider.Id,
+		ModelID:    a.Model.Id,
+	})
+	if err != nil {
+		slog.Error("Failed to compact session", "error", err)
+	}
+	if response != nil && response.StatusCode() != 200 {
+		slog.Error("Failed to compact session", "error", response.StatusCode)
+	}
+	return nil
+}
+
 func (a *App) MarkProjectInitialized(ctx context.Context) error {
 func (a *App) MarkProjectInitialized(ctx context.Context) error {
 	response, err := a.Client.PostAppInitialize(ctx)
 	response, err := a.Client.PostAppInitialize(ctx)
 	if err != nil {
 	if err != nil {

+ 21 - 1
packages/tui/internal/commands/command.go

@@ -59,6 +59,27 @@ func NewCommandRegistry() Registry {
 				key.WithKeys("f5", "super+t"),
 				key.WithKeys("f5", "super+t"),
 			),
 			),
 		},
 		},
+		"share": {
+			Name:        "share",
+			Description: "create shareable link",
+			KeyBinding: key.NewBinding(
+				key.WithKeys("f6"),
+			),
+		},
+		"init": {
+			Name:        "init",
+			Description: "create or update AGENTS.md",
+			KeyBinding: key.NewBinding(
+				key.WithKeys("f7"),
+			),
+		},
+		// "compact": {
+		// 	Name:        "compact",
+		// 	Description: "compact the session",
+		// 	KeyBinding: key.NewBinding(
+		// 		key.WithKeys("f8"),
+		// 	),
+		// },
 		"quit": {
 		"quit": {
 			Name:        "quit",
 			Name:        "quit",
 			Description: "quit",
 			Description: "quit",
@@ -68,4 +89,3 @@ func NewCommandRegistry() Registry {
 		},
 		},
 	}
 	}
 }
 }
-

+ 31 - 9
packages/tui/internal/completions/commands.go

@@ -2,10 +2,14 @@ package completions
 
 
 import (
 import (
 	"sort"
 	"sort"
+	"strings"
 
 
+	"github.com/charmbracelet/lipgloss/v2"
 	"github.com/lithammer/fuzzysearch/fuzzy"
 	"github.com/lithammer/fuzzysearch/fuzzy"
 	"github.com/sst/opencode/internal/app"
 	"github.com/sst/opencode/internal/app"
+	"github.com/sst/opencode/internal/commands"
 	"github.com/sst/opencode/internal/components/dialog"
 	"github.com/sst/opencode/internal/components/dialog"
+	"github.com/sst/opencode/internal/theme"
 )
 )
 
 
 type CommandCompletionProvider struct {
 type CommandCompletionProvider struct {
@@ -27,15 +31,36 @@ func (c *CommandCompletionProvider) GetEntry() dialog.CompletionItemI {
 	})
 	})
 }
 }
 
 
+func (c *CommandCompletionProvider) GetEmptyMessage() string {
+	return "no matching commands"
+}
+
+func getCommandCompletionItem(cmd commands.Command, space int) dialog.CompletionItemI {
+	t := theme.CurrentTheme()
+	spacer := strings.Repeat(" ", space)
+	title := "  /" + cmd.Name + lipgloss.NewStyle().Foreground(t.TextMuted()).Render(spacer+cmd.Description)
+	value := "/" + cmd.Name
+	return dialog.NewCompletionItem(dialog.CompletionItem{
+		Title: title,
+		Value: value,
+	})
+}
+
 func (c *CommandCompletionProvider) GetChildEntries(query string) ([]dialog.CompletionItemI, error) {
 func (c *CommandCompletionProvider) GetChildEntries(query string) ([]dialog.CompletionItemI, error) {
+	space := 1
+	for _, cmd := range c.app.Commands {
+		if lipgloss.Width(cmd.Name) > space {
+			space = lipgloss.Width(cmd.Name)
+		}
+	}
+	space += 2
+
 	if query == "" {
 	if query == "" {
 		// If no query, return all commands
 		// If no query, return all commands
 		items := []dialog.CompletionItemI{}
 		items := []dialog.CompletionItemI{}
 		for _, cmd := range c.app.Commands {
 		for _, cmd := range c.app.Commands {
-			items = append(items, dialog.NewCompletionItem(dialog.CompletionItem{
-				Title: "  /" + cmd.Name,
-				Value: "/" + cmd.Name,
-			}))
+			space := space - lipgloss.Width(cmd.Name)
+			items = append(items, getCommandCompletionItem(cmd, space))
 		}
 		}
 		return items, nil
 		return items, nil
 	}
 	}
@@ -45,11 +70,9 @@ func (c *CommandCompletionProvider) GetChildEntries(query string) ([]dialog.Comp
 	commandMap := make(map[string]dialog.CompletionItemI)
 	commandMap := make(map[string]dialog.CompletionItemI)
 
 
 	for _, cmd := range c.app.Commands {
 	for _, cmd := range c.app.Commands {
+		space := space - lipgloss.Width(cmd.Name)
 		commandNames = append(commandNames, cmd.Name)
 		commandNames = append(commandNames, cmd.Name)
-		commandMap[cmd.Name] = dialog.NewCompletionItem(dialog.CompletionItem{
-			Title: "  /" + cmd.Name,
-			Value: "/" + cmd.Name,
-		})
+		commandMap[cmd.Name] = getCommandCompletionItem(cmd, space)
 	}
 	}
 
 
 	// Find fuzzy matches
 	// Find fuzzy matches
@@ -68,4 +91,3 @@ func (c *CommandCompletionProvider) GetChildEntries(query string) ([]dialog.Comp
 
 
 	return items, nil
 	return items, nil
 }
 }
-

+ 4 - 0
packages/tui/internal/completions/files-folders.go

@@ -24,6 +24,10 @@ func (cg *filesAndFoldersContextGroup) GetEntry() dialog.CompletionItemI {
 	})
 	})
 }
 }
 
 
+func (cg *filesAndFoldersContextGroup) GetEmptyMessage() string {
+	return "no matching files"
+}
+
 func (cg *filesAndFoldersContextGroup) getFiles(query string) ([]string, error) {
 func (cg *filesAndFoldersContextGroup) getFiles(query string) ([]string, error) {
 	response, err := cg.app.Client.PostFileSearchWithResponse(context.Background(), client.PostFileSearchJSONRequestBody{
 	response, err := cg.app.Client.PostFileSearchWithResponse(context.Background(), client.PostFileSearchJSONRequestBody{
 		Query: query,
 		Query: query,

+ 12 - 6
packages/tui/internal/components/chat/editor.go

@@ -101,6 +101,8 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	switch msg := msg.(type) {
 	switch msg := msg.(type) {
 	case dialog.ThemeChangedMsg:
 	case dialog.ThemeChangedMsg:
 		m.textarea = createTextArea(&m.textarea)
 		m.textarea = createTextArea(&m.textarea)
+		m.spinner = createSpinner()
+		return m, m.spinner.Tick
 	case dialog.CompletionSelectedMsg:
 	case dialog.CompletionSelectedMsg:
 		if msg.IsCommand {
 		if msg.IsCommand {
 			// Execute the command directly
 			// Execute the command directly
@@ -421,12 +423,8 @@ func createTextArea(existing *textarea.Model) textarea.Model {
 	return ta
 	return ta
 }
 }
 
 
-func (m *editorComponent) GetValue() string {
-	return m.textarea.Value()
-}
-
-func NewEditorComponent(app *app.App) layout.ModelWithView {
-	s := spinner.New(
+func createSpinner() spinner.Model {
+	return spinner.New(
 		spinner.WithSpinner(spinner.Ellipsis),
 		spinner.WithSpinner(spinner.Ellipsis),
 		spinner.WithStyle(
 		spinner.WithStyle(
 			styles.
 			styles.
@@ -434,6 +432,14 @@ func NewEditorComponent(app *app.App) layout.ModelWithView {
 				Background(theme.CurrentTheme().Background()).
 				Background(theme.CurrentTheme().Background()).
 				Width(3)),
 				Width(3)),
 	)
 	)
+}
+
+func (m *editorComponent) GetValue() string {
+	return m.textarea.Value()
+}
+
+func NewEditorComponent(app *app.App) layout.ModelWithView {
+	s := createSpinner()
 	ta := createTextArea(nil)
 	ta := createTextArea(nil)
 
 
 	return &editorComponent{
 	return &editorComponent{

+ 4 - 4
packages/tui/internal/components/dialog/complete.go

@@ -13,7 +13,6 @@ import (
 )
 )
 
 
 type CompletionItem struct {
 type CompletionItem struct {
-	title string
 	Title string
 	Title string
 	Value string
 	Value string
 }
 }
@@ -35,8 +34,7 @@ func (ci *CompletionItem) Render(selected bool, width int) string {
 
 
 	if selected {
 	if selected {
 		itemStyle = itemStyle.
 		itemStyle = itemStyle.
-			Foreground(t.Primary()).
-			Bold(true)
+			Foreground(t.Primary())
 	}
 	}
 
 
 	title := itemStyle.Render(
 	title := itemStyle.Render(
@@ -62,6 +60,7 @@ type CompletionProvider interface {
 	GetId() string
 	GetId() string
 	GetEntry() CompletionItemI
 	GetEntry() CompletionItemI
 	GetChildEntries(query string) ([]CompletionItemI, error)
 	GetChildEntries(query string) ([]CompletionItemI, error)
+	GetEmptyMessage() string
 }
 }
 
 
 type CompletionSelectedMsg struct {
 type CompletionSelectedMsg struct {
@@ -250,6 +249,7 @@ func (c *completionDialogComponent) IsEmpty() bool {
 func (c *completionDialogComponent) SetProvider(provider CompletionProvider) {
 func (c *completionDialogComponent) SetProvider(provider CompletionProvider) {
 	if c.completionProvider.GetId() != provider.GetId() {
 	if c.completionProvider.GetId() != provider.GetId() {
 		c.completionProvider = provider
 		c.completionProvider = provider
+		c.list.SetEmptyMessage(" " + provider.GetEmptyMessage())
 	}
 	}
 }
 }
 
 
@@ -259,7 +259,7 @@ func NewCompletionDialogComponent(completionProvider CompletionProvider) Complet
 	li := list.NewListComponent(
 	li := list.NewListComponent(
 		[]CompletionItemI{},
 		[]CompletionItemI{},
 		7,
 		7,
-		"No matches",
+		completionProvider.GetEmptyMessage(),
 		false,
 		false,
 	)
 	)
 
 

+ 5 - 0
packages/tui/internal/components/list/list.go

@@ -18,6 +18,7 @@ type List[T ListItem] interface {
 	SetItems(items []T)
 	SetItems(items []T)
 	GetItems() []T
 	GetItems() []T
 	SetSelectedIndex(idx int)
 	SetSelectedIndex(idx int)
+	SetEmptyMessage(msg string)
 	IsEmpty() bool
 	IsEmpty() bool
 }
 }
 
 
@@ -100,6 +101,10 @@ func (c *listComponent[T]) GetItems() []T {
 	return c.items
 	return c.items
 }
 }
 
 
+func (c *listComponent[T]) SetEmptyMessage(msg string) {
+	c.fallbackMsg = msg
+}
+
 func (c *listComponent[T]) IsEmpty() bool {
 func (c *listComponent[T]) IsEmpty() bool {
 	return len(c.items) == 0
 	return len(c.items) == 0
 }
 }

+ 8 - 0
packages/tui/internal/tui/tui.go

@@ -145,6 +145,14 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		case "theme":
 		case "theme":
 			themeDialog := dialog.NewThemeDialog()
 			themeDialog := dialog.NewThemeDialog()
 			a.modal = themeDialog
 			a.modal = themeDialog
+		case "share":
+			a.app.Client.PostSessionShareWithResponse(context.Background(), client.PostSessionShareJSONRequestBody{
+				SessionID: a.app.Session.Id,
+			})
+		case "init":
+			return a, a.app.InitializeProject(context.Background())
+		// case "compact":
+		// 	return a, a.app.CompactSession(context.Background())
 		case "help":
 		case "help":
 			var helpBindings []key.Binding
 			var helpBindings []key.Binding
 			for _, cmd := range a.app.Commands {
 			for _, cmd := range a.app.Commands {