From 0331815cc5a5faede2ca1abdbbe433cc57988124 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 25 Jul 2024 14:24:04 -0700 Subject: [PATCH 1/2] feat: Integration with Captain (alpha) (#9834) - Integration with captain (alpha) Co-authored-by: Pranav --- .env.example | 2 + .../accounts/integrations/apps_controller.rb | 2 +- app/jobs/hook_job.rb | 8 +++ app/models/inbox.rb | 11 ++- app/models/integrations/app.rb | 6 +- config/features.yml | 2 + config/integration/apps.yml | 53 +++++++++++++- config/locales/en.yml | 3 + lib/integrations/captain/processor_service.rb | 65 ++++++++++++++++++ .../dialogflow/processor_service.rb | 4 +- .../images/integrations/captain-dark.png | Bin 0 -> 2492 bytes .../dashboard/images/integrations/captain.png | Bin 0 -> 2496 bytes .../integrations/apps_controller_spec.rb | 4 +- spec/models/integrations/app_spec.rb | 16 ++--- 14 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 lib/integrations/captain/processor_service.rb create mode 100644 public/dashboard/images/integrations/captain-dark.png create mode 100644 public/dashboard/images/integrations/captain.png diff --git a/.env.example b/.env.example index befcde463..81228f00a 100644 --- a/.env.example +++ b/.env.example @@ -257,3 +257,5 @@ AZURE_APP_SECRET= # Set to true if you want to remove stale contact inboxes # contact_inboxes with no conversation older than 90 days will be removed # REMOVE_STALE_CONTACT_INBOX_JOB_STATUS=false + +# CAPTAIN_API_URL=http://localhost:3001/api diff --git a/app/controllers/api/v1/accounts/integrations/apps_controller.rb b/app/controllers/api/v1/accounts/integrations/apps_controller.rb index 358a15d87..c2284bc2e 100644 --- a/app/controllers/api/v1/accounts/integrations/apps_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/apps_controller.rb @@ -10,7 +10,7 @@ class Api::V1::Accounts::Integrations::AppsController < Api::V1::Accounts::BaseC private def fetch_apps - @apps = Integrations::App.all.select(&:active?) + @apps = Integrations::App.all.select { |app| app.active?(Current.account) } end def fetch_app diff --git a/app/jobs/hook_job.rb b/app/jobs/hook_job.rb index 29c6dccfe..1912fac01 100644 --- a/app/jobs/hook_job.rb +++ b/app/jobs/hook_job.rb @@ -9,6 +9,8 @@ class HookJob < ApplicationJob process_slack_integration(hook, event_name, event_data) when 'dialogflow' process_dialogflow_integration(hook, event_name, event_data) + when 'captain' + process_captain_integration(hook, event_name, event_data) when 'google_translate' google_translate_integration(hook, event_name, event_data) end @@ -35,6 +37,12 @@ class HookJob < ApplicationJob Integrations::Dialogflow::ProcessorService.new(event_name: event_name, hook: hook, event_data: event_data).perform end + def process_captain_integration(hook, event_name, event_data) + return unless ['message.created'].include?(event_name) + + Integrations::Captain::ProcessorService.new(event_name: event_data, hook: hook, event_data: event_data).perform + end + def google_translate_integration(hook, event_name, event_data) return unless ['message.created'].include?(event_name) diff --git a/app/models/inbox.rb b/app/models/inbox.rb index 17f4fc045..8158ab733 100644 --- a/app/models/inbox.rb +++ b/app/models/inbox.rb @@ -129,7 +129,16 @@ class Inbox < ApplicationRecord end def active_bot? - agent_bot_inbox&.active? || hooks.where(app_id: 'dialogflow', status: 'enabled').count.positive? + agent_bot_inbox&.active? || hooks.where(app_id: %w[dialogflow], + status: 'enabled').count.positive? || captain_enabled? + end + + def captain_enabled? + captain_hook = account.hooks.where( + app_id: %w[captain], status: 'enabled' + ).first + + captain_hook.present? && captain_hook.settings['inbox_ids'].split(',').include?(id.to_s) end def inbox_type diff --git a/app/models/integrations/app.rb b/app/models/integrations/app.rb index 10e221a80..9629bdd84 100644 --- a/app/models/integrations/app.rb +++ b/app/models/integrations/app.rb @@ -34,12 +34,14 @@ class Integrations::App end end - def active? + def active?(account) case params[:id] when 'slack' ENV['SLACK_CLIENT_SECRET'].present? when 'linear' - Current.account.feature_enabled?('linear_integration') + account.feature_enabled?('linear_integration') + when 'captain' + account.feature_enabled?('captain_integration') && ENV['CAPTAIN_API_URL'].present? else true end diff --git a/config/features.yml b/config/features.yml index 07552a238..42cf3460d 100644 --- a/config/features.yml +++ b/config/features.yml @@ -87,3 +87,5 @@ premium: true - name: linear_integration enabled: false +- name: captain_integration + enabled: false diff --git a/config/integration/apps.yml b/config/integration/apps.yml index cf6730aec..24c19595c 100644 --- a/config/integration/apps.yml +++ b/config/integration/apps.yml @@ -8,7 +8,58 @@ # settings_json_schema: the json schema used to validate the settings hash (https://json-schema.org/) # settings_form_schema: the formulate schema used in frontend to render settings form (https://vueformulate.com/) ######################################################## - +captain: + id: captain + logo: captain.png + i18n_key: captain + action: /captain + hook_type: account + allow_multiple_hooks: false + settings_json_schema: { + "type": "object", + "properties": { + "access_token": { "type": "string" }, + "account_id": { "type": "string" }, + "account_email": { "type": "string" }, + "assistant_id": { "type": "string" }, + "inbox_ids": { "type": "strings" }, + }, + "required": ["access_token", "account_id", "account_email", "assistant_id"], + "additionalProperties": false, + } + settings_form_schema: [ + { + "label": "Access Token", + "type": "text", + "name": "access_token", + "validation": "required", + }, + { + "label": "Account ID", + "type": "text", + "name": "account_id", + "validation": "required", + }, + { + "label": "Account Email", + "type": "text", + "name": "account_email", + "validation": "required", + }, + { + "label": "Assistant Id", + "type": "text", + "name": "assistant_id", + "validation": "required", + }, + { + "label": "Inbox Ids", + "type": "text", + "name": "inbox_ids", + "validation": "", + }, + ] + visible_properties: [] webhooks: id: webhook logo: webhooks.png diff --git a/config/locales/en.yml b/config/locales/en.yml index 802ed529d..63593ef04 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -227,6 +227,9 @@ en: linear: name: "Linear" description: "Create issues in Linear directly from your conversation window. Alternatively, link existing Linear issues for a more streamlined and efficient issue tracking process." + captain: + name: "Captain" + description: "Captain is a native AI assistant built for your product and trained on your company's knowledge base. It responds like a human and resolves customer queries effectively. Configure it to your inboxes easily." public_portal: search: search_placeholder: Search for article by title or body... diff --git a/lib/integrations/captain/processor_service.rb b/lib/integrations/captain/processor_service.rb new file mode 100644 index 000000000..20aa202fa --- /dev/null +++ b/lib/integrations/captain/processor_service.rb @@ -0,0 +1,65 @@ +class Integrations::Captain::ProcessorService < Integrations::BotProcessorService + pattr_initialize [:event_name!, :hook!, :event_data!] + + private + + def get_response(_session_id, message_content) + call_captain(message_content) + end + + def process_response(message, response) + if response == 'conversation_handoff' + message.conversation.bot_handoff! + else + create_conversation(message, { content: response }) + end + end + + def create_conversation(message, content_params) + return if content_params.blank? + + conversation = message.conversation + conversation.messages.create!( + content_params.merge( + { + message_type: :outgoing, + account_id: conversation.account_id, + inbox_id: conversation.inbox_id + } + ) + ) + end + + def call_captain(message_content) + url = "#{ENV.fetch('CAPTAIN_API_URL', nil)}/accounts/#{hook.settings['account_id']}/assistants/#{hook.settings['assistant_id']}/chat" + + headers = { + 'X-USER-EMAIL' => hook.settings['account_email'], + 'X-USER-TOKEN' => hook.settings['access_token'], + 'Content-Type' => 'application/json' + } + + body = { + message: message_content, + previous_messages: previous_messages + } + + response = HTTParty.post(url, headers: headers, body: body.to_json) + response.parsed_response['message'] + end + + def previous_messages + previous_messages = [] + conversation.messages.where(message_type: [:outgoing, :incoming]).where(private: false).offset(1).find_each do |message| + next if message.content_type != 'text' + + role = determine_role(message) + previous_messages << { message: message.content, type: role } + end + previous_messages + end + + def determine_role(message) + message.message_type == 'incoming' ? 'User' : 'Bot' + end +end diff --git a/lib/integrations/dialogflow/processor_service.rb b/lib/integrations/dialogflow/processor_service.rb index 103ef05c5..f3962a66c 100644 --- a/lib/integrations/dialogflow/processor_service.rb +++ b/lib/integrations/dialogflow/processor_service.rb @@ -14,13 +14,13 @@ class Integrations::Dialogflow::ProcessorService < Integrations::BotProcessorSer message.content end - def get_response(session_id, message) + def get_response(session_id, message_content) if hook.settings['credentials'].blank? Rails.logger.warn "Account: #{hook.try(:account_id)} Hook: #{hook.id} credentials are not present." && return end configure_dialogflow_client_defaults - detect_intent(session_id, message) + detect_intent(session_id, message_content) rescue Google::Cloud::PermissionDeniedError => e Rails.logger.warn "DialogFlow Error: (account-#{hook.try(:account_id)}, hook-#{hook.id}) #{e.message}" hook.prompt_reauthorization! diff --git a/public/dashboard/images/integrations/captain-dark.png b/public/dashboard/images/integrations/captain-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..edf8029d94d0adbe6060a3f5efc9e21a0480f9ba GIT binary patch literal 2492 zcmbuB`#;l*AICo%V@zkF%-zWS5+USvY>2VkqawGIbksV`C25A-71dHg(?ycoR<6}t zC)W@Yxixok$u*YEzWM{cKb_YvulMWuct75+U*9QC4mJk_B?SQhIDoOWasdGF{s;zm zdG?LZy}f5apQ$q69O`6x#O*Di+b~+5$k>aVveKWP0jO^3CBpmAx$e9#GOeKfqW>u3 zsm_NL&wbunejm|Sl+@0!u^DTOYG>9J@iwmp z=^ydl(V>u1 zgs(P9fh^Qn=MJ!tk>f%}MHwei!ka561meED-oq+Iw8*Ze$l!R*%7>$vrImc%GXZWs zYND?Td|LR%uDs{KBba&V>;auvyllQK-p8C}R!JiT9WN44bOgaJR*exZl1dun&>&eu zA|?}c(&R*=dOD;^){`Pv*VSbp>awKWRokF6O_9^%&6dJ*cE)eohuAOMedIG#2N_J| zdPfvF)ZUtulH@^b@Wil85G%#h>|;8+y2`BSD|4dPV@9CyF&$dYCXeF2zDzc}^qYLa z%H6z57-p@~xaf7HFI$|=6#*%P8?}EEtln(QFAk%BZ%0_&Q)Mb|i&^o3l+ur)s%n(Dts)fXQp@%tY%WTTD2 zaDDLQ97?&PEjFpSyx`X$1L~A@x9Lf2?x>qS`>aG_o5)6MhwWv}yF4@6ekJ<#EWgJw zs=6~o^CI3M#Bce*rj_YB9kHXH!oHTO78p6`$xvN85aF}{MN8;Ym1eLbPVykLD-H;R zL{+6!Q%ZecL9Koh5T|Yns_Q)`B1RrEgi&f}^&5&Lua|3xr7yialT=fGi^csra4(vh z@C^z zqNAmn^bqW&C`dUt^8T~t2J|Fvje{hp|LiVDbGzayjM*ydZ`>W?I8p%=3|V5Yy_%DH z2xi>p9ZFrkqSmkNa|e3;VT~8t&?TcIgwhQTriUMoYTDZ}hEYT{8VtZCX+R;yGagC= zpp>s*X%(3s6u{f&K}1*|%D)Z#!A=1n(j1R401~#)Ca#mj!AK$qRF%O0a32Xl^G)Bl zM}n}5*8RB*G%-vJ*@7wE5@VuOi7B7IOotjnDdS*8128D$Ak$dQHhZ{cxT^x{^<2eU zA*C6o_bby)t>4n6H4Bpbx3Wifh1g|((>g1W3z@ba+&c`8W+(iP7EHKkJ@l|)FaqxV zsmXEY0i-55Z2RU}G4r*A(*gGf<#VXI9#%4SE>b{SYcf*-K}DF1 zT@CcOO)V+$%S01#%GYJ2Xqw&$t~abyPn(BmR56zZ)3TM*3~K|<19Nj9>c&6jUfmw^ zc~_(5c#bYV_6l1oLU+rl-(AdpPznSZK-$U=)WUq9-A=4Xj7?Bb@3_JO^B}Gjeco`8C_r z&4qU-cQES2H*ZS5!v8tH8my`yoPWuV$WsJ7?;DM}~(UcnPbCg)~rVy`X{0M#! zN_o+BvTN3|Y0}J_E4X?3bW6OG&zUo(@P+=SNn0!O;lun>zt`6%XC9ktsMpQxgRM#l z0YkSrI^%$o3S{QS0@X-GIRgD7_i?nO3k;b-XJAk_G|ReEt` zPS7MfZmHK%uCs2Q62J3RbM%(CXk7x;T;%RJGH1gGZ+QzT9%gl-r=|Z`TVOWOPhBlr z`9cl=lknj+IHYj;U-aC<*e3}_s^_FvOOTEPRkeHN@>yt@pM}13=K$hof>;xA-qa{4 z75mdpwZZ-MSyC37g;{2$TRdHSM(edO@a>kEGnA<%T)rTZe7A*-Wm;Yi5J~0>s7+|* zrkHNpsasSK!gr1$TBkJJ9H$_-&&T*7G1TdjJl~FB^#a@Q>6ex@3R1I zT>G6hXJToitdc`6l8@1DoM44K9Vio;BUfpNHEcDDOOBytE$i0}Rxs~Lk)utflW^^K zqR-9?u=!;}U#xJu3gq<^(xV0-Qx)#@eQRieClC_Gyl3ln0cv(<)k%5&s)Nt=)O8!jU%UHlu@4EVojL`yX2eY7wb)h~rQtt){) zhIUa3=eh8^@(yXHfD45Ftq0JScAZ|~BShpXXy_ap3a3Rg&Uw^)Hf< zG++H_QnO8+jK8oTYV?!@m5#Abjpw z+7DnvHz#)xWe!z-mgz^t>3s)*#4xrNra02r*V3MZF)O5XdT_V>7fp5l=9Y{wTSr32 zfh@^jLVw20oac3^VDGzbUZSY+?WQju*{%PJf4L!CM3#(Le`Mf#EVtW@6ov->}S zvgAq|J?rep{!&DsPcBF*(%984dq82>bnL*R>ajh&WUnxf9lXYFNNZC7YH8G*#=+)s zf72O*!{eWCjPL?%=-yppygos3%=a(X?eGVD>&g)9`RfuH8D(9ZlW&$nLJiYOKRGj4 z2L~f}=bC+lkrsH=q!g;6ws~(lWp#i>8sZzK&tv1SmO0`AE7?2NP+IPl)QC@&0}03w zx)6yC4+$t@?a`9KD}w}~kc=G`of4!xme`qnW1y$J*KP>b5jh03e}xxup(R^xvp0jL z*y8z8X;}aG@xqkpZ$_M~8yudX5P8>#-OS(*{_1%y)ToNaXLCy%+_|3dRd9zH+QrUw zwW)7X?K)`Q zYaw6N`qt}Xe;m8(tM-?TEV){u@*6e)aXWt5nvj#XkSVIrNp~A7fIQ38GPXs<(N{K)TemA3eES?@TVr0h?4=zeb4l?cw$pxNyf+ow8MK)! z3h9targJK1kDkx_p!epM+(SbTdH^r9c|= z*`sET5(o_Y*aiyrRsh3HK)@_eAP_-NK#&EfT0+)w5LgtZ2GiI0SGYuYyy*a`WIv3K z4?7-M24#MHN}mSm-XfwR(r=xMWz z=<8TNa8(ZIB2o$X&BiVDk^x2S2JY{zLn5I$&i*0A(=qRf;E}wt@ucT`_50Q$w&YXJ z`vn~t+KK(}GvUVAjS{$~Hy9ZrZnJXK4H?*b5cNUlfP;aBysjWNm3wrN60-%|Nt#JVQC)#^v#Gb>-mJN#xwIbF#F)jrM0u*@Gg+NmY{% zZVX3yu&s`ZK=5z?x(kOFA@3*a#+3_a9yKc~2OJ1v?9^gxUY~tHJ}rlal{1bWef3?v zj)e8k|8)|jq{PE9H+jYMiZ`Yrw*cxdHp!>+bv~7zAYjmPt7B7S1-o;IV{)tW+?|U` zyr=xJYk7<^SkwC~v1#KnSZX@eSXwg0RBtXCFW7c4kjr$tm&1w8_bsAq;Z|`8i+S2$ z%L`Z|eWim3B$)-3)GVHn zi0KM(_1Ty^OTVoN5S*@sc7fTHiolX7;^p?PBR5T=(2qxq<;|lRFU&nju71zj&1r|$`U~0j3%{AP zKtpA)x?{gJ3|BF?+TD`{FXpKpDl?|$i9`?I(e>qe7Wd^|PHz%ESF6R!Lf98uvd*Wz z=cM+T_pkZvW($@WI33gO2bjND+G)*IyCFmh$AY@M1j1wjucJJ5EKS}Q!bqV5FJ3R~l#O#vA6&DT=D_=cwpFU?~=oRVl}Z@a!7lR$mZEW!|We*4xlroQw?eW8o< zlZgT>tERL(+M656k`0;ux!-dn;@gm{|K*_b;kRgEBV9uahPC(PpD#|uW-qi`bn3)+ zq(oZ2X1H|{4F7VP902i>Du)$QgIKLqqv-$mm+YTY2G^u8cU9U(Z35i?X|uI-vZyl) GO#2&M{8zvL literal 0 HcmV?d00001 diff --git a/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb b/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb index 43735046e..8662a5006 100644 --- a/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'Integration Apps API', type: :request do let(:admin) { create(:user, account: account, role: :administrator) } it 'returns all active apps without sensitive information if the user is an agent' do - first_app = Integrations::App.all.find(&:active?) + first_app = Integrations::App.all.find { |app| app.active?(account) } get api_v1_account_integrations_apps_url(account), headers: agent.create_new_auth_token, as: :json @@ -41,7 +41,7 @@ RSpec.describe 'Integration Apps API', type: :request do end it 'returns all active apps with sensitive information if user is an admin' do - first_app = Integrations::App.all.find(&:active?) + first_app = Integrations::App.all.find { |app| app.active?(account) } get api_v1_account_integrations_apps_url(account), headers: admin.create_new_auth_token, as: :json diff --git a/spec/models/integrations/app_spec.rb b/spec/models/integrations/app_spec.rb index 703069349..b9652ff89 100644 --- a/spec/models/integrations/app_spec.rb +++ b/spec/models/integrations/app_spec.rb @@ -5,10 +5,6 @@ RSpec.describe Integrations::App do let(:app) { apps.find(id: app_name) } let(:account) { create(:account) } - before do - allow(Current).to receive(:account).and_return(account) - end - describe '#name' do let(:app_name) { 'slack' } @@ -28,6 +24,10 @@ RSpec.describe Integrations::App do describe '#action' do let(:app_name) { 'slack' } + before do + allow(Current).to receive(:account).and_return(account) + end + context 'when the app is slack' do it 'returns the action URL with client_id and redirect_uri' do with_modified_env SLACK_CLIENT_ID: 'dummy_client_id' do @@ -46,7 +46,7 @@ RSpec.describe Integrations::App do context 'when the app is slack' do it 'returns true if SLACK_CLIENT_SECRET is present' do with_modified_env SLACK_CLIENT_SECRET: 'random_secret' do - expect(app.active?).to be true + expect(app.active?(account)).to be true end end end @@ -55,14 +55,14 @@ RSpec.describe Integrations::App do let(:app_name) { 'linear' } it 'returns true if the linear integration feature is disabled' do - expect(app.active?).to be false + expect(app.active?(account)).to be false end it 'returns false if the linear integration feature is enabled' do account.enable_features('linear_integration') account.save! - expect(app.active?).to be true + expect(app.active?(account)).to be true end end @@ -70,7 +70,7 @@ RSpec.describe Integrations::App do let(:app_name) { 'webhook' } it 'returns true' do - expect(app.active?).to be true + expect(app.active?(account)).to be true end end end From 6694db093fc0635d622fe3c49a42277e62fbd078 Mon Sep 17 00:00:00 2001 From: Pranav Date: Thu, 25 Jul 2024 16:26:00 -0700 Subject: [PATCH 2/2] feat: Update the design for dashboard_apps (#9840) This PR migrates the dashboard apps page to the new layout and includes the following updates: - Create a compact design for the back button - Add a back button to the settings header - Reduce letter-spacing on the description - Fix mobile styles - Migrate the layout of dashboard apps/index to new layouts Note: I've moved all feature help URLs from features.yml to the frontend. This change prevents features.yml from becoming bloated due to frontend modifications. --------- Co-authored-by: Sojan Jose --- .../assets/scss/widgets/_woot-tables.scss | 6 +- .../components/widgets/BackButton.vue | 60 ++++--- .../dashboard/helper/featureHelper.js | 20 ++- .../i18n/locale/en/integrations.json | 1 + .../dashboard/settings/SettingsHeader.vue | 1 + .../dashboard/settings/SettingsWrapper.vue | 2 +- .../components/BaseSettingsHeader.vue | 22 ++- .../DashboardApps/DashboardAppsRow.vue | 49 ++--- .../integrations/DashboardApps/Index.vue | 169 +++++++++--------- .../dashboard/settings/integrations/Index.vue | 2 +- .../integrations/integrations.routes.js | 17 +- 11 files changed, 188 insertions(+), 161 deletions(-) diff --git a/app/javascript/dashboard/assets/scss/widgets/_woot-tables.scss b/app/javascript/dashboard/assets/scss/widgets/_woot-tables.scss index 97535bf0d..b5182bf87 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_woot-tables.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_woot-tables.scss @@ -1,6 +1,10 @@ table { @apply border-spacing-0 text-sm w-full; + +} + +.woot-table { thead { th { @apply font-semibold tracking-[1px] text-left px-2.5 uppercase text-slate-900 dark:text-slate-200; @@ -16,9 +20,7 @@ table { @apply p-2.5 text-slate-700 dark:text-slate-100; } } -} -.woot-table { tr { .show-if-hover { transition: opacity 0.2s $swift-ease-out-function; diff --git a/app/javascript/dashboard/components/widgets/BackButton.vue b/app/javascript/dashboard/components/widgets/BackButton.vue index 2528aada7..111e838cb 100644 --- a/app/javascript/dashboard/components/widgets/BackButton.vue +++ b/app/javascript/dashboard/components/widgets/BackButton.vue @@ -1,34 +1,40 @@ + + - diff --git a/app/javascript/dashboard/helper/featureHelper.js b/app/javascript/dashboard/helper/featureHelper.js index edbbf4472..fb514f806 100644 --- a/app/javascript/dashboard/helper/featureHelper.js +++ b/app/javascript/dashboard/helper/featureHelper.js @@ -1,4 +1,20 @@ +const FEATURE_HELP_URLS = { + channel_email: 'https://chwt.app/hc/email', + channel_facebook: 'https://chwt.app/hc/fb', + help_center: 'https://chwt.app/hc/help-center', + agent_bots: 'https://chwt.app/hc/agent-bots', + team_management: 'https://chwt.app/hc/teams', + labels: 'https://chwt.app/hc/labels', + custom_attributes: 'https://chwt.app/hc/custom-attributes', + canned_responses: 'https://chwt.app/hc/canned', + integrations: 'https://chwt.app/hc/integrations', + campaigns: 'https://chwt.app/hc/campaigns', + reports: 'https://chwt.app/hc/reports', + message_reply_to: 'https://chwt.app/hc/reply-to', + sla: 'https://chwt.app/hc/sla', + dashboard_apps: 'https://chwt.app/hc/dashboard-apps', +}; + export function getHelpUrlForFeature(featureName) { - const { helpUrls } = window.chatwootConfig; - return helpUrls[featureName]; + return FEATURE_HELP_URLS[featureName]; } diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index 3ad79379f..80742f881 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -172,6 +172,7 @@ "HEADER_BTN_TXT": "Add a new dashboard app", "SIDEBAR_TXT": "

Dashboard Apps

Dashboard Apps allow organizations to embed an application inside the Chatwoot dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that inside the dashboard to provide user information, their orders, or their previous payment history.

When you embed your application using the dashboard in Chatwoot, your application will get the context of the conversation and contact as a window event. Implement a listener for the message event on your page to receive the context.

To add a new dashboard app, click on the button 'Add a new dashboard app'.

", "DESCRIPTION": "Dashboard Apps allow organizations to embed an application inside the dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that to provide user information, their orders, or their previous payment history.", + "LEARN_MORE": "Learn more about Dashboard Apps", "LIST": { "404": "There are no dashboard apps configured on this account yet", "LOADING": "Fetching dashboard apps...", diff --git a/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue b/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue index 2cb27b463..b378d8d11 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue @@ -10,6 +10,7 @@ v-if="showBackButton" :button-label="backButtonLabel" :back-url="backUrl" + class="ml-2 mr-4" />
diff --git a/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue b/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue index c3933689d..5a2110a8e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue @@ -1,6 +1,7 @@