ollama_chat 0.0.129 โ†’ 0.0.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +65 -0
  3. data/lib/ollama_chat/asr.rb +3 -3
  4. data/lib/ollama_chat/context_usage.rb +123 -0
  5. data/lib/ollama_chat/dialog.rb +24 -8
  6. data/lib/ollama_chat/information.rb +21 -58
  7. data/lib/ollama_chat/message_list.rb +2 -2
  8. data/lib/ollama_chat/ollama_chat_config/default_config.yml +1 -0
  9. data/lib/ollama_chat/rag_handling.rb +51 -13
  10. data/lib/ollama_chat/session_management.rb +1 -1
  11. data/lib/ollama_chat/tools/read_file.rb +4 -4
  12. data/lib/ollama_chat/version.rb +1 -1
  13. data/lib/ollama_chat.rb +1 -0
  14. data/ollama_chat.gemspec +5 -5
  15. data/spec/ollama_chat/asr_spec.rb +1 -4
  16. data/spec/ollama_chat/commands_spec.rb +3 -3
  17. data/spec/ollama_chat/context_usage_spec.rb +65 -0
  18. data/spec/ollama_chat/conversation_spec.rb +2 -2
  19. data/spec/ollama_chat/message_list_spec.rb +11 -8
  20. data/spec/ollama_chat/model_handling_spec.rb +2 -2
  21. data/spec/ollama_chat/parsing_spec.rb +5 -5
  22. data/spec/ollama_chat/prompt_handling_spec.rb +2 -2
  23. data/spec/ollama_chat/prompt_management_spec.rb +1 -1
  24. data/spec/ollama_chat/rag_handling_spec.rb +51 -14
  25. data/spec/ollama_chat/session_management_spec.rb +2 -2
  26. data/spec/ollama_chat/source_fetching_spec.rb +2 -2
  27. data/spec/ollama_chat/tools/delete_file_spec.rb +5 -5
  28. data/spec/ollama_chat/tools/list_directory_spec.rb +1 -1
  29. data/spec/ollama_chat/tools/move_file_spec.rb +10 -10
  30. data/spec/ollama_chat/tools/patch_file_spec.rb +2 -2
  31. data/spec/ollama_chat/tools/read_file_spec.rb +58 -22
  32. data/spec/ollama_chat/tools/resolve_tag_spec.rb +3 -4
  33. data/spec/ollama_chat/tools/write_file_spec.rb +2 -2
  34. data/spec/spec_helper.rb +23 -0
  35. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aae892b3b152a70e519a4d5a6268f046611c55d5b8ecfda66e2e4386a4681db
4
- data.tar.gz: '038e498781b238aef6f3d6ff595ec3beb50d00b7efd722f8636bcb87a273bbb2'
3
+ metadata.gz: 8e7543207de9d23c4731b5c8b1e768ad28559f57a4d767bfcabaaf25dc7f4541
4
+ data.tar.gz: 7a3a03b47e077f2c0b6fa7d042a910d8dab32d30bcd0253c0e706f105d0a4e86
5
5
  SHA512:
