ollama-ruby 0.4.0 → 0.5.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 +103 -0
- data/README.md +19 -16
- data/Rakefile +2 -0
- data/bin/ollama_chat +250 -136
- data/bin/ollama_cli +11 -9
- data/lib/ollama/documents/cache/redis_backed_memory_cache.rb +5 -11
- data/lib/ollama/documents/cache/redis_cache.rb +11 -5
- data/lib/ollama/documents/splitters/character.rb +8 -6
- data/lib/ollama/documents/splitters/semantic.rb +1 -1
- data/lib/ollama/documents.rb +8 -5
- data/lib/ollama/utils/fetcher.rb +38 -7
- data/lib/ollama/utils/file_argument.rb +2 -4
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama.rb +1 -0
- data/ollama-ruby.gemspec +6 -4
- data/spec/ollama/documents/redis_backed_memory_cache_spec.rb +11 -0
- data/spec/ollama/documents/redis_cache_spec.rb +21 -1
- data/spec/ollama/documents/splitters/character_spec.rb +28 -14
- data/spec/ollama/utils/fetcher_spec.rb +40 -0
- metadata +32 -4
data/bin/ollama_chat
CHANGED
@@ -5,6 +5,8 @@ include Ollama
|
|
5
5
|
require 'term/ansicolor'
|
6
6
|
include Term::ANSIColor
|
7
7
|
require 'tins'
|
8
|
+
require 'tins/xt/full'
|
9
|
+
require 'tins/xt/hash_union'
|
8
10
|
include Tins::GO
|
9
11
|
require 'reline'
|
10
12
|
require 'reverse_markdown'
|
@@ -23,16 +25,23 @@ class OllamaChatConfig
|
|
23
25
|
DEFAULT_CONFIG = <<~EOT
|
24
26
|
---
|
25
27
|
url: <%= ENV['OLLAMA_URL'] || 'http://%s' % ENV.fetch('OLLAMA_HOST') %>
|
28
|
+
proxy: null # http://localhost:8080
|
26
29
|
model:
|
27
30
|
name: <%= ENV.fetch('OLLAMA_CHAT_MODEL', 'llama3.1') %>
|
28
31
|
options:
|
29
32
|
num_ctx: 8192
|
30
33
|
prompts:
|
31
34
|
system: <%= ENV.fetch('OLLAMA_CHAT_SYSTEM', 'null') %>
|
35
|
+
embed: "This source was now embedded: %{source}"
|
32
36
|
summarize: |
|
33
|
-
Generate an abstract summary of the content in this document
|
37
|
+
Generate an abstract summary of the content in this document using
|
38
|
+
%{words} words:
|
34
39
|
|
35
|
-
%
|
40
|
+
%{source_content}
|
41
|
+
web: |
|
42
|
+
Answer the the query %{query} using these sources and summaries:
|
43
|
+
|
44
|
+
%{results}
|
36
45
|
voice: Samantha
|
37
46
|
markdown: true
|
38
47
|
embedding:
|
@@ -42,7 +51,7 @@ class OllamaChatConfig
|
|
42
51
|
options: {}
|
43
52
|
# Retrieval prompt template:
|
44
53
|
prompt: 'Represent this sentence for searching relevant passages: %s'
|
45
|
-
collection: <%= ENV
|
54
|
+
collection: <%= ENV['OLLAMA_CHAT_COLLECTION'] %>
|
46
55
|
found_texts_size: 4096
|
47
56
|
found_texts_count: null
|
48
57
|
splitter:
|
@@ -50,8 +59,12 @@ class OllamaChatConfig
|
|
50
59
|
chunk_size: 1024
|
51
60
|
cache: Ollama::Documents::Cache::RedisBackedMemoryCache
|
52
61
|
redis:
|
53
|
-
|
62
|
+
documents:
|
63
|
+
url: <%= ENV.fetch('REDIS_URL', 'null') %>
|
64
|
+
expiring:
|
65
|
+
url: <%= ENV.fetch('REDIS_EXPIRING_URL', 'null') %>
|
54
66
|
debug: <%= ENV['OLLAMA_CHAT_DEBUG'].to_i == 1 ? true : false %>
|
67
|
+
ssl_no_verify: []
|
55
68
|
EOT
|
56
69
|
|
57
70
|
def initialize(filename = nil)
|
@@ -112,8 +125,8 @@ class FollowChat
|
|
112
125
|
end
|
113
126
|
content = response.message&.content
|
114
127
|
@messages.last.content << content
|
115
|
-
if @markdown and @messages.last.content.
|
116
|
-
markdown_content = Utils::ANSIMarkdown.parse(
|
128
|
+
if @markdown and content = @messages.last.content.full?
|
129
|
+
markdown_content = Utils::ANSIMarkdown.parse(content)
|
117
130
|
@output.print clear_screen, move_home, @user, ?\n, markdown_content
|
118
131
|
else
|
119
132
|
@output.print content
|
@@ -217,32 +230,25 @@ def save_conversation(filename, messages)
|
|
217
230
|
end
|
218
231
|
|
219
232
|
def message_type(images)
|
220
|
-
|
221
|
-
?📸
|
222
|
-
else
|
223
|
-
?📨
|
224
|
-
end
|
233
|
+
images.present? ? ?📸 : ?📨
|
225
234
|
end
|
226
235
|
|
227
|
-
def list_conversation(messages,
|
228
|
-
messages.
|
236
|
+
def list_conversation(messages, last = nil)
|
237
|
+
last = (last || messages.size).clamp(0, messages.size)
|
238
|
+
messages[-last..-1].to_a.each do |m|
|
229
239
|
role_color = case m.role
|
230
240
|
when 'user' then 172
|
231
241
|
when 'assistant' then 111
|
232
242
|
when 'system' then 213
|
233
243
|
else 210
|
234
244
|
end
|
235
|
-
content =
|
236
|
-
Utils::ANSIMarkdown.parse(m.content)
|
237
|
-
else
|
238
|
-
m.content
|
239
|
-
end
|
245
|
+
content = m.content.full? { $markdown ? Utils::ANSIMarkdown.parse(_1) : _1 }
|
240
246
|
message_text = message_type(m.images) + " "
|
241
247
|
message_text += bold { color(role_color) { m.role } }
|
242
248
|
message_text += ":\n#{content}"
|
243
|
-
|
244
|
-
message_text += "\nImages: " + italic {
|
245
|
-
|
249
|
+
m.images.full? { |images|
|
250
|
+
message_text += "\nImages: " + italic { images.map(&:path) * ', ' }
|
251
|
+
}
|
246
252
|
puts message_text
|
247
253
|
end
|
248
254
|
end
|
@@ -258,37 +264,37 @@ end
|
|
258
264
|
|
259
265
|
def parse_rss(source_io)
|
260
266
|
feed = RSS::Parser.parse(source_io, false, false)
|
261
|
-
title = <<~
|
267
|
+
title = <<~EOT
|
262
268
|
# #{feed&.channel&.title}
|
263
269
|
|
264
|
-
|
270
|
+
EOT
|
265
271
|
feed.items.inject(title) do |text, item|
|
266
|
-
text << <<~
|
272
|
+
text << <<~EOT
|
267
273
|
## [#{item&.title}](#{item&.link})
|
268
274
|
|
269
275
|
updated on #{item&.pubDate}
|
270
276
|
|
271
277
|
#{reverse_markdown(item&.description)}
|
272
278
|
|
273
|
-
|
279
|
+
EOT
|
274
280
|
end
|
275
281
|
end
|
276
282
|
|
277
283
|
def parse_atom(source_io)
|
278
284
|
feed = RSS::Parser.parse(source_io, false, false)
|
279
|
-
title = <<~
|
285
|
+
title = <<~EOT
|
280
286
|
# #{feed.title.content}
|
281
287
|
|
282
|
-
|
288
|
+
EOT
|
283
289
|
feed.items.inject(title) do |text, item|
|
284
|
-
text << <<~
|
290
|
+
text << <<~EOT
|
285
291
|
## [#{item&.title&.content}](#{item&.link&.href})
|
286
292
|
|
287
293
|
updated on #{item&.updated&.content}
|
288
294
|
|
289
295
|
#{reverse_markdown(item&.content&.content)}
|
290
296
|
|
291
|
-
|
297
|
+
EOT
|
292
298
|
end
|
293
299
|
end
|
294
300
|
|
@@ -325,38 +331,51 @@ def parse_source(source_io)
|
|
325
331
|
reader = PDF::Reader.new(source_io)
|
326
332
|
reader.pages.inject(+'') { |result, page| result << page.text }
|
327
333
|
else
|
328
|
-
STDERR.puts "Cannot
|
334
|
+
STDERR.puts "Cannot embed #{source_io&.content_type} document."
|
329
335
|
return
|
330
336
|
end
|
331
337
|
end
|
332
338
|
|
333
|
-
def
|
339
|
+
def embed_source(source_io, source)
|
334
340
|
embedding_enabled? or return parse_source(source_io)
|
335
|
-
puts "
|
341
|
+
puts "Embedding #{italic { source_io&.content_type }} document #{source.to_s.inspect}."
|
336
342
|
text = parse_source(source_io) or return
|
337
343
|
text.downcase!
|
338
344
|
splitter_config = $config.embedding.splitter
|
339
|
-
inputs =
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
345
|
+
inputs = nil
|
346
|
+
case splitter_config.name
|
347
|
+
when 'Character'
|
348
|
+
splitter = Ollama::Documents::Splitters::Character.new(
|
349
|
+
chunk_size: splitter_config.chunk_size,
|
350
|
+
)
|
351
|
+
inputs = splitter.split(text)
|
352
|
+
when 'RecursiveCharacter'
|
353
|
+
splitter = Ollama::Documents::Splitters::RecursiveCharacter.new(
|
354
|
+
chunk_size: splitter_config.chunk_size,
|
355
|
+
)
|
356
|
+
inputs = splitter.split(text)
|
357
|
+
when 'Semantic'
|
358
|
+
splitter = Ollama::Documents::Splitters::Semantic.new(
|
359
|
+
ollama:, model: $config.embedding.model.name,
|
360
|
+
chunk_size: splitter_config.chunk_size,
|
361
|
+
)
|
362
|
+
inputs = splitter.split(
|
363
|
+
text,
|
364
|
+
breakpoint: splitter_config.breakpoint.to_sym,
|
365
|
+
percentage: splitter_config.percentage?,
|
366
|
+
percentile: splitter_config.percentile?,
|
367
|
+
)
|
368
|
+
inputs = splitter.split(text)
|
369
|
+
end
|
370
|
+
inputs or return
|
371
|
+
source = source.to_s
|
372
|
+
if source.start_with?(?!)
|
373
|
+
source = Ollama::Utils::Width.truncate(
|
374
|
+
source[1..-1].gsub(/\W+/, ?_),
|
375
|
+
length: 10
|
376
|
+
)
|
377
|
+
end
|
378
|
+
$documents.add(inputs, source: source)
|
360
379
|
end
|
361
380
|
|
362
381
|
def add_image(images, source_io, source)
|
@@ -365,10 +384,27 @@ def add_image(images, source_io, source)
|
|
365
384
|
(images << image).uniq!
|
366
385
|
end
|
367
386
|
|
387
|
+
def http_options(url)
|
388
|
+
options = {}
|
389
|
+
if ssl_no_verify = $config.ssl_no_verify?
|
390
|
+
hostname = URI.parse(url).hostname
|
391
|
+
options |= { ssl_verify_peer: !ssl_no_verify.include?(hostname) }
|
392
|
+
end
|
393
|
+
if proxy = $config.proxy?
|
394
|
+
options |= { proxy: }
|
395
|
+
end
|
396
|
+
options
|
397
|
+
end
|
398
|
+
|
368
399
|
def fetch_source(source, &block)
|
369
400
|
case source
|
401
|
+
when %r(\A!(.*))
|
402
|
+
command = $1
|
403
|
+
Utils::Fetcher.execute(command) do |tmp|
|
404
|
+
block.(tmp)
|
405
|
+
end
|
370
406
|
when %r(\Ahttps?://\S+)
|
371
|
-
Utils::Fetcher.get(source, debug: $config.debug) do |tmp|
|
407
|
+
Utils::Fetcher.get(source, debug: $config.debug, http_options: http_options(source)) do |tmp|
|
372
408
|
block.(tmp)
|
373
409
|
end
|
374
410
|
when %r(\Afile://(?:(?:[.-]|[[:alnum:]])*)(/\S*)|([~.]?/\S*))
|
@@ -384,17 +420,45 @@ rescue => e
|
|
384
420
|
STDERR.puts "Cannot add source #{source.to_s.inspect}: #{e}\n#{e.backtrace * ?\n}"
|
385
421
|
end
|
386
422
|
|
387
|
-
def
|
423
|
+
def import(source)
|
424
|
+
puts "Now importing #{source.to_s.inspect}."
|
425
|
+
fetch_source(source) do |source_io|
|
426
|
+
content = parse_source(source_io)
|
427
|
+
content.present? or return
|
428
|
+
source_io.rewind
|
429
|
+
content
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def summarize(source, words: nil)
|
434
|
+
words = words.to_i
|
435
|
+
words < 1 and words = 100
|
388
436
|
puts "Now summarizing #{source.to_s.inspect}."
|
389
437
|
source_content =
|
390
438
|
fetch_source(source) do |source_io|
|
391
439
|
content = parse_source(source_io)
|
392
440
|
content.present? or return
|
393
441
|
source_io.rewind
|
394
|
-
import_document(source_io, source)
|
395
442
|
content
|
396
443
|
end
|
397
|
-
$config.prompts.summarize % source_content
|
444
|
+
$config.prompts.summarize % { source_content:, words: }
|
445
|
+
end
|
446
|
+
|
447
|
+
def embed(source)
|
448
|
+
if embedding_enabled?
|
449
|
+
puts "Now embedding #{source.to_s.inspect}."
|
450
|
+
fetch_source(source) do |source_io|
|
451
|
+
content = parse_source(source_io)
|
452
|
+
content.present? or return
|
453
|
+
source_io.rewind
|
454
|
+
embed_source(source_io, source)
|
455
|
+
content
|
456
|
+
end
|
457
|
+
$config.prompts.embed % { source: }
|
458
|
+
else
|
459
|
+
puts "Embedding is off, so I will just give a small summary of this source."
|
460
|
+
summarize(source)
|
461
|
+
end
|
398
462
|
end
|
399
463
|
|
400
464
|
def parse_content(content, images)
|
@@ -412,7 +476,7 @@ def parse_content(content, images)
|
|
412
476
|
when 'image'
|
413
477
|
add_image(images, source_io, source)
|
414
478
|
when 'text', 'application'
|
415
|
-
|
479
|
+
embed_source(source_io, source)
|
416
480
|
else
|
417
481
|
STDERR.puts(
|
418
482
|
"Cannot fetch #{source.to_s.inspect} with content type "\
|
@@ -438,22 +502,28 @@ ensure
|
|
438
502
|
end
|
439
503
|
|
440
504
|
def choose_collection(default_collection)
|
441
|
-
collections = [ default_collection ] + $documents.collections
|
442
|
-
collections = collections.uniq.sort
|
443
|
-
|
444
|
-
|
505
|
+
collections = [ default_collection ] + $documents.collections
|
506
|
+
collections = collections.compact.map(&:to_s).uniq.sort
|
507
|
+
collections.unshift('[NEW]')
|
508
|
+
collection = Ollama::Utils::Chooser.choose(collections) || default_collection
|
509
|
+
if collection == '[NEW]'
|
510
|
+
print "Enter name of the new collection: "
|
511
|
+
collection = STDIN.gets.chomp
|
512
|
+
end
|
513
|
+
$documents.collection = collection
|
445
514
|
ensure
|
446
515
|
puts "Changing to collection #{bold{collection}}."
|
447
516
|
collection_stats
|
448
517
|
end
|
449
518
|
|
450
519
|
def collection_stats
|
451
|
-
puts <<~
|
520
|
+
puts <<~EOT
|
452
521
|
Collection
|
453
522
|
Name: #{bold{$documents.collection}}
|
523
|
+
Embedding model: #{bold{$embedding_model}}
|
454
524
|
#Embeddings: #{$documents.size}
|
455
525
|
Tags: #{$documents.tags}
|
456
|
-
|
526
|
+
EOT
|
457
527
|
end
|
458
528
|
|
459
529
|
def configure_cache
|
@@ -467,46 +537,78 @@ rescue => e
|
|
467
537
|
Ollama::Documents::MemoryCache
|
468
538
|
end
|
469
539
|
|
470
|
-
def
|
471
|
-
|
540
|
+
def toggle_markdown
|
541
|
+
$markdown = !$markdown
|
542
|
+
show_markdown
|
543
|
+
end
|
544
|
+
|
545
|
+
def show_markdown
|
546
|
+
if $markdown
|
472
547
|
puts "Using ANSI markdown to output content."
|
473
|
-
true
|
474
548
|
else
|
475
549
|
puts "Using plaintext for outputting content."
|
476
|
-
false
|
477
550
|
end
|
551
|
+
$markdown
|
478
552
|
end
|
479
553
|
|
480
|
-
def
|
481
|
-
|
554
|
+
def set_embedding(embedding)
|
555
|
+
$embedding_enabled = embedding
|
556
|
+
show_embedding
|
557
|
+
end
|
558
|
+
|
559
|
+
def show_embedding
|
560
|
+
puts "Embedding is #{embedding_enabled? ? "on" : "off"}."
|
561
|
+
$embedding_enabled
|
482
562
|
end
|
483
563
|
|
484
564
|
def embedding_enabled?
|
485
|
-
$
|
565
|
+
$embedding_enabled && !$embedding_paused
|
486
566
|
end
|
487
567
|
|
488
|
-
def
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
/collection clear [tag]|stats|change|new clear or show stats of current collection
|
499
|
-
/summarize source summarize the URL/file source's content
|
500
|
-
/web [n] query query web search & return n or 1 results
|
501
|
-
/save filename store conversation messages
|
502
|
-
/load filename load conversation messages
|
503
|
-
/quit to quit
|
504
|
-
/help to view this help
|
568
|
+
def toggle_embedding_paused
|
569
|
+
$embedding_paused = !$embedding_paused
|
570
|
+
show_embedding
|
571
|
+
end
|
572
|
+
|
573
|
+
def info
|
574
|
+
puts "Current model is #{bold{$model}}."
|
575
|
+
collection_stats
|
576
|
+
if show_embedding
|
577
|
+
puts "Text splitter is #{bold{$config.embedding.splitter.name}}."
|
505
578
|
end
|
579
|
+
puts "Documents database cache is #{$documents.nil? ? 'n/a' : $documents.cache.class}"
|
580
|
+
show_markdown
|
581
|
+
end
|
582
|
+
|
583
|
+
def clear_messages(messages)
|
584
|
+
messages.delete_if { _1.role != 'system' }
|
585
|
+
end
|
586
|
+
|
587
|
+
def display_chat_help
|
588
|
+
puts <<~EOT
|
589
|
+
/paste to paste content
|
590
|
+
/markdown toggle markdown output
|
591
|
+
/list [n] list the last n / all conversation exchanges
|
592
|
+
/clear clear the whole conversation
|
593
|
+
/clobber clear the conversation and collection
|
594
|
+
/pop [n] pop the last n exchanges, defaults to 1
|
595
|
+
/model change the model
|
596
|
+
/regenerate the last answer message
|
597
|
+
/collection clear [tag]|change clear or show stats of current collection
|
598
|
+
/import source import the source's content
|
599
|
+
/summarize [n] source summarize the source's content in n words
|
600
|
+
/embedding toggle embedding paused or not
|
601
|
+
/embed source embed the source's content
|
602
|
+
/web [n] query query web search & return n or 1 results
|
603
|
+
/save filename store conversation messages
|
604
|
+
/load filename load conversation messages
|
605
|
+
/quit to quit
|
606
|
+
/help to view this help
|
607
|
+
EOT
|
506
608
|
end
|
507
609
|
|
508
610
|
def usage
|
509
|
-
puts <<~
|
611
|
+
puts <<~EOT
|
510
612
|
#{File.basename($0)} [OPTIONS]
|
511
613
|
|
512
614
|
-f CONFIG config file to read
|
@@ -521,7 +623,7 @@ def usage
|
|
521
623
|
-v use voice output
|
522
624
|
-h this help
|
523
625
|
|
524
|
-
|
626
|
+
EOT
|
525
627
|
exit 0
|
526
628
|
end
|
527
629
|
|
@@ -541,23 +643,29 @@ puts "Configuration read from #{config.filename.inspect} is:", $config
|
|
541
643
|
base_url = $opts[?u] || $config.url
|
542
644
|
$ollama = Client.new(base_url:, debug: $config.debug)
|
543
645
|
|
544
|
-
model
|
646
|
+
$model = choose_model($opts[?m], $config.model.name)
|
545
647
|
options = Options[$config.model.options]
|
546
|
-
model_system = pull_model_unless_present(model, options)
|
648
|
+
model_system = pull_model_unless_present($model, options)
|
547
649
|
messages = []
|
650
|
+
set_embedding($config.embedding.enabled && !$opts[?E])
|
651
|
+
|
652
|
+
if voice = ($config.voice if $opts[?v])
|
653
|
+
puts "Using voice #{bold{voice}} to speak."
|
654
|
+
end
|
655
|
+
$markdown = $config.markdown
|
548
656
|
|
549
657
|
if embedding_enabled?
|
550
|
-
embedding_model = $config.embedding.model.name
|
658
|
+
$embedding_model = $config.embedding.model.name
|
551
659
|
embedding_model_options = Options[$config.embedding.model.options]
|
552
|
-
pull_model_unless_present(embedding_model, embedding_model_options)
|
660
|
+
pull_model_unless_present($embedding_model, embedding_model_options)
|
553
661
|
collection = $opts[?C] || $config.embedding.collection
|
554
662
|
$documents = Documents.new(
|
555
663
|
ollama:,
|
556
|
-
model: $
|
664
|
+
model: $embedding_model,
|
557
665
|
model_options: $config.embedding.model.options,
|
558
666
|
collection:,
|
559
667
|
cache: configure_cache,
|
560
|
-
redis_url: $config.redis.url?,
|
668
|
+
redis_url: $config.redis.documents.url?,
|
561
669
|
)
|
562
670
|
|
563
671
|
document_list = $opts[?D].to_a
|
@@ -578,31 +686,26 @@ if embedding_enabled?
|
|
578
686
|
document_list.each_slice(25) do |docs|
|
579
687
|
docs.each do |doc|
|
580
688
|
fetch_source(doc) do |doc_io|
|
581
|
-
|
689
|
+
embed_source(doc_io, doc)
|
582
690
|
end
|
583
691
|
end
|
584
692
|
end
|
585
693
|
end
|
586
694
|
collection_stats
|
587
695
|
else
|
588
|
-
$documents =
|
696
|
+
$documents = Tins::NULL
|
589
697
|
end
|
590
698
|
|
591
|
-
if voice = ($config.voice if $opts[?v])
|
592
|
-
puts "Using voice #{bold{voice}} to speak."
|
593
|
-
end
|
594
|
-
markdown = set_markdown($config.markdown)
|
595
|
-
|
596
699
|
if $opts[?c]
|
597
700
|
messages.concat load_conversation($opts[?c])
|
598
701
|
else
|
599
702
|
if system = Ollama::Utils::FileArgument.
|
600
703
|
get_file_argument($opts[?s], default: $config.prompts.system? || model_system)
|
601
704
|
messages << Message.new(role: 'system', content: system)
|
602
|
-
puts <<~
|
705
|
+
puts <<~EOT
|
603
706
|
Configured system prompt is:
|
604
707
|
#{italic{Ollama::Utils::Width.wrap(system, percentage: 90)}}
|
605
|
-
|
708
|
+
EOT
|
606
709
|
end
|
607
710
|
end
|
608
711
|
|
@@ -618,14 +721,14 @@ loop do
|
|
618
721
|
when %r(^/paste$)
|
619
722
|
puts bold { "Paste your content and then press C-d!" }
|
620
723
|
content = STDIN.read
|
621
|
-
when %r(^/quit$)
|
622
|
-
puts "Goodbye."
|
623
|
-
exit 0
|
624
724
|
when %r(^/markdown$)
|
625
|
-
markdown =
|
725
|
+
$markdown = toggle_markdown
|
626
726
|
next
|
627
|
-
when %r(^/list
|
628
|
-
|
727
|
+
when %r(^/list(?:\s+(\d*))?$)
|
728
|
+
last = if $1
|
729
|
+
2 * $1.to_i
|
730
|
+
end
|
731
|
+
list_conversation(messages, last)
|
629
732
|
next
|
630
733
|
when %r(^/clear$)
|
631
734
|
clear_messages(messages)
|
@@ -636,7 +739,7 @@ loop do
|
|
636
739
|
$documents.clear
|
637
740
|
puts "Cleared messages and collection."
|
638
741
|
next
|
639
|
-
when %r(^/collection\s+(clear|
|
742
|
+
when %r(^/collection\s+(clear|change)(?:\s+(.+))?$)
|
640
743
|
command, arg = $1, $2
|
641
744
|
case command
|
642
745
|
when 'clear'
|
@@ -648,24 +751,26 @@ loop do
|
|
648
751
|
$documents.clear
|
649
752
|
puts "Cleared collection #{bold{collection}}."
|
650
753
|
end
|
651
|
-
when 'stats'
|
652
|
-
collection_stats
|
653
754
|
when 'change'
|
654
755
|
choose_collection(collection)
|
655
|
-
when 'new'
|
656
|
-
print "Enter name of the new collection: "
|
657
|
-
$documents.collection = collection = STDIN.gets.chomp
|
658
|
-
collection_stats
|
659
756
|
end
|
660
757
|
next
|
661
|
-
when %r(
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
758
|
+
when %r(/info)
|
759
|
+
info
|
760
|
+
next
|
761
|
+
when %r(^/pop(?:\s+(\d*))?$)
|
762
|
+
if messages.size > 1
|
763
|
+
n = $1.to_i.clamp(1, Float::INFINITY)
|
764
|
+
r = messages.pop(2 * n)
|
765
|
+
m = r.size / 2
|
766
|
+
puts "Popped the last #{m} exchanges."
|
767
|
+
else
|
768
|
+
puts "No more exchanges you can pop."
|
769
|
+
end
|
770
|
+
list_conversation(messages, 2)
|
666
771
|
next
|
667
772
|
when %r(^/model$)
|
668
|
-
model = choose_model('', model)
|
773
|
+
$model = choose_model('', $model)
|
669
774
|
next
|
670
775
|
when %r(^/regenerate$)
|
671
776
|
if content = messages[-2]&.content
|
@@ -677,23 +782,29 @@ loop do
|
|
677
782
|
end
|
678
783
|
parse_content = false
|
679
784
|
content
|
680
|
-
when %r(^/
|
785
|
+
when %r(^/import\s+(.+))
|
786
|
+
parse_content = false
|
787
|
+
content = import($1) or next
|
788
|
+
when %r(^/summarize\s+(?:(\d+)\s+)?(.+))
|
681
789
|
parse_content = false
|
682
|
-
content = summarize($1) or next
|
790
|
+
content = summarize($2, words: $1) or next
|
791
|
+
when %r(^/embedding$)
|
792
|
+
toggle_embedding_paused
|
793
|
+
next
|
794
|
+
when %r(^/embed\s+(.+))
|
795
|
+
parse_content = false
|
796
|
+
content = embed($1) or next
|
683
797
|
when %r(^/web\s+(?:(\d+)\s+)?(.+))
|
684
798
|
parse_content = false
|
685
799
|
urls = search_web($2, $1.to_i)
|
686
800
|
urls.each do |url|
|
687
|
-
fetch_source(url)
|
688
|
-
import_document(url_io, url)
|
689
|
-
end
|
801
|
+
fetch_source(url) { |url_io| embed_source(url_io, url) }
|
690
802
|
end
|
691
803
|
urls_summarized = urls.map { summarize(_1) }
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
end
|
804
|
+
query = $2.inspect
|
805
|
+
results = urls.zip(urls_summarized).
|
806
|
+
map { |u, s| "%s as \n:%s" % [ u, s ] } * "\n\n"
|
807
|
+
content = $config.prompts.web % { query:, results: }
|
697
808
|
when %r(^/save\s+(.+)$)
|
698
809
|
save_conversation($1, messages)
|
699
810
|
puts "Saved conversation to #$1."
|
@@ -702,6 +813,9 @@ loop do
|
|
702
813
|
messages = load_conversation($1)
|
703
814
|
puts "Loaded conversation from #$1."
|
704
815
|
next
|
816
|
+
when %r(^/quit$)
|
817
|
+
puts "Goodbye."
|
818
|
+
exit 0
|
705
819
|
when %r(^/)
|
706
820
|
display_chat_help
|
707
821
|
next
|
@@ -734,8 +848,8 @@ loop do
|
|
734
848
|
end
|
735
849
|
|
736
850
|
messages << Message.new(role: 'user', content:, images:)
|
737
|
-
handler = FollowChat.new(messages:, markdown
|
738
|
-
ollama.chat(model
|
851
|
+
handler = FollowChat.new(messages:, markdown: $markdown, voice:)
|
852
|
+
ollama.chat(model: $model, messages:, options:, stream: true, &handler)
|
739
853
|
|
740
854
|
if embedding_enabled? && !records.empty?
|
741
855
|
puts "", records.map { |record|
|