feat: Implementa gerenciamento de blocos de prompt do sistema e configurações de habilidades para assistentes, e ajusta permissões de estado vazio de ferramentas personalizadas.
This commit is contained in:
parent
4ee44fd953
commit
4141980f72
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { reactive, computed, watch } from 'vue';
|
||||
import { reactive, computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { minLength } from '@vuelidate/validators';
|
||||
@ -7,7 +7,10 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
|
||||
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 Draggable from 'vuedraggable';
|
||||
|
||||
const props = defineProps({
|
||||
assistant: {
|
||||
@ -36,6 +39,18 @@ const initialState = {
|
||||
};
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
const systemPromptBlocks = ref([]);
|
||||
const activeBlockIndex = ref(null);
|
||||
const activeBlockDraft = reactive({ title: '', content: '' });
|
||||
const blockEditorDialog = ref(null);
|
||||
const promptPreviewDialog = ref(null);
|
||||
|
||||
const systemPromptVersions = computed(
|
||||
() => props.assistant?.config?.system_prompt_versions || []
|
||||
);
|
||||
const hasSystemPromptVersions = computed(
|
||||
() => systemPromptVersions.value.length > 0
|
||||
);
|
||||
|
||||
const validationRules = {
|
||||
handoffMessage: { minLength: minLength(1) },
|
||||
@ -57,6 +72,15 @@ const formErrors = computed(() => ({
|
||||
playbook: getErrorMessage('playbook'),
|
||||
}));
|
||||
|
||||
const normalizeBlocks = blocks =>
|
||||
blocks.map((block, index) => ({
|
||||
uid: block.uid || `${Date.now()}-${index}`,
|
||||
key: block.key || null,
|
||||
title: block.title || '',
|
||||
content: block.content || '',
|
||||
order: block.order ?? index,
|
||||
}));
|
||||
|
||||
const updateStateFromAssistant = assistant => {
|
||||
const { config = {} } = assistant;
|
||||
state.handoffMessage = config.handoff_message || '';
|
||||
@ -67,9 +91,94 @@ const updateStateFromAssistant = assistant => {
|
||||
state.distanceThreshold =
|
||||
config.distance_threshold !== undefined ? config.distance_threshold : 0.35;
|
||||
state.maxRagResults = config.max_rag_results || 3;
|
||||
const blocks =
|
||||
config.system_prompt_blocks || assistant.system_prompt_blocks_preview || [];
|
||||
systemPromptBlocks.value = normalizeBlocks(blocks);
|
||||
};
|
||||
|
||||
const sanitizedBlocks = computed(() =>
|
||||
systemPromptBlocks.value.map((block, index) => ({
|
||||
key: block.key || null,
|
||||
title: block.title,
|
||||
content: block.content,
|
||||
order: index,
|
||||
}))
|
||||
);
|
||||
|
||||
const fullPrompt = computed(() =>
|
||||
systemPromptBlocks.value
|
||||
.map(block => {
|
||||
const title = block.title?.trim();
|
||||
const content = block.content?.trim();
|
||||
if (!title && !content) return null;
|
||||
return `[${title}]\n${content}`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n\n')
|
||||
);
|
||||
|
||||
const promptCharLimit = computed(() => {
|
||||
const provider = props.assistant?.llm_provider || '';
|
||||
const model = props.assistant?.llm_model || '';
|
||||
const isGemini =
|
||||
provider.toLowerCase().includes('gemini') ||
|
||||
model.toLowerCase().includes('gemini');
|
||||
return isGemini ? 80000 : 40000;
|
||||
});
|
||||
|
||||
const isPromptOverLimit = computed(
|
||||
() => fullPrompt.value.length > promptCharLimit.value
|
||||
);
|
||||
|
||||
const activeBlockTitle = computed({
|
||||
get() {
|
||||
return activeBlockDraft.title;
|
||||
},
|
||||
set(value) {
|
||||
activeBlockDraft.title = value;
|
||||
},
|
||||
});
|
||||
|
||||
const activeBlockContent = computed({
|
||||
get() {
|
||||
return activeBlockDraft.content;
|
||||
},
|
||||
set(value) {
|
||||
activeBlockDraft.content = value;
|
||||
},
|
||||
});
|
||||
|
||||
const playbookFromBlocks = computed(() => {
|
||||
const block = systemPromptBlocks.value.find(b => b.key === 'playbook');
|
||||
return block?.content || '';
|
||||
});
|
||||
|
||||
const buildPayload = (extra = {}) => {
|
||||
const config = {
|
||||
...props.assistant.config,
|
||||
handoff_message: state.handoffMessage,
|
||||
resolution_message: state.resolutionMessage,
|
||||
temperature: state.temperature !== undefined ? state.temperature : 1,
|
||||
playbook: state.playbook,
|
||||
distance_threshold: state.distanceThreshold,
|
||||
max_rag_results: state.maxRagResults,
|
||||
};
|
||||
|
||||
if (isCaptainV2Enabled.value) {
|
||||
config.system_prompt_blocks = sanitizedBlocks.value;
|
||||
config.playbook = playbookFromBlocks.value || state.playbook;
|
||||
}
|
||||
|
||||
return {
|
||||
config,
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
|
||||
const handleSystemMessagesUpdate = async () => {
|
||||
if (isCaptainV2Enabled.value && isPromptOverLimit.value) {
|
||||
return;
|
||||
}
|
||||
const validations = [
|
||||
v$.value.handoffMessage.$validate(),
|
||||
v$.value.resolutionMessage.$validate(),
|
||||
@ -84,17 +193,7 @@ const handleSystemMessagesUpdate = async () => {
|
||||
);
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
config: {
|
||||
...props.assistant.config,
|
||||
handoff_message: state.handoffMessage,
|
||||
resolution_message: state.resolutionMessage,
|
||||
temperature: state.temperature !== undefined ? state.temperature : 1,
|
||||
playbook: state.playbook,
|
||||
distance_threshold: state.distanceThreshold,
|
||||
max_rag_results: state.maxRagResults,
|
||||
},
|
||||
};
|
||||
const payload = buildPayload();
|
||||
|
||||
if (!isCaptainV2Enabled.value) {
|
||||
payload.config.instructions = state.instructions;
|
||||
@ -103,6 +202,71 @@ const handleSystemMessagesUpdate = async () => {
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleSaveSystemPromptVersion = () => {
|
||||
if (isPromptOverLimit.value) return;
|
||||
const payload = buildPayload({ system_prompt_action: 'save_version' });
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleRevertSystemPromptVersion = () => {
|
||||
const payload = buildPayload({ system_prompt_action: 'revert_last' });
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleRestoreSystemPromptDefault = () => {
|
||||
const payload = buildPayload({ system_prompt_action: 'restore_default' });
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleAddBlock = () => {
|
||||
systemPromptBlocks.value.push({
|
||||
uid: `${Date.now()}-${systemPromptBlocks.value.length}`,
|
||||
key: null,
|
||||
title: '',
|
||||
content: '',
|
||||
order: systemPromptBlocks.value.length,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveBlock = index => {
|
||||
systemPromptBlocks.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const handleEditBlock = index => {
|
||||
activeBlockIndex.value = index;
|
||||
const block = systemPromptBlocks.value[index];
|
||||
activeBlockDraft.title = block?.title || '';
|
||||
activeBlockDraft.content = block?.content || '';
|
||||
blockEditorDialog.value?.open();
|
||||
};
|
||||
|
||||
const handleBlockModalClosed = () => {
|
||||
activeBlockIndex.value = null;
|
||||
};
|
||||
|
||||
const handleCancelBlockModal = () => {
|
||||
blockEditorDialog.value?.close();
|
||||
};
|
||||
|
||||
const handleApplyBlockChanges = () => {
|
||||
const block = systemPromptBlocks.value[activeBlockIndex.value];
|
||||
if (block) {
|
||||
block.title = activeBlockDraft.title;
|
||||
block.content = activeBlockDraft.content;
|
||||
}
|
||||
handleCancelBlockModal();
|
||||
};
|
||||
|
||||
const handleOpenPromptPreview = () => {
|
||||
promptPreviewDialog.value?.open();
|
||||
};
|
||||
|
||||
const handleClosePromptPreview = () => {
|
||||
promptPreviewDialog.value?.close();
|
||||
};
|
||||
|
||||
const handlePromptPreviewClosed = () => {};
|
||||
|
||||
watch(
|
||||
() => props.assistant,
|
||||
newAssistant => {
|
||||
@ -143,7 +307,118 @@ watch(
|
||||
class="z-0"
|
||||
/>
|
||||
|
||||
<div v-if="isCaptainV2Enabled" class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.LABEL') }}
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT.SAVE_VERSION')"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
@click="handleSaveSystemPromptVersion"
|
||||
/>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT.REVERT_LAST')"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:disabled="!hasSystemPromptVersions"
|
||||
@click="handleRevertSystemPromptVersion"
|
||||
/>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT.RESTORE_DEFAULT')"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
@click="handleRestoreSystemPromptDefault"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-n-slate-11">
|
||||
<span>
|
||||
{{
|
||||
t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.CHAR_COUNT', {
|
||||
count: fullPrompt.length,
|
||||
limit: promptCharLimit,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.VIEW_FULL')"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
@click="handleOpenPromptPreview"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="isPromptOverLimit" class="text-xs text-ruby-9">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.LIMIT_WARNING') }}
|
||||
</p>
|
||||
<Draggable
|
||||
v-model="systemPromptBlocks"
|
||||
item-key="uid"
|
||||
handle=".drag-handle"
|
||||
class="flex flex-col gap-3"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="rounded-lg border border-n-weak p-4 flex flex-col gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="drag-handle cursor-grab text-n-slate-11">
|
||||
<i class="i-lucide-grip-vertical" aria-hidden="true" />
|
||||
</span>
|
||||
<Input
|
||||
v-model="element.title"
|
||||
:placeholder="
|
||||
t(
|
||||
'CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.TITLE_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between gap-3 text-xs text-n-slate-11"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{
|
||||
element.content?.slice(0, 120) ||
|
||||
t(
|
||||
'CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.EMPTY_CONTENT'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
:label="
|
||||
t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.EDIT')
|
||||
"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
@click="handleEditBlock(index)"
|
||||
/>
|
||||
<Button
|
||||
v-if="!element.key"
|
||||
:label="
|
||||
t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.REMOVE')
|
||||
"
|
||||
variant="faded"
|
||||
color="ruby"
|
||||
@click="handleRemoveBlock(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.ADD')"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
class="w-fit"
|
||||
@click="handleAddBlock"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Editor
|
||||
v-if="!isCaptainV2Enabled"
|
||||
v-model="state.playbook"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.PLAYBOOK.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.PLAYBOOK.PLACEHOLDER')"
|
||||
@ -215,8 +490,89 @@ watch(
|
||||
<div>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.UPDATE')"
|
||||
:disabled="isCaptainV2Enabled && isPromptOverLimit"
|
||||
@click="handleSystemMessagesUpdate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
ref="blockEditorDialog"
|
||||
:title="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.EDIT_TITLE')"
|
||||
width="3xl"
|
||||
overflow-y-auto
|
||||
:show-confirm-button="false"
|
||||
@close="handleBlockModalClosed"
|
||||
>
|
||||
<div class="flex flex-col gap-4 min-h-[75vh]">
|
||||
<Input
|
||||
v-model="activeBlockTitle"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.TITLE_LABEL')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.TITLE_PLACEHOLDER')
|
||||
"
|
||||
/>
|
||||
<div class="system-prompt-block-editor">
|
||||
<Editor
|
||||
v-model="activeBlockContent"
|
||||
:label="
|
||||
t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.CONTENT_LABEL')
|
||||
"
|
||||
:placeholder="
|
||||
t(
|
||||
'CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.CONTENT_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
:max-length="promptCharLimit"
|
||||
class="z-0 h-[70vh]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-end w-full gap-2">
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.CANCEL')"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
type="button"
|
||||
@click.stop="handleCancelBlockModal"
|
||||
/>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.DONE')"
|
||||
type="button"
|
||||
@click.stop="handleApplyBlockChanges"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
ref="promptPreviewDialog"
|
||||
:title="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.PREVIEW_TITLE')"
|
||||
width="3xl"
|
||||
:show-confirm-button="false"
|
||||
@close="handlePromptPreviewClosed"
|
||||
>
|
||||
<pre
|
||||
class="text-xs text-n-slate-11 whitespace-pre-wrap max-h-[60vh] overflow-y-auto"
|
||||
>
|
||||
{{ fullPrompt }}
|
||||
</pre>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-end w-full">
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.SYSTEM_PROMPT_BLOCKS.CLOSE')"
|
||||
type="button"
|
||||
@click="handleClosePromptPreview"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.system-prompt-block-editor :deep(.ProseMirror-woot-style) {
|
||||
min-height: 60vh;
|
||||
max-height: 60vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -13,7 +13,7 @@ const onClick = () => {
|
||||
<EmptyStateLayout
|
||||
:title="$t('CAPTAIN.CUSTOM_TOOLS.EMPTY_STATE.TITLE')"
|
||||
:subtitle="$t('CAPTAIN.CUSTOM_TOOLS.EMPTY_STATE.SUBTITLE')"
|
||||
:action-perms="['administrator']"
|
||||
:action-perms="[]"
|
||||
>
|
||||
<template #empty-state-item>
|
||||
<div class="min-h-[600px]" />
|
||||
|
||||
@ -501,6 +501,32 @@
|
||||
"LABEL": "Instructions",
|
||||
"PLACEHOLDER": "Enter instructions for the assistant"
|
||||
},
|
||||
"SYSTEM_PROMPT": {
|
||||
"LABEL": "System Prompt",
|
||||
"PLACEHOLDER": "Edit the system prompt used by the assistant",
|
||||
"SAVE_VERSION": "Save version",
|
||||
"REVERT_LAST": "Revert last",
|
||||
"RESTORE_DEFAULT": "Restore default"
|
||||
},
|
||||
"SYSTEM_PROMPT_BLOCKS": {
|
||||
"LABEL": "System prompt blocks",
|
||||
"ADD": "Add block",
|
||||
"EDIT": "Edit",
|
||||
"REMOVE": "Remove",
|
||||
"CANCEL": "Cancel",
|
||||
"DONE": "Done",
|
||||
"VIEW_FULL": "View full prompt",
|
||||
"PREVIEW_TITLE": "Full system prompt",
|
||||
"EDIT_TITLE": "Edit block",
|
||||
"TITLE_LABEL": "Block title",
|
||||
"TITLE_PLACEHOLDER": "e.g., Identity",
|
||||
"CONTENT_LABEL": "Block content",
|
||||
"CONTENT_PLACEHOLDER": "Write the content for this block",
|
||||
"EMPTY_CONTENT": "No content yet",
|
||||
"CHAR_COUNT": "{{count}} / {{limit}} characters",
|
||||
"LIMIT_WARNING": "Prompt length exceeds the limit for this model.",
|
||||
"CLOSE": "Close"
|
||||
},
|
||||
"PLAYBOOK": {
|
||||
"LABEL": "SDR Playbook",
|
||||
"PLACEHOLDER": "Sales script and objection handling...",
|
||||
@ -735,6 +761,26 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
,
|
||||
"SKILLS": {
|
||||
"HEADER": "Assistant Skills",
|
||||
"DESCRIPTION": "Configure the capabilities and tools available to this assistant.",
|
||||
"SAVING": "Saving...",
|
||||
"CONFIGURATION": "Configuration",
|
||||
"EMPTY_STATE": "No skills available for this assistant.",
|
||||
"WEBHOOK_URL": {
|
||||
"LABEL": "Webhook URL",
|
||||
"PLACEHOLDER": "https://oxpi.com.br/api/..."
|
||||
},
|
||||
"PLUG_PLAY_ID": {
|
||||
"LABEL": "Plug&Play Client ID",
|
||||
"PLACEHOLDER": "Client ID"
|
||||
},
|
||||
"PLUG_PLAY_TOKEN": {
|
||||
"LABEL": "Plug&Play Token",
|
||||
"PLACEHOLDER": "Token"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DOCUMENTS": {
|
||||
"HEADER": "Documents",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user