richat 0.2.4 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90dc1c0cb9c1069b11e432d7164fe8b17d0095e19a82ea5830ba1f57fd98511c
4
- data.tar.gz: 11b9948cc323b52f14f3d89e0d2ca392988130c284c95e5b2e44df3c0e2ce356
3
+ metadata.gz: 9b76efa6dcbdfe7a9f2184c7823b6c0292a6ce8b35ed44378679c06b0c847503
4
+ data.tar.gz: cda10863c00442ecd0a5c24c5758251bcfa1bb04b84f623843c2b6df30d1ead5
5
5
  SHA512:
6
- metadata.gz: 3c4171cad77ca01fa510cda74a1a8ff08c7a9db288d7172c16bf6d31219f4436f8e228b10bd735369bdf2f3f7b0b7782ebfcf003df86572941b7bc0add8e1abc
7
- data.tar.gz: e37cd9b94f2e2346d8412627be3538453922c1ce0b0bd1b165451861f6060b13781a7cde6f4cc399f1096d128238549508e0449326a739d5e14b8d2fb4c3b69a
6
+ metadata.gz: 3251caa055e0e5e60b9cac6981e81b52c083eebf06c5c5a4d4e2f115171f4445ed79142694ef514c1f067aed13276f0e03230f85896e8fce425804abb2cbbd16
7
+ data.tar.gz: '04008b119d99774c298f20f3d631a938221be60951df86e8e1c0eb6630060f3762561cfc62798f6cfa65746465c1b704febf63f1834dda82da248679d5adc6ea'
data/exe/richat CHANGED
@@ -1,36 +1,76 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bundler/setup'
3
+ require 'optparse'
4
4
  require 'richat'
5
5
 
6
- log_dir = File.expand_path(Richat::Config.get("log", "log_dir"))
7
- FileUtils.mkdir_p(log_dir) unless File.directory?(log_dir)
6
+ cli_opts = {}
8
7
 
9
- prompt_dir = File.expand_path(Richat::Config.get("prompt", "prompt_dir"))
10
- FileUtils.mkdir_p(prompt_dir) unless File.directory?(prompt_dir)
8
+ OptionParser.new do |opts|
9
+ opts.banner = %q{Usage: richat [options] [chat content]
10
+ richat will enter shell mode if chat content is empty}
11
+ opts.version = Richat::VERSION
12
+ opts.program_name = "richat"
11
13
 
12
- chat_config = {
13
- api_key: Richat::Config.get("chatgpt", "api_key"),
14
- model: Richat::Config.get("chatgpt", "model"),
15
- temperature: Richat::Config.get("chatgpt", "temperature"),
16
- stream: true
17
- }
14
+ opts.separator "\nOptions:"
15
+
16
+ opts.on("-l", "--logfile=LOG FILE", "Set log file path") do |l|
17
+ cli_opts["log"] = { "log_file" => l }
18
+ end
19
+
20
+ opts.on_tail("-h", "--help", "Print help") do
21
+ puts opts
22
+ puts "\nFurther help: https://github.com/fzdp/richat"
23
+ exit
24
+ end
18
25
 
19
- if chat_config[:api_key].nil? || chat_config[:api_key].empty?
26
+ opts.on_tail("-v", "--version", "Version") do
27
+ puts "richat " + Richat::VERSION
28
+ exit
29
+ end
30
+ end.parse!
31
+
32
+ Richat::Config.override_with(cli_opts) unless cli_opts.empty?
33
+
34
+ api_key = Richat::Config.get("chatgpt", "api_key")
35
+ if api_key.nil? || api_key.empty?
20
36
  puts "OpenAI API key not set, refer https://github.com/fzdp/richat#usage for more info."
21
37
  exit
22
38
  end
23
39
 
24
40
  if Richat::Config.get("log", "enable")
