ollama_chat 0.0.103 → 0.0.105

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: e723f48c6459c1ab6aa79f1acaf19a1bab5484f7c36e75c3491cef88deba0d92
4
- data.tar.gz: 71e979fa1419bb82abd7f9d0dd44b70aa8576509a7735b3e1b2abee4f0a53f81
3
+ metadata.gz: cbf0c7c0df08fa3ef8a15a9c62b7e092d035924c071302a4f37e01776dec2504
4
+ data.tar.gz: '0996355cc3d0324936394a174aaeeee8b67b950d5306cf320fa22a70eef9ea0d'
5
5
  SHA512:
6
- metadata.gz: 8c018d0417e676f8a9d598df4459443704da4f2c75559cab4e9b2f1f4034ea40807fc6672e656a8eacef29aec0454df8ec19f1259727592b68c0467b1f68a2fb
7
- data.tar.gz: 8c8e31bd6bdbfaf5617a32e237bc6c7f975d4d3e9827c9c6761906b509a7108f7ec5971c463ea70779b15b1ba368c7202af3aa641b478946647c48a2d6d18481
6
+ metadata.gz: 879adc039769558c938911909dbfdfc9625d6b4774cd398d8b39b4ebeb7055d7f7eb1fdaa1c26caf79d7fab3330c57c95b1169927e34d6909518e5f30f1c9d74
7
+ data.tar.gz: 7fbe2ad8b0f887cab6d83ece7efe118fd0189e1957fdbd5a40725200729b7e4d93e9c018c1a41cc8fc933dd2cee453a3a7b9090a7e647422314175d567e5496c
data/CHANGES.md CHANGED
@@ -1,5 +1,74 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-08-04 v0.0.105
4
+
5
+ ### New Features
6
+
7
+ - **Collection Management**: Introduced the
8
+ `OllamaChat::Database::Models::Collection` model and a corresponding `Sequel`
9
+ migration for persistent storage. Added interactive CLI commands under
10
+ `/collection` including `new`, `edit`, `delete`, and `update all`.
11
+ - **Model Profile Copying**: Added an `options copy` subcommand to allow users
12
+ to interactively copy configuration profiles between models, including
13
+ implementation of `copy_model_options_profile`.
14
+ - **Interactive Prompt Contexts**: Implemented `choose_prompt_context` in
15
+ `prompt_management.rb`, allowing the `/prompt` command to dynamically query
16
+ and select contexts via a new `-c ?` flag.
17
+
18
+ ### Enhancements
19
+
20
+ - **Model Options Matching**: Improved profile matching by replacing default
21
+ overwrite prompts with comprehensive checks against all saved `ModelOptions`
22
+ profiles using `ask_and_send(:symbolize_keys_recursive)`. Added pager display
23
+ for available profiles via `use_pager` and the ability to switch profiles via
24
+ `choose_profile_for_model`.
25
+ - **Collection Synchronization**: Enhanced `list_collections` with descriptions
26
+ and paging, and implemented a `Collection.sync` method to backfill database
27
+ records during startup.
28
+ - **Command Refactoring**: Renamed `show_session` to `info_session` within the
29
+ `Commands` module for increased verbosity when no subcommand is provided.
30
+
31
+ ### Bug Fixes & Maintenance
32
+
33
+ - **Documentation**:
34
+ - Clarified that the `checksum` for the `patch_file` tool must be an 8 hex
35
+ digit long CRC32.
36
+ - Added YARD `@!attribute` documentation for `links` and `history` in the
37
+ `Session` model.
38
+ - **Code Cleanup**:
39
+ - Replaced `.exists?` and `.first` dataset checks with `.present?` in
40
+ `model_handling.rb` and `session_management.rb`.
41
+ - Removed deprecated CLI flags `-c`, `-C`, `-D`, `-M`, and `-E` in favor of
42
+ interactive management.
43
+ - Added an `after_destroy` hook to the `Collection` model to purge
44
+ corresponding `Documentrix` vector data.
45
+
46
+ ## 2026-07-31 v0.0.104
47
+
48
+ ### Changed
49
+
50
+ #### Added
51
+
52
+ - Enabled interactive profile creation with duplicate validation by supporting
53
+ the `allow_new` keyword argument in `choose_profile_for_model`.
54
+
55
+ #### Changed
56
+
57
+ - Standardized API naming conventions by renaming the `offer_new_session`
58
+ parameter to `allow_new` in `session_management.rb` and `choose_session`.
59
+ - Updated `commands.rb` to pass `allow_new: true` to `choose_profile_for_model`
60
+ during `/model options` and implemented an early return on profile selection
61
+ cancellation.
62
+ - Renamed the `OllamaChat::Tools::RetrieveDocumentSnippets` class to
63
+ `SearchKnowledge`.
64
+ - Updated tool registration, default configuration, and descriptions for
65
+ `search_knowledge` to reference knowledge collections.
66
+
67
+ #### Fixed
68
+
69
+ - Adjusted `commands_spec.rb` test cases to reflect updated method signatures
70
+ and expected behavior for profile selection.
71
+
3
72
  ## 2026-07-30 v0.0.103
4
73
 
5
74
  ### Added
data/README.md CHANGED
@@ -83,11 +83,6 @@ Usage: ollama_chat [OPTIONS]
83
83
  -n create a new session
84
84
  -u URL the ollama base url, OLLAMA_URL
85
85
  -m MODEL the ollama model to chat with, OLLAMA_CHAT_MODEL, ?selector
86
- -c CHAT a saved chat conversation to load
87
- -C COLLECTION name of the collection used in this conversation
88
- -D DOCUMENT load document and add to embeddings collection (multiple)
89
- -M use (empty) MemoryCache for this chat session
90
- -E disable embeddings for this chat session
91
86
  -S open a socket to receive input from ollama_chat_send
92
87
  -V display the current version number and quit
93
88
  -h this help
@@ -420,10 +415,10 @@ context, manipulate files, and retrieve external information.
420
415
  | **Web/External** | `search_web`, `get_url`, `browse`, `get_rfc`, `get_cve`, `get_endoflife`, `get_ghr`, `get_jira_issue` | Access the internet, fetch specific URLs, and look up technical standards. |
421
416
  | **System/Util** | `get_time`, `get_location`, `get_current_weather`, `generate_password`, `compute_bmi`, `roll_dice` | General utility functions for time, location, and simple calculations. |
422
417
  | **Editor/Clip** | `copy_to_clipboard`, `paste_from_clipboard`, `paste_into_editor`, `open_file_in_editor` | Bridge the gap between the chat and the system clipboard or editor. |
423
- | **Knowledge** | `retrieve_document_snippets`, `file_context` | Semantic search for specific snippets vs. broad retrieval of structured project context. |
418
+ | **Knowledge** | `search_knowledge`, `file_context` | Semantic search for specific snippets vs. broad retrieval of structured project context. |
424
419
  | **Multimodal** | `generate_image` | Generate images via a local ComfyUI server. |
425
420
 
426
- ***Note on Knowledge Tools**: Use `retrieve_document_snippets` for precise,
421
+ ***Note on Knowledge Tools**: Use `search_knowledge` for precise,
427
422
  low-token semantic discovery and `file_context` for a comprehensive view of
428
423
  modules or patterns. Be cautious with broad patterns in `file_context`, as
429
424
  importing too many files can exceed the LLM's context window.*
@@ -101,7 +101,7 @@ class OllamaChat::Chat
101
101
  # @raise [RuntimeError] If the Ollama API version is less than 0.9.0, indicating
102
102
  # incompatibility with required API features
103
103
  def initialize(argv: ARGV.dup)
104
- @opts = go 'f:u:m:c:C:D:l:nMESVh', argv
104
+ @opts = go 'f:u:m:l:nSVh', argv
105
105
  @opts[?h] and exit usage
106
106
  @opts[?V] and exit version
107
107
  @ollama_chat_config = OllamaChat::OllamaChatConfig.new(@opts[?f])
@@ -112,14 +112,8 @@ class OllamaChat::Chat
112
112
  setup_switches
113
113
  setup_state_selectors(config)
114
114
  connect_ollama
115
- if conversation_file = @opts[?c]
116
- messages.load_conversation(conversation_file)
117
- else
118
- messages.read_conversation_jsonl(session.messages.to_s)
119
- end
120
- repair_group_uuids
121
- embedding_enabled.set(config.embedding.enabled && !@opts[?E])
122
- @documents = setup_documents
115
+ @documents = setup_documents
116
+ models::Collection.sync(self)
123
117
  @cache = setup_cache
124
118
  @images = []
125
119
  @kramdown_ansi_styles = configure_kramdown_ansi_styles
@@ -221,7 +215,6 @@ class OllamaChat::Chat
221
215
  # @return [ Symbol ] the collection name symbol
222
216
  def initial_collection
223
217
  (
224
- @opts[?C] ||
225
218
  session&.current_collection.full? ||
226
219
  config.embedding.collection.full? ||
227
220
  :default
@@ -639,10 +632,6 @@ class OllamaChat::Chat
639
632
  redis_url: config.redis.documents.url?,
640
633
  debug:
641
634
  )
642
-
643
- document_list = @opts[?D].to_a
644
- add_documents_from_argv(document_list)
645
- @documents
646
635
  else
647
636
  NULL
648
637
  end
@@ -139,12 +139,13 @@ module OllamaChat::Commands
139
139
 
140
140
  command(
141
141
  name: :model,
142
- regexp: %r(^/model(?:\s+(change|options|options from session|options to session))?((?:\s+(?:-m))*)$),
143
- complete: [ 'model', %w[ change options options\ from\ session options\ to\ session ] ],
142
+ regexp: %r(^/model(?:\s+(change|options(?: copy)?|options from session|options to session))?((?:\s+(?:-m))*)$),
143
+ complete: [ 'model', %w[ change options options\ copy options\ from\ session options\ to\ session ] ],
144
144
  help: <<~EOT
145
145
  🤖 Manage AI models & profiles:
146
146
  - change: Switch active model
147
147
  - options: Edit saved profile config
148
+ - options copy: Copy profile from another model
148
149
  - options from session: Save live → Saved
149
150
  - options to session: Apply Saved → Live
150
151
  -m interactively choose a model
@@ -163,13 +164,17 @@ module OllamaChat::Commands
163
164
  end
164
165
  opts = go_command('m', opts)
165
166
  model = opts[?m] ? choose_model('', @model) : @model
166
- profile = choose_profile_for_model(model) || 'default'
167
167
  case subcommand
168
168
  when 'options'
169
+ profile = choose_profile_for_model(model, allow_new: true) or next :next
169
170
  edit_model_options(model, profile:)
171
+ when 'options copy'
172
+ copy_model_options_profile(model)
170
173
  when 'options from session'
174
+ profile = choose_profile_for_model(model) || 'default'
171
175
  copy_model_options_from_session(model, profile:)
172
176
  when 'options to session'
177
+ profile = choose_profile_for_model(model) || 'default'
173
178
  copy_model_options_to_session(model, profile:)
174
179
  end
175
180
  :next
@@ -274,7 +279,7 @@ module OllamaChat::Commands
274
279
  ) do |subcommand, opts, name|
275
280
  case subcommand
276
281
  when nil
277
- show_session
282
+ info_session
278
283
  when 'list'
279
284
  list_sessions
280
285
  when 'new'
@@ -455,7 +460,7 @@ module OllamaChat::Commands
455
460
 
456
461
  command(
457
462
  name: :prompt,
458
- regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|-e))?(\s+(?:-[ef]|-c\s+\w+))?(?:\s+([^-].*))?$),
463
+ regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|-e))?(\s+(?:-[ef]|-c\s+(?:\w+|\?)))?(?:\s+([^-].*))?$),
459
464
  complete: [ 'prompt', %w[ edit info add delete list duplicate import export reset ] ],
460
465
  optional: true,
461
466
  options: '[-c CONTEXT|-e|-f]',
@@ -463,42 +468,61 @@ module OllamaChat::Commands
463
468
  📝 Manage prompt templates:
464
469
  Subcommands: edit, info, add, delete, list,
465
470
  duplicate, import, export, reset.
466
- Options: -c [context], -e (edit next)
471
+ Options: -c [context]
472
+ (? for interactive in /prompt),
473
+ -e (edit next)
467
474
  EOT
468
475
  ) do |subcommand, opts, filename|
469
476
  opts = go_command('fc:', opts)
477
+ context = if subcommand.nil? || subcommand == '-e'
478
+ if opts[?c] == ??
479
+ choose_prompt_context
480
+ else
481
+ opts[?c] || 'prompt'
482
+ end
483
+ else
484
+ opts[?c] || choose_prompt_context
485
+ end
486
+ unless context
487
+ STDOUT.puts 'Cancelled.'
488
+ next :next
489
+ end
490
+
470
491
  case subcommand
471
492
  when 'add'
472
- add_new_prompt(context: opts[?c])
493
+ add_new_prompt(context:)
473
494
  when 'delete'
474
- choose_and_delete_prompt(context: opts[?c], force: opts[?f])
495
+ choose_and_delete_prompt(context:, force: opts[?f])
475
496
  when 'edit'
476
- choose_and_edit_prompt(context: opts[?c])
497
+ choose_and_edit_prompt(context:)
477
498
  when 'list'
478
- list_prompts(context: opts[?c])
499
+ list_prompts(context:)
479
500
  when 'duplicate'
480
- duplicate_prompt(context: opts[?c])
501
+ duplicate_prompt(context:)
481
502
  when 'import'
482
- import_prompt(filename, context: opts[?c])
503
+ import_prompt(filename, context:)
483
504
  when 'export'
484
- export_prompt(context: opts[?c])
505
+ export_prompt(context:)
485
506
  when 'info'
486
- info_prompt(context: opts[?c])
507
+ info_prompt(context:)
487
508
  when 'reset'
488
509
  if prompt = choose_prompt(
489
510
  default: true,
490
- context: opts[?c],
511
+ context:,
491
512
  prompt: 'Which prompt needs to be restored to its origin? %s'
492
513
  )
493
514
  then
494
- if reset_prompt_to_default(prompt.name, context: opts[?c])
515
+ if reset_prompt_to_default(prompt.name, context:)
495
516
  STDOUT.puts "Reset prompt #{bold{prompt.name}} to default."
496
517
  else
497
518
  STDOUT.puts "No default value found for prompt #{bold{prompt.name}}."
498
519
  end
499
520
  end
500
521
  when nil, '-e'
501
- if prompt = choose_prompt(prompt: 'Which template shall guide the next response? %s').full?(&:to_s)
522
+ if prompt = choose_prompt(
523
+ prompt: 'Which template shall guide the next response? %s',
524
+ context:
525
+ ).full?(&:to_s)
502
526
  if subcommand
503
527
  prompt = edit_text(prompt)
504
528
  next prompt
@@ -580,13 +604,17 @@ module OllamaChat::Commands
580
604
 
581
605
  command(
582
606
  name: :collection,
583
- regexp: %r(^/collection(?:\s+(change|clear|list|rename|update))?$),
584
- complete: [ 'collection', %w[ change clear list rename update ] ],
607
+ regexp: %r(^/collection(?:\s+(change|clear|list|rename|update(?: all)?|new|edit|delete))?$),
608
+ complete: [ 'collection', %w[ change clear list rename update update\ all new edit delete ] ],
585
609
  optional: true,
586
610
  help: <<~EOT
587
611
  📚 Manage RAG collections:
588
612
  - change/clear/list/rename
589
613
  - update: Re-index modified docs
614
+ - update all: Re-index all collections
615
+ - new: Interactively create a new collection
616
+ - edit: Interactively update an existing collection
617
+ - delete: Permanently remove a collection
590
618
  - (no subcommand): Show stats
591
619
  EOT
592
620
  ) do |subcommand|
@@ -599,11 +627,25 @@ module OllamaChat::Commands
599
627
  list_collections
600
628
  when 'rename'
601
629
  rename_collection(collection)
630
+ when 'update all'
631
+ results = ''
632
+ all_collections.pluck(:name).each do |collection|
633
+ STDOUT.puts "📝 Updating collection #{collection.inspect}… "
634
+ results << update_collection(collection) << ?\n
635
+ STDOUT.puts "✅ Done."
636
+ end
637
+ results.full? and next results
602
638
  when 'update'
603
- if results = update_collection
639
+ if results = update_collection(collection)
604
640
  disable_content_parsing
605
641
  next results
606
642
  end
643
+ when 'new'
644
+ create_collection
645
+ when 'edit'
646
+ edit_collection
647
+ when 'delete'
648
+ delete_collection
607
649
  when nil
608
650
  collection_stats
609
651
  end
@@ -0,0 +1,12 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table :collections do
4
+ primary_key :id
5
+ String :name, null: false, unique: true
6
+ Text :description, null: false
7
+ Text :patterns # JSON-encoded array of globs
8
+ DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP, null: false
9
+ DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP, null: false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,81 @@
1
+ # Represents a document collection stored in the database, allowing for
2
+ # dynamic management of documentrix collections.
3
+ #
4
+ # This model stores collections with a name, description, and patterns
5
+ # for automatic file discovery and synchronization.
6
+ class OllamaChat::Database::Models::Collection < Sequel::Model(OllamaChat::DB)
7
+ include OllamaChat::Database::Duplicatable
8
+
9
+ plugin :timestamps, update_on_create: true
10
+ plugin :serialization, :json, :patterns
11
+ plugin :validation_helpers
12
+
13
+ # Validates the collection.
14
+ #
15
+ # Ensures that the `name` is present and unique.
16
+ def validate
17
+ super
18
+ validates_presence :name
19
+ validates_unique :name
20
+ validates_presence :description
21
+ end
22
+
23
+ # Sets the chat instance associated with this collection.
24
+ #
25
+ # This allows the collection to interact with the chat's document
26
+ # store during lifecycle events, such as cleanup upon destruction.
27
+ #
28
+ # @param chat [OllamaChat::Chat] the chat instance
29
+ attr_writer :chat
30
+
31
+ # Hook to clean up the corresponding Documentrix collection when
32
+ # the database record is destroyed.
33
+ #
34
+ # This ensures that we don't leave orphaned vector data in the
35
+ # Documentrix engine when the underlying collection record is removed.
36
+ def after_destroy
37
+ super
38
+ @chat or return
39
+ @chat.switch_collection(name) do
40
+ @chat.documents.clear
41
+ end
42
+ true
43
+ end
44
+
45
+ # Synchronizes the database records with the Documentrix collections.
46
+ #
47
+ # This method iterates through the configured collections in the chat's
48
+ # document store and ensures that every collection has a corresponding
49
+ # record in the database. If a collection exists in Documentrix but not
50
+ # in the database, a new record is created with a default description.
51
+ #
52
+ # @param chat [OllamaChat::Chat] the chat instance providing the configuration
53
+ def self.sync(chat)
54
+ chat.documents.collections.each do |name|
55
+ where(name: name.to_s).first and next
56
+ create(
57
+ name: name.to_s,
58
+ description: "TODO: Describe collection #{name}",
59
+ patterns: []
60
+ )
61
+ end
62
+ end
63
+
64
+ # @!attribute [v] id
65
+ # @return [Integer] The primary key for the collection entry.
66
+ #
67
+ # @!attribute [v] name
68
+ # @return [String] The name of the collection (matches documentrix name).
69
+ #
70
+ # @!attribute [v] description
71
+ # @return [String, nil] A description of the collection.
72
+ #
73
+ # @!attribute [v] patterns
74
+ # @return [Array, nil] An array of glob patterns for file discovery.
75
+ #
76
+ # @!attribute [v] created_at
77
+ # @return [Time, nil] The timestamp when the collection was created.
78
+ #
79
+ # @!attribute [v] updated_at
80
+ # @return [Time, nil] The timestamp of the last update to the collection.
81
+ end
@@ -52,7 +52,7 @@ class OllamaChat::Database::Models::Prompt < Sequel::Model(OllamaChat::DB)
52
52
  # database when the underlying prompt is removed.
53
53
  def after_destroy
54
54
  super
55
- OllamaChat::Database::Models::Favourite.where(context:, name:).destroy
55
+ models::Favourite.where(context:, name:).destroy
56
56
  end
57
57
 
58
58
  # Seeds the prompt table from the provided chat configuration.
@@ -137,6 +137,12 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
137
137
  # @!attribute [v] messages
138
138
  # @return [String] The full conversation history, stored in JSONL format.
139
139
  #
140
+ # @!attribute [v] links
141
+ # @return [String] A text field storing links associated with the session.
142
+ #
143
+ # @!attribute [v] history
144
+ # @return [String] A text field storing the history for the session.
145
+ #
140
146
  # @!attribute [v] created_at
141
147
  # @return [Time, nil] The timestamp when the session was created.
142
148
  #
@@ -72,6 +72,7 @@ module OllamaChat::Information
72
72
  #
73
73
  # @param output [IO] the output stream to write the message to
74
74
  def collection_stats(output: STDOUT)
75
+ col = database_collection?(collection)
75
76
  length = (Tins::Terminal.cols - 10).clamp(0..)
76
77
  wrapped_tags = Kramdown::ANSI::Width.
77
78
  wrap(@documents.tags.to_a.join(', '), length:).
@@ -79,6 +80,7 @@ module OllamaChat::Information
79
80
  output.puts <<~EOT
80
81
  Current Collection
81
82
  Name: #{bold{collection}}
83
+ Patterns: #{italic{col&.patterns&.join(' ')}}
82
84
  #Embeddings: #{@documents.size}
83
85
  #Tags: #{@documents.tags.size}
84
86
  Tags:
@@ -235,11 +237,6 @@ module OllamaChat::Information
235
237
  -n create a new session
236
238
  -u URL the ollama base url, OLLAMA_URL
237
239
  -m MODEL the ollama chat model, OLLAMA_CHAT_MODEL, ?selector
238
- -c CHAT a saved chat conversation to load
239
- -C COLLECTION name of the collection used in this conversation
240
- -D DOCUMENT load document and add to embeddings collection (multiple)
241
- -M use (empty) MemoryCache for this chat session
242
- -E disable embeddings for this chat session
243
240
  -S open a socket to receive input from ollama_chat_send
244
241
  -V display the current version number and quit
245
242
  -h this help
@@ -297,6 +294,21 @@ module OllamaChat::Information
297
294
  config.infobar.message.to_h
298
295
  end
299
296
 
297
+ # Retrieves a hash of collection names and their descriptions from the
298
+ # database.
299
+ #
300
+ # This is used to provide context to the AI model about available RAG
301
+ # collections.
302
+ #
303
+ # @return [Hash{String => String}] a hash mapping collection names to their
304
+ # descriptions
305
+ def collection_descriptions
306
+ cols = models::Collection.select(:name, :description).order(:name)
307
+ cols.each_with_object({}) do |c, hash|
308
+ hash[c.name] = c.description
309
+ end
310
+ end
311
+
300
312
  # Generates a hash containing static runtime information.
301
313
  #
302
314
  # This method collects session-level constants including the user,
@@ -307,7 +319,7 @@ module OllamaChat::Information
307
319
  def static_runtime_information_values
308
320
  {
309
321
  client: ,
310
- collections: JSON.pretty_generate(config.embedding.collection_descriptions?),
322
+ collections: JSON.pretty_generate(collection_descriptions),
311
323
  current_directory: Pathname.pwd.expand_path.to_path,
312
324
  languages: config.languages * ', ',
313
325
  location: location.on?.full? { location_description } || 'n/a',
@@ -172,6 +172,37 @@ module OllamaChat::ModelHandling
172
172
  STDOUT.puts "Model options #{italic{profile}} of #{bold{model_name}} were copied to session model options."
173
173
  end
174
174
 
175
+ # Interactively copies model options from a source model/profile to the
176
+ # current model/profile. Displays a side-by-side comparison if the
177
+ # destination profile already exists and prompts for override confirmation.
178
+ #
179
+ # @param model [String] the destination model for the copied options
180
+ def copy_model_options_profile(model)
181
+ src_model = choose_model('', model)
182
+ src_profile = choose_profile_for_model(src_model) || 'default'
183
+ dst_model = model
184
+ dst_profile = choose_profile_for_model(dst_model, allow_new: true) || src_profile
185
+
186
+ src_opts = get_stored_model_options(src_model, profile: src_profile).full? or return
187
+
188
+ dst_opts = get_stored_model_options(dst_model, profile: dst_profile)
189
+ if dst_opts.present?
190
+ STDOUT.puts "Profile #{italic{dst_profile}} already exists for #{bold{dst_model}}."
191
+ STDOUT.puts "\n📥 Source (#{src_model}/#{src_profile}):"
192
+ STDOUT.puts JSON.pretty_generate(src_opts)
193
+ STDOUT.puts "\n📤 Destination (#{dst_model}/#{dst_profile}):"
194
+ STDOUT.puts JSON.pretty_generate(dst_opts)
195
+
196
+ unless confirm?(prompt: "⚠️ Override existing profile? (y/n) ", yes: /\Ay/i)
197
+ STDOUT.puts "Cancelled."
198
+ return
199
+ end
200
+ end
201
+
202
+ store_model_options(dst_model, src_opts, profile: dst_profile)
203
+ STDOUT.puts "✅ Copied options from #{italic{src_model}}/#{italic{src_profile}} to #{bold{dst_model}}/#{italic{dst_profile}}."
204
+ end
205
+
175
206
  # Presents an interactive list of stored configuration profiles for the
176
207
  # specified model and prompts the user to select one.
177
208
  #
@@ -181,14 +212,27 @@ module OllamaChat::ModelHandling
181
212
  #
182
213
  # @param model_name [String] the name of the model whose profiles are to be listed
183
214
  # @return [String, nil] the selected profile name, or `nil` if none was chosen
184
- def choose_profile_for_model(model_name)
215
+ def choose_profile_for_model(model_name, allow_new: false)
185
216
  profiles = models::ModelOptions.where(model_name:).order(:profile).map(&:profile)
186
- profiles.size < 2 and return profiles.first
187
- profiles = [ '[EXIT]' ] + profiles
217
+
218
+ if allow_new
219
+ profiles = [ '[EXIT]', '[NEW]' ] + profiles
220
+ else
221
+ profiles.size < 2 and return profiles.first
222
+ profiles = [ '[EXIT]' ] + profiles
223
+ end
224
+
188
225
  case chosen = choose_entry(profiles, prompt: "Choose profile for #{bold{model_name}}: %s")
189
226
  when '[EXIT]', nil
190
227
  STDOUT.puts "Cancelled."
191
228
  return
229
+ when '[NEW]'
230
+ name = ask?(prompt: 'Enter new profile name: ') or return
231
+ if models::ModelOptions.where(model_name:, profile: name).present?
232
+ STDERR.puts "Profile #{name.inspect} already exists!"
233
+ return
234
+ end
235
+ name
192
236
  else
193
237
  chosen
194
238
  end
@@ -307,16 +351,32 @@ module OllamaChat::ModelHandling
307
351
  session.update(model_options: default_model_options)
308
352
  end
309
353
  elsif !keep_options && session_model_options != stored_model_options
310
- STDOUT.puts <<~EOT
311
- ⚠️ Session model options differ from defaults for model #@model!
312
- Session model options:
313
- #{JSON.pretty_generate(session_model_options)}
314
- Default model options:
315
- #{JSON.pretty_generate(stored_model_options)}
316
- EOT
317
- confirm?(
318
- prompt: "❓ Overwrite session model options with defaults? (y/n) ", yes: /\Ay/i
319
- ) and session.update(model_options: stored_model_options)
354
+ all_profiles = models::ModelOptions.where(model_name: @model).
355
+ order(:profile).all
356
+
357
+ matching = all_profiles.any? do |mo|
358
+ mo.options.ask_and_send(:symbolize_keys_recursive) == session_model_options
359
+ end
360
+
361
+ unless matching
362
+ use_pager do |output|
363
+ output.puts "⚠️ Session model options differ from current profile (#{italic{profile}}) and don't match any saved profile!"
364
+ output.puts "\n📋 Available profiles for #{bold{@model}}:"
365
+ all_profiles.each do |mo|
366
+ output.puts "\n📄 #{italic{mo.profile}}:"
367
+ output.puts JSON.pretty_generate(mo.options.ask_and_send(:symbolize_keys_recursive))
368
+ end
369
+ end
370
+
371
+ if confirm?(prompt: "\n❓ Switch to an existing profile? (y/n) ", yes: /\Ay/i)
372
+ chosen = choose_profile_for_model(@model)
373
+ if chosen
374
+ new_opts = get_stored_model_options(@model, profile: chosen)
375
+ session.update(model_options: new_opts)
376
+ STDOUT.puts "Switched to profile #{italic{chosen}}."
377
+ end
378
+ end
379
+ end
320
380
  end
321
381
  end
322
382
 
@@ -57,7 +57,7 @@ prompts:
57
57
  - Current directory is "%{current_directory}"
58
58
  - Client is %{client}.
59
59
  - These tools have access to these allowed paths: %{tool_paths_allowed}
60
- - Search in these collections with `retrieve_document_snippets`: %{collections}
60
+ - Search in these collections with `search_knowledge`: %{collections}
61
61
  dynamic_runtime_info: |
62
62
  There is usually no reason to mention this information to the user unless
63
63
  asked about it.
@@ -525,7 +525,7 @@ tools:
525
525
  default: true
526
526
  allowed:
527
527
  - './tmp'
528
- retrieve_document_snippets:
528
+ search_knowledge:
529
529
  result_display_timeout: 3
530
530
  default: true
531
531
  compute_bmi:
@@ -21,6 +21,27 @@ module OllamaChat::PromptManagement
21
21
  end
22
22
  end
23
23
 
24
+ # The choose_prompt_context method presents a menu of available prompt
25
+ # contexts for selection. It allows the user to choose between 'prompt',
26
+ # 'system', and 'suggest' contexts.
27
+ #
28
+ # @return [String, nil] the selected context name, or nil if the user
29
+ # cancels the selection.
30
+ def choose_prompt_context
31
+ contexts = models::Prompt.group(:context).order(:context).pluck(:context)
32
+ contexts.unshift('[EXIT]')
33
+ case chosen = choose_entry(
34
+ contexts,
35
+ prompt: '📝 Which prompt context shall we work in? %s'
36
+ )
37
+ when '[EXIT]', nil
38
+ STDOUT.puts "Exiting chooser."
39
+ return
40
+ else
41
+ chosen
42
+ end
43
+ end
44
+
24
45
  # The choose_prompt method presents a menu of available prompts for
25
46
  # selection. It retrieves the list of prompt names from the database, adds an
26
47
  # '[EXIT]' option, and displays them via the Chooser utility.