Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
<script setup>
|
||||
import { reactive, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
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({
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => ['edit', 'create'].includes(value),
|
||||
},
|
||||
assistant: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const formState = {
|
||||
uiFlags: useMapGetter('captainAssistants/getUIFlags'),
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
name: '',
|
||||
description: '',
|
||||
productName: '',
|
||||
featureFaq: false,
|
||||
featureMemory: false,
|
||||
featureCitation: 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 isLoading = computed(() => formState.uiFlags.value.creatingItem);
|
||||
|
||||
const getErrorMessage = (field, errorKey) => {
|
||||
return v$.value[field].$error
|
||||
? t(`CAPTAIN.ASSISTANTS.FORM.${errorKey}.ERROR`)
|
||||
: '';
|
||||
};
|
||||
|
||||
const formErrors = computed(() => ({
|
||||
name: getErrorMessage('name', 'NAME'),
|
||||
description: getErrorMessage('description', 'DESCRIPTION'),
|
||||
productName: getErrorMessage('productName', 'PRODUCT_NAME'),
|
||||
}));
|
||||
|
||||
const handleCancel = () => emit('cancel');
|
||||
|
||||
const prepareAssistantDetails = () => ({
|
||||
name: state.name,
|
||||
description: state.description,
|
||||
config: {
|
||||
product_name: state.productName,
|
||||
feature_faq: state.featureFaq,
|
||||
feature_memory: state.featureMemory,
|
||||
feature_citation: state.featureCitation,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const isFormValid = await v$.value.$validate();
|
||||
if (!isFormValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('submit', prepareAssistantDetails());
|
||||
};
|
||||
|
||||
const updateStateFromAssistant = assistant => {
|
||||
if (!assistant) return;
|
||||
|
||||
const { name, description, config } = assistant;
|
||||
|
||||
Object.assign(state, {
|
||||
name,
|
||||
description,
|
||||
productName: config.product_name,
|
||||
featureFaq: config.feature_faq || false,
|
||||
featureMemory: config.feature_memory || false,
|
||||
featureCitation: config.feature_citation || false,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.assistant,
|
||||
newAssistant => {
|
||||
if (props.mode === 'edit' && newAssistant) {
|
||||
updateStateFromAssistant(newAssistant);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
|
||||
<Input
|
||||
v-model="state.name"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.NAME.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.NAME.PLACEHOLDER')"
|
||||
:message="formErrors.name"
|
||||
:message-type="formErrors.name ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-model="state.description"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.DESCRIPTION.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.DESCRIPTION.PLACEHOLDER')"
|
||||
:message="formErrors.description"
|
||||
:message-type="formErrors.description ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Input
|
||||
v-model="state.productName"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.PRODUCT_NAME.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.PRODUCT_NAME.PLACEHOLDER')"
|
||||
:message="formErrors.productName"
|
||||
:message-type="formErrors.productName ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<fieldset class="flex flex-col gap-2.5">
|
||||
<legend class="mb-3 text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.TITLE') }}
|
||||
</legend>
|
||||
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.featureFaq" type="checkbox" />
|
||||
<span class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_CONVERSATION_FAQS') }}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.featureMemory" type="checkbox" />
|
||||
<span class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_MEMORIES') }}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.featureCitation" type="checkbox" />
|
||||
<span class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_CITATIONS') }}
|
||||
</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-11 hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
:label="t(`CAPTAIN.FORM.${mode.toUpperCase()}`)"
|
||||
class="w-full"
|
||||
:is-loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import AssistantForm from './AssistantForm.vue';
|
||||
|
||||
const props = defineProps({
|
||||
selectedAssistant: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'create',
|
||||
validator: value => ['create', 'edit'].includes(value),
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'created']);
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
const dialogRef = ref(null);
|
||||
const assistantForm = ref(null);
|
||||
|
||||
const updateAssistant = assistantDetails =>
|
||||
store.dispatch('captainAssistants/update', {
|
||||
id: props.selectedAssistant.id,
|
||||
...assistantDetails,
|
||||
});
|
||||
|
||||
const i18nKey = computed(
|
||||
() => `CAPTAIN.ASSISTANTS.${props.type.toUpperCase()}`
|
||||
);
|
||||
|
||||
const createAssistant = async assistantDetails => {
|
||||
try {
|
||||
const newAssistant = await store.dispatch(
|
||||
'captainAssistants/create',
|
||||
assistantDetails
|
||||
);
|
||||
emit('created', newAssistant);
|
||||
} catch (error) {
|
||||
const errorMessage = error?.message || t(`${i18nKey.value}.ERROR_MESSAGE`);
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async updatedAssistant => {
|
||||
try {
|
||||
if (props.type === 'edit') {
|
||||
await updateAssistant(updatedAssistant);
|
||||
} else {
|
||||
await createAssistant(updatedAssistant);
|
||||
}
|
||||
useAlert(t(`${i18nKey.value}.SUCCESS_MESSAGE`));
|
||||
dialogRef.value.close();
|
||||
} catch (error) {
|
||||
const errorMessage = error?.message || t(`${i18nKey.value}.ERROR_MESSAGE`);
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
dialogRef.value.close();
|
||||
};
|
||||
|
||||
defineExpose({ dialogRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
ref="dialogRef"
|
||||
type="edit"
|
||||
:title="t(`${i18nKey}.TITLE`)"
|
||||
:description="t('CAPTAIN.ASSISTANTS.FORM_DESCRIPTION')"
|
||||
:show-cancel-button="false"
|
||||
:show-confirm-button="false"
|
||||
overflow-y-auto
|
||||
@close="handleClose"
|
||||
>
|
||||
<AssistantForm
|
||||
ref="assistantForm"
|
||||
:mode="type"
|
||||
:assistant="selectedAssistant"
|
||||
@submit="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
<template #footer />
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,151 @@
|
||||
<script setup>
|
||||
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,
|
||||
citations: 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,
|
||||
citations: config.feature_citation || 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,
|
||||
feature_citation: state.features.citations,
|
||||
},
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.assistant,
|
||||
newAssistant => {
|
||||
if (newAssistant) updateStateFromAssistant(newAssistant);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6">
|
||||
<Input
|
||||
v-model="state.name"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.NAME.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.NAME.PLACEHOLDER')"
|
||||
:message="formErrors.name"
|
||||
:message-type="formErrors.name ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Input
|
||||
v-model="state.productName"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.PRODUCT_NAME.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.PRODUCT_NAME.PLACEHOLDER')"
|
||||
:message="formErrors.productName"
|
||||
:message-type="formErrors.productName ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-model="state.description"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.DESCRIPTION.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.DESCRIPTION.PLACEHOLDER')"
|
||||
:message="formErrors.description"
|
||||
:message-type="formErrors.description ? 'error' : 'info'"
|
||||
class="z-0"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.TITLE') }}
|
||||
</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.features.conversationFaqs" type="checkbox" />
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_CONVERSATION_FAQS') }}
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.features.memories" type="checkbox" />
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_MEMORIES') }}
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.features.citations" type="checkbox" />
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_CITATIONS') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.UPDATE')"
|
||||
@click="handleBasicInfoUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
defineProps({
|
||||
controlItem: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const onClick = name => {
|
||||
router.push({ name });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:key="controlItem.name"
|
||||
class="pt-3 ltr:pl-4 rtl:pr-4 ltr:pr-2 rtl:pl-2 pb-5 gap-2 flex flex-col w-full shadow outline-1 outline outline-n-container rounded-2xl bg-n-solid-2 cursor-pointer"
|
||||
@click="onClick(controlItem.routeName)"
|
||||
>
|
||||
<div class="flex items-center justify-between w-full gap-1 h-8">
|
||||
<span class="text-sm font-medium text-n-slate-12 line-clamp-1">
|
||||
{{ controlItem.name }}
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
icon="i-lucide-chevron-right"
|
||||
slate
|
||||
ghost
|
||||
xs
|
||||
@click="onClick(controlItem.routeName)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-n-slate-11 text-sm leading-[21px] line-clamp-5">
|
||||
{{ controlItem.description }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,161 @@
|
||||
<script setup>
|
||||
import { reactive, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { minLength } from '@vuelidate/validators';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
|
||||
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 { isCloudFeatureEnabled } = useAccount();
|
||||
|
||||
const isCaptainV2Enabled = computed(() =>
|
||||
isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_V2)
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
handoffMessage: '',
|
||||
resolutionMessage: '',
|
||||
instructions: '',
|
||||
temperature: 1,
|
||||
};
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
|
||||
const validationRules = {
|
||||
handoffMessage: { minLength: minLength(1) },
|
||||
resolutionMessage: { minLength: minLength(1) },
|
||||
instructions: { 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(() => ({
|
||||
handoffMessage: getErrorMessage('handoffMessage'),
|
||||
resolutionMessage: getErrorMessage('resolutionMessage'),
|
||||
instructions: getErrorMessage('instructions'),
|
||||
}));
|
||||
|
||||
const updateStateFromAssistant = assistant => {
|
||||
const { config = {} } = assistant;
|
||||
state.handoffMessage = config.handoff_message;
|
||||
state.resolutionMessage = config.resolution_message;
|
||||
state.instructions = config.instructions;
|
||||
state.temperature = config.temperature || 1;
|
||||
};
|
||||
|
||||
const handleSystemMessagesUpdate = async () => {
|
||||
const validations = [
|
||||
v$.value.handoffMessage.$validate(),
|
||||
v$.value.resolutionMessage.$validate(),
|
||||
];
|
||||
|
||||
if (!isCaptainV2Enabled.value) {
|
||||
validations.push(v$.value.instructions.$validate());
|
||||
}
|
||||
|
||||
const result = await Promise.all(validations).then(results =>
|
||||
results.every(Boolean)
|
||||
);
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
config: {
|
||||
...props.assistant.config,
|
||||
handoff_message: state.handoffMessage,
|
||||
resolution_message: state.resolutionMessage,
|
||||
temperature: state.temperature || 1,
|
||||
},
|
||||
};
|
||||
|
||||
if (!isCaptainV2Enabled.value) {
|
||||
payload.config.instructions = state.instructions;
|
||||
}
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.assistant,
|
||||
newAssistant => {
|
||||
if (newAssistant) updateStateFromAssistant(newAssistant);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6">
|
||||
<Editor
|
||||
v-model="state.handoffMessage"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_MESSAGE.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_MESSAGE.PLACEHOLDER')"
|
||||
:message="formErrors.handoffMessage"
|
||||
:message-type="formErrors.handoffMessage ? 'error' : 'info'"
|
||||
class="z-0"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-model="state.resolutionMessage"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.RESOLUTION_MESSAGE.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.RESOLUTION_MESSAGE.PLACEHOLDER')"
|
||||
:message="formErrors.resolutionMessage"
|
||||
:message-type="formErrors.resolutionMessage ? 'error' : 'info'"
|
||||
class="z-0"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-if="!isCaptainV2Enabled"
|
||||
v-model="state.instructions"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.INSTRUCTIONS.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.INSTRUCTIONS.PLACEHOLDER')"
|
||||
:message="formErrors.instructions"
|
||||
:max-length="20000"
|
||||
:message-type="formErrors.instructions ? 'error' : 'info'"
|
||||
class="z-0"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.LABEL') }}
|
||||
</label>
|
||||
<div class="flex items-center gap-4">
|
||||
<input
|
||||
v-model="state.temperature"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
class="w-full"
|
||||
/>
|
||||
<span class="text-sm text-n-slate-12">{{ state.temperature }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-n-slate-11 italic">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.DESCRIPTION') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.UPDATE')"
|
||||
@click="handleSystemMessagesUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user