wip: api key

Kujtim Hoxha created

Change summary

internal/config/config.go                        |  2 
internal/config/init.go                          |  2 
internal/config/load.go                          | 17 ++
internal/tui/components/dialogs/models/apikey.go | 88 ++++++++++++++++++
4 files changed, 104 insertions(+), 5 deletions(-)

Detailed changes

internal/config/config.go 🔗

@@ -299,7 +299,7 @@ func UpdatePreferredModel(modelType SelectedModelType, model SelectedModel) erro
 }
 
 func (c *Config) SetConfigField(key string, value any) error {
-	configPath := globalConfigData()
+	configPath := GlobalConfigData()
 	// read the data
 	data, err := os.ReadFile(configPath)
 	if err != nil {

internal/config/init.go 🔗

@@ -105,7 +105,7 @@ func MarkProjectInitialized() error {
 }
 
 func HasInitialDataConfig() bool {
-	cfgPath := globalConfigData()
+	cfgPath := GlobalConfigData()
 	if _, err := os.Stat(cfgPath); err != nil {
 		return false
 	}

internal/config/load.go 🔗

@@ -37,7 +37,7 @@ func Load(workingDir string, debug bool) (*Config, error) {
 	// uses default config paths
 	configPaths := []string{
 		globalConfig(),
-		globalConfigData(),
+		GlobalConfigData(),
 		filepath.Join(workingDir, fmt.Sprintf("%s.json", appName)),
 		filepath.Join(workingDir, fmt.Sprintf(".%s.json", appName)),
 	}
@@ -510,9 +510,9 @@ func globalConfig() string {
 	return filepath.Join(os.Getenv("HOME"), ".config", appName, fmt.Sprintf("%s.json", appName))
 }
 
-// globalConfigData returns the path to the main data directory for the application.
+// GlobalConfigData returns the path to the main data directory for the application.
 // this config is used when the app overrides configurations instead of updating the global config.
-func globalConfigData() string {
+func GlobalConfigData() string {
 	xdgDataHome := os.Getenv("XDG_DATA_HOME")
 	if xdgDataHome != "" {
 		return filepath.Join(xdgDataHome, appName, fmt.Sprintf("%s.json", appName))
@@ -531,3 +531,14 @@ func globalConfigData() string {
 
 	return filepath.Join(os.Getenv("HOME"), ".local", "share", appName, fmt.Sprintf("%s.json", appName))
 }
+
+func HomeDir() string {
+	homeDir := os.Getenv("HOME")
+	if homeDir == "" {
+		homeDir = os.Getenv("USERPROFILE") // For Windows compatibility
+	}
+	if homeDir == "" {
+		homeDir = os.Getenv("HOMEPATH") // Fallback for some environments
+	}
+	return homeDir
+}

internal/tui/components/dialogs/models/apikey.go 🔗

@@ -0,0 +1,88 @@
+package models
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/charmbracelet/bubbles/v2/textinput"
+	tea "github.com/charmbracelet/bubbletea/v2"
+	"github.com/charmbracelet/crush/internal/config"
+	"github.com/charmbracelet/crush/internal/tui/styles"
+	"github.com/charmbracelet/lipgloss/v2"
+)
+
+type APIKeyInput struct {
+	input  textinput.Model
+	width  int
+	height int
+}
+
+func NewAPIKeyInput() *APIKeyInput {
+	t := styles.CurrentTheme()
+
+	ti := textinput.New()
+	ti.Placeholder = "Enter your API key..."
+	ti.SetWidth(50)
+	ti.SetVirtualCursor(false)
+	ti.Prompt = "> "
+	ti.SetStyles(t.S().TextInput)
+	ti.Focus()
+
+	return &APIKeyInput{
+		input: ti,
+		width: 60,
+	}
+}
+
+func (a *APIKeyInput) Init() tea.Cmd {
+	return textinput.Blink
+}
+
+func (a *APIKeyInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	switch msg := msg.(type) {
+	case tea.WindowSizeMsg:
+		a.width = msg.Width
+		a.height = msg.Height
+	}
+
+	var cmd tea.Cmd
+	a.input, cmd = a.input.Update(msg)
+	return a, cmd
+}
+
+func (a *APIKeyInput) View() tea.View {
+	t := styles.CurrentTheme()
+
+	title := t.S().Base.
+		Foreground(t.Secondary).
+		Bold(true).
+		Render("Enter your Anthropic API Key")
+
+	inputView := a.input.View()
+
+	dataPath := config.GlobalConfigData()
+	dataPath = strings.Replace(dataPath, config.HomeDir(), "~", 1)
+	helpText := t.S().Muted.
+		Render(fmt.Sprintf("This will be written to the global configuration: %s", dataPath))
+
+	content := lipgloss.JoinVertical(
+		lipgloss.Left,
+		title,
+		"",
+		inputView,
+		"",
+		helpText,
+	)
+
+	view := tea.NewView(content)
+	cursor := a.input.Cursor()
+	if cursor != nil {
+		cursor.Y += 2 // Adjust for title and spacing
+	}
+	view.SetCursor(cursor)
+	return view
+}
+
+func (a *APIKeyInput) Value() string {
+	return a.input.Value()
+}