6
- metadata.gz: 740289c0e7fdf453718f120f26fa201f68c1f677777b71e440789df72bb14db52526026114adb06ce15a2ea93b78ce6e39a478bfe77a22c1657692e1a5162445
7
- data.tar.gz: 1b553c6648636b5088857f199a21cd87b86836c59df746fa50c504569be8609fd0181c3b233dda977a065cefdddfda1cd7ce7640a154a742bef8ab7392513346
6
+ metadata.gz: d585ff38e8daeb72ddccedf4eb0ef1711c35f843fb3028f73c00bad1df84045ba5228b82916dec834bb4c2d518a15c198a5f009eb5bc541bd46eca0513458542
7
+ data.tar.gz: 291074810f529cf30873946a74e52a83de45461808ce0cb8aff4f366ef17c8cb70a5fccefc1b98c1b6e0a234dd2d67419788e59ad5da22ea64d1c1e04982b171
data/CHANGES.md CHANGED
@@ -1,5 +1,70 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-10-10 v0.0.131
4
+
5
+ * Extracted ANSI, emoji, and formatting logic from `Information` into a new
6
+ `OllamaChat::ContextUsage` class, providing a `colored`, `plain`, `filled`,
7
+ and `percent` API.
8
+ * Converted `context_usage_colored`, `context_filled`, and
9
+ `context_usage_plain` into one-line delegators on `Information`.
10
+ * Removed `context_percentage` and `context_gauge` methods, absorbing their
11
+ logic into `ContextUsage` private helpers.
12
+ * Updated `MessageList#compact!` to capture `context_usage_colored` for both
13
+ before and after states, ensuring the compaction report renders
14
+ threshold-colored values.
15
+ * Changed the plain format to be percent-first: `27.9% ยท 73.1 KT of 262.1 KT`.
16
+ * Updated `session_management.rb` log data to use `context_usage_plain`.
17
+ * Added `spec/ollama_chat/context_usage_spec.rb` to pin green/yellow/red
18
+ thresholds, plain format, nil-context fallback, and fill ratio.
19
+ * Added per-letter emojis to clear menu prompts in `rag_handling.rb` (๐Ÿ” view,
20
+ ๐Ÿงน clear, ๐Ÿ”™ other) to improve scannability of single-keypress `confirm?`
21
+ letter menus.
22
+ * Updated `asr.rb` to use `%w[]` for `Tempfile.new` prefix/suffix arguments and
23
+ replaced `File.size(wav.path)` with `wav.size`.
24
+ * Updated `asr_spec.rb` to stub the `Tempfile#size` instance method instead of
25
+ the `File.size` class method.
26
+ * Migrated spec temporary paths to the `asset_tmp_path` helper across 15 spec
27
+ files, replacing hardcoded `tmp/` paths, `Dir.tmpdir` + `$$` PID suffixes,
28
+ hand-rolled `Tins::Token.new(bits: 128)` strings, and `Time.now.to_i` epoch
29
+ hacks.
30
+ * Converted `File.write(pathname, ...)` calls to `pathname.write(...)` in
31
+ `delete_file_spec`, `parsing_spec`, `resolve_tag_spec`, and
32
+ `prompt_handling_spec`.
33
+ * Updated `prompt_handling_spec` to use `asset_pathname('prompt_file.txt')` for
34
+ the `Pathname#write` idiom.
35
+ * Removed `.txt` suffix from `include` assertions in `move_file_spec` as
36
+ tokenized basenames no longer match the original extension.
37
+ * Added a collection chooser to `memory_restore`, allowing users to select
38
+ which collections from a dump file to restore, with `[ALL]` / `[DONE]`
39
+ support.
40
+ * Updated `memory_dump` to use `Set#sort` directly instead of `Set#to_a.sort`.
41
+ * Added a new `asset_tmp_path` helper in `spec_helper.rb` that generates
42
+ collision-free temp paths via `Tins::Token` (128-bit).
43
+ * Updated restore specs to include chooser stubs, cancel and selective-restore
44
+ cases, and switched temp files to `asset_tmp_path`.
45
+
46
+ ## 2026-10-08 v0.0.130
47
+
48
+ * Replace `ask?` with `confirm?` in `rag_handling.rb` for the `[v]iew` /
49
+ `[c]lear` prompts in `#clear_collection_tags` and
50
+ `#clear_collection_sources` to bypass `Reline` and keep command history
51
+ clean.
52
+ * Add `use_pager(force: true)` in the `#clear_collection_sources` view branch
53
+ to fix display.
54
+ * Rewrite `confirm?` YARD documentation in `dialog.rb` to clarify the
55
+ two-branch return contract (`yes: nil` โ†’ raw char, `yes:` matcher โ†’ matched
56
+ char or `nil`) and history-pollution rationale.
57
+ * Swap 7 `ask?` โ†’ `confirm?` stubs in `rag_handling_spec.rb`.
58
+ * Add `line_numbers: true` to the `read_file` tool block in
59
+ `default_config.yml`.
60
+ * Update `read_file.rb` to fall back to `tool_config.line_numbers?` when the
61
+ model omits the argument and interpolate the live default into the tool
62
+ description.
63
+ * Fix typo in description: `linenumbers` โ†’ `line numbers`.
64
+ * Update specs to extract `let :tool`, adjust `nil`-arg expectations for the
65
+ `1: ` prefix, checksum, and `line_count`, and add a new spec asserting raw
66
+ content when `tool_config.line_numbers` is stubbed to `false`.
67
+
3
68
  ## 2026-10-08 v0.0.129
