class SlackSmartBot #todo: add tests 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? #error connecting 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| #get the last from date if p.from > from[0..9].gsub("-", "/") from = p.from from_time_off = true end end #from will be the day before the last time off 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 = {} # store the messages by year/month 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 #to avoid rate limit Tier 3 (50 requests per minute) 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 # the order on repls is from older to newer 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 # the order on history is from newer to older 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? 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. To ensure accuracy, please read the text carefully and pay attention to any nuances or complexities in the language. Please also add the most important conversations in the summary. 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. The links added need to follow this \n" prompt_orig += "For example \n" prompt_orig += "Add also the date of the message for relevant conversations.\n" prompt_orig += "This is the conversation:\n" #sort by year/month from older to newer 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 num_tokens = Tiktoken.encoding_for_model(chatgpt.smartbot_model).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| num_tokens = Tiktoken.encoding_for_model(chatgpt.smartbot_model).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| num_tokens = Tiktoken.encoding_for_model(chatgpt.smartbot_model).encode(prompt).length #if model != chatgpt.smartbot_model 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#{res}" 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| "" }.join(", ")}" end end respond result_messages.join("\n").gsub("**", "*") end unreact :speech_balloon end end end end end end