apikey.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package models
  2. import (
  3. "fmt"
  4. "github.com/charmbracelet/bubbles/v2/spinner"
  5. "github.com/charmbracelet/bubbles/v2/textinput"
  6. tea "github.com/charmbracelet/bubbletea/v2"
  7. "github.com/charmbracelet/crush/internal/config"
  8. "github.com/charmbracelet/crush/internal/home"
  9. "github.com/charmbracelet/crush/internal/tui/styles"
  10. "github.com/charmbracelet/crush/internal/tui/util"
  11. "github.com/charmbracelet/lipgloss/v2"
  12. )
  13. type APIKeyInputState int
  14. const (
  15. APIKeyInputStateInitial APIKeyInputState = iota
  16. APIKeyInputStateVerifying
  17. APIKeyInputStateVerified
  18. APIKeyInputStateError
  19. )
  20. type APIKeyStateChangeMsg struct {
  21. State APIKeyInputState
  22. }
  23. type APIKeyInput struct {
  24. input textinput.Model
  25. width int
  26. spinner spinner.Model
  27. providerName string
  28. state APIKeyInputState
  29. title string
  30. showTitle bool
  31. }
  32. func NewAPIKeyInput() *APIKeyInput {
  33. t := styles.CurrentTheme()
  34. ti := textinput.New()
  35. ti.Placeholder = "Enter your API key..."
  36. ti.SetVirtualCursor(false)
  37. ti.Prompt = "> "
  38. ti.SetStyles(t.S().TextInput)
  39. ti.Focus()
  40. return &APIKeyInput{
  41. input: ti,
  42. state: APIKeyInputStateInitial,
  43. spinner: spinner.New(
  44. spinner.WithSpinner(spinner.Dot),
  45. spinner.WithStyle(t.S().Base.Foreground(t.Green)),
  46. ),
  47. providerName: "Provider",
  48. showTitle: true,
  49. }
  50. }
  51. func (a *APIKeyInput) SetProviderName(name string) {
  52. a.providerName = name
  53. a.updateStatePresentation()
  54. }
  55. func (a *APIKeyInput) SetShowTitle(show bool) {
  56. a.showTitle = show
  57. }
  58. func (a *APIKeyInput) GetTitle() string {
  59. return a.title
  60. }
  61. func (a *APIKeyInput) Init() tea.Cmd {
  62. a.updateStatePresentation()
  63. return a.spinner.Tick
  64. }
  65. func (a *APIKeyInput) Update(msg tea.Msg) (util.Model, tea.Cmd) {
  66. switch msg := msg.(type) {
  67. case spinner.TickMsg:
  68. if a.state == APIKeyInputStateVerifying {
  69. var cmd tea.Cmd
  70. a.spinner, cmd = a.spinner.Update(msg)
  71. a.updateStatePresentation()
  72. return a, cmd
  73. }
  74. return a, nil
  75. case APIKeyStateChangeMsg:
  76. a.state = msg.State
  77. var cmd tea.Cmd
  78. if msg.State == APIKeyInputStateVerifying {
  79. cmd = a.spinner.Tick
  80. }
  81. a.updateStatePresentation()
  82. return a, cmd
  83. }
  84. var cmd tea.Cmd
  85. a.input, cmd = a.input.Update(msg)
  86. return a, cmd
  87. }
  88. func (a *APIKeyInput) updateStatePresentation() {
  89. t := styles.CurrentTheme()
  90. prefixStyle := t.S().Base.
  91. Foreground(t.Primary)
  92. accentStyle := t.S().Base.Foreground(t.Green).Bold(true)
  93. errorStyle := t.S().Base.Foreground(t.Cherry)
  94. switch a.state {
  95. case APIKeyInputStateInitial:
  96. titlePrefix := prefixStyle.Render("Enter your ")
  97. a.title = titlePrefix + accentStyle.Render(a.providerName+" API Key") + prefixStyle.Render(".")
  98. a.input.SetStyles(t.S().TextInput)
  99. a.input.Prompt = "> "
  100. case APIKeyInputStateVerifying:
  101. titlePrefix := prefixStyle.Render("Verifying your ")
  102. a.title = titlePrefix + accentStyle.Render(a.providerName+" API Key") + prefixStyle.Render("...")
  103. ts := t.S().TextInput
  104. // make the blurred state be the same
  105. ts.Blurred.Prompt = ts.Focused.Prompt
  106. a.input.Prompt = a.spinner.View()
  107. a.input.Blur()
  108. case APIKeyInputStateVerified:
  109. a.title = accentStyle.Render(a.providerName+" API Key") + prefixStyle.Render(" validated.")
  110. ts := t.S().TextInput
  111. // make the blurred state be the same
  112. ts.Blurred.Prompt = ts.Focused.Prompt
  113. a.input.SetStyles(ts)
  114. a.input.Prompt = styles.CheckIcon + " "
  115. a.input.Blur()
  116. case APIKeyInputStateError:
  117. a.title = errorStyle.Render("Invalid ") + accentStyle.Render(a.providerName+" API Key") + errorStyle.Render(". Try again?")
  118. ts := t.S().TextInput
  119. ts.Focused.Prompt = ts.Focused.Prompt.Foreground(t.Cherry)
  120. a.input.Focus()
  121. a.input.SetStyles(ts)
  122. a.input.Prompt = styles.ErrorIcon + " "
  123. }
  124. }
  125. func (a *APIKeyInput) View() string {
  126. inputView := a.input.View()
  127. dataPath := config.GlobalConfigData()
  128. dataPath = home.Short(dataPath)
  129. helpText := styles.CurrentTheme().S().Muted.
  130. Render(fmt.Sprintf("This will be written to the global configuration: %s", dataPath))
  131. var content string
  132. if a.showTitle && a.title != "" {
  133. content = lipgloss.JoinVertical(
  134. lipgloss.Left,
  135. a.title,
  136. "",
  137. inputView,
  138. "",
  139. helpText,
  140. )
  141. } else {
  142. content = lipgloss.JoinVertical(
  143. lipgloss.Left,
  144. inputView,
  145. "",
  146. helpText,
  147. )
  148. }
  149. return content
  150. }
  151. func (a *APIKeyInput) Cursor() *tea.Cursor {
  152. cursor := a.input.Cursor()
  153. if cursor != nil && a.showTitle {
  154. cursor.Y += 2 // Adjust for title and spacing
  155. }
  156. return cursor
  157. }
  158. func (a *APIKeyInput) Value() string {
  159. return a.input.Value()
  160. }
  161. func (a *APIKeyInput) Tick() tea.Cmd {
  162. if a.state == APIKeyInputStateVerifying {
  163. return a.spinner.Tick
  164. }
  165. return nil
  166. }
  167. func (a *APIKeyInput) SetWidth(width int) {
  168. a.width = width
  169. a.input.SetWidth(width - 4)
  170. }
  171. func (a *APIKeyInput) Reset() {
  172. a.state = APIKeyInputStateInitial
  173. a.input.SetValue("")
  174. a.input.Focus()
  175. a.updateStatePresentation()
  176. }