diff --git a/app/controllers/api/v1/accounts/integrations/hooks_controller.rb b/app/controllers/api/v1/accounts/integrations/hooks_controller.rb index 087a9b78d..f06cbb1e2 100644 --- a/app/controllers/api/v1/accounts/integrations/hooks_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/hooks_controller.rb @@ -8,6 +8,7 @@ class Api::V1::Accounts::Integrations::HooksController < Api::V1::Accounts::Base def update @hook.update!(permitted_params.slice(:status, :settings)) + @hook.reauthorized! if @hook.reauthorization_required? end def process_event @@ -18,7 +19,7 @@ class Api::V1::Accounts::Integrations::HooksController < Api::V1::Accounts::Base if response.nil? render json: { message: nil } elsif response[:error] - render json: { error: response[:error] }, status: :unprocessable_entity + render json: { error: response[:error], error_type: response[:error_type] }, status: :unprocessable_entity else render json: { message: response[:message] } end diff --git a/app/javascript/dashboard/composables/useAI.js b/app/javascript/dashboard/composables/useAI.js index 9076d5716..8aab69d75 100644 --- a/app/javascript/dashboard/composables/useAI.js +++ b/app/javascript/dashboard/composables/useAI.js @@ -8,6 +8,7 @@ import { useAlert, useTrack } from 'dashboard/composables'; import { useI18n } from 'vue-i18n'; import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; import OpenAPI from 'dashboard/api/integrations/openapi'; +import { frontendURL } from 'dashboard/helper/URLHelper'; /** * Cleans and normalizes a list of labels. @@ -155,6 +156,8 @@ export function useAI() { } }; + const accountId = computed(() => getters.getCurrentAccountId.value); + /** * Processes an AI event, such as rephrasing content. * @param {string} [type='rephrase'] - The type of AI event to process. @@ -173,11 +176,29 @@ export function useAI() { } = result; return generatedMessage; } catch (error) { - const errorData = error.response.data.error; + const errorData = error.response?.data; const errorMessage = errorData?.error?.message || + errorData?.error || t('INTEGRATION_SETTINGS.OPEN_AI.GENERATE_ERROR'); - useAlert(errorMessage); + + const errorType = errorData?.error_type; + if (errorType === 'rate_limit') { + useAlert(t('INTEGRATION_SETTINGS.OPEN_AI.RATE_LIMIT_ERROR'), { + duration: 5000, + }); + } else if (errorType === 'auth') { + useAlert(t('INTEGRATION_SETTINGS.OPEN_AI.AUTH_ERROR'), { + type: 'link', + to: frontendURL( + `accounts/${accountId.value}/settings/integrations/openai` + ), + message: t('INTEGRATION_SETTINGS.OPEN_AI.GO_TO_SETTINGS'), + duration: 5000, + }); + } else { + useAlert(errorMessage); + } return ''; } }; diff --git a/app/javascript/dashboard/composables/useIntegrationHook.js b/app/javascript/dashboard/composables/useIntegrationHook.js index 42546cceb..f91345c12 100644 --- a/app/javascript/dashboard/composables/useIntegrationHook.js +++ b/app/javascript/dashboard/composables/useIntegrationHook.js @@ -57,6 +57,14 @@ export const useIntegrationHook = integrationId => { return integrationType.value === 'single'; }); + /** + * Whether any hook needs reauthorization (e.g., API key issues) + * @type {import('vue').ComputedRef} + */ + const hookNeedsReauthorization = computed(() => { + return integration.value.hooks?.some(hook => hook.reauthorization_required); + }); + return { integration, integrationType, @@ -64,5 +72,6 @@ export const useIntegrationHook = integrationId => { isIntegrationSingle, isHookTypeInbox, hasConnectedHooks, + hookNeedsReauthorization, }; }; diff --git a/app/javascript/dashboard/i18n/locale/en/integrationApps.json b/app/javascript/dashboard/i18n/locale/en/integrationApps.json index b91b434f7..4c3a108a3 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrationApps.json +++ b/app/javascript/dashboard/i18n/locale/en/integrationApps.json @@ -55,8 +55,13 @@ "DISCONNECT": { "BUTTON_TEXT": "Disconnect" }, + "REAUTHORIZE": { + "BUTTON_TEXT": "Disconnect", + "DESCRIPTION": "Your API key may be invalid or your credit balance may be exhausted. Please disconnect and reconnect with a valid API key.", + "OPENAI_LINK": "Check your OpenAI dashboard" + }, "SIDEBAR_DESCRIPTION": { "DIALOGFLOW": "Dialogflow is a natural language processing platform for building conversational interfaces. Integrating it with {installationName} lets bots handle queries first and transfer them to agents when needed. It helps qualify leads and reduce agent workload by answering FAQs. To add Dialogflow, create a Service Account in Google Console and share the credentials. Refer to the docs for details" } } -} +} \ No newline at end of file diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index 21e9e72af..3ede029ea 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -184,7 +184,10 @@ "GENERATING": "Generating...", "CANCEL": "Cancel" }, - "GENERATE_ERROR": "There was an error processing the content, please verify your OpenAI API key and try again" + "GENERATE_ERROR": "Could not process request. Check your API key, credits, or rate limits.", + "RATE_LIMIT_ERROR": "Rate limit exceeded. Check your OpenAI usage tier.", + "AUTH_ERROR": "Could not authenticate. Check your API key or credits.", + "GO_TO_SETTINGS": "Click here to update" }, "DELETE": { "BUTTON_TEXT": "Delete", diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/SingleIntegrationHooks.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/SingleIntegrationHooks.vue index f32926d79..9b85564d3 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/SingleIntegrationHooks.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/SingleIntegrationHooks.vue @@ -1,8 +1,9 @@