-
{{ title }}
+
+ {{ title }}
+
+ {{
+ `(${t('CAPTAIN.ASSISTANTS.SCENARIOS.DISABLED') || 'Desativado'})`
+ }}
+
+
{{ description }}
@@ -167,14 +251,21 @@ const renderInstruction = instruction => () =>
+
+
@@ -250,6 +341,40 @@ const renderInstruction = instruction => () =>
:show-character-count="false"
enable-captain-tools
/>
+
+
+
+ {{
+ t(
+ 'CAPTAIN.ASSISTANTS.SCENARIOS.ADD.NEW.FORM.TRIGGER_KEYWORDS.LABEL'
+ )
+ }}
+
+
+
+
+
+
{{ t('CAPTAIN.ASSISTANTS.SCENARIOS.ADD.NEW.FORM.TOOLS.LABEL') }}
diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantBasicSettingsForm.vue b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantBasicSettingsForm.vue
index 0b70041..923a59b 100755
--- a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantBasicSettingsForm.vue
+++ b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantBasicSettingsForm.vue
@@ -37,6 +37,7 @@ const initialState = {
memories: false,
citations: false,
handoffOnSentiment: false,
+ allowHandoff: true,
},
};
@@ -75,6 +76,7 @@ const updateStateFromAssistant = assistant => {
memories: config.feature_memory || false,
citations: config.feature_citation || false,
handoffOnSentiment: config.handoff_on_sentiment || false,
+ allowHandoff: config.allow_handoff !== false,
};
};
@@ -100,6 +102,7 @@ const handleBasicInfoUpdate = async () => {
feature_memory: state.features.memories,
feature_citation: state.features.citations,
handoff_on_sentiment: state.features.handoffOnSentiment,
+ allow_handoff: state.features.allowHandoff,
},
};
@@ -258,6 +261,10 @@ onMounted(() => {
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_SENTIMENT_HANDOFF') }}
+
+
+ {{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_HANDOFF') }}
+
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
index cfdbb88..2536bf5 100755
--- a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantSystemSettingsForm.vue
+++ b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/settings/AssistantSystemSettingsForm.vue
@@ -10,6 +10,7 @@ import Button from 'dashboard/components-next/button/Button.vue';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import Editor from 'dashboard/components-next/Editor/Editor.vue';
import Input from 'dashboard/components-next/input/Input.vue';
+import SelectMenu from 'dashboard/components-next/selectmenu/SelectMenu.vue';
import Draggable from 'vuedraggable';
const props = defineProps({
@@ -30,6 +31,15 @@ const isCaptainV2Enabled = computed(() =>
const initialState = {
handoffMessage: '',
+ handoffInstructions: '',
+ handoffOnToolFailureAction: 'ignore',
+ handoffOnToolFailureMessage: '',
+ handoffOnLlmErrorAction: 'handoff',
+ handoffOnLlmErrorMessage: '',
+ handoffOnUserRequestAction: 'handoff',
+ handoffOnUserRequestMessage: '',
+ handoffOnSentimentAction: 'handoff',
+ handoffOnSentimentMessage: '',
resolutionMessage: '',
instructions: '',
playbook: '',
@@ -54,6 +64,7 @@ const hasSystemPromptVersions = computed(
const validationRules = {
handoffMessage: { minLength: minLength(1) },
+ handoffInstructions: { minLength: minLength(1) },
resolutionMessage: { minLength: minLength(1) },
instructions: { minLength: minLength(1) },
playbook: { minLength: minLength(1) },
@@ -67,6 +78,7 @@ const getErrorMessage = field => {
const formErrors = computed(() => ({
handoffMessage: getErrorMessage('handoffMessage'),
+ handoffInstructions: getErrorMessage('handoffInstructions'),
resolutionMessage: getErrorMessage('resolutionMessage'),
instructions: getErrorMessage('instructions'),
playbook: getErrorMessage('playbook'),
@@ -83,7 +95,31 @@ const normalizeBlocks = blocks =>
const updateStateFromAssistant = assistant => {
const { config = {} } = assistant;
+ const hasHandoffInstructions = Object.prototype.hasOwnProperty.call(
+ config,
+ 'handoff_instructions'
+ );
+ const defaultHandoffInstructions = t(
+ 'CAPTAIN.ASSISTANTS.FORM.HANDOFF_INSTRUCTIONS.DEFAULT'
+ );
state.handoffMessage = config.handoff_message || '';
+ state.handoffInstructions = hasHandoffInstructions
+ ? config.handoff_instructions || ''
+ : defaultHandoffInstructions;
+ state.handoffOnToolFailureAction =
+ config.handoff_on_tool_failure_action || 'ignore';
+ state.handoffOnToolFailureMessage =
+ config.handoff_on_tool_failure_message || '';
+ state.handoffOnLlmErrorAction =
+ config.handoff_on_llm_error_action || 'handoff';
+ state.handoffOnLlmErrorMessage = config.handoff_on_llm_error_message || '';
+ state.handoffOnUserRequestAction =
+ config.handoff_on_user_request_action || 'handoff';
+ state.handoffOnUserRequestMessage =
+ config.handoff_on_user_request_message || '';
+ state.handoffOnSentimentAction =
+ config.handoff_on_sentiment_action || 'handoff';
+ state.handoffOnSentimentMessage = config.handoff_on_sentiment_message || '';
state.resolutionMessage = config.resolution_message || '';
state.instructions = config.instructions || '';
state.playbook = config.playbook || '';
@@ -157,6 +193,15 @@ const buildPayload = (extra = {}) => {
const config = {
...props.assistant.config,
handoff_message: state.handoffMessage,
+ handoff_instructions: state.handoffInstructions,
+ handoff_on_tool_failure_action: state.handoffOnToolFailureAction,
+ handoff_on_tool_failure_message: state.handoffOnToolFailureMessage,
+ handoff_on_llm_error_action: state.handoffOnLlmErrorAction,
+ handoff_on_llm_error_message: state.handoffOnLlmErrorMessage,
+ handoff_on_user_request_action: state.handoffOnUserRequestAction,
+ handoff_on_user_request_message: state.handoffOnUserRequestMessage,
+ handoff_on_sentiment_action: state.handoffOnSentimentAction,
+ handoff_on_sentiment_message: state.handoffOnSentimentMessage,
resolution_message: state.resolutionMessage,
temperature: state.temperature !== undefined ? state.temperature : 1,
playbook: state.playbook,
@@ -183,6 +228,9 @@ const handleSystemMessagesUpdate = async () => {
v$.value.handoffMessage.$validate(),
v$.value.resolutionMessage.$validate(),
];
+ if (state.handoffInstructions?.length) {
+ validations.push(v$.value.handoffInstructions.$validate());
+ }
if (!isCaptainV2Enabled.value) {
validations.push(v$.value.instructions.$validate());
@@ -202,6 +250,32 @@ const handleSystemMessagesUpdate = async () => {
emit('submit', payload);
};
+const handleRestoreHandoffInstructions = () => {
+ state.handoffInstructions = t(
+ 'CAPTAIN.ASSISTANTS.FORM.HANDOFF_INSTRUCTIONS.DEFAULT'
+ );
+};
+
+const handoffActionOptions = computed(() => [
+ {
+ value: 'handoff',
+ label: t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_ACTIONS.HANDOFF'),
+ },
+ {
+ value: 'reply',
+ label: t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_ACTIONS.REPLY'),
+ },
+ {
+ value: 'ignore',
+ label: t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_ACTIONS.IGNORE'),
+ },
+]);
+
+const handoffActionLabel = action => {
+ const option = handoffActionOptions.value.find(opt => opt.value === action);
+ return option ? option.label : action;
+};
+
const handleSaveSystemPromptVersion = () => {
if (isPromptOverLimit.value) return;
const payload = buildPayload({ system_prompt_action: 'save_version' });
@@ -288,6 +362,112 @@ watch(
class="z-0"
/>
+
+
+
+ {{ t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_INSTRUCTIONS.LABEL') }}
+
+
+
+
+
+
+
+
+import { reactive, onMounted } from 'vue';
+import { useI18n } from 'vue-i18n';
+import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
+import Input from 'dashboard/components-next/input/Input.vue';
+
+const props = defineProps({
+ reservation: {
+ type: Object,
+ required: true,
+ },
+ units: {
+ type: Array,
+ default: () => [],
+ },
+ isLoading: {
+ type: Boolean,
+ default: false,
+ },
+});
+
+const emit = defineEmits(['confirm', 'close']);
+
+const { t } = useI18n();
+
+const form = reactive({
+ suite_identifier: '',
+ check_in_at: '',
+ check_out_at: '',
+ total_amount: 0,
+ status: '',
+ captain_unit_id: '',
+});
+
+const statusOptions = [
+ { value: 'scheduled', label: 'Agendada' },
+ { value: 'active', label: 'Confirmada' },
+ { value: 'pending_payment', label: 'Pendente Pagamento' },
+ { value: 'cancelled', label: 'Cancelada' },
+ { value: 'completed', label: 'Concluída' },
+];
+
+const formatDateTimeForInput = dateString => {
+ if (!dateString) return '';
+ const date = new Date(dateString);
+ const tzoffset = date.getTimezoneOffset() * 60000;
+ const localISOTime = new Date(date.getTime() - tzoffset)
+ .toISOString()
+ .slice(0, 16);
+ return localISOTime;
+};
+
+onMounted(() => {
+ form.suite_identifier = props.reservation.suite_identifier || '';
+ form.check_in_at = formatDateTimeForInput(props.reservation.check_in_at);
+ form.check_out_at = formatDateTimeForInput(props.reservation.check_out_at);
+ form.total_amount = props.reservation.total_amount || 0;
+ form.status = props.reservation.status || 'scheduled';
+ form.captain_unit_id =
+ props.reservation.unit?.id || props.reservation.captain_unit_id || '';
+});
+
+const handleSubmit = () => {
+ emit('confirm', { ...form });
+};
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
index 20767d2..7c0aec0 100755
--- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
+++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
@@ -132,6 +132,11 @@ const newReportRoutes = () => [
to: accountScopedRoute('team_reports_index'),
activeOn: ['team_reports_show'],
},
+ {
+ name: 'Reports Frequent Questions',
+ label: t('FREQUENT_QUESTIONS.HEADER'),
+ to: accountScopedRoute('frequent_questions_reports'),
+ },
];
const reportRoutes = computed(() => newReportRoutes());
diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json
index c738e27..e65aadf 100755
--- a/app/javascript/dashboard/i18n/locale/en/integrations.json
+++ b/app/javascript/dashboard/i18n/locale/en/integrations.json
@@ -493,6 +493,26 @@
"LABEL": "Handoff Message",
"PLACEHOLDER": "Enter handoff message"
},
+ "HANDOFF_INSTRUCTIONS": {
+ "LABEL": "Handoff Instructions (internal)",
+ "PLACEHOLDER": "Explain when and how the assistant should handoff",
+ "RESTORE": "Restore default",
+ "DEFAULT": "Use handoff only when the user asks for a human or when a critical error happens. If a tool fails, explain the issue and ask them to retry. Never promise handoff when it is disabled."
+ },
+ "HANDOFF_RULES": {
+ "LABEL": "Handoff rules",
+ "USER_REQUEST": "User asks for a human",
+ "SENTIMENT": "Negative sentiment",
+ "TOOL_FAILURE": "Tool failure",
+ "LLM_ERROR": "LLM error",
+ "MESSAGE": "Custom message",
+ "MESSAGE_PLACEHOLDER": "Type the message to send"
+ },
+ "HANDOFF_ACTIONS": {
+ "HANDOFF": "Handoff to human",
+ "REPLY": "Reply with message",
+ "IGNORE": "Ignore"
+ },
"RESOLUTION_MESSAGE": {
"LABEL": "Resolution Message",
"PLACEHOLDER": "Enter resolution message"
@@ -545,7 +565,8 @@
"ALLOW_CONVERSATION_FAQS": "Generate FAQs from resolved conversations",
"ALLOW_MEMORIES": "Capture key details as memories from customer interactions.",
"ALLOW_CITATIONS": "Include source citations in responses",
- "ALLOW_SENTIMENT_HANDOFF": "Automatically handoff to human on negative sentiment (angry/frustrated)"
+ "ALLOW_SENTIMENT_HANDOFF": "Automatically handoff to human on negative sentiment (angry/frustrated)",
+ "ALLOW_HANDOFF": "Allow automatic handoff to human"
},
"WEBHOOK": {
"TITLE": "Handoff Webhook",
@@ -764,6 +785,18 @@
},
"EMPTY_MESSAGE": "No scenarios found. Create or add examples to begin.",
"SEARCH_EMPTY_MESSAGE": "No scenarios found for this search.",
+ "DUPLICATE": {
+ "TITLE": "Duplicate scenario",
+ "DESCRIPTION": "Choose the destination assistant to copy this scenario.",
+ "CONFIRM": "Duplicate",
+ "SUCCESS": "Scenario duplicated successfully",
+ "ERROR": "There was an error duplicating the scenario, please try again.",
+ "NO_TARGETS": "No other assistants found to duplicate the scenario.",
+ "TARGET_LABEL": "Destination assistant",
+ "SCENARIO_LABEL": "Scenario",
+ "SELECT": "Select an assistant",
+ "COPY_SUFFIX": " (copy)"
+ },
"API": {
"ADD": {
"SUCCESS": "Scenarios added successfully",
diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/generalSettings.json b/app/javascript/dashboard/i18n/locale/pt_BR/generalSettings.json
index 28bdffd..cf40434 100755
--- a/app/javascript/dashboard/i18n/locale/pt_BR/generalSettings.json
+++ b/app/javascript/dashboard/i18n/locale/pt_BR/generalSettings.json
@@ -51,6 +51,7 @@
"LABEL": "Duração da inatividade",
"HELP": "Período de tempo de inatividade após o qual a conversa é resolvida automaticamente",
"PLACEHOLDER": "30",
+ "DESCRIPTION": "Defina em minutos. Deixe 0 ou vazio para usar a configuração da Conta.",
"ERROR": "O tempo decorrido para resolução automática deve ser entre 10 minutos e 999 dias",
"API": {
"SUCCESS": "Configurações de resolução automática atualizadas com sucesso",
diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/integrations.json b/app/javascript/dashboard/i18n/locale/pt_BR/integrations.json
index 2f1aa96..f82998b 100755
--- a/app/javascript/dashboard/i18n/locale/pt_BR/integrations.json
+++ b/app/javascript/dashboard/i18n/locale/pt_BR/integrations.json
@@ -194,15 +194,15 @@
"TESTED_LABEL": "Testado",
"VALIDATED_COUNT": "Modelos validadeos: ",
"STATUS": {
- "NOT_TESTED": "Não testado",
- "OK": "OK (testado)",
- "FAIL": "Falha",
- "SUCCESS": "Modelo {model} testado com sucesso",
- "ERROR": "Falha ao testar modelo"
+ "NOT_TESTED": "Não testado",
+ "OK": "OK (testado)",
+ "FAIL": "Falha",
+ "SUCCESS": "Modelo {model} testado com sucesso",
+ "ERROR": "Falha ao testar modelo"
},
"BUTTON": {
- "TESTING": "Testando...",
- "TEST": "Testar"
+ "TESTING": "Testando...",
+ "TEST": "Testar"
}
}
},
@@ -531,6 +531,26 @@
"LABEL": "Mensagem de transferência",
"PLACEHOLDER": "Digite a mensagem de transferência"
},
+ "HANDOFF_INSTRUCTIONS": {
+ "LABEL": "Instruções de handoff (interno)",
+ "PLACEHOLDER": "Explique quando e como a assistente deve fazer handoff",
+ "RESTORE": "Voltar ao padrao",
+ "DEFAULT": "Use handoff apenas quando o cliente pedir atendimento humano ou quando houver erro critico. Se houver falha de ferramenta, explique o problema e peça para tentar novamente. Nunca prometa handoff se ele estiver desativado."
+ },
+ "HANDOFF_RULES": {
+ "LABEL": "Regras de handoff",
+ "USER_REQUEST": "Pedido de humano",
+ "SENTIMENT": "Sentimento negativo",
+ "TOOL_FAILURE": "Falha de ferramenta",
+ "LLM_ERROR": "Erro do LLM",
+ "MESSAGE": "Mensagem personalizada",
+ "MESSAGE_PLACEHOLDER": "Digite a mensagem que deve ser enviada"
+ },
+ "HANDOFF_ACTIONS": {
+ "HANDOFF": "Transferir para humano",
+ "REPLY": "Responder com mensagem",
+ "IGNORE": "Ignorar"
+ },
"RESOLUTION_MESSAGE": {
"LABEL": "Mensagem de resolução",
"PLACEHOLDER": "Digite a mensagem de resolução"
@@ -570,7 +590,8 @@
"ALLOW_CONVERSATION_FAQS": "Gerar perguntas frequentes a partir de conversas resolvidas",
"ALLOW_MEMORIES": "Capture os principais detalhes como memórias de interações do cliente.",
"ALLOW_CITATIONS": "Incluir fonte de citações nas respostas",
- "ALLOW_SENTIMENT_HANDOFF": "Transferir automaticamente para humano em caso de sentimento negativo (raiva/frustração)"
+ "ALLOW_SENTIMENT_HANDOFF": "Transferir automaticamente para humano em caso de sentimento negativo (raiva/frustração)",
+ "ALLOW_HANDOFF": "Permitir handoff automatico para humano"
},
"WEBHOOK": {
"TITLE": "Webhook de Escalação",
@@ -764,6 +785,14 @@
"LABEL": "Capacidades / Poderes",
"PLACEHOLDER": "Selecione os poderes que este sub-agente pode usar"
},
+ "TRIGGER_KEYWORDS": {
+ "LABEL": "Palavras-chave Gatilho",
+ "PLACEHOLDER": "Ex: orçamento, preço... (Pressione Enter)",
+ "SUGGEST_BUTTON": "Sugestão Mágica",
+ "SUGGEST_LOADING": "Sugerindo...",
+ "SUGGEST_SUCCESS": "Palavras-chave sugeridas com sucesso!",
+ "SUGGEST_ERROR": "Erro ao sugerir palavras-chave."
+ },
"CREATE": "Criar",
"CANCEL": "Cancelar"
}
@@ -778,6 +807,18 @@
},
"EMPTY_MESSAGE": "Nenhum cenário encontrado. Crie ou adicione exemplos para começar.",
"SEARCH_EMPTY_MESSAGE": "Nenhum cenário encontrado para esta pesquisa.",
+ "DUPLICATE": {
+ "TITLE": "Duplicar cenário",
+ "DESCRIPTION": "Escolha o assistente de destino para copiar este cenário.",
+ "CONFIRM": "Duplicar",
+ "SUCCESS": "Cenário duplicado com sucesso",
+ "ERROR": "Ocorreu um erro ao duplicar o cenário, por favor tente novamente.",
+ "NO_TARGETS": "Nenhum outro assistente encontrado para duplicar o cenário.",
+ "TARGET_LABEL": "Assistente de destino",
+ "SCENARIO_LABEL": "Cenário",
+ "SELECT": "Selecione um assistente",
+ "COPY_SUFFIX": " (copia)"
+ },
"API": {
"ADD": {
"SUCCESS": "Cenários adicionados com sucesso",
@@ -792,8 +833,7 @@
"ERROR": "Ocorreu um erro ao excluir os cenários, por favor tente novamente."
}
}
- }
- ,
+ },
"SKILLS": {
"HEADER": "Skills do assistente",
"DESCRIPTION": "Configure as capacidades e ferramentas disponíveis para este assistente.",
@@ -811,7 +851,15 @@
"PLUG_PLAY_TOKEN": {
"LABEL": "Plug&Play Token",
"PLACEHOLDER": "Token"
- }
+ },
+ "FALLBACK": {
+ "TITLE": "Fallback",
+ "LABEL": "Mensagem de fallback (opcional)",
+ "PLACEHOLDER": "Ex: Se não conseguir concluir aqui, finalize pelo link ...",
+ "SAVE": "Salvar fallback",
+ "HELP_TEXT": "Se vazio, o sistema usa o fallback padrão da ferramenta."
+ },
+ "ALWAYS_ACTIVE": "Sempre ativo"
}
},
"DOCUMENTS": {
@@ -921,36 +969,37 @@
},
"RESERVATIONS": {
"AUTOMATIONS": {
- "TITLE": "Automacoes",
- "LIST_TITLE": "Automacoes existentes",
- "EMPTY": "Nenhuma automacao configurada",
- "EDIT": "Editar",
- "DELETE": "Excluir",
+ "TITLE": "Automações de Inbox",
"TRIGGER_CHECK_IN": "Check-in",
"TRIGGER_CHECK_OUT": "Check-out",
"TIMING_BEFORE": "Antes",
"TIMING_AFTER": "Depois",
+ "LIST_TITLE": "Automações Configuradas",
+ "EMPTY": "Nenhuma automação configurada para este Inbox",
+ "EDIT": "Editar",
+ "DELETE": "Excluir",
+ "SUMMARY": "{trigger} · {timing} · {minutes}m",
"FORM": {
- "TITLE": "Titulo",
+ "TITLE": "Título",
"MESSAGE": "Mensagem",
- "TRIGGER": "Disparo",
+ "TRIGGER": "Gatilho",
"TIMING": "Momento",
"MINUTES": "Minutos",
- "SUBMIT": "Adicionar automacao",
- "UPDATE": "Atualizar automacao",
+ "SUBMIT": "Criar Automação",
+ "UPDATE": "Atualizar",
"CANCEL": "Cancelar"
},
"SUCCESS": {
- "CREATED": "Automacao criada",
- "UPDATED": "Automacao atualizada",
- "DELETED": "Automacao excluida"
+ "CREATED": "Automação criada",
+ "UPDATED": "Automação atualizada",
+ "DELETED": "Automação excluída"
},
"ERRORS": {
- "LOAD_FAILED": "Nao foi possivel carregar as automacoes",
- "MISSING_FIELDS": "Preencha titulo e mensagem",
- "CREATE_FAILED": "Nao foi possivel criar a automacao",
- "UPDATE_FAILED": "Nao foi possivel atualizar a automacao",
- "DELETE_FAILED": "Nao foi possivel excluir a automacao"
+ "LOAD_FAILED": "Falha ao carregar automações",
+ "CREATE_FAILED": "Falha ao criar automação",
+ "UPDATE_FAILED": "Falha ao atualizar automação",
+ "DELETE_FAILED": "Falha ao excluir automação",
+ "MISSING_FIELDS": "Preencha todos os campos"
}
},
"PAGE": {
@@ -988,6 +1037,11 @@
"CHECK_OUT_PREVIEW": "Horario estimado de saida",
"SUBMIT": "Criar reserva"
},
+ "EDIT": {
+ "TITLE": "Editar Reserva",
+ "DESCRIPTION": "Altere os detalhes da reserva conforme necessário.",
+ "CONFIRM": "Salvar Alterações"
+ },
"SETTINGS": {
"TITLE": "Mensagens automaticas",
"ENABLED": "Ativar automacoes",
@@ -1009,6 +1063,44 @@
"CANCEL_FAILED": "Nao foi possivel cancelar a reserva",
"SETTINGS_LOAD_FAILED": "Nao foi possivel carregar as configuracoes",
"SETTINGS_SAVE_FAILED": "Nao foi possivel salvar as configuracoes"
+ },
+ "LIST": {
+ "HEADER_TITLE": "Gestão de Reservas",
+ "HEADER_DESCRIPTION": "Visualize e gerencie todas as reservas das suas unidades em um único lugar.",
+ "UNITS": "Unidade",
+ "ALL_UNITS": "Todas as Unidades",
+ "TOTAL_STATUS": "Todos os Status",
+ "FROM": "De",
+ "TO": "Até",
+ "EMPTY": "Nenhuma reserva encontrada",
+ "EMPTY_DESC": "Tente ajustar os filtros.",
+ "GUEST": "Hóspede",
+ "DEFAULT_SUITE": "Suíte Padrão",
+ "UNKNOWN_UNIT": "Unidade Desconhecida",
+ "VIEW_CONVERSATION": "Ver Conversa",
+ "EDIT": "Editar",
+ "DELETE": "Excluir",
+ "LOAD_MORE": "Carregar Mais",
+ "RESERVATION_ID": "Reserva #{id}",
+ "CPF_FORMAT": "- {cpf}",
+ "CHECK_IN": "Check-in: {time}",
+ "CHECK_OUT": "Check-out: {time}",
+ "NO_NAME": "Contato desconhecido",
+ "DELETE_CONFIRMATION": "Tem certeza que deseja excluir a reserva #{id}?",
+ "DELETE_SUCCESS": "Reserva excluída com sucesso",
+ "DELETE_ERROR": "Erro ao excluir reserva",
+ "UPDATE_SUCCESS": "Reserva atualizada com sucesso",
+ "UPDATE_ERROR": "Erro ao atualizar reserva"
+ },
+ "STATUS": {
+ "ALL": "Todos os Status",
+ "SCHEDULED": "Agendada",
+ "ACTIVE": "Confirmada",
+ "PENDING_PAYMENT": "Pendente Pagamento",
+ "CANCELLED": "Cancelada",
+ "COMPLETED": "Concluída",
+ "PAID": "Pago",
+ "PENDING": "Pendente"
}
},
"REMINDERS": {
@@ -1035,6 +1127,102 @@
"PHONE_REQUIRED": "Telefone obrigatorio",
"CREATE_FAILED": "Nao foi possivel agendar o lembrete",
"CANCEL_FAILED": "Nao foi possivel cancelar o lembrete"
+ },
+ "BRANDS": {
+ "TITLE": "Gestão de Marcas",
+ "ADMIN_PANEL": "Painel Administrativo",
+ "HEADER": "Gerenciar Marcas",
+ "ADD_NEW": "Adicionar Nova Marca",
+ "TABLE": {
+ "NAME": "Nome",
+ "CATEGORIES": "Categorias",
+ "STAYS": "Permanências",
+ "ACTIONS": "Ações"
+ },
+ "VIEW_IMAGE": "Ver Imagem",
+ "EDIT": "Editar",
+ "DELETE": "Excluir",
+ "EMPTY_STATE_TITLE": "Nenhuma marca cadastrada",
+ "EMPTY_STATE_DESC": "Clique no botão acima para adicionar a primeira marca.",
+ "DELETE_CONFIRMATION": "Tem certeza que deseja excluir esta marca?",
+ "SUCCESS": {
+ "CREATED": "Marca criada com sucesso",
+ "UPDATED": "Marca atualizada com sucesso",
+ "DELETED": "Marca excluída com sucesso"
+ },
+ "ERRORS": {
+ "FETCH_FAILED": "Erro ao buscar marcas",
+ "SAVE_FAILED": "Erro ao salvar marca",
+ "DELETE_FAILED": "Erro ao excluir marca"
+ },
+ "BRAND_MODAL": {
+ "TITLE_NEW": "Nova Marca",
+ "TITLE_EDIT": "Editar Marca",
+ "NAME_LABEL": "Nome da Marca",
+ "NAME_PLACEHOLDER": "Ex: Hotel Prime",
+ "SUITE_CATEGORIES_LABEL": "Categorias de Suíte e Fotos",
+ "SUITE_NAME_PLACEHOLDER": "Nome (Ex: Presidencial)",
+ "SUITE_IMAGE_PLACEHOLDER": "URL da Imagem (https://...)",
+ "ADD_CATEGORY": "Adicionar Categoria",
+ "REMOVE_CATEGORY": "Remover",
+ "SUITE_CATEGORIES_HELP": "Insira o nome da categoria e opcionalmente a URL da foto.",
+ "STAYS_LABEL": "Permanências (separadas por vírgula)",
+ "STAYS_PLACEHOLDER": "Ex: 2h, 4h, Pernoite, Diária",
+ "CANCEL": "Cancelar",
+ "CREATE": "Criar",
+ "UPDATE": "Atualizar"
+ },
+ "CONFIGURATIONS": {
+ "TITLE": "Configurações Gerais",
+ "DESCRIPTION": "Personalize a aparência e dados da página pública de reservas.",
+ "FORM": {
+ "PAGE_TITLE_LABEL": "Título da Página",
+ "PAGE_TITLE_PLACEHOLDER": "Ex: Reservas Hotel Prime",
+ "SUBTITLE_LABEL": "Subtítulo",
+ "SUBTITLE_PLACEHOLDER": "Ex: As melhores suítes da região",
+ "PHONE_LABEL": "Telefone de Suporte / WhatsApp",
+ "PHONE_PLACEHOLDER": "Ex: 5511999999999",
+ "PRIMARY_COLOR_LABEL": "Cor Primária",
+ "SUBMIT": "Salvar Alterações"
+ },
+ "SUCCESS": "Configurações salvas!",
+ "ERROR": "Erro ao salvar configurações"
+ },
+ "EXTRAS": {
+ "TITLE": "Configuração de Extras",
+ "ADD_NEW": "Adicionar Extra",
+ "EMPTY_STATE_TITLE": "Nenhum extra cadastrado",
+ "EMPTY_STATE_DESC": "Clique no botão acima para adicionar o primeiro serviço extra.",
+ "TABLE": {
+ "TITLE": "Título",
+ "PRICE": "Preço",
+ "ACTIONS": "Ações"
+ },
+ "EDIT": "Editar",
+ "DELETE": "Excluir",
+ "DELETE_CONFIRMATION": "Tem certeza que deseja excluir este extra?",
+ "MODAL": {
+ "TITLE_NEW": "Novo Extra",
+ "TITLE_EDIT": "Editar Extra",
+ "TITLE_LABEL": "Título",
+ "TITLE_PLACEHOLDER": "Ex: Decoração Romântica",
+ "DESCRIPTION_LABEL": "Descrição",
+ "DESCRIPTION_PLACEHOLDER": "Detalhes...",
+ "PRICE_LABEL": "Preço (R$)",
+ "PRICE_PREFIX": "R$",
+ "CANCEL": "Cancelar",
+ "SUBMIT": "Salvar"
+ },
+ "SUCCESS": {
+ "SAVED": "Extra salvo!",
+ "DELETED": "Extra excluído com sucesso"
+ },
+ "ERRORS": {
+ "FETCH_FAILED": "Erro ao buscar extras",
+ "SAVE_FAILED": "Erro ao salvar extra",
+ "DELETE_FAILED": "Erro ao excluir extra"
+ }
+ }
}
},
"CUSTOM_TOOLS": {
@@ -1281,6 +1469,111 @@
"TITLE": "Caixa de entrada não conectada",
"SUBTITLE": "Conectar uma caixa de entrada permite ao assistente lidar com perguntas iniciais de seus clientes antes de transferi-las para você."
}
+ },
+ "PRICINGS": {
+ "HEADER": "Painel Administrativo",
+ "TITLE": "Tabela de Preços",
+ "DESCRIPTION": "Configure as regras de preço por inbox, marca, dia e categoria.",
+ "ADD_BUTTON": "Nova Regra",
+ "EMPTY_STATE": "Nenhuma regra encontrada com os filtros atuais.",
+ "FIELDS": {
+ "INBOX": "Inbox",
+ "BRAND": "Marca",
+ "DAY": "Dia",
+ "DAYS": "Dias",
+ "CATEGORY": "Categoria",
+ "DURATION": "Duração",
+ "PRICE": "Preço",
+ "PRICE_DISPLAY": "R$ {price}",
+ "ACTIONS": "Ações",
+ "MIN_PRICE": "Preço mínimo",
+ "MAX_PRICE": "Preço máximo"
+ },
+ "FILTERS": {
+ "TITLE": "Filtros",
+ "ALL": "Todas",
+ "ALL_DAYS": "Todos",
+ "CLEAR": "Limpar filtros"
+ },
+ "DELETE_CONFIRMATION": "Tem certeza que deseja excluir esta regra?",
+ "DELETE_SUCCESS": "Regra removida",
+ "DELETE_ERROR": "Erro ao remover regra",
+ "FETCH_ERROR": "Erro ao buscar dados",
+ "MODAL": {
+ "ADD_TITLE": "Nova Regra de Preço",
+ "EDIT_TITLE": "Editar Regra",
+ "SELECT_DAYS_REQUIRED": "Selecione pelo menos um dia.",
+ "SELECT_BRAND_FIRST": "Selecione uma marca primeiro.",
+ "NO_CATEGORIES": "Nenhuma categoria cadastrada nesta marca.",
+ "NO_DURATIONS": "Nenhuma duração cadastrada nesta marca.",
+ "SAVE_SUCCESS": "Preço salvo!",
+ "SAVE_ERROR": "Erro ao salvar preço",
+ "CANCEL": "Cancelar",
+ "SAVE": "Salvar",
+ "SELECT_CATEGORY": "Selecione uma categoria",
+ "SELECT_DURATION": "Selecione uma duração",
+ "PRICE_PLACEHOLDER": "0,00",
+ "REMOVE_INBOX": "Remover",
+ "CLOSE": "×",
+ "FIELDS": {
+ "DAYS_WEEK": "Dias da Semana"
+ }
+ }
+ },
+ "SCENARIOS": {
+ "TITLE": "Cenários",
+ "DESCRIPTION": "Cenários ajudam o assistente a entender como lidar com situações específicas.",
+ "EMPTY_MESSAGE": "Nenhum cenário encontrado.",
+ "SEARCH_EMPTY_MESSAGE": "Nenhum cenário corresponde à sua busca.",
+ "BULK_ACTION": {
+ "SELECT_ALL": "Selecionar todos ({count})",
+ "UNSELECT_ALL": "Desmarcar todos ({count})",
+ "SELECTED": "{count} selecionado(s)",
+ "BULK_DELETE_BUTTON": "Excluir Selecionados"
+ },
+ "LIST": {
+ "SEARCH_PLACEHOLDER": "Buscar cenários..."
+ },
+ "ADD": {
+ "SUGGESTED": {
+ "TITLE": "Cenários Sugeridos",
+ "ADD_SINGLE": "Adicionar",
+ "TOOLS_USED": "Ferramentas usadas:"
+ }
+ },
+ "API": {
+ "ADD": {
+ "SUCCESS": "Cenário adicionado com sucesso",
+ "ERROR": "Erro ao adicionar cenário"
+ },
+ "UPDATE": {
+ "SUCCESS": "Cenário atualizado com sucesso",
+ "ERROR": "Erro ao atualizar cenário"
+ },
+ "DELETE": {
+ "SUCCESS": "Cenário excluído com sucesso",
+ "ERROR": "Erro ao excluir cenário"
+ }
+ },
+ "DUPLICATE": {
+ "TITLE": "Duplicar Cenário",
+ "DESCRIPTION": "Escolha para qual assistente você deseja copiar este cenário.",
+ "CONFIRM": "Duplicar",
+ "SELECT": "Selecionar Assistente",
+ "SCENARIO_LABEL": "Cenário:",
+ "TARGET_LABEL": "Assistente de Destino:",
+ "COPY_SUFFIX": " (Cópia)",
+ "SUCCESS": "Cenário duplicado com sucesso",
+ "ERROR": "Erro ao duplicar cenário",
+ "NO_TARGETS": "Não há outros assistentes disponíveis para duplicação."
+ },
+ "EXAMPLES": {
+ "PROSPECTIVE_BUYER": {
+ "TITLE": "Comprador em Potencial",
+ "DESCRIPTION": "Lidando com clientes interessados em adquirir uma licença.",
+ "INSTRUCTION": "Se alguém estiver interessado em comprar uma licença, pergunte o seguinte:\n\n1. Quantas licenças deseja comprar?\n2. Está migrando de outra plataforma?\n\nAssim que os detalhes forem coletados, siga estas etapas:\n1. Adicione uma nota privada com as informações coletadas usando [Adicionar Nota Privada](tool://add_private_note)\n2. Adicione a etiqueta \"vendas\" ao contato usando [Adicionar Etiqueta à Conversa](tool://add_label_to_conversation)\n3. Responda dizendo \"um de nós entrará em contato em breve\" e forneça um tempo estimado de resposta e [Transferir para Humano](tool://handoff)"
+ }
+ }
}
}
}
diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/report.json b/app/javascript/dashboard/i18n/locale/pt_BR/report.json
index 426e895..7b0ef07 100755
--- a/app/javascript/dashboard/i18n/locale/pt_BR/report.json
+++ b/app/javascript/dashboard/i18n/locale/pt_BR/report.json
@@ -568,6 +568,14 @@
"VIEW_DETAILS": "Ver detalhes"
}
},
+
+ "FREQUENT_QUESTIONS": {
+ "HEADER": "Perguntas Frequentes",
+ "DESCRIPTION": "Principais motivos de contato clusterizados pela Inteligência Artificial.",
+ "QUESTION": "Pergunta Identificada",
+ "COUNT": "Ocorrências",
+ "DATE": "Data de Identificação"
+ },
"SUMMARY_REPORTS": {
"INBOX": "Caixa de Entrada",
"AGENT": "Agente",
diff --git a/app/javascript/dashboard/routes/dashboard/captain/assistants/Index.vue b/app/javascript/dashboard/routes/dashboard/captain/assistants/Index.vue
index cd51182..c6403f1 100755
--- a/app/javascript/dashboard/routes/dashboard/captain/assistants/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/captain/assistants/Index.vue
@@ -46,7 +46,6 @@ const handleAfterCreate = newAssistant => {
-
-import { computed, ref } from 'vue';
+import { computed, ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
@@ -48,23 +48,20 @@ const displayGuardrails = computed(() =>
guardrailsContent.value.map((c, idx) => ({ id: idx, content: c }))
);
-const guardrailsExample = [
+const guardrailsExample = computed(() => [
{
id: 1,
- content:
- 'Block queries that share or request sensitive personal information (e.g. phone numbers, passwords).',
+ content: t('CAPTAIN.ASSISTANTS.GUARDRAILS.EXAMPLES.PERSONAL_INFO'),
},
{
id: 2,
- content:
- 'Reject queries that include offensive, discriminatory, or threatening language.',
+ content: t('CAPTAIN.ASSISTANTS.GUARDRAILS.EXAMPLES.OFFENSIVE_LANGUAGE'),
},
{
id: 3,
- content:
- 'Deflect when the assistant is asked for legal or medical diagnosis or treatment.',
+ content: t('CAPTAIN.ASSISTANTS.GUARDRAILS.EXAMPLES.LEGAL_MEDICAL'),
},
-];
+]);
const filteredGuardrails = computed(() => {
const query = searchQuery.value.trim();
@@ -111,7 +108,7 @@ const selectedCountLabel = computed(() => {
const saveGuardrails = async list => {
await store.dispatch('captainAssistants/update', {
id: assistantId.value,
- assistant: { guardrails: list },
+ guardrails: list,
});
};
@@ -163,17 +160,22 @@ const bulkDeleteGuardrails = async () => {
const addAllExample = () => {
updateUISettings({ show_guardrails_suggestions: false });
try {
- const exampleContents = guardrailsExample.map(example => example.content);
+ const exampleContents = guardrailsExample.value.map(
+ example => example.content
+ );
const newGuardrails = [...guardrailsContent.value, ...exampleContents];
saveGuardrails(newGuardrails);
} catch {
useAlert(t('CAPTAIN.ASSISTANTS.GUARDRAILS.API.ADD.ERROR'));
}
};
+
+onMounted(() => {
+ store.dispatch('captainAssistants/show', assistantId.value);
+});
-
{
$t('CAPTAIN.ASSISTANTS.GUARDRAILS.BULK_ACTION.BULK_DELETE_BUTTON')
"
@bulk-delete="bulkDeleteGuardrails"
- >
-
-
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/captain/assistants/guidelines/Index.vue b/app/javascript/dashboard/routes/dashboard/captain/assistants/guidelines/Index.vue
index a7b8452..42e1adc 100755
--- a/app/javascript/dashboard/routes/dashboard/captain/assistants/guidelines/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/captain/assistants/guidelines/Index.vue
@@ -50,23 +50,20 @@ const displayGuidelines = computed(() =>
guidelinesContent.value.map((c, idx) => ({ id: idx, content: c }))
);
-const guidelinesExample = [
+const guidelinesExample = computed(() => [
{
id: 1,
- content:
- 'Block queries that share or request sensitive personal information (e.g. phone numbers, passwords).',
+ content: t('CAPTAIN.ASSISTANTS.GUARDRAILS.EXAMPLES.PERSONAL_INFO'),
},
{
id: 2,
- content:
- 'Reject queries that include offensive, discriminatory, or threatening language.',
+ content: t('CAPTAIN.ASSISTANTS.GUARDRAILS.EXAMPLES.OFFENSIVE_LANGUAGE'),
},
{
id: 3,
- content:
- 'Deflect when the assistant is asked for legal or medical diagnosis or treatment.',
+ content: t('CAPTAIN.ASSISTANTS.GUARDRAILS.EXAMPLES.LEGAL_MEDICAL'),
},
-];
+]);
const filteredGuidelines = computed(() => {
const query = searchQuery.value.trim();
@@ -169,7 +166,9 @@ const bulkDeleteGuidelines = async () => {
const addAllExample = async () => {
updateUISettings({ show_response_guidelines_suggestions: false });
try {
- const exampleContents = guidelinesExample.map(example => example.content);
+ const exampleContents = guidelinesExample.value.map(
+ example => example.content
+ );
const newGuidelines = [...guidelinesContent.value, ...exampleContents];
await saveGuidelines(newGuidelines);
useAlert(t('CAPTAIN.ASSISTANTS.RESPONSE_GUIDELINES.API.ADD.SUCCESS'));
@@ -180,7 +179,6 @@ const addAllExample = async () => {
-
-
Number(route.params.assistantId));
-
Number(route.params.assistantId));
const uiFlags = useMapGetter('captainScenarios/getUIFlags');
const isFetching = computed(() => uiFlags.value.fetchingList);
const scenarios = useMapGetter('captainScenarios/getRecords');
+const assistants = useMapGetter('captainAssistants/getRecords');
const searchQuery = ref('');
+const duplicateDialogRef = ref(null);
+const duplicateScenario = ref(null);
+const duplicateTargetId = ref(null);
const LINK_INSTRUCTION_CLASS =
'[&_a[href^="tool://"]]:text-n-iris-11 [&_a:not([href^="tool://"])]:text-n-slate-12 [&_a]:pointer-events-none [&_a]:cursor-default';
@@ -40,17 +46,19 @@ const renderInstruction = instruction =>
});
// Suggested example scenarios for quick add
-const scenariosExample = [
+const scenariosExample = computed(() => [
{
id: 1,
- title: 'Prospective Buyer',
- description:
- 'Handle customers who are showing interest in purchasing a license',
- instruction:
- 'If someone is interested in purchasing a license, ask them for following:\n\n1. How many licenses are they willing to purchase?\n2. Are they migrating from another platform?\n. Once these details are collected, do the following steps\n1. add a private note to with the information you collected using [Add Private Note](tool://add_private_note)\n2. Add label "sales" to the contact using [Add Label to Conversation](tool://add_label_to_conversation)\n3. Reply saying "one of us will reach out soon" and provide an estimated timeline for the response and [Handoff to Human](tool://handoff)',
+ title: t('CAPTAIN.ASSISTANTS.SCENARIOS.EXAMPLES.PROSPECTIVE_BUYER.TITLE'),
+ description: t(
+ 'CAPTAIN.ASSISTANTS.SCENARIOS.EXAMPLES.PROSPECTIVE_BUYER.DESCRIPTION'
+ ),
+ instruction: t(
+ 'CAPTAIN.ASSISTANTS.SCENARIOS.EXAMPLES.PROSPECTIVE_BUYER.INSTRUCTION'
+ ),
tools: ['add_private_note', 'add_label_to_conversation', 'handoff'],
},
-];
+]);
const filteredScenarios = computed(() => {
const query = searchQuery.value.trim();
@@ -59,6 +67,22 @@ const filteredScenarios = computed(() => {
return picoSearch(source, query, ['title', 'description', 'instruction']);
});
+const assistantOptions = computed(() =>
+ assistants.value
+ .filter(assistant => assistant.id !== assistantId.value)
+ .map(assistant => ({
+ label: assistant.name,
+ value: assistant.id,
+ }))
+);
+
+const selectedAssistantLabel = computed(() => {
+ const option = assistantOptions.value.find(
+ item => item.value === duplicateTargetId.value
+ );
+ return option?.label || t('CAPTAIN.ASSISTANTS.SCENARIOS.DUPLICATE.SELECT');
+});
+
const shouldShowSuggestedRules = computed(() => {
return uiSettings.value?.show_scenarios_suggestions !== false;
});
@@ -172,9 +196,57 @@ const addScenario = async scenario => {
}
};
+const openDuplicateDialog = scenario => {
+ if (!assistantOptions.value.length) {
+ useAlert(t('CAPTAIN.ASSISTANTS.SCENARIOS.DUPLICATE.NO_TARGETS'));
+ return;
+ }
+
+ duplicateScenario.value = scenario;
+ duplicateTargetId.value = assistantOptions.value[0]?.value || null;
+ duplicateDialogRef.value?.open();
+};
+
+const closeDuplicateDialog = () => {
+ duplicateDialogRef.value?.close();
+ duplicateScenario.value = null;
+ duplicateTargetId.value = null;
+};
+
+const handleDuplicateConfirm = async () => {
+ if (!duplicateScenario.value || !duplicateTargetId.value) return;
+
+ try {
+ const instructionTools = getToolsFromInstruction(
+ duplicateScenario.value.instruction
+ );
+ const combinedTools = [
+ ...new Set([
+ ...(duplicateScenario.value.tools || []),
+ ...instructionTools,
+ ]),
+ ];
+ const titleSuffix = t('CAPTAIN.ASSISTANTS.SCENARIOS.DUPLICATE.COPY_SUFFIX');
+ await store.dispatch('captainScenarios/create', {
+ assistantId: duplicateTargetId.value,
+ title: `${duplicateScenario.value.title}${titleSuffix}`,
+ description: duplicateScenario.value.description,
+ instruction: duplicateScenario.value.instruction,
+ tools: combinedTools,
+ });
+ useAlert(t('CAPTAIN.ASSISTANTS.SCENARIOS.DUPLICATE.SUCCESS'));
+ closeDuplicateDialog();
+ } catch (error) {
+ const errorMessage =
+ error?.response?.message ||
+ t('CAPTAIN.ASSISTANTS.SCENARIOS.DUPLICATE.ERROR');
+ useAlert(errorMessage);
+ }
+};
+
const addAllExampleScenarios = async () => {
try {
- scenariosExample.forEach(async scenario => {
+ scenariosExample.value.forEach(async scenario => {
await store.dispatch('captainScenarios/create', {
assistantId: assistantId.value,
...scenario,
@@ -193,12 +265,12 @@ onMounted(() => {
store.dispatch('captainScenarios/get', {
assistantId: assistantId.value,
});
+ store.dispatch('captainAssistants/get', { page: 1 });
store.dispatch('captainTools/getTools');
});
-
{
$t('CAPTAIN.ASSISTANTS.SCENARIOS.BULK_ACTION.BULK_DELETE_BUTTON')
"
@bulk-delete="bulkDeleteScenarios"
- >
-
-
-
-
-
@@ -295,6 +366,8 @@ onMounted(() => {
:description="scenario.description"
:instruction="scenario.instruction"
:tools="scenario.tools"
+ :trigger-keywords="scenario.trigger_keywords"
+ :enabled="scenario.enabled"
:is-selected="bulkSelectedIds.has(scenario.id)"
:selectable="
hoveredCard === scenario.id || bulkSelectedIds.size > 0
@@ -302,10 +375,44 @@ onMounted(() => {
@select="handleRuleSelect"
@delete="deleteScenario(scenario.id)"
@update="updateScenario"
+ @duplicate="openDuplicateDialog"
@hover="isHovered => handleRuleHover(isHovered, scenario.id)"
/>
+
+
+
+
+ {{ t('CAPTAIN.ASSISTANTS.SCENARIOS.DUPLICATE.SCENARIO_LABEL') }}
+
+
+ {{ duplicateScenario?.title || '' }}
+
+
+
+
+ {{ t('CAPTAIN.ASSISTANTS.SCENARIOS.DUPLICATE.TARGET_LABEL') }}
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/captain/assistants/settings/Settings.vue b/app/javascript/dashboard/routes/dashboard/captain/assistants/settings/Settings.vue
index d320b7b..9b4bdf1 100755
--- a/app/javascript/dashboard/routes/dashboard/captain/assistants/settings/Settings.vue
+++ b/app/javascript/dashboard/routes/dashboard/captain/assistants/settings/Settings.vue
@@ -57,9 +57,10 @@ const controlItems = computed(() => {
routeName: 'captain_assistants_guidelines_index',
},
{
- name: 'Assistant Skills',
- description:
- 'Configure external tools and integrations available to this assistant.',
+ name: t('CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.TOOLS.TITLE'),
+ description: t(
+ 'CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.TOOLS.DESCRIPTION'
+ ),
routeName: 'captain_tools_index',
},
];
@@ -110,7 +111,6 @@ const handleDeleteSuccess = () => {
-
route.params.assistantId);
@@ -63,6 +67,7 @@ const handleUpdate = async tool => {
webhook_url: tool.webhook_url,
plug_play_id: tool.plug_play_id,
plug_play_token: tool.plug_play_token,
+ fallback_message: tool.fallback_message,
},
});
} catch (e) {
@@ -76,6 +81,9 @@ const handleConfigUpdate = async tool => {
if (!tool.enabled) return;
handleUpdate(tool);
};
+const handleFallbackUpdate = async tool => {
+ handleUpdate(tool);
+};
const fetchCustomTools = (page = 1) => {
store.dispatch('captainCustomTools/get', { page });
};
@@ -127,7 +135,6 @@ onMounted(() => {
-
{
>
{{ $t('CAPTAIN.ASSISTANTS.SKILLS.SAVING') }}
-
+
+ {{ $t('CAPTAIN.ASSISTANTS.SKILLS.ALWAYS_ACTIVE') }}
+
+