ollama_chat 0.0.116 → 0.0.118

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: beebfc1a29093b596fe008ba59b2f377922e2f7beeeb529ebed899fc374c6e02
4
- data.tar.gz: e704bce9b15101283e802a6010ee08c347cf94d8fcdc2ee785da3880debde736
3
+ metadata.gz: f5db2a60d43d3a57a30e3c33a16b9ba26e60807afd9ff6b74c0781af25fe2516
4
+ data.tar.gz: e670d0cfcbbcfa815e4b9a7f6e0ccb6093dc4986b736035aea3dc38b4bc646ab
5
5
  SHA512:
6
- metadata.gz: 7cab4e5d47d97dd215e058ab9960ad8801252a2ab950d3b30ce1e514fad30b22e87f917e3b1764592070f385fcfec748cc3b59398cadf8e3553f41e6ec48e9bb
7
- data.tar.gz: 0fb71bb0ab178cb1fed41ea2b257360fd19d3d180a2fc2f497d1372874d4665c3b82676ad8c056ab80a22e21e95deebb8afee8ef5f2809a07671e6cfb53ba5d7
6
+ metadata.gz: 8f7cb31ed0e38db5baca42a29076b3a01e3bae64d5a80d55584bd0ecdaaa41fd15eb814fc8b34ac55d2e73e4d767c52f305167cd8d2a7b32af4e9668f7d43706
7
+ data.tar.gz: a54ebe900c8b8a2c9e31ce3a4aded809f1b6e179679b2f2b4e3174f9e4ba5f6c976c8e7f1aaf3ae7efb8b8c8417f605b8c2522676eb0952f6d4b38165ac087af
data/CHANGES.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-09-05 v0.0.118
4
+
5
+ * Documented the upgrade workflow in the README, adding an "Upgrading"
6
+ section that covers the post-`gem update` flow: `/config diff` with
7
+ `:diffget`, reload, and `/prompt sync` for per-prompt drift resolution.
8
+ * Clarified that the local `config.yml` is a partial override requiring no
9
+ migration script.
10
+ * Improved the prompt drift nagging message in `app_state.rb#seed` by
11
+ replacing the previous prompt with a two-part message featuring a bold
12
+ `Tip:` line suggesting `/prompt sync` and a "Seen these changes?"
13
+ confirmation.
14
+ * Replaced literal `⚠️` emoji with `\u26A0\uFE0F` escapes for consistent
15
+ rendering and introduced `Term::ANSIColor` locally for the bold tip prefix.
16
+ * Added a `session.standup` template to `default_config.yml` defining a
17
+ six-section end-of-day document.
18
+ * Moved the compaction system prompt from `prompts.compaction.system` to
19
+ `prompts.system.compaction` and updated `compaction.rb` to call
20
+ `prompt(:compaction, context: 'system')`.
21
+ * Added `prompts.system.report`, a genre-neutral writer system prompt, and
22
+ updated `session_management.rb#report_session` to pass `system:` and
23
+ `think: true` to `generate` for report generation.
24
+ * Simplified `compaction_spec.rb` to resolve templates from
25
+ `chat.config.prompts.system.compaction` and added a `prompt(:report,
26
+ context: 'system')` expectation to `session_management_spec.rb`.
27
+ * Fixed the `get_url` summarizing policy by wrapping it in `Infobar.busy` and
28
+ `chat.generate` to ensure the LLM produces the summary.
29
+ * Added a `words` (integer) parameter to the `get_url` tool schema, forwarded
30
+ to `summarize_source` to control summary length.
31
+ * Changed `STDOUT.puts` to `infobar.puts` in `summarize_source` to prevent
32
+ garbling spinner output.
33
+ * Updated `get_url_spec.rb` to include `words: nil` in argument doubles,
34
+ reworked the summarizing test to expect `chat.generate`, and added a
35
+ `words: 50` forwarding test.
36
+ * Added `Infobar.busy` spinners with descriptive labels to LLM `generate`
37
+ calls in `personae_management.rb`, `prompt_management.rb`, and
38
+ `session_management.rb`.
39
+ * Modified `prompt_management.rb` to only call `edit_text(suggestions)` if
40
+ `suggestions.present?`.
41
+
42
+ ## 2026-09-03 v0.0.117
43
+
44
+ * Fixed `/favourite` subcommand completion rendering by replacing separate
45
+ `add`/`delete` and type arrays in `complete` with `.product`, ensuring each
46
+ subcommand renders as an explicit pair (e.g., `add model`, `delete
47
+ persona`) instead of being independently cross-multiplied by `arguments`.
48
+ * Added command categories to help output:
49
+ * Added `category(name)` and `current_category` class methods to
50
+ `command_concern.rb` for tracking the active category during
51
+ registration.
52
+ * Added `category:` keyword to `command` and `Command#initialize` with
53
+ `attr_reader :category`.
54
+ * Reworked `help_message` to sort commands by `[category, name]` and
55
+ insert bold full-width category header rows via `colspan: 4`.
56
+ * Added `extend Term::ANSIColor` to the `included` block for `bold{}`
57
+ access.
58
+ * Replaced `## Section` comments in `commands.rb` with `category
59
+ :Section` calls.
60
+ * Added two new categories: `Models` (`/model`, `/think`) and `Prompts`
61
+ (`/prompt`, `/system`, `/suggest`).
62
+ * Moved session-persisted commands (`/toggle`, `/tools`, `/voice`,
63
+ `/document_policy`, `/context_format`) under `category :Session`.
64
+ * Moved `/change_response` and `/conversation` under `category
65
+ :Conversation`.
66
+ * Reduced `Settings` to app-level commands only (`/config`,
67
+ `/favourite`).
68
+ * Made `report_session` a pure generator:
69
+ * Removed `&block` parameter and `block&.(result)` call from
70
+ `report_session`, eliminating the infobar side-effect that caused
71
+ double output.
72
+ * Simplified `report_conversation` to call `report_session(name:)`
73
+ without a block; display logic (pager or file write) is the sole
74
+ consumer of the returned string.
75
+ * Removed `@param block [Proc]` from `report_session` YARD docs.
76
+
3
77
  ## 2026-09-03 v0.0.116
4
78
 
5
79
  ### Added
data/README.md CHANGED
@@ -19,6 +19,39 @@ gem install ollama_chat
19
19
 
20
20
  in your terminal.
21
21
 
