Ver Fonte

feat: faster tui init

adamdottv há 8 meses atrás
pai
commit
7c0d10a4ce

+ 1 - 1
packages/opencode/src/index.ts

@@ -47,7 +47,6 @@ const cli = yargs(hideBin(process.argv))
         const result = await App.provide(
           { cwd, version: VERSION },
           async (app) => {
-            App.info().path.config
             const providers = await Provider.list()
             if (Object.keys(providers).length === 0) {
               return "needs_provider"
@@ -79,6 +78,7 @@ const cli = yargs(hideBin(process.argv))
               env: {
                 ...process.env,
                 OPENCODE_SERVER: server.url.toString(),
+                OPENCODE_APP_INFO: JSON.stringify(app),
               },
               onExit: () => {
                 server.stop()

+ 30 - 22
packages/tui/cmd/opencode/main.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"encoding/json"
 	"log/slog"
 	"os"
 	"path/filepath"
@@ -16,7 +17,16 @@ import (
 var Version = "dev"
 
 func main() {
+	version := Version
+	if version != "dev" && !strings.HasPrefix(Version, "v") {
+		version = "v" + Version
+	}
+
 	url := os.Getenv("OPENCODE_SERVER")
+	appInfoStr := os.Getenv("OPENCODE_APP_INFO")
+	var appInfo client.AppInfo
+	json.Unmarshal([]byte(appInfoStr), &appInfo)
+
 	httpClient, err := client.NewClientWithResponses(url)
 	if err != nil {
 		slog.Error("Failed to create client", "error", err)
@@ -27,11 +37,7 @@ func main() {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	version := Version
-	if version != "dev" && !strings.HasPrefix(Version, "v") {
-		version = "v" + Version
-	}
-	app_, err := app.New(ctx, version, httpClient)
+	app_, err := app.New(ctx, version, appInfo, httpClient)
 	if err != nil {
 		panic(err)
 	}
@@ -61,27 +67,29 @@ func main() {
 		}
 	}()
 
-	paths, err := httpClient.PostPathGetWithResponse(context.Background())
-	if err != nil {
-		panic(err)
-	}
-	logfile := filepath.Join(paths.JSON200.Data, "log", "tui.log")
+	go func() {
+		paths, err := httpClient.PostPathGetWithResponse(context.Background())
+		if err != nil {
+			panic(err)
+		}
+		logfile := filepath.Join(paths.JSON200.Data, "log", "tui.log")
 
-	if _, err := os.Stat(filepath.Dir(logfile)); os.IsNotExist(err) {
-		err := os.MkdirAll(filepath.Dir(logfile), 0755)
+		if _, err := os.Stat(filepath.Dir(logfile)); os.IsNotExist(err) {
+			err := os.MkdirAll(filepath.Dir(logfile), 0755)
+			if err != nil {
+				slog.Error("Failed to create log directory", "error", err)
+				os.Exit(1)
+			}
+		}
+		file, err := os.Create(logfile)
 		if err != nil {
-			slog.Error("Failed to create log directory", "error", err)
+			slog.Error("Failed to create log file", "error", err)
 			os.Exit(1)
 		}
-	}
-	file, err := os.Create(logfile)
-	if err != nil {
-		slog.Error("Failed to create log file", "error", err)
-		os.Exit(1)
-	}
-	defer file.Close()
-	logger := slog.New(slog.NewTextHandler(file, &slog.HandlerOptions{Level: slog.LevelDebug}))
-	slog.SetDefault(logger)
+		defer file.Close()
+		logger := slog.New(slog.NewTextHandler(file, &slog.HandlerOptions{Level: slog.LevelDebug}))
+		slog.SetDefault(logger)
+	}()
 
 	// Run the TUI
 	result, err := program.Run()

+ 79 - 71
packages/tui/internal/app/app.go

@@ -17,7 +17,11 @@ import (
 	"github.com/sst/opencode/pkg/client"
 )
 
+var RootPath string
+
 type App struct {
+	Info       client.AppInfo
+	Version    string
 	ConfigPath string
 	Config     *config.Config
 	Client     *client.ClientWithResponses
@@ -28,95 +32,99 @@ type App struct {
 	Commands   commands.Registry
 }
 
-type AppInfo struct {
-	client.AppInfo
-	Version string
-}
-
-var Info AppInfo
-
-func New(ctx context.Context, version string, httpClient *client.ClientWithResponses) (*App, error) {
-	appInfoResponse, _ := httpClient.PostAppInfoWithResponse(ctx)
-	appInfo := appInfoResponse.JSON200
-	Info = AppInfo{
-		AppInfo: *appInfo,
-		Version: version,
-	}
-
-	providersResponse, err := httpClient.PostProviderListWithResponse(ctx)
-	if err != nil {
-		return nil, err
-	}
-	providers := []client.ProviderInfo{}
-	var defaultProvider *client.ProviderInfo
-	var defaultModel *client.ModelInfo
-
-	var anthropic *client.ProviderInfo
-	for _, provider := range providersResponse.JSON200.Providers {
-		if provider.Id == "anthropic" {
-			anthropic = &provider
-		}
-	}
-
-	// default to anthropic if available
-	if anthropic != nil {
-		defaultProvider = anthropic
-		defaultModel = getDefaultModel(providersResponse, *anthropic)
-	}
+func New(
+	ctx context.Context,
+	version string,
+	appInfo client.AppInfo,
+	httpClient *client.ClientWithResponses,
+) (*App, error) {
+	RootPath = appInfo.Path.Root
 
-	for _, provider := range providersResponse.JSON200.Providers {
-		if defaultProvider == nil || defaultModel == nil {
-			defaultProvider = &provider
-			defaultModel = getDefaultModel(providersResponse, provider)
-		}
-		providers = append(providers, provider)
-	}
-	if len(providers) == 0 {
-		return nil, fmt.Errorf("no providers found")
-	}
-
-	appConfigPath := filepath.Join(Info.Path.Config, "config")
+	appConfigPath := filepath.Join(appInfo.Path.Config, "config")
 	appConfig, err := config.LoadConfig(appConfigPath)
 	if err != nil {
-		slog.Info("No TUI config found, using default values", "error", err)
-		appConfig = config.NewConfig("opencode", defaultProvider.Id, defaultModel.Id)
+		appConfig = config.NewConfig()
 		config.SaveConfig(appConfigPath, appConfig)
 	}
-
-	var currentProvider *client.ProviderInfo
-	var currentModel *client.ModelInfo
-	for _, provider := range providers {
-		if provider.Id == appConfig.Provider {
-			currentProvider = &provider
-
-			for _, model := range provider.Models {
-				if model.Id == appConfig.Model {
-					currentModel = &model
-				}
-			}
-		}
-	}
-	if currentProvider == nil || currentModel == nil {
-		currentProvider = defaultProvider
-		currentModel = defaultModel
-	}
+	theme.SetTheme(appConfig.Theme)
 
 	app := &App{
+		Info:       appInfo,
+		Version:    version,
 		ConfigPath: appConfigPath,
 		Config:     appConfig,
 		Client:     httpClient,
-		Provider:   currentProvider,
-		Model:      currentModel,
 		Session:    &client.SessionInfo{},
 		Messages:   []client.MessageInfo{},
 		Commands:   commands.NewCommandRegistry(),
 	}
 
-	theme.SetTheme(appConfig.Theme)
-
 	return app, nil
 }
 
+func (a *App) InitializeProvider() tea.Cmd {
+	return func() tea.Msg {
+		providersResponse, err := a.Client.PostProviderListWithResponse(context.Background())
+		if err != nil {
+			slog.Error("Failed to list providers", "error", err)
+			// TODO: notify user
+			return nil
+		}
+		providers := []client.ProviderInfo{}
+		var defaultProvider *client.ProviderInfo
+		var defaultModel *client.ModelInfo
+
+		var anthropic *client.ProviderInfo
+		for _, provider := range providersResponse.JSON200.Providers {
+			if provider.Id == "anthropic" {
+				anthropic = &provider
+			}
+		}
+
+		// default to anthropic if available
+		if anthropic != nil {
+			defaultProvider = anthropic
+			defaultModel = getDefaultModel(providersResponse, *anthropic)
+		}
+
+		for _, provider := range providersResponse.JSON200.Providers {
+			if defaultProvider == nil || defaultModel == nil {
+				defaultProvider = &provider
+				defaultModel = getDefaultModel(providersResponse, provider)
+			}
+			providers = append(providers, provider)
+		}
+		if len(providers) == 0 {
+			slog.Error("No providers configured")
+			return nil
+		}
+
+		var currentProvider *client.ProviderInfo
+		var currentModel *client.ModelInfo
+		for _, provider := range providers {
+			if provider.Id == a.Config.Provider {
+				currentProvider = &provider
+
+				for _, model := range provider.Models {
+					if model.Id == a.Config.Model {
+						currentModel = &model
+					}
+				}
+			}
+		}
+		if currentProvider == nil || currentModel == nil {
+			currentProvider = defaultProvider
+			currentModel = defaultModel
+		}
+
+		// TODO: handle no provider or model setup, yet
+		return state.ModelSelectedMsg{
+			Provider: *currentProvider,
+			Model:    *currentModel,
+		}
+	}
+}
+
 func getDefaultModel(response *client.PostProviderListResponse, provider client.ProviderInfo) *client.ModelInfo {
 	if match, ok := response.JSON200.Default[provider.Id]; ok {
 		model := provider.Models[match]

+ 2 - 2
packages/tui/internal/components/chat/message.go

@@ -23,7 +23,7 @@ import (
 
 func toMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
 	r := styles.GetMarkdownRenderer(width, backgroundColor)
-	content = strings.ReplaceAll(content, app.Info.Path.Root+"/", "")
+	content = strings.ReplaceAll(content, app.RootPath+"/", "")
 	rendered, _ := r.Render(content)
 	lines := strings.Split(rendered, "\n")
 
@@ -584,7 +584,7 @@ func truncateHeight(content string, height int) string {
 }
 
 func relative(path string) string {
-	return strings.TrimPrefix(path, app.Info.Path.Root+"/")
+	return strings.TrimPrefix(path, app.RootPath+"/")
 }
 
 func extension(path string) string {

+ 2 - 2
packages/tui/internal/components/chat/messages.go

@@ -139,7 +139,7 @@ func (m *messagesComponent) renderView() {
 		author := ""
 		switch message.Role {
 		case client.User:
-			author = app.Info.User
+			author = m.app.Info.User
 		case client.Assistant:
 			author = message.Metadata.Assistant.ModelID
 		}
@@ -328,7 +328,7 @@ func (m *messagesComponent) home() string {
 	logoAndVersion := lipgloss.JoinVertical(
 		lipgloss.Right,
 		logo,
-		muted(app.Info.Version),
+		muted(m.app.Version),
 	)
 
 	lines := []string{}

+ 4 - 4
packages/tui/internal/components/core/status.go

@@ -34,14 +34,14 @@ func (m statusComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	return m, nil
 }
 
-func logo() string {
+func (m statusComponent) logo() string {
 	t := theme.CurrentTheme()
 	base := lipgloss.NewStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Render
 	emphasis := lipgloss.NewStyle().Bold(true).Background(t.BackgroundElement()).Foreground(t.Text()).Render
 
 	open := base("open")
 	code := emphasis("code ")
-	version := base(app.Info.Version)
+	version := base(m.app.Version)
 	return styles.Padded().
 		Background(t.BackgroundElement()).
 		Render(open + code + version)
@@ -84,12 +84,12 @@ func (m statusComponent) View() string {
 			Render("")
 	}
 
-	logo := logo()
+	logo := m.logo()
 
 	cwd := styles.Padded().
 		Foreground(t.TextMuted()).
 		Background(t.BackgroundSubtle()).
-		Render(app.Info.Path.Cwd)
+		Render(m.app.Info.Path.Cwd)
 
 	sessionInfo := ""
 	if m.app.Session.Id != "" {

+ 2 - 8
packages/tui/internal/config/config.go

@@ -17,11 +17,9 @@ type Config struct {
 
 // NewConfig creates a new Config instance with default values.
 // This can be useful for initializing a new configuration file.
-func NewConfig(theme, provider, model string) *Config {
+func NewConfig() *Config {
 	return &Config{
-		Theme:    theme,
-		Provider: provider,
-		Model:    model,
+		Theme: "opencode",
 	}
 }
 
@@ -35,12 +33,10 @@ func SaveConfig(filePath string, config *Config) error {
 	defer file.Close()
 
 	writer := bufio.NewWriter(file)
-
 	encoder := toml.NewEncoder(writer)
 	if err := encoder.Encode(config); err != nil {
 		return fmt.Errorf("failed to encode config to TOML file %s: %w", filePath, err)
 	}
-
 	if err := writer.Flush(); err != nil {
 		return fmt.Errorf("failed to flush writer for config file %s: %w", filePath, err)
 	}
@@ -53,13 +49,11 @@ func SaveConfig(filePath string, config *Config) error {
 // It returns a pointer to the Config struct and an error if any issues occur.
 func LoadConfig(filePath string) (*Config, error) {
 	var config Config
-
 	if _, err := toml.DecodeFile(filePath, &config); err != nil {
 		if _, statErr := os.Stat(filePath); os.IsNotExist(statErr) {
 			return nil, fmt.Errorf("config file not found at %s: %w", filePath, statErr)
 		}
 		return nil, fmt.Errorf("failed to decode TOML from file %s: %w", filePath, err)
 	}
-
 	return &config, nil
 }

+ 3 - 1
packages/tui/internal/tui/tui.go

@@ -38,6 +38,8 @@ type appModel struct {
 func (a appModel) Init() tea.Cmd {
 	t := theme.CurrentTheme()
 	var cmds []tea.Cmd
+	cmds = append(cmds, a.app.InitializeProvider())
+
 	cmds = append(cmds, tea.SetBackgroundColor(t.Background()))
 	cmds = append(cmds, tea.RequestBackgroundColor)
 
@@ -50,7 +52,7 @@ func (a appModel) Init() tea.Cmd {
 
 	// Check if we should show the init dialog
 	cmds = append(cmds, func() tea.Msg {
-		shouldShow := app.Info.Git && app.Info.Time.Initialized == nil
+		shouldShow := a.app.Info.Git && a.app.Info.Time.Initialized == nil
 		return dialog.ShowInitDialogMsg{Show: shouldShow}
 	})