From 773be6f8ec85426edfea60502539e4ab706bd040 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 26 Feb 2024 20:20:12 +0530 Subject: [PATCH] feat: Interface to validate response_source (#8894) - This PR adds a UI to validate the response source quality quickly. It also helps to test with sample questions and update responses in the database when missing. Co-authored-by: Pranav Raj S --- .../super_admin/application_controller.rb | 15 ++ app/javascript/packs/superadmin_pages.js | 27 ++++ .../components/playground/BotMessage.vue | 17 +++ .../components/playground/Header.vue | 40 +++++ .../components/playground/TypingIndicator.vue | 13 ++ .../components/playground/UserMessage.vue | 17 +++ .../components/playground/assets/typing.gif | Bin 0 -> 19296 bytes .../views/playground/Index.vue | 139 ++++++++++++++++++ app/views/super_admin/accounts/show.html.erb | 2 +- .../super_admin/application/show.html.erb | 65 ++++++++ app/views/super_admin/users/show.html.erb | 8 +- config/routes.rb | 5 +- .../super_admin/enterprise_base_controller.rb | 8 + .../response_documents_controller.rb | 2 +- .../response_sources_controller.rb | 34 ++++- .../super_admin/responses_controller.rb | 2 +- enterprise/app/jobs/response_builder_job.rb | 22 +-- enterprise/app/models/response_source.rb | 5 + .../message_templates/response_bot_service.rb | 71 +++++---- .../response_sources/chat.html.erb | 5 + .../response_sources/show.html.erb | 71 +++++++++ enterprise/lib/chat_gpt.rb | 2 +- tailwind.config.js | 1 + 23 files changed, 514 insertions(+), 57 deletions(-) create mode 100644 app/javascript/superadmin_pages/components/playground/BotMessage.vue create mode 100644 app/javascript/superadmin_pages/components/playground/Header.vue create mode 100644 app/javascript/superadmin_pages/components/playground/TypingIndicator.vue create mode 100644 app/javascript/superadmin_pages/components/playground/UserMessage.vue create mode 100644 app/javascript/superadmin_pages/components/playground/assets/typing.gif create mode 100644 app/javascript/superadmin_pages/views/playground/Index.vue create mode 100644 app/views/super_admin/application/show.html.erb create mode 100644 enterprise/app/controllers/super_admin/enterprise_base_controller.rb create mode 100644 enterprise/app/views/super_admin/response_sources/chat.html.erb create mode 100644 enterprise/app/views/super_admin/response_sources/show.html.erb diff --git a/app/controllers/super_admin/application_controller.rb b/app/controllers/super_admin/application_controller.rb index 3b98a6e21..775fb34fc 100644 --- a/app/controllers/super_admin/application_controller.rb +++ b/app/controllers/super_admin/application_controller.rb @@ -5,6 +5,10 @@ # If you want to add pagination or other controller-level concerns, # you're free to overwrite the RESTful controller actions. class SuperAdmin::ApplicationController < Administrate::ApplicationController + include ActionView::Helpers::TagHelper + include ActionView::Context + + helper_method :render_vue_component # authenticiation done via devise : SuperAdmin Model before_action :authenticate_super_admin! @@ -23,6 +27,17 @@ class SuperAdmin::ApplicationController < Administrate::ApplicationController private + def render_vue_component(component_name, props = {}) + html_options = { + id: 'app', + data: { + component_name: component_name, + props: props.to_json + } + } + content_tag(:div, '', html_options) + end + def invalid_action_perfomed # rubocop:disable Rails/I18nLocaleTexts flash[:error] = 'Invalid action performed' diff --git a/app/javascript/packs/superadmin_pages.js b/app/javascript/packs/superadmin_pages.js index ef82fc261..301d2bbee 100644 --- a/app/javascript/packs/superadmin_pages.js +++ b/app/javascript/packs/superadmin_pages.js @@ -1 +1,28 @@ import 'chart.js'; +import Vue from 'vue'; +import VueDOMPurifyHTML from 'vue-dompurify-html'; +Vue.use(VueDOMPurifyHTML); + +const PlaygroundIndex = () => + import('../superadmin_pages/views/playground/Index.vue'); + +const ComponentMapping = { + PlaygroundIndex: PlaygroundIndex, +}; + +const renderComponent = (componentName, props) => { + Vue.component(componentName, ComponentMapping[componentName]); + new Vue({ + data: { props: props }, + template: `<${componentName} :component-data="props"/>`, + }).$mount('#app'); +}; + +document.addEventListener('DOMContentLoaded', () => { + const element = document.getElementById('app'); + if (element) { + const componentName = element.dataset.componentName; + const props = JSON.parse(element.dataset.props); + renderComponent(componentName, props); + } +}); diff --git a/app/javascript/superadmin_pages/components/playground/BotMessage.vue b/app/javascript/superadmin_pages/components/playground/BotMessage.vue new file mode 100644 index 000000000..51cff90fe --- /dev/null +++ b/app/javascript/superadmin_pages/components/playground/BotMessage.vue @@ -0,0 +1,17 @@ + + + diff --git a/app/javascript/superadmin_pages/components/playground/Header.vue b/app/javascript/superadmin_pages/components/playground/Header.vue new file mode 100644 index 000000000..e17dad415 --- /dev/null +++ b/app/javascript/superadmin_pages/components/playground/Header.vue @@ -0,0 +1,40 @@ + + + diff --git a/app/javascript/superadmin_pages/components/playground/TypingIndicator.vue b/app/javascript/superadmin_pages/components/playground/TypingIndicator.vue new file mode 100644 index 000000000..c11e523da --- /dev/null +++ b/app/javascript/superadmin_pages/components/playground/TypingIndicator.vue @@ -0,0 +1,13 @@ + + + diff --git a/app/javascript/superadmin_pages/components/playground/UserMessage.vue b/app/javascript/superadmin_pages/components/playground/UserMessage.vue new file mode 100644 index 000000000..ee90f163e --- /dev/null +++ b/app/javascript/superadmin_pages/components/playground/UserMessage.vue @@ -0,0 +1,17 @@ + + + diff --git a/app/javascript/superadmin_pages/components/playground/assets/typing.gif b/app/javascript/superadmin_pages/components/playground/assets/typing.gif new file mode 100644 index 0000000000000000000000000000000000000000..b288d2559786cff49bc67fd8d994f85a7db6188f GIT binary patch literal 19296 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 +

+ +
+
+ + +
+ +
+
+