22
+ ## Upgrading
23
+
24
+ After `gem update ollama_chat`, the next launch will either
25
+ start cleanly or prompt you to fix the config:
26
+
27
+ 1. **Open the config diff.** Run `/config diff`, or wait for
28
+ the automatic `fix_config` prompt if the new version
29
+ introduced required keys. Either way, your `$DIFF_TOOL`
30
+ (default: `vimdiff`) opens with your local `config.yml`
31
+ on one side and the shipped `default_config.yml` on the
32
+ other.
33
+ 2. **Pull in the changes.** In vimdiff, use `:diffget` to
34
+ copy new or updated keys from the default into your local
35
+ file. Ignore keys you've intentionally overridden.
36
+ 3. **Reload.** Close the diff tool and answer `y` to the
37
+ "restart?" prompt.
38
+ 4. **Prompt drift.** If the new version changed any shipped
39
+ prompts, a fingerprint notice lists modified, added, and
40
+ removed prompts. To reconcile your stored prompts with
41
+ the new defaults, run `/prompt sync`. It diffs each
42
+ prompt against the shipped version and lets you decide
43
+ per prompt: adopt the new default, keep your local edit,
44
+ or review the change. After syncing, the fingerprint is
45
+ updated and the nagging stops.
46
+
47
+ If you simply want to dismiss the notice without
48
+ syncing, answer `y` to the boot prompt. The fingerprint
49
+ is stored and you won't be asked again.
50
+
51
+ Your local config is a partial override. Keys you don't
52
+ have fall through to the shipped defaults. No migration
53
+ script needed.
54
+
22
55
  ## Configuration
23
56
 
24
57
  ### Environment Variables
@@ -11,6 +11,20 @@ module OllamaChat::CommandConcern
11
11
  # `Command` instances.
12
12
  attr_accessor :commands
13
13
 
14
+ # @return [Symbol] the category currently in effect for new commands
15
+ def current_category
16
+ (@category || :Misc).to_sym
17
+ end
18
+
19
+ # Set the category that subsequent `command` registrations will be
20
+ # assigned to. Commands registered without an explicit `category:`
21
+ # keyword inherit the current category (defaults to `:Misc`).
22
+ #
23
+ # @param name [String, Symbol] the category name
24
+ def category(name)
25
+ @category = name.to_sym
26
+ end
27
+
14
28
  # Register a new command.
15
29
  #
16
30
  # @param name [String, Symbol] The primary name of the command.
@@ -24,17 +38,19 @@ module OllamaChat::CommandConcern
24
38
  # @param options [String, nil] A string describing command options
25
39
  # for help output.
26
40
  # @param help [String, nil] Short help text for the command.
41
+ # @param category [Symbol] Category for grouping in help output
42
+ # (defaults to `current_category`).
27
43
  # @yield [context] Block that receives a binding context for
28
44
  # execution. The block must be provided.
29
45
  #
30
46
  # @raise [ArgumentError] if a command with the same name already
31
47
  # exists or if no block is given.
32
- def command(name:, regexp:, complete: nil, optional: false, options: nil, help: nil, &block)
48
+ def command(name:, regexp:, complete: nil, optional: false, options: nil, help: nil, category: current_category, &block)
33
49
  name = name.to_sym
34
50
  commands.key?(name) and
35
51
  raise ArgumentError, "command #{name} already registered!"
36
- commands[name] =Command.new(
37
- name:, regexp:, complete:, optional:, options:, help:, &block
52
+ commands[name] = Command.new(
53
+ name:, regexp:, complete:, optional:, options:, help:, category:, &block
38
54
  )
39
55
  end
40
56
 
@@ -52,15 +68,18 @@ module OllamaChat::CommandConcern
52
68
  #
53
69
  # @param pattern [String, Regexp, nil] An optional pattern to filter
54
70
  # commands by their names.
55
- # @return [Terminal::Table] A table with columns CMD, SUBCMD, OPTS, HELP.
71
+ # @return [Terminal::Table] A table with columns CMD, SUBCMD, OPTS, HELP,
72
+ # with bold category header rows inserted between groups.
56
73
  def help_message(pattern = nil)
57
74
  table = Terminal::Table.new
58
75
  table.style = {
59
76
  all_separators: true,
60
77
  border: :unicode_round,
61
78
  }
62
- table.headings = %w[ CMD SUBCMD OPTS HELP ]
63
- commands.each_value do |command|
79
+ table.headings = %w[ CMD SUBCMD OPTS HELP ]
80
+ sorted_commands = self.commands.sort_by { [ _2.category, _2.name ] }.map(&:last)
81
+ cat = nil
82
+ sorted_commands.each do |command|
64
83
  command.help or next
65
84
  if pattern
66
85
  command.command_names.any? { _1 =~ pattern } or next
@@ -72,6 +91,10 @@ module OllamaChat::CommandConcern
72
91
  [ _1, (?﹡ unless command.optional?) ].compact.join
73
92
  }.sort.join(?\n)
74
93
  }