4
69
 
5
70
  ### Features
@@ -26,8 +26,8 @@ module OllamaChat
26
26
  def transcribe(source_io, chat:, language: nil)
27
27
  asr_url = OC::OLLAMA::CHAT::ASR::URL
28
28
  asr_model = OC::OLLAMA::CHAT::ASR::MODEL
29
- input = Tempfile.new(['asr_in'])
30
- wav = Tempfile.new(['asr', '.wav'])
29
+ input = Tempfile.new(%w[ asr_in ])
30
+ wav = Tempfile.new(%w[ asr .wav ])
31
31
  begin
32
32
  IO.copy_stream(source_io, input)
33
33
  input.flush
@@ -41,7 +41,7 @@ module OllamaChat
41
41
  chat.feedback("ASR: #{executable} not found in PATH.", type: :warn)
42
42
  return
43
43
  end
44
- unless File.size(wav.path) > 0
44
+ unless wav.size > 0
45
45
  chat.feedback("ASR: #{executable} produced no output.", type: :warn)
46
46
  return
47
47
  end
@@ -0,0 +1,123 @@
1
+ require 'term/ansicolor'
2
+ require 'ollama_chat/utils/value_formatter'
3
+
4
+ module OllamaChat
5
+ # Renders the current context-window usage as colored or plain strings.
6
+ #
7
+ # Wraps an {OllamaChat::Chat} instance and concentrates the ANSI coloring,
8
+ # emoji, and token-formatting logic that was previously inlined in
9
+ # {OllamaChat::Information}. The color reflects how full the context is
10
+ # relative to the compaction `keep_recent` / `reserve` budgets:
11
+ #
12
+ # ๐ŸŸข green โ€” below `keep_recent` (plenty of room)
13
+ # ๐ŸŸก yellow โ€” between `keep_recent` and `reserve` (getting tight)
14
+ # ๐Ÿ”ด red โ€” above `reserve` (compaction imminent)
15
+ class ContextUsage
16
+ include Term::ANSIColor
17
+ include OllamaChat::Utils::ValueFormatter
18
+
19
+ EMOJI = { green: '๐ŸŸข', yellow: '๐ŸŸก', red: '๐Ÿ”ด' }.freeze
20
+
21
+ # @param chat [OllamaChat::Chat] the chat instance to read state from
22
+ def initialize(chat)
23
+ @chat = chat
24
+ end
25
+
26
+ # Colored usage string, e.g. `๐ŸŸข 73.1 KT of 262.1 KT (27.9%)`.
27
+ #
28
+ # Every value (used tokens, context limit, percentage) is painted with the
29
+ # threshold color; the surrounding text is bold. Returns a bold `n/a` when
30
+ # the context length cannot be determined.
31
+ #
32
+ # @return [String] the ANSI-colored usage string
33
+ def colored
34
+ ctx = @chat.current_context_length
35
+ return bold { 'n/a' } unless ctx
36
+
37
+ es = estimate
38
+ color = color_for(es.tokens, ctx)
39
+ percent = percent_for(es.tokens, ctx)
40
+
41
+ [
42
+ EMOJI[color], ' ',
43
+ bold { paint(color, percent) },
44
+ ' ยท ',
45
+ paint(color, es.tokens_formatted),
46
+ ' of ',
47
+ paint(color, format_tokens(ctx)),
48
+ ] * ''
49
+ end
50
+
51
+ # Plain (uncolored) usage string, e.g. `73.1 KT of 262.1 KT (27.9%)`.
52
+ #
53
+ # @return [String, nil] the usage string, or nil when the context length
54
+ # is unknown.
55
+ def plain
56
+ ctx = @chat.current_context_length
57
+ return nil unless ctx
58
+
59
+ es = estimate
60
+ '%s ยท %s of %s' % [
61
+ percent_for(es.tokens, ctx),
62
+ es.tokens_formatted,
63
+ format_tokens(ctx),
64
+ ]
65
+ end
66
+
67
+ # Fraction of the context window currently in use, clamped to 0.0โ€“1.0.
68
+ #
69
+ # @return [Float] the fill ratio (e.g. `0.279`)
70
+ def filled
71
+ es = estimate
72
+ (es.tokens.to_f / @chat.current_context_length).clamp(0..1).to_f
73
+ end
74
+
75
+ # Formatted percentage string, e.g. `27.9%`.
76
+ #
77
+ # @return [String] the percentage
78
+ def percent
79
+ format('%.1f%%', 100 * filled)
80
+ end
81
+
82
+ private
83
+
84
+ # @return [OllamaChat::TokenEstimator::Estimate] the compacted estimate
85
+ def estimate
86
+ @chat.messages.compacted_estimate_tokens
87
+ end
88
+
89
+ # @param tokens [Integer] used tokens
90
+ # @param ctx [Integer] context length
91
+ # @return [String] the formatted percentage
92
+ def percent_for(tokens, ctx)
93
+ format('%.1f%%', 100 * (tokens.to_f / ctx).clamp(0..1))
94
+ end
95
+
96
+ # Resolves the threshold color for a given fill level.
97
+ #
98
+ # @param tokens [Integer] used tokens
99
+ # @param ctx [Integer] context length
100
+ # @return [Symbol] one of :green, :yellow, :red
101
+ def color_for(tokens, ctx)
102
+ keep_recent = @chat.compact_ratio_tokens(:keep_recent, ctx)
103
+ reserve = @chat.compact_ratio_tokens(:reserve, ctx)
104
+ if tokens < keep_recent then :green
105
+ elsif tokens <= reserve then :yellow
106
+ else :red
107
+ end
108
+ end
109
+
110
+ # Paints a string with the given threshold color.
111
+ #
112
+ # @param color [Symbol] one of :green, :yellow, :red
113
+ # @param string [String] the text to colorize
114
+ # @return [String] the ANSI-colored string
115
+ def paint(color, string)
116
+ case color
117
+ when :green then green { string }
118
+ when :yellow then yellow { string }
119
+ else red { string }
120
+ end
121
+ end
122
+ end
123
+ end
@@ -29,21 +29,37 @@ module OllamaChat::Dialog
29
29
  prefill and Reline.pre_input_hook = old_pre_input_hook
