ollama_chat 0.0.118 → 0.0.120
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 +103 -0
- data/README.md +66 -0
- data/Rakefile +2 -2
- data/lib/ollama_chat/chat.rb +2 -0
- data/lib/ollama_chat/commands.rb +2 -1
- data/lib/ollama_chat/input_content.rb +1 -1
- data/lib/ollama_chat/kramdown_ansi.rb +15 -0
- data/lib/ollama_chat/oc.rb +44 -0
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +13 -4
- data/lib/ollama_chat/parsing.rb +1 -1
- data/lib/ollama_chat/prompt_management.rb +1 -1
- data/lib/ollama_chat/rag_handling.rb +9 -12
- data/lib/ollama_chat/session_management.rb +9 -7
- data/lib/ollama_chat/source_fetching.rb +33 -5
- data/lib/ollama_chat/state_selectors.rb +3 -1
- data/lib/ollama_chat/tools/directory_structure.rb +11 -5
- data/lib/ollama_chat/tools/generate_image.rb +10 -10
- data/lib/ollama_chat/tools/patch_file.rb +1 -1
- data/lib/ollama_chat/tts.rb +325 -0
- data/lib/ollama_chat/utils/analyze_directory.rb +12 -6
- data/lib/ollama_chat/utils/audio_player.rb +147 -0
- data/lib/ollama_chat/utils/chooser.rb +1 -1
- data/lib/ollama_chat/utils/excon_logger.rb +43 -0
- data/lib/ollama_chat/utils/ordered_queue.rb +97 -0
- data/lib/ollama_chat/utils.rb +3 -0
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +1 -0
- data/ollama_chat.gemspec +8 -8
- data/spec/ollama_chat/commands_spec.rb +5 -5
- data/spec/ollama_chat/history_spec.rb +1 -1
- data/spec/ollama_chat/information_spec.rb +1 -1
- data/spec/ollama_chat/input_content_spec.rb +10 -0
- data/spec/ollama_chat/parsing_spec.rb +13 -0
- data/spec/ollama_chat/tool_calling_spec.rb +2 -2
- data/spec/ollama_chat/tools/directory_structure_spec.rb +32 -3
- data/spec/ollama_chat/tools/patch_file_spec.rb +1 -1
- data/spec/ollama_chat/tts_spec.rb +257 -0
- data/spec/ollama_chat/utils/analyze_directory_spec.rb +8 -0
- data/spec/ollama_chat/utils/audio_player_spec.rb +180 -0
- data/spec/ollama_chat/utils/excon_logger_spec.rb +55 -0
- data/spec/ollama_chat/utils/ordered_queue_spec.rb +287 -0
- metadata +22 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cfff20e994aa12d921fd4ce0d050fbaaae9d66ade06fe419d6dbd9ce0dad1a4e
|
|
4
|
+
data.tar.gz: 45f485ac6fc502e6634626d6e891402acf74516491af95a8e26773905924d5a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13a2ca128a857c8980684bf437a5c9470d92b42babed878010db2eaba75de655f5ded18f7baccf3ef27d05191c4edfad7fd1c2a190a3f098ff880e0fefe0606d
|
|
7
|
+
data.tar.gz: c6785ad30f764d56ea19c8bdaaad6dee89385317ecd4a15c6925f12f5637f8a31c4393dcca4143669f2771bd594a9a383ff24e4fdd1e1dcbd070f357128a0446
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,108 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-09-18 v0.0.120
|
|
4
|
+
|
|
5
|
+
* **TTS / audio.cpp integration**
|
|
6
|
+
* Add `OllamaChat::TTS` with parallel block synthesis via worker threads,
|
|
7
|
+
ordered playback through `OrderedQueue`, RIFF-header stripping,
|
|
8
|
+
config-driven streaming, and `join_workers` for test teardown.
|
|
9
|
+
* Add `OllamaChat::Utils::AudioPlayer` (PCM pipe to `ffplay` with silence
|
|
10
|
+
injection to prevent EOF hangs), `OllamaChat::Utils::OrderedQueue`
|
|
11
|
+
(thread-safe min-heap using `Mutex` + `ConditionVariable`), and
|
|
12
|
+
`OllamaChat::Utils::ExconLogger` (routes `Excon` log calls through
|
|
13
|
+
`chat.log(:debug)`).
|
|
14
|
+
* Add `KramdownANSI#kramdown_markdown_remove` to strip markdown to plain
|
|
15
|
+
text for TTS playback.
|
|
16
|
+
* Add `TTS_URL` (required, sensitive, URI-decoded) and
|
|
17
|
+
`AUDIO_PLAYER_CONFIG` (JSON: `command`, `frequency`, `bits`, `pause`)
|
|
18
|
+
to `OC`, enabling `ConstConf::JSONPlugin`.
|
|
19
|
+
* Default `voice.handler` to `OllamaChat::TTS` in `default_config.yml`;
|
|
20
|
+
add `model: 'chatterbox'` and `stream: { enabled: false, format:
|
|
21
|
+
'audio' }`.
|
|
22
|
+
* Simplify `state_selectors.rb` to a single
|
|
23
|
+
`ask_and_send(:voices, model:)` call with `Array()` guard.
|
|
24
|
+
* Switch `generate_image.rb` to `Excon.new(url, logger:)` with
|
|
25
|
+
`ExconLogger`.
|
|
26
|
+
* Add specs: `tts_spec.rb`, `audio_player_spec.rb`,
|
|
27
|
+
`excon_logger_spec.rb`, `ordered_queue_spec.rb`; migrate existing
|
|
28
|
+
specs from `chat.send` to `chat.expose`.
|
|
29
|
+
|
|
30
|
+
* **`include_hidden` in `directory_structure`**
|
|
31
|
+
* Add `include_hidden:` keyword argument to `generate_structure` and
|
|
32
|
+
`recurse_generate_structure` in `analyze_directory.rb` with YARD docs;
|
|
33
|
+
fix the recursive call to forward the flag so dotfiles are honored at
|
|
34
|
+
all depths.
|
|
35
|
+
* Add an `include_hidden` boolean parameter to the `directory_structure`
|
|
36
|
+
tool definition and pass-through in `execute`.
|
|
37
|
+
* `parsing.rb` now passes `include_hidden: true` for all directory
|
|
38
|
+
references.
|
|
39
|
+
* Add `include_hidden` tests to `analyze_directory_spec.rb`,
|
|
40
|
+
`directory_structure_spec.rb`, and `parsing_spec.rb`.
|
|
41
|
+
|
|
42
|
+
* **Housekeeping**
|
|
43
|
+
* Bump `tins` to **~> 1.57** in `Rakefile` and gemspec (kwarg support for
|
|
44
|
+
`ask_and_send`).
|
|
45
|
+
* Add an External Dependencies section to `README.md` with per-executable
|
|
46
|
+
tables, install instructions, and `TTS_URL` documentation.
|
|
47
|
+
|
|
48
|
+
## 2026-09-14 v0.0.119
|
|
49
|
+
|
|
50
|
+
* **Word-boundary fuzzy matching in chooser**
|
|
51
|
+
* Pass `/\b/` as the word-boundary argument to `matcher.similar` in
|
|
52
|
+
`chooser.rb` to filter out partial-word hits, improving ranked results
|
|
53
|
+
for model names containing `/`, `-`, `:`, and `.` separators.
|
|
54
|
+
|
|
55
|
+
* **Parallel embedding via bounded thread pool**
|
|
56
|
+
* Add `concurrency: 3` under `embedding:` in `default_config.yml`.
|
|
57
|
+
* Add `bulk_embed_sources` to `source_fetching.rb` using a
|
|
58
|
+
`Tins::Limited` bounded thread pool with a `Mutex`-guarded results
|
|
59
|
+
array.
|
|
60
|
+
* Change `embed` to accept a `prompt:` kwarg (defaults to
|
|
61
|
+
`prompt(:embed)`) so worker threads share a single resolved template
|
|
62
|
+
instead of each hitting Sequel/SQLite.
|
|
63
|
+
* Replace `STDOUT.puts` with `infobar.puts` in `embed_source` to prevent
|
|
64
|
+
raw output from clobbering the progress bar in concurrent workers.
|
|
65
|
+
* Rewrite the `-p` pattern path in `commands.rb` embedding to build a
|
|
66
|
+
`{source => tags}` Hash and call `bulk_embed_sources`.
|
|
67
|
+
* Rewrite `update_collection` in `rag_handling.rb` to collect both
|
|
68
|
+
modified and new sources into a single Hash and issue one
|
|
69
|
+
`bulk_embed_sources` call instead of a sequential `embed` loop.
|
|
70
|
+
* Update `commands_spec.rb` to stub `bulk_embed_sources` returning an
|
|
71
|
+
array.
|
|
72
|
+
|
|
73
|
+
* **Speed up embedding generation**
|
|
74
|
+
* Implement batch processing in the ollama server.
|
|
75
|
+
|
|
76
|
+
* **Dependency updates**
|
|
77
|
+
* Update `documentrix` minimum version to **0.7.0** in `Rakefile` and
|
|
78
|
+
`ollama_chat.gemspec` to support improved embedding speed.
|
|
79
|
+
|
|
80
|
+
* **Improve session switching and duplication robustness**
|
|
81
|
+
* In `duplicate_session`, replaced defensive `session.lock?` check with a
|
|
82
|
+
direct `session.lock` call, since a freshly duplicated session is never
|
|
83
|
+
yet locked.
|
|
84
|
+
* In `change_session`, added `except_id: session.id` to `choose_session`
|
|
85
|
+
to prevent re-selecting the current session from the chooser.
|
|
86
|
+
* Track `previous_session` as a full object instead of just an ID,
|
|
87
|
+
enabling restoration on lock failure.
|
|
88
|
+
* On lock failure, restore `@session` to the previous session, re-acquire
|
|
89
|
+
its lock, and reset `name` to `??` so the next iteration opens the
|
|
90
|
+
interactive picker instead of retrying the same locked session.
|
|
91
|
+
* Log `previous_session_id` correctly in the "Session changed" info
|
|
92
|
+
message.
|
|
93
|
+
|
|
94
|
+
* **Formatting fix**
|
|
95
|
+
* Add space before `\`.
|
|
96
|
+
|
|
97
|
+
* **Fix `context_spook` returning `nil` instead of context**
|
|
98
|
+
* Reordered `STDOUT.puts` and `ctx.send("to_#{format.downcase}")` in
|
|
99
|
+
`context_spook` so the serialized context is the last expression in the
|
|
100
|
+
`if count > 0` branch, preventing `STDOUT.puts` from clobbering the
|
|
101
|
+
implicit return value.
|
|
102
|
+
* Added a regression spec in `input_content_spec.rb` that exercises the
|
|
103
|
+
`count > 0` path with a real file and `all: true`, asserting the return
|
|
104
|
+
value is a non-blank `String` containing the ingested file path.
|
|
105
|
+
|
|
3
106
|
## 2026-09-05 v0.0.118
|
|
4
107
|
|
|
5
108
|
* Documented the upgrade workflow in the README, adding an "Upgrading"
|
data/README.md
CHANGED
|
@@ -62,6 +62,7 @@ The following environment variables can be used to configure behavior:
|
|
|
62
62
|
- `OLLAMA_URL` - Base URL for Ollama server (default: `http://localhost:11434`)
|
|
63
63
|
- `OLLAMA_HOST` - Base hostname for Ollama server (default: `localhost:11434`)
|
|
64
64
|
- `OLLAMA_SEARXNG_URL` - SearxNG search endpoint URL
|
|
65
|
+
- `TTS_URL` - Base URL for the [audio.cpp](https://github.com/0xshug0/audio.cpp) TTS server (default: `http://localhost:8880`)
|
|
65
66
|
|
|
66
67
|
#### Chat Settings
|
|
67
68
|
- `OLLAMA_CHAT_MODEL` - Default model to use (e.g., `llama3.1`)
|
|
@@ -479,6 +480,71 @@ low-token semantic discovery and `file_context` for a comprehensive view of
|
|
|
479
480
|
modules or patterns. Be cautious with broad patterns in `file_context`, as
|
|
480
481
|
importing too many files can exceed the LLM's context window.*
|
|
481
482
|
|
|
483
|
+
## External Dependencies
|
|
484
|
+
|
|
485
|
+
**ollama_chat** invokes several external executables at runtime. Most are
|
|
486
|
+
optional and only required for specific features; the app degrades gracefully
|
|
487
|
+
when they are absent.
|
|
488
|
+
|
|
489
|
+
### Always Available
|
|
490
|
+
|
|
491
|
+
| Executable | Purpose |
|
|
492
|
+
| :--- | :--- |
|
|
493
|
+
| `sh` | Shell executor tool (`execute_shell`) |
|
|
494
|
+
| `which` | Detecting optional executables on `$PATH` |
|
|
495
|
+
| `git` | Runtime info (branch, SHA, remote origin) |
|
|
496
|
+
|
|
497
|
+
### Editor & Diff
|
|
498
|
+
|
|
499
|
+
| Executable | Purpose | Fallback |
|
|
500
|
+
| :--- | :--- | :--- |
|
|
501
|
+
| `vim` | Editor integration (`/vim`, `open_file_in_editor`) | `vi` |
|
|
502
|
+
| `vimdiff` | Interactive diff (`patch_file`, `/config diff`) | `$DIFF_TOOL` env var |
|
|
503
|
+
| `diff` | Unified diff generation | `$DIFF_COMMAND` env var |
|
|
504
|
+
| `less` / `more` | Pager fallback | `$PAGER` env var |
|
|
505
|
+
|
|
506
|
+
### Optional Feature Dependencies
|
|
507
|
+
|
|
508
|
+
| Executable | Feature | Notes |
|
|
509
|
+
| :--- | :--- | :--- |
|
|
510
|
+
| `ffplay` | TTS audio playback | Part of **FFmpeg**; required only when `voice.handler` is `OllamaChat::TTS`; the TTS backend is [audio.cpp](https://github.com/0xshug0/audio.cpp) |
|
|
511
|
+
| `say` | macOS system TTS | Required only when `voice.handler` is `OllamaChat::Say` (macOS only) |
|
|
512
|
+
| `ctags` | Symbol resolution | Part of **Exuberant Ctags** or **Universal Ctags**; required for `resolve_tag` tool |
|
|
513
|
+
| `twg` | Jira / Teamwork Graph CLI | Required for `execute_jira_twg` and `get_jira_issue` tools |
|
|
514
|
+
| `pandoc` | EPUB document parsing | Part of **Pandoc**; used when importing `.epub` files |
|
|
515
|
+
| `gs` | PDF compression | Part of **Ghostscript**; used when parsing PDFs |
|
|
516
|
+
| `ctc` / `pbcopy` / `wl-copy` | Clipboard copy | `ctc` (default), `pbcopy` (macOS), `wl-copy` (Wayland); configurable via `copy:` in `config.yml` |
|
|
517
|
+
| `pfc` / `pbpaste` / `wl-paste` | Clipboard paste | `pfc` (default), `pbpaste` (macOS), `wl-paste` (Wayland); configurable via `paste:` in `config.yml` |
|
|
518
|
+
| `open` / `xdg-open` | Open URLs in browser | macOS / Linux respectively; used by `browse` tool |
|
|
519
|
+
| `rspec` | Test runner | Required for `run_tests` tool (configurable via `$OLLAMA_CHAT_TOOLS_TEST_RUNNER`) |
|
|
520
|
+
| `docker` | Ruby sandbox | Required for `eval_ruby` tool |
|
|
521
|
+
| `ruby` | Syntax checker | Used by `syntax_checkers.ruby` (`ruby -wc`) |
|
|
522
|
+
| `node` | Syntax checker | Used by `syntax_checkers.node` (`node --check`) |
|
|
523
|
+
| `python3` | Syntax checker | Used by `syntax_checkers.python` (`python3 -m py_compile`) |
|
|
524
|
+
| `bash` | Syntax checker | Used by `syntax_checkers.bash` (`bash -n`) |
|
|
525
|
+
|
|
526
|
+
### Example Installation (Debian / Ubuntu)
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
# Core (usually already present)
|
|
530
|
+
sudo apt install git vim vimdiff diff
|
|
531
|
+
|
|
532
|
+
# TTS (audio.cpp handler)
|
|
533
|
+
sudo apt install ffmpeg
|
|
534
|
+
|
|
535
|
+
# Clipboard
|
|
536
|
+
# ctc is a small helper; see the project README or install from source
|
|
537
|
+
|
|
538
|
+
# Document parsing
|
|
539
|
+
sudo apt install pandoc ghostscript
|
|
540
|
+
|
|
541
|
+
# Symbol resolution
|
|
542
|
+
sudo apt install universal-ctags
|
|
543
|
+
|
|
544
|
+
# eval_ruby
|
|
545
|
+
sudo apt install docker.io
|
|
546
|
+
```
|
|
547
|
+
|
|
482
548
|
## Download
|
|
483
549
|
|
|
484
550
|
The homepage of this app is located at
|
data/Rakefile
CHANGED
|
@@ -40,7 +40,7 @@ GemHadar do
|
|
|
40
40
|
|
|
41
41
|
dependency 'excon', '~> 1.0'
|
|
42
42
|
dependency 'ollama-ruby', '~> 1.23'
|
|
43
|
-
dependency 'documentrix', '>= 0.
|
|
43
|
+
dependency 'documentrix', '>= 0.7.0'
|
|
44
44
|
dependency 'unix_socks', '>= 0.4'
|
|
45
45
|
dependency 'rss', '~> 0.3'
|
|
46
46
|
dependency 'term-ansicolor', '~> 1.11'
|
|
@@ -49,7 +49,7 @@ GemHadar do
|
|
|
49
49
|
dependency 'reverse_markdown', '~> 3.0'
|
|
50
50
|
dependency 'kramdown-ansi', '~> 0.5'
|
|
51
51
|
dependency 'complex_config', '~> 0.22', '>= 0.22.2'
|
|
52
|
-
dependency 'tins', '~> 1.
|
|
52
|
+
dependency 'tins', '~> 1.57'
|
|
53
53
|
dependency 'search_ui', '>= 0.2'
|
|
54
54
|
dependency 'amatch', '~> 0.4'
|
|
55
55
|
dependency 'pdf-reader', '~> 2.0'
|
data/lib/ollama_chat/chat.rb
CHANGED
data/lib/ollama_chat/commands.rb
CHANGED
|
@@ -883,7 +883,8 @@ module OllamaChat::Commands
|
|
|
883
883
|
if opts[?p]
|
|
884
884
|
all = opts.fetch(?a, false)
|
|
885
885
|
patterns = extract_patterns(arg)
|
|
886
|
-
|
|
886
|
+
sources = file_set_each(patterns, all:).map(&:to_s).to_h { [_1, tags] }
|
|
887
|
+
next bulk_embed_sources(sources) || :next
|
|
887
888
|
elsif arg
|
|
888
889
|
next embed(arg, tags:) || :next
|
|
889
890
|
else
|
|
@@ -106,8 +106,8 @@ module OllamaChat::InputContent
|
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
if count > 0
|
|
109
|
-
ctx.send("to_#{format.downcase}")
|
|
110
109
|
STDOUT.puts "✅ Ingesting context now."
|
|
110
|
+
ctx.send("to_#{format.downcase}")
|
|
111
111
|
else
|
|
112
112
|
STDERR.puts "❌ No files in context. ⇨ Cancelled."
|
|
113
113
|
end
|
|
@@ -48,4 +48,19 @@ module OllamaChat::KramdownANSI
|
|
|
48
48
|
content = content.encode(invalid: :replace)
|
|
49
49
|
retry
|
|
50
50
|
end
|
|
51
|
+
|
|
52
|
+
# The kramdown_markdown_remove method strips all markdown formatting from
|
|
53
|
+
# content by first rendering it to ANSI and then removing the escape
|
|
54
|
+
# sequences, yielding plain text suitable for speech synthesis.
|
|
55
|
+
#
|
|
56
|
+
# @param content [ String, nil ] the markdown content to strip.
|
|
57
|
+
# If nil, returns an empty string.
|
|
58
|
+
#
|
|
59
|
+
# @return [ String ] the content with all markdown and ANSI
|
|
60
|
+
# formatting removed, suitable for TTS playback
|
|
61
|
+
def kramdown_markdown_remove(content)
|
|
62
|
+
content = kramdown_ansi_parse(content)
|
|
63
|
+
content.empty? and return ''
|
|
64
|
+
OllamaChat::Utils::StripANSI.strip_ansi(content)
|
|
65
|
+
end
|
|
51
66
|
end
|
data/lib/ollama_chat/oc.rb
CHANGED
|
@@ -14,6 +14,8 @@ require 'shellwords'
|
|
|
14
14
|
module OC
|
|
15
15
|
include ConstConf
|
|
16
16
|
|
|
17
|
+
plugin ConstConf::JSONPlugin
|
|
18
|
+
|
|
17
19
|
description 'Environment config for OllamaChat'
|
|
18
20
|
prefix ''
|
|
19
21
|
|
|
@@ -183,6 +185,48 @@ module OC
|
|
|
183
185
|
default { ENV['USER'] }
|
|
184
186
|
end
|
|
185
187
|
|
|
188
|
+
TTS_URL = set do
|
|
189
|
+
description 'Base URL for the TTS (Text-to-Speech) service'
|
|
190
|
+
default 'http://localhost:8880'
|
|
191
|
+
required true
|
|
192
|
+
sensitive true
|
|
193
|
+
decode { URI.parse(_1) if _1.present? }
|
|
194
|
+
check { value.scheme =~ /\Ahttps?\z/ }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
AUDIO_PLAYER_CONFIG = set do
|
|
198
|
+
description <<~EOT
|
|
199
|
+
Audio player configuration (JSON with four keys):
|
|
200
|
+
|
|
201
|
+
command – Shell command that receives raw PCM on stdin.
|
|
202
|
+
Opened via IO.popen(cmd, "w"); the playback thread
|
|
203
|
+
writes audio bytes and silence chunks to its stdin.
|
|
204
|
+
Default: ffplay s16le 24 kHz mono pipe.
|
|
205
|
+
|
|
206
|
+
frequency – Sample rate in Hz (e.g. 24000). Used to compute
|
|
207
|
+
the byte size of silence chunks: frequency ×
|
|
208
|
+
(bits / 8) × pause.
|
|
209
|
+
|
|
210
|
+
bits – Bit depth per sample (e.g. 16). Determines bytes
|
|
211
|
+
per sample for silence chunk calculation.
|
|
212
|
+
|
|
213
|
+
pause – Seconds of silence injected when the audio queue
|
|
214
|
+
is empty. Prevents the playback process from
|
|
215
|
+
seeing EOF and hanging. Also the sleep duration
|
|
216
|
+
between silence writes.
|
|
217
|
+
EOT
|
|
218
|
+
default <<~EOT
|
|
219
|
+
{
|
|
220
|
+
"command": "ffplay -autoexit -nodisp -loglevel quiet -vn -volume 80 -f s16le -ar 24000 -ch_layout mono -i -",
|
|
221
|
+
"frequency": 24000,
|
|
222
|
+
"bits": 16,
|
|
223
|
+
"pause": 0.01
|
|
224
|
+
}
|
|
225
|
+
EOT
|
|
226
|
+
decode json
|
|
227
|
+
check { value.command && value.frequency && value.bits && value.pause if value.present? }
|
|
228
|
+
end
|
|
229
|
+
|
|
186
230
|
module TOOLS
|
|
187
231
|
description 'Tool specific configuration settings'
|
|
188
232
|
|
|
@@ -445,9 +445,16 @@ prompts:
|
|
|
445
445
|
no reference to yourself or the act of
|
|
446
446
|
writing.
|
|
447
447
|
voice:
|
|
448
|
-
handler: OllamaChat::Say
|
|
448
|
+
#handler: OllamaChat::Say
|
|
449
|
+
#enabled: false
|
|
450
|
+
#default: Samantha
|
|
451
|
+
handler: OllamaChat::TTS
|
|
449
452
|
enabled: false
|
|
450
|
-
default:
|
|
453
|
+
default: miyu
|
|
454
|
+
model: 'chatterbox'
|
|
455
|
+
stream:
|
|
456
|
+
enabled: false # false for offline models, true for streaming
|
|
457
|
+
format: 'audio'
|
|
451
458
|
roles:
|
|
452
459
|
user: 👨 %{sender_name}
|
|
453
460
|
assistant: 🤖 %{sender_name}
|
|
@@ -478,10 +485,12 @@ embedding:
|
|
|
478
485
|
name: snowflake-arctic-embed:137m
|
|
479
486
|
embedding_length: 768
|
|
480
487
|
context_length: 2048
|
|
481
|
-
options:
|
|
488
|
+
options:
|
|
489
|
+
num_batch: 2048
|
|
482
490
|
# Retrieval prompt template:
|
|
483
491
|
prompt: 'Represent this sentence for searching relevant passages: %s'
|
|
484
|
-
batch_size:
|
|
492
|
+
batch_size: 500
|
|
493
|
+
concurrency: 3
|
|
485
494
|
database_filename: null # ':memory:'
|
|
486
495
|
collection: <%= OC::OLLAMA::CHAT::COLLECTION %>
|
|
487
496
|
found_texts_size: 16384
|
data/lib/ollama_chat/parsing.rb
CHANGED
|
@@ -280,7 +280,7 @@ module OllamaChat::Parsing
|
|
|
280
280
|
content.scan(CONTENT_REGEXP).each { |url, file_url, quoted_file, file|
|
|
281
281
|
if file && Pathname.new(file).expand_path.directory?
|
|
282
282
|
max_depth = config.parsing.directory_structure_max_depth
|
|
283
|
-
contents << generate_structure(file, max_depth:).to_json
|
|
283
|
+
contents << generate_structure(file, max_depth:, include_hidden: true).to_json
|
|
284
284
|
next
|
|
285
285
|
end
|
|
286
286
|
check_exist = false
|
|
@@ -432,7 +432,7 @@ module OllamaChat::PromptManagement
|
|
|
432
432
|
end
|
|
433
433
|
|
|
434
434
|
if orphans.any?
|
|
435
|
-
STDOUT.puts "\n#{orphans.count} orphaned prompt(s) "\
|
|
435
|
+
STDOUT.puts "\n#{orphans.count} orphaned prompt(s) " \
|
|
436
436
|
"(no longer in default config):\n"
|
|
437
437
|
orphans.each { |p| STDOUT.puts " • #{bold{p.name}} in #{italic{p.context}}" }
|
|
438
438
|
STDOUT.puts
|
|
@@ -184,12 +184,13 @@ module OllamaChat::RAGHandling
|
|
|
184
184
|
#
|
|
185
185
|
# @return [String] a newline-separated string of embedding result messages.
|
|
186
186
|
def update_collection(collection)
|
|
187
|
+
results = []
|
|
187
188
|
switch_collection(collection) do
|
|
188
189
|
unless col = database_collection?(collection)
|
|
189
190
|
STDERR.puts "❌ Collection #{collection.inspect} not found in database."
|
|
190
|
-
return
|
|
191
|
+
return ''
|
|
191
192
|
end
|
|
192
|
-
|
|
193
|
+
sources = {}
|
|
193
194
|
seen = {}
|
|
194
195
|
@documents.each_record do |record|
|
|
195
196
|
source = @documents.normalize_source(record.source) or next
|
|
@@ -199,24 +200,20 @@ module OllamaChat::RAGHandling
|
|
|
199
200
|
infobar.puts "Source #{source.to_s.inspect} is unmodified. => Skipping."
|
|
200
201
|
next
|
|
201
202
|
end
|
|
202
|
-
|
|
203
|
+
sources[source] = record.tags_set
|
|
203
204
|
@documents.source_remove(source)
|
|
204
|
-
r = embed(source, tags:) or next
|
|
205
|
-
results << r
|
|
206
205
|
end
|
|
207
206
|
|
|
208
207
|
if patterns = col.patterns.full?
|
|
209
|
-
all_file_set(patterns).
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
r = embed(file.to_s, tags: []) or next
|
|
213
|
-
results << r
|
|
214
|
-
end
|
|
208
|
+
new_sources = all_file_set(patterns).map(&:to_s).reject { seen.key?(_1) }
|
|
209
|
+
new_sources.each { seen[_1] = true }
|
|
210
|
+
new_sources.each { sources[_1] = [] }
|
|
215
211
|
end
|
|
216
212
|
|
|
213
|
+
results.concat(bulk_embed_sources(sources))
|
|
217
214
|
log(:info, "Collection updated", data: { collection:, sources_updated: results.size })
|
|
218
|
-
results * "\n"
|
|
219
215
|
end
|
|
216
|
+
results * ?\n
|
|
220
217
|
end
|
|
221
218
|
|
|
222
219
|
# Extracts and normalizes file patterns from a shell-style string.
|
|
@@ -219,8 +219,7 @@ module OllamaChat::SessionManagement
|
|
|
219
219
|
@session = session.duplicate
|
|
220
220
|
set_previous_session_on_change(old_session.id)
|
|
221
221
|
session.update(name:)
|
|
222
|
-
session.lock
|
|
223
|
-
"Could not lock session #{session.id} #{session.errors.full?(:inspect)}"
|
|
222
|
+
session.lock
|
|
224
223
|
confirm?(
|
|
225
224
|
prompt: "🔔 Clear messages of duplicated session? (y/n) ",
|
|
226
225
|
yes: /\Ay/i
|
|
@@ -537,9 +536,9 @@ module OllamaChat::SessionManagement
|
|
|
537
536
|
# @param name [String] the name or ID of the session to switch to
|
|
538
537
|
def change_session(name)
|
|
539
538
|
name.full? or name = ??
|
|
540
|
-
|
|
539
|
+
previous_session = nil
|
|
541
540
|
loop do
|
|
542
|
-
if chosen_session = choose_session(name, allow_new: true)
|
|
541
|
+
if chosen_session = choose_session(name, except_id: session.id, allow_new: true)
|
|
543
542
|
if chosen_session.nil? || chosen_session == session
|
|
544
543
|
confirm?(
|
|
545
544
|
prompt: "\n⏎ Same session chosen, Press any key to continue (%s). ",
|
|
@@ -548,7 +547,7 @@ module OllamaChat::SessionManagement
|
|
|
548
547
|
break
|
|
549
548
|
end
|
|
550
549
|
session_close
|
|
551
|
-
|
|
550
|
+
previous_session = session
|
|
552
551
|
@session = chosen_session
|
|
553
552
|
if session.lock?
|
|
554
553
|
messages.read_conversation_jsonl(session.messages.to_s)
|
|
@@ -563,10 +562,13 @@ module OllamaChat::SessionManagement
|
|
|
563
562
|
set_default_persona_name(session.default_persona_name.full? || :none)
|
|
564
563
|
set_current_system_prompt(session.current_system_prompt.full? || 'default')
|
|
565
564
|
session_apply
|
|
566
|
-
log(:info, "Session changed", data: { session_id: session.id, name: session.name, previous_session_id: })
|
|
565
|
+
log(:info, "Session changed", data: { session_id: session.id, name: session.name, previous_session_id: previous_session&.id })
|
|
567
566
|
info_session
|
|
568
567
|
break
|
|
569
568
|
else
|
|
569
|
+
@session = previous_session
|
|
570
|
+
@session.lock
|
|
571
|
+
name = ??
|
|
570
572
|
confirm?(
|
|
571
573
|
prompt: "\n⏎ Session locked: could not switch, Press any key to continue (%s). ",
|
|
572
574
|
timeout: 3
|
|
@@ -579,7 +581,7 @@ module OllamaChat::SessionManagement
|
|
|
579
581
|
end
|
|
580
582
|
end
|
|
581
583
|
ensure
|
|
582
|
-
set_previous_session_on_change(
|
|
584
|
+
set_previous_session_on_change(previous_session&.id)
|
|
583
585
|
session_sync
|
|
584
586
|
end
|
|
585
587
|
|
|
@@ -185,7 +185,7 @@ module OllamaChat::SourceFetching
|
|
|
185
185
|
def embed_source(source_io, source, tags: [], count: nil)
|
|
186
186
|
@embedding.on? or return parse_source(source_io)
|
|
187
187
|
unless @documents.source_modified?(source)
|
|
188
|
-
|
|
188
|
+
infobar.puts "Source #{source.to_s.inspect} already up-to-date. => Skipping."
|
|
189
189
|
log(:info, "Source up-to-date", data: { source: source.to_s })
|
|
190
190
|
return
|
|
191
191
|
end
|
|
@@ -225,9 +225,9 @@ module OllamaChat::SourceFetching
|
|
|
225
225
|
chunks: inputs.size
|
|
226
226
|
})
|
|
227
227
|
if count
|
|
228
|
-
|
|
228
|
+
infobar.puts '%u. %s' % [ count, m ]
|
|
229
229
|
else
|
|
230
|
-
|
|
230
|
+
infobar.puts m
|
|
231
231
|
end
|
|
232
232
|
source = source.to_s
|
|
233
233
|
command = false
|
|
@@ -253,10 +253,14 @@ module OllamaChat::SourceFetching
|
|
|
253
253
|
#
|
|
254
254
|
# @param source [String] The source identifier which can be a command, URL,
|
|
255
255
|
# or file path
|
|
256
|
+
# @param tags [Array<String>] Tags to apply to the embedded source
|
|
257
|
+
# @param prompt [#to_s] The prompt template to format the result with
|
|
258
|
+
# (defaults to `prompt(:embed)`)
|
|
256
259
|
#
|
|
257
260
|
# @return [String, nil] The formatted embedding result or summary message, or
|
|
258
261
|
# nil if the operation fails
|
|
259
|
-
def embed(source, tags: [])
|
|
262
|
+
def embed(source, tags: [], prompt: prompt(:embed))
|
|
263
|
+
prompt = prompt.to_s
|
|
260
264
|
@embedding.on? or return
|
|
261
265
|
fetch_source(source) do |source_io|
|
|
262
266
|
content = parse_source(source_io)
|
|
@@ -264,7 +268,31 @@ module OllamaChat::SourceFetching
|
|
|
264
268
|
source_io.rewind
|
|
265
269
|
embed_source(source_io, source, tags:) or return
|
|
266
270
|
end
|
|
267
|
-
prompt
|
|
271
|
+
prompt.to_s % { source:, collection: }
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Embeds multiple sources concurrently using a bounded thread pool.
|
|
275
|
+
#
|
|
276
|
+
# @param sources_tags [Hash{String => Array}] mapping of source identifier
|
|
277
|
+
# to the tags array to apply to that source
|
|
278
|
+
# @return [Array<String>] results from each successful embed call
|
|
279
|
+
def bulk_embed_sources(sources_tags)
|
|
280
|
+
return sources_tags.map { |s, tags| embed(s, tags:) }.compact if
|
|
281
|
+
sources_tags.size <= 1 or config.embedding.concurrency <= 1
|
|
282
|
+
|
|
283
|
+
results = []
|
|
284
|
+
mutex = Mutex.new
|
|
285
|
+
prompt = prompt(:embed)
|
|
286
|
+
Tins::Limited.new(config.embedding.concurrency, name: 'embed').process do |l|
|
|
287
|
+
sources_tags.each do |s, tags|
|
|
288
|
+
l.execute do
|
|
289
|
+
r = embed(s, prompt:, tags:)
|
|
290
|
+
mutex.synchronize { results << r } if r
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
l.stop
|
|
294
|
+
end
|
|
295
|
+
results
|
|
268
296
|
end
|
|
269
297
|
|
|
270
298
|
private
|
|
@@ -269,7 +269,9 @@ module OllamaChat::StateSelectors
|
|
|
269
269
|
states: OllamaChat::ThinkControl::THINK_MODE_STATES,
|
|
270
270
|
off: OllamaChat::ThinkControl::THINK_MODE_STATES[0, 1],
|
|
271
271
|
)
|
|
272
|
-
list =
|
|
272
|
+
list = Array(
|
|
273
|
+
voice_handler.ask_and_send(:voices, model: config.voice.model?)
|
|
274
|
+
)
|
|
273
275
|
@voices = DatabaseStateSelector.new(
|
|
274
276
|
chat: self,
|
|
275
277
|
attribute: :current_voice,
|
|
@@ -55,6 +55,10 @@ class OllamaChat::Tools::DirectoryStructure
|
|
|
55
55
|
unlimited (defaults to nil)
|
|
56
56
|
EOT
|
|
57
57
|
),
|
|
58
|
+
include_hidden: Tool::Function::Parameters::Property.new(
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
description: 'Include hidden files and directories (dotfiles), (default: false)'
|
|
61
|
+
),
|
|
58
62
|
},
|
|
59
63
|
required: []
|
|
60
64
|
)
|
|
@@ -75,16 +79,18 @@ class OllamaChat::Tools::DirectoryStructure
|
|
|
75
79
|
# @raise [StandardError] if there's an issue with directory traversal or JSON
|
|
76
80
|
# serialization
|
|
77
81
|
def execute(tool_call, **opts)
|
|
78
|
-
chat
|
|
79
|
-
config
|
|
80
|
-
path
|
|
81
|
-
suffix
|
|
82
|
-
max_depth
|
|
82
|
+
chat = opts[:chat]
|
|
83
|
+
config = chat.config
|
|
84
|
+
path = Pathname.new(tool_call.function.arguments.path || '.')
|
|
85
|
+
suffix = tool_call.function.arguments.suffix.full?
|
|
86
|
+
max_depth = tool_call.function.arguments.max_depth.full?
|
|
87
|
+
include_hidden = tool_call.function.arguments.include_hidden
|
|
83
88
|
|
|
84
89
|
structure = generate_structure(
|
|
85
90
|
path,
|
|
86
91
|
max_depth:,
|
|
87
92
|
suffix:,
|
|
93
|
+
include_hidden:,
|
|
88
94
|
exclude: config.tools.functions.directory_structure.exclude?,
|
|
89
95
|
)
|
|
90
96
|
structure.to_json
|
|
@@ -77,13 +77,13 @@ class OllamaChat::Tools::GenerateImage
|
|
|
77
77
|
url = service_url + '/prompt'
|
|
78
78
|
payload = { prompt: workflow }
|
|
79
79
|
started = Time.now
|
|
80
|
-
prompt_id = post_url(url, payload).prompt_id
|
|
80
|
+
prompt_id = post_url(url, payload, chat).prompt_id
|
|
81
81
|
|
|
82
82
|
prompt_id.nil? and raise OllamaChat::OllamaChatError,
|
|
83
83
|
"failed to trigger ComfyUI with #{prompt_id}"
|
|
84
84
|
|
|
85
85
|
# 3. Poll for completion
|
|
86
|
-
filename = poll_for_image(service_url, prompt_id, config)
|
|
86
|
+
filename = poll_for_image(service_url, prompt_id, config, chat)
|
|
87
87
|
|
|
88
88
|
filename.nil? and raise OllamaChat::OllamaChatError,
|
|
89
89
|
'Image generation took too long or failed'
|
|
@@ -122,9 +122,9 @@ class OllamaChat::Tools::GenerateImage
|
|
|
122
122
|
# @param url [URI] the target URL
|
|
123
123
|
# @param payload [Hash] the data to be sent as JSON
|
|
124
124
|
# @return [JSON::GenericObject] the parsed JSON response
|
|
125
|
-
def post_url(url, payload)
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
def post_url(url, payload, chat)
|
|
126
|
+
logger = OllamaChat::Utils::ExconLogger.new(chat)
|
|
127
|
+
response = Excon.new(url, logger:).post(
|
|
128
128
|
body: JSON.dump(payload),
|
|
129
129
|
headers: { 'Content-Type' => 'application/json' },
|
|
130
130
|
expects: 200
|
|
@@ -136,9 +136,9 @@ class OllamaChat::Tools::GenerateImage
|
|
|
136
136
|
#
|
|
137
137
|
# @param url [URI] the target URL
|
|
138
138
|
# @return [JSON::GenericObject] the parsed JSON response
|
|
139
|
-
def get_url(url)
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
def get_url(url, chat)
|
|
140
|
+
logger = OllamaChat::Utils::ExconLogger.new(chat)
|
|
141
|
+
response = Excon.new(url, logger:).get(
|
|
142
142
|
headers: { 'Accept' => 'application/json' },
|
|
143
143
|
expects: 200
|
|
144
144
|
)
|
|
@@ -151,7 +151,7 @@ class OllamaChat::Tools::GenerateImage
|
|
|
151
151
|
# @param prompt_id [String] the ID of the prompt to track
|
|
152
152
|
# @param config [ComplexConfig::Settings] the configuration settings for timeout
|
|
153
153
|
# @return [String, nil] the filename of the generated image, or nil if it timed out
|
|
154
|
-
def poll_for_image(service_url, prompt_id, config)
|
|
154
|
+
def poll_for_image(service_url, prompt_id, config, chat)
|
|
155
155
|
history_url = service_url + '/history'
|
|
156
156
|
filename = nil
|
|
157
157
|
|
|
@@ -159,7 +159,7 @@ class OllamaChat::Tools::GenerateImage
|
|
|
159
159
|
sleep = -(config.tools.functions.generate_image.timeout_duration? || 60)
|
|
160
160
|
|
|
161
161
|
attempt attempts:, sleep:, exception_class: nil do
|
|
162
|
-
response = get_url(history_url)
|
|
162
|
+
response = get_url(history_url, chat)
|
|
163
163
|
output_data = response[prompt_id]
|
|
164
164
|
|
|
165
165
|
if filename = output_data&.outputs&.each_pair&.first&.last&.images&.first&.filename
|