94
+ if cat != command.category
95
+ cat = command.category
96
+ table << [ { value: bold{cat}, colspan: 4 } ]
97
+ end
75
98
  table << [
76
99
  "%s" % command.command_names.map { ?/ + _1 }.join(?\n),
77
100
  '%s' % subcommands,
@@ -91,6 +114,8 @@ module OllamaChat::CommandConcern
91
114
  delegate :command_completions, to: self
92
115
 
93
116
  delegate :help_message, to: self
117
+
118
+ extend Term::ANSIColor
94
119
  end
95
120
 
96
121
  # Represents a registered command in the OllamaChat command DSL.
@@ -117,13 +142,14 @@ module OllamaChat::CommandConcern
117
142
  # @param optional [Boolean] Whether the command is optional.
118
143
  # @param options [String, nil] Options description.
119
144
  # @param help [String] Help text.
145
+ # @param category [Symbol] Category for grouping in help output.
120
146
  # @yield [context] Execution block.
121
147
  #
122
148
  # @raise [ArgumentError] if no block is given.
123
- def initialize(name:, regexp:, complete: nil, optional: false, options: nil, help:, &block)
149
+ def initialize(name:, regexp:, complete: nil, optional: false, options: nil, help:, category:, &block)
124
150
  block or raise ArgumentError, 'require &block'
125
- @name, @regexp, @optional, @options, @help, @block =
126
- name, regexp, optional, options, help, block
151
+ @name, @regexp, @optional, @options, @help, @category, @block =
152
+ name, regexp, optional, options, help, category, block
127
153
  @complete = Array(complete || name.to_s).map { Array(_1) }
128
154
  end
129
155
 
@@ -133,6 +159,9 @@ module OllamaChat::CommandConcern
133
159
  # @return [String] Help text for the command.
134
160
  attr_reader :help
135
161
 
162
+ # @return [Symbol] Category for grouping in help output.
163
+ attr_reader :category
164
+
136
165
  # @return [String, nil] Options description.
137
166
  attr_reader :options
138
167
 
@@ -22,7 +22,7 @@ require 'ollama_chat/command_concern'
22
22
  module OllamaChat::Commands
23
23
  include OllamaChat::CommandConcern
24
24
 
25
- ## Clipboard
25
+ category :Clipboard
26
26
 
27
27
  command(
28
28
  name: :copy,
@@ -58,7 +58,7 @@ module OllamaChat::Commands
58
58
  end
59
59
  end
60
60
 
61
- ## Settings
61
+ category :Settings
62
62
 
63
63
  command(
64
64
  name: :config,
@@ -85,14 +85,67 @@ module OllamaChat::Commands
85
85
  end
86
86
 
87
87
  command(
88
- name: :document_policy,
89
- regexp: %r(^/document policy$),
90
- complete: %w[ document policy ],
88
+ name: :favourite,
89
+ regexp: %r(^/favourite(?:\s+(add|delete))?(?:\s+(model|prompt|system|persona|suggest))$),
90
+ complete: [ 'favourite', %w[ add delete ].product(%w[ model prompt system persona suggest ]) ],
91
91
  help: <<~EOT
92
- 📜 Select a scanning policy for documents
92
+ \u2B50\uFE0F Manage favorites (add/delete models,
93
+ prompts, personae)
93
94
  EOT
94
- ) do
95
- document_policy.choose
95
+ ) do |subcommand, type|
96
+ case subcommand
97
+ when 'add'
98
+ add_favourite(type)
99
+ when 'delete'
100
+ delete_favourite(type)
101
+ end
102
+ :next
103
+ end
104
+
105
+ category :Session
106
+
107
+ command(
108
+ name: :session,
109
+ regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|delete|model options change|model options))?((?:\s+-(?:p\s*\w+))*)(?:\s+([^-].*))?$),
110
+ complete: [ 'session', %w[ change previous list new duplicate rename delete model\ options\ change model\ options ] ],
111
+ optional: true,
112
+ options: "[-p profile]\n[name]",
113
+ help: <<~EOT
114
+ 💬 Manage sessions:
115
+ - list/new/delete/rename/duplicate
116
+ - change [name]/previous
117
+ - model options/change
118
+ EOT
119
+ ) do |subcommand, opts, name|
120
+ case subcommand
121
+ when nil
122
+ info_session
123
+ when 'list'
124
+ list_sessions
125
+ when 'new'
126
+ set_new_session
127
+ when 'duplicate'
128
+ duplicate_session
129
+ when 'delete'
130
+ delete_session
131
+ when 'rename'
132
+ rename_session
133
+ when 'change'
134
+ change_session(name)
135
+ when 'model options'
136
+ edit_session_model_options
137
+ when 'model options change'
138
+ opts = go_command('p:', opts)
139
+ if profile = opts[?p] || choose_profile_for_model(@model)
140
+ copy_model_options_to_session(profile:)
141
+ end
142
+ when 'previous'
143
+ if prev = previous_session
144
+ change_session(prev.id)
145
+ else
146
+ STDOUT.puts "No previous session defined."
147
+ end
148
+ end
96
149
  :next
97
150
  end
98
151
 
@@ -132,123 +185,6 @@ module OllamaChat::Commands
132
185
  :next
133
186
  end
134
187
 
135
- command(
136
- name: :favourite,
137
- regexp: %r(^/favourite(?:\s+(add|delete))?(?:\s+(model|prompt|system|persona|suggest))$),
138
- complete: [ 'favourite', %w[ add delete ], %w[ model prompt system persona suggest ] ],
139
- help: <<~EOT
140
- \u2B50\uFE0F Manage favorites (add/delete models,
141
- prompts, personae)
142
- EOT
143
- ) do |subcommand, type|
144
- case subcommand
145
- when 'add'
146
- add_favourite(type)
147
- when 'delete'
148
- delete_favourite(type)
149
- end
150
- :next
151
- end
152
-
153
- command(
154
- name: :model,
155
- regexp: %r(^/model(?:\s+(change|options(?: copy| delete| export| import)?|options from session|options to session))?((?:\s+(?:-m|-p\s+[\S]+))*)$),
156
- complete: [ 'model', %w[ change options options\ copy options\ delete options\ export options\ import options\ from\ session options\ to\ session ] ],
157
- options: '[-m|-p pattern]',
158
- help: <<~EOT
159
- 🤖 Manage AI models & profiles:
160
- - change: Switch active model
161
- - options: Edit saved profile config
162
- - options copy: Copy profile from another model
163
- - options delete: Delete a saved profile
164
- - options export: Export ALL model profiles to JSON
165
- - options import: Selectively import profiles from JSON
166
- - options from session: Save live → Saved
167
- - options to session: Apply Saved → Live
168
- -m interactively choose a model
169
- -p PATTERN narrow file search for import
170
- EOT
171
- ) do |subcommand, opts|
172
- if subcommand == 'change'
173
- begin
174
- model = choose_model('', @model)
175
- profile = choose_profile_for_model(model) || 'default'
176
- use_model(model, profile:)
177
- rescue OllamaChat::UnknownModelError => e
178
- msg = "Caught #{e.class}: #{e}"
179
- log(:error, msg, data: { command: 'model', profile: }, warn: true)
180
- end
181
- next :next
182
- end
183
- opts = go_command('mp:', opts)
184
- model = opts[?m] ? choose_model('', @model) : @model
185
- case subcommand
186
- when 'options'
187
- profile = choose_profile_for_model(model, allow_new: true) or next :next
188
- edit_model_options(model, profile:)
189
- when 'options copy'
190
- copy_model_options_profile(model)
191
- when 'options delete'
192
- delete_model_options_profile(model)
193
- when 'options from session'
194
- profile = choose_profile_for_model(model) || 'default'
195
- copy_model_options_from_session(model, profile:)
196
- when 'options to session'
197
- profile = choose_profile_for_model(model) || 'default'
198
- copy_model_options_to_session(model, profile:)
199
- when 'options export'
200
- export_model_options
201
- when 'options import'
202
- pattern = opts[?p] || '**/*.json'
203
- filename = choose_filename(pattern) or next :next
204
- import_model_options(filename)
205
- end
206
- :next
207
- end
208
-
209
- command(
210
- name: :system,
211
- regexp: %r(^/system(?:\s+(change))?$),
212
- complete: [ 'system', %w[ change ] ],
213
- optional: true,
214
- help: <<~EOT,
215
- 🧬 Manage active system prompt (change)
216
- Note: Use '/prompt -c system' for managing
217
- (add, delete, edit, list, etc.) system templates.
218
- EOT
219
- ) do |subcommand, filename|
220
- case subcommand
221
- when 'change'
222
- change_system_prompt(@messages.system_name)
223
- @messages.show_system_prompt
224
- when nil
225
- @messages.show_system_prompt
226
- end
227
- :next
228
- end
229
-
230
- command(
231
- name: :think,
232
- regexp: %r(^/think$),
233
- help: <<~EOT
234
- 🧠 Configure model thinking mode
235
- EOT
236
- ) do
237
- think_mode.choose
238
- :next
239
- end
240
-
241
- command(
242
- name: :context_format,
243
- regexp: %r(^/context_format$),
244
- help: <<~EOT,
245
- 📐 Set context format (JSON|TOON)
246
- EOT
247
- ) do
248
- context_format.choose
249
- :next
250
- end
251
-
252
188
  command(
253
189
  name: :tools,
254
190
  regexp: %r(^/tools(?:\s+(on|off|enable|disable))?),
@@ -287,54 +223,31 @@ module OllamaChat::Commands
287
223
  :next
288
224
  end
289
225
 
290
- ## Session
291
-
292
226
  command(
293
- name: :session,
294
- regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|delete|model options change|model options))?((?:\s+-(?:p\s*\w+))*)(?:\s+([^-].*))?$),
295
- complete: [ 'session', %w[ change previous list new duplicate rename delete model\ options\ change model\ options ] ],
296
- optional: true,
297
- options: "[-p profile]\n[name]",
227
+ name: :document_policy,
228
+ regexp: %r(^/document policy$),
229
+ complete: %w[ document policy ],
298
230
  help: <<~EOT
