Module: AI
- Defined in:
- lib/app.rb,
lib/aids.rb,
lib/aids.rb,
lib/ansi.rb,
lib/paths.rb,
lib/stats.rb,
lib/profile.rb,
lib/session.rb,
lib/commands.rb,
lib/highlighter.rb,
lib/line_editor.rb
Defined Under Namespace
Modules: Ansi, Commands, Paths, Profile, Stats
Classes: App, Highlighter, LineEditor, Session
Constant Summary
collapse
- VERSION =
"0.1.0"
- API_URL =
URI("https://api.deepseek.com/chat/completions")
- MODEL =
"deepseek-chat"
- DATA_DIR =
File.expand_path("~/.local/share/ai")
- META_DIR =
File.join(DATA_DIR, ".meta")
- STATS_FILE =
File.join(META_DIR, "stats.json")
- RATES =
{ hit: 0.028, miss: 0.28, out: 0.42 }.freeze
- LCAP =
"\ue0b6"
- RCAP =
"\ue0b4"
- USER_ICON =
"👤"
- USER_FG =
117
- ATT_FG =
180
- CMT_FG =
247
- CMT_STYLE =
"\e[2;3;38;5;#{CMT_FG}m"
Class Method Summary
collapse
Class Method Details
.api_message(msg) ⇒ Object
99
100
101
102
103
104
105
106
|
# File 'lib/aids.rb', line 99
def self.api_message(msg)
if msg["role"] == "user" && (atts = msg["attachments"]) && atts.any?
blocks = atts.map { |a| "<attached path=\"#{a["path"]}\">\n#{a["content"]}\n</attached>" }
{ "role" => "user", "content" => blocks.join("\n") + "\n\n" + msg["content"].to_s }
else
{ "role" => msg["role"], "content" => msg["content"] }
end
end
|
.clean! ⇒ Object
131
132
133
134
135
136
|
# File 'lib/aids.rb', line 131
def self.clean!
files = Dir[File.join(DATA_DIR, "*.md")] +
Dir[File.join(META_DIR, "*")].select { |x| File.file?(x) }
files.each { |f| File.delete(f) }
puts "#{Ansi.fg(114)}\u2726 #{files.size} files removed#{Ansi.reset}"
end
|
.print_usage(usage) ⇒ Object
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/aids.rb', line 81
def self.print_usage(usage)
return unless usage
g = "#{Ansi.dim}#{Ansi.fg(243)}"; r = Ansi.reset
cells = %w[hit miss out total].zip([
usage["prompt_cache_hit_tokens"] || 0,
usage["prompt_cache_miss_tokens"] || 0,
usage["completion_tokens"] || 0,
usage["total_tokens"] || 0,
]).map { |label, val| "#{g}#{label}#{r} #{Ansi.fg(87)}#{val}#{r}" }
cells << "#{Ansi.fg(220)}#{format("%.4f", Stats.cost(usage))}\u03bc$#{r}"
row = " #{cells.join(" #{g}\u2502#{r} ")} "
w = Ansi.width(row)
puts "\n#{g}\u256d#{"─" * w}\u256e#{r}"
puts "#{g}\u2502#{r}#{row}#{g}\u2502#{r}"
puts "#{g}\u2570#{"─" * w}\u256f#{r}"
puts
end
|
.run(argv = ARGV) ⇒ Object
── Entry point ──────────────────────────────────────────────────────
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
# File 'lib/aids.rb', line 109
def self.run(argv = ARGV)
resume = false
rest = OptionParser.new { |o|
o.on("-c", "--continue") { resume = true }
o.on("--history") { exec("lf", DATA_DIR) }
o.on("--clean") { clean!; exit }
}.order(argv)
key = ENV["DEEPSEEK_API_KEY"] or abort "#{Ansi.fg(196)}DEEPSEEK_API_KEY not set#{Ansi.reset}"
app = App.new(key: key, profile: Profile.load, resume: resume)
piped = $stdin.tty? ? "" : $stdin.read.strip
text = [piped, rest.join(" ")].map(&:strip).reject(&:empty?).join(" ")
if text.empty?
app.repl
else
puts "#{Ansi.dim}> #{text}#{Ansi.reset}"
app.ask(text); puts
end
end
|
.stream(messages, system, key) ⇒ Object
── HTTP / streaming ─────────────────────────────────────────────────
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
# File 'lib/aids.rb', line 29
def self.stream(messages, system, key)
body = { model: MODEL,
messages: [{ role: "system", content: system }, *messages],
stream: true,
stream_options: { include_usage: true } }.to_json
usage = nil
full = +""
Net::HTTP.start(API_URL.host, API_URL.port, use_ssl: true, read_timeout: 120) do |http|
req = Net::HTTP::Post.new(API_URL,
"Authorization" => "Bearer #{key}",
"Content-Type" => "application/json")
req.body = body
http.request(req) do |res|
abort "#{Ansi.fg(196)}API #{res.code}#{Ansi.reset}" unless res.is_a?(Net::HTTPSuccess)
buf = +""
res.read_body do |chunk|
buf << chunk
while (nl = buf.index("\n"))
line = buf.slice!(0..nl).strip
next unless line.start_with?("data: ")
payload = line.delete_prefix("data: ")
next if payload == "[DONE]"
event = JSON.parse(payload) rescue next
usage = event["usage"] if event["usage"]
if (tok = event.dig("choices", 0, "delta", "content"))
full << tok
yield tok
end
end
end
end
end
[full, usage]
end
|
.title_query(system, user, key) ⇒ Object
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
# File 'lib/aids.rb', line 64
def self.title_query(system, user, key)
body = { model: MODEL, max_tokens: 40,
messages: [{ role: "system", content: system },
{ role: "user", content: user }] }.to_json
Net::HTTP.start(API_URL.host, API_URL.port, use_ssl: true, read_timeout: 15) do |http|
req = Net::HTTP::Post.new(API_URL,
"Authorization" => "Bearer #{key}",
"Content-Type" => "application/json")
req.body = body
res = http.request(req)
return nil unless res.is_a?(Net::HTTPSuccess)
(JSON.parse(res.body) rescue nil)&.dig("choices", 0, "message", "content")&.strip
end
rescue StandardError
nil
end
|