diff --git a/.github/workflows/auto-assign-pr.yml b/.github/workflows/auto-assign-pr.yml new file mode 100644 index 000000000..98df89707 --- /dev/null +++ b/.github/workflows/auto-assign-pr.yml @@ -0,0 +1,28 @@ +name: Auto-assign PR to Author + +on: + pull_request: + types: [opened] + +jobs: + auto-assign: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Auto-assign PR to author + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const pull_number = context.payload.pull_request.number; + const author = context.payload.pull_request.user.login; + + await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: pull_number, + assignees: [author] + }); + + console.log(`Assigned PR #${pull_number} to ${author}`); \ No newline at end of file diff --git a/app/javascript/dashboard/components-next/breadcrumb/Breadcrumb.vue b/app/javascript/dashboard/components-next/breadcrumb/Breadcrumb.vue index 8ca325607..8a1b26e90 100644 --- a/app/javascript/dashboard/components-next/breadcrumb/Breadcrumb.vue +++ b/app/javascript/dashboard/components-next/breadcrumb/Breadcrumb.vue @@ -15,8 +15,8 @@ const emit = defineEmits(['click']); const { t } = useI18n(); -const onClick = event => { - emit('click', event); +const onClick = (item, index) => { + emit('click', item, index); }; @@ -24,22 +24,25 @@ const onClick = event => { diff --git a/app/javascript/dashboard/components-next/captain/SettingsPageLayout.vue b/app/javascript/dashboard/components-next/captain/SettingsPageLayout.vue new file mode 100644 index 000000000..4a48794b3 --- /dev/null +++ b/app/javascript/dashboard/components-next/captain/SettingsPageLayout.vue @@ -0,0 +1,91 @@ + + + diff --git a/app/javascript/dashboard/components-next/captain/assistant/ResponseCard.vue b/app/javascript/dashboard/components-next/captain/assistant/ResponseCard.vue index 7879411c8..a924ca228 100644 --- a/app/javascript/dashboard/components-next/captain/assistant/ResponseCard.vue +++ b/app/javascript/dashboard/components-next/captain/assistant/ResponseCard.vue @@ -55,6 +55,10 @@ const props = defineProps({ type: Boolean, default: false, }, + showMenu: { + type: Boolean, + default: true, + }, }); const emit = defineEmits(['action', 'navigate', 'select', 'hover']); @@ -130,7 +134,7 @@ const handleDocumentableClick = () => { {{ question }} -
+
+import { reactive, computed, watch } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useVuelidate } from '@vuelidate/core'; +import { required, minLength } from '@vuelidate/validators'; + +import Input from 'dashboard/components-next/input/Input.vue'; +import Button from 'dashboard/components-next/button/Button.vue'; +import Editor from 'dashboard/components-next/Editor/Editor.vue'; + +const props = defineProps({ + assistant: { + type: Object, + default: () => ({}), + }, +}); + +const emit = defineEmits(['submit']); + +const { t } = useI18n(); + +const initialState = { + name: '', + description: '', + productName: '', + features: { + conversationFaqs: false, + memories: false, + }, +}; + +const state = reactive({ ...initialState }); + +const validationRules = { + name: { required, minLength: minLength(1) }, + description: { required, minLength: minLength(1) }, + productName: { required, minLength: minLength(1) }, +}; + +const v$ = useVuelidate(validationRules, state); + +const getErrorMessage = field => { + return v$.value[field].$error ? v$.value[field].$errors[0].$message : ''; +}; + +const formErrors = computed(() => ({ + name: getErrorMessage('name'), + description: getErrorMessage('description'), + productName: getErrorMessage('productName'), +})); + +const updateStateFromAssistant = assistant => { + const { config = {} } = assistant; + state.name = assistant.name; + state.description = assistant.description; + state.productName = config.product_name; + state.features = { + conversationFaqs: config.feature_faq || false, + memories: config.feature_memory || false, + }; +}; + +const handleBasicInfoUpdate = async () => { + const result = await Promise.all([ + v$.value.name.$validate(), + v$.value.description.$validate(), + v$.value.productName.$validate(), + ]).then(results => results.every(Boolean)); + if (!result) return; + + const payload = { + name: state.name, + description: state.description, + config: { + ...props.assistant.config, + product_name: state.productName, + feature_faq: state.features.conversationFaqs, + feature_memory: state.features.memories, + }, + }; + + emit('submit', payload); +}; + +watch( + () => props.assistant, + newAssistant => { + if (newAssistant) updateStateFromAssistant(newAssistant); + }, + { immediate: true } +); + + + diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantControlItems.vue b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantControlItems.vue new file mode 100644 index 000000000..2bbede899 --- /dev/null +++ b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantControlItems.vue @@ -0,0 +1,43 @@ + + + diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantSystemSettingsForm.vue b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantSystemSettingsForm.vue new file mode 100644 index 000000000..b86bdacb4 --- /dev/null +++ b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantSystemSettingsForm.vue @@ -0,0 +1,125 @@ + + + diff --git a/app/javascript/dashboard/featureFlags.js b/app/javascript/dashboard/featureFlags.js index 1a6ada9fa..98234539b 100644 --- a/app/javascript/dashboard/featureFlags.js +++ b/app/javascript/dashboard/featureFlags.js @@ -36,6 +36,7 @@ export const FEATURE_FLAGS = { REPORT_V4: 'report_v4', CHANNEL_INSTAGRAM: 'channel_instagram', CONTACT_CHATWOOT_SUPPORT_TEAM: 'contact_chatwoot_support_team', + CAPTAIN_V2: 'captain_integration_v2', }; export const PREMIUM_FEATURES = [ @@ -44,4 +45,5 @@ export const PREMIUM_FEATURES = [ FEATURE_FLAGS.CUSTOM_ROLES, FEATURE_FLAGS.AUDIT_LOGS, FEATURE_FLAGS.HELP_CENTER, + FEATURE_FLAGS.CAPTAIN_V2, ]; diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index b3e091722..fc8cff644 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -478,6 +478,37 @@ "ERROR_MESSAGE": "There was an error updating the assistant, please try again.", "NOT_FOUND": "Could not find the assistant. Please try again." }, + "SETTINGS": { + "BREADCRUMB": { + "ASSISTANT": "Assistant" + }, + "BASIC_SETTINGS": { + "TITLE": "Basic settings", + "DESCRIPTION": "Customize what the assistant says when ending a conversation or transferring to a human." + }, + "SYSTEM_SETTINGS": { + "TITLE": "System settings", + "DESCRIPTION": "Customize what the assistant says when ending a conversation or transferring to a human." + }, + "CONTROL_ITEMS": { + "TITLE": "The Fun Stuff", + "DESCRIPTION": "Add more control to the assistant. (a bit more visual like a story : Query guardrail → scenarios → output) Nudges user to actually utilise these.", + "OPTIONS": { + "GUARDRAILS": { + "TITLE": "Guardrails", + "DESCRIPTION": "Keeps things on track—only the kinds of questions you want your assistant to answer, nothing off-limits or off-topic." + }, + "SCENARIOS": { + "TITLE": "Scenarios", + "DESCRIPTION": "Give your assistant some context—like “what to do when a user is stuck,” or “how to act during a refund request.”" + }, + "RESPONSE_GUIDELINES": { + "TITLE": "Response guidelines", + "DESCRIPTION": "The vibe and structure of your assistant’s replies—clear and friendly? Short and snappy? Detailed and formal?" + } + } + } + }, "OPTIONS": { "EDIT_ASSISTANT": "Edit Assistant", "DELETE_ASSISTANT": "Delete Assistant", diff --git a/app/javascript/dashboard/routes/dashboard/captain/assistants/Edit.vue b/app/javascript/dashboard/routes/dashboard/captain/assistants/Edit.vue index 8b64e2663..2b2506934 100644 --- a/app/javascript/dashboard/routes/dashboard/captain/assistants/Edit.vue +++ b/app/javascript/dashboard/routes/dashboard/captain/assistants/Edit.vue @@ -5,10 +5,13 @@ import { useStore } from 'dashboard/composables/store'; import { useMapGetter } from 'dashboard/composables/store'; import { useAlert } from 'dashboard/composables'; import { useI18n } from 'vue-i18n'; +import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import PageLayout from 'dashboard/components-next/captain/PageLayout.vue'; import EditAssistantForm from '../../../../components-next/captain/pageComponents/assistant/EditAssistantForm.vue'; import AssistantPlayground from 'dashboard/components-next/captain/assistant/AssistantPlayground.vue'; +import AssistantSettings from 'dashboard/routes/dashboard/captain/assistants/settings/Settings.vue'; + const route = useRoute(); const store = useStore(); const { t } = useI18n(); @@ -19,6 +22,16 @@ const assistant = computed(() => store.getters['captainAssistants/getRecord'](Number(assistantId)) ); +const isFeatureEnabledonAccount = useMapGetter( + 'accounts/isFeatureEnabledonAccount' +); +const currentAccountId = useMapGetter('getCurrentAccountId'); + +const isCaptainV2Enabled = isFeatureEnabledonAccount.value( + currentAccountId.value, + FEATURE_FLAGS.CAPTAIN_V2 +); + const isAssistantAvailable = computed(() => !!assistant.value?.id); const handleSubmit = async updatedAssistant => { @@ -36,14 +49,16 @@ const handleSubmit = async updatedAssistant => { }; onMounted(() => { - if (!isAssistantAvailable.value) { + if (!isAssistantAvailable.value || !isCaptainV2Enabled) { store.dispatch('captainAssistants/show', assistantId); } });