299
- 💬 Manage sessions:
300
- - list/new/delete/rename/duplicate
301
- - change [name]/previous
302
- - model options/change
231
+ 📜 Select a scanning policy for documents
303
232
  EOT
304
- ) do |subcommand, opts, name|
305
- case subcommand
306
- when nil
307
- info_session
308
- when 'list'
309
- list_sessions
310
- when 'new'
311
- set_new_session
312
- when 'duplicate'
313
- duplicate_session
314
- when 'delete'
315
- delete_session
316
- when 'rename'
317
- rename_session
318
- when 'change'
319
- change_session(name)
320
- when 'model options'
321
- edit_session_model_options
322
- when 'model options change'
323
- opts = go_command('p:', opts)
324
- if profile = opts[?p] || choose_profile_for_model(@model)
325
- copy_model_options_to_session(profile:)
326
- end
327
- when 'previous'
328
- if prev = previous_session
329
- change_session(prev.id)
330
- else
331
- STDOUT.puts "No previous session defined."
332
- end
333
- end
233
+ ) do
234
+ document_policy.choose
235
+ :next
236
+ end
237
+
238
+ command(
239
+ name: :context_format,
240
+ regexp: %r(^/context_format$),
241
+ help: <<~EOT,
242
+ 📐 Set context format (JSON|TOON)
243
+ EOT
244
+ ) do
245
+ context_format.choose
334
246
  :next
335
247
  end
336
248
 
337
- ## Conversation
249
+
250
+ category :Conversation
338
251
 
339
252
  command(
340
253
  name: :list,
@@ -447,6 +360,77 @@ module OllamaChat::Commands
447
360
  content
448
361
  end
449
362
 
363
+ command(
364
+ name: :change_response,
365
+ regexp: %r(^/change response$),
366
+ complete: %w[ change response ],
367
+ help: <<~EOT,
368
+ ✏️ Edit last AI response in editor
369
+ EOT
370
+ ) do
371
+ change_response
372
+ :next
373
+ end
374
+
375
+ command(
376
+ name: :conversation,
377
+ regexp: %r(^/conversation\s+(clean|compact|save|load|summarize|report)((?:\s+-(?:[sf]|c))*)(?:\s+([^-].*\.jsonl?))?$),
378
+ complete: [ 'conversation', %w[ save load clean compact summarize report ] ],
379
+ options: '[-s|-f|-c] [FILENAME]',
380
+ help: <<~EOT
381
+ 💾 Manage conversation content:
382
+ - save/load: Export/import as .json or .jsonl
383
+ - clean: Remove tool content, images, thinking
384
+ - compact: Summarize old messages, keep recent
385
+ - summarize: Per-message narrative (-s sentence, -f save)
386
+ - report: Generate a session report document
387
+ EOT
388
+ ) do |subcommand,opts,path|
389
+ if %w[ save load ].include?(subcommand) && path.blank?
390
+ STDERR.puts "Require a path as argument to save/load!"
391
+ next :next
392
+ end
393
+ case subcommand
394
+ when 'save'
395
+ opts = go_command('c', opts.to_s)
396
+ save_conversation(path, clean: opts[?c])
397
+ when 'load'
398
+ load_conversation(path)
399
+ repair_group_uuids
400
+ when 'clean'
401
+ if confirm?(
402
+ prompt: '🔔 Clean tool content, images, and thinking from conversation? (y/n) ',
403
+ yes: /\Ay/i
404
+ )
405
+ then
406
+ messages.clean_messages!
407
+ session_sync
408
+ STDOUT.puts "Conversation cleaned."
409
+ else
410
+ STDOUT.puts 'Cancelled.'
411
+ end
412
+ when 'compact'
413
+ if confirm?(
414
+ prompt: '🔔 Compact conversation? Old messages will be summarized. (y/n) ',
415
+ yes: /\Ay/i
416
+ )
417
+ then
418
+ compact_with_retry
419
+ else
420
+ STDOUT.puts 'Cancelled.'
421
+ end
422
+ when 'summarize'
423
+ opts = go_command('sf', opts)
424
+ summarize_conversation(save: opts[?f], sentence: opts[?s])
425
+ when 'report'
426
+ opts = go_command('f', opts)
427
+ report_conversation(save: opts[?f])
428
+ end
429
+ :next
430
+ end
431
+
432
+ category :Prompts
433
+
450
434
  command(
451
435
  name: :prompt,
452
436
  regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|rename|sync|-e))?(\s+(?:-[ef]|-c\s+(?:\w+|\?)))?(?:\s+([^-].*))?$),
@@ -528,6 +512,27 @@ module OllamaChat::Commands
528
512
  :next
529
513
  end
530
514
 
