feat: Implement UI for Agent Bots in settings and remove CSML support (#11276)

- Add agent bots management UI in settings with avatar upload
- Enable agent bot configuration for all inbox types
- Implement proper CRUD operations with webhook URL support
- Fix agent bots menu item visibility in settings sidebar
- Remove all CSML-related code and features
- Add migration to convert existing CSML bots to webhook bots
- Simplify agent bot model and services to focus on webhook bots
- Improve UI to differentiate between system bots and account bots

## Video 





https://github.com/user-attachments/assets/3f4edbb7-b758-468c-8dd6-a9537b983f7d

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Sojan Jose
2025-04-16 18:02:49 +05:30
committed by GitHub
co-authored by iamsivin Sivin Varghese Muhsin Keloth Pranav
parent 72509f9e38
commit 630826baed
41 changed files with 657 additions and 1019 deletions
@@ -1,102 +1,191 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ref, computed, onMounted } from 'vue';
import { useMapGetter, useStore } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { frontendURL } from 'dashboard/helper/URLHelper';
import AgentBotRow from './components/AgentBotRow.vue';
import SettingsLayout from '../SettingsLayout.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
import AgentBotModal from './components/AgentBotModal.vue';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
const MODAL_TYPES = {
CREATE: 'create',
EDIT: 'edit',
};
const router = useRouter();
const store = useStore();
const { t } = useI18n();
const accountId = useMapGetter('getCurrentAccountId');
const agentBots = useMapGetter('agentBots/getBots');
const uiFlags = useMapGetter('agentBots/getUIFlags');
const confirmDialog = ref(null);
const selectedBot = ref({});
const loading = ref({});
const modalType = ref(MODAL_TYPES.CREATE);
const agentBotModalRef = ref(null);
const agentBotDeleteDialogRef = ref(null);
const onConfigureNewBot = () => {
router.push({
name: 'agent_bots_csml_new',
});
const tableHeaders = computed(() => {
return [
t('AGENT_BOTS.LIST.TABLE_HEADER.DETAILS'),
t('AGENT_BOTS.LIST.TABLE_HEADER.URL'),
];
});
const selectedBotName = computed(() => selectedBot.value?.name || '');
const openAddModal = () => {
modalType.value = MODAL_TYPES.CREATE;
selectedBot.value = {};
agentBotModalRef.value.dialogRef.open();
};
const openEditModal = bot => {
modalType.value = MODAL_TYPES.EDIT;
selectedBot.value = bot;
agentBotModalRef.value.dialogRef.open();
};
const openDeletePopup = bot => {
selectedBot.value = bot;
agentBotDeleteDialogRef.value.open();
};
const deleteAgentBot = async id => {
try {
await store.dispatch('agentBots/delete', id);
useAlert(t('AGENT_BOTS.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) {
useAlert(t('AGENT_BOTS.DELETE.API.ERROR_MESSAGE'));
} finally {
loading.value[id] = false;
selectedBot.value = {};
}
};
const confirmDeletion = () => {
loading.value[selectedBot.value.id] = true;
deleteAgentBot(selectedBot.value.id);
agentBotDeleteDialogRef.value.close();
};
onMounted(() => {
store.dispatch('agentBots/get');
});
const onDeleteAgentBot = async bot => {
const ok = await confirmDialog.value.showConfirmation();
if (ok) {
try {
await store.dispatch('agentBots/delete', bot.id);
useAlert(t('AGENT_BOTS.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) {
useAlert(t('AGENT_BOTS.DELETE.API.ERROR_MESSAGE'));
}
}
};
const onEditAgentBot = bot => {
router.push(
frontendURL(
`accounts/${accountId.value}/settings/agent-bots/csml/${bot.id}`
)
);
};
</script>
<template>
<SettingsLayout
:is-loading="uiFlags.isFetching"
:loading-message="$t('AGENT_BOTS.LIST.LOADING')"
:loading-message="t('AGENT_BOTS.LIST.LOADING')"
:no-records-found="!agentBots.length"
:no-records-message="$t('AGENT_BOTS.LIST.404')"
:no-records-message="t('AGENT_BOTS.LIST.404')"
>
<template #header>
<BaseSettingsHeader
:title="$t('AGENT_BOTS.HEADER')"
:description="$t('AGENT_BOTS.DESCRIPTION')"
:link-text="$t('AGENT_BOTS.LEARN_MORE')"
:title="t('AGENT_BOTS.HEADER')"
:description="t('AGENT_BOTS.DESCRIPTION')"
:link-text="t('AGENT_BOTS.LEARN_MORE')"
feature-name="agent_bots"
>
<template #actions>
<Button
icon="i-lucide-circle-plus"
:label="$t('AGENT_BOTS.ADD.TITLE')"
@click="onConfigureNewBot"
@click="openAddModal"
/>
</template>
</BaseSettingsHeader>
</template>
<template #body>
<div class="flex-1 overflow-auto">
<table class="divide-y divide-slate-75 dark:divide-slate-700">
<tbody class="divide-y divide-n-weak text-n-slate-11">
<AgentBotRow
v-for="(agentBot, index) in agentBots"
:key="agentBot.id"
:agent-bot="agentBot"
:index="index"
@delete="onDeleteAgentBot"
@edit="onEditAgentBot"
/>
</tbody>
</table>
<woot-confirm-modal
ref="confirmDialog"
:title="$t('AGENT_BOTS.DELETE.TITLE')"
:description="$t('AGENT_BOTS.DELETE.DESCRIPTION')"
/>
</div>
<table class="min-w-full overflow-x-auto divide-y divide-n-strong">
<thead>
<th
v-for="thHeader in tableHeaders"
:key="thHeader"
class="py-4 font-semibold text-left ltr:pr-4 rtl:pl-4 text-n-slate-11"
>
{{ thHeader }}
</th>
</thead>
<tbody class="flex-1 divide-y divide-n-weak text-n-slate-12">
<tr v-for="bot in agentBots" :key="bot.id">
<td class="py-4 ltr:pr-4 rtl:pl-4">
<div class="flex flex-row items-center gap-4">
<Avatar
:name="bot.name"
:src="bot.thumbnail"
:size="40"
rounded-full
/>
<div>
<span class="block font-medium break-words">
{{ bot.name }}
<span
v-if="bot.system_bot"
class="text-xs text-n-slate-12 bg-n-blue-5 inline-block rounded-md py-0.5 px-1 ltr:ml-1 rtl:mr-1"
>
{{ $t('AGENT_BOTS.GLOBAL_BOT_BADGE') }}
</span>
</span>
<span class="text-sm text-n-slate-11">
{{ bot.description }}
</span>
</div>
</div>
</td>
<td class="py-4 ltr:pr-4 rtl:pl-4 text-sm">
{{ bot.outgoing_url || bot.bot_config?.webhook_url }}
</td>
<td class="py-4 min-w-xs">
<div class="flex gap-1 justify-end">
<Button
v-if="!bot.system_bot"
v-tooltip.top="t('AGENT_BOTS.EDIT.BUTTON_TEXT')"
icon="i-lucide-pen"
slate
xs
faded
:is-loading="loading[bot.id]"
@click="openEditModal(bot)"
/>
<Button
v-if="!bot.system_bot"
v-tooltip.top="t('AGENT_BOTS.DELETE.BUTTON_TEXT')"
icon="i-lucide-trash-2"
xs
ruby
faded
:is-loading="loading[bot.id]"
@click="openDeletePopup(bot)"
/>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<AgentBotModal
ref="agentBotModalRef"
:type="modalType"
:selected-bot="selectedBot"
/>
<Dialog
ref="agentBotDeleteDialogRef"
type="alert"
:title="t('AGENT_BOTS.DELETE.CONFIRM.TITLE')"
:description="
t('AGENT_BOTS.DELETE.CONFIRM.MESSAGE', { name: selectedBotName })
"
:is-loading="uiFlags.isDeleting"
:confirm-button-label="t('AGENT_BOTS.DELETE.CONFIRM.YES')"
:cancel-button-label="t('AGENT_BOTS.DELETE.CONFIRM.NO')"
@confirm="confirmDeletion"
/>
</SettingsLayout>
</template>
@@ -1,9 +1,6 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import Bot from './Index.vue';
import CsmlEditBot from './csml/Edit.vue';
import CsmlNewBot from './csml/New.vue';
import { frontendURL } from '../../../../helper/URLHelper';
import SettingsContent from '../Wrapper.vue';
import SettingsWrapper from '../SettingsWrapper.vue';
export default {
@@ -26,36 +23,5 @@ export default {
},
],
},
{
path: frontendURL('accounts/:accountId/settings/agent-bots'),
component: SettingsContent,
props: () => {
return {
headerTitle: 'AGENT_BOTS.HEADER',
icon: 'bot',
showBackButton: true,
};
},
children: [
{
path: 'csml/new',
name: 'agent_bots_csml_new',
component: CsmlNewBot,
meta: {
featureFlag: FEATURE_FLAGS.AGENT_BOTS,
permissions: ['administrator'],
},
},
{
path: 'csml/:botId',
name: 'agent_bots_csml_edit',
component: CsmlEditBot,
meta: {
featureFlag: FEATURE_FLAGS.AGENT_BOTS,
permissions: ['administrator'],
},
},
],
},
],
};
@@ -0,0 +1,251 @@
<script setup>
import { ref, computed, reactive, watch } from 'vue';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { required, helpers, url } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
import Input from 'dashboard/components-next/input/Input.vue';
import TextArea from 'dashboard/components-next/textarea/TextArea.vue';
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
const props = defineProps({
type: {
type: String,
default: 'create',
validator: value => ['create', 'edit'].includes(value),
},
selectedBot: {
type: Object,
default: () => ({}),
},
});
const MODAL_TYPES = {
CREATE: 'create',
EDIT: 'edit',
};
const store = useStore();
const { t } = useI18n();
const dialogRef = ref(null);
const uiFlags = useMapGetter('agentBots/getUIFlags');
const formState = reactive({
botName: '',
botDescription: '',
botUrl: '',
botAvatar: null,
botAvatarUrl: '',
});
const v$ = useVuelidate(
{
botName: {
required: helpers.withMessage(
() => t('AGENT_BOTS.FORM.ERRORS.NAME'),
required
),
},
botUrl: {
required: helpers.withMessage(
() => t('AGENT_BOTS.FORM.ERRORS.URL'),
required
),
url: helpers.withMessage(
() => t('AGENT_BOTS.FORM.ERRORS.VALID_URL'),
url
),
},
},
formState
);
const isLoading = computed(() =>
props.type === MODAL_TYPES.CREATE
? uiFlags.value.isCreating
: uiFlags.value.isUpdating
);
const dialogTitle = computed(() =>
props.type === MODAL_TYPES.CREATE
? t('AGENT_BOTS.ADD.TITLE')
: t('AGENT_BOTS.EDIT.TITLE')
);
const confirmButtonLabel = computed(() =>
props.type === MODAL_TYPES.CREATE
? t('AGENT_BOTS.FORM.CREATE')
: t('AGENT_BOTS.FORM.UPDATE')
);
const botNameError = computed(() =>
v$.value.botName.$error ? v$.value.botName.$errors[0]?.$message : ''
);
const botUrlError = computed(() =>
v$.value.botUrl.$error ? v$.value.botUrl.$errors[0]?.$message : ''
);
const resetForm = () => {
Object.assign(formState, {
botName: '',
botDescription: '',
botUrl: '',
botAvatar: null,
botAvatarUrl: '',
});
v$.value.$reset();
};
const handleImageUpload = ({ file, url: avatarUrl }) => {
formState.botAvatar = file;
formState.botAvatarUrl = avatarUrl;
};
const handleAvatarDelete = async () => {
if (props.selectedBot?.id) {
try {
await store.dispatch(
'agentBots/deleteAgentBotAvatar',
props.selectedBot.id
);
formState.botAvatar = null;
formState.botAvatarUrl = '';
useAlert(t('AGENT_BOTS.AVATAR.SUCCESS_DELETE'));
} catch (error) {
useAlert(t('AGENT_BOTS.AVATAR.ERROR_DELETE'));
}
} else {
formState.botAvatar = null;
formState.botAvatarUrl = '';
}
};
const handleSubmit = async () => {
v$.value.$touch();
if (v$.value.$invalid) return;
const botData = {
name: formState.botName,
description: formState.botDescription,
outgoing_url: formState.botUrl,
bot_type: 'webhook',
avatar: formState.botAvatar,
};
const isCreate = props.type === MODAL_TYPES.CREATE;
try {
const actionPayload = isCreate
? botData
: { id: props.selectedBot.id, data: botData };
await store.dispatch(
`agentBots/${isCreate ? 'create' : 'update'}`,
actionPayload
);
const alertKey = isCreate
? t('AGENT_BOTS.ADD.API.SUCCESS_MESSAGE')
: t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE');
useAlert(alertKey);
dialogRef.value.close();
resetForm();
} catch (error) {
const errorKey = isCreate
? t('AGENT_BOTS.ADD.API.ERROR_MESSAGE')
: t('AGENT_BOTS.EDIT.API.ERROR_MESSAGE');
useAlert(errorKey);
}
};
const initializeForm = () => {
if (props.selectedBot && Object.keys(props.selectedBot).length) {
const { name, description, outgoing_url, thumbnail, bot_config } =
props.selectedBot;
formState.botName = name || '';
formState.botDescription = description || '';
formState.botUrl = outgoing_url || bot_config?.webhook_url || '';
formState.botAvatarUrl = thumbnail || '';
} else {
resetForm();
}
};
watch(() => props.selectedBot, initializeForm, { immediate: true, deep: true });
defineExpose({ dialogRef });
</script>
<template>
<Dialog
ref="dialogRef"
type="edit"
:title="dialogTitle"
:show-cancel-button="false"
:show-confirm-button="false"
@close="v$.$reset()"
>
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
<div class="mb-2 flex flex-col items-start">
<span class="mb-2 text-sm font-medium text-n-slate-12">
{{ $t('AGENT_BOTS.FORM.AVATAR.LABEL') }}
</span>
<Avatar
:src="formState.botAvatarUrl"
:name="formState.botName"
:size="68"
allow-upload
@upload="handleImageUpload"
@delete="handleAvatarDelete"
/>
</div>
<Input
v-model="formState.botName"
:label="$t('AGENT_BOTS.FORM.NAME.LABEL')"
:placeholder="$t('AGENT_BOTS.FORM.NAME.PLACEHOLDER')"
:message="botNameError"
:message-type="botNameError ? 'error' : 'info'"
@blur="v$.botName.$touch()"
/>
<TextArea
v-model="formState.botDescription"
:label="$t('AGENT_BOTS.FORM.DESCRIPTION.LABEL')"
:placeholder="$t('AGENT_BOTS.FORM.DESCRIPTION.PLACEHOLDER')"
/>
<Input
v-model="formState.botUrl"
:label="$t('AGENT_BOTS.FORM.WEBHOOK_URL.LABEL')"
:placeholder="$t('AGENT_BOTS.FORM.WEBHOOK_URL.PLACEHOLDER')"
:message="botUrlError"
:message-type="botUrlError ? 'error' : 'info'"
@blur="v$.botUrl.$touch()"
/>
<div class="flex items-center justify-end w-full gap-2 px-0 py-2">
<NextButton
faded
slate
type="reset"
:label="$t('AGENT_BOTS.FORM.CANCEL')"
@click="dialogRef.close()"
/>
<NextButton
type="submit"
data-testid="label-submit"
:label="confirmButtonLabel"
:is-loading="isLoading"
:disabled="v$.$invalid"
/>
</div>
</form>
</Dialog>
</template>
@@ -1,59 +0,0 @@
<script setup>
import { computed } from 'vue';
import ShowMore from 'dashboard/components/widgets/ShowMore.vue';
import AgentBotType from './AgentBotType.vue';
import Button from 'dashboard/components-next/button/Button.vue';
const props = defineProps({
agentBot: {
type: Object,
required: true,
},
index: {
type: Number,
required: true,
},
});
const emit = defineEmits(['edit', 'delete']);
const isACSMLTypeBot = computed(() => {
const { bot_type: botType } = props.agentBot;
return botType === 'csml';
});
</script>
<template>
<tr class="space-x-2">
<td class="py-4 ltr:pl-0 ltr:pr-4 rtl:pl-4 rtl:pr-0">
<div class="flex items-center break-words font-medium">
{{ agentBot.name }}
(<AgentBotType :bot-type="agentBot.bot_type" />)
</div>
<div class="text-sm">
<ShowMore :text="agentBot.description || ''" :limit="120" />
</div>
</td>
<td class="align-middle">
<div class="flex justify-end gap-1 h-full items-center">
<Button
v-if="isACSMLTypeBot"
v-tooltip.top="$t('AGENT_BOTS.EDIT.BUTTON_TEXT')"
icon="i-lucide-pen"
slate
xs
faded
@click="emit('edit', agentBot)"
/>
<Button
v-tooltip.top="$t('AGENT_BOTS.DELETE.BUTTON_TEXT')"
icon="i-lucide-trash-2"
xs
ruby
faded
@click="emit('delete', agentBot, index)"
/>
</div>
</td>
</tr>
</template>
@@ -1,36 +0,0 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
defineProps({
botType: {
type: String,
default: 'webhook',
},
});
const { t } = useI18n();
const botTypeConfig = computed(() => ({
csml: {
label: t('AGENT_BOTS.TYPES.CSML'),
thumbnail: '/dashboard/images/agent-bots/csml.png',
},
webhook: {
label: t('AGENT_BOTS.TYPES.WEBHOOK'),
thumbnail: '/dashboard/images/agent-bots/webhook.svg',
},
}));
</script>
<template>
<span class="inline-flex items-center gap-1">
<img
v-tooltip="botTypeConfig[botType].label"
class="h-3 w-auto"
:src="botTypeConfig[botType].thumbnail"
:alt="botTypeConfig[botType].label"
/>
<span>{{ botTypeConfig[botType].label }}</span>
</span>
</template>
@@ -1,99 +0,0 @@
<script>
import { useVuelidate } from '@vuelidate/core';
import { required } from '@vuelidate/validators';
import CsmlMonacoEditor from './CSMLMonacoEditor.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
export default {
components: { CsmlMonacoEditor, NextButton },
props: {
agentBot: {
type: Object,
default: () => {},
},
},
emits: ['submit'],
setup() {
return { v$: useVuelidate() };
},
validations: {
bot: {
name: { required },
csmlContent: { required },
},
},
data() {
return {
bot: {
name: this.agentBot.name || '',
description: this.agentBot.description || '',
csmlContent: this.agentBot.bot_config.csml_content || '',
},
};
},
methods: {
onSubmit() {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
this.$emit('submit', {
id: this.agentBot.id || '',
...this.bot,
});
},
},
};
</script>
<template>
<div class="flex flex-col h-auto overflow-auto">
<div class="flex flex-row">
<div class="w-[68%]">
<div class="h-[calc(100vh-56px)] relative">
<CsmlMonacoEditor v-model="bot.csmlContent" class="w-full h-full" />
<div
v-if="v$.bot.csmlContent.$error"
class="bg-red-100 dark:bg-red-200 text-white dark:text-white absolute bottom-0 w-full p-2.5 flex items-center text-xs justify-center flex-shrink-0"
>
<span>{{ $t('AGENT_BOTS.CSML_BOT_EDITOR.BOT_CONFIG.ERROR') }}</span>
</div>
</div>
</div>
<div class="w-[32%] overflow-auto p-4 h-[calc(100vh-56px)]">
<form
class="flex flex-col justify-between h-full"
@submit.prevent="onSubmit"
>
<div>
<label :class="{ error: v$.bot.name.$error }">
{{ $t('AGENT_BOTS.CSML_BOT_EDITOR.NAME.LABEL') }}
<input
v-model="bot.name"
type="text"
:placeholder="$t('AGENT_BOTS.CSML_BOT_EDITOR.NAME.PLACEHOLDER')"
/>
<span v-if="v$.bot.name.$error" class="message">
{{ $t('AGENT_BOTS.CSML_BOT_EDITOR.NAME.ERROR') }}
</span>
</label>
<label>
{{ $t('AGENT_BOTS.CSML_BOT_EDITOR.DESCRIPTION.LABEL') }}
<textarea
v-model="bot.description"
rows="4"
:placeholder="
$t('AGENT_BOTS.CSML_BOT_EDITOR.DESCRIPTION.PLACEHOLDER')
"
/>
</label>
<NextButton
type="submit"
:label="$t('AGENT_BOTS.CSML_BOT_EDITOR.SUBMIT')"
/>
</div>
</form>
</div>
</div>
</div>
</template>
@@ -1,71 +0,0 @@
<script>
import LoadingState from 'dashboard/components/widgets/LoadingState.vue';
import { mapGetters } from 'vuex';
export default {
components: { LoadingState },
props: {
modelValue: {
type: String,
default: '',
},
},
emits: ['update:modelValue'],
data() {
return {
iframeLoading: true,
};
},
computed: {
...mapGetters({
globalConfig: 'globalConfig/get',
}),
},
mounted() {
window.onmessage = e => {
if (
typeof e.data !== 'string' ||
!e.data.startsWith('chatwoot-csml-editor:update')
) {
return;
}
const csmlContent = e.data.replace('chatwoot-csml-editor:update', '');
this.$emit('update:modelValue', csmlContent);
};
},
methods: {
onEditorLoad() {
const frameElement = document.getElementById(`csml-editor--frame`);
const eventData = {
event: 'editorContext',
data: this.modelValue || '',
};
frameElement.contentWindow.postMessage(JSON.stringify(eventData), '*');
this.iframeLoading = false;
},
},
};
</script>
<template>
<div class="csml-editor--container">
<LoadingState
v-if="iframeLoading"
:message="$t('AGENT_BOTS.LOADING_EDITOR')"
class="dashboard-app_loading-container"
/>
<iframe
id="csml-editor--frame"
:src="globalConfig.csmlEditorHost"
@load="onEditorLoad"
/>
</div>
</template>
<style scoped>
#csml-editor--frame {
border: 0;
width: 100%;
height: 100%;
}
</style>
@@ -1,40 +0,0 @@
<script>
import { useAlert } from 'dashboard/composables';
import Spinner from 'shared/components/Spinner.vue';
import CsmlBotEditor from '../components/CSMLBotEditor.vue';
export default {
components: { Spinner, CsmlBotEditor },
computed: {
agentBot() {
return this.$store.getters['agentBots/getBot'](this.$route.params.botId);
},
},
mounted() {
this.$store.dispatch('agentBots/show', this.$route.params.botId);
},
methods: {
async updateBot(bot) {
try {
await this.$store.dispatch('agentBots/update', {
id: bot.id,
name: bot.name,
description: bot.description,
bot_type: 'csml',
bot_config: { csml_content: bot.csmlContent },
});
useAlert(this.$t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE'));
} catch (error) {
useAlert(this.$t('AGENT_BOTS.CSML_BOT_EDITOR.BOT_CONFIG.API_ERROR'));
}
},
},
};
</script>
<template>
<CsmlBotEditor v-if="agentBot.id" :agent-bot="agentBot" @submit="updateBot" />
<div v-else class="flex flex-col h-auto overflow-auto no-padding">
<Spinner />
</div>
</template>
@@ -1,41 +0,0 @@
<script>
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables';
import { frontendURL } from '../../../../../helper/URLHelper';
import CsmlBotEditor from '../components/CSMLBotEditor.vue';
export default {
components: { CsmlBotEditor },
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
}),
},
methods: {
async saveBot(bot) {
try {
const agentBot = await this.$store.dispatch('agentBots/create', {
name: bot.name,
description: bot.description,
bot_type: 'csml',
bot_config: { csml_content: bot.csmlContent },
});
if (agentBot) {
this.$router.replace(
frontendURL(
`accounts/${this.accountId}/settings/agent-bots/csml/${agentBot.id}`
)
);
}
useAlert(this.$t('AGENT_BOTS.ADD.API.SUCCESS_MESSAGE'));
} catch (error) {
useAlert(this.$t('AGENT_BOTS.ADD.API.ERROR_MESSAGE'));
}
},
},
};
</script>
<template>
<CsmlBotEditor :agent-bot="{ bot_config: {} }" @submit="saveBot" />
</template>
@@ -141,11 +141,7 @@ export default {
}
if (
this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.AGENT_BOTS
) &&
!(this.isAnEmailChannel || this.isATwitterInbox)
this.isFeatureEnabledonAccount(this.accountId, FEATURE_FLAGS.AGENT_BOTS)
) {
visibleToAllChannelTabs = [
...visibleToAllChannelTabs,