class SlackSmartBot
def summarize(user, dest, channel, from, thread_ts)
save_stats(__method__)
ai_conn, message = SlackSmartBot::AI::OpenAI.connect({}, config, {}, service: :chat_gpt)
if message.empty?
ai_models_conn, message = SlackSmartBot::AI::OpenAI.connect({}, config, {}, service: :models)
end
if !message.empty? respond message
else
channels_bot_is_in = get_channels(bot_is_in: true)
if dest[0] == "D" and channel == dest
respond "Sorry, I can't summarize a direct message. Please use this command in a channel or supply the channel you want to summarize."
elsif !channels_bot_is_in.id.include?(channel) or !get_channel_members(channel).include?(config.nick_id_granular)
respond "Sorry, I can't summarize a channel where <@#{config.nick_id_granular}> and <@#{config.nick_id}> are not members. Please invite them to the channel."
elsif !get_channel_members(channel).include?(user.id)
respond "Sorry, I can't summarize a channel where you are not a member."
else
if (from == "" and channel == dest and Thread.current[:on_thread]) or thread_ts != ""
summarize_thread = true
if thread_ts == ""
thread_ts = Thread.current[:thread_ts]
else
if thread_ts.include?(".")
thread_ts = thread_ts
else
thread_ts = thread_ts.scan(/(\d+)/).join
thread_ts = "#{thread_ts[0..9]}.#{thread_ts[10..-1]}"
end
end
else
summarize_thread = false
end
from_time_off = false
if from == ""
from = (Time.now - (60 * 60) * 24 * 30).to_s
get_vacations()
if @vacations.key?(user.team_id_user) and @vacations[user.team_id_user].key?(:periods)
@vacations[user.team_id_user].periods.each do |p|
if p.from > from[0..9].gsub("-", "/") and p.from < Time.now.to_s[0..9].gsub("-", "/")
from = p.from
from_time_off = true
end
end
if from_time_off
from = (Time.strptime(from, "%Y/%m/%d") - (60 * 60) * 24).to_s
end
end
end
from.gsub!("-", "/")
if from.length == 10
from = from + " 00:00:00"
elsif from.length == 16
from = from + ":00"
end
from = Time.strptime(from, "%Y/%m/%d %H:%M:%S")
if summarize_thread
last_msg = respond("I'm going to summarize the thread messages", return_message: true)
elsif from_time_off
last_msg = respond("I'm going to summarize the messages since the day before your last time off #{from.strftime("%Y/%m/%d")} in <##{channel}>. This may take a while.", return_message: true)
else
last_msg = respond("I'm going to summarize the messages since #{from.strftime("%Y/%m/%d %H:%M:%S")} in <##{channel}>. This may take a while.", return_message: true)
end
@history_still_running ||= false
if @history_still_running
respond "Due to Slack API rate limit, `summarize` command is limited. Waiting for other `summarize` command to finish."
num_times = 0
while @history_still_running and num_times < 30
num_times += 1
sleep 1
end
if @history_still_running
respond "Sorry, Another `summarize` command is still running after 30 seconds. Please try again later."
end
end
unless @history_still_running
@history_still_running = true
react :running
if summarize_thread
hist = client_granular.conversations_history(channel: channel, oldest: thread_ts, inclusive: true, limit: 1)
else
hist = client_granular.conversations_history(channel: channel)
end
messages = {} act_users = {}
act_threads = {}
hist.messages.each do |message|
if Time.at(message.ts.to_f) >= from or summarize_thread
year_month = Time.at(message.ts.to_f).strftime("%Y/%m")
messages[year_month] ||= []
if message.key?("thread_ts")
thread_ts_message = message.thread_ts
replies = client_granular.conversations_replies(channel: channel, ts: thread_ts_message, latest: last_msg.ts)
sleep 0.5 messages_replies = ["Thread Started about last message:"]
act_threads[message.ts] = replies.messages.size
replies.messages.each_with_index do |msgrepl, i|
act_users[msgrepl.user] ||= 0
act_users[msgrepl.user] += 1
messages_replies << "<@#{msgrepl.user}> (#{Time.at(msgrepl.ts.to_f)}) wrote:> #{msgrepl.text}" if i > 0
end
messages_replies << "Thread ended."
messages[year_month] += messages_replies.reverse end
act_users[message.user] ||= 0
act_users[message.user] += 1
url_to_message = "https://#{client.team.domain}.slack.com/archives/#{channel}/#{message.ts}"
messages[year_month] << "<@#{message.user}> (#{Time.at(message.ts.to_f)}) (link to the message: #{url_to_message}) wrote:> #{message.text}"
end
end
messages.each do |year_month, msgs|
messages[year_month] = msgs.reverse end
@history_still_running = false
unreact :running
if messages.empty?
respond "There are no Slack Messages since #{from}"
else
react :speech_balloon
chatgpt = ai_conn[user.team_id_user].chat_gpt
models = ai_models_conn[user.team_id_user].models
prompt_orig = "Could you please provide a summary of the given conversation, including all key points and supporting details?\n"
prompt_orig += "The summary should be comprehensive and accurately reflect the main message and arguments presented in the original text, while also being concise and easy to understand.\n"
prompt_orig += "To ensure accuracy, please read the text carefully and pay attention to any nuances or complexities in the language.\n"
prompt_orig += "Please also add the most important conversations in the summary.\n"
prompt_orig += "Additionally, the summary should avoid any personal biases or interpretations and remain objective and factual throughout.\n"
prompt_orig += "If you name an user remember to name it as <@user_id> so it is not replaced by the user name.\n"
prompt_orig += "Add the link to the message so it is easy to find it.\n"
prompt_orig += "Add also the date of the message for relevant conversations.\n"
prompt_orig += "This is the conversation:\n"
messages = messages.sort_by { |k, v| k }.to_h
@open_ai_model_info ||= {}
@open_ai_model_info[chatgpt.smartbot_model] ||= SlackSmartBot::AI::OpenAI.models(models.client, models, chatgpt.smartbot_model, return_response: true)
if @open_ai_model_info[chatgpt.smartbot_model].key?(:max_input_tokens)
max_num_tokens = @open_ai_model_info[chatgpt.smartbot_model][:max_input_tokens].to_i
elsif @open_ai_model_info[chatgpt.smartbot_model].key?(:max_tokens)
max_num_tokens = @open_ai_model_info[chatgpt.smartbot_model][:max_tokens].to_i
else
max_num_tokens = 8000
end
enc = Tiktoken.encoding_for_model("gpt-4") num_tokens = enc.encode(prompt_orig + messages.values.flatten.join).length
respond ":information_source: ChatGPT model: *#{chatgpt.smartbot_model}*. Max tokens: *#{max_num_tokens}*. Characters: #{messages.values.flatten.join.size}. Messages: #{messages.values.flatten.size}. Threads: #{act_threads.size}. Users: #{act_users.size}. Chatgpt tokens: *#{num_tokens}*"
prompts = []
i = 0
messages.each do |year_month, msgs|
msgs.each do |msg|
enc = Tiktoken.encoding_for_model("gpt-4") num_tokens = enc.encode(prompts[i].to_s + msg).length
i += 1 if num_tokens > max_num_tokens
prompts[i] ||= prompt_orig
prompts[i] += "#{msg}\n"
end
end
prompts.each_with_index do |prompt, i|
enc = Tiktoken.encoding_for_model("gpt-4") num_tokens = enc.encode(prompt).length
respond ":information_source: The total number of chatgpt tokens is more than the max allowed for this chatgpt model. *Part #{i + 1} of #{prompts.size}*.\n" if prompts.size > 1
success, res = SlackSmartBot::AI::OpenAI.send_gpt_chat(chatgpt.client, chatgpt.smartbot_model, prompt, chatgpt)
result_messages = []
if success
result_messages << "*ChatGPT:*\n#{transform_to_slack_markdown(res.to_s.strip)}"
else
result_messages << "*ChatGPT:*\nI'm sorry, I couldn't summarize the conversation. This is the issue: #{res}"
end
if i == prompts.size - 1
act_users.delete(config.nick_id_granular)
act_users.delete(config.nick_id)
act_users = act_users.sort_by { |k, v| v }.reverse
result_messages << "\n\t:runner: Most active users: #{act_users[0..2].map { |k, v| "<@#{k}> (#{v})" }.join(", ")}"
if act_threads.size > 0 and !summarize_thread
act_threads = act_threads.sort_by { |k, v| v }.reverse
result_messages << "\t:fire: Most active threads: #{act_threads[0..2].map { |k, v| "<https://#{client.team.domain}.slack.com/archives/#{channel}/#{k}|#{v - 1} replies>" }.join(", ")}"
end
end
respond result_messages.join("\n").gsub("**", "*")
end
unreact :speech_balloon
end
end
end
end
end
end