state.go 5.1 KB

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