richat 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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