25
- logger = Richat::Logger.new(log_file: "#{log_dir}/#{Time.now.strftime('%Y%m%d')}.md")
41
+ log_dir = File.expand_path(Richat::Config.get("log", "log_dir"))
42
+ log_file = Richat::Config.get("log", "log_file")
43
+
44
+ if log_file.nil? || log_file.empty?
45
+ log_file = File.join(log_dir, "#{Time.now.strftime('%Y%m%d')}.md")
46
+ elsif log_file.start_with? "~/"
47
+ log_file = File.expand_path(log_file)
48
+ elsif !Richat::Utils.absolute_path?(log_file)
49
+ log_file = File.join(log_dir, log_file)
50
+ end
51
+
52
+ Richat::Utils.ensure_dir_exist(File.dirname(log_file))
53
+ logger = Richat::Logger.new(log_file: log_file)
26
54
  else
27
55
  logger = Richat::Logger.empty_logger
28
56
  end
29
57
 
30
- if ARGV.size.zero?
58
+ prompt_dir = File.expand_path(Richat::Config.get("prompt", "prompt_dir"))
59
+ Richat::Utils.ensure_dir_exist(prompt_dir)
60
+
61
+ user_content = ARGV.join(" ")
62
+
63
+ chat_config = {
64
+ api_key: Richat::Config.get("chatgpt", "api_key"),
65
+ model: Richat::Config.get("chatgpt", "model"),
66
+ temperature: Richat::Config.get("chatgpt", "temperature"),
67
+ stream: true
68
+ }
69
+
70
+ if user_content.empty?
31
71
  client = Openai::Chat.new(chat_config)
32
72
  Richat::Shell.new(chat_client: client, logger: logger).call
33
73
  else
34
74
  client = Openai::Chat.new(chat_config.merge(stream: false))
35
- Richat::Cli.new(chat_client: client, logger: logger, user_content: ARGV.join(" ")).call
75
+ Richat::Cli.new(chat_client: client, logger: logger, user_content: user_content).call
36
76
  end
@@ -3,19 +3,21 @@ module Richat
3
3
  EXIT_CODE = 0
4
4
  NEXT_CODE = 1
5
5
  PROMPT_CHANGED_CODE = 2
6
+ SYS_CMD_CODE = 3
7
+ SYS_CHAT_CODE = 4
6
8
 
7
9
  class << self
8
10
  attr_reader :prompt, :prompt_id
9
11
 
10
- def call(user_input)
12
+ def call(user_input, sys_cmd_mode=false)
11
13
  user_input = user_input.strip
14
+ return handle_system_command(user_input, sys_cmd_mode) if user_input.start_with?(*Config.get("sys_cmd", "activate_keywords"))
15
+ return handle_exit if Config.get("shell", "exit_keywords").include?(user_input)
12
16
  return unless user_input.start_with?("/")
13
17
  if user_input == "/help"
14
18
  handle_help
15
19
  elsif user_input == "/config"
16
20
  handle_config
17
- elsif user_input == "/exit"
18
- handle_exit
19
21
  elsif user_input =~ /^\/prompt\s*/
20
22
  if user_input == "/prompt"
21
23
  handle_prompt
@@ -25,6 +27,25 @@ module Richat
25
27
  end
26
28
  end
27
29
 
30
+ def handle_system_command(user_input, sys_cmd_mode)
31
+ Config.get("sys_cmd", "activate_keywords").each do |sub_str|
32
+ if user_input.start_with?(sub_str)
33
+ cmd = user_input[sub_str.length..-1].strip
34
+ return SYS_CHAT_CODE if Config.get("sys_cmd", "deactivate_keywords").include?(cmd) && sys_cmd_mode
35
+ if (match = /^cd\s?(.*)$/.match(cmd))
36
+ Dir.chdir(File.expand_path(match[1].empty? ? "~" : match[1]))
37
+ else
38
+ system(cmd)
39
+ end
40
+ if sys_cmd_mode
41
+ return NEXT_CODE
42
+ else
43
+ return SYS_CMD_CODE, sub_str
44
+ end
45
+ end
46
+ end
47
+ end
48
+
28
49
  def handle_exit
29
50
  puts "Bye"
30
51
  EXIT_CODE
@@ -32,11 +53,12 @@ module Richat
32
53
 
33
54
  def handle_config
34
55
  puts "\e[32mConfiguration file path is #{File.expand_path("~/.richat/config.json")}\e[0m"
