gpt-storyteller/src/main.cr

207 lines
5.1 KiB
Crystal

require "option_parser"
require "pretty_print"
require "openai"
require "spinner"
require "./zone"
require "./parsers/prompt_string"
require "./builders/prompt_string"
require "./builders/openai_chat"
require "./builders/openai_insert"
class Storyteller
enum OpenAIMode
Chat
Complete
Insert
def self.from_s(str_mode)
return OpenAIMode.values.find do |openai_mode|
openai_mode.to_s.downcase == str_mode.downcase
end
end
end
def initialize()
end
def self.start(argv)
input_file = STDIN
input_file_path = ""
output_file = STDOUT
output_file_path = ""
past_characters_limit = 1000
use_color = true
make_request = true
gpt_mode = OpenAIMode::Chat
verbose = false
gpt_temperature = 0.82
gpt_presence_penalty = 1
gpt_frequency_penalty = 1
gpt_max_tokens = 256
parser = OptionParser.new do |parser|
parser.banner = "Usage: storyteller [options]"
parser.separator("Options:")
parser.on("-i FILE", "--input=FILE", "Path to input file") do |file|
input_file_path = file
end
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
parser.on("-n", "--no-color", "Disable color output") do
use_color = false
end
parser.on("-o FILE", "--output=FILE", "Path to output file") do |file|
use_color = false
output_file_path = file
end
parser.on("-v", "--verbose", "Be verbose (cumulative)") do
verbose = true
end
parser.on("--dry-run", "Don't call the API") do
make_request = false
end
parser.separator("GPT options")
parser.on("--gpt-mode MODE", "GPT mode (chat,insert,complete) (default: chat)") do |chosen_mode|
result_mode = OpenAIMode.from_s(chosen_mode.downcase)
if result_mode.nil?
STDERR.puts "ERROR: unknown mode #{chosen_mode}"
exit 1
end
gpt_mode = result_mode unless result_mode.nil?
end
parser.on("--gpt-temperature TEMPERATURE", "GPT Temperature") do |temperature|
gpt_temperature = temperature
end
parser.on("--gpt-presence-penalty PENALTY", "GPT Presence Penalty") do |presence_penalty|
gpt_presence_penalty = presence_penalty
end
parser.on("--gpt-frequency-penalty PENALTY", "GPT Frequency Penalty") do |frequency_penalty|
gpt_frequency_penalty = frequency_penalty
end
parser.on("--gpt-max-tokens TOKENS", "GPT Max Tokens") do |max_tokens|
gpt_max_tokens = max_tokens
end
end
parser.parse(ARGV)
# Create Storyteller instance
storyteller = Storyteller.new()
# Read file and initialize zones
if !input_file_path.empty?
# puts "d: Using input file #{input_file_path}"
input_file = File.open(input_file_path)
end
prompt = storyteller.read_file(input_file)
input_file.close
# Build GPT-3 request
prompt = storyteller.complete(prompt, make_request, verbose)
pp prompt if verbose
exit 0 if !make_request
if !output_file_path.empty?
# puts "d: Using output file #{input_file_path}"
output_file = File.open(output_file_path, "w")
end
storyteller.write_file(output_file, prompt, use_color)
output_file.close
rescue ex : OptionParser::InvalidOption
STDERR.puts parser
STDERR.puts "\nERROR: #{ex.message}"
exit 1
end
def complete(prompt : Prompt, make_request : Bool, verbose : Bool)
builder = Builder::OpenAIChat.new(verbose: verbose)
messages = builder.build(prompt)
STDERR.puts messages if verbose
return prompt if !make_request
channel_ready = Channel(Bool).new
channel_tick = Channel(Bool).new
# sp = Spin.new(0.5, Spinner::Charset[:progress])
spawn do
tick = 0
loop do
tick += 1
print "."
if tick > (3 * 60)
print "(timeout)"
exit 1
end
channel_tick.send(true)
sleep 1.seconds
end
end
spawn do
openai = OpenAI::Client.new(access_token: ENV.fetch("OPENAI_API_KEY"))
result = openai.chat(
"gpt-3.5-turbo",
messages,
{
"temperature" => 0.82,
"presence_penalty" => 1,
"frequency_penalty" => 1,
"max_tokens" => 256
}
)
prompt.past_zone.content << "\n" + result.choices.first["message"]["content"] + "\n"
channel_ready.send(true)
end
# sp.start
channel_ready.receive
channel_ready.close
# sp.stop
prompt
end
def read_file(input_file : IO::FileDescriptor)
content = input_file.gets_to_end
# puts "d: building parser"
parser = Parser::PromptString.new
# puts "d: parsing"
prompt = parser.parse(content)
# pp prompt
end
def write_file(output_file : IO::FileDescriptor, prompt : Prompt, use_color : Bool)
# STDERR.puts "d: building builder"
builder = Builder::PromptString.new(use_color)
# STDERR.puts "d: building"
text = builder.build(prompt)
output_file.write_string(text.to_slice)
end
def display_completion(completion : String)
# Code pour afficher la complétion
end
end
Storyteller.start(ARGV)