feat: new Captain Editor (#13235)

Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
This commit is contained in:
Shivam Mishra
2026-01-21 13:39:07 +05:30
committed by GitHub
parent c77c9c9d8a
commit 6a482926b4
83 changed files with 3887 additions and 1798 deletions

View File

@@ -0,0 +1,32 @@
module Enterprise::Captain::BaseTaskService
def perform
return { error: I18n.t('captain.copilot_limit'), error_code: 429 } unless responses_available?
unless captain_tasks_enabled?
return { error: I18n.t('captain.upgrade') } if ChatwootApp.chatwoot_cloud?
return { error: I18n.t('captain.disabled') }
end
result = super
increment_usage if successful_result?(result)
result
end
private
def responses_available?
return true unless ChatwootApp.chatwoot_cloud?
account.usage_limits[:captain][:responses][:current_available].positive?
end
def successful_result?(result)
result.is_a?(Hash) && result[:message].present? && !result[:error]
end
def increment_usage
Rails.logger.info("[CAPTAIN][#{self.class.name}] Incrementing response usage for account #{account.id}")
account.increment_response_usage
end
end

View File

@@ -1,82 +0,0 @@
module Enterprise::Integrations::OpenaiProcessorService
ALLOWED_EVENT_NAMES = %w[rephrase summarize reply_suggestion label_suggestion fix_spelling_grammar shorten expand
make_friendly make_formal simplify].freeze
CACHEABLE_EVENTS = %w[label_suggestion].freeze
def label_suggestion_message
payload = label_suggestion_body
return nil if payload.blank?
response = make_api_call(label_suggestion_body)
return response if response[:error].present?
# LLMs are not deterministic, so this is bandaid solution
# To what you ask? Sometimes, the response includes
# "Labels:" in it's response in some format. This is a hacky way to remove it
# TODO: Fix with with a better prompt
{ message: response[:message] ? response[:message].gsub(/^(label|labels):/i, '') : '' }
end
private
def labels_with_messages
return nil unless valid_conversation?(conversation)
labels = hook.account.labels.pluck(:title).join(', ')
character_count = labels.length
messages = init_messages_body(false)
add_messages_until_token_limit(conversation, messages, false, character_count)
return nil if messages.blank? || labels.blank?
"Messages:\n#{messages}\nLabels:\n#{labels}"
end
def valid_conversation?(conversation)
return false if conversation.nil?
return false if conversation.messages.incoming.count < 3
# Think Mark think, at this point the conversation is beyond saving
return false if conversation.messages.count > 100
# if there are more than 20 messages, only trigger this if the last message is from the client
return false if conversation.messages.count > 20 && !conversation.messages.last.incoming?
true
end
def summarize_body
{
model: self.class::GPT_MODEL,
messages: [
{ role: 'system',
content: prompt_from_file('summary', enterprise: true) },
{ role: 'user', content: conversation_messages }
]
}.to_json
end
def label_suggestion_body
return unless label_suggestions_enabled?
content = labels_with_messages
return value_from_cache if content.blank?
{
model: self.class::GPT_MODEL,
messages: [
{
role: 'system',
content: prompt_from_file('label_suggestion', enterprise: true)
},
{ role: 'user', content: content }
]
}.to_json
end
def label_suggestions_enabled?
hook.settings['label_suggestion'].present?
end
end

View File

@@ -1 +0,0 @@
Your role is as an assistant to a customer support agent. You will be provided with a transcript of a conversation between a customer and the support agent, along with a list of potential labels. Your task is to analyze the conversation and select the two labels from the given list that most accurately represent the themes or issues discussed. Ensure you preserve the exact casing of the labels as they are provided in the list. Do not create new labels; only choose from those provided. Once you have made your selections, please provide your response as a comma-separated list of the provided labels. Remember, your response should only contain the labels you\'ve selected,in their original casing, and nothing else.

View File

@@ -1,28 +0,0 @@
As an AI-powered summarization tool, your task is to condense lengthy interactions between customer support agents and customers into brief, digestible summaries. The objective of these summaries is to provide a quick overview, enabling any agent, even those without prior context, to grasp the essence of the conversation promptly.
Make sure you strongly adhere to the following rules when generating the summary
1. Be brief and concise. The shorter the summary the better.
2. Aim to summarize the conversation in approximately 200 words, formatted as multiple small paragraphs that are easier to read.
3. Describe the customer intent in around 50 words.
4. Remove information that is not directly relevant to the customer's problem or the agent's solution. For example, personal anecdotes, small talk, etc.
5. Don't include segments of the conversation that didn't contribute meaningful content, like greetings or farewell.
6. The 'Action Items' should be a bullet list, arranged in order of priority if possible.
7. 'Action Items' should strictly encapsulate tasks committed to by the agent or left incomplete. Any suggestions made by the agent should not be included.
8. The 'Action Items' should be brief and concise
9. Mark important words or parts of sentences as bold.
10. Apply markdown syntax to format any included code, using backticks.
11. Include a section for "Follow-up Items" or "Open Questions" if there are any unresolved issues or outstanding questions.
12. If any section does not have any content, remove that section and the heading from the response
13. Do not insert your own opinions about the conversation.
Reply in the user's language, as a markdown of the following format.
**Customer Intent**
**Conversation Summary**
**Action Items**
**Follow-up Items**