ollama-ruby 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +166 -57
- data/README.md +6 -3
- data/Rakefile +1 -1
- data/bin/ollama_chat +163 -63
- data/bin/ollama_cli +3 -3
- data/bin/ollama_update +2 -0
- data/docker-compose.yml +1 -1
- data/lib/ollama/client.rb +0 -3
- data/lib/ollama/documents/cache/redis_cache.rb +11 -1
- data/lib/ollama/documents.rb +13 -6
- data/lib/ollama/image.rb +3 -1
- data/lib/ollama/utils/cache_fetcher.rb +3 -3
- data/lib/ollama/utils/chooser.rb +9 -3
- data/lib/ollama/utils/fetcher.rb +20 -8
- data/lib/ollama/utils/tags.rb +60 -6
- data/lib/ollama/utils/width.rb +5 -3
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama.rb +4 -0
- data/ollama-ruby.gemspec +6 -6
- data/spec/ollama/image_spec.rb +5 -0
- data/spec/ollama/utils/cache_fetcher_spec.rb +4 -3
- data/spec/ollama/utils/tags_spec.rb +26 -2
- data/spec/ollama/utils/width_spec.rb +82 -0
- metadata +18 -16
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: |
|
@@ -47,7 +49,7 @@ class OllamaChatConfig
|
|
47
49
|
voice:
|
48
50
|
enabled: false
|
49
51
|
default: Samantha
|
50
|
-
list: <%= `say -v
|
52
|
+
list: <%= `say -v ? 2>/dev/null`.lines.map { _1[/^(.+?)\s+[a-z]{2}_[a-zA-Z0-9]{2,}/, 1] }.uniq.sort.to_s.force_encoding('ASCII-8BIT') %>
|
51
53
|
markdown: true
|
52
54
|
stream: true
|
53
55
|
embedding:
|
@@ -57,6 +59,7 @@ class OllamaChatConfig
|
|
57
59
|
options: {}
|
58
60
|
# Retrieval prompt template:
|
59
61
|
prompt: 'Represent this sentence for searching relevant passages: %s'
|
62
|
+
batch_size: 10
|
60
63
|
collection: <%= ENV['OLLAMA_CHAT_COLLECTION'] %>
|
61
64
|
found_texts_size: 4096
|
62
65
|
found_texts_count: null
|
@@ -83,7 +86,7 @@ class OllamaChatConfig
|
|
83
86
|
if @filename == default_path && !retried
|
84
87
|
retried = true
|
85
88
|
mkdir_p File.dirname(default_path)
|
86
|
-
File.secure_write(default_path
|
89
|
+
File.secure_write(default_path, DEFAULT_CONFIG)
|
87
90
|
retry
|
88
91
|
else
|
89
92
|
raise
|
@@ -178,7 +181,7 @@ end
|
|
178
181
|
|
179
182
|
class Switch
|
180
183
|
def initialize(name, msg:, config: $config)
|
181
|
-
@value = !!config.send("#{name}?")
|
184
|
+
@value = [ false, true ].include?(config) ? config : !!config.send("#{name}?")
|
182
185
|
@msg = msg
|
183
186
|
end
|
184
187
|
|
@@ -259,9 +262,21 @@ def setup_switches
|
|
259
262
|
false => "Embedding is currently not performed.",
|
260
263
|
}
|
261
264
|
)
|
265
|
+
|
266
|
+
$location = Switch.new(
|
267
|
+
:location,
|
268
|
+
msg: {
|
269
|
+
true => "Location and localtime enabled.",
|
270
|
+
false => "Location and localtime disabled.",
|
271
|
+
},
|
272
|
+
config: $config.location.enabled
|
273
|
+
)
|
262
274
|
end
|
263
275
|
|
264
276
|
def search_web(query, n = nil)
|
277
|
+
if l = at_location
|
278
|
+
query += " #{at_location}"
|
279
|
+
end
|
265
280
|
n = n.to_i
|
266
281
|
n < 1 and n = 1
|
267
282
|
query = URI.encode_uri_component(query)
|
@@ -401,6 +416,32 @@ def parse_atom(source_io)
|
|
401
416
|
end
|
402
417
|
end
|
403
418
|
|
419
|
+
def pdf_read(io)
|
420
|
+
reader = PDF::Reader.new(io)
|
421
|
+
reader.pages.inject(+'') { |result, page| result << page.text }
|
422
|
+
end
|
423
|
+
|
424
|
+
def ps_read(io)
|
425
|
+
gs = `which gs`.chomp
|
426
|
+
if gs.present?
|
427
|
+
Tempfile.create do |tmp|
|
428
|
+
IO.popen("#{gs} -q -sDEVICE=pdfwrite -sOutputFile=#{tmp.path} -", 'wb') do |gs_io|
|
429
|
+
until io.eof?
|
430
|
+
buffer = io.read(1 << 17)
|
431
|
+
IO.select(nil, [ gs_io ], nil)
|
432
|
+
gs_io.write buffer
|
433
|
+
end
|
434
|
+
gs_io.close
|
435
|
+
File.open(tmp.path, 'rb') do |pdf|
|
436
|
+
pdf_read(pdf)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
else
|
441
|
+
STDERR.puts "Cannot convert #{io&.content_type} whith ghostscript, gs not in path."
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
404
445
|
def parse_source(source_io)
|
405
446
|
case source_io&.content_type
|
406
447
|
when 'text/html'
|
@@ -426,12 +467,11 @@ def parse_source(source_io)
|
|
426
467
|
parse_rss(source_io)
|
427
468
|
when 'application/atom+xml'
|
428
469
|
parse_atom(source_io)
|
429
|
-
when 'application/
|
430
|
-
source_io
|
470
|
+
when 'application/postscript'
|
471
|
+
ps_read(source_io)
|
431
472
|
when 'application/pdf'
|
432
|
-
|
433
|
-
|
434
|
-
when %r(\Atext/), nil
|
473
|
+
pdf_read(source_io)
|
474
|
+
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
475
|
source_io.read
|
436
476
|
else
|
437
477
|
STDERR.puts "Cannot embed #{source_io&.content_type} document."
|
@@ -439,9 +479,14 @@ def parse_source(source_io)
|
|
439
479
|
end
|
440
480
|
end
|
441
481
|
|
442
|
-
def embed_source(source_io, source)
|
482
|
+
def embed_source(source_io, source, count: nil)
|
443
483
|
$embedding.on? or return parse_source(source_io)
|
444
|
-
|
484
|
+
m = "Embedding #{italic { source_io&.content_type }} document #{source.to_s.inspect}."
|
485
|
+
if count
|
486
|
+
puts '%u. %s' % [ count, m ]
|
487
|
+
else
|
488
|
+
puts m
|
489
|
+
end
|
445
490
|
text = parse_source(source_io) or return
|
446
491
|
text.downcase!
|
447
492
|
splitter_config = $config.embedding.splitter
|
@@ -478,7 +523,7 @@ def embed_source(source_io, source)
|
|
478
523
|
length: 10
|
479
524
|
)
|
480
525
|
end
|
481
|
-
$documents.add(inputs, source:
|
526
|
+
$documents.add(inputs, source:, batch_size: $config.embedding.batch_size?)
|
482
527
|
end
|
483
528
|
|
484
529
|
def add_image(images, source_io, source)
|
@@ -515,7 +560,7 @@ def fetch_source(source, &block)
|
|
515
560
|
) do |tmp|
|
516
561
|
block.(tmp)
|
517
562
|
end
|
518
|
-
when %r(\Afile://(
|
563
|
+
when %r(\Afile://(/\S*)|\A((?:\.\.|[~.]?)/\S*))
|
519
564
|
filename = $~.captures.compact.first
|
520
565
|
filename = File.expand_path(filename)
|
521
566
|
Utils::Fetcher.read(filename) do |tmp|
|
@@ -573,10 +618,10 @@ def parse_content(content, images)
|
|
573
618
|
images.clear
|
574
619
|
tags = Utils::Tags.new
|
575
620
|
|
576
|
-
content.scan(%r([.~]?/\S+|https?://\S+|#\S+)).each do |source|
|
621
|
+
content.scan(%r((?:\.\.|[.~])?/\S+|https?://\S+|#\S+)).each do |source|
|
577
622
|
case source
|
578
623
|
when /\A#(\S+)/
|
579
|
-
tags
|
624
|
+
tags.add($1, source:)
|
580
625
|
else
|
581
626
|
source = source.sub(/(["')]|\*+)\z/, '')
|
582
627
|
fetch_source(source) do |source_io|
|
@@ -598,39 +643,53 @@ def parse_content(content, images)
|
|
598
643
|
return content, (tags unless tags.empty?)
|
599
644
|
end
|
600
645
|
|
601
|
-
def choose_model(cli_model,
|
646
|
+
def choose_model(cli_model, current_model)
|
602
647
|
models = ollama.tags.models.map(&:name).sort
|
603
648
|
model = if cli_model == ''
|
604
|
-
Ollama::Utils::Chooser.choose(models) ||
|
649
|
+
Ollama::Utils::Chooser.choose(models) || current_model
|
605
650
|
else
|
606
|
-
cli_model ||
|
651
|
+
cli_model || current_model
|
607
652
|
end
|
608
653
|
ensure
|
609
654
|
puts green { "Connecting to #{model}@#{ollama.base_url} now…" }
|
610
655
|
end
|
611
656
|
|
612
|
-
def
|
613
|
-
|
657
|
+
def ask?(prompt:)
|
658
|
+
print prompt
|
659
|
+
STDIN.gets.chomp
|
660
|
+
end
|
661
|
+
|
662
|
+
def choose_collection(current_collection)
|
663
|
+
collections = [ current_collection ] + $documents.collections
|
614
664
|
collections = collections.compact.map(&:to_s).uniq.sort
|
615
|
-
collections.unshift('[NEW]')
|
616
|
-
collection = Ollama::Utils::Chooser.choose(collections) ||
|
617
|
-
|
618
|
-
|
619
|
-
|
665
|
+
collections.unshift('[EXIT]').unshift('[NEW]')
|
666
|
+
collection = Ollama::Utils::Chooser.choose(collections) || current_collection
|
667
|
+
case collection
|
668
|
+
when '[NEW]'
|
669
|
+
$documents.collection = ask?(prompt: "Enter name of the new collection: ")
|
670
|
+
when nil, '[EXIT]'
|
671
|
+
puts "Exiting chooser."
|
672
|
+
when /./
|
673
|
+
$documents.collection = collection
|
620
674
|
end
|
621
|
-
$documents.collection = collection
|
622
675
|
ensure
|
623
|
-
puts "
|
676
|
+
puts "Using collection #{bold{$documents.collection}}."
|
624
677
|
collection_stats
|
625
678
|
end
|
626
679
|
|
627
680
|
def collection_stats
|
681
|
+
list = $documents.collections.sort.map { |c|
|
682
|
+
' ' + ($documents.collection == c ? bold { c } : c).to_s
|
683
|
+
}.join(?\n)
|
628
684
|
puts <<~EOT
|
629
|
-
Collection
|
685
|
+
Current Collection
|
630
686
|
Name: #{bold{$documents.collection}}
|
631
687
|
Embedding model: #{bold{$embedding_model}}
|
632
688
|
#Embeddings: #{$documents.size}
|
689
|
+
#Tags: #{$documents.tags.size}
|
633
690
|
Tags: #{$documents.tags}
|
691
|
+
List:
|
692
|
+
#{list}
|
634
693
|
EOT
|
635
694
|
end
|
636
695
|
|
@@ -652,13 +711,25 @@ def show_system_prompt
|
|
652
711
|
EOT
|
653
712
|
end
|
654
713
|
|
714
|
+
def at_location
|
715
|
+
if $location.on?
|
716
|
+
location_name = $config.location.name
|
717
|
+
location_decimal_degrees = $config.location.decimal_degrees * ', '
|
718
|
+
localtime = Time.now.iso8601
|
719
|
+
units = $config.location.units
|
720
|
+
$config.prompts.location % {
|
721
|
+
location_name:, location_decimal_degrees:, localtime:, units:,
|
722
|
+
}
|
723
|
+
end.to_s
|
724
|
+
end
|
725
|
+
|
655
726
|
def set_system_prompt(messages, system)
|
656
727
|
$system = system
|
657
728
|
messages.clear
|
658
729
|
messages << Message.new(role: 'system', content: system)
|
659
730
|
end
|
660
731
|
|
661
|
-
def change_system_prompt(messages)
|
732
|
+
def change_system_prompt(messages, default)
|
662
733
|
prompts = $config.system_prompts.attribute_names.compact
|
663
734
|
chosen = Ollama::Utils::Chooser.choose(prompts)
|
664
735
|
system = if chosen
|
@@ -684,6 +755,7 @@ def info
|
|
684
755
|
puts "Documents database cache is #{$documents.nil? ? 'n/a' : bold{$documents.cache.class}}"
|
685
756
|
$markdown.show
|
686
757
|
$stream.show
|
758
|
+
$location.show
|
687
759
|
if $voice.on?
|
688
760
|
puts "Using voice #{bold{$current_voice}} to speak."
|
689
761
|
end
|
@@ -716,6 +788,7 @@ def display_chat_help
|
|
716
788
|
/paste to paste content
|
717
789
|
/markdown toggle markdown output
|
718
790
|
/stream toggle stream output
|
791
|
+
/location toggle location submission
|
719
792
|
/voice( change) toggle voice output or change the voice
|
720
793
|
/list [n] list the last n / all conversation exchanges
|
721
794
|
/clear clear the whole conversation
|
@@ -724,7 +797,8 @@ def display_chat_help
|
|
724
797
|
/model change the model
|
725
798
|
/system change system prompt (clears conversation)
|
726
799
|
/regenerate the last answer message
|
727
|
-
/collection clear
|
800
|
+
/collection( clear|change) change (default) collection or clear
|
801
|
+
/info show information for current session
|
728
802
|
/import source import the source's content
|
729
803
|
/summarize [n] source summarize the source's content in n words
|
730
804
|
/embedding toggle embedding paused or not
|
@@ -739,7 +813,7 @@ end
|
|
739
813
|
|
740
814
|
def usage
|
741
815
|
puts <<~EOT
|
742
|
-
#{File.basename($0)} [OPTIONS]
|
816
|
+
Usage: #{File.basename($0)} [OPTIONS]
|
743
817
|
|
744
818
|
-f CONFIG config file to read
|
745
819
|
-u URL the ollama base url, OLLAMA_URL
|
@@ -790,7 +864,7 @@ if $opts[?c]
|
|
790
864
|
else
|
791
865
|
default = $config.system_prompts.default? || model_system
|
792
866
|
if $opts[?s] == ??
|
793
|
-
change_system_prompt(messages)
|
867
|
+
change_system_prompt(messages, default)
|
794
868
|
else
|
795
869
|
system = Ollama::Utils::FileArgument.get_file_argument($opts[?s], default:)
|
796
870
|
system.present? and set_system_prompt(messages, system)
|
@@ -827,11 +901,13 @@ if $embedding.on?
|
|
827
901
|
end
|
828
902
|
end
|
829
903
|
puts "Collection #{bold{collection}}: Adding #{document_list.size} documents…"
|
904
|
+
count = 1
|
830
905
|
document_list.each_slice(25) do |docs|
|
831
906
|
docs.each do |doc|
|
832
907
|
fetch_source(doc) do |doc_io|
|
833
|
-
embed_source(doc_io, doc)
|
908
|
+
embed_source(doc_io, doc, count:)
|
834
909
|
end
|
910
|
+
count += 1
|
835
911
|
end
|
836
912
|
end
|
837
913
|
end
|
@@ -860,18 +936,21 @@ loop do
|
|
860
936
|
content = Reline.readline(input_prompt, true)&.chomp
|
861
937
|
|
862
938
|
case content
|
863
|
-
when %r(^/paste$)
|
864
|
-
puts bold { "Paste your content and then press C-d!" }
|
865
|
-
content = STDIN.read
|
866
939
|
when %r(^/copy$)
|
867
940
|
copy_to_clipboard(messages)
|
868
941
|
next
|
942
|
+
when %r(^/paste$)
|
943
|
+
puts bold { "Paste your content and then press C-d!" }
|
944
|
+
content = STDIN.read
|
869
945
|
when %r(^/markdown$)
|
870
946
|
$markdown.toggle
|
871
947
|
next
|
872
948
|
when %r(^/stream$)
|
873
949
|
$stream.toggle
|
874
950
|
next
|
951
|
+
when %r(^/location$)
|
952
|
+
$location.toggle
|
953
|
+
next
|
875
954
|
when %r(^/voice(?:\s+(change))?$)
|
876
955
|
if $1 == 'change'
|
877
956
|
change_voice
|
@@ -890,33 +969,14 @@ loop do
|
|
890
969
|
puts "Cleared messages."
|
891
970
|
next
|
892
971
|
when %r(^/clobber$)
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
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)
|
972
|
+
if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
|
973
|
+
clear_messages(messages)
|
974
|
+
$documents.clear
|
975
|
+
puts "Cleared messages and collection #{bold{$documents.collection}}."
|
976
|
+
else
|
977
|
+
puts 'Cancelled.'
|
911
978
|
end
|
912
979
|
next
|
913
|
-
when %r(^/system$)
|
914
|
-
change_system_prompt(messages)
|
915
|
-
info
|
916
|
-
next
|
917
|
-
when %r(/info)
|
918
|
-
info
|
919
|
-
next
|
920
980
|
when %r(^/pop(?:\s+(\d*))?$)
|
921
981
|
if messages.size > 1
|
922
982
|
n = $1.to_i.clamp(1, Float::INFINITY)
|
@@ -931,6 +991,10 @@ loop do
|
|
931
991
|
when %r(^/model$)
|
932
992
|
$model = choose_model('', $model)
|
933
993
|
next
|
994
|
+
when %r(^/system$)
|
995
|
+
change_system_prompt(messages, $system)
|
996
|
+
info
|
997
|
+
next
|
934
998
|
when %r(^/regenerate$)
|
935
999
|
if content = messages[-2]&.content
|
936
1000
|
content.gsub!(/\nConsider these chunks for your answer.*\z/, '')
|
@@ -941,6 +1005,38 @@ loop do
|
|
941
1005
|
end
|
942
1006
|
parse_content = false
|
943
1007
|
content
|
1008
|
+
when %r(^/collection(?:\s+(clear|change))?$)
|
1009
|
+
case $1 || 'change'
|
1010
|
+
when 'clear'
|
1011
|
+
loop do
|
1012
|
+
tags = $documents.tags.add('[EXIT]').add('[ALL]')
|
1013
|
+
tag = Ollama::Utils::Chooser.choose(tags, prompt: 'Clear? %s')
|
1014
|
+
case tag
|
1015
|
+
when nil, '[EXIT]'
|
1016
|
+
puts "Exiting chooser."
|
1017
|
+
break
|
1018
|
+
when '[ALL]'
|
1019
|
+
if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
|
1020
|
+
$documents.clear
|
1021
|
+
puts "Cleared collection #{bold{$documents.collection}}."
|
1022
|
+
break
|
1023
|
+
else
|
1024
|
+
puts 'Cancelled.'
|
1025
|
+
sleep 3
|
1026
|
+
end
|
1027
|
+
when /./
|
1028
|
+
$documents.clear(tags: [ tag ])
|
1029
|
+
puts "Cleared tag #{tag} from collection #{bold{$documents.collection}}."
|
1030
|
+
sleep 3
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
when 'change'
|
1034
|
+
choose_collection($documents.collection)
|
1035
|
+
end
|
1036
|
+
next
|
1037
|
+
when %r(/info)
|
1038
|
+
info
|
1039
|
+
next
|
944
1040
|
when %r(^/import\s+(.+))
|
945
1041
|
parse_content = false
|
946
1042
|
content = import($1) or next
|
@@ -1007,6 +1103,10 @@ loop do
|
|
1007
1103
|
end
|
1008
1104
|
end
|
1009
1105
|
|
1106
|
+
if location = at_location.full?
|
1107
|
+
content += " [#{location} – do not comment on this information, just consider it for eventual queries]"
|
1108
|
+
end
|
1109
|
+
|
1010
1110
|
messages << Message.new(role: 'user', content:, images: images.dup)
|
1011
1111
|
images.clear
|
1012
1112
|
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 <<~
|
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
|
-
|
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/docker-compose.yml
CHANGED
data/lib/ollama/client.rb
CHANGED
@@ -23,7 +23,17 @@ class Ollama::Documents::RedisCache
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def []=(key, value)
|
26
|
-
|
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)
|
data/lib/ollama/documents.rb
CHANGED
@@ -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)
|
@@ -55,10 +55,13 @@ class Ollama::Documents
|
|
55
55
|
@cache.prefix = prefix
|
56
56
|
end
|
57
57
|
|
58
|
-
def add(inputs, batch_size:
|
58
|
+
def add(inputs, batch_size: nil, source: nil, tags: [])
|
59
59
|
inputs = Array(inputs)
|
60
|
-
|
61
|
-
|
60
|
+
batch_size ||= 10
|
61
|
+
tags = Ollama::Utils::Tags.new(tags, source:)
|
62
|
+
if source
|
63
|
+
tags.add(File.basename(source).gsub(/\?.*/, ''), source:)
|
64
|
+
end
|
62
65
|
inputs.map! { |i|
|
63
66
|
text = i.respond_to?(:read) ? i.read : i.to_s
|
64
67
|
text
|
@@ -70,7 +73,7 @@ class Ollama::Documents
|
|
70
73
|
end
|
71
74
|
batches = inputs.each_slice(batch_size).
|
72
75
|
with_infobar(
|
73
|
-
label: "Add #{truncate(tags.to_s, percentage: 25)}",
|
76
|
+
label: "Add #{truncate(tags.to_s(link: false), percentage: 25)}",
|
74
77
|
total: inputs.size
|
75
78
|
)
|
76
79
|
batches.each do |batch|
|
@@ -159,7 +162,11 @@ class Ollama::Documents
|
|
159
162
|
end
|
160
163
|
|
161
164
|
def tags
|
162
|
-
@cache.
|
165
|
+
@cache.each_with_object(Ollama::Utils::Tags.new) do |(_, record), t|
|
166
|
+
record.tags.each do |tag|
|
167
|
+
t.add(tag, source: record.source)
|
168
|
+
end
|
169
|
+
end
|
163
170
|
end
|
164
171
|
|
165
172
|
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
|
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::
|
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
|
29
|
-
@cache
|
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
|
|
data/lib/ollama/utils/chooser.rb
CHANGED
@@ -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
|
-
|
31
|
+
if entry
|
32
|
+
entry
|
33
|
+
else
|
34
|
+
print clear_screen, move_home
|
35
|
+
nil
|
36
|
+
end
|
31
37
|
end
|
32
38
|
end
|