35
- puts JSON.pretty_generate(Config.config)
56
+ puts JSON.pretty_generate(Config.get_config)
36
57
  NEXT_CODE
37
58
  end
38
59
 
39
60
  def handle_help
61
+ puts "Version #{VERSION}"
40
62
  puts "\e[32m/exit\e[0m exit Richat"
41
63
  puts "\e[32m/config\e[0m show configuration"
42
64
  puts "\e[32m/prompt\e[0m show prompt list"
data/lib/richat/config.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+
1
3
  module Richat
2
4
  class Config
3
5
  DEFAULT_CONFIG = {
@@ -9,6 +11,7 @@ module Richat
9
11
  "log" => {
10
12
  "enable" => true,
11
13
  "log_dir" => "~/.richat/logs",
14
+ "log_file" => nil,
12
15
  "user_role" => "USR",
13
16
  "ai_role" => "GPT",
14
17
  "system_role" => "SYS"
@@ -17,7 +20,12 @@ module Richat
17
20
  "save_shell_history" => true,
18
21
  "enable_chat_context" => true,
19
22
  "show_welcome_info" => true,
20
- "shell_history_file" => "~/.richat/history.txt"
23
+ "shell_history_file" => "~/.richat/history.txt",
24
+ "exit_keywords" => ["/exit", "q", "quit", "exit"]
25
+ },
26
+ "sys_cmd" => {
27
+ "activate_keywords" => [">", "!"],
28
+ "deactivate_keywords" => ["q", "quit", "exit"]
21
29
  },
22
30
  "prompt" => {
23
31
  "prompt_dir" => "~/.richat/prompts",
@@ -28,8 +36,16 @@ module Richat
28
36
  class << self
29
37
  attr_reader :config
30
38
 
39
+ def get_config
40
+ (@config ||= merge_config)
41
+ end
42
+
31
43
  def get(*keys)
32
- (@config ||= merge_config).dig(*keys)
44
+ get_config.dig(*keys)
45
+ end
46
+
47
+ def override_with(other_config)
48
+ @config = Utils.deep_merge_hash(get_config, other_config)
33
49
  end
34
50
  end
35
51
 
@@ -41,7 +57,7 @@ module Richat
41
57
  else
42
58
  {}
43
59
  end
44
- DEFAULT_CONFIG.deep_merge(user_config)
60
+ Utils.deep_merge_hash(DEFAULT_CONFIG, user_config)
45
61
  end
46
62
  end
47
63
  end
data/lib/richat/shell.rb CHANGED
@@ -33,17 +33,21 @@ module Richat
33
33
  end
34
34
 
35
35
  Command.print_welcome if Config.get("shell", "show_welcome_info")
36
+ sys_cmd_mode = false
37
+ current_cmd_prefix = nil
36
38
 
37
39
  begin
38
- while (user_content = Readline.readline("\e[32m#{Command.prompt_id&.+" "}\e[0m\e[33m>> \e[0m", true))
40
+ while (user_content = Readline.readline(shell_prompt(sys_cmd_mode, current_cmd_prefix), true))
39
41
  if user_content.empty?
40
42
  Readline::HISTORY&.pop
41
43
  next
42
44
  end
43
45
 
46
+ user_content = current_cmd_prefix + user_content if sys_cmd_mode
44
47
  File.open(history_path, 'a') { |f| f.puts(user_content) } if enable_full_completion
45
48
 
46
- if (code = Command.call(user_content))
49
+ code, _ = Command.call(user_content, sys_cmd_mode)
50
+ if code
47
51
  if code == Command::NEXT_CODE
48
52
  next
49
53
  elsif code == Command::EXIT_CODE
@@ -51,6 +55,13 @@ module Richat
51
55
  elsif code == Command::PROMPT_CHANGED_CODE
52
56
  context_messages = [{ role: 'system', content: Command.prompt }]
53
57
  next
58
+ elsif code == Command::SYS_CMD_CODE
59
+ sys_cmd_mode = true
60
+ current_cmd_prefix = _
61
+ next
62
+ elsif code == Command::SYS_CHAT_CODE
63
+ sys_cmd_mode = false
64
+ next
54
65
  end
55
66
  end
56
67
 
@@ -84,5 +95,15 @@ module Richat
84
95
  Command.handle_exit
85
96
  end
86
97
  end
98
+
99
+ private
100
+
101
+ def shell_prompt(sys_cmd_mode, current_cmd_prefix)
102
+ if sys_cmd_mode
103
+ "\e[32m#{Command.prompt_id&.+" "}\e[0m\e[33m>>\e[0m \e[32m#{current_cmd_prefix}\e[0m"
104
+ else
105
+ "\e[32m#{Command.prompt_id&.+" "}\e[0m\e[33m>> \e[0m"
106
+ end
107
+ end
87
108
  end
88
109
  end
@@ -0,0 +1,33 @@
1
+ module Richat
2
+ class Utils
3
+ class << self
4
+ def deep_merge_hash(hs1, hs2)
5
+ result = hs1.dup
6
+
7
+ hs2.each do |key, value|
8
+ if value.is_a?(Hash) && hs1[key].is_a?(Hash)
9
+ result[key] = deep_merge_hash(result[key], value)
10
+ else
11
+ result[key] = value
12
+ end
13
+ end
14
+
15
+ result
16
+ end
17
+
18
+ def absolute_path?(fp)
19
+ File.expand_path(fp) == fp
20
+ end
21
+
22
+ def ensure_dir_exist(*dirs)
23
+ dirs.each do |dir_name|
24
+ if dir_name.nil? || dir_name.empty?
25
+ puts "invalid directory"
26
+ exit
27
+ end
28
+ FileUtils.mkdir_p(dir_name) unless File.directory?(dir_name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Richat
2
- VERSION = "0.2.4"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/richat.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "richat/version"
2
2
 
3
- require_relative 'extension/hash'
3
+ require_relative 'richat/utils'
4
4
  require_relative 'richat/config'
5
5
  require_relative 'richat/command'
6
6
  require_relative 'openai/chat'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: richat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fzdp
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-04 00:00:00.000000000 Z
11
+ date: 2023-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -67,8 +67,9 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.12'
69
69
  description: Richat is a command-line ChatGPT tool implemented in Ruby that supports
70
- highly customizable configuration. It can save chat logs, performs fuzzy searches
71
- on historical inputs, allows for prompt customization and switching at any time
70
+ highly customizable configuration. It can save chat contents, performs fuzzy searches
71
+ on historical inputs, allows for prompt switching at any time and can event act
72
+ as Linux terminal.
72
73
  email:
73
74
  - fzdp01@gmail.com
74
75
  executables:
@@ -77,7 +78,6 @@ extensions: []
77
78
  extra_rdoc_files: []
78
79
  files:
79
80
  - exe/richat
80
- - lib/extension/hash.rb
81
81
  - lib/openai/chat.rb
82
82
  - lib/richat.rb
83
83
  - lib/richat/cli.rb
@@ -85,6 +85,7 @@ files:
85
85
  - lib/richat/config.rb
86
86
  - lib/richat/logger.rb
87
87
  - lib/richat/shell.rb
88
+ - lib/richat/utils.rb
88
89
  - lib/richat/version.rb
89
90
  homepage: https://github.com/fzdp/richat
90
91
  licenses:
@@ -107,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
108
  - !ruby/object:Gem::Version
108
109
  version: '0'
109
110
  requirements: []
110
- rubygems_version: 3.0.9
111
+ rubygems_version: 3.4.10
111
112
  signing_key:
112
113
  specification_version: 4
113
114
  summary: Richat is a command-line ChatGPT tool
@@ -1,15 +0,0 @@
1
- class Hash
2
- def deep_merge(other_hash)
3
- result = self.dup
4
-
5
- other_hash.each do |key, value|
6
- if value.is_a?(Hash) && self[key].is_a?(Hash)
7
- result[key] = result[key].deep_merge(value)
8
- else
9
- result[key] = value
10
- end
11
- end
12
-
13
- result
14
- end
15
- end