515
+ command(
516
+ name: :system,
517
+ regexp: %r(^/system(?:\s+(change))?$),
518
+ complete: [ 'system', %w[ change ] ],
519
+ optional: true,
520
+ help: <<~EOT,
521
+ 🧬 Manage active system prompt (change)
522
+ Note: Use '/prompt -c system' for managing
523
+ (add, delete, edit, list, etc.) system templates.
524
+ EOT
525
+ ) do |subcommand, filename|
526
+ case subcommand
527
+ when 'change'
528
+ change_system_prompt(@messages.system_name)
529
+ @messages.show_system_prompt
530
+ when nil
531
+ @messages.show_system_prompt
532
+ end
533
+ :next
534
+ end
535
+
531
536
  command(
532
537
  name: :suggest,
533
538
  regexp: %r(^/suggest(\s+(?:-e))?$),
@@ -545,76 +550,76 @@ module OllamaChat::Commands
545
550
  :next
546
551
  end
547
552
 
548
- command(
549
- name: :change_response,
550
- regexp: %r(^/change response$),
551
- complete: %w[ change response ],
552
- help: <<~EOT,
553
- ✏️ Edit last AI response in editor
554
- EOT
555
- ) do
556
- change_response
557
- :next
558
- end
553
+ category :Models
559
554
 
560
555
  command(
561
- name: :conversation,
562
- regexp: %r(^/conversation\s+(clean|compact|save|load|summarize|report)((?:\s+-(?:[sf]|c))*)(?:\s+([^-].*\.jsonl?))?$),
563
- complete: [ 'conversation', %w[ save load clean compact summarize report ] ],
564
- options: '[-s|-f|-c] [FILENAME]',
556
+ name: :model,
557
+ regexp: %r(^/model(?:\s+(change|options(?: copy| delete| export| import)?|options from session|options to session))?((?:\s+(?:-m|-p\s+[\S]+))*)$),
558
+ complete: [ 'model', %w[ change options options\ copy options\ delete options\ export options\ import options\ from\ session options\ to\ session ] ],
559
+ options: '[-m|-p pattern]',
565
560
  help: <<~EOT
566
- 💾 Manage conversation content:
567
- - save/load: Export/import as .json or .jsonl
568
- - clean: Remove tool content, images, thinking
569
- - compact: Summarize old messages, keep recent
570
- - summarize: Per-message narrative (-s sentence, -f save)
571
- - report: Generate a session report document
561
+ 🤖 Manage AI models & profiles:
562
+ - change: Switch active model
563
+ - options: Edit saved profile config
564
+ - options copy: Copy profile from another model
565
+ - options delete: Delete a saved profile
566
+ - options export: Export ALL model profiles to JSON
567
+ - options import: Selectively import profiles from JSON
568
+ - options from session: Save live → Saved
569
+ - options to session: Apply Saved → Live
570
+ -m interactively choose a model
571
+ -p PATTERN narrow file search for import
572
572
  EOT
573
- ) do |subcommand,opts,path|
574
- if %w[ save load ].include?(subcommand) && path.blank?
575
- STDERR.puts "Require a path as argument to save/load!"
573
+ ) do |subcommand, opts|
574
+ if subcommand == 'change'
575
+ begin
576
+ model = choose_model('', @model)
577
+ profile = choose_profile_for_model(model) || 'default'
578
+ use_model(model, profile:)
579
+ rescue OllamaChat::UnknownModelError => e
580
+ msg = "Caught #{e.class}: #{e}"
581
+ log(:error, msg, data: { command: 'model', profile: }, warn: true)
582
+ end
576
583
  next :next
577
584
  end
585
+ opts = go_command('mp:', opts)
586
+ model = opts[?m] ? choose_model('', @model) : @model
578
587
  case subcommand
579
- when 'save'
580
- opts = go_command('c', opts.to_s)
581
- save_conversation(path, clean: opts[?c])
582
- when 'load'
583
- load_conversation(path)
584
- repair_group_uuids
585
- when 'clean'
586
- if confirm?(
587
- prompt: '🔔 Clean tool content, images, and thinking from conversation? (y/n) ',
588
- yes: /\Ay/i
589
- )
590
- then
591
- messages.clean_messages!
592
- session_sync
593
- STDOUT.puts "Conversation cleaned."
594
- else
595
- STDOUT.puts 'Cancelled.'
596
- end
597
- when 'compact'
598
- if confirm?(
599
- prompt: '🔔 Compact conversation? Old messages will be summarized. (y/n) ',
600
- yes: /\Ay/i
601
- )
602
- then
603
- compact_with_retry
604
- else
605
- STDOUT.puts 'Cancelled.'
606
- end
607
- when 'summarize'
608
- opts = go_command('sf', opts)
609
- summarize_conversation(save: opts[?f], sentence: opts[?s])
610
- when 'report'
611
- opts = go_command('f', opts)
612
- report_conversation(save: opts[?f])
588
+ when 'options'
589
+ profile = choose_profile_for_model(model, allow_new: true) or next :next
590
+ edit_model_options(model, profile:)
591
+ when 'options copy'
592
+ copy_model_options_profile(model)
593
+ when 'options delete'
594
+ delete_model_options_profile(model)
595
+ when 'options from session'
596
+ profile = choose_profile_for_model(model) || 'default'
597
+ copy_model_options_from_session(model, profile:)
598
+ when 'options to session'
599
+ profile = choose_profile_for_model(model) || 'default'
600
+ copy_model_options_to_session(model, profile:)
601
+ when 'options export'
602
+ export_model_options
603
+ when 'options import'
604
+ pattern = opts[?p] || '**/*.json'
605
+ filename = choose_filename(pattern) or next :next
606
+ import_model_options(filename)
613
607
  end
614
608
  :next
615
609
  end
616
610
 
617
- ## Collection
611
+ command(
612
+ name: :think,
613
+ regexp: %r(^/think$),
614
+ help: <<~EOT
615
+ 🧠 Configure model thinking mode
616
+ EOT
617
+ ) do
618
+ think_mode.choose
619
+ :next
620
+ end
621
+
622
+ category :Collection
618
623
 