30
30
  end
31
31
 
32
- # The confirm? method displays a prompt and reads a single character input
33
- # from the user in raw mode, then returns that character. This is best used
34
- # for confirmation prompts.
32
+ # The confirm? method displays a prompt and reads a **single keypress**
33
+ # from the user in raw mode (no Enter required), returning that character.
34
+ # Unlike {#ask?}, it does not go through Reline, so it never pollutes the
35
+ # command history โ€” making it the right tool for confirmation gates *and*
36
+ # terse single-key selections such as `[v]iew` / `[c]lear` menus.
37
+ #
38
+ # The returned value depends on whether a positive-response matcher (`yes`)
39
+ # is supplied:
40
+ #
41
+ # - `yes: nil` (default) โ€” the raw keypress is returned verbatim so the
42
+ # caller can dispatch on it (e.g. `case key; when /\Av/i; ...`); on
43
+ # timeout the `default` value is returned, and on interrupt (Ctrl-C)
44
+ # `nil` is returned.
45
+ # - `yes: <Regexp/Object>` โ€” the keypress is returned only when it
46
+ # satisfies `yes === keypress` (e.g. `yes: /\Ay/i`); any other keypress,
47
+ # or an interrupt, yields `nil`.
35
48
  #
36
49
  # @param prompt [String] the prompt to display to the user
37
50
  # @param timeout [Integer, nil] optional timeout in seconds; if nil, the
