Class: OllamaChat::Database::Models::AppState

Inherits:
Object
  • Object
show all
Defined in:
lib/ollama_chat/database/models/app_state.rb

Overview

Represents a key/value pair of persistent application state stored in the database.

This model provides a generic, JSON-backed store for small bits of runtime state that need to survive across sessions—such as the XOR-SHA256 fingerprint of the shipped default prompts used for boot drift detection.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.acknowledge(chat) ⇒ Object

Acknowledges the current shipped-prompt state, suppressing the boot drift notification. Called after /prompt sync so the user is not nagged again on the next launch.

Parameters:



103
104
105
106
107
# File 'lib/ollama_chat/database/models/app_state.rb', line 103

def self.acknowledge(chat)
  hashes      = compute_hashes(chat.config.prompts)
  fingerprint = fingerprint_from(hashes)
  store_fingerprint(fingerprint, hashes)
end

.compute_hashes(prompts_config) ⇒ Hash{String => String}

Builds a per-prompt SHA256 hash map from a prompts config object.

Parameters:

  • prompts_config (OllamaChatConfig::Prompts)

    the config prompts

Returns:

  • (Hash{String => String})

    mapping "context/name" → SHA256 hex



130
131
132
133
134
135
136
137
# File 'lib/ollama_chat/database/models/app_state.rb', line 130

def self.compute_hashes(prompts_config)
  prompts_config.to_h.each_with_object({}) do |(context, prompts), hashes|
    prompts.each do |name, content|
      hashes["#{context}/#{name}"] =
        Digest::SHA256.hexdigest(content.to_s)
    end
  end
end

.fingerprint_from(hashes) ⇒ String

Computes an XOR fingerprint from a hash map.

Parameters:

  • hashes (Hash{String => String})

    the per-prompt SHA256 hex values

Returns:

  • (String)

    the XOR-combined fingerprint as a hex string



143
144
145
# File 'lib/ollama_chat/database/models/app_state.rb', line 143

def self.fingerprint_from(hashes)
  hashes.values.reduce(0) { |sum, hex| sum ^ hex.to_i(16) }.to_s(16)
end

.get(key) ⇒ Hash?

Retrieves the JSON payload stored under the given key.

Parameters:

  • key (String, Symbol)

    the state key to look up

Returns:

  • (Hash, nil)

    the deserialized JSON payload, or nil if no record exists for the key



29
30
31
# File 'lib/ollama_chat/database/models/app_state.rb', line 29

def self.get(key)
  where(key: key.to_s).first&.value
end

.seed(chat) ⇒ Object

Computes per-prompt SHA256 hashes and an XOR fingerprint of all shipped default prompts.

At boot, if the stored fingerprint differs from the current one, a detail report (modified / added / removed) is printed to STDERR and the user is prompted to accept the new fingerprint. If declined, the old fingerprint is retained and the notice will reappear on the next boot.

This method is invoked automatically by the model seeding loop in OllamaChat::Database.setup_models.

Parameters:

  • chat (OllamaChat::Chat)

    the chat instance providing the config and confirm?



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ollama_chat/database/models/app_state.rb', line 60

def self.seed(chat)
  stored = get(:prompt_defaults_fingerprint)

  stored_fingerprint = stored&.dig('fingerprint')

  hashes    = compute_hashes(chat.config.prompts)
  fingerprint = fingerprint_from(hashes)

  return true if fingerprint == stored_fingerprint

  if stored&.dig('hashes')
    old     = stored['hashes']
    added   = hashes.keys - old.keys
    removed = old.keys - hashes.keys
    changed = hashes.select { |k, v| old[k] && old[k] != v }.keys

    STDERR.puts "\u26A0\uFE0F Shipped prompts changed since last boot:"
    changed.each { |k| STDERR.puts "  ~ #{k} (modified)" }
    added.each   { |k| STDERR.puts "  + #{k} (new)" }
    removed.each { |k| STDERR.puts "  - #{k} (removed)" }
  elsif !OllamaChat.test_mode?
    STDERR.puts "\u26A0\uFE0F First run — storing prompt fingerprints."
    store_fingerprint(fingerprint, hashes)
    return true
  end

  c = Term::ANSIColor
  prompt = <<~EOT.chomp << ' '
    #{c.bold{'Tip'}}: /prompt sync adopts shipped defaults into local config.
    Seen these changes? (y = stop nagging, n = ask again next boot) %s
  EOT
  if OllamaChat.test_mode? || chat.confirm?(prompt:, yes: /\Ay/i, timeout: 5)
    store_fingerprint(fingerprint, hashes)
  end

  true
end

.set(key, value) ⇒ OllamaChat::Database::Models::AppState

Creates or updates the JSON payload stored under the given key.

Parameters:

  • key (String, Symbol)

    the state key to write

  • value (Hash, nil)

    the JSON-serializable payload to store

Returns:



38
39
40
41
42
43
44
45
# File 'lib/ollama_chat/database/models/app_state.rb', line 38

def self.set(key, value)
  if found = where(key: key.to_s).first
    found.update(value:)
    found
  else
    create(key: key.to_s, value:)
  end
end

.store_fingerprint(fingerprint, hashes) ⇒ Object

Stores the fingerprint and hash map under the standard state key.

Parameters:

  • fingerprint (String)

    the XOR fingerprint

  • hashes (Hash{String => String})

    the per-prompt hash map



151
152
153
154
# File 'lib/ollama_chat/database/models/app_state.rb', line 151

def self.store_fingerprint(fingerprint, hashes)
  set(:prompt_defaults_fingerprint,
      { fingerprint:, hashes:, computed_at: Time.now.iso8601 })
end

Instance Method Details

#validateObject

Validates the application state entry.

Ensures that the key is present and unique.



18
19
20
21
22
# File 'lib/ollama_chat/database/models/app_state.rb', line 18

def validate
  super
  validates_presence :key
  validates_unique :key
end