619
624
  command(
620
625
  name: :collection,
@@ -666,7 +671,7 @@ module OllamaChat::Commands
666
671
  :next
667
672
  end
668
673
 
669
- ## Persona
674
+ category :Persona
670
675
 
671
676
  command(
672
677
  name: :persona,
@@ -788,7 +793,7 @@ module OllamaChat::Commands
788
793
  end
789
794
  end
790
795
 
791
- ## Input
796
+ category :Input
792
797
 
793
798
  command(
794
799
  name: :compose,
@@ -930,7 +935,7 @@ module OllamaChat::Commands
930
935
  end
931
936
  end
932
937
 
933
- ## Output
938
+ category :Output
934
939
 
935
940
  command(
936
941
  name: :pipe,
@@ -977,7 +982,7 @@ module OllamaChat::Commands
977
982
  :next
978
983
  end
979
984
 
980
- ## Actions
985
+ category :Actions
981
986
 
982
987
  command(
983
988
  name: :reconnect,
@@ -1003,7 +1008,7 @@ module OllamaChat::Commands
1003
1008
  quit_app
1004
1009
  end
1005
1010
 
1006
- ## Information
1011
+ category :Information
1007
1012
 
1008
1013
  command(
1009
1014
  name: :info,
@@ -1072,5 +1077,4 @@ module OllamaChat::Commands
1072
1077
  STDOUT.puts "Type /quit to quit."
1073
1078
  :next
1074
1079
  end
1075
-
1076
1080
  end
@@ -191,7 +191,7 @@ module OllamaChat::Compaction
191
191
  prompt = prompt(:summarize, context: 'compaction').to_s % {
192
192
  previous:, groups:
193
193
  }
194
- system = prompt(:system, context: 'compaction').to_s
194
+ system = prompt(:compaction, context: 'system').to_s
195
195
 
196
196
  es = OllamaChat::TokenEstimator.estimate(prompt)
197
197
  log(:info, "Compaction: sending prompt " \
@@ -73,19 +73,20 @@ class OllamaChat::Database::Models::AppState < Sequel::Model(OllamaChat::DB)
73
73
  removed = old.keys - hashes.keys
74
74
  changed = hashes.select { |k, v| old[k] && old[k] != v }.keys
75
75
 
76
- STDERR.puts "⚠️ Shipped prompts changed since last boot:"
76
+ STDERR.puts "\u26A0\uFE0F Shipped prompts changed since last boot:"
77
77
  changed.each { |k| STDERR.puts " ~ #{k} (modified)" }
78
78
  added.each { |k| STDERR.puts " + #{k} (new)" }
79
79
  removed.each { |k| STDERR.puts " - #{k} (removed)" }
80
80
  elsif !OllamaChat.test_mode?
81
- STDERR.puts '⚠️ First run — storing prompt fingerprints.'
81
+ STDERR.puts "\u26A0\uFE0F First run — storing prompt fingerprints."
82
82
  store_fingerprint(fingerprint, hashes)
83
83
  return true
84
84
  end
85
85
 
86
+ c = Term::ANSIColor
86
87
  prompt = <<~EOT.chomp << ' '
87
- Keep local prompts (stop nagging)?
88
- Consider adopting via /prompt sync or just ignore? (y/n) %s
88
+ #{c.bold{'Tip'}}: /prompt sync adopts shipped defaults into local config.
89
+ Seen these changes? (y = stop nagging, n = ask again next boot) %s
89
90
  EOT
90
91
  if OllamaChat.test_mode? || chat.confirm?(prompt:, yes: /\Ay/i, timeout: 5)
91
92
  store_fingerprint(fingerprint, hashes)
@@ -190,12 +190,40 @@ prompts:
190
190
 
191
191
  Write a narrative retelling of the roleplay session
192
192
  above. Your story MUST:
193
+
193
194
  - Capture the emotional arc and key plot beats.
194
195
  - Stay in the established setting and character voices.
195
196
  - Use vivid, evocative prose; show, don't tell.
196
197
  - Highlight turning points, revelations, and character
197
198
  growth.
198
199
  - Read it like a short story chapter, not a log.
200
+ standup: |
201
+ %{content}
202
+
203
+ Write a document for an end-of-session snapshot. It captures what was
204
+ accomplished, which decisions were made and why, and what happens next —
205
+ the same alignment a Scrum standup provides, but written down so no
206
+ context is lost between sessions.
207
+
208
+ Your document MUST contain these exact sections:
209
+
210
+ 1. ## Progress Log — bulleted list of completed tasks with relative file
211
+ paths. 2. ## External References — all URLs, API docs, or GitHub links
212
+ discussed during the session. 3. ## Current Mental Model — current focus,
213
+ existing blockers, and the immediate next step. 4. ## Technical Decision
214
+ Log — rationale for chosen implementations vs. rejected alternatives
215
+ (table). 5. ## Pending Roadmap — Critical → Important → Future Polish. 6.
216
+ ## Quick-Start Pointers — 2–3 files or links the next session must read
217
+ #first.
218
+
219
+ Constraints:
220
+ - Use relative Markdown paths for file references.
221
+ - Document *why* decisions were made, not just *what*.
222
+ - Do not restate what the conversation records verbatim.
223
+ - Professional, precise, technical tone.
224
+
225
+ Output only the standup document. No preamble, no
226
+ postamble, no meta-commentary.
199
227
  suggest:
200
228
  coding: |
201
229
  ## Context
@@ -366,12 +394,6 @@ prompts:
366
394
  Output only the 9 suggestions as plain text, each separated by one empty line.
367
395
  No other text.
368
396
  compaction:
369
- system: |
370
- You are a context summarizer for a chat application.
371
- Your sole task is to produce a concise, factual narrative
372
- summary of the conversation provided. Be precise, omit
373
- speculation, and follow the formatting instructions in the
374
- user prompt exactly. Output only the summary text.
375
397
  summarize: |
376
398
  previous_summary:
377
399
  %{previous}
@@ -408,6 +430,20 @@ prompts:
408
430
 
409
431
  %{runtime_info}
410
432
  assistant: You are a helpful assistant.
433
+ compaction: |
434
+ You are a context summarizer for a chat application.
435
+ Your sole task is to produce a concise, factual narrative
436
+ summary of the conversation provided. Be precise, omit
437
+ speculation, and follow the formatting instructions in the
438
+ user prompt exactly. Output only the summary text.
439
+ report: |
440
+ You are a writer. The user prompt below
441
+ specifies the document to produce: its
442
+ structure, tone, and constraints. Follow
443
+ it exactly. Output only the final document
444
+ — no preamble, no postamble, no commentary,
445
+ no reference to yourself or the act of
446
+ writing.
411
447
  voice:
412
448
  handler: OllamaChat::Say
413
449
  enabled: false
@@ -597,12 +597,18 @@ module OllamaChat::PersonaeManagement
597
597
  # @param persona_name [String] the name of the character to replace {{char}} with
598
598
  # @return [String] the resulting structured Markdown persona profile
599
599
  def convert_json_character_to_markdown(character, persona_name)
600
- generate(
601
- prompt: prompt(:persona_architect).to_s % {
602
- character:,
603
- persona_template: prompt(:persona).to_s
604
- }
605
- ).gsub(/{{user}}/i, '%{user}').gsub(/{{char}}/i, persona_name)
600
+ Infobar.busy(
601
+ label: 'Converting character…',
602
+ frames: :braille7,
603
+ output: STDOUT,
604
+ ) do
605
+ generate(
606
+ prompt: prompt(:persona_architect).to_s % {
607
+ character:,
608
+ persona_template: prompt(:persona).to_s
609
+ }
610
+ ).gsub(/{{user}}/i, '%{user}').gsub(/{{char}}/i, persona_name)
611
+ end
606
612
  end
607
613
 
608
614
  # Interactively exports a persona profile to a specified file.
@@ -346,10 +346,16 @@ module OllamaChat::PromptManagement
346
346
  full_prompt = template % { history:, instruction: }
347
347
 
348
348
  # Execute a silent chat oneshot call (doesn't add to history)
349
- suggestions = generate(prompt: full_prompt).full? or return
349
+ suggestions = Infobar.busy(
350
+ label: 'Creating suggestions…',
351
+ frames: :braille7,
352
+ output: STDOUT,
353
+ ) do
354
+ generate(prompt: full_prompt)
355
+ end
350
356
 
351
357
  # Pass the AI's suggestions through the editor for final refinement
352
- edit_text(suggestions)
358
+ edit_text(suggestions) if suggestions.present?
353
359
  end
354
360
 
355
361
  # Lists all prompt templates in the database, indicating which are defaults
@@ -425,9 +425,8 @@ module OllamaChat::SessionManagement
425
425
  # stays bounded even after multiple compaction rounds.
426
426
  #
427
427
  # @param name [String, nil] specific template name (skips chooser)
428
- # @param block [Proc] a block to handle the report content
429
428
  # @return [String, nil] the report or nil if empty
430
- def report_session(name: nil, &block)
429
+ def report_session(name: nil)
431
430
  content = messages.compacted_messages.inject('') do |c, message|
432
431
  message.content.present? or next c
433
432
  sender = sender_name_displayed(message)
@@ -445,9 +444,15 @@ module OllamaChat::SessionManagement
445
444
  end
446
445
  template or return
447
446
 
448
- result = generate(prompt: template.to_s % { content: })
449
- block&.(result)
450
- result
447
+ system = prompt(:report, context: 'system').to_s
448
+
449
+ Infobar.busy(
450
+ label: 'Generating session report…',
451
+ frames: :braille7,
452
+ output: STDOUT,
453
+ ) do
454
+ generate(system:, prompt: template.to_s % { content: }, think: true)
455
+ end
451
456
  end
452
457
 
453
458
  # Generates a report and displays or saves the result.
@@ -458,9 +463,7 @@ module OllamaChat::SessionManagement
458
463
  if save
459
464
  filename = ask_for_filename?(action: 'for report') or return
460
465
  should_overwrite?(filename) or return
461
- result = report_session(name:) do |content|
462
- infobar.puts kramdown_ansi_parse(content)
463
- end
466
+ result = report_session(name:)
464
467
  if result.full?
465
468
  filename.write(result)
466
469
  STDOUT.puts "File successfully written."
@@ -468,9 +471,7 @@ module OllamaChat::SessionManagement
468
471
  STDERR.puts "Nothing to report!"
469
472
  end
470
473
  else
471
- result = report_session(name:) do |content|
472
- infobar.puts kramdown_ansi_parse(content) << ?\n
473
- end
474
+ result = report_session(name:)
474
475
  if result.full?
475
476
  use_pager do |output|
476
477
  output.puts kramdown_ansi_parse(result)
@@ -492,11 +493,17 @@ module OllamaChat::SessionManagement
492
493
  c << "%s: %s\n\n" % [ sender_name, message.content ]
493
494
  end
494
495
  prompt = prompt(:session_title).to_s % { length:, content: }
495
- generate(prompt:).full? do |name|
496
- name = name.
497
- gsub(/(\A(\s|[^A-Za-z])+|(\s|[^A-Za-z])+\z)/m, '').
498
- gsub(/\s+/, ' ')
499
- Kramdown::ANSI::Width.truncate(name, length:)
496
+ Infobar.busy(
497
+ label: 'Naming session…',
498
+ frames: :braille7,
499
+ output: STDOUT,
500
+ ) do
501
+ generate(prompt:).full? do |name|
502
+ name = name.
503
+ gsub(/(\A(\s|[^A-Za-z])+|(\s|[^A-Za-z])+\z)/m, '').
504
+ gsub(/\s+/, ' ')
505
+ Kramdown::ANSI::Width.truncate(name, length:)
506
+ end
500
507
  end
501
508
  end
502
509
 
@@ -144,7 +144,7 @@ module OllamaChat::SourceFetching
144
144
  # @param words [Integer, nil] The target number of words for the summary (defaults to 100)
145
145
  # @return [String, nil] The formatted summary message or nil if content is empty or cannot be processed
146
146
  def summarize_source(source_io, source, words: nil)
147
- STDOUT.puts "Summarizing #{italic { source_io&.content_type }} document #{source.to_s.inspect} now."
147
+ infobar.puts "Summarizing #{italic { source_io&.content_type }} document #{source.to_s.inspect} now."
148
148
  log(:info, "Source summarized", data: { source: source.to_s, content_type: source_io&.content_type, words: })
149
149
  words = words.to_i
150
150
  words < 1 and words = 100
@@ -52,6 +52,13 @@ class OllamaChat::Tools::GetURL
52
52
  - 'summarizing': Returns a condensed summary of the content.
53
53
  EOT
54
54
  ),
55
+ words: Tool::Function::Parameters::Property.new(
56
+ type: 'integer',
57
+ description: <<~EOT,
58
+ Target word count for the summary (only applies when
59
+ document_policy is 'summarizing'). Defaults to 100.
60
+ EOT
61
+ ),
55
62
  },
56
63
  required: %w[url]
57
64
  )
@@ -75,6 +82,7 @@ class OllamaChat::Tools::GetURL
75
82
  args = tool_call.function.arguments
76
83
  url = args.url.to_s
77
84
  document_policy = args.document_policy.full? || 'ignoring'
85
+ words = args.words.to_i
78
86
 
79
87
  allowed_schemes = Array(config.tools.functions.get_url.schemes?).map(&:to_s)
80
88
 
@@ -99,7 +107,13 @@ class OllamaChat::Tools::GetURL
99
107
  when 'embedding'
100
108
  content = chat.embed_source(source_io, source)
101
109
  when 'summarizing'
102
- content = chat.summarize_source(source_io, source)
110
+ content = Infobar.busy(
111
+ label: 'Summarizing…',
112
+ frames: :braille7,
113
+ output: STDOUT,
114
+ ) do
115
+ chat.generate(prompt: chat.summarize_source(source_io, source, words:))
116
+ end
103
117
  else
104
118
  message = "Invalid document policy #{document_policy.inspect} used."
105
119
  end
@@ -1,6 +1,6 @@
1
1
  module OllamaChat
2
2
  # OllamaChat version
3
- VERSION = '0.0.116'
3
+ VERSION = '0.0.118'
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/ollama_chat.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: ollama_chat 0.0.116 ruby lib
2
+ # stub: ollama_chat 0.0.118 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "ollama_chat".freeze
6
- s.version = "0.0.116".freeze
6
+ s.version = "0.0.118".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -9,31 +9,11 @@ describe OllamaChat::Compaction do
9
9
 
10
10
  connect_to_ollama_server
11
11
 
12
- let :summarize_template do
13
- chat.config.prompts.compaction.summarize
14
- end
15
-
16
12
  let :system_template do
17
- chat.config.prompts.compaction.system
18
- end
19
-
20
- let :assemble_template do
21
- chat.config.prompts.compaction.assemble
13
+ chat.config.prompts.system.compaction
22
14
  end
23
15
 
24
16
  before do
25
- allow(chat).to receive(:prompt)
26
- .with(:summarize, context: 'compaction')
27
- .and_return(summarize_template)
28
-
29
- allow(chat).to receive(:prompt)
30
- .with(:system, context: 'compaction')
31
- .and_return(system_template)
32
-
33
- allow(chat).to receive(:prompt)
34
- .with(:assemble, context: 'compaction')
35
- .and_return(assemble_template)
36
-
37
17
  allow(chat).to receive(:log)
38
18
  end
39
19
 
@@ -352,10 +352,8 @@ describe OllamaChat::SessionManagement do
352
352
  expect(chat).to receive(:choose_prompt).and_return(double(to_s: '%{content}'))
353
353
  expect(chat).to receive(:generate).and_return('Generated report')
354
354
 
355
- results = []
356
- result = chat.report_session { |c| results << c }
355
+ result = chat.report_session
357
356
  expect(result).to eq('Generated report')
358
- expect(results).to eq(['Generated report'])
359
357
  end
360
358
 
361
359
  it 'returns nil when no messages have content' do
@@ -368,6 +366,8 @@ describe OllamaChat::SessionManagement do
368
366
  expect(chat).to receive(:sender_name_displayed).and_return('User')
369
367
  expect(chat).to receive(:prompt).with('coding_brief', context: 'session')
370
368
  .and_return(double(to_s: '%{content}'))
369
+ expect(chat).to receive(:prompt).with(:report, context: 'system')
370
+ .and_return(double(to_s: '%{content}'))
371
371
  expect(chat).to receive(:generate).and_return('Brief')
372
372
  expect(chat.report_session(name: 'coding_brief')).to eq('Brief')
373
373
  end
@@ -26,7 +26,8 @@ describe OllamaChat::Tools::GetURL do
26
26
  name: 'get_url',
27
27
  arguments: double(
28
28
  url: 'https://www.example.com/foo',
29
- document_policy: nil
29
+ document_policy: nil,
30
+ words: nil
30
31
  )
31
32
  )
32
33
  )
@@ -55,7 +56,8 @@ describe OllamaChat::Tools::GetURL do
55
56
  name: 'get_url',
56
57
  arguments: double(
57
58
  url:,
58
- document_policy: nil
59
+ document_policy: nil,
60
+ words: nil
59
61
  )
60
62
  )
61
63
  )
