diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb index ec51305b5..e9d27c67a 100644 --- a/app/controllers/super_admin/app_configs_controller.rb +++ b/app/controllers/super_admin/app_configs_controller.rb @@ -49,7 +49,8 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController 'tiktok' => %w[TIKTOK_APP_ID TIKTOK_APP_SECRET], 'whatsapp_embedded' => %w[WHATSAPP_APP_ID WHATSAPP_APP_SECRET WHATSAPP_CONFIGURATION_ID WHATSAPP_API_VERSION], 'notion' => %w[NOTION_CLIENT_ID NOTION_CLIENT_SECRET], - 'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI ENABLE_GOOGLE_OAUTH_LOGIN] + 'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI ENABLE_GOOGLE_OAUTH_LOGIN], + 'captain' => %w[CAPTAIN_OPEN_AI_API_KEY CAPTAIN_OPEN_AI_MODEL CAPTAIN_OPEN_AI_ENDPOINT] } @allowed_configs = mapping.fetch( diff --git a/app/helpers/super_admin/features.yml b/app/helpers/super_admin/features.yml index 34c7a8138..f21a97f78 100644 --- a/app/helpers/super_admin/features.yml +++ b/app/helpers/super_admin/features.yml @@ -2,13 +2,6 @@ # No need to replicate the same values in two places # ------- Premium Features ------- # -captain: - name: 'Captain' - description: 'Enable AI-powered conversations with your customers.' - enabled: <%= (ChatwootHub.pricing_plan != 'community') %> - icon: 'icon-captain' - config_key: 'captain' - enterprise: true saml: name: 'SAML SSO' description: 'Configuration for controlling SAML Single Sign-On availability' @@ -48,6 +41,12 @@ help_center: description: 'Allow agents to create help center articles and publish them in a portal.' enabled: true icon: 'icon-book-2-line' +captain: + name: 'Captain' + description: 'Enable AI-powered conversations with your customers.' + enabled: true + icon: 'icon-captain' + config_key: 'captain' # ------- Communication Channels ------- # live_chat: diff --git a/app/javascript/dashboard/api/captain/tasks.js b/app/javascript/dashboard/api/captain/tasks.js new file mode 100644 index 000000000..1b5a38335 --- /dev/null +++ b/app/javascript/dashboard/api/captain/tasks.js @@ -0,0 +1,107 @@ +/* global axios */ +import ApiClient from '../ApiClient'; + +/** + * A client for the Captain Tasks API. + * @extends ApiClient + */ +class TasksAPI extends ApiClient { + /** + * Creates a new TasksAPI instance. + */ + constructor() { + super('captain/tasks', { accountScoped: true }); + } + + /** + * Rewrites content with a specific operation. + * @param {Object} options - The rewrite options. + * @param {string} options.content - The content to rewrite. + * @param {string} options.operation - The rewrite operation (fix_spelling_grammar, casual, professional, etc). + * @param {string} [options.conversationId] - The conversation ID for context (required for 'improve'). + * @param {AbortSignal} [signal] - AbortSignal to cancel the request. + * @returns {Promise} A promise that resolves with the rewritten content. + */ + rewrite({ content, operation, conversationId }, signal) { + return axios.post( + `${this.url}/rewrite`, + { + content, + operation, + conversation_display_id: conversationId, + }, + { signal } + ); + } + + /** + * Summarizes a conversation. + * @param {string} conversationId - The conversation ID to summarize. + * @param {AbortSignal} [signal] - AbortSignal to cancel the request. + * @returns {Promise} A promise that resolves with the summary. + */ + summarize(conversationId, signal) { + return axios.post( + `${this.url}/summarize`, + { + conversation_display_id: conversationId, + }, + { signal } + ); + } + + /** + * Gets a reply suggestion for a conversation. + * @param {string} conversationId - The conversation ID. + * @param {AbortSignal} [signal] - AbortSignal to cancel the request. + * @returns {Promise} A promise that resolves with the reply suggestion. + */ + replySuggestion(conversationId, signal) { + return axios.post( + `${this.url}/reply_suggestion`, + { + conversation_display_id: conversationId, + }, + { signal } + ); + } + + /** + * Gets label suggestions for a conversation. + * @param {string} conversationId - The conversation ID. + * @param {AbortSignal} [signal] - AbortSignal to cancel the request. + * @returns {Promise} A promise that resolves with label suggestions. + */ + labelSuggestion(conversationId, signal) { + return axios.post( + `${this.url}/label_suggestion`, + { + conversation_display_id: conversationId, + }, + { signal } + ); + } + + /** + * Sends a follow-up message to continue refining a previous task result. + * @param {Object} options - The follow-up options. + * @param {Object} options.followUpContext - The follow-up context from a previous task. + * @param {string} options.message - The follow-up message/request from the user. + * @param {string} [options.conversationId] - The conversation ID for Langfuse session tracking. + * @param {AbortSignal} [signal] - AbortSignal to cancel the request. + * @returns {Promise} A promise that resolves with the follow-up response and updated follow-up context. + */ + followUp({ followUpContext, message, conversationId }, signal) { + return axios.post( + `${this.url}/follow_up`, + { + follow_up_context: followUpContext, + message, + conversation_display_id: conversationId, + }, + { signal } + ); + } +} + +export default new TasksAPI(); diff --git a/app/javascript/dashboard/api/integrations/openapi.js b/app/javascript/dashboard/api/integrations/openapi.js deleted file mode 100644 index 3fcf241ee..000000000 --- a/app/javascript/dashboard/api/integrations/openapi.js +++ /dev/null @@ -1,81 +0,0 @@ -/* global axios */ - -import ApiClient from '../ApiClient'; - -/** - * Represents the data object for a OpenAI hook. - * @typedef {Object} ConversationMessageData - * @property {string} [tone] - The tone of the message. - * @property {string} [content] - The content of the message. - * @property {string} [conversation_display_id] - The display ID of the conversation (optional). - */ - -/** - * A client for the OpenAI API. - * @extends ApiClient - */ -class OpenAIAPI extends ApiClient { - /** - * Creates a new OpenAIAPI instance. - */ - constructor() { - super('integrations', { accountScoped: true }); - - /** - * The conversation events supported by the API. - * @type {string[]} - */ - this.conversation_events = [ - 'summarize', - 'reply_suggestion', - 'label_suggestion', - ]; - - /** - * The message events supported by the API. - * @type {string[]} - */ - this.message_events = ['rephrase']; - } - - /** - * Processes an event using the OpenAI API. - * @param {Object} options - The options for the event. - * @param {string} [options.type='rephrase'] - The type of event to process. - * @param {string} [options.content] - The content of the event. - * @param {string} [options.tone] - The tone of the event. - * @param {string} [options.conversationId] - The ID of the conversation to process the event for. - * @param {string} options.hookId - The ID of the hook to use for processing the event. - * @returns {Promise} A promise that resolves with the result of the event processing. - */ - processEvent({ type = 'rephrase', content, tone, conversationId, hookId }) { - /** - * @type {ConversationMessageData} - */ - let data = { - tone, - content, - }; - - // Always include conversation_display_id when available for session tracking - if (conversationId) { - data.conversation_display_id = conversationId; - } - - // For conversation-level events, only send conversation_display_id - if (this.conversation_events.includes(type)) { - data = { - conversation_display_id: conversationId, - }; - } - - return axios.post(`${this.url}/hooks/${hookId}/process_event`, { - event: { - name: type, - data, - }, - }); - } -} - -export default new OpenAIAPI(); diff --git a/app/javascript/dashboard/assets/scss/_next-colors.scss b/app/javascript/dashboard/assets/scss/_next-colors.scss index f23c01d42..4d7975a32 100644 --- a/app/javascript/dashboard/assets/scss/_next-colors.scss +++ b/app/javascript/dashboard/assets/scss/_next-colors.scss @@ -94,6 +94,19 @@ --gray-11: 100 100 100; --gray-12: 32 32 32; + --violet-1: 253 252 254; + --violet-2: 250 248 255; + --violet-3: 244 240 254; + --violet-4: 235 228 255; + --violet-5: 225 217 255; + --violet-6: 212 202 254; + --violet-7: 194 178 248; + --violet-8: 169 153 236; + --violet-9: 110 86 207; + --violet-10: 100 84 196; + --violet-11: 101 85 183; + --violet-12: 47 38 95; + --background-color: 253 253 253; --text-blue: 8 109 224; --border-container: 236 236 236; @@ -209,6 +222,19 @@ --gray-11: 180 180 180; --gray-12: 238 238 238; + --violet-1: 20 17 31; + --violet-2: 27 21 37; + --violet-3: 41 31 67; + --violet-4: 50 37 85; + --violet-5: 60 46 105; + --violet-6: 71 56 135; + --violet-7: 86 70 151; + --violet-8: 110 86 171; + --violet-9: 110 86 207; + --violet-10: 125 109 217; + --violet-11: 169 153 236; + --violet-12: 226 221 254; + --background-color: 18 18 19; --border-strong: 52 52 52; --border-weak: 38 38 42; diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue b/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue deleted file mode 100644 index f7a94fb85..000000000 --- a/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue +++ /dev/null @@ -1,160 +0,0 @@ - - - diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceCTAButton.vue b/app/javascript/dashboard/components/widgets/AIAssistanceCTAButton.vue deleted file mode 100644 index 6fbdfe6e7..000000000 --- a/app/javascript/dashboard/components/widgets/AIAssistanceCTAButton.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - - - diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue deleted file mode 100644 index 04bba8f59..000000000 --- a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - - - diff --git a/app/javascript/dashboard/components/widgets/AICTAModal.vue b/app/javascript/dashboard/components/widgets/AICTAModal.vue deleted file mode 100644 index 13e201326..000000000 --- a/app/javascript/dashboard/components/widgets/AICTAModal.vue +++ /dev/null @@ -1,130 +0,0 @@ - - - diff --git a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue index 75afb5263..c924dd65d 100644 --- a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue +++ b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue @@ -46,11 +46,11 @@ const fileName = file => {