ollama-ruby 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|