Module: MonadicChat

Included in:
MonadicApp
Defined in:
lib/monadic_chat.rb,
lib/monadic_chat/version.rb,
lib/monadic_chat/commands.rb,
lib/monadic_chat/authenticate.rb

Constant Summary collapse

SETTINGS =
{
  "normal_model" => "gpt-4o-mini",
  "research_model" => "gpt-4o-latest",
  "max_tokens_wiki" => 1600,
  "num_retrials" => 2,
  "min_query_size" => 5,
  "timeout_sec" => 120
}
BLINGFIRE =
BlingFire.load_model(gpt2model_path)
CONFIG =
File.join(Dir.home, "monadic_chat.conf")
TITLE_WIDTH =
72
APPS_DIR =
File.absolute_path(File.join(__dir__, "..", "apps"))
USER_APPS_DIR =
File.absolute_path(File.join(__dir__, "..", "user_apps"))
APPS_DIR_LIST =
apps_dir_list + user_apps_dir_list
APPS =
APPS_DIR_LIST.map { |dir| File.basename(dir, ".*") }
TEMPLATES =
templates
PASTEL =
Pastel.new
TEMP_HTML =
File.join(Dir.home, "monadic_chat.html")
TEMP_JSON =
File.join(Dir.home, "monadic_chat.json")
TEMP_MD =
File.join(Dir.home, "monadic_chat.md")
GITHUB_STYLE =
style
PROMPT_USER =
TTY::PromptX.new(active_color: :blue, prefix: prompt_user)
PROMPT_SYSTEM =
TTY::PromptX.new(active_color: :blue, prefix: "#{prompt_system} ")
PROMPT_ASSISTANT =
TTY::PromptX.new(active_color: :red, prefix: "#{prompt_assistant} ")
SPINNER =
TTY::Spinner.new(format: :arrow_pulse, clear: true)
BULLET =
"\e[33m●\e[0m"
HOME =
File.expand_path(File.join(__dir__, ".."))
VERSION =
"0.4.6"

Class Method Summary collapse

Class Method Details

.authenticate(overwrite: false, message: true) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/monadic_chat/authenticate.rb', line 4

def self.authenticate(overwrite: false, message: true)
  check = lambda do |token|
    if message
      print TTY::Cursor.restore
      print TTY::Cursor.clear_screen_down
      print "\n"
      SPINNER.auto_spin
    end

    if !token || token.strip == ""
      if message
        SPINNER.stop
        print TTY::Cursor.restore
        print "\n"
        mdprint "- Authentication: #{PASTEL.bold.red("Failure")}\n" if message
      end
      return false
    end

    begin
      models = OpenAI.models(token)
      raise if models.empty?

      if message
        SPINNER.stop
        print TTY::Cursor.restore, "\n"
        mdprint "#{PASTEL.on_green(" System ")} Config file: `#{CONFIG}`\n"
        print "\n"
        mdprint "- Authentication: #{PASTEL.bold.green("Success")}\n"
      end

      if SETTINGS["normal_model"] && !models.map { |m| m["id"] }.index(SETTINGS["normal_model"])
        if message
          SPINNER.stop
          mdprint "- Normal mode model specified in config file not available\n"
          mdprint "- Fallback to the default model (`#{OpenAI.default_model(research_mode: false)}`)\n"
        end
        SETTINGS["normal_model"] = false
      end
      SETTINGS["normal_model"] ||= OpenAI.default_model(research_mode: false)
      mdprint "- Normal mode model: `#{SETTINGS["normal_model"]}`\n" if message

      if SETTINGS["research_model"] && !models.map { |m| m["id"] }.index(SETTINGS["research_model"])
        if message
          SPINNER.stop
          mdprint "- Research mode model specified in config file not available\n"
          mdprint "- Fallback to the default model (`#{OpenAI.default_model(research_mode: true)}`)\n"
        end
        SETTINGS["research_model"] = false
      end
      SETTINGS["research_model"] ||= OpenAI.default_model(research_mode: true)
      mdprint "- Research mode model: `#{SETTINGS["research_model"]}`\n" if message

      SETTINGS["max_tokens_wiki"] = 1000 unless SETTINGS["max_chars_wiki"].to_i.between?(100, 4000)
      SETTINGS["num_retrials"] = 2 unless SETTINGS["num_retrials"].to_i.between?(1, 10)
      SETTINGS["min_query_size"] = 5 unless SETTINGS["min_query_size"].to_i.between?(1, 20)
      SETTINGS["timeout_sec"] = 120 unless SETTINGS["timeout_sec"].to_i.between?(10, 600)

      OpenAI::Completion.new(token)
    rescue StandardError
      if message
        SPINNER.stop
        print TTY::Cursor.restore
        print "\n"
        mdprint "- Authentication: #{PASTEL.bold.red("Failure")}\n" if message
      end
      false
    end
  end

  completion = nil

  if overwrite
    access_token = PROMPT_SYSTEM.ask("Input your OpenAI access token:")
    return false if access_token.to_s == ""

    completion = check.call(access_token)

    if completion
      File.open(CONFIG, "w") do |f|
        SETTINGS["access_token"] = access_token
        f.write(JSON.pretty_generate(SETTINGS))
        print "New access token has been saved to #{CONFIG}\n\n" if message
        print "Please #{PASTEL.bold.green("restart")} Monadic Chat\n"
        exit
      end
    end
  elsif File.exist?(CONFIG)
    json = File.read(CONFIG)
    begin
      config = JSON.parse(json)
    rescue JSON::ParserError
      puts "Error: config file does not contain a valid JSON object."
      exit
    end
    SETTINGS.merge!(config)
    access_token = config["access_token"]
    completion = check.call(access_token)
  else
    access_token ||= PROMPT_SYSTEM.ask("Input your OpenAI access token:")
    return false if access_token.to_s == ""

    completion = check.call(access_token)
    if completion
      File.open(CONFIG, "w") do |f|
        SETTINGS["access_token"] = access_token
        f.write(JSON.pretty_generate(SETTINGS))
      end
      print "Access token has been saved to #{CONFIG}\n\n" if message
      print "Please #{PASTEL.bold.green("restart")} Monadic Chat\n"
      exit
    end
  end
  completion || authenticate(overwrite: true)