@@ -81,10 +83,11 @@ describe OllamaChat::Tools::GetURL do
81
83
  'ToolCall',
82
84
  function: double(
83
85
  name: 'get_url',
84
- arguments: double(
85
- url: 'https://www.example.com/foo',
86
- document_policy: nil,
87
- )
86
+ arguments: double(
87
+ url: 'https://www.example.com/foo',
88
+ document_policy: nil,
89
+ words: nil,
90
+ )
88
91
  )
89
92
  )
90
93
 
@@ -111,7 +114,7 @@ describe OllamaChat::Tools::GetURL do
111
114
  end
112
115
 
113
116
  it 'handles the "ignoring" policy' do
114
- args = double(url:, document_policy: 'ignoring')
117
+ args = double(url:, document_policy: 'ignoring', words: nil)
115
118
  tool_call = double(function: double(arguments: args))
116
119
 
117
120
  result = described_class.new.execute(tool_call, chat:)
@@ -120,7 +123,7 @@ describe OllamaChat::Tools::GetURL do
120
123
  end
121
124
 
122
125
  it 'handles the "importing" policy' do
123
- args = double(url:, document_policy: 'importing')
126
+ args = double(url:, document_policy: 'importing', words: nil)
124
127
  tool_call = double(function: double(arguments: args))
