state.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package app
  2. import (
  3. "bufio"
  4. "fmt"
  5. "log/slog"
  6. "os"
  7. "time"
  8. "github.com/BurntSushi/toml"
  9. )
  10. type ModelUsage struct {
  11. ProviderID string `toml:"provider_id"`
  12. ModelID string `toml:"model_id"`
  13. LastUsed time.Time `toml:"last_used"`
  14. }
  15. type AgentModel struct {
  16. ProviderID string `toml:"provider_id"`
  17. ModelID string `toml:"model_id"`
  18. }
  19. type State struct {
  20. Theme string `toml:"theme"`
  21. ScrollSpeed *int `toml:"scroll_speed"`
  22. AgentModel map[string]AgentModel `toml:"agent_model"`
  23. Provider string `toml:"provider"`
  24. Model string `toml:"model"`
  25. Agent string `toml:"agent"`
  26. RecentlyUsedModels []ModelUsage `toml:"recently_used_models"`
  27. MessagesRight bool `toml:"messages_right"`
  28. SplitDiff bool `toml:"split_diff"`
  29. MessageHistory []Prompt `toml:"message_history"`
  30. ShowToolDetails *bool `toml:"show_tool_details"`
  31. ShowThinkingBlocks *bool `toml:"show_thinking_blocks"`
  32. }
  33. func NewState() *State {
  34. return &State{
  35. Theme: "opencode",
  36. Agent: "build",
  37. AgentModel: make(map[string]AgentModel),
  38. RecentlyUsedModels: make([]ModelUsage, 0),
  39. MessageHistory: make([]Prompt, 0),
  40. }
  41. }
  42. // UpdateModelUsage updates the recently used models list with the specified model
  43. func (s *State) UpdateModelUsage(providerID, modelID string) {
  44. now := time.Now()
  45. // Check if this model is already in the list
  46. for i, usage := range s.RecentlyUsedModels {
  47. if usage.ProviderID == providerID && usage.ModelID == modelID {
  48. s.RecentlyUsedModels[i].LastUsed = now
  49. usage := s.RecentlyUsedModels[i]
  50. copy(s.RecentlyUsedModels[1:i+1], s.RecentlyUsedModels[0:i])
  51. s.RecentlyUsedModels[0] = usage
  52. return
  53. }
  54. }
  55. newUsage := ModelUsage{
  56. ProviderID: providerID,
  57. ModelID: modelID,
  58. LastUsed: now,
  59. }
  60. // Prepend to slice and limit to last 50 entries
  61. s.RecentlyUsedModels = append([]ModelUsage{newUsage}, s.RecentlyUsedModels...)
  62. if len(s.RecentlyUsedModels) > 50 {
  63. s.RecentlyUsedModels = s.RecentlyUsedModels[:50]
  64. }
  65. }
  66. func (s *State) RemoveModelFromRecentlyUsed(providerID, modelID string) {
  67. for i, usage := range s.RecentlyUsedModels {
  68. if usage.ProviderID == providerID && usage.ModelID == modelID {
  69. s.RecentlyUsedModels = append(s.RecentlyUsedModels[:i], s.RecentlyUsedModels[i+1:]...)
  70. return
  71. }
  72. }
  73. }
  74. func (s *State) AddPromptToHistory(prompt Prompt) {
  75. s.MessageHistory = append([]Prompt{prompt}, s.MessageHistory...)
  76. if len(s.MessageHistory) > 50 {
  77. s.MessageHistory = s.MessageHistory[:50]
  78. }
  79. }
  80. // SaveState writes the provided Config struct to the specified TOML file.
  81. // It will create the file if it doesn't exist, or overwrite it if it does.
  82. func SaveState(filePath string, state *State) error {
  83. file, err := os.Create(filePath)
  84. if err != nil {
  85. return fmt.Errorf("failed to create/open config file %s: %w", filePath, err)
  86. }
  87. defer file.Close()
  88. writer := bufio.NewWriter(file)
  89. encoder := toml.NewEncoder(writer)
  90. if err := encoder.Encode(state); err != nil {
  91. return fmt.Errorf("failed to encode state to TOML file %s: %w", filePath, err)
  92. }
  93. if err := writer.Flush(); err != nil {
  94. return fmt.Errorf("failed to flush writer for state file %s: %w", filePath, err)
  95. }
  96. slog.Debug("State saved to file", "file", filePath)
  97. return nil
  98. }
  99. // LoadState loads the state from the specified TOML file.
  100. // It returns a pointer to the State struct and an error if any issues occur.
  101. func LoadState(filePath string) (*State, error) {
  102. var state State
  103. if _, err := toml.DecodeFile(filePath, &state); err != nil {
  104. if _, statErr := os.Stat(filePath); os.IsNotExist(statErr) {
  105. return nil, fmt.Errorf("state file not found at %s: %w", filePath, statErr)
  106. }
  107. return nil, fmt.Errorf("failed to decode TOML from file %s: %w", filePath, err)
  108. }
  109. // Restore attachment sources types that were deserialized as map[string]any
  110. for _, prompt := range state.MessageHistory {
  111. for _, att := range prompt.Attachments {
  112. att.RestoreSourceType()
  113. }
  114. }
  115. return &state, nil
  116. }