-
+
+
+
- {{ writtenBy }}
+ {{ writtenBy }}
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.WROTE') }}
- {{ dynamicTime(note.createdAt) }}
+
+ {{ dynamicTime(note.createdAt) }}
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/CategoryPage/CategoryForm.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/CategoryPage/CategoryForm.vue
index b9953dabb..18fabd2af 100644
--- a/app/javascript/dashboard/components-next/HelpCenter/Pages/CategoryPage/CategoryForm.vue
+++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/CategoryPage/CategoryForm.vue
@@ -200,6 +200,7 @@ defineExpose({ state, isSubmitDisabled });
:label="state.icon"
color="slate"
size="sm"
+ type="button"
:icon="!state.icon ? 'i-lucide-smile-plus' : ''"
class="!h-[2.4rem] !w-[2.375rem] absolute top-[1.94rem] !outline-none !rounded-[0.438rem] border-0 ltr:left-px rtl:right-px ltr:!rounded-r-none rtl:!rounded-l-none"
@click="isEmojiPickerOpen = !isEmojiPickerOpen"
diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue
index 5a60fefdc..34f1e003f 100644
--- a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue
+++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue
@@ -7,8 +7,8 @@ import { useStore, useStoreGetters } from 'dashboard/composables/store';
import { uploadFile } from 'dashboard/helper/uploadHelper';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
import { useVuelidate } from '@vuelidate/core';
-import { required, minLength } from '@vuelidate/validators';
-import { shouldBeUrl } from 'shared/helpers/Validators';
+import { required, minLength, helpers } from '@vuelidate/validators';
+import { shouldBeUrl, isValidSlug } from 'shared/helpers/Validators';
import Button from 'dashboard/components-next/button/Button.vue';
import Input from 'dashboard/components-next/input/Input.vue';
@@ -61,7 +61,16 @@ const liveChatWidgets = computed(() => {
const rules = {
name: { required, minLength: minLength(2) },
- slug: { required },
+ slug: {
+ required: helpers.withMessage(
+ () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR'),
+ required
+ ),
+ isValidSlug: helpers.withMessage(
+ () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.FORMAT_ERROR'),
+ isValidSlug
+ ),
+ },
homePageLink: { shouldBeUrl },
};
@@ -71,9 +80,9 @@ const nameError = computed(() =>
v$.value.name.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.NAME.ERROR') : ''
);
-const slugError = computed(() =>
- v$.value.slug.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR') : ''
-);
+const slugError = computed(() => {
+ return v$.value.slug.$errors[0]?.$message || '';
+});
const homePageLinkError = computed(() =>
v$.value.homePageLink.$error
diff --git a/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue b/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue
index d245656d0..70c30a241 100644
--- a/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue
+++ b/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue
@@ -6,8 +6,9 @@ import { useAlert, useTrack } from 'dashboard/composables';
import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
import { useVuelidate } from '@vuelidate/core';
-import { required, minLength } from '@vuelidate/validators';
+import { required, minLength, helpers } from '@vuelidate/validators';
import { buildPortalURL } from 'dashboard/helper/portalHelper';
+import { isValidSlug } from 'shared/helpers/Validators';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import Input from 'dashboard/components-next/input/Input.vue';
@@ -31,7 +32,16 @@ const state = reactive({
const rules = {
name: { required, minLength: minLength(2) },
- slug: { required },
+ slug: {
+ required: helpers.withMessage(
+ () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR'),
+ required
+ ),
+ isValidSlug: helpers.withMessage(
+ () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.FORMAT_ERROR'),
+ isValidSlug
+ ),
+ },
};
const v$ = useVuelidate(rules, state);
@@ -40,9 +50,9 @@ const nameError = computed(() =>
v$.value.name.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.NAME.ERROR') : ''
);
-const slugError = computed(() =>
- v$.value.slug.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR') : ''
-);
+const slugError = computed(() => {
+ return v$.value.slug.$errors[0]?.$message || '';
+});
const isSubmitDisabled = computed(() => v$.value.$invalid);
@@ -131,6 +141,7 @@ defineExpose({ dialogRef });
:message="
nameError || t('HELP_CENTER.CREATE_PORTAL_DIALOG.NAME.MESSAGE')
"
+ @blur="v$.name.$touch()"
/>
diff --git a/app/javascript/dashboard/components-next/TeleportWithDirection.vue b/app/javascript/dashboard/components-next/TeleportWithDirection.vue
new file mode 100644
index 000000000..3e9009f08
--- /dev/null
+++ b/app/javascript/dashboard/components-next/TeleportWithDirection.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/captain/PageLayout.vue b/app/javascript/dashboard/components-next/captain/PageLayout.vue
index e9fae9ca7..7355ac616 100644
--- a/app/javascript/dashboard/components-next/captain/PageLayout.vue
+++ b/app/javascript/dashboard/components-next/captain/PageLayout.vue
@@ -2,6 +2,7 @@
import { computed } from 'vue';
import { usePolicy } from 'dashboard/composables/usePolicy';
import Button from 'dashboard/components-next/button/Button.vue';
+import BackButton from 'dashboard/components/widgets/BackButton.vue';
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Policy from 'dashboard/components/policy.vue';
@@ -23,6 +24,10 @@ const props = defineProps({
type: String,
default: '',
},
+ backUrl: {
+ type: [String, Object],
+ default: '',
+ },
buttonPolicy: {
type: Array,
default: () => [],
@@ -39,6 +44,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ showKnowMore: {
+ type: Boolean,
+ default: true,
+ },
isEmpty: {
type: Boolean,
default: false,
@@ -73,19 +82,23 @@ const handlePageChange = event => {
class="flex items-start lg:items-center justify-between w-full py-6 lg:py-0 lg:h-20 gap-4 lg:gap-2 flex-col lg:flex-row"
>
+
{{ headerTitle }}
-
@@ -104,7 +117,7 @@ const handlePageChange = event => {
-
+
{
-
+
{{ name }}
-
+
+import { ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import NextButton from 'dashboard/components-next/button/Button.vue';
+import MessageList from './MessageList.vue';
+import CaptainAssistant from 'dashboard/api/captain/assistant';
+
+const { assistantId } = defineProps({
+ assistantId: {
+ type: Number,
+ required: true,
+ },
+});
+
+const { t } = useI18n();
+const messages = ref([]);
+const newMessage = ref('');
+const isLoading = ref(false);
+
+const formatMessagesForApi = () => {
+ return messages.value.map(message => ({
+ role: message.sender,
+ content: message.content,
+ }));
+};
+
+const resetConversation = () => {
+ messages.value = [];
+ newMessage.value = '';
+};
+
+const sendMessage = async () => {
+ if (!newMessage.value.trim() || isLoading.value) return;
+
+ const userMessage = {
+ content: newMessage.value,
+ sender: 'user',
+ timestamp: new Date().toISOString(),
+ };
+ messages.value.push(userMessage);
+ const currentMessage = newMessage.value;
+ newMessage.value = '';
+
+ try {
+ isLoading.value = true;
+ const { data } = await CaptainAssistant.playground({
+ assistantId,
+ messageContent: currentMessage,
+ messageHistory: formatMessagesForApi(),
+ });
+
+ messages.value.push({
+ content: data.response,
+ sender: 'assistant',
+ timestamp: new Date().toISOString(),
+ });
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Error getting assistant response:', error);
+ } finally {
+ isLoading.value = false;
+ }
+};
+
+
+
+
+
+
+
+ {{ t('CAPTAIN.PLAYGROUND.HEADER') }}
+
+
+
+
+ {{ t('CAPTAIN.PLAYGROUND.DESCRIPTION') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('CAPTAIN.PLAYGROUND.CREDIT_NOTE') }}
+
+
+
diff --git a/app/javascript/dashboard/components-next/captain/assistant/MessageList.vue b/app/javascript/dashboard/components-next/captain/assistant/MessageList.vue
new file mode 100644
index 000000000..3eca35744
--- /dev/null
+++ b/app/javascript/dashboard/components-next/captain/assistant/MessageList.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue
new file mode 100644
index 000000000..362950802
--- /dev/null
+++ b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue
@@ -0,0 +1,309 @@
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/copilot/Copilot.vue b/app/javascript/dashboard/components-next/copilot/Copilot.vue
index e284bb983..5feb474a6 100644
--- a/app/javascript/dashboard/components-next/copilot/Copilot.vue
+++ b/app/javascript/dashboard/components-next/copilot/Copilot.vue
@@ -1,5 +1,6 @@
-
+
useSuggestion(prompt)"
>
- {{ prompt.label }}
+ {{ t(prompt.label) }}
diff --git a/app/javascript/dashboard/components-next/dialog/Dialog.vue b/app/javascript/dashboard/components-next/dialog/Dialog.vue
index 6800b1254..78ca5206b 100644
--- a/app/javascript/dashboard/components-next/dialog/Dialog.vue
+++ b/app/javascript/dashboard/components-next/dialog/Dialog.vue
@@ -2,9 +2,9 @@
import { ref, computed } from 'vue';
import { OnClickOutside } from '@vueuse/components';
import { useI18n } from 'vue-i18n';
-import { useMapGetter } from 'dashboard/composables/store.js';
import Button from 'dashboard/components-next/button/Button.vue';
+import TeleportWithDirection from 'dashboard/components-next/TeleportWithDirection.vue';
const props = defineProps({
type: {
@@ -59,8 +59,6 @@ const emit = defineEmits(['confirm', 'close']);
const { t } = useI18n();
-const isRTL = useMapGetter('accounts/isRTL');
-
const dialogRef = ref(null);
const dialogContentRef = ref(null);
@@ -94,7 +92,7 @@ defineExpose({ open, close });
-
+
-
+