125
128
  expect(chat).to receive(:import_source).with(source_io, URI.parse(url)).and_return('imported content')
126
129
 
@@ -130,7 +133,7 @@ describe OllamaChat::Tools::GetURL do
130
133
  end
131
134
 
132
135
  it 'handles the "embedding" policy' do
133
- args = double(url:, document_policy: 'embedding')
136
+ args = double(url:, document_policy: 'embedding', words: nil)
134
137
  tool_call = double(function: double(arguments: args))
135
138
  expect(chat).to receive(:embed_source).with(source_io, URI.parse(url)).and_return('embedded content')
136
139
 
@@ -140,17 +143,29 @@ describe OllamaChat::Tools::GetURL do
140
143
  end
141
144
 
142
145
  it 'handles the "summarizing" policy' do
143
- args = double(url:, document_policy: 'summarizing')
146
+ args = double(url:, document_policy: 'summarizing', words: nil)
144
147
  tool_call = double(function: double(arguments: args))
145
- expect(chat).to receive(:summarize_source).with(source_io, URI.parse(url)).and_return('summarized content')
148
+ expect(chat).to receive(:summarize_source).with(source_io, URI.parse(url), words: 0).and_return('summarize prompt')
149
+ expect(chat).to receive(:generate).with(prompt: 'summarize prompt').and_return('summarized content')
146
150
 
147
151
  result = described_class.new.execute(tool_call, chat:)
148
152
  json = json_object(result)
149
153
  expect(json.content).to eq('summarized content')
150
154
  end
151
155
 
156
+ it 'handles the "summarizing" policy with custom words' do
157
+ args = double(url:, document_policy: 'summarizing', words: 50)
158
+ tool_call = double(function: double(arguments: args))
159
+ expect(chat).to receive(:summarize_source).with(source_io, URI.parse(url), words: 50).and_return('summarize prompt 50')
160
+ expect(chat).to receive(:generate).with(prompt: 'summarize prompt 50').and_return('short summary')
161
+
162
+ result = described_class.new.execute(tool_call, chat:)
163
+ json = json_object(result)
164
+ expect(json.content).to eq('short summary')
165
+ end
166
+
152
167
  it 'handles an invalid policy' do
153
- args = double(url:, document_policy: 'chaos_mode')
168
+ args = double(url:, document_policy: 'chaos_mode', words: nil)
154
169
  tool_call = double(function: double(arguments: args))
155
170
 
156
171
  result = described_class.new.execute(tool_call, chat:)
@@ -167,7 +182,7 @@ describe OllamaChat::Tools::GetURL do
167
182
  expect(chat).to receive(:fetch_source).and_yield(source_io)
168
183
  expect(chat).to receive(:add_image).with(chat.images, source_io, URI.parse(url))
169
184
 
170
- args = double(url:, document_policy: 'ignoring')
185
+ args = double(url:, document_policy: 'ignoring', words: nil)
171
186
  tool_call = double(function: double(arguments: args))
172
187
 
173
188
  result = described_class.new.execute(tool_call, chat:)
@@ -179,7 +194,7 @@ describe OllamaChat::Tools::GetURL do
179
194
  source_io = double('SourceIO', content_type: double(media_type: 'video'))
180
195
  expect(chat).to receive(:fetch_source).and_yield(source_io)
181
196
 
182
- args = double(url:, document_policy: 'ignoring')
197
+ args = double(url:, document_policy: 'ignoring', words: nil)
183
198
  tool_call = double(function: double(arguments: args))
184
199
 
185
200
  result = described_class.new.execute(tool_call, chat:)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ollama_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.116
4
+ version: 0.0.118
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank