ollama-ruby 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb7858cf04c638fa3369fe7d96d96eed12e5553fd17abe9ed48c1eaf25113ffb
4
- data.tar.gz: ccb56d4b85a6e74256feb52ac5642d50850f7b980f9060dbb9fdf933985be746
3
+ metadata.gz: 5bdf85f296b631c52b44984eee6a5e6d98c82a4cfc4a5764529f4d46b3bdaba7
4
+ data.tar.gz: 05e391e492f85f8c6fb1106d49ff4118c26a0922c5978156e2f1364aad82299d
5
5
  SHA512:
6
- metadata.gz: f36aacde399c0be934425f12669fa6cc55cdfad938ea8157c5195db8c2367243cc6470dff24ec5bf83800079732bfa271450a4779246335c45c2bedbb0e3a6cf
7
- data.tar.gz: 56e465f056934210a6cc45677d52518d8adb76e8702561248b6db4cd1e4b129cfd22eedbdcc0c935f69bf85eeb9bc324aef6876f39969942af4529978a277756
6
+ metadata.gz: 7a3f77296a526154ed46860a675515b864baa8c991933e76ceeb178b00037df844f20201ad200cb9df8d4fa105066fb40bb0248db3bb65c5bad81012ce3b7288
7
+ data.tar.gz: 1d25c9420fba86d992b77f29e4f4f57734dcbdaba864650f626af1c828262a9470f9c981da602335f8d3a03ef3cfc9773ad33d90d3e88a70a4d86be0518d762a
data/CHANGES.md CHANGED
@@ -1,5 +1,89 @@
1
1
  # Changes
2
2
 
3
+ ## 2024-10-03 v0.7.0
4
+
5
+ * **Refactor command line interface**
6
+ + Moved case order
7
+ + Renamed `/collection clear [tag]|change` to `/collection (clear|change)`
8
+ + Improved help message, added /info
9
+ * **Update README.md**
10
+ + Update README.md to reflect changed/added commands
11
+ * **Add support for reading PostScript**
12
+ + Extracted `pdf_read` method to read PDF files using `PDF::Reader`
13
+ + Added `ps_read` method to read PostScript files by converting them to PDF with Ghostscript and using `pdf_read`.
14
+ + Updated `parse_source` method to handle PostScript files
15
+ * **Update read buffer size for tempfile writes**
16
+ + Updated `tmp.write` to use a larger buffer size (**16KB**) in IO.popen block.
17
+ * **Refactor Collection Chooser and usages**
18
+ + Added confirmation prompt before clearing collection
19
+ + Improved collection chooser with `[EXIT]` and `[ALL]` options
20
+ + Added `ask?` method for user input
21
+ * **Add prompt to choose method**
22
+ + Added `prompt` parameter to `choose` method in `Ollama::Utils::Chooser`
23
+ + Modified output formatting for selected entry in `choose` method
24
+ + Updated `choose` method to handle cases better where no entry was chosen
25
+ * **Fix Redis cache expiration logic**
26
+ + Update `set` method to delete key expiration time is less than 1 second.
27
+ * **Update dependencies and add source tracking**
28
+ - Remove `sorted_set` dependency from Rakefile
29
+ - Modify `Ollama::Documents` class to track source of tags
30
+ - Update `Ollama::Utils::Tags` class to include source in tag output and add methods for tracking source
31
+ - Update tests for `Ollama::Utils::Tags` class
32
+ * **Refactor width calculation and add tests for wrap and truncate methods.**
33
+ + Extend `Term::ANSIColor` in `Ollama::Utils::Width`
34
+ + Update `width` method to use ellipsis length when truncating text
35
+ + Add tests for `wrap` and `truncate` methods with percentage and length arguments
36
+ * **Add attr_reader for data and update equality check**
37
+ + Added `attr_reader :data` to Ollama::Image class
38
+ + Updated `==` method in Ollama::Image class to use `other.data`
39
+ + Added test case in `image_spec.rb` to verify equality of images
40
+
41
+ ## 2024-09-30 v0.6.0
42
+
43
+ ### Significant Changes
44
+
45
+ * **Added voice toggle and change functionality**:
46
+ + Removed `-v` command line switch
47
+ + Added new Switch class for voice output
48
+ + Added new method `change_voice` to toggle or change voice output
49
+ + Updated `info` method to display current voice output if enabled
50
+ + Updated `display_chat_help` method to include /voice command
51
+ * **Added expiring cache support**:
52
+ + Added `Ollama::Utils::CacheFetcher` class for caching HTTP responses
53
+ + Modified `Ollama::Utils::Fetcher` to use the new cache class
54
+ + Updated `ollama_chat` script to use the cache when fetching sources
55
+ + Added specs for the new cache fetcher class
56
+ * **Added change system prompt feature**:
57
+ + Added `/system` command to change system prompt
58
+ + Implemented `set_system_prompt` and `change_system_prompt` methods in `bin/ollama_chat`
59
+ + Updated help messages in `README.md`
60
+
61
+ ### Other Changes
62
+
63
+ * **Updated dependencies**:
64
+ + Updated version of `xdg` gem to **7.0**
65
+ + Added `xdg` dependency to Rakefile
66
+ * **Refactored error handling**:
67
+ + Warn message updated to include more context about the error
68
+ + `warn` statement now mentions "while pulling model"
69
+ * **Updated chat commands and added clipboard functionality**:
70
+ + Added `/copy` command to copy last response to clipboard
71
+ + Implemented `copy_to_clipboard` method in `ollama_chat`
72
+ + Updated chat help display to include new `/copy` command
73
+ * **Refactored Ollama::Utils::Fetcher**:
74
+ + Made instance methods private and only exposed class methods
75
+ + Added `expose` method to `Ollama::Utils::FetcherSpec` for testing
76
+ * **Added version command to ollama chat binary**:
77
+ + Added `version` method to print Ollama version and exit
78
+ + Updated `$opts` string in `ollama` script to include `-V` option for version command
79
+ + Added call to `version` method when `-V` option is used
80
+ * **Updated system prompt display**:
81
+ + Changed `Ollama::Utils::Width.wrap` to `Ollama::Utils::ANSIMarkdown.parse` in `show_system_prompt` method
82
+ * **Added system prompt configuration via search_ui for ? argument value**:
83
+ + Added `show_system_prompt` method to print configured system prompt
84
+ + Modified `info` method to include system prompt in output
85
+ + Implemented option `-s ?` to choose or specify system prompt
86
+
3
87
  ## 2024-09-26 v0.5.0
4
88
 
5
89
  ### New Features
data/README.md CHANGED
@@ -34,7 +34,7 @@ This a chat client, that can be used to connect to an ollama server and enter a
34
34
  chat converstation with a LLM. It can be called with the following arguments:
35
35
 
36
36
  ```
37
- ollama_chat [OPTIONS]
37
+ Usage: ollama_chat [OPTIONS]
38
38
 
39
39
  -f CONFIG config file to read
40
40
  -u URL the ollama base url, OLLAMA_URL
@@ -42,9 +42,10 @@ ollama_chat [OPTIONS]
42
42
  -s SYSTEM the system prompt to use as a file, OLLAMA_CHAT_SYSTEM
43
43
  -c CHAT a saved chat conversation to load
44
44
  -C COLLECTION name of the collection used in this conversation
45
- -D DOCUMENT load document and add to collection (multiple)
45
+ -D DOCUMENT load document and add to embeddings collection (multiple)
46
46
  -M use (empty) MemoryCache for this chat session
47
47
  -E disable embeddings for this chat session
48
+ -V display the current version number and quit
48
49
  -h this help
49
50
  ```
50
51
 
@@ -157,6 +158,7 @@ The following commands can be given inside the chat, if prefixed by a `/`:
157
158
  /paste to paste content
158
159
  /markdown toggle markdown output
159
160
  /stream toggle stream output
161
+ /location toggle location submission
160
162
  /voice( change) toggle voice output or change the voice
161
163
  /list [n] list the last n / all conversation exchanges
162
164
  /clear clear the whole conversation
@@ -165,7 +167,8 @@ The following commands can be given inside the chat, if prefixed by a `/`:
165
167
  /model change the model
166
168
  /system change system prompt (clears conversation)
167
169
  /regenerate the last answer message
168
- /collection clear [tag]|change clear or show stats of current collection
170
+ /collection( clear|change) change (default) collection or clear
171
+ /info show information for current session
169
172
  /import source import the source's content
170
173
  /summarize [n] source summarize the source's content in n words
171
174
  /embedding toggle embedding paused or not
data/Rakefile CHANGED
@@ -32,7 +32,6 @@ GemHadar do
32
32
  dependency 'redis', '~> 5.0'
33
33
  dependency 'numo-narray', '~> 0.9'
34
34
  dependency 'more_math', '~> 1.1'
35
- dependency 'sorted_set', '~> 1.0'
36
35
  dependency 'mime-types', '~> 3.0'
37
36
  dependency 'reverse_markdown', '~> 2.0'
38
37
  dependency 'complex_config', '~> 0.22'
@@ -42,6 +41,7 @@ GemHadar do
42
41
  dependency 'logger', '~> 1.0'
43
42
  dependency 'json', '~> 2.0'
44
43
  dependency 'xdg', '~> 7.0'
44
+ dependency 'tins', '~> 1.34'
45
45
  development_dependency 'all_images', '~> 0.4'
46
46
  development_dependency 'rspec', '~> 3.2'
47
47
  development_dependency 'webmock'
data/bin/ollama_chat CHANGED
@@ -4,9 +4,6 @@ require 'ollama'
4
4
  include Ollama
5
5
  require 'term/ansicolor'
6
6
  include Term::ANSIColor
7
- require 'tins'
8
- require 'tins/xt/full'
9
- require 'tins/xt/hash_union'
10
7
  include Tins::GO
11
8
  require 'reline'
12
9
  require 'reverse_markdown'
@@ -31,6 +28,11 @@ class OllamaChatConfig
31
28
  name: <%= ENV.fetch('OLLAMA_CHAT_MODEL', 'llama3.1') %>
32
29
  options:
33
30
  num_ctx: 8192
31
+ location:
32
+ enabled: false
33
+ name: Berlin
34
+ decimal_degrees: [ 52.514127, 13.475211 ]
35
+ units: SI (International System of Units) # or USCS (United States Customary System)
34
36
  prompts:
35
37
  embed: "This source was now embedded: %{source}"
36
38
  summarize: |
@@ -83,7 +85,7 @@ class OllamaChatConfig
83
85
  if @filename == default_path && !retried
84
86
  retried = true
85
87
  mkdir_p File.dirname(default_path)
86
- File.secure_write(default_path.to_s, DEFAULT_CONFIG)
88
+ File.secure_write(default_path, DEFAULT_CONFIG)
87
89
  retry
88
90
  else
89
91
  raise
@@ -178,7 +180,7 @@ end
178
180
 
179
181
  class Switch
180
182
  def initialize(name, msg:, config: $config)
181
- @value = !!config.send("#{name}?")
183
+ @value = [ false, true ].include?(config) ? config : !!config.send("#{name}?")
182
184
  @msg = msg
183
185
  end
184
186
 
@@ -259,9 +261,21 @@ def setup_switches
259
261
  false => "Embedding is currently not performed.",
260
262
  }
261
263
  )
264
+
265
+ $location = Switch.new(
266
+ :location,
267
+ msg: {
268
+ true => "Location and localtime enabled.",
269
+ false => "Location and localtime disabled.",
270
+ },
271
+ config: $config.location.enabled
272
+ )
262
273
  end
263
274
 
264
275
  def search_web(query, n = nil)
276
+ if l = at_location
277
+ query += " #{at_location}"
278
+ end
265
279
  n = n.to_i
266
280
  n < 1 and n = 1
267
281
  query = URI.encode_uri_component(query)
@@ -401,6 +415,32 @@ def parse_atom(source_io)
401
415
  end
402
416
  end
403
417
 
418
+ def pdf_read(io)
419
+ reader = PDF::Reader.new(io)
420
+ reader.pages.inject(+'') { |result, page| result << page.text }
421
+ end
422
+
423
+ def ps_read(io)
424
+ gs = `which gs`.chomp
425
+ if gs.present?
426
+ Tempfile.create do |tmp|
427
+ IO.popen("#{gs} -q -sDEVICE=pdfwrite -sOutputFile=#{tmp.path} -", 'wb') do |gs_io|
428
+ until io.eof?
429
+ buffer = io.read(1 << 17)
430
+ IO.select(nil, [ gs_io ], nil)
431
+ gs_io.write buffer
432
+ end
433
+ gs_io.close
434
+ File.open(tmp.path, 'rb') do |pdf|
435
+ pdf_read(pdf)
436
+ end
437
+ end
438
+ end
439
+ else
440
+ STDERR.puts "Cannot convert #{io&.content_type} whith ghostscript, gs not in path."
441
+ end
442
+ end
443
+
404
444
  def parse_source(source_io)
405
445
  case source_io&.content_type
406
446
  when 'text/html'
@@ -426,12 +466,11 @@ def parse_source(source_io)
426
466
  parse_rss(source_io)
427
467
  when 'application/atom+xml'
428
468
  parse_atom(source_io)
429
- when 'application/json'
430
- source_io.read
469
+ when 'application/postscript'
470
+ ps_read(source_io)
431
471
  when 'application/pdf'
432
- reader = PDF::Reader.new(source_io)
433
- reader.pages.inject(+'') { |result, page| result << page.text }
434
- when %r(\Atext/), nil
472
+ pdf_read(source_io)
473
+ when %r(\Aapplication/(json|ld\+json|x-ruby|x-perl|x-gawk|x-python|x-javascript|x-c?sh|x-dosexec|x-shellscript|x-tex|x-latex|x-lyx|x-bibtex)), %r(\Atext/), nil
435
474
  source_io.read
436
475
  else
437
476
  STDERR.puts "Cannot embed #{source_io&.content_type} document."
@@ -478,7 +517,7 @@ def embed_source(source_io, source)
478
517
  length: 10
479
518
  )
480
519
  end
481
- $documents.add(inputs, source: source)
520
+ $documents.add(inputs, source:)
482
521
  end
483
522
 
484
523
  def add_image(images, source_io, source)
@@ -576,7 +615,7 @@ def parse_content(content, images)
576
615
  content.scan(%r([.~]?/\S+|https?://\S+|#\S+)).each do |source|
577
616
  case source
578
617
  when /\A#(\S+)/
579
- tags << $1
618
+ tags.add($1, source:)
580
619
  else
581
620
  source = source.sub(/(["')]|\*+)\z/, '')
582
621
  fetch_source(source) do |source_io|
@@ -598,29 +637,37 @@ def parse_content(content, images)
598
637
  return content, (tags unless tags.empty?)
599
638
  end
600
639
 
601
- def choose_model(cli_model, default_model)
640
+ def choose_model(cli_model, current_model)
602
641
  models = ollama.tags.models.map(&:name).sort
603
642
  model = if cli_model == ''
604
- Ollama::Utils::Chooser.choose(models) || default_model
643
+ Ollama::Utils::Chooser.choose(models) || current_model
605
644
  else
606
- cli_model || default_model
645
+ cli_model || current_model
607
646
  end
608
647
  ensure
609
648
  puts green { "Connecting to #{model}@#{ollama.base_url} now…" }
610
649
  end
611
650
 
612
- def choose_collection(default_collection)
613
- collections = [ default_collection ] + $documents.collections
651
+ def ask?(prompt:)
652
+ print prompt
653
+ STDIN.gets.chomp
654
+ end
655
+
656
+ def choose_collection(current_collection)
657
+ collections = [ current_collection ] + $documents.collections
614
658
  collections = collections.compact.map(&:to_s).uniq.sort
615
- collections.unshift('[NEW]')
616
- collection = Ollama::Utils::Chooser.choose(collections) || default_collection
617
- if collection == '[NEW]'
618
- print "Enter name of the new collection: "
619
- collection = STDIN.gets.chomp
659
+ collections.unshift('[EXIT]').unshift('[NEW]')
660
+ collection = Ollama::Utils::Chooser.choose(collections) || current_collection
661
+ case collection
662
+ when '[NEW]'
663
+ $documents.collection = ask?(prompt: "Enter name of the new collection: ")
664
+ when nil, '[EXIT]'
665
+ puts "Exiting chooser."
666
+ when /./
667
+ $documents.collection = collection
620
668
  end
621
- $documents.collection = collection
622
669
  ensure
623
- puts "Changing to collection #{bold{collection}}."
670
+ puts "Using collection #{bold{$documents.collection}}."
624
671
  collection_stats
625
672
  end
626
673
 
@@ -652,13 +699,25 @@ def show_system_prompt
652
699
  EOT
653
700
  end
654
701
 
702
+ def at_location
703
+ if $location.on?
704
+ location_name = $config.location.name
705
+ location_decimal_degrees = $config.location.decimal_degrees * ', '
706
+ localtime = Time.now.iso8601
707
+ units = $config.location.units
708
+ $config.prompts.location % {
709
+ location_name:, location_decimal_degrees:, localtime:, units:,
710
+ }
711
+ end.to_s
712
+ end
713
+
655
714
  def set_system_prompt(messages, system)
656
715
  $system = system
657
716
  messages.clear
658
717
  messages << Message.new(role: 'system', content: system)
659
718
  end
660
719
 
661
- def change_system_prompt(messages)
720
+ def change_system_prompt(messages, default)
662
721
  prompts = $config.system_prompts.attribute_names.compact
663
722
  chosen = Ollama::Utils::Chooser.choose(prompts)
664
723
  system = if chosen
@@ -684,6 +743,7 @@ def info
684
743
  puts "Documents database cache is #{$documents.nil? ? 'n/a' : bold{$documents.cache.class}}"
685
744
  $markdown.show
686
745
  $stream.show
746
+ $location.show
687
747
  if $voice.on?
688
748
  puts "Using voice #{bold{$current_voice}} to speak."
689
749
  end
@@ -716,6 +776,7 @@ def display_chat_help
716
776
  /paste to paste content
717
777
  /markdown toggle markdown output
718
778
  /stream toggle stream output
779
+ /location toggle location submission
719
780
  /voice( change) toggle voice output or change the voice
720
781
  /list [n] list the last n / all conversation exchanges
721
782
  /clear clear the whole conversation
@@ -724,7 +785,8 @@ def display_chat_help
724
785
  /model change the model
725
786
  /system change system prompt (clears conversation)
726
787
  /regenerate the last answer message
727
- /collection clear [tag]|change clear or show stats of current collection
788
+ /collection( clear|change) change (default) collection or clear
789
+ /info show information for current session
728
790
  /import source import the source's content
729
791
  /summarize [n] source summarize the source's content in n words
730
792
  /embedding toggle embedding paused or not
@@ -739,7 +801,7 @@ end
739
801
 
740
802
  def usage
741
803
  puts <<~EOT
742
- #{File.basename($0)} [OPTIONS]
804
+ Usage: #{File.basename($0)} [OPTIONS]
743
805
 
744
806
  -f CONFIG config file to read
745
807
  -u URL the ollama base url, OLLAMA_URL
@@ -790,7 +852,7 @@ if $opts[?c]
790
852
  else
791
853
  default = $config.system_prompts.default? || model_system
792
854
  if $opts[?s] == ??
793
- change_system_prompt(messages)
855
+ change_system_prompt(messages, default)
794
856
  else
795
857
  system = Ollama::Utils::FileArgument.get_file_argument($opts[?s], default:)
796
858
  system.present? and set_system_prompt(messages, system)
@@ -860,18 +922,21 @@ loop do
860
922
  content = Reline.readline(input_prompt, true)&.chomp
861
923
 
862
924
  case content
863
- when %r(^/paste$)
864
- puts bold { "Paste your content and then press C-d!" }
865
- content = STDIN.read
866
925
  when %r(^/copy$)
867
926
  copy_to_clipboard(messages)
868
927
  next
928
+ when %r(^/paste$)
929
+ puts bold { "Paste your content and then press C-d!" }
930
+ content = STDIN.read
869
931
  when %r(^/markdown$)
870
932
  $markdown.toggle
871
933
  next
872
934
  when %r(^/stream$)
873
935
  $stream.toggle
874
936
  next
937
+ when %r(^/location$)
938
+ $location.toggle
939
+ next
875
940
  when %r(^/voice(?:\s+(change))?$)
876
941
  if $1 == 'change'
877
942
  change_voice
@@ -890,33 +955,14 @@ loop do
890
955
  puts "Cleared messages."
891
956
  next
892
957
  when %r(^/clobber$)
893
- clear_messages(messages)
894
- $documents.clear
895
- puts "Cleared messages and collection."
896
- next
897
- when %r(^/collection\s+(clear|change)(?:\s+(.+))?$)
898
- command, arg = $1, $2
899
- case command
900
- when 'clear'
901
- tags = arg.present? ? arg.sub(/\A#*/, '') : nil
902
- if tags
903
- $documents.clear(tags:)
904
- puts "Cleared tag ##{tags} from collection #{bold{collection}}."
905
- else
906
- $documents.clear
907
- puts "Cleared collection #{bold{collection}}."
908
- end
909
- when 'change'
910
- choose_collection(collection)
958
+ if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
959
+ clear_messages(messages)
960
+ $documents.clear
961
+ puts "Cleared messages and collection #{bold{$documents.collection}}."
962
+ else
963
+ puts 'Cancelled.'
911
964
  end
912
965
  next
913
- when %r(^/system$)
914
- change_system_prompt(messages)
915
- info
916
- next
917
- when %r(/info)
918
- info
919
- next
920
966
  when %r(^/pop(?:\s+(\d*))?$)
921
967
  if messages.size > 1
922
968
  n = $1.to_i.clamp(1, Float::INFINITY)
@@ -931,6 +977,10 @@ loop do
931
977
  when %r(^/model$)
932
978
  $model = choose_model('', $model)
933
979
  next
980
+ when %r(^/system$)
981
+ change_system_prompt(messages, $system)
982
+ info
983
+ next
934
984
  when %r(^/regenerate$)
935
985
  if content = messages[-2]&.content
936
986
  content.gsub!(/\nConsider these chunks for your answer.*\z/, '')
@@ -941,6 +991,38 @@ loop do
941
991
  end
942
992
  parse_content = false
943
993
  content
994
+ when %r(^/collection(?:\s+(clear|change))?$)
995
+ case $1 || 'change'
996
+ when 'clear'
997
+ loop do
998
+ tags = $documents.tags.add('[EXIT]').add('[ALL]')
999
+ tag = Ollama::Utils::Chooser.choose(tags, prompt: 'Clear? %s')
1000
+ case tag
1001
+ when nil, '[EXIT]'
1002
+ puts "Exiting chooser."
1003
+ break
1004
+ when '[ALL]'
1005
+ if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
1006
+ $documents.clear
1007
+ puts "Cleared collection #{bold{$documents.collection}}."
1008
+ break
1009
+ else
1010
+ puts 'Cancelled.'
1011
+ sleep 3
1012
+ end
1013
+ when /./
1014
+ $documents.clear(tags: [ tag ])
1015
+ puts "Cleared tag #{tag} from collection #{bold{$documents.collection}}."
1016
+ sleep 3
1017
+ end
1018
+ end
1019
+ when 'change'
1020
+ choose_collection($documents.collection)
1021
+ end
1022
+ next
1023
+ when %r(/info)
1024
+ info
1025
+ next
944
1026
  when %r(^/import\s+(.+))
945
1027
  parse_content = false
946
1028
  content = import($1) or next
@@ -1007,6 +1089,10 @@ loop do
1007
1089
  end
1008
1090
  end
1009
1091
 
1092
+ if location = at_location.full?
1093
+ content += " [#{location} – do not comment on this information, just consider it for eventual queries]"
1094
+ end
1095
+
1010
1096
  messages << Message.new(role: 'user', content:, images: images.dup)
1011
1097
  images.clear
1012
1098
  handler = FollowChat.new(messages:, markdown: $markdown.on?, voice: ($current_voice if $voice.on?))
data/bin/ollama_cli CHANGED
@@ -8,8 +8,8 @@ include Tins::GO
8
8
  require 'json'
9
9
 
10
10
  def usage
11
- puts <<~end
12
- #{File.basename($0)} [OPTIONS]
11
+ puts <<~EOT
12
+ Usage: #{File.basename($0)} [OPTIONS]
13
13
 
14
14
  -u URL the ollama base url, OLLAMA_URL
15
15
  -m MODEL the ollama model to chat with, OLLAMA_MODEL
@@ -22,7 +22,7 @@ def usage
22
22
  -S use streaming for generation
23
23
  -h this help
24
24
 
25
- end
25
+ EOT
26
26
  exit 0
27
27
  end
28
28
 
data/bin/ollama_update CHANGED
@@ -14,4 +14,6 @@ ollama.tags.models.each do |model|
14
14
  "Updating model #{bold {name}} (last modified at #{modified_at.iso8601}):"
15
15
  )
16
16
  ollama.pull(name:)
17
+ rescue Ollama::Errors::Error => e
18
+ infobar.puts "Caught #{e.class} for model #{bold { model.name }}: #{e} => Continuing."
17
19
  end
data/lib/ollama/client.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'tins/xt/string_camelize'
2
- require 'tins/annotate'
3
-
4
1
  class Ollama::Client
5
2
  end
6
3
  require 'ollama/client/doc'
@@ -23,7 +23,17 @@ class Ollama::Documents::RedisCache
23
23
  end
24
24
 
25
25
  def []=(key, value)
26
- redis.set(pre(key), JSON.generate(value), ex: @ex)
26
+ set(key, value)
27
+ end
28
+
29
+ def set(key, value, ex: nil)
30
+ ex ||= @ex
31
+ if !ex.nil? && ex < 1
32
+ redis.del(pre(key))
33
+ else
34
+ redis.set(pre(key), JSON.generate(value), ex:)
35
+ end
36
+ value
27
37
  end
28
38
 
29
39
  def ttl(key)
@@ -25,7 +25,7 @@ class Ollama::Documents
25
25
  end
26
26
 
27
27
  def tags_set
28
- Ollama::Utils::Tags.new(tags)
28
+ Ollama::Utils::Tags.new(tags, source:)
29
29
  end
30
30
 
31
31
  def ==(other)
@@ -57,8 +57,10 @@ class Ollama::Documents
57
57
 
58
58
  def add(inputs, batch_size: 10, source: nil, tags: [])
59
59
  inputs = Array(inputs)
60
- tags = Ollama::Utils::Tags.new(tags)
61
- source and tags.add File.basename(source).gsub(/\?.*/, '')
60
+ tags = Ollama::Utils::Tags.new(tags, source:)
61
+ if source
62
+ tags.add(File.basename(source).gsub(/\?.*/, ''), source:)
63
+ end
62
64
  inputs.map! { |i|
63
65
  text = i.respond_to?(:read) ? i.read : i.to_s
64
66
  text
@@ -70,7 +72,7 @@ class Ollama::Documents
70
72
  end
71
73
  batches = inputs.each_slice(batch_size).
72
74
  with_infobar(
73
- label: "Add #{truncate(tags.to_s, percentage: 25)}",
75
+ label: "Add #{truncate(tags.to_s(link: false), percentage: 25)}",
74
76
  total: inputs.size
75
77
  )
76
78
  batches.each do |batch|
@@ -159,7 +161,11 @@ class Ollama::Documents
159
161
  end
160
162
 
161
163
  def tags
162
- @cache.inject(Ollama::Utils::Tags.new) { |t, (_, record)| t.merge(record.tags) }
164
+ @cache.each_with_object(Ollama::Utils::Tags.new) do |(_, record), t|
165
+ record.tags.each do |tag|
166
+ t.add(tag, source: record.source)
167
+ end
168
+ end
163
169
  end
164
170
 
165
171
  private
data/lib/ollama/image.rb CHANGED
@@ -7,6 +7,8 @@ class Ollama::Image
7
7
 
8
8
  attr_accessor :path
9
9
 
10
+ attr_reader :data
11
+
10
12
  class << self
11
13
  def for_base64(data, path: nil)
12
14
  obj = new(data)
@@ -31,7 +33,7 @@ class Ollama::Image
31
33
  end
32
34
 
33
35
  def ==(other)
34
- @data == other..data
36
+ @data == other.data
35
37
  end
36
38
 
37
39
  def to_s
@@ -13,7 +13,7 @@ class Ollama::Utils::CacheFetcher
13
13
  if body && content_type
14
14
  io = StringIO.new(body)
15
15
  io.rewind
16
- io.extend(Ollama::Utils::Fetcher::ContentType)
16
+ io.extend(Ollama::Utils::Fetcher::HeaderExtension)
17
17
  io.content_type = content_type
18
18
  block.(io)
19
19
  end
@@ -25,8 +25,8 @@ class Ollama::Utils::CacheFetcher
25
25
  body.empty? and return
26
26
  content_type = io.content_type
27
27
  content_type.nil? and return
28
- @cache[key(:body, url)] = body
29
- @cache[key(:content_type, url)] = content_type.to_s
28
+ @cache.set(key(:body, url), body, ex: io.ex)
29
+ @cache.set(key(:content_type, url), content_type.to_s, ex: io.ex)
30
30
  self
31
31
  end
32
32
 
@@ -8,8 +8,9 @@ module Ollama::Utils::Chooser
8
8
 
9
9
  module_function
10
10
 
11
- def choose(entries)
11
+ def choose(entries, prompt: 'Search? %s')
12
12
  entry = Search.new(
13
+ prompt:,
13
14
  match: -> answer {
14
15
  matcher = Amatch::PairDistance.new(answer.downcase)
15
16
  matches = entries.map { |n| [ n, -matcher.similar(n.to_s.downcase) ] }.
@@ -19,7 +20,7 @@ module Ollama::Utils::Chooser
19
20
  },
20
21
  query: -> _answer, matches, selector {
21
22
  matches.each_with_index.map { |m, i|
22
- i == selector ? "#{blue{?⮕}} #{on_blue{m}}" : " #{m}"
23
+ i == selector ? "#{blue{?⮕}} #{on_blue{m}}" : " #{m.to_s}"
23
24
  } * ?\n
24
25
  },
25
26
  found: -> _answer, matches, selector {
@@ -27,6 +28,11 @@ module Ollama::Utils::Chooser
27
28
  },
28
29
  output: STDOUT
29
30
  ).start
30
- return entry if entry
31
+ if entry
32
+ entry
33
+ else
34
+ print clear_screen, move_home
35
+ nil
36
+ end
31
37
  end
32
38
  end
@@ -6,9 +6,11 @@ require 'stringio'
6
6
  require 'ollama/utils/cache_fetcher'
7
7
 
8
8
  class Ollama::Utils::Fetcher
9
- module ContentType
9
+ module HeaderExtension
10
10
  attr_accessor :content_type
11
11
 
12
+ attr_accessor :ex
13
+
12
14
  def self.failed
13
15
  object = StringIO.new.extend(self)
14
16
  object.content_type = MIME::Types['text/plain'].first
@@ -22,7 +24,7 @@ class Ollama::Utils::Fetcher
22
24
  cache = options.delete(:cache) and
23
25
  cache = Ollama::Utils::CacheFetcher.new(cache)
24
26
  if result = cache&.get(url, &block)
25
- infobar.puts "Getting #{url.inspect} from cache."
27
+ infobar.puts "Getting #{url.to_s.inspect} from cache."
26
28
  return result
27
29
  else
28
30
  new(**options).send(:get, url) do |tmp|
@@ -39,7 +41,7 @@ class Ollama::Utils::Fetcher
39
41
  def self.read(filename, &block)
40
42
  if File.exist?(filename)
41
43
  File.open(filename) do |file|
42
- file.extend(Ollama::Utils::Fetcher::ContentType)
44
+ file.extend(Ollama::Utils::Fetcher::HeaderExtension)
43
45
  file.content_type = MIME::Types.type_for(filename).first
44
46
  block.(file)
45
47
  end
@@ -50,10 +52,10 @@ class Ollama::Utils::Fetcher
50
52
  Tempfile.open do |tmp|
51
53
  IO.popen(command) do |command|
52
54
  until command.eof?
53
- tmp.write command.read(4096)
55
+ tmp.write command.read(1 << 14)
54
56
  end
55
57
  tmp.rewind
56
- tmp.extend(Ollama::Utils::Fetcher::ContentType)
58
+ tmp.extend(Ollama::Utils::Fetcher::HeaderExtension)
57
59
  tmp.content_type = MIME::Types['text/plain'].first
58
60
  block.(tmp)
59
61
  end
@@ -63,7 +65,7 @@ class Ollama::Utils::Fetcher
63
65
  if @debug && !e.is_a?(RuntimeError)
64
66
  STDERR.puts "#{e.backtrace * ?\n}"
65
67
  end
66
- yield ContentType.failed
68
+ yield HeaderExtension.failed
67
69
  end
68
70
 
69
71
  def initialize(debug: false, http_options: {})
@@ -110,7 +112,7 @@ class Ollama::Utils::Fetcher
110
112
  if @debug && !e.is_a?(RuntimeError)
111
113
  STDERR.puts "#{e.backtrace * ?\n}"
112
114
  end
113
- yield ContentType.failed
115
+ yield HeaderExtension.failed
114
116
  end
115
117
 
116
118
  def headers
@@ -123,12 +125,20 @@ class Ollama::Utils::Fetcher
123
125
  (Excon.defaults[:middlewares] + [ Excon::Middleware::RedirectFollower ]).uniq
124
126
  end
125
127
 
128
+ private
129
+
126
130
  def decorate_io(tmp, response)
127
131
  tmp.rewind
128
- tmp.extend(ContentType)
132
+ tmp.extend(HeaderExtension)
129
133
  if content_type = MIME::Types[response.headers['content-type']].first
130
134
  tmp.content_type = content_type
131
135
  end
136
+ if cache_control = response.headers['cache-control'] and
137
+ cache_control !~ /no-store|no-cache/ and
138
+ ex = cache_control[/s-maxage\s*=\s*(\d+)/, 1] || cache_control[/max-age\s*=\s*(\d+)/, 1]
139
+ then
140
+ tmp.ex = ex.to_i
141
+ end
132
142
  end
133
143
 
134
144
  def callback(tmp)
@@ -1,12 +1,66 @@
1
- require 'sorted_set'
1
+ class Ollama::Utils::Tags
2
+ class Tag < String
3
+ include Term::ANSIColor
2
4
 
5
+ def initialize(tag, source: nil)
6
+ super(tag.to_s)
7
+ self.source = source
8
+ end
3
9
 
4
- class Ollama::Utils::Tags < SortedSet
5
- def to_a
6
- super.map(&:to_s)
10
+ attr_accessor :source
11
+
12
+ alias_method :internal, :to_s
13
+
14
+ def to_s(link: true)
15
+ tag_string = start_with?(?#) ? super() : ?# + super()
16
+ my_source = source
17
+ if link && my_source
18
+ unless my_source =~ %r(\A(https?|file)://)
19
+ my_source = 'file://%s' % File.expand_path(my_source)
20
+ end
21
+ hyperlink(my_source) { tag_string }
22
+ else
23
+ tag_string
24
+ end
25
+ end
26
+ end
27
+
28
+ def initialize(tags = [], source: nil)
29
+ @set = []
30
+ tags.each { |tag| add(tag, source:) }
31
+ end
32
+
33
+ def add(tag, source: nil)
34
+ unless tag.is_a?(Tag)
35
+ tag = Tag.new(tag, source:)
36
+ end
37
+ index = @set.bsearch_index { _1 >= tag }
38
+ if index == nil
39
+ @set.push(tag)
40
+ elsif @set.at(index) != tag
41
+ @set.insert(index, tag)
42
+ end
43
+ self
44
+ end
45
+
46
+ def empty?
47
+ @set.empty?
48
+ end
49
+
50
+ def size
51
+ @set.size
52
+ end
53
+
54
+ def clear
55
+ @set.clear
56
+ end
57
+
58
+ def each(&block)
59
+ @set.each(&block)
7
60
  end
61
+ include Enumerable
8
62
 
9
- def to_s
10
- map { |t| '#%s' % t } * ' '
63
+ def to_s(link: true)
64
+ @set.map { |tag| tag.to_s(link:) } * ' '
11
65
  end
12
66
  end
@@ -2,6 +2,7 @@ require 'tins/terminal'
2
2
 
3
3
  module Ollama::Utils::Width
4
4
  include Term::ANSIColor
5
+ extend Term::ANSIColor
5
6
 
6
7
  module_function
7
8
 
@@ -26,10 +27,11 @@ module Ollama::Utils::Width
26
27
  percentage.nil? ^ length.nil? or
27
28
  raise ArgumentError, "either pass percentage or length argument"
28
29
  percentage and length ||= width(percentage:)
29
- if length < 1
30
+ ellipsis_length = ellipsis.size
31
+ if length < ellipsis_length
30
32
  +''
31
- elsif text.size > length
32
- text[0, length - 1] + ?…
33
+ elsif text.size >= length + ellipsis_length
34
+ text[0, length - ellipsis_length] + ellipsis
33
35
  else
34
36
  text
35
37
  end
@@ -1,6 +1,6 @@
1
1
  module Ollama
2
2
  # Ollama version
3
- VERSION = '0.6.0'
3
+ VERSION = '0.7.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/ollama.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require 'json'
2
2
  require 'logger'
3
3
  require 'excon'
4
+ require 'tins'
5
+ require 'tins/xt/full'
6
+ require 'tins/xt/hash_union'
7
+ require 'tins/xt/string_camelize'
4
8
 
5
9
  module Ollama
6
10
  end
data/ollama-ruby.gemspec CHANGED
@@ -1,26 +1,26 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: ollama-ruby 0.6.0 ruby lib
2
+ # stub: ollama-ruby 0.7.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "ollama-ruby".freeze
6
- s.version = "0.6.0".freeze
6
+ s.version = "0.7.0".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]
10
10
  s.authors = ["Florian Frank".freeze]
11
- s.date = "2024-09-30"
11
+ s.date = "2024-10-02"
12
12
  s.description = "Library that allows interacting with the Ollama API".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["ollama_console".freeze, "ollama_chat".freeze, "ollama_update".freeze, "ollama_cli".freeze]
15
15
  s.extra_rdoc_files = ["README.md".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/documents.rb".freeze, "lib/ollama/documents/cache/common.rb".freeze, "lib/ollama/documents/cache/memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_backed_memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_cache.rb".freeze, "lib/ollama/documents/splitters/character.rb".freeze, "lib/ollama/documents/splitters/semantic.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/utils/ansi_markdown.rb".freeze, "lib/ollama/utils/cache_fetcher.rb".freeze, "lib/ollama/utils/chooser.rb".freeze, "lib/ollama/utils/colorize_texts.rb".freeze, "lib/ollama/utils/fetcher.rb".freeze, "lib/ollama/utils/file_argument.rb".freeze, "lib/ollama/utils/math.rb".freeze, "lib/ollama/utils/tags.rb".freeze, "lib/ollama/utils/width.rb".freeze, "lib/ollama/version.rb".freeze]
16
- s.files = [".envrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_cli".freeze, "bin/ollama_console".freeze, "bin/ollama_update".freeze, "config/redis.conf".freeze, "docker-compose.yml".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/documents.rb".freeze, "lib/ollama/documents/cache/common.rb".freeze, "lib/ollama/documents/cache/memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_backed_memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_cache.rb".freeze, "lib/ollama/documents/splitters/character.rb".freeze, "lib/ollama/documents/splitters/semantic.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/utils/ansi_markdown.rb".freeze, "lib/ollama/utils/cache_fetcher.rb".freeze, "lib/ollama/utils/chooser.rb".freeze, "lib/ollama/utils/colorize_texts.rb".freeze, "lib/ollama/utils/fetcher.rb".freeze, "lib/ollama/utils/file_argument.rb".freeze, "lib/ollama/utils/math.rb".freeze, "lib/ollama/utils/tags.rb".freeze, "lib/ollama/utils/width.rb".freeze, "lib/ollama/version.rb".freeze, "ollama-ruby.gemspec".freeze, "spec/assets/embeddings.json".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/prompt.txt".freeze, "spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
16
+ s.files = [".envrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_cli".freeze, "bin/ollama_console".freeze, "bin/ollama_update".freeze, "config/redis.conf".freeze, "docker-compose.yml".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/documents.rb".freeze, "lib/ollama/documents/cache/common.rb".freeze, "lib/ollama/documents/cache/memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_backed_memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_cache.rb".freeze, "lib/ollama/documents/splitters/character.rb".freeze, "lib/ollama/documents/splitters/semantic.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/utils/ansi_markdown.rb".freeze, "lib/ollama/utils/cache_fetcher.rb".freeze, "lib/ollama/utils/chooser.rb".freeze, "lib/ollama/utils/colorize_texts.rb".freeze, "lib/ollama/utils/fetcher.rb".freeze, "lib/ollama/utils/file_argument.rb".freeze, "lib/ollama/utils/math.rb".freeze, "lib/ollama/utils/tags.rb".freeze, "lib/ollama/utils/width.rb".freeze, "lib/ollama/version.rb".freeze, "ollama-ruby.gemspec".freeze, "spec/assets/embeddings.json".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/prompt.txt".freeze, "spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/ollama/utils/width_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
17
17
  s.homepage = "https://github.com/flori/ollama-ruby".freeze
18
18
  s.licenses = ["MIT".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "Ollama-ruby - Interacting with the Ollama API".freeze, "--main".freeze, "README.md".freeze]
20
20
  s.required_ruby_version = Gem::Requirement.new("~> 3.1".freeze)
21
21
  s.rubygems_version = "3.5.18".freeze
22
22
  s.summary = "Interacting with the Ollama API".freeze
23
- s.test_files = ["spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/spec_helper.rb".freeze]
23
+ s.test_files = ["spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/ollama/utils/width_spec.rb".freeze, "spec/spec_helper.rb".freeze]
24
24
 
25
25
  s.specification_version = 4
26
26
 
@@ -38,7 +38,6 @@ Gem::Specification.new do |s|
38
38
  s.add_runtime_dependency(%q<redis>.freeze, ["~> 5.0".freeze])
39
39
  s.add_runtime_dependency(%q<numo-narray>.freeze, ["~> 0.9".freeze])
40
40
  s.add_runtime_dependency(%q<more_math>.freeze, ["~> 1.1".freeze])
41
- s.add_runtime_dependency(%q<sorted_set>.freeze, ["~> 1.0".freeze])
42
41
  s.add_runtime_dependency(%q<mime-types>.freeze, ["~> 3.0".freeze])
43
42
  s.add_runtime_dependency(%q<reverse_markdown>.freeze, ["~> 2.0".freeze])
44
43
  s.add_runtime_dependency(%q<complex_config>.freeze, ["~> 0.22".freeze])
@@ -48,4 +47,5 @@ Gem::Specification.new do |s|
48
47
  s.add_runtime_dependency(%q<logger>.freeze, ["~> 1.0".freeze])
49
48
  s.add_runtime_dependency(%q<json>.freeze, ["~> 2.0".freeze])
50
49
  s.add_runtime_dependency(%q<xdg>.freeze, ["~> 7.0".freeze])
50
+ s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.34".freeze])
51
51
  end
@@ -9,6 +9,11 @@ RSpec.describe Ollama::Image do
9
9
  expect(image).to be_a described_class
10
10
  end
11
11
 
12
+ it 'can be equal or not' do
13
+ expect(image).not_to eq described_class.for_string('')
14
+ expect(image).to eq described_class.for_filename(asset('kitten.jpg'))
15
+ end
16
+
12
17
  it 'cannot be created via .new' do
13
18
  expect {
14
19
  described_class.new('nix')
@@ -33,10 +33,11 @@ RSpec.describe Ollama::Utils::CacheFetcher do
33
33
 
34
34
  it 'has #put' do
35
35
  io = StringIO.new('world')
36
- io.extend(Ollama::Utils::Fetcher::ContentType)
36
+ io.extend(Ollama::Utils::Fetcher::HeaderExtension)
37
37
  io.content_type = MIME::Types['text/plain'].first
38
- expect(cache).to receive(:[]=).with('body-69ce405ab83f42dffa9fd22bbd47783f', 'world')
39
- expect(cache).to receive(:[]=).with('content_type-69ce405ab83f42dffa9fd22bbd47783f', 'text/plain')
38
+ io.ex = 666
39
+ expect(cache).to receive(:set).with('body-69ce405ab83f42dffa9fd22bbd47783f', 'world', ex: 666)
40
+ expect(cache).to receive(:set).with('content_type-69ce405ab83f42dffa9fd22bbd47783f', 'text/plain', ex: 666)
40
41
  fetcher.put(url, io)
41
42
  end
42
43
  end
@@ -14,11 +14,35 @@ RSpec.describe Ollama::Utils::Tags do
14
14
  tags = described_class.new([ 'foo' ])
15
15
  tags.add 'bar'
16
16
  expect(tags.to_a).to eq %w[ bar foo ]
17
- tags.merge %w[ baz baz2 ]
18
- expect(tags.to_a).to eq %w[ bar baz baz2 foo ]
17
+ end
18
+
19
+ it 'can increase in size' do
20
+ tags = described_class.new
21
+ expect { tags.add 'foo' }.to change { tags.size }.from(0).to(1)
22
+ expect { tags.add 'bar' }.to change { tags.size }.from(1).to(2)
23
+ end
24
+
25
+ it 'can be cleared' do
26
+ tags = described_class.new([ 'foo', 'bar' ])
27
+ expect { tags.clear }.to change { tags.size }.from(2).to(0)
28
+ end
29
+
30
+ it 'tags can be empt' do
31
+ tags = described_class.new([ 'foo' ])
32
+ expect { tags.clear }.to change { tags.empty? }.from(false).to(true)
19
33
  end
20
34
 
21
35
  it 'can be output nicely' do
22
36
  expect(described_class.new(%w[ foo bar ]).to_s).to eq '#bar #foo'
23
37
  end
38
+
39
+ it 'can be output nicely with links to source' do
40
+ tags = described_class.new([ 'foo' ], source: 'https://foo.example.com')
41
+ tags.add 'bar', source: '/path/to/bar.html'
42
+ expect(tags.to_a).to eq %w[ bar foo ]
43
+ tags.all? { expect(_1).to be_a(Ollama::Utils::Tags::Tag) }
44
+ expect(tags.to_s).to eq(
45
+ "\e]8;;file:///path/to/bar.html\e\\#bar\e]8;;\e\\ \e]8;;https://foo.example.com\e\\#foo\e]8;;\e\\"
46
+ )
47
+ end
24
48
  end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Ollama::Utils::Width do
4
+ before do
5
+ allow(Tins::Terminal).to receive(:columns).and_return 80
6
+ end
7
+
8
+ describe '.width' do
9
+ it 'defaults to 100%' do
10
+ expect(described_class.width).to eq 80
11
+ end
12
+
13
+ it 'can be to 80%' do
14
+ expect(described_class.width(percentage: 80)).to eq 64
15
+ end
16
+ end
17
+
18
+ describe '.wrap' do
19
+ it 'can wrap with percentage' do
20
+ wrapped = described_class.wrap([ ?A * 10 ] * 10 * ' ', percentage: 80)
21
+ expect(wrapped).to eq(
22
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA\n"\
23
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA"
24
+ )
25
+ expect(wrapped.size).to eq 109
26
+ end
27
+
28
+ it 'can wrap with length' do
29
+ wrapped = described_class.wrap([ ?A * 10 ] * 10 * ' ', length: 64)
30
+ expect(wrapped).to eq(
31
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA\n"\
32
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA"
33
+ )
34
+ expect(wrapped.size).to eq 109
35
+ end
36
+
37
+ it "doesn't wrap with length 0" do
38
+ wrapped = described_class.wrap([ ?A * 10 ] * 10 * ' ', length: 0)
39
+ expect(wrapped).to eq(
40
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA "\
41
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA"
42
+ )
43
+ end
44
+ end
45
+
46
+ describe '.truncate' do
47
+ it 'can truncate with percentage' do
48
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', percentage: 80)
49
+ expect(truncated).to eq(
50
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAA…"
51
+ )
52
+ expect(truncated.size).to eq 64
53
+ end
54
+
55
+ it 'can truncate with length' do
56
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', length: 64)
57
+ expect(truncated).to eq(
58
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAA…"
59
+ )
60
+ expect(truncated.size).to eq 64
61
+ end
62
+
63
+ it 'cannot truncate if not necessary' do
64
+ text = [ ?A * 10 ] * 5 * ' '
65
+ truncated = described_class.truncate(text, length: 54)
66
+ expect(truncated).to eq text
67
+ end
68
+
69
+ it 'can truncate with length 0' do
70
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', length: 0)
71
+ expect(truncated).to be_empty
72
+ end
73
+
74
+ it 'can truncate with ...' do
75
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', length: 64, ellipsis: '...')
76
+ expect(truncated).to eq(
77
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAA..."
78
+ )
79
+ expect(truncated.size).to eq 64
80
+ end
81
+ end
82
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ollama-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-30 00:00:00.000000000 Z
11
+ date: 2024-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gem_hadar
@@ -206,20 +206,6 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: '1.1'
209
- - !ruby/object:Gem::Dependency
210
- name: sorted_set
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - "~>"
214
- - !ruby/object:Gem::Version
215
- version: '1.0'
216
- type: :runtime
217
- prerelease: false
218
- version_requirements: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - "~>"
221
- - !ruby/object:Gem::Version
222
- version: '1.0'
223
209
  - !ruby/object:Gem::Dependency
224
210
  name: mime-types
225
211
  requirement: !ruby/object:Gem::Requirement
@@ -346,6 +332,20 @@ dependencies:
346
332
  - - "~>"
347
333
  - !ruby/object:Gem::Version
348
334
  version: '7.0'
335
+ - !ruby/object:Gem::Dependency
336
+ name: tins
337
+ requirement: !ruby/object:Gem::Requirement
338
+ requirements:
339
+ - - "~>"
340
+ - !ruby/object:Gem::Version
341
+ version: '1.34'
342
+ type: :runtime
343
+ prerelease: false
344
+ version_requirements: !ruby/object:Gem::Requirement
345
+ requirements:
346
+ - - "~>"
347
+ - !ruby/object:Gem::Version
348
+ version: '1.34'
349
349
  description: Library that allows interacting with the Ollama API
350
350
  email: flori@ping.de
351
351
  executables:
@@ -519,6 +519,7 @@ files:
519
519
  - spec/ollama/utils/fetcher_spec.rb
520
520
  - spec/ollama/utils/file_argument_spec.rb
521
521
  - spec/ollama/utils/tags_spec.rb
522
+ - spec/ollama/utils/width_spec.rb
522
523
  - spec/spec_helper.rb
523
524
  - tmp/.keep
524
525
  homepage: https://github.com/flori/ollama-ruby
@@ -587,4 +588,5 @@ test_files:
587
588
  - spec/ollama/utils/fetcher_spec.rb
588
589
  - spec/ollama/utils/file_argument_spec.rb
589
590
  - spec/ollama/utils/tags_spec.rb
591
+ - spec/ollama/utils/width_spec.rb
590
592
  - spec/spec_helper.rb