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.
- checksums.yaml +4 -4
- data/CHANGES.md +65 -0
- data/lib/ollama_chat/asr.rb +3 -3
- data/lib/ollama_chat/context_usage.rb +123 -0
- data/lib/ollama_chat/dialog.rb +24 -8
- data/lib/ollama_chat/information.rb +21 -58
- data/lib/ollama_chat/message_list.rb +2 -2
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +1 -0
- data/lib/ollama_chat/rag_handling.rb +51 -13
- data/lib/ollama_chat/session_management.rb +1 -1
- data/lib/ollama_chat/tools/read_file.rb +4 -4
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +1 -0
- data/ollama_chat.gemspec +5 -5
- data/spec/ollama_chat/asr_spec.rb +1 -4
- data/spec/ollama_chat/commands_spec.rb +3 -3
- data/spec/ollama_chat/context_usage_spec.rb +65 -0
- data/spec/ollama_chat/conversation_spec.rb +2 -2
- data/spec/ollama_chat/message_list_spec.rb +11 -8
- data/spec/ollama_chat/model_handling_spec.rb +2 -2
- data/spec/ollama_chat/parsing_spec.rb +5 -5
- data/spec/ollama_chat/prompt_handling_spec.rb +2 -2
- data/spec/ollama_chat/prompt_management_spec.rb +1 -1
- data/spec/ollama_chat/rag_handling_spec.rb +51 -14
- data/spec/ollama_chat/session_management_spec.rb +2 -2
- data/spec/ollama_chat/source_fetching_spec.rb +2 -2
- data/spec/ollama_chat/tools/delete_file_spec.rb +5 -5
- data/spec/ollama_chat/tools/list_directory_spec.rb +1 -1
- data/spec/ollama_chat/tools/move_file_spec.rb +10 -10
- data/spec/ollama_chat/tools/patch_file_spec.rb +2 -2
- data/spec/ollama_chat/tools/read_file_spec.rb +58 -22
- data/spec/ollama_chat/tools/resolve_tag_spec.rb +3 -4
- data/spec/ollama_chat/tools/write_file_spec.rb +2 -2
- data/spec/spec_helper.rb +23 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e7543207de9d23c4731b5c8b1e768ad28559f57a4d767bfcabaaf25dc7f4541
|
|
4
|
+
data.tar.gz: 7a3a03b47e077f2c0b6fa7d042a910d8dab32d30bcd0253c0e706f105d0a4e86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/ollama_chat/asr.rb
CHANGED
|
@@ -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([
|
|
30
|
-
wav = Tempfile.new([
|
|
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
|
|
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
|
data/lib/ollama_chat/dialog.rb
CHANGED
|
@@ -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
|
|
33
|
-
# from the user in raw mode,
|
|
34
|
-
#
|
|
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
|
|
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]
|
|
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
|
|
46
|
-
#
|
|
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
|
|
128
|
-
# context window's token usage
|
|
129
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
483
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
#
|
|
497
|
-
#
|
|
498
|
-
|
|
499
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
@@ -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.
|
|
140
|
+
feedback("Dumping #{selected.size} collection(s): #{selected.sort.join(', ')}")
|
|
141
141
|
|
|
142
142
|
records = []
|
|
143
|
-
selected.
|
|
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.
|
|
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
|
-
#
|
|
168
|
-
#
|
|
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
|
-
|
|
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(
|
|
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',
|
|
199
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
data/lib/ollama_chat/version.rb
CHANGED
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'
|