chore: Update settings to match the new design (#11084)

This commit is contained in:
Sivin Varghese
2025-03-14 14:28:14 +05:30
committed by GitHub
parent 325dc4a741
commit ed970ee190
10 changed files with 224 additions and 217 deletions

View File

@@ -2,8 +2,8 @@
"AGENT_BOTS": {
"HEADER": "Bots",
"LOADING_EDITOR": "Loading editor...",
"HEADER_BTN_TXT": "Add bot configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.</p> <p> You can manage your bots from this page or create new ones using the 'Add bot configuraton' button.</p> <p> Open the <a href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677497472-how-to-use-agent-bots\" target=\"blank\">Agent bots handbook</a> in another tab for a helping hand.</p>",
"DESCRIPTION": "Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.You can manage your bots from this page or create new ones using the 'Configure new bot' button.",
"LEARN_MORE": "Learn about agent bots",
"CSML_BOT_EDITOR": {
"NAME": {
"LABEL": "Bot name",

View File

@@ -56,7 +56,7 @@
"BUTTON_TEXT": "Disconnect"
},
"SIDEBAR_DESCRIPTION": {
"DIALOGFLOW": "Dialogflow is a natural language understanding platform that makes it easy to design and integrate a conversational user interface into your mobile app, web application, device, bot, interactive voice response system, and so on. <br /> <br /> Dialogflow integration with {installationName} allows you to configure a Dialogflow bot with your inboxes which lets the bot handle the queries initially and hand them over to an agent when needed. Dialogflow can be used to qualifying the leads, reduce the workload of agents by providing frequently asked questions etc. <br /> <br /> To add Dialogflow, you need to create a Service Account in your Google project console and share the credentials. Please refer to the Dialogflow docs for more information."
"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"
}
}
}

View File

@@ -19,12 +19,21 @@ const { t } = useI18n();
const showNewButton = computed(
() => props.newButtonRoutes.length && !props.showBackButton
);
const showSettingsHeader = computed(
() =>
props.headerTitle ||
props.icon ||
props.showBackButton ||
showNewButton.value
);
</script>
<template>
<div class="flex flex-1 flex-col m-0 bg-n-background overflow-auto">
<div class="max-w-6xl mx-auto w-full flex flex-col flex-1">
<SettingsHeader
v-if="showSettingsHeader"
button-route="new"
:icon="icon"
:header-title="t(headerTitle)"

View File

@@ -1,59 +1,87 @@
<script>
import { mapGetters } from 'vuex';
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useMapGetter, useStore } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { frontendURL } from '../../../../helper/URLHelper';
import { useI18n } from 'vue-i18n';
import { frontendURL } from 'dashboard/helper/URLHelper';
import AgentBotRow from './components/AgentBotRow.vue';
export default {
components: { AgentBotRow },
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
agentBots: 'agentBots/getBots',
uiFlags: 'agentBots/getUIFlags',
}),
newAgentBotsURL() {
return frontendURL(
`accounts/${this.accountId}/settings/agent-bots/csml/new`
);
},
},
mounted() {
this.$store.dispatch('agentBots/get');
},
methods: {
async onDeleteAgentBot(bot) {
const ok = await this.$refs.confirmDialog.showConfirmation();
if (ok) {
try {
await this.$store.dispatch('agentBots/delete', bot.id);
useAlert(this.$t('AGENT_BOTS.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) {
useAlert(this.$t('AGENT_BOTS.DELETE.API.ERROR_MESSAGE'));
}
}
},
onEditAgentBot(bot) {
this.$router.push(
frontendURL(
`accounts/${this.accountId}/settings/agent-bots/csml/${bot.id}`
)
);
},
},
import SettingsLayout from '../SettingsLayout.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
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 onConfigureNewBot = () => {
router.push({
name: 'agent_bots_csml_new',
});
};
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>
<div class="flex-1 p-4 overflow-auto">
<div class="flex flex-row gap-4">
<div class="w-full lg:w-3/5">
<woot-loading-state
v-if="uiFlags.isFetching"
:message="$t('AGENT_BOTS.LIST.LOADING')"
/>
<table v-else-if="agentBots.length" class="woot-table">
<tbody>
<SettingsLayout
:is-loading="uiFlags.isFetching"
:loading-message="$t('AGENT_BOTS.LIST.LOADING')"
:no-records-found="!agentBots.length"
: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')"
feature-name="agent_bots"
>
<template #actions>
<woot-button
class="rounded-md button nice"
icon="add-circle"
@click="onConfigureNewBot"
>
{{ $t('AGENT_BOTS.ADD.TITLE') }}
</woot-button>
</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"
@@ -64,42 +92,12 @@ export default {
/>
</tbody>
</table>
<p v-else class="flex flex-col items-center justify-center h-full">
{{ $t('AGENT_BOTS.LIST.404') }}
</p>
<woot-confirm-modal
ref="confirmDialog"
:title="$t('AGENT_BOTS.DELETE.TITLE')"
:description="$t('AGENT_BOTS.DELETE.DESCRIPTION')"
/>
</div>
<div class="hidden w-1/3 lg:block">
<p v-dompurify-html="$t('AGENT_BOTS.SIDEBAR_TXT')" />
</div>
</div>
<woot-button
color-scheme="success"
class-names="button--fixed-top"
icon="add-circle"
>
<router-link :to="newAgentBotsURL" class="white-text">
{{ $t('AGENT_BOTS.ADD.TITLE') }}
</router-link>
</woot-button>
<woot-confirm-modal
ref="confirmDialog"
:title="$t('AGENT_BOTS.DELETE.TITLE')"
:description="$t('AGENT_BOTS.DELETE.DESCRIPTION')"
/>
</div>
</template>
</SettingsLayout>
</template>
<style scoped>
.bots-list {
list-style: none;
}
.nowrap {
white-space: nowrap;
}
.white-text {
color: white;
}
</style>

View File

@@ -4,6 +4,7 @@ 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 {
routes: [
@@ -12,12 +13,7 @@ export default {
meta: {
permissions: ['administrator'],
},
component: SettingsContent,
props: {
headerTitle: 'AGENT_BOTS.HEADER',
icon: 'bot',
showNewButton: false,
},
component: SettingsWrapper,
children: [
{
path: '',
@@ -28,6 +24,19 @@ export default {
permissions: ['administrator'],
},
},
],
},
{
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',

View File

@@ -1,81 +1,58 @@
<script>
<script setup>
import { computed } from 'vue';
import ShowMore from 'dashboard/components/widgets/ShowMore.vue';
import AgentBotType from './AgentBotType.vue';
export default {
components: { ShowMore, AgentBotType },
props: {
agentBot: {
type: Object,
required: true,
},
index: {
type: Number,
required: true,
},
const props = defineProps({
agentBot: {
type: Object,
required: true,
},
emits: ['edit', 'delete'],
computed: {
isACSMLTypeBot() {
const { bot_type: botType } = this.agentBot;
return botType === 'csml';
},
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="agent-bot--details">
<div class="agent-bot--link">
<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="agent-bot--description">
<div class="text-sm">
<ShowMore :text="agentBot.description || ''" :limit="120" />
</div>
</td>
<td class="flex justify-end gap-1">
<woot-button
v-if="isACSMLTypeBot"
v-tooltip.top="$t('AGENT_BOTS.EDIT.BUTTON_TEXT')"
variant="smooth"
size="tiny"
color-scheme="secondary"
icon="edit"
@click="$emit('edit', agentBot)"
/>
<woot-button
v-tooltip.top="$t('AGENT_BOTS.DELETE.BUTTON_TEXT')"
variant="smooth"
color-scheme="alert"
size="tiny"
icon="dismiss-circle"
@click="$emit('delete', agentBot, index)"
/>
<td class="align-middle">
<div class="flex justify-end gap-1 h-full items-center">
<woot-button
v-if="isACSMLTypeBot"
v-tooltip.top="$t('AGENT_BOTS.EDIT.BUTTON_TEXT')"
variant="smooth"
size="tiny"
color-scheme="secondary"
icon="edit"
@click="emit('edit', agentBot)"
/>
<woot-button
v-tooltip.top="$t('AGENT_BOTS.DELETE.BUTTON_TEXT')"
variant="smooth"
color-scheme="alert"
size="tiny"
icon="dismiss-circle"
@click="emit('delete', agentBot, index)"
/>
</div>
</td>
</tr>
</template>
<style scoped lang="scss">
.agent-bot--link {
align-items: center;
display: flex;
font-weight: var(--font-weight-medium);
word-break: break-word;
}
.agent-bot--description {
font-size: var(--font-size-mini);
}
.agent-bot--type {
color: var(--s-600);
font-weight: var(--font-weight-medium);
margin-bottom: var(--space-small);
}
.agent-bot--details {
width: 90%;
}
</style>

View File

@@ -1,43 +1,36 @@
<script>
export default {
props: {
botType: {
type: String,
default: 'webhook',
},
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
defineProps({
botType: {
type: String,
default: 'webhook',
},
data() {
return {
botTypeConfig: {
csml: {
label: this.$t('AGENT_BOTS.TYPES.CSML'),
thumbnail: '/dashboard/images/agent-bots/csml.png',
},
webhook: {
label: this.$t('AGENT_BOTS.TYPES.WEBHOOK'),
thumbnail: '/dashboard/images/agent-bots/webhook.svg',
},
},
};
});
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="agent-bot-type--thumbnail"
class="h-3 w-auto"
:src="botTypeConfig[botType].thumbnail"
:alt="botTypeConfig[botType].label"
/>
<span>{{ botTypeConfig[botType].label }}</span>
</span>
</template>
<style scoped>
.agent-bot-type--thumbnail {
width: auto;
height: var(--space-slab);
}
</style>

View File

@@ -108,22 +108,13 @@ export default {
</script>
<template>
<div
class="overflow-auto p-4 max-w-6xl mx-auto my-auto flex flex-wrap h-full"
>
<woot-button
v-if="showAddButton"
color-scheme="success"
class-names="button--fixed-top"
icon="add-circle"
@click="openAddHookModal"
>
{{ $t('INTEGRATION_APPS.ADD_BUTTON') }}
</woot-button>
<div class="overflow-auto p-4 w-full my-auto flex flex-wrap h-full">
<div v-if="showIntegrationHooks" class="w-full">
<div v-if="isIntegrationMultiple">
<MultipleIntegrationHooks
:integration-id="integrationId"
:show-add-button="showAddButton"
@add="openAddHookModal"
@delete="openDeletePopup"
/>
</div>

View File

@@ -1,14 +1,23 @@
<script>
import { mapGetters } from 'vuex';
import { useIntegrationHook } from 'dashboard/composables/useIntegrationHook';
import BaseSettingsHeader from 'dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue';
export default {
components: {
BaseSettingsHeader,
},
props: {
integrationId: {
type: String,
required: true,
},
showAddButton: {
type: Boolean,
default: false,
},
},
emits: ['delete'],
emits: ['delete', 'add'],
setup(props) {
const { integration, isHookTypeInbox, hasConnectedHooks } =
useIntegrationHook(props.integrationId);
@@ -45,11 +54,37 @@ export default {
</script>
<template>
<div class="flex flex-row gap-4">
<div class="w-full lg:w-3/5">
<div class="flex flex-col flex-1 gap-8 overflow-auto">
<BaseSettingsHeader
:title="integration.name"
:description="
$t(
`INTEGRATION_APPS.SIDEBAR_DESCRIPTION.${integration.name.toUpperCase()}`,
{ installationName: globalConfig.installationName }
)
"
:feature-name="integrationId"
:back-button-label="$t('INTEGRATION_SETTINGS.HEADER')"
>
<template #actions>
<woot-button
v-if="showAddButton"
class="rounded-md button nice"
icon="add-circle"
@click="$emit('add')"
>
{{ $t('INTEGRATION_APPS.ADD_BUTTON') }}
</woot-button>
</template>
</BaseSettingsHeader>
<div class="w-full">
<table v-if="hasConnectedHooks" class="woot-table">
<thead>
<th v-for="hookHeader in hookHeaders" :key="hookHeader">
<th
v-for="hookHeader in hookHeaders"
:key="hookHeader"
class="ltr:!pl-0 rtl:!pr-0"
>
{{ hookHeader }}
</th>
<th v-if="isHookTypeInbox">
@@ -61,7 +96,7 @@ export default {
<td
v-for="property in hook.properties"
:key="property"
class="break-words"
class="ltr:!pl-0 rtl:!pr-0"
>
{{ property }}
</td>
@@ -90,18 +125,5 @@ export default {
}}
</p>
</div>
<div class="hidden w-1/3 lg:block">
<p>
<b>{{ integration.name }}</b>
</p>
<p
v-dompurify-html="
$t(
`INTEGRATION_APPS.SIDEBAR_DESCRIPTION.${integration.name.toUpperCase()}`,
{ installationName: globalConfig.installationName }
)
"
/>
</div>
</div>
</template>

View File

@@ -48,6 +48,14 @@ export default {
path: frontendURL('accounts/:accountId/settings/integrations'),
component: SettingsContent,
props: params => {
const integrationId = params.params?.integration_id;
const hideHeader = ['dialogflow'].includes(integrationId);
// Don't show header
if (hideHeader) {
return {};
}
const showBackButton = params.name !== 'settings_integrations';
const backUrl =
params.name === 'settings_integrations_integration'