ollama_chat 0.0.8 → 0.0.10
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 +22 -1
- data/README.md +1 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/ollama_chat_send +2 -1
- data/lib/ollama_chat/chat.rb +240 -208
- data/lib/ollama_chat/information.rb +1 -0
- data/lib/ollama_chat/parsing.rb +2 -2
- data/lib/ollama_chat/server_socket.rb +2 -2
- data/lib/ollama_chat/version.rb +1 -1
- data/ollama_chat.gemspec +3 -3
- data/spec/ollama_chat/chat_spec.rb +186 -11
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7e85d325cc40d5cb5dbe01e9971c22da13b4afada35fbb17f130d9430b1f508
|
4
|
+
data.tar.gz: 275b88f332344e7d1664e58c60540243d6cf512b2ba78547fbe784cfd02a6acc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b3a8bbf023960e38a33f4f2c4fc1e75e7e7be4c11213144975b1ad370efd9a71e4d61c76a7de3f3340d80eaa77d53882184f1f7635d7d5f678753e2e2c0dc6f
|
7
|
+
data.tar.gz: 69f57301a1308f75d55842630f7902172c8b67cf37d0ea2aad7564423be0567beb9e70bdff81b6e485cf2b3700040d6039e7c170d6a6bc4ebf65e1bf32bb32f9
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 2025-05-28 v0.0.10
|
4
|
+
|
5
|
+
* Simplify and improve command handling logic.
|
6
|
+
* Update chat input handling to use a single `handle_input` method for all commands.
|
7
|
+
* Add tests for various chat commands, including input handling, document
|
8
|
+
policy selection, summarization, and more.
|
9
|
+
* Improve test coverage for `DocumentCache`, `Information`, and other modules.
|
10
|
+
* Improved handling of commands, e.g. **don't** when sending via `ollama_chat_send` by default.
|
11
|
+
* Added support for sending content to server socket with specific type.
|
12
|
+
|
13
|
+
## 2025-05-26 v0.0.9
|
14
|
+
|
15
|
+
* Improved tag parsing in OllamaChat:
|
16
|
+
* Added regex validation for valid tags to `Documentrix::Utils::Tags`.
|
17
|
+
* Modified `parse_content` method in `OllamaChat::Parsing` to handle valid tag formats.
|
18
|
+
* Updated `scan` methods in `content` processing to more correctly identify tags.
|
19
|
+
* Added option to explicitly open socket for receiving input from `ollama_chat_send`:
|
20
|
+
* Added new command-line option `-S` to enable server socket functionality.
|
21
|
+
* Updated `OllamaChat::Chat` class to include server socket initialization based on the new option.
|
22
|
+
* Modified usage message in `README.md` and `information.rb` files.
|
23
|
+
|
3
24
|
## 2025-05-23 v0.0.8
|
4
25
|
|
5
26
|
* Introduce `fix_config` method to rescue `ComplexConfig` exceptions and prompt
|
@@ -13,7 +34,7 @@
|
|
13
34
|
|
14
35
|
## 2025-05-22 v0.0.7
|
15
36
|
|
16
|
-
* Added `ollama_chat_send` executable in `/bin`, required
|
37
|
+
* Added `ollama_chat_send` executable in `/bin`, required `ollama_chat` gem,
|
17
38
|
sent user input to Ollama server via
|
18
39
|
`OllamaChat::ServerSocket.send_to_server_socket` method and handled
|
19
40
|
exceptions and exit with non-zero status code if an error occurs.
|
data/README.md
CHANGED
@@ -31,6 +31,7 @@ Usage: ollama_chat [OPTIONS]
|
|
31
31
|
-D DOCUMENT load document and add to embeddings collection (multiple)
|
32
32
|
-M use (empty) MemoryCache for this chat session
|
33
33
|
-E disable embeddings for this chat session
|
34
|
+
-S open a socket to receive input from ollama_chat_send
|
34
35
|
-V display the current version number and quit
|
35
36
|
-h this help
|
36
37
|
```
|
data/Rakefile
CHANGED
@@ -31,7 +31,7 @@ GemHadar do
|
|
31
31
|
|
32
32
|
dependency 'excon', '~> 1.0'
|
33
33
|
dependency 'ollama-ruby', '~> 1.0'
|
34
|
-
dependency 'documentrix', '~> 0.0'
|
34
|
+
dependency 'documentrix', '~> 0.0', '>= 0.0.2'
|
35
35
|
dependency 'rss', '~> 0.3'
|
36
36
|
dependency 'term-ansicolor', '~> 1.11'
|
37
37
|
dependency 'redis', '~> 5.0'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.10
|
data/bin/ollama_chat_send
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
require 'ollama_chat'
|
4
4
|
|
5
5
|
begin
|
6
|
-
|
6
|
+
type = (ARGV.shift || 'socket_input').to_sym
|
7
|
+
OllamaChat::ServerSocket.send_to_server_socket(STDIN.read, type:)
|
7
8
|
rescue => e
|
8
9
|
warn "Caught #{e.class}: #{e}"
|
9
10
|
exit 1
|
data/lib/ollama_chat/chat.rb
CHANGED
@@ -32,7 +32,7 @@ class OllamaChat::Chat
|
|
32
32
|
include OllamaChat::ServerSocket
|
33
33
|
|
34
34
|
def initialize(argv: ARGV.dup)
|
35
|
-
@opts = go 'f:u:m:s:c:C:D:
|
35
|
+
@opts = go 'f:u:m:s:c:C:D:MESVh', argv
|
36
36
|
@opts[?h] and exit usage
|
37
37
|
@opts[?V] and exit version
|
38
38
|
@ollama_chat_config = OllamaChat::OllamaChatConfig.new(@opts[?f])
|
@@ -67,7 +67,7 @@ class OllamaChat::Chat
|
|
67
67
|
@current_voice = config.voice.default
|
68
68
|
@images = []
|
69
69
|
init_chat_history
|
70
|
-
init_server_socket
|
70
|
+
@opts[?S] and init_server_socket
|
71
71
|
rescue ComplexConfig::AttributeMissing, ComplexConfig::ConfigurationSyntaxError => e
|
72
72
|
fix_config(e)
|
73
73
|
end
|
@@ -106,232 +106,264 @@ class OllamaChat::Chat
|
|
106
106
|
|
107
107
|
private
|
108
108
|
|
109
|
+
def handle_input(content)
|
110
|
+
case content
|
111
|
+
when %r(^/copy$)
|
112
|
+
copy_to_clipboard
|
113
|
+
:next
|
114
|
+
when %r(^/paste$)
|
115
|
+
paste_from_input
|
116
|
+
when %r(^/markdown$)
|
117
|
+
markdown.toggle
|
118
|
+
:next
|
119
|
+
when %r(^/stream$)
|
120
|
+
stream.toggle
|
121
|
+
:next
|
122
|
+
when %r(^/location$)
|
123
|
+
location.toggle
|
124
|
+
:next
|
125
|
+
when %r(^/voice(?:\s+(change))?$)
|
126
|
+
if $1 == 'change'
|
127
|
+
change_voice
|
128
|
+
else
|
129
|
+
voice.toggle
|
130
|
+
end
|
131
|
+
:next
|
132
|
+
when %r(^/list(?:\s+(\d*))?$)
|
133
|
+
last = 2 * $1.to_i if $1
|
134
|
+
messages.list_conversation(last)
|
135
|
+
:next
|
136
|
+
when %r(^/clear(?:\s+(messages|links|history|all))?$)
|
137
|
+
clean($1)
|
138
|
+
:next
|
139
|
+
when %r(^/clobber$)
|
140
|
+
clean('all')
|
141
|
+
:next
|
142
|
+
when %r(^/drop(?:\s+(\d*))?$)
|
143
|
+
messages.drop($1)
|
144
|
+
messages.list_conversation(2)
|
145
|
+
:next
|
146
|
+
when %r(^/model$)
|
147
|
+
@model = choose_model('', @model)
|
148
|
+
:next
|
149
|
+
when %r(^/system$)
|
150
|
+
change_system_prompt(@system)
|
151
|
+
info
|
152
|
+
:next
|
153
|
+
when %r(^/regenerate$)
|
154
|
+
if content = messages.second_last&.content
|
155
|
+
content.gsub!(/\nConsider these chunks for your answer.*\z/, '')
|
156
|
+
messages.drop(2)
|
157
|
+
else
|
158
|
+
STDOUT.puts "Not enough messages in this conversation."
|
159
|
+
return :redo
|
160
|
+
end
|
161
|
+
@parse_content = false
|
162
|
+
content
|
163
|
+
when %r(^/collection(?:\s+(clear|change))?$)
|
164
|
+
case $1 || 'change'
|
165
|
+
when 'clear'
|
166
|
+
loop do
|
167
|
+
tags = @documents.tags.add('[EXIT]').add('[ALL]')
|
168
|
+
tag = OllamaChat::Utils::Chooser.choose(tags, prompt: 'Clear? %s')
|
169
|
+
case tag
|
170
|
+
when nil, '[EXIT]'
|
171
|
+
STDOUT.puts "Exiting chooser."
|
172
|
+
break
|
173
|
+
when '[ALL]'
|
174
|
+
if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
|
175
|
+
@documents.clear
|
176
|
+
STDOUT.puts "Cleared collection #{bold{@documents.collection}}."
|
177
|
+
break
|
178
|
+
else
|
179
|
+
STDOUT.puts 'Cancelled.'
|
180
|
+
sleep 3
|
181
|
+
end
|
182
|
+
when /./
|
183
|
+
@documents.clear(tags: [ tag ])
|
184
|
+
STDOUT.puts "Cleared tag #{tag} from collection #{bold{@documents.collection}}."
|
185
|
+
sleep 3
|
186
|
+
end
|
187
|
+
end
|
188
|
+
when 'change'
|
189
|
+
choose_collection(@documents.collection)
|
190
|
+
end
|
191
|
+
:next
|
192
|
+
when %r(^/info$)
|
193
|
+
info
|
194
|
+
:next
|
195
|
+
when %r(^/document_policy$)
|
196
|
+
choose_document_policy
|
197
|
+
:next
|
198
|
+
when %r(^/import\s+(.+))
|
199
|
+
@parse_content = false
|
200
|
+
import($1) or :next
|
201
|
+
when %r(^/summarize\s+(?:(\d+)\s+)?(.+))
|
202
|
+
@parse_content = false
|
203
|
+
summarize($2, words: $1) or :next
|
204
|
+
when %r(^/embedding$)
|
205
|
+
embedding_paused.toggle(show: false)
|
206
|
+
embedding.show
|
207
|
+
:next
|
208
|
+
when %r(^/embed\s+(.+))
|
209
|
+
@parse_content = false
|
210
|
+
embed($1) or :next
|
211
|
+
when %r(^/web\s+(?:(\d+)\s+)?(.+))
|
212
|
+
@parse_content = false
|
213
|
+
web($1, $2)
|
214
|
+
when %r(^/save\s+(.+)$)
|
215
|
+
messages.save_conversation($1)
|
216
|
+
STDOUT.puts "Saved conversation to #$1."
|
217
|
+
:next
|
218
|
+
when %r(^/links(?:\s+(clear))?$)
|
219
|
+
manage_links($1)
|
220
|
+
:next
|
221
|
+
when %r(^/load\s+(.+)$)
|
222
|
+
messages.load_conversation($1)
|
223
|
+
if messages.size > 1
|
224
|
+
messages.list_conversation(2)
|
225
|
+
end
|
226
|
+
STDOUT.puts "Loaded conversation from #$1."
|
227
|
+
:next
|
228
|
+
when %r(^/config$)
|
229
|
+
display_config
|
230
|
+
:next
|
231
|
+
when %r(^/quit$), nil
|
232
|
+
STDOUT.puts "Goodbye."
|
233
|
+
:return
|
234
|
+
when %r(^/)
|
235
|
+
display_chat_help
|
236
|
+
:next
|
237
|
+
when /\A\s*\z/
|
238
|
+
STDOUT.puts "Type /quit to quit."
|
239
|
+
:next
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def web(count, query)
|
244
|
+
urls = search_web(query, count.to_i) or return :next
|
245
|
+
urls.each do |url|
|
246
|
+
fetch_source(url) { |url_io| embed_source(url_io, url) }
|
247
|
+
end
|
248
|
+
urls_summarized = urls.map { summarize(_1) }
|
249
|
+
query = $2.inspect
|
250
|
+
results = urls.zip(urls_summarized).
|
251
|
+
map { |u, s| "%s as \n:%s" % [ u, s ] } * "\n\n"
|
252
|
+
config.prompts.web % { query:, results: }
|
253
|
+
end
|
254
|
+
|
255
|
+
def manage_links(command)
|
256
|
+
case command
|
257
|
+
when 'clear'
|
258
|
+
loop do
|
259
|
+
links_options = links.dup.add('[EXIT]').add('[ALL]')
|
260
|
+
link = OllamaChat::Utils::Chooser.choose(links_options, prompt: 'Clear? %s')
|
261
|
+
case link
|
262
|
+
when nil, '[EXIT]'
|
263
|
+
STDOUT.puts "Exiting chooser."
|
264
|
+
break
|
265
|
+
when '[ALL]'
|
266
|
+
if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
|
267
|
+
links.clear
|
268
|
+
STDOUT.puts "Cleared all links in list."
|
269
|
+
break
|
270
|
+
else
|
271
|
+
STDOUT.puts 'Cancelled.'
|
272
|
+
sleep 3
|
273
|
+
end
|
274
|
+
when /./
|
275
|
+
links.delete(link)
|
276
|
+
STDOUT.puts "Cleared link from links in list."
|
277
|
+
sleep 3
|
278
|
+
end
|
279
|
+
end
|
280
|
+
when nil
|
281
|
+
if links.empty?
|
282
|
+
STDOUT.puts "List is empty."
|
283
|
+
else
|
284
|
+
Math.log10(links.size).ceil
|
285
|
+
format = "% #{}s. %s"
|
286
|
+
connect = -> link { hyperlink(link) { link } }
|
287
|
+
STDOUT.puts links.each_with_index.map { |x, i| format % [ i + 1, connect.(x) ] }
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def clean(what)
|
293
|
+
what = 'messages' if what.nil?
|
294
|
+
case what
|
295
|
+
when 'messages'
|
296
|
+
messages.clear
|
297
|
+
STDOUT.puts "Cleared messages."
|
298
|
+
when 'links'
|
299
|
+
links.clear
|
300
|
+
STDOUT.puts "Cleared links."
|
301
|
+
when 'history'
|
302
|
+
clear_history
|
303
|
+
STDOUT.puts "Cleared history."
|
304
|
+
when 'all'
|
305
|
+
if ask?(prompt: 'Are you sure to clear messages and collection? (y/n) ') =~ /\Ay/i
|
306
|
+
messages.clear
|
307
|
+
@documents.clear
|
308
|
+
links.clear
|
309
|
+
clear_history
|
310
|
+
STDOUT.puts "Cleared messages and collection #{bold{@documents.collection}}."
|
311
|
+
else
|
312
|
+
STDOUT.puts 'Cancelled.'
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def display_config
|
318
|
+
default_pager = ENV['PAGER'].full?
|
319
|
+
if fallback_pager = `which less`.chomp.full? || `which more`.chomp.full?
|
320
|
+
fallback_pager << ' -r'
|
321
|
+
end
|
322
|
+
my_pager = default_pager || fallback_pager
|
323
|
+
rendered = config.to_s
|
324
|
+
Kramdown::ANSI::Pager.pager(
|
325
|
+
lines: rendered.count(?\n),
|
326
|
+
command: my_pager
|
327
|
+
) do |output|
|
328
|
+
output.puts rendered
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
109
332
|
def interact_with_user
|
110
333
|
loop do
|
111
|
-
parse_content = true
|
112
|
-
|
334
|
+
@parse_content = true
|
335
|
+
type = :terminal_input
|
336
|
+
input_prompt = bold { color(172) { message_type(@images) + " user" } } + bold { "> " }
|
113
337
|
|
114
338
|
begin
|
115
339
|
content = Reline.readline(input_prompt, true)&.chomp
|
116
340
|
rescue Interrupt
|
117
341
|
if message = server_socket_message
|
118
342
|
self.server_socket_message = nil
|
343
|
+
type = message.fetch('type', 'socket_input').to_sym
|
119
344
|
content = message['content']
|
120
345
|
else
|
121
346
|
raise
|
122
347
|
end
|
123
348
|
end
|
124
349
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
content = paste_from_input
|
131
|
-
when %r(^/markdown$)
|
132
|
-
markdown.toggle
|
133
|
-
next
|
134
|
-
when %r(^/stream$)
|
135
|
-
stream.toggle
|
136
|
-
next
|
137
|
-
when %r(^/location$)
|
138
|
-
location.toggle
|
139
|
-
next
|
140
|
-
when %r(^/voice(?:\s+(change))?$)
|
141
|
-
if $1 == 'change'
|
142
|
-
change_voice
|
143
|
-
else
|
144
|
-
voice.toggle
|
145
|
-
end
|
146
|
-
next
|
147
|
-
when %r(^/list(?:\s+(\d*))?$)
|
148
|
-
last = 2 * $1.to_i if $1
|
149
|
-
messages.list_conversation(last)
|
150
|
-
next
|
151
|
-
when %r(^/clear(?:\s+(messages|links|history))?$)
|
152
|
-
what = $1.nil? ? 'messages' : $1
|
153
|
-
case what
|
154
|
-
when 'messages'
|
155
|
-
messages.clear
|
156
|
-
STDOUT.puts "Cleared messages."
|
157
|
-
when 'links'
|
158
|
-
links.clear
|
159
|
-
STDOUT.puts "Cleared links."
|
160
|
-
when 'history'
|
161
|
-
clear_history
|
162
|
-
STDOUT.puts "Cleared history."
|
163
|
-
end
|
164
|
-
next
|
165
|
-
when %r(^/clobber$)
|
166
|
-
if ask?(prompt: 'Are you sure to clear messages and collection? (y/n) ') =~ /\Ay/i
|
167
|
-
messages.clear
|
168
|
-
@documents.clear
|
169
|
-
links.clear
|
170
|
-
clear_history
|
171
|
-
STDOUT.puts "Cleared messages and collection #{bold{@documents.collection}}."
|
172
|
-
else
|
173
|
-
STDOUT.puts 'Cancelled.'
|
174
|
-
end
|
175
|
-
next
|
176
|
-
when %r(^/drop(?:\s+(\d*))?$)
|
177
|
-
messages.drop($1)
|
178
|
-
messages.list_conversation(2)
|
179
|
-
next
|
180
|
-
when %r(^/model$)
|
181
|
-
@model = choose_model('', @model)
|
182
|
-
next
|
183
|
-
when %r(^/system$)
|
184
|
-
change_system_prompt(@system)
|
185
|
-
info
|
186
|
-
next
|
187
|
-
when %r(^/regenerate$)
|
188
|
-
if content = messages.second_last&.content
|
189
|
-
content.gsub!(/\nConsider these chunks for your answer.*\z/, '')
|
190
|
-
messages.drop(2)
|
191
|
-
else
|
192
|
-
STDOUT.puts "Not enough messages in this conversation."
|
350
|
+
unless type == :socket_input
|
351
|
+
case next_action = handle_input(content)
|
352
|
+
when :next
|
353
|
+
next
|
354
|
+
when :redo
|
193
355
|
redo
|
356
|
+
when :return
|
357
|
+
return
|
358
|
+
when String
|
359
|
+
content = next_action
|
194
360
|
end
|
195
|
-
parse_content = false
|
196
|
-
content
|
197
|
-
when %r(^/collection(?:\s+(clear|change))?$)
|
198
|
-
case $1 || 'change'
|
199
|
-
when 'clear'
|
200
|
-
loop do
|
201
|
-
tags = @documents.tags.add('[EXIT]').add('[ALL]')
|
202
|
-
tag = OllamaChat::Utils::Chooser.choose(tags, prompt: 'Clear? %s')
|
203
|
-
case tag
|
204
|
-
when nil, '[EXIT]'
|
205
|
-
STDOUT.puts "Exiting chooser."
|
206
|
-
break
|
207
|
-
when '[ALL]'
|
208
|
-
if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
|
209
|
-
@documents.clear
|
210
|
-
STDOUT.puts "Cleared collection #{bold{@documents.collection}}."
|
211
|
-
break
|
212
|
-
else
|
213
|
-
STDOUT.puts 'Cancelled.'
|
214
|
-
sleep 3
|
215
|
-
end
|
216
|
-
when /./
|
217
|
-
@documents.clear(tags: [ tag ])
|
218
|
-
STDOUT.puts "Cleared tag #{tag} from collection #{bold{@documents.collection}}."
|
219
|
-
sleep 3
|
220
|
-
end
|
221
|
-
end
|
222
|
-
when 'change'
|
223
|
-
choose_collection(@documents.collection)
|
224
|
-
end
|
225
|
-
next
|
226
|
-
when %r(^/info$)
|
227
|
-
info
|
228
|
-
next
|
229
|
-
when %r(^/document_policy$)
|
230
|
-
choose_document_policy
|
231
|
-
next
|
232
|
-
when %r(^/import\s+(.+))
|
233
|
-
parse_content = false
|
234
|
-
content = import($1) or next
|
235
|
-
when %r(^/summarize\s+(?:(\d+)\s+)?(.+))
|
236
|
-
parse_content = false
|
237
|
-
content = summarize($2, words: $1) or next
|
238
|
-
when %r(^/embedding$)
|
239
|
-
embedding_paused.toggle(show: false)
|
240
|
-
embedding.show
|
241
|
-
next
|
242
|
-
when %r(^/embed\s+(.+))
|
243
|
-
parse_content = false
|
244
|
-
content = embed($1) or next
|
245
|
-
when %r(^/web\s+(?:(\d+)\s+)?(.+))
|
246
|
-
parse_content = false
|
247
|
-
urls = search_web($2, $1.to_i) or next
|
248
|
-
urls.each do |url|
|
249
|
-
fetch_source(url) { |url_io| embed_source(url_io, url) }
|
250
|
-
end
|
251
|
-
urls_summarized = urls.map { summarize(_1) }
|
252
|
-
query = $2.inspect
|
253
|
-
results = urls.zip(urls_summarized).
|
254
|
-
map { |u, s| "%s as \n:%s" % [ u, s ] } * "\n\n"
|
255
|
-
content = config.prompts.web % { query:, results: }
|
256
|
-
when %r(^/save\s+(.+)$)
|
257
|
-
messages.save_conversation($1)
|
258
|
-
STDOUT.puts "Saved conversation to #$1."
|
259
|
-
next
|
260
|
-
when %r(^/links(?:\s+(clear))?$)
|
261
|
-
case $1
|
262
|
-
when 'clear'
|
263
|
-
loop do
|
264
|
-
links_options = links.dup.add('[EXIT]').add('[ALL]')
|
265
|
-
link = OllamaChat::Utils::Chooser.choose(links_options, prompt: 'Clear? %s')
|
266
|
-
case link
|
267
|
-
when nil, '[EXIT]'
|
268
|
-
STDOUT.puts "Exiting chooser."
|
269
|
-
break
|
270
|
-
when '[ALL]'
|
271
|
-
if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
|
272
|
-
links.clear
|
273
|
-
STDOUT.puts "Cleared all links in list."
|
274
|
-
break
|
275
|
-
else
|
276
|
-
STDOUT.puts 'Cancelled.'
|
277
|
-
sleep 3
|
278
|
-
end
|
279
|
-
when /./
|
280
|
-
links.delete(link)
|
281
|
-
STDOUT.puts "Cleared link from links in list."
|
282
|
-
sleep 3
|
283
|
-
end
|
284
|
-
end
|
285
|
-
when nil
|
286
|
-
if links.empty?
|
287
|
-
STDOUT.puts "List is empty."
|
288
|
-
else
|
289
|
-
Math.log10(links.size).ceil
|
290
|
-
format = "% #{}s. %s"
|
291
|
-
connect = -> link { hyperlink(link) { link } }
|
292
|
-
STDOUT.puts links.each_with_index.map { |x, i| format % [ i + 1, connect.(x) ] }
|
293
|
-
end
|
294
|
-
end
|
295
|
-
next
|
296
|
-
when %r(^/load\s+(.+)$)
|
297
|
-
messages.load_conversation($1)
|
298
|
-
if messages.size > 1
|
299
|
-
messages.list_conversation(2)
|
300
|
-
end
|
301
|
-
STDOUT.puts "Loaded conversation from #$1."
|
302
|
-
next
|
303
|
-
when %r(^/config$)
|
304
|
-
default_pager = ENV['PAGER'].full?
|
305
|
-
if fallback_pager = `which less`.chomp.full? || `which more`.chomp.full?
|
306
|
-
fallback_pager << ' -r'
|
307
|
-
end
|
308
|
-
my_pager = default_pager || fallback_pager
|
309
|
-
rendered = config.to_s
|
310
|
-
Kramdown::ANSI::Pager.pager(
|
311
|
-
lines: rendered.count(?\n),
|
312
|
-
command: my_pager
|
313
|
-
) do |output|
|
314
|
-
output.puts rendered
|
315
|
-
end
|
316
|
-
next
|
317
|
-
when %r(^/quit$)
|
318
|
-
STDOUT.puts "Goodbye."
|
319
|
-
return
|
320
|
-
when %r(^/)
|
321
|
-
display_chat_help
|
322
|
-
next
|
323
|
-
when ''
|
324
|
-
STDOUT.puts "Type /quit to quit."
|
325
|
-
next
|
326
|
-
when nil
|
327
|
-
STDOUT.puts "Goodbye."
|
328
|
-
return
|
329
361
|
end
|
330
362
|
|
331
|
-
content, tags = if parse_content
|
363
|
+
content, tags = if @parse_content
|
332
364
|
parse_content(content, @images)
|
333
365
|
else
|
334
|
-
[ content, Documentrix::Utils::Tags.new ]
|
366
|
+
[ content, Documentrix::Utils::Tags.new(valid_tag: /\A#*([\w\]\[]+)/) ]
|
335
367
|
end
|
336
368
|
|
337
369
|
if embedding.on? && content
|
@@ -102,6 +102,7 @@ module OllamaChat::Information
|
|
102
102
|
-D DOCUMENT load document and add to embeddings collection (multiple)
|
103
103
|
-M use (empty) MemoryCache for this chat session
|
104
104
|
-E disable embeddings for this chat session
|
105
|
+
-S open a socket to receive input from ollama_chat_send
|
105
106
|
-V display the current version number and quit
|
106
107
|
-h this help
|
107
108
|
|
data/lib/ollama_chat/parsing.rb
CHANGED
@@ -113,10 +113,10 @@ module OllamaChat::Parsing
|
|
113
113
|
|
114
114
|
def parse_content(content, images)
|
115
115
|
images.clear
|
116
|
-
tags = Documentrix::Utils::Tags.new
|
116
|
+
tags = Documentrix::Utils::Tags.new valid_tag: /\A#*([\w\]\[]+)/
|
117
117
|
|
118
118
|
contents = [ content ]
|
119
|
-
content.scan(%r((https?://\S+)|(
|
119
|
+
content.scan(%r((https?://\S+)|(?<![a-zA-Z\d])#+([\w\]\[]+)|(?:file://)?(\S*\/\S+))).each do |url, tag, file|
|
120
120
|
case
|
121
121
|
when tag
|
122
122
|
tags.add(tag)
|
@@ -8,9 +8,9 @@ module OllamaChat::ServerSocket
|
|
8
8
|
File.join(runtime_dir, 'ollama_chat.sock')
|
9
9
|
end
|
10
10
|
|
11
|
-
def send_to_server_socket(content)
|
11
|
+
def send_to_server_socket(content, type: :socket_input)
|
12
12
|
FileUtils.mkdir_p runtime_dir
|
13
|
-
message = { content: }
|
13
|
+
message = { content:, type: }
|
14
14
|
socket = UNIXSocket.new(server_socket_path)
|
15
15
|
socket.puts JSON(message)
|
16
16
|
socket.close
|
data/lib/ollama_chat/version.rb
CHANGED
data/ollama_chat.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: ollama_chat 0.0.
|
2
|
+
# stub: ollama_chat 0.0.10 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "ollama_chat".freeze
|
6
|
-
s.version = "0.0.
|
6
|
+
s.version = "0.0.10".freeze
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib".freeze]
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |s|
|
|
33
33
|
s.add_development_dependency(%q<simplecov>.freeze, [">= 0".freeze])
|
34
34
|
s.add_runtime_dependency(%q<excon>.freeze, ["~> 1.0".freeze])
|
35
35
|
s.add_runtime_dependency(%q<ollama-ruby>.freeze, ["~> 1.0".freeze])
|
36
|
-
s.add_runtime_dependency(%q<documentrix>.freeze, ["~> 0.0".freeze])
|
36
|
+
s.add_runtime_dependency(%q<documentrix>.freeze, ["~> 0.0".freeze, ">= 0.0.2".freeze])
|
37
37
|
s.add_runtime_dependency(%q<rss>.freeze, ["~> 0.3".freeze])
|
38
38
|
s.add_runtime_dependency(%q<term-ansicolor>.freeze, ["~> 1.11".freeze])
|
39
39
|
s.add_runtime_dependency(%q<redis>.freeze, ["~> 5.0".freeze])
|
@@ -6,43 +6,211 @@ RSpec.describe OllamaChat::Chat do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
let :chat do
|
9
|
-
OllamaChat::Chat.new
|
9
|
+
OllamaChat::Chat.new(argv: argv).expose
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
describe 'instantiation' do
|
13
|
+
connect_to_ollama_server(instantiate: false)
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
it 'can be instantiated' do
|
16
|
+
expect(chat).to be_a described_class
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'handle_input' do
|
21
|
+
connect_to_ollama_server
|
22
|
+
|
23
|
+
it 'returns :next when input is "/copy"' do
|
24
|
+
expect(chat).to receive(:copy_to_clipboard)
|
25
|
+
expect(chat.handle_input("/copy")).to eq :next
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns :next when input is "/paste"' do
|
29
|
+
expect(chat).to receive(:paste_from_input).and_return "pasted this"
|
30
|
+
expect(chat.handle_input("/paste")).to eq "pasted this"
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns :next when input is "/markdown"' do
|
34
|
+
expect(chat.markdown).to receive(:toggle)
|
35
|
+
expect(chat.handle_input("/markdown")).to eq :next
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns :next when input is "/stream"' do
|
39
|
+
expect(chat.stream).to receive(:toggle)
|
40
|
+
expect(chat.handle_input("/stream")).to eq :next
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns :next when input is "/location"' do
|
44
|
+
expect(chat.location).to receive(:toggle)
|
45
|
+
expect(chat.handle_input("/location")).to eq :next
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns :next when input is "/voice(?:\s+(change))? "' do
|
49
|
+
expect(chat.voice).to receive(:toggle)
|
50
|
+
expect(chat.handle_input("/voice")).to eq :next
|
51
|
+
expect(chat).to receive(:change_voice)
|
52
|
+
expect(chat.handle_input("/voice change")).to eq :next
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns :next when input is "/list(?:\s+(\d*))? "' do
|
56
|
+
expect(chat.messages).to receive(:list_conversation).with(4)
|
57
|
+
expect(chat.handle_input("/list 2")).to eq :next
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns :next when input is "/clear(messages|links|history|all)"' do
|
61
|
+
expect(chat).to receive(:clean).with('messages')
|
62
|
+
expect(chat.handle_input("/clear messages")).to eq :next
|
63
|
+
expect(chat).to receive(:clean).with('links')
|
64
|
+
expect(chat.handle_input("/clear links")).to eq :next
|
65
|
+
expect(chat).to receive(:clean).with('history')
|
66
|
+
expect(chat.handle_input("/clear history")).to eq :next
|
67
|
+
expect(chat).to receive(:clean).with('all')
|
68
|
+
expect(chat.handle_input("/clear all")).to eq :next
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns :next when input is "/clobber"' do
|
72
|
+
expect(chat).to receive(:clean).with('all')
|
73
|
+
expect(chat.handle_input("/clobber")).to eq :next
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns :next when input is "/drop(?:\s+(\d*))?"' do
|
77
|
+
expect(chat.messages).to receive(:drop).with(?2)
|
78
|
+
expect(chat.messages).to receive(:list_conversation).with(2)
|
79
|
+
expect(chat.handle_input("/drop 2")).to eq :next
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns :next when input is "/model"' do
|
83
|
+
expect(chat).to receive(:choose_model).with('', 'llama3.1')
|
84
|
+
expect(chat.handle_input("/model")).to eq :next
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns :next when input is "/system"' do
|
88
|
+
expect(chat).to receive(:change_system_prompt).with(nil)
|
89
|
+
expect(chat).to receive(:info)
|
90
|
+
expect(chat.handle_input("/system")).to eq :next
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'returns :next when input is "/regenerate"' do
|
94
|
+
expect(STDOUT).to receive(:puts).with(/Not enough messages/)
|
95
|
+
expect(chat.handle_input("/regenerate")).to eq :redo
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'returns :next when input is "/collection(clear|change)"' do
|
99
|
+
expect(OllamaChat::Utils::Chooser).to receive(:choose)
|
100
|
+
expect(STDOUT).to receive(:puts).with(/Exiting/)
|
101
|
+
expect(chat.handle_input("/collection clear")).to eq :next
|
102
|
+
expect(OllamaChat::Utils::Chooser).to receive(:choose)
|
103
|
+
expect(chat).to receive(:info)
|
104
|
+
expect(STDOUT).to receive(:puts).with(/./)
|
105
|
+
expect(chat.handle_input("/collection change")).to eq :next
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'returns :next when input is "/info"' do
|
109
|
+
expect(chat).to receive(:info)
|
110
|
+
expect(chat.handle_input("/info")).to eq :next
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'returns :next when input is "/document_policy"' do
|
114
|
+
expect(chat).to receive(:choose_document_policy)
|
115
|
+
expect(chat.handle_input("/document_policy")).to eq :next
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'returns :next when input is "/import\s+(.+)"' do
|
119
|
+
expect(chat).to receive(:import).with('./some_file')
|
120
|
+
expect(chat.handle_input("/import ./some_file")).to eq :next
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'returns :next when input is "/summarize\s+(?:(\d+)\s+)?(.+)"' do
|
124
|
+
expect(chat).to receive(:summarize).with('./some_file', words: '23')
|
125
|
+
expect(chat.handle_input("/summarize 23 ./some_file")).to eq :next
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'returns :next when input is "/embedding"' do
|
129
|
+
expect(chat.embedding_paused).to receive(:toggle)
|
130
|
+
expect(chat.embedding).to receive(:show)
|
131
|
+
expect(chat.handle_input("/embedding")).to eq :next
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'returns :next when input is "/embed\s+(.+)"' do
|
135
|
+
expect(chat).to receive(:embed).with('./some_file')
|
136
|
+
expect(chat.handle_input("/embed ./some_file")).to eq :next
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'returns :next when input is "/web\s+(?:(\d+)\s+)?(.+)"' do
|
140
|
+
expect(chat).to receive(:web).with('23', 'query').and_return 'the response'
|
141
|
+
expect(chat.handle_input("/web 23 query")).to eq 'the response'
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'returns :next when input is "/save\s+(.+)$"' do
|
145
|
+
expect(chat.messages).to receive(:save_conversation).with('./some_file')
|
146
|
+
expect(chat.handle_input("/save ./some_file")).to eq :next
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'returns :next when input is "/links(?:\s+(clear))?$" ' do
|
150
|
+
expect(chat).to receive(:manage_links).with(nil)
|
151
|
+
expect(chat.handle_input("/links")).to eq :next
|
152
|
+
expect(chat).to receive(:manage_links).with('clear')
|
153
|
+
expect(chat.handle_input("/links clear")).to eq :next
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'returns :next when input is "/load\s+(.+)$"' do
|
157
|
+
expect(chat.messages).to receive(:load_conversation).with('./some_file')
|
158
|
+
expect(chat.handle_input("/load ./some_file")).to eq :next
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'returns :next when input is "/config"' do
|
162
|
+
expect(chat).to receive(:display_config)
|
163
|
+
expect(chat.handle_input("/config")).to eq :next
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'returns :next when input is "/quit"' do
|
167
|
+
expect(STDOUT).to receive(:puts).with(/Goodbye/)
|
168
|
+
expect(chat.handle_input("/quit")).to eq :return
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'returns :next when input is "/nixda"' do
|
172
|
+
expect(chat).to receive(:display_chat_help)
|
173
|
+
expect(chat.handle_input("/nixda")).to eq :next
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'returns :next when input is " "' do
|
177
|
+
expect(STDOUT).to receive(:puts).with(/to quit/)
|
178
|
+
expect(chat.handle_input(" ")).to eq :next
|
179
|
+
end
|
16
180
|
end
|
17
181
|
|
18
182
|
describe 'chat history' do
|
183
|
+
connect_to_ollama_server(instantiate: false)
|
184
|
+
|
19
185
|
it 'derives chat_history_filename' do
|
20
|
-
expect(chat.
|
186
|
+
expect(chat.chat_history_filename).to_not be_nil
|
21
187
|
end
|
22
188
|
|
23
189
|
it 'can save chat history' do
|
24
190
|
expect(File).to receive(:secure_write).with(
|
25
|
-
chat.
|
191
|
+
chat.chat_history_filename,
|
26
192
|
kind_of(String)
|
27
193
|
)
|
28
|
-
chat.
|
194
|
+
chat.save_history
|
29
195
|
end
|
30
196
|
|
31
197
|
it 'can initialize chat history' do
|
32
|
-
expect(File).to receive(:exist?).with(chat.
|
198
|
+
expect(File).to receive(:exist?).with(chat.chat_history_filename).
|
33
199
|
and_return true
|
34
|
-
expect(File).to receive(:open).with(chat.
|
35
|
-
chat.
|
200
|
+
expect(File).to receive(:open).with(chat.chat_history_filename, ?r)
|
201
|
+
chat.init_chat_history
|
36
202
|
end
|
37
203
|
|
38
204
|
it 'can clear history' do
|
39
205
|
chat
|
40
206
|
expect(Readline::HISTORY).to receive(:clear)
|
41
|
-
chat.
|
207
|
+
chat.clear_history
|
42
208
|
end
|
43
209
|
end
|
44
210
|
|
45
211
|
context 'loading conversations' do
|
212
|
+
connect_to_ollama_server(instantiate: false)
|
213
|
+
|
46
214
|
let :argv do
|
47
215
|
%w[ -C test -c ] << asset('conversation.json')
|
48
216
|
end
|
@@ -56,7 +224,10 @@ RSpec.describe OllamaChat::Chat do
|
|
56
224
|
end
|
57
225
|
|
58
226
|
describe OllamaChat::DocumentCache do
|
227
|
+
connect_to_ollama_server(instantiate: false)
|
228
|
+
|
59
229
|
context 'with MemoryCache' do
|
230
|
+
|
60
231
|
let :argv do
|
61
232
|
%w[ -M ]
|
62
233
|
end
|
@@ -77,6 +248,8 @@ RSpec.describe OllamaChat::Chat do
|
|
77
248
|
|
78
249
|
describe Documentrix::Documents do
|
79
250
|
context 'with documents' do
|
251
|
+
connect_to_ollama_server(instantiate: false)
|
252
|
+
|
80
253
|
let :argv do
|
81
254
|
%w[ -C test -D ] << asset('example.html')
|
82
255
|
end
|
@@ -90,6 +263,8 @@ RSpec.describe OllamaChat::Chat do
|
|
90
263
|
end
|
91
264
|
|
92
265
|
describe OllamaChat::Information do
|
266
|
+
connect_to_ollama_server(instantiate: false)
|
267
|
+
|
93
268
|
it 'has progname' do
|
94
269
|
expect(chat.progname).to eq 'ollama_chat'
|
95
270
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ollama_chat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Frank
|
@@ -142,6 +142,9 @@ dependencies:
|
|
142
142
|
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
144
|
version: '0.0'
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: 0.0.2
|
145
148
|
type: :runtime
|
146
149
|
prerelease: false
|
147
150
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -149,6 +152,9 @@ dependencies:
|
|
149
152
|
- - "~>"
|
150
153
|
- !ruby/object:Gem::Version
|
151
154
|
version: '0.0'
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.0.2
|
152
158
|
- !ruby/object:Gem::Dependency
|
153
159
|
name: rss
|
154
160
|
requirement: !ruby/object:Gem::Requirement
|