ollama_chat 0.0.87 → 0.0.89
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 +86 -0
- data/lib/ollama_chat/chat.rb +2 -822
- data/lib/ollama_chat/clipboard.rb +4 -4
- data/lib/ollama_chat/commands.rb +873 -0
- data/lib/ollama_chat/database/migrations/004_add_profile_to_model_options.rb +56 -0
- data/lib/ollama_chat/database/models/model_options.rb +1 -2
- data/lib/ollama_chat/dialog.rb +2 -2
- data/lib/ollama_chat/information.rb +14 -3
- data/lib/ollama_chat/input_content.rb +7 -2
- data/lib/ollama_chat/message_format.rb +16 -8
- data/lib/ollama_chat/message_list.rb +20 -12
- data/lib/ollama_chat/message_output.rb +17 -10
- data/lib/ollama_chat/model_handling.rb +57 -34
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +1 -1
- data/lib/ollama_chat/personae_management.rb +1 -1
- data/lib/ollama_chat/source_fetching.rb +15 -21
- data/lib/ollama_chat/switches.rb +62 -5
- data/lib/ollama_chat/tools/directory_structure.rb +2 -1
- data/lib/ollama_chat/utils/analyze_directory.rb +2 -2
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +1 -1
- data/ollama_chat.gemspec +4 -4
- data/spec/ollama_chat/chat_spec.rb +72 -14
- data/spec/ollama_chat/message_list_spec.rb +16 -10
- data/spec/ollama_chat/message_output_spec.rb +31 -3
- data/spec/ollama_chat/source_fetching_spec.rb +1 -1
- data/spec/ollama_chat/switches_spec.rb +81 -4
- data/spec/ollama_chat/utils/analyze_directory_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- metadata +5 -1
data/lib/ollama_chat/chat.rb
CHANGED
|
@@ -34,7 +34,7 @@ class OllamaChat::Chat
|
|
|
34
34
|
include Tins::GO
|
|
35
35
|
include Term::ANSIColor
|
|
36
36
|
include OllamaChat::HTTPHandling
|
|
37
|
-
include OllamaChat::
|
|
37
|
+
include OllamaChat::Commands
|
|
38
38
|
include OllamaChat::Logging
|
|
39
39
|
include OllamaChat::DocumentCache
|
|
40
40
|
include OllamaChat::Switches
|
|
@@ -122,6 +122,7 @@ class OllamaChat::Chat
|
|
|
122
122
|
@tool_call_results = Hash.new { |h, name| h[name] = [] }
|
|
123
123
|
setup_personae_directory
|
|
124
124
|
@opts[?S] and init_server_socket
|
|
125
|
+
info_session
|
|
125
126
|
rescue ComplexConfig::AttributeMissing, ComplexConfig::ConfigurationSyntaxError => e
|
|
126
127
|
fix_config(e)
|
|
127
128
|
end
|
|
@@ -281,827 +282,6 @@ class OllamaChat::Chat
|
|
|
281
282
|
!!@parse_content
|
|
282
283
|
end
|
|
283
284
|
|
|
284
|
-
# Chat commands
|
|
285
|
-
|
|
286
|
-
## Clipboard
|
|
287
|
-
|
|
288
|
-
command(
|
|
289
|
-
name: :copy,
|
|
290
|
-
regexp: %r(^/copy(?:\s+(edit))?$),
|
|
291
|
-
complete: [ 'copy', %w[ edit ] ],
|
|
292
|
-
optional: true,
|
|
293
|
-
help: 'Copy the last response to the clipboard (optionally edit it first)'
|
|
294
|
-
) do |subcommand|
|
|
295
|
-
copy_to_clipboard(edit: subcommand == 'edit')
|
|
296
|
-
:next
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
command(
|
|
300
|
-
name: :paste,
|
|
301
|
-
regexp: %r(^/paste(?:\s+(edit))?$),
|
|
302
|
-
complete: [ 'paste', %w[ edit ] ],
|
|
303
|
-
optional: true,
|
|
304
|
-
help: 'Paste content from the clipboard (optionally edit it afterwards)'
|
|
305
|
-
) do |subcommand|
|
|
306
|
-
disable_content_parsing
|
|
307
|
-
paste_from_clipboard(edit: subcommand == 'edit')
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
## Settings
|
|
311
|
-
|
|
312
|
-
command(
|
|
313
|
-
name: :config,
|
|
314
|
-
regexp: %r(^/config(?:\s+(edit|reload))?$),
|
|
315
|
-
complete: [ 'config', %w[ edit reload ] ],
|
|
316
|
-
optional: true,
|
|
317
|
-
help: 'View, edit, or reload configuration'
|
|
318
|
-
) do |subcommand|
|
|
319
|
-
case subcommand
|
|
320
|
-
when 'edit'
|
|
321
|
-
edit_config
|
|
322
|
-
when 'reload'
|
|
323
|
-
reload_config
|
|
324
|
-
else
|
|
325
|
-
display_config
|
|
326
|
-
end
|
|
327
|
-
:next
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
command(
|
|
331
|
-
name: :document_policy,
|
|
332
|
-
regexp: %r(^/document policy$),
|
|
333
|
-
complete: %w[ document policy ],
|
|
334
|
-
help: 'Select a scanning policy for documents'
|
|
335
|
-
) do
|
|
336
|
-
document_policy.choose
|
|
337
|
-
:next
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
command(
|
|
341
|
-
name: :toggle,
|
|
342
|
-
regexp: %r(^/toggle(?:\s+(markdown|stream|location|runtime_info|voice|think_loud|think_strip))?$),
|
|
343
|
-
complete: [ 'toggle', %w[ markdown stream location runtime_info voice think_loud think_strip embedding ] ],
|
|
344
|
-
help: 'Toggle feature switches (markdown, stream, location, runtime_info, voice, think_loud, think_strip, embedding)'
|
|
345
|
-
) do |toggle_name|
|
|
346
|
-
if toggle_name
|
|
347
|
-
send(toggle_name).toggle
|
|
348
|
-
else
|
|
349
|
-
STDOUT.puts "Available toggles: markdown|stream|location|runtime_info|voice|think_loud|embedding"
|
|
350
|
-
end
|
|
351
|
-
:next
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
command(
|
|
355
|
-
name: :toggle_embedding,
|
|
356
|
-
regexp: %r(^/toggle\s+embedding$),
|
|
357
|
-
complete: [],
|
|
358
|
-
help: nil
|
|
359
|
-
) do
|
|
360
|
-
embedding_paused.toggle(show: false)
|
|
361
|
-
embedding.show
|
|
362
|
-
:next
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
command(
|
|
366
|
-
name: :favourite,
|
|
367
|
-
regexp: %r(^/favourite(?:\s+(add|delete))?(?:\s+(model|prompt|system_prompt|persona))$),
|
|
368
|
-
complete: [ 'favourite', %w[ add delete ], %w[ model prompt system_prompt persona ] ],
|
|
369
|
-
help: 'Manage favorites for models, prompts, and personas (add, delete)'
|
|
370
|
-
) do |subcommand, type|
|
|
371
|
-
case subcommand
|
|
372
|
-
when 'add'
|
|
373
|
-
add_favourite(type)
|
|
374
|
-
when 'delete'
|
|
375
|
-
delete_favourite(type)
|
|
376
|
-
end
|
|
377
|
-
:next
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
command(
|
|
381
|
-
name: :model,
|
|
382
|
-
regexp: %r(^/model(?:\s+(change|options|options from session|options to session))$),
|
|
383
|
-
complete: [ 'model', %w[ change options options\ from\ session options\ to\ session ] ],
|
|
384
|
-
help: <<~EOT
|
|
385
|
-
Change the model or manage model options (change, options, options from
|
|
386
|
-
session, options to session)
|
|
387
|
-
EOT
|
|
388
|
-
) do |subcommand|
|
|
389
|
-
case subcommand
|
|
390
|
-
when 'change'
|
|
391
|
-
begin
|
|
392
|
-
use_model
|
|
393
|
-
rescue OllamaChat::UnknownModelError => e
|
|
394
|
-
msg = "Caught #{e.class}: #{e}"
|
|
395
|
-
log(:error, msg, warn: true)
|
|
396
|
-
end
|
|
397
|
-
when 'options'
|
|
398
|
-
edit_model_options(@model)
|
|
399
|
-
when 'options from session'
|
|
400
|
-
copy_model_options_from_session
|
|
401
|
-
when 'options to session'
|
|
402
|
-
copy_model_options_to_session
|
|
403
|
-
end
|
|
404
|
-
:next
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
command(
|
|
408
|
-
name: :system,
|
|
409
|
-
regexp: %r(^/system(?:\s+(change|info|edit|add|delete|list|duplicate|export|import|reset))?(?:\s+(\S+))?$),
|
|
410
|
-
complete: [ 'system', %w[ change info edit add delete list duplicate export import reset ] ],
|
|
411
|
-
optional: true,
|
|
412
|
-
help: <<~EOT
|
|
413
|
-
Manage the system prompt (change, info, edit, add, delete, list, duplicate,
|
|
414
|
-
export, import, reset)
|
|
415
|
-
EOT
|
|
416
|
-
) do |subcommand, filename|
|
|
417
|
-
case subcommand
|
|
418
|
-
when 'add'
|
|
419
|
-
add_new_system_prompt and @messages.show_system_prompt
|
|
420
|
-
when 'delete'
|
|
421
|
-
choose_and_delete_system_prompt
|
|
422
|
-
when 'edit'
|
|
423
|
-
choose_and_edit_system_prompt
|
|
424
|
-
when 'list'
|
|
425
|
-
list_system_prompts
|
|
426
|
-
when 'change'
|
|
427
|
-
change_system_prompt(@system)
|
|
428
|
-
@messages.show_system_prompt
|
|
429
|
-
when 'duplicate'
|
|
430
|
-
duplicate_system_prompt
|
|
431
|
-
when 'import'
|
|
432
|
-
import_system_prompt(filename)
|
|
433
|
-
when 'export'
|
|
434
|
-
export_system_prompt
|
|
435
|
-
when 'info'
|
|
436
|
-
info_system_prompt
|
|
437
|
-
when 'reset'
|
|
438
|
-
if prompt = choose_system_prompt
|
|
439
|
-
if reset_system_prompt_to_default(prompt.name)
|
|
440
|
-
STDOUT.puts "Reset system prompt #{bold{prompt.name}} to default."
|
|
441
|
-
else
|
|
442
|
-
STDOUT.puts "No default value found for system prompt #{bold{prompt.name}}."
|
|
443
|
-
end
|
|
444
|
-
end
|
|
445
|
-
when nil
|
|
446
|
-
@messages.show_system_prompt
|
|
447
|
-
end
|
|
448
|
-
:next
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
command(
|
|
452
|
-
name: :think,
|
|
453
|
-
regexp: %r(^/think$),
|
|
454
|
-
help: 'Configure the think mode for models'
|
|
455
|
-
) do
|
|
456
|
-
think_mode.choose
|
|
457
|
-
:next
|
|
458
|
-
end
|
|
459
|
-
|
|
460
|
-
command(
|
|
461
|
-
name: :tools,
|
|
462
|
-
regexp: %r(^/tools(?:\s+(on|off|enable|disable))?),
|
|
463
|
-
complete: [ 'tools', %w[ on off enable disable ] ],
|
|
464
|
-
optional: true,
|
|
465
|
-
help: 'Manage tool support and enabled tools (on, off, enable, disable)'
|
|
466
|
-
) do |subcommand|
|
|
467
|
-
case subcommand
|
|
468
|
-
when nil
|
|
469
|
-
list_tools
|
|
470
|
-
when 'enable'
|
|
471
|
-
enable_tool
|
|
472
|
-
when 'disable'
|
|
473
|
-
disable_tool
|
|
474
|
-
when 'on'
|
|
475
|
-
tools_support.set(true, show: true)
|
|
476
|
-
when 'off'
|
|
477
|
-
tools_support.set(false, show: true)
|
|
478
|
-
end
|
|
479
|
-
:next
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
command(
|
|
483
|
-
name: :voice,
|
|
484
|
-
regexp: %r(^/voice$),
|
|
485
|
-
help: 'Change the voice output settings'
|
|
486
|
-
) do
|
|
487
|
-
change_voice
|
|
488
|
-
:next
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
## Session
|
|
492
|
-
|
|
493
|
-
command(
|
|
494
|
-
name: :session,
|
|
495
|
-
regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|summarize|delete|model options))?((?:\s+-(?:[sf]))*)(?:\s+(.+))?$),
|
|
496
|
-
complete: [ 'session', %w[ change previous list new duplicate rename summarize delete model\ options ] ],
|
|
497
|
-
optional: true,
|
|
498
|
-
options: '[-s|-f] [name]',
|
|
499
|
-
help: <<~EOT
|
|
500
|
-
Manage chat sessions (change, previous, list, new, duplicate, rename, summarize,
|
|
501
|
-
delete, model options).
|
|
502
|
-
For summarize: -s (single sentence), -f (output to markdown file)
|
|
503
|
-
EOT
|
|
504
|
-
) do |subcommand, opts, name|
|
|
505
|
-
case subcommand
|
|
506
|
-
when nil
|
|
507
|
-
show_session
|
|
508
|
-
when 'list'
|
|
509
|
-
list_sessions
|
|
510
|
-
when 'new'
|
|
511
|
-
set_new_session
|
|
512
|
-
when 'duplicate'
|
|
513
|
-
duplicate_session
|
|
514
|
-
when 'delete'
|
|
515
|
-
delete_session
|
|
516
|
-
when 'rename'
|
|
517
|
-
rename_session
|
|
518
|
-
when 'summarize'
|
|
519
|
-
opts = go_command('fs', opts)
|
|
520
|
-
if opts[?f] and
|
|
521
|
-
filename = ask?(prompt: "❓ Enter filename: ").full? { Pathname.new(_1) }
|
|
522
|
-
then
|
|
523
|
-
if filename.exist? && !confirm?(
|
|
524
|
-
prompt: "🔔 File #{filename.to_s.inspect} already exists, overwrite? (y/n) ",
|
|
525
|
-
yes: /\Ay/i
|
|
526
|
-
)
|
|
527
|
-
then
|
|
528
|
-
STDERR.puts "File not written!"
|
|
529
|
-
next :next
|
|
530
|
-
end
|
|
531
|
-
summary = summarize_session(pretty: true, sentence: opts[?s]) do |content|
|
|
532
|
-
infobar.puts kramdown_ansi_parse(content)
|
|
533
|
-
end
|
|
534
|
-
if summary.full?
|
|
535
|
-
filename.write(summary)
|
|
536
|
-
STDOUT.puts "File successfully written."
|
|
537
|
-
else
|
|
538
|
-
STDERR.puts "Nothing to summarize!"
|
|
539
|
-
next :next
|
|
540
|
-
end
|
|
541
|
-
end
|
|
542
|
-
summary = summarize_session(pretty: true, sentence: opts[?s]) do |content|
|
|
543
|
-
infobar.puts kramdown_ansi_parse(content) << ?\n
|
|
544
|
-
end
|
|
545
|
-
if summary.full?
|
|
546
|
-
use_pager do |output|
|
|
547
|
-
output.puts kramdown_ansi_parse(summary)
|
|
548
|
-
end
|
|
549
|
-
else
|
|
550
|
-
STDERR.puts "Nothing to summarize!"
|
|
551
|
-
next :next
|
|
552
|
-
end
|
|
553
|
-
when 'change'
|
|
554
|
-
change_session(name)
|
|
555
|
-
when 'model options'
|
|
556
|
-
edit_session_model_options
|
|
557
|
-
when 'previous'
|
|
558
|
-
if prev = previous_session
|
|
559
|
-
change_session(prev.id)
|
|
560
|
-
else
|
|
561
|
-
STDOUT.puts "No previous session defined."
|
|
562
|
-
end
|
|
563
|
-
end
|
|
564
|
-
:next
|
|
565
|
-
end
|
|
566
|
-
|
|
567
|
-
## Conversation
|
|
568
|
-
|
|
569
|
-
command(
|
|
570
|
-
name: :list,
|
|
571
|
-
regexp: %r(^/list(?:\s+(\d*))?$),
|
|
572
|
-
options: '[n=1]',
|
|
573
|
-
help: 'List the last n or all conversation exchanges'
|
|
574
|
-
) do
|
|
575
|
-
n = 2 * _1.to_i if _1
|
|
576
|
-
messages.list_conversation(n)
|
|
577
|
-
:next
|
|
578
|
-
end
|
|
579
|
-
|
|
580
|
-
command(
|
|
581
|
-
name: :last,
|
|
582
|
-
regexp: %r(^/last(?:\s+(-p))?(?:\s+(\d*))?$),
|
|
583
|
-
options: '[-p|n=1]',
|
|
584
|
-
help: <<~EOT
|
|
585
|
-
Show the last n or the most recent system/assistant message (-p for plain
|
|
586
|
-
output = no pager)
|
|
587
|
-
EOT
|
|
588
|
-
) do |opts,number|
|
|
589
|
-
opts = go_command('p', opts.to_s)
|
|
590
|
-
n = number.to_i.clamp(1..)
|
|
591
|
-
messages.show_last(n, pager: !opts[?p])
|
|
592
|
-
:next
|
|
593
|
-
end
|
|
594
|
-
|
|
595
|
-
command(
|
|
596
|
-
name: :drop,
|
|
597
|
-
regexp: %r(^/drop(?:\s+(\d*))?$),
|
|
598
|
-
options: '[n=1]',
|
|
599
|
-
help: 'Remove the last n conversation exchanges'
|
|
600
|
-
) do
|
|
601
|
-
messages.drop(_1)
|
|
602
|
-
messages.show_last
|
|
603
|
-
:next
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
command(
|
|
607
|
-
name: :clear,
|
|
608
|
-
regexp: %r(^/clear(?:\s+(messages|images|links|history|tags|all))?$),
|
|
609
|
-
complete: [ 'clear', %w[ messages images links history tags all ] ],
|
|
610
|
-
optional: true,
|
|
611
|
-
help: 'Clear messages, images, links, history, tags or all'
|
|
612
|
-
) do |subcommand|
|
|
613
|
-
if result = clean(subcommand)
|
|
614
|
-
disable_content_parsing
|
|
615
|
-
result
|
|
616
|
-
else
|
|
617
|
-
:next
|
|
618
|
-
end
|
|
619
|
-
end
|
|
620
|
-
|
|
621
|
-
command(
|
|
622
|
-
name: :links,
|
|
623
|
-
regexp: %r(^/links(?:\s+(clear))?$),
|
|
624
|
-
complete: [ 'links', %w[ clear ] ],
|
|
625
|
-
optional: true,
|
|
626
|
-
help: 'Clear links used in the chat',
|
|
627
|
-
) do |subcommand|
|
|
628
|
-
manage_links(subcommand)
|
|
629
|
-
:next
|
|
630
|
-
end
|
|
631
|
-
|
|
632
|
-
command(
|
|
633
|
-
name: :regenerate,
|
|
634
|
-
regexp: %r(^/regenerate(?:\s+(edit))?$),
|
|
635
|
-
complete: [ 'regenerate', %w[ edit ] ],
|
|
636
|
-
optional: true,
|
|
637
|
-
help: 'Regenerate the last response (optionally edit the user message)'
|
|
638
|
-
) do |subcommand|
|
|
639
|
-
if message = messages.find_last { !_1.tool? && _1.role == 'user' }
|
|
640
|
-
content = message.content.to_s
|
|
641
|
-
messages.drop(1)
|
|
642
|
-
if subcommand == 'edit'
|
|
643
|
-
content = edit_text(content)
|
|
644
|
-
end
|
|
645
|
-
else
|
|
646
|
-
STDOUT.puts "Not enough messages in this conversation."
|
|
647
|
-
next :redo
|
|
648
|
-
end
|
|
649
|
-
disable_content_parsing
|
|
650
|
-
content
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
command(
|
|
654
|
-
name: :prompt,
|
|
655
|
-
regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset))?(?:\s+(\S+))?$),
|
|
656
|
-
complete: [ 'prompt', %w[ edit info add delete list duplicate import export reset ] ],
|
|
657
|
-
optional: true,
|
|
658
|
-
help: <<~EOT,
|
|
659
|
-
Manage preset prompt templates or prefill the prompt (edit, info, add,
|
|
660
|
-
delete, list, duplicate, import, export, reset)
|
|
661
|
-
EOT
|
|
662
|
-
) do |subcommand, filename|
|
|
663
|
-
case subcommand
|
|
664
|
-
when 'add'
|
|
665
|
-
add_new_prompt
|
|
666
|
-
when 'delete'
|
|
667
|
-
choose_and_delete_prompt
|
|
668
|
-
when 'edit'
|
|
669
|
-
choose_and_edit_prompt
|
|
670
|
-
when 'list'
|
|
671
|
-
list_prompts
|
|
672
|
-
when 'duplicate'
|
|
673
|
-
duplicate_prompt
|
|
674
|
-
when 'import'
|
|
675
|
-
import_prompt(filename)
|
|
676
|
-
when 'export'
|
|
677
|
-
export_prompt
|
|
678
|
-
when 'info'
|
|
679
|
-
info_prompt
|
|
680
|
-
when 'reset'
|
|
681
|
-
if prompt = choose_prompt(default: true)
|
|
682
|
-
if reset_prompt_to_default(prompt.name)
|
|
683
|
-
STDOUT.puts "Reset prompt #{bold{prompt.name}} to default."
|
|
684
|
-
else
|
|
685
|
-
STDOUT.puts "No default value found for prompt #{bold{prompt.name}}."
|
|
686
|
-
end
|
|
687
|
-
end
|
|
688
|
-
when nil
|
|
689
|
-
@prefill_prompt = choose_prompt&.to_s
|
|
690
|
-
end
|
|
691
|
-
:next
|
|
692
|
-
end
|
|
693
|
-
|
|
694
|
-
command(
|
|
695
|
-
name: :change_response,
|
|
696
|
-
regexp: %r(^/change response$),
|
|
697
|
-
complete: %w[ change response ],
|
|
698
|
-
help: 'Edit the last assistant response in the editor',
|
|
699
|
-
) do
|
|
700
|
-
change_response
|
|
701
|
-
:next
|
|
702
|
-
end
|
|
703
|
-
|
|
704
|
-
command(
|
|
705
|
-
name: :conversation,
|
|
706
|
-
regexp: %r(^/conversation\s+(save|load)((?:\s+-(?:[c]))*)\s+(.+)$),
|
|
707
|
-
complete: [ 'conversation', %w[ save load ] ],
|
|
708
|
-
options: '[-c]',
|
|
709
|
-
help: 'Load conversations or save conversations (-c to clean first)'
|
|
710
|
-
) do |subcommand,opts,path|
|
|
711
|
-
opts = go_command('c', opts.to_s)
|
|
712
|
-
case subcommand
|
|
713
|
-
when 'save'
|
|
714
|
-
save_conversation(path, clean: opts[?c])
|
|
715
|
-
when 'load'
|
|
716
|
-
load_conversation(path)
|
|
717
|
-
end
|
|
718
|
-
:next
|
|
719
|
-
end
|
|
720
|
-
|
|
721
|
-
## Collection
|
|
722
|
-
|
|
723
|
-
command(
|
|
724
|
-
name: :collection,
|
|
725
|
-
regexp: %r(^/collection(?:\s+(change|clear|list|rename|update))?$),
|
|
726
|
-
complete: [ 'collection', %w[ change clear list rename update ] ],
|
|
727
|
-
optional: true,
|
|
728
|
-
help: <<~EOT
|
|
729
|
-
Manage the current RAG document collection: change, clear, list,
|
|
730
|
-
rename, update and show
|
|
731
|
-
EOT
|
|
732
|
-
) do |subcommand|
|
|
733
|
-
case subcommand
|
|
734
|
-
when 'clear'
|
|
735
|
-
clear_collection
|
|
736
|
-
when 'change'
|
|
737
|
-
choose_collection(collection)
|
|
738
|
-
when 'list'
|
|
739
|
-
list_collections
|
|
740
|
-
when 'rename'
|
|
741
|
-
rename_collection(collection)
|
|
742
|
-
when 'update'
|
|
743
|
-
update_collection
|
|
744
|
-
when nil
|
|
745
|
-
collection_stats
|
|
746
|
-
end
|
|
747
|
-
:next
|
|
748
|
-
end
|
|
749
|
-
|
|
750
|
-
## Persona
|
|
751
|
-
|
|
752
|
-
command(
|
|
753
|
-
name: :persona,
|
|
754
|
-
regexp: %r(^/persona(?:\s+(play|load|edit|info|list|add|delete|backup|import|export|duplicate))?$),
|
|
755
|
-
complete: [ 'persona', %w[ play load edit info list add delete backup import export duplicate ] ],
|
|
756
|
-
optional: true,
|
|
757
|
-
help: <<~EOT,
|
|
758
|
-
Manage and activate personas for roleplay (play, load, edit, info, list,
|
|
759
|
-
add, delete, backup, import, export, duplicate)
|
|
760
|
-
EOT
|
|
761
|
-
) do |subcommand|
|
|
762
|
-
disable_content_parsing
|
|
763
|
-
case subcommand
|
|
764
|
-
when 'add'
|
|
765
|
-
add_persona
|
|
766
|
-
:next
|
|
767
|
-
when 'delete'
|
|
768
|
-
delete_persona
|
|
769
|
-
:next
|
|
770
|
-
when 'edit'
|
|
771
|
-
edit_persona
|
|
772
|
-
:next
|
|
773
|
-
when 'backup'
|
|
774
|
-
backup_persona
|
|
775
|
-
:next
|
|
776
|
-
when 'duplicate'
|
|
777
|
-
duplicate_persona
|
|
778
|
-
:next
|
|
779
|
-
when 'import'
|
|
780
|
-
filename = choose_filename('**/*.md')
|
|
781
|
-
if filename and name = import_persona(filename)
|
|
782
|
-
STDOUT.puts "Imported persona as #{name.inspect}."
|
|
783
|
-
end
|
|
784
|
-
:next
|
|
785
|
-
when 'export'
|
|
786
|
-
export_persona
|
|
787
|
-
:next
|
|
788
|
-
when 'info'
|
|
789
|
-
info_persona
|
|
790
|
-
:next
|
|
791
|
-
when 'list'
|
|
792
|
-
list_personae
|
|
793
|
-
:next
|
|
794
|
-
when 'load'
|
|
795
|
-
if result = load_personae
|
|
796
|
-
result
|
|
797
|
-
else
|
|
798
|
-
:next
|
|
799
|
-
end
|
|
800
|
-
when 'play'
|
|
801
|
-
set_default_persona
|
|
802
|
-
:next
|
|
803
|
-
else
|
|
804
|
-
select_persona_path
|
|
805
|
-
:next
|
|
806
|
-
end
|
|
807
|
-
end
|
|
808
|
-
|
|
809
|
-
command(
|
|
810
|
-
name: :character,
|
|
811
|
-
regexp: %r(^/character(?:\s+(info|load|import))(?:\s+(\S+))?$),
|
|
812
|
-
complete: [ 'character', %w[ info load import ] ],
|
|
813
|
-
help: 'Display character info, load or import a character from JSON/PNG as persona'
|
|
814
|
-
) do |subcommand, path|
|
|
815
|
-
path = if path
|
|
816
|
-
Pathname.new(path)
|
|
817
|
-
else
|
|
818
|
-
choose_filename('**/*.{png,json}')
|
|
819
|
-
end
|
|
820
|
-
case
|
|
821
|
-
when path.nil?
|
|
822
|
-
STDOUT.puts 'Cancelled.'
|
|
823
|
-
next :next
|
|
824
|
-
when !path.exist?
|
|
825
|
-
STDERR.puts "Path #{path.to_s.inspect} does not exist!"
|
|
826
|
-
next :next
|
|
827
|
-
end
|
|
828
|
-
data = case path.extname
|
|
829
|
-
when '.json'
|
|
830
|
-
path.read
|
|
831
|
-
when '.png'
|
|
832
|
-
path.open do |io|
|
|
833
|
-
OllamaChat::Utils::PNGCharacterExtractor.extract_character_json(io)
|
|
834
|
-
end
|
|
835
|
-
else
|
|
836
|
-
STDERR.puts "Only json and png characters are supported!"
|
|
837
|
-
next :next
|
|
838
|
-
end
|
|
839
|
-
json_to_yaml = -> d {
|
|
840
|
-
yaml = YAML.dump(JSON(d)).sub(%r{\A---\n}, '')
|
|
841
|
-
Kramdown::ANSI::Width.wrap(
|
|
842
|
-
yaml,
|
|
843
|
-
length: Tins::Terminal.columns * 0.9
|
|
844
|
-
)
|
|
845
|
-
}
|
|
846
|
-
case subcommand
|
|
847
|
-
when 'info'
|
|
848
|
-
puts json_to_yaml.(data)
|
|
849
|
-
:next
|
|
850
|
-
when 'load'
|
|
851
|
-
disable_content_parsing
|
|
852
|
-
data
|
|
853
|
-
when 'import'
|
|
854
|
-
markdown = convert_json_character_to_markdown(data)
|
|
855
|
-
Tempfile.create('character.md') do |tmp|
|
|
856
|
-
tmp.puts markdown
|
|
857
|
-
tmp.flush
|
|
858
|
-
import_persona(Pathname.new(tmp.path))
|
|
859
|
-
end
|
|
860
|
-
:next
|
|
861
|
-
end
|
|
862
|
-
end
|
|
863
|
-
|
|
864
|
-
## Input
|
|
865
|
-
|
|
866
|
-
command(
|
|
867
|
-
name: :compose,
|
|
868
|
-
regexp: %r(^/compose$),
|
|
869
|
-
help: 'Compose a message using the text editor'
|
|
870
|
-
) do
|
|
871
|
-
edit_text.full? or :next
|
|
872
|
-
end
|
|
873
|
-
|
|
874
|
-
command(
|
|
875
|
-
name: :web,
|
|
876
|
-
regexp: %r(^/web\s+(?:(\d+)\s+)?(.+)),
|
|
877
|
-
options: '[number=1] query',
|
|
878
|
-
help: 'Query the web for a specified number of results'
|
|
879
|
-
) do |count, query|
|
|
880
|
-
disable_content_parsing
|
|
881
|
-
web(count, query)
|
|
882
|
-
end
|
|
883
|
-
|
|
884
|
-
command(
|
|
885
|
-
name: :input,
|
|
886
|
-
regexp: %r(^/input(?:\s+(path|context|embedding|summary)(?:\s*(?=\z))?)?((?:\s+-(?:[apr]|c\s*\w+|w\s*\d+|t\s*[-\w\.]+(?:,[-\w\.]+)*))*)(?:\s+(.+))?$),
|
|
887
|
-
optional: true,
|
|
888
|
-
complete: [ 'input', %w[ path context embedding summary ] ],
|
|
889
|
-
options: '[-w|-a|-p|-c <collection>|-t <tags>] [arg…]',
|
|
890
|
-
help: <<~EOT
|
|
891
|
-
Import content from files, URLs, or globs into the context
|
|
892
|
-
Use subcommands: path, context, embedding, summary,
|
|
893
|
-
import (the default).
|
|
894
|
-
Options:
|
|
895
|
-
-p (enable pattern mode to allow using globs/wildcards)
|
|
896
|
-
-w <words> (summary subcommand only, default 100)
|
|
897
|
-
-a (pattern mode only, include all files for patterns)
|
|
898
|
-
-c <collection> use this collection (embedding subcommand only)
|
|
899
|
-
-t <tag1,tag2,…> the custom tags to appy (embedding subcommand only)
|
|
900
|
-
EOT
|
|
901
|
-
) do |input_mode,opts,arg|
|
|
902
|
-
disable_content_parsing
|
|
903
|
-
case input_mode
|
|
904
|
-
when 'summary'
|
|
905
|
-
opts = go_command('paw:', opts)
|
|
906
|
-
if opts[?p]
|
|
907
|
-
words = opts.fetch(?w, 100)
|
|
908
|
-
all = opts.fetch(?a, false)
|
|
909
|
-
arg and patterns = arg.scan(/(\S+)/).flatten
|
|
910
|
-
next provide_file_set_content(patterns, all:) { summarize(_1, words:) } || :next
|
|
911
|
-
elsif arg
|
|
912
|
-
words = opts.fetch(?w, 100)
|
|
913
|
-
source = arg
|
|
914
|
-
next summarize(source, words:) || :next
|
|
915
|
-
else
|
|
916
|
-
STDERR.puts "Need a source to summarize for input!"
|
|
917
|
-
next :next
|
|
918
|
-
end
|
|
919
|
-
when 'context'
|
|
920
|
-
opts = go_command('pa', opts)
|
|
921
|
-
if opts[?p]
|
|
922
|
-
all = opts.fetch(?a, false)
|
|
923
|
-
patterns = arg&.scan(/(\S+)/)&.flatten.full? || [ '**/*' ]
|
|
924
|
-
next context_spook(patterns, all:) || :next
|
|
925
|
-
elsif arg
|
|
926
|
-
next context_spook(Array(arg.to_s), all: true) || :next
|
|
927
|
-
else
|
|
928
|
-
next context_spook(nil) || :next
|
|
929
|
-
end
|
|
930
|
-
when 'embedding'
|
|
931
|
-
opts = go_command('pac:t:', opts)
|
|
932
|
-
switch_collection(opts[?c]) do |other_collection|
|
|
933
|
-
if collection == other_collection and !confirm?(
|
|
934
|
-
prompt: "🔔 Are you sure to embed into current collection #{other_collection.to_s.inspect}? (y/n) ",
|
|
935
|
-
yes: /\Ay/i
|
|
936
|
-
)
|
|
937
|
-
then
|
|
938
|
-
STDOUT.puts 'Cancelled.'
|
|
939
|
-
next :next
|
|
940
|
-
end
|
|
941
|
-
tags = opts[?t].full?(:split, ?,)
|
|
942
|
-
if opts[?p]
|
|
943
|
-
all = opts.fetch(?a, false)
|
|
944
|
-
arg and patterns = arg.scan(/(\S+)/).flatten
|
|
945
|
-
next provide_file_set_content(patterns, all:) { embed(_1, tags:) } || :next
|
|
946
|
-
elsif arg
|
|
947
|
-
next embed(arg, tags:) || :next
|
|
948
|
-
else
|
|
949
|
-
STDERR.puts "Need a source to embed for input!"
|
|
950
|
-
next :next
|
|
951
|
-
end
|
|
952
|
-
end
|
|
953
|
-
when 'path'
|
|
954
|
-
opts = go_command('pa', opts)
|
|
955
|
-
if opts[?p]
|
|
956
|
-
all = opts.fetch(?a, false)
|
|
957
|
-
arg and patterns = arg.scan(/(\S+)/).flatten
|
|
958
|
-
read = -> pathname {
|
|
959
|
-
STDOUT.puts "Reading #{pathname.to_s.inspect}."
|
|
960
|
-
pathname.read
|
|
961
|
-
}
|
|
962
|
-
next provide_file_set_content(patterns, all:, &read) || :next
|
|
963
|
-
elsif arg
|
|
964
|
-
filename = Pathname.new(arg).expand_path
|
|
965
|
-
next filename.file? && filename.read || :next
|
|
966
|
-
else
|
|
967
|
-
STDERR.puts "Need a filename to read for input!"
|
|
968
|
-
next :next
|
|
969
|
-
end
|
|
970
|
-
else
|
|
971
|
-
opts = go_command('pa', opts)
|
|
972
|
-
if opts[?p]
|
|
973
|
-
all = opts.fetch(?a, false)
|
|
974
|
-
arg and patterns = arg.scan(/(\S+)/).flatten
|
|
975
|
-
next provide_file_set_content(patterns, all:) { import(_1) } || :next
|
|
976
|
-
elsif arg
|
|
977
|
-
source = arg
|
|
978
|
-
next import(source) || :next
|
|
979
|
-
else
|
|
980
|
-
STDERR.puts "Need a source to import for input!"
|
|
981
|
-
next :next
|
|
982
|
-
end
|
|
983
|
-
end
|
|
984
|
-
end
|
|
985
|
-
|
|
986
|
-
## Output
|
|
987
|
-
|
|
988
|
-
command(
|
|
989
|
-
name: :pipe,
|
|
990
|
-
regexp: %r(^/pipe\s+(.+)$),
|
|
991
|
-
options: 'path',
|
|
992
|
-
help: 'Pipe the last response into another command\'s stdin',
|
|
993
|
-
) do |command|
|
|
994
|
-
pipe(command)
|
|
995
|
-
:next
|
|
996
|
-
end
|
|
997
|
-
|
|
998
|
-
command(
|
|
999
|
-
name: :vim,
|
|
1000
|
-
regexp: %r(^/vim(?:\s+(.+))?$),
|
|
1001
|
-
help: 'Insert the last message into a Vim server buffer'
|
|
1002
|
-
) do |servername|
|
|
1003
|
-
if message = messages.last
|
|
1004
|
-
vim(servername).insert message.content
|
|
1005
|
-
else
|
|
1006
|
-
STDERR.puts "Warning: No message found to insert into Vim"
|
|
1007
|
-
end
|
|
1008
|
-
:next
|
|
1009
|
-
end
|
|
1010
|
-
|
|
1011
|
-
command(
|
|
1012
|
-
name: :output,
|
|
1013
|
-
regexp: %r(^/output\s+(.+)$),
|
|
1014
|
-
options: 'path',
|
|
1015
|
-
help: 'Save the last response to a file',
|
|
1016
|
-
) do |path|
|
|
1017
|
-
output(path)
|
|
1018
|
-
:next
|
|
1019
|
-
end
|
|
1020
|
-
|
|
1021
|
-
## Actions
|
|
1022
|
-
|
|
1023
|
-
command(
|
|
1024
|
-
name: :reconnect,
|
|
1025
|
-
regexp: %r(^/reconnect$),
|
|
1026
|
-
help: 'Reconnect to the Ollama server'
|
|
1027
|
-
) do
|
|
1028
|
-
STDERR.print green { "Reconnecting to ollama #{base_url.to_s.inspect}…" }
|
|
1029
|
-
connect_ollama
|
|
1030
|
-
STDERR.puts green { " Done." }
|
|
1031
|
-
:next
|
|
1032
|
-
end
|
|
1033
|
-
|
|
1034
|
-
command(
|
|
1035
|
-
name: :quit,
|
|
1036
|
-
regexp: %r(^/(?:quit|exit)$),
|
|
1037
|
-
complete: [ %w[ quit exit ] ],
|
|
1038
|
-
help: 'Quit the application',
|
|
1039
|
-
) do
|
|
1040
|
-
STDOUT.puts "Goodbye."
|
|
1041
|
-
:return
|
|
1042
|
-
end
|
|
1043
|
-
|
|
1044
|
-
## Information
|
|
1045
|
-
|
|
1046
|
-
command(
|
|
1047
|
-
name: :info,
|
|
1048
|
-
regexp: %r(^/info(?:\s+(session|model|runtime|rag))?$),
|
|
1049
|
-
complete: [ 'info', %w[ session model runtime rag ] ],
|
|
1050
|
-
optional: true,
|
|
1051
|
-
help: 'Show info about the session, model, runtime, or RAG',
|
|
1052
|
-
) do |subcommand|
|
|
1053
|
-
use_pager do |output|
|
|
1054
|
-
case subcommand
|
|
1055
|
-
when 'session'
|
|
1056
|
-
info_session(output:)
|
|
1057
|
-
when 'model'
|
|
1058
|
-
info_model(output:)
|
|
1059
|
-
when 'runtime'
|
|
1060
|
-
info_runtime(output:)
|
|
1061
|
-
when 'rag'
|
|
1062
|
-
info_rag(output:)
|
|
1063
|
-
else
|
|
1064
|
-
info(output:)
|
|
1065
|
-
end
|
|
1066
|
-
end
|
|
1067
|
-
:next
|
|
1068
|
-
end
|
|
1069
|
-
|
|
1070
|
-
command(
|
|
1071
|
-
name: :help,
|
|
1072
|
-
regexp: %r(^/help(?:\s+(\S+))?$),
|
|
1073
|
-
optional: true,
|
|
1074
|
-
complete: [ 'help', %w[ me ] ],
|
|
1075
|
-
help: 'View the help menu (use \'me\' for AI help or a pattern to filter)'
|
|
1076
|
-
) do |subcommand|
|
|
1077
|
-
case subcommand
|
|
1078
|
-
when 'me'
|
|
1079
|
-
disable_content_parsing
|
|
1080
|
-
prompt(:help).to_s % { commands: help_message }
|
|
1081
|
-
when /\S+/
|
|
1082
|
-
display_chat_help(Regexp.new(Regexp.quote($&)))
|
|
1083
|
-
:next
|
|
1084
|
-
end
|
|
1085
|
-
end
|
|
1086
|
-
|
|
1087
|
-
command(
|
|
1088
|
-
name: :help_fallback,
|
|
1089
|
-
regexp: %r(^/),
|
|
1090
|
-
complete: []
|
|
1091
|
-
) do
|
|
1092
|
-
display_chat_help
|
|
1093
|
-
:next
|
|
1094
|
-
end
|
|
1095
|
-
|
|
1096
|
-
command(
|
|
1097
|
-
name: :type_quit,
|
|
1098
|
-
regexp: nil,
|
|
1099
|
-
complete: [],
|
|
1100
|
-
) do
|
|
1101
|
-
STDOUT.puts "Type /quit to quit."
|
|
1102
|
-
:next
|
|
1103
|
-
end
|
|
1104
|
-
|
|
1105
285
|
# Handles user input commands and processes chat interactions.
|
|
1106
286
|
#
|
|
1107
287
|
# @param content [String] The input content to process
|