state.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 AgentUsage struct {
  16. AgentName string `toml:"agent_name"`
  17. LastUsed time.Time `toml:"last_used"`
  18. }
  19. type AgentModel struct {
  20. ProviderID string `toml:"provider_id"`
  21. ModelID string `toml:"model_id"`
  22. }
  23. type State struct {
  24. Theme string `toml:"theme"`
  25. ScrollSpeed *int `toml:"scroll_speed"`
  26. AgentModel map[string]AgentModel `toml:"agent_model"`
  27. Provider string `toml:"provider"`
  28. Model string `toml:"model"`
  29. Agent string `toml:"agent"`
  30. RecentlyUsedModels []ModelUsage `toml:"recently_used_models"`
  31. RecentlyUsedAgents []AgentUsage `toml:"recently_used_agents"`
  32. MessagesRight bool `toml:"messages_right"`
  33. SplitDiff bool `toml:"split_diff"`
  34. MessageHistory []Prompt `toml:"message_history"`
  35. ShowToolDetails *bool `toml:"show_tool_details"`
  36. ShowThinkingBlocks *bool `toml:"show_thinking_blocks"`
  37. }
  38. func NewState() *State {
  39. return &State{
  40. Theme: "opencode",
  41. Agent: "build",
  42. AgentModel: make(map[string]AgentModel),
  43. RecentlyUsedModels: make([]ModelUsage, 0),
  44. RecentlyUsedAgents: make([]AgentUsage, 0),
  45. MessageHistory: make([]Prompt, 0),
  46. }
  47. }
  48. // UpdateModelUsage updates the recently used models list with the specified model
  49. func (s *State) UpdateModelUsage(providerID, modelID string) {
  50. now := time.Now()
  51. // Check if this model is already in the list
  52. for i, usage := range s.RecentlyUsedModels {
  53. if usage.ProviderID == providerID && usage.ModelID == modelID {
  54. s.RecentlyUsedModels[i].LastUsed = now
  55. usage := s.RecentlyUsedModels[i]
  56. copy(s.RecentlyUsedModels[1:i+1], s.RecentlyUsedModels[0:i])
  57. s.RecentlyUsedModels[0] = usage
  58. return
  59. }
  60. }
  61. newUsage := ModelUsage{
  62. ProviderID: providerID,
  63. ModelID: modelID,
  64. LastUsed: now,
  65. }
  66. // Prepend to slice and limit to last 50 entries
  67. s.RecentlyUsedModels = append([]ModelUsage{newUsage}, s.RecentlyUsedModels...)
  68. if len(s.RecentlyUsedModels) > 50 {
  69. s.RecentlyUsedModels = s.RecentlyUsedModels[:50]
  70. }
  71. }
  72. func (s *State) RemoveModelFromRecentlyUsed(providerID, modelID string) {
  73. for i, usage := range s.RecentlyUsedModels {
  74. if usage.ProviderID == providerID && usage.ModelID == modelID {
  75. s.RecentlyUsedModels = append(s.RecentlyUsedModels[:i], s.RecentlyUsedModels[i+1:]...)
  76. return
  77. }
  78. }
  79. }
  80. // UpdateAgentUsage updates the recently used agents list with the specified agent
  81. func (s *State) UpdateAgentUsage(agentName string) {
  82. now := time.Now()
  83. // Check if this agent is already in the list
  84. for i, usage := range s.RecentlyUsedAgents {
  85. if usage.AgentName == agentName {
  86. s.RecentlyUsedAgents[i].LastUsed = now
  87. usage := s.RecentlyUsedAgents[i]
  88. copy(s.RecentlyUsedAgents[1:i+1], s.RecentlyUsedAgents[0:i])
  89. s.RecentlyUsedAgents[0] = usage
  90. return
  91. }
  92. }
  93. newUsage := AgentUsage{
  94. AgentName: agentName,
  95. LastUsed: now,
  96. }
  97. // Prepend to slice and limit to last 20 entries
  98. s.RecentlyUsedAgents = append([]AgentUsage{newUsage}, s.RecentlyUsedAgents...)
  99. if len(s.RecentlyUsedAgents) > 20 {
  100. s.RecentlyUsedAgents = s.RecentlyUsedAgents[:20]
  101. }
  102. }
  103. func (s *State) RemoveAgentFromRecentlyUsed(agentName string) {
  104. for i, usage := range s.RecentlyUsedAgents {
  105. if usage.AgentName == agentName {
  106. s.RecentlyUsedAgents = append(s.RecentlyUsedAgents[:i], s.RecentlyUsedAgents[i+1:]...)
  107. return
  108. }
  109. }
  110. }
  111. func (s *State) AddPromptToHistory(prompt Prompt) {
  112. s.MessageHistory = append([]Prompt{prompt}, s.MessageHistory...)
  113. if len(s.MessageHistory) > 50 {
  114. s.MessageHistory = s.MessageHistory[:50]
  115. }
  116. }
  117. // SaveState writes the provided Config struct to the specified TOML file.
  118. // It will create the file if it doesn't exist, or overwrite it if it does.
  119. func SaveState(filePath string, state *State) error {
  120. file, err := os.Create(filePath)
  121. if err != nil {
  122. return fmt.Errorf("failed to create/open config file %s: %w", filePath, err)
  123. }
  124. defer file.Close()
  125. writer := bufio.NewWriter(file)
  126. encoder := toml.NewEncoder(writer)
  127. if err := encoder.Encode(state); err != nil {
  128. return fmt.Errorf("failed to encode state to TOML file %s: %w", filePath, err)
  129. }
  130. if err := writer.Flush(); err != nil {
  131. return fmt.Errorf("failed to flush writer for state file %s: %w", filePath, err)
  132. }
  133. slog.Debug("State saved to file", "file", filePath)
  134. return nil
  135. }
  136. // LoadState loads the state from the specified TOML file.
  137. // It returns a pointer to the State struct and an error if any issues occur.
  138. func LoadState(filePath string) (*State, error) {
  139. var state State
  140. if _, err := toml.DecodeFile(filePath, &state); err != nil {
  141. if _, statErr := os.Stat(filePath); os.IsNotExist(statErr) {
  142. return nil, fmt.Errorf("state file not found at %s: %w", filePath, statErr)
  143. }
  144. return nil, fmt.Errorf("failed to decode TOML from file %s: %w", filePath, err)
  145. }
  146. // Restore attachment sources types that were deserialized as map[string]any
  147. for _, prompt := range state.MessageHistory {
  148. for _, att := range prompt.Attachments {
  149. att.RestoreSourceType()
  150. }
  151. }
  152. return &state, nil
  153. }