end

.create_app(app_name) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/monadic_chat/commands.rb', line 38

def self.create_app(app_name)
  app_name = +app_name.downcase
  user_apps_dir = File.join(HOME, "user_apps")
  user_app_dir = File.join(user_apps_dir, app_name)
  FileUtils.mkdir_p(user_app_dir)
  # replace certain strings in boilerplate files (boilerplate.rb, boilerplate.json, boilerplate.md)
  [".rb", ".json", ".md"].each do |ext|
    file = File.join(HOME, "user_apps", "boilerplates", "boilerplate#{ext}")
    content = File.read(file)
    content.gsub!("{{APP_NAME}}", app_name)
    content.gsub!("{{APP_CLASS_NAME}}", app_name.capitalize)
    File.open(File.join(user_app_dir, "#{app_name}#{ext}"), "w") do |f|
      f.write(content)
    end
  end
  print PROMPT_SYSTEM.prefix, "Scaffolding of the app created successfully", "\n"
  print "Edit the app files:", "\n"
  print HOME, "\n"
  print "user_apps", "\n"
  print "└── #{app_name}", "\n"
  print "    ├── #{app_name}.json", "\n"
  print "    ├── #{app_name}.md", "\n"
  print "    └── #{app_name}.rb", "\n"
end

.delete_app(app_name) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/monadic_chat/commands.rb', line 63

def self.delete_app(app_name)
  app_name = +app_name.downcase
  user_apps_dir = File.join(HOME, "user_apps")
  user_app_dir = File.join(user_apps_dir, app_name)
  # confirm user wants to delete the app
  if PROMPT_SYSTEM.yes?("Are you sure you want to delete the app #{app_name}?")
    FileUtils.rm_rf(user_app_dir)
    print PROMPT_SYSTEM.prefix, "App deleted successfully", "\n"
  else
    print PROMPT_SYSTEM.prefix, "App deletion cancelled", "\n"
  end
end

.mdprint(str) ⇒ Object



9
10
11
# File 'lib/monadic_chat/commands.rb', line 9

def self.mdprint(str)
  print TTY::Markdown.parse(str, indent: 0)
end

.open_readmeObject



4
5
6
7
# File 'lib/monadic_chat/commands.rb', line 4

def self.open_readme
  url = "https://github.com/yohasebe/monadic-chat/"
  Launchy.open(url)
end

.prompt_assistantObject



27
28
29
30
31
32
# File 'lib/monadic_chat/commands.rb', line 27

def self.prompt_assistant
  box_width = 5
  color = "red"
  name = "GPT".center(box_width, " ")
  "\n#{PASTEL.send(:"on_#{color}", name)}"
end

.prompt_systemObject



13
14
15
16
17
18
# File 'lib/monadic_chat/commands.rb', line 13

def self.prompt_system
  box_width = 8
  name = "System".center(box_width, " ")
  color = "green"
  "\n#{PASTEL.send(:"on_#{color}", name)}"
end

.prompt_userObject



20
21
22
23
24
25
# File 'lib/monadic_chat/commands.rb', line 20

def self.prompt_user
  box_width = 6
  color = "blue"
  name = "User".center(box_width, " ")
  "\n#{PASTEL.send(:"on_#{color}", name)}"
end

.require_appsObject



136
137
138
139
140
141
# File 'lib/monadic_chat.rb', line 136

def self.require_apps
  MonadicChat::APPS_DIR_LIST.each do |app_dir|
    basename = app_dir.split("/").last
    require "#{app_dir}/#{basename}"
  end
end

.tokenize(text) ⇒ Object



34
35
36
# File 'lib/monadic_chat/commands.rb', line 34

def self.tokenize(text)
  BLINGFIRE.text_to_ids(text)
end