38
- # method blocks until input, if 0 the method immediately returns the default
39
- # value.
51
+ # method blocks until input, if 0 it immediately returns the `default`
52
+ # value without waiting.
40
53
  # @param default [Object, nil] value returned when the timeout expires
41
54
  # (defaults to `nil`)
42
- # @param yes [Object, nil] value that is considered a positive response
55
+ # @param yes [Object, nil] matcher (`#===`) that defines a positive
56
+ # response; when `nil` the raw keypress is returned for the caller to
57
+ # interpret (default: `nil`)
43
58
  # @param output [IO] the IO object to write the prompt and the answer to
44
59
  #
45
- # @return [Object] the character entered by the user, the `default` value
46
- # if a timeout occurs, or `nil` if the read is interrupted (e.g. Ctrl-C)
60
+ # @return [Object, nil] see the dispatch rules above: the keypress (or
61
+ # `default` on timeout / `nil` on interrupt) when `yes:` is nil, otherwise
62
+ # the keypress only if it matches `yes`, else `nil`.
47
63
  def confirm?(prompt:, timeout: nil, default: nil, yes: nil, output: STDOUT)
48
64
  return default if timeout&.zero?
49
65
  if prompt.include?('%s')
@@ -124,18 +124,15 @@ module OllamaChat::Information
124
124
  end
125
125
  end
126
126
 
127
- # The context_usage_colored method computes a summary string of the current
128
- # context window's token usage. It formats the compacted message
129
- # estimate, the maximum context length, and a visual gauge indicating
130
- # the percentage of the context that is currently filled.
127
+ # The context_usage_colored method returns a summary string of the current
128
+ # context window's token usage, with the used, maximum, and percentage
129
+ # values all ANSI-colored according to the compaction thresholds.
131
130
  #
132
- # @return [ String ] the formatted string representing context usage
131
+ # Delegates the presentation (emoji + color) to {ContextUsage}.
132
+ #
133
+ # @return [ String ] the formatted, colored string representing context usage
133
134
  def context_usage_colored
134
- '%s of %s (%s)' % [
135
- messages.compacted_estimate_tokens.tokens_formatted,
136
- format_tokens(current_context_length),
137
- context_gauge(context_percentage),
138
- ]
135
+ context_usage.colored
139
136
  end
140
137
 
141
138
  # Displays a detailed view of the current chat session state, including the
@@ -397,7 +394,7 @@ module OllamaChat::Information
397
394
  tools_support: tools_support.on? ? 'enabled' : 'disabled',
398
395
  voice: voice.on? ? 'enabled' : 'disabled',
399
396
  weekday: now.strftime('%A'),
400
- context_usage: ,
397
+ context_usage: context_usage_plain,
401
398
  conversation_length: ,
402
399
  }
403
400
  end
@@ -455,66 +452,32 @@ module OllamaChat::Information
455
452
 
456
453
  # Computes the fraction of the context window currently in use.
457
454
  #
458
- # Divides the estimated token count of the session's messages by the
459
- # effective context length, clamped to the range 0.0โ€“1.0.
455
+ # Delegates to {ContextUsage#filled}; clamped to 0.0โ€“1.0.
460
456
  #
461
457
  # @return [Float] the ratio of used context (e.g. `0.73` for 73%)
462
458
  def context_filled
463
- es = messages.compacted_estimate_tokens
464
- (es.tokens.to_f / current_context_length).clamp(0..1).to_f
465
- end
466
-
467
- # Formats the current context fill ratio as a percentage string.
468
- #
469
- # @return [String] the formatted percentage (e.g. `"64.0%"`)
470
- def context_percentage
471
- format('%.1f%%', 100 * context_filled)
459
+ context_usage.filled
472
460
  end
473
461
 
474
462
  # Formats the current context usage as a human-readable string.
475
463
  #
476
464
  # Returns a string like `"167.7 KT of 262.1 KT (64.0%)"` combining
477
465
  # the estimated tokens in use, the effective context length, and
478
- # the percentage (via +context_filled+).
466
+ # the percentage. Delegates to {ContextUsage#plain}.
479
467
  #
480
468
  # @return [String, nil] the formatted usage string, or +nil+ if
481
469
  # the context length cannot be determined.
482
- def context_usage
483
- if cl = current_context_length
484
- '%s of %s (%s)' % [
485
- messages.compacted_estimate_tokens.tokens_formatted,
486
- format_tokens(current_context_length),
487
- context_percentage,
488
- ]
489
- end
470
+ def context_usage_plain
471
+ context_usage.plain
490
472
  end
491
473
 
492
- # Wraps a percentage string in an ANSI color based on context usage
493
- # relative to compaction thresholds.
494
- #
495
- # Green: below `keep_recent` budget โ€” plenty of room.
496
- # Yellow: between `keep_recent` and `reserve` โ€” getting tight.
497
- # Red: above `reserve` โ€” compaction imminent.
498
- #
499
- # Falls back to bold if context length is unknown.
500
- #
501
- # @param percent_string [String] the formatted percentage to colorize
502
- # @return [String] the ANSI-colored percentage string
503
- def context_gauge(percent_string)
504
- tokens = messages.compacted_estimate_tokens.tokens
505
- ctx = current_context_length
506
- return bold { percent_string } unless ctx
507
-
508
- keep_recent = compact_ratio_tokens(:keep_recent, ctx)
509
- reserve = compact_ratio_tokens(:reserve, ctx)
510
- bold do
511
- if tokens < keep_recent
512
- '๐ŸŸข ' + green { percent_string }
513
- elsif tokens <= reserve
514
- '๐ŸŸก ' + yellow { percent_string }
515
- else
516
- '๐Ÿ”ด ' + red { percent_string }
517
- end
518
- end
474
+ private
475
+
476
+ # Lazily builds and caches the {ContextUsage} bound to this chat,
477
+ # which owns the ANSI/emoji presentation logic for context usage.
478
+ #
479
+ # @return [ContextUsage]
480
+ def context_usage
481
+ @context_usage ||= OllamaChat::ContextUsage.new(self)
519
482
  end
520
483
  end
@@ -164,7 +164,7 @@ class OllamaChat::MessageList
164
164
  cand_es = OllamaChat::TokenEstimator::Crude.new(
165
165
  candidates.sum { |m| m.content.to_s.bytesize }
166
166
  ).perform
167
- context_before = @chat.context_usage
167
+ context_before = @chat.context_usage_colored
168
168
  context_filled_before = @chat.context_filled
169
169
 
170
170
  @chat.log(:info, 'Compaction: starting', data: {
@@ -211,7 +211,7 @@ class OllamaChat::MessageList
211
211
 
212
212
  OllamaChat::Compaction::Result.new(
213
213
  context_before:,
214
- context_after: @chat.context_usage,
214
+ context_after: @chat.context_usage_colored,
215
215
  context_filled_before: ,
216
216
  context_filled_after: @chat.context_filled,
217
217
  candidates: candidates.size,
@@ -623,6 +623,7 @@ tools:
623
623
  default: true
624
624
  require_confirmation: false
625
625
  result_display_timeout: 3
626
+ line_numbers: true
626
627
  allowed:
627
628
  - './tmp'
628
629
  - './lib'
@@ -137,10 +137,10 @@ module OllamaChat::RAGHandling
137
137
  filename = Pathname.new(filename).expand_path
138
138
  should_overwrite?(filename) or return
139
139
 
140
- feedback("Dumping #{selected.size} collection(s): #{selected.to_a.sort.join(', ')}")
140
+ feedback("Dumping #{selected.size} collection(s): #{selected.sort.join(', ')}")
141
141
 
142
142
  records = []
143
- selected.to_a.sort.each do |col|
143
+ selected.sort.each do |col|
144
144
  switch_collection(col) do
145
145
  @documents.each_record do |r|
146
146
  records << {
@@ -155,7 +155,7 @@ module OllamaChat::RAGHandling
155
155
 
156
156
  OllamaChat::Utils::JSONJSONLIO.new(filename).write(collection: records)
157
157
  log(:info, 'Memory dumped',
158
- data: { collections: selected.to_a.sort, records: records.size, file: filename.to_s })
158
+ data: { collections: selected.sort, records: records.size, file: filename.to_s })
159
159
  feedback(
160
160
  "Dumped #{records.size} record(s) across #{selected.size} collection(s) to #{filename.to_s.inspect}.",
161
161
  type: :success
@@ -164,8 +164,10 @@ module OllamaChat::RAGHandling
164
164
 
165
165
  # Restores `memory-<persona>` collections from a JSONL dump file.
166
166
  #
167
- # Each line's `collection` field determines the target collection;
168
- # records are added with their original tag and source so timestamps
167
+ # Presents an accumulating chooser of all collections found in the file:
168
+ # pick individual collections (they drop out of the list once selected),
169
+ # `[ALL]` to grab everything remaining, or `[DONE]` to commit the restore.
170
+ # Records are added with their original tag and source so timestamps
169
171
  # and file associations survive.
170
172
  #
171
173
  # @param filename [String, nil] the input path (must end in .json/.jsonl);
@@ -184,19 +186,55 @@ module OllamaChat::RAGHandling
184
186
  entries = OllamaChat::Utils::JSONJSONLIO.new(filename).read.to_a
185
187
  return feedback("No records found in #{filename.to_s.inspect}.", type: :warn) if entries.empty?
186
188
 
189
+ collections = entries.group_by { |e| e['collection'] }.reject { _1.nil? }
190
+ return feedback('No collection field found in entries.', type: :warn) if collections.empty?
191
+
192
+ selected = Set.new
193
+ choose_with_state do
194
+ loop do
195
+ remaining = collections.keys - selected.to_a
196
+ choices = (remaining.empty? ? [] : ['[ALL]'] + remaining) + ['[DONE]']
197
+ choice = choose_entry(choices, prompt: 'Which memory collections to restore? %s')
198
+ case choice
199
+ when nil, '[DONE]'
200
+ break
201
+ when '[ALL]'
202
+ selected.merge(remaining)
203
+ break
204
+ else
205
+ selected.add(choice)
206
+ end
207
+ end
208
+ end
209
+
210
+ return feedback('Cancelled, no collections selected.', type: :cancel) if selected.empty?
211
+
212
+ selected = selected.sort
213
+
214
+ feedback("Restoring #{selected.size} collection(s): #{selected.join(', ')}")
215
+
187
216
  total = 0
188
- entries.group_by { |e| e['collection'] }.each do |col, group|
217
+ collections.slice(*selected).sort.each do |col, group|
189
218
  create_memory_collection(col)
190
219
  switch_collection(col) do
191
220
  group.each do |entry|
192
- @documents.add([entry['text']], tags: Array(entry['tags']), batch_size: 1, source: entry['source'])
221
+ @documents.add(
222
+ [entry['text']],
223
+ tags: Array(entry['tags']),
224
+ batch_size: 1,
225
+ source: entry['source']
226
+ )
193
227
  total += 1
194
228
  end
195
229
  end
196
230
  end
197
231
 
198
- log(:info, 'Memory restored', data: { file: filename.to_s, records: total })
199
- feedback("Restored #{total} record(s) from #{filename.to_s.inspect}.", type: :success)
232
+ log(:info, 'Memory restored',
233
+ data: { file: filename.to_s, collections: selected, records: total })
234
+ feedback(
235
+ "Restored #{total} record(s) across #{selected.size} collection(s) from #{filename.to_s.inspect}.",
236
+ type: :success
237
+ )
200
238
  end
201
239
 
202
240
  private
@@ -248,7 +286,7 @@ module OllamaChat::RAGHandling
248
286
  label = source ? "#{source} #{snippet}" : snippet
249
287
  feedback(label, type: :info)
250
288
  view_tag_records(tag, [record])
251
- ask?(prompt: " [c]lear / other to go back: ") =~ /\Ac/i or next
289
+ confirm?(prompt: " ๐Ÿงน[c]lear / ๐Ÿ”™other to go back: ") =~ /\Ac/i or next
252
290
  clear_tag_records(tag, source)
253
291
  else
254
292
  group_lines = []
@@ -270,7 +308,7 @@ module OllamaChat::RAGHandling
270
308
  )
271
309
  next if chosen.nil? || chosen == '[back]'
272
310
  src, recs = group_map[chosen]
273
- action = ask?(prompt: " [v]iew / [c]lear / other to go back: ")
311
+ action = confirm?(prompt: " ๐Ÿ”[v]iew / ๐Ÿงน[c]lear / ๐Ÿ”™other to go back: ")
274
312
  case action
275
313
  when /\Av/i
276
314
  view_tag_records(tag, recs)
@@ -350,11 +388,11 @@ module OllamaChat::RAGHandling
350
388
  break
351
389
  else
352
390
  source = display_map[chosen]
353
- action = ask?(prompt: " [v]iew / [c]lear / other to go back: ")
391
+ action = confirm?(prompt: " ๐Ÿ”[v]iew / ๐Ÿงน[c]lear / ๐Ÿ”™other to go back: ")
354
392
  case action
355
393
  when /\Av/i
356
394
  records = @documents.records(sources: [ source ])
357
- use_pager do |output|
395
+ use_pager(force: true) do |output|
358
396
  output.puts "#{bold{source}} (#{records.size} records)"
359
397
  output.puts
360
398
  records.each do |record|
@@ -17,7 +17,7 @@ module OllamaChat::SessionManagement
17
17
  log(:info, "Messages stored in session", data: {
18
18
  session_id: session.id,
19
19
  size: es.bytes_formatted,
20
- context_usage: ,
20
+ context_usage: context_usage_plain,
21
21
  messages: session.count_messages
22
22
  })
23
23
  self
@@ -28,7 +28,7 @@ class OllamaChat::Tools::ReadFile
28
28
  File reader โ€“ Returns raw text from path if itโ€™s within allowed
29
29
  directories. No side effects; useful for inspecting config or source
30
30
  files. You can optionally specify a line range to read. You can also
31
- enable prefixing with linenumbers; this is highly recommended (and
31
+ enable prefixing with line numbers; this is highly recommended (and
32
32
  practically mandatory) before calling `open_file_in_editor` to ensure
33
33
  the line range is precise.
34
34
  EOT
@@ -53,8 +53,8 @@ class OllamaChat::Tools::ReadFile
53
53
  line_numbers: Tool::Function::Parameters::Property.new(
54
54
  type: 'boolean',
55
55
  description: <<~EOT
56
- Whether to prefix each line with its line number,
57
- e.g. "666: The line contentโ€ฆ
56
+ Whether to prefix each line with its line number, e.g.
57
+ "666: The line contentโ€ฆ" (default: #{!!tool_config.line_numbers?})
58
58
  EOT
59
59
  )
60
60
  },
@@ -77,12 +77,12 @@ class OllamaChat::Tools::ReadFile
77
77
  # @return [String] an error message as a JSON string if the operation fails
78
78
  # @raise [JSON::ParserError] if the result cannot be serialized to JSON
79
79
  def execute(tool_call, **opts)
80
-
81
80
  args = tool_call.function.arguments
82
81
 
83
82
  start_line = args.start_line.full?
84
83
  end_line = args.end_line.full?
85
84
  line_numbers = args.line_numbers
85
+ line_numbers.nil? and line_numbers = !!tool_config.line_numbers?
86
86
 
87
87
  path = assert_valid_path(args.path, tool_config.allowed?, check: :file)
88
88
  full_content = path.read
@@ -1,6 +1,6 @@
1
1
  module OllamaChat
2
2
  # OllamaChat version
3
- VERSION = '0.0.129'
3
+ VERSION = '0.0.131'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/ollama_chat.rb CHANGED
@@ -128,6 +128,7 @@ require 'ollama_chat/source_fetching'
128
128
  require 'ollama_chat/web_searching'
129
129
  require 'ollama_chat/dialog'
130
130
  require 'ollama_chat/think_control'
131
+ require 'ollama_chat/context_usage'
131
132
  require 'ollama_chat/information'
132
133
  require 'ollama_chat/message_output'
133
134
  require 'ollama_chat/clipboard'