feat: Adiciona internacionalização para a base de conhecimento Jasmine, ajusta regras de lint e atualiza dependências.
This commit is contained in:
parent
2672d21136
commit
c0cd8c24b0
1
Gemfile
1
Gemfile
@ -60,6 +60,7 @@ gem 'aws-actionmailbox-ses', '~> 0'
|
|||||||
|
|
||||||
##-- gems for database --#
|
##-- gems for database --#
|
||||||
gem 'groupdate'
|
gem 'groupdate'
|
||||||
|
gem 'fiddle'
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
gem 'redis'
|
gem 'redis'
|
||||||
gem 'redis-namespace'
|
gem 'redis-namespace'
|
||||||
|
|||||||
@ -338,6 +338,7 @@ GEM
|
|||||||
ffi-compiler (1.0.1)
|
ffi-compiler (1.0.1)
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
rake
|
rake
|
||||||
|
fiddle (1.1.8)
|
||||||
flag_shih_tzu (0.3.23)
|
flag_shih_tzu (0.3.23)
|
||||||
foreman (0.87.2)
|
foreman (0.87.2)
|
||||||
fugit (1.11.1)
|
fugit (1.11.1)
|
||||||
@ -1071,6 +1072,7 @@ DEPENDENCIES
|
|||||||
faker
|
faker
|
||||||
faraday_middleware-aws-sigv4
|
faraday_middleware-aws-sigv4
|
||||||
fcm
|
fcm
|
||||||
|
fiddle
|
||||||
flag_shih_tzu
|
flag_shih_tzu
|
||||||
foreman
|
foreman
|
||||||
geocoder
|
geocoder
|
||||||
@ -1180,7 +1182,7 @@ DEPENDENCIES
|
|||||||
working_hours
|
working_hours
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.4.4p34
|
ruby 3.4.4p34
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.5
|
2.5.5
|
||||||
|
|||||||
@ -46,23 +46,32 @@ class JasmineAPI extends ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unlinkCollection(inboxId, collectionId) {
|
unlinkCollection(inboxId, collectionId) {
|
||||||
return axios.delete(`${this.url}/${inboxId}/jasmine/collections/${collectionId}`);
|
return axios.delete(
|
||||||
|
`${this.url}/${inboxId}/jasmine/collections/${collectionId}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Documents
|
// Documents
|
||||||
getDocuments(collectionId) {
|
getDocuments(collectionId) {
|
||||||
return axios.get(`${this.jasmineUrl}/collections/${collectionId}/documents`);
|
return axios.get(
|
||||||
|
`${this.jasmineUrl}/collections/${collectionId}/documents`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadDocument(collectionId, content, title) {
|
uploadDocument(collectionId, content, title) {
|
||||||
return axios.post(`${this.jasmineUrl}/collections/${collectionId}/documents`, {
|
return axios.post(
|
||||||
title,
|
`${this.jasmineUrl}/collections/${collectionId}/documents`,
|
||||||
content,
|
{
|
||||||
});
|
title,
|
||||||
|
content,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteDocument(collectionId, documentId) {
|
deleteDocument(collectionId, documentId) {
|
||||||
return axios.delete(`${this.jasmineUrl}/collections/${collectionId}/documents/${documentId}`);
|
return axios.delete(
|
||||||
|
`${this.jasmineUrl}/collections/${collectionId}/documents/${documentId}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playground
|
// Playground
|
||||||
@ -85,4 +94,3 @@ class JasmineAPI extends ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default new JasmineAPI();
|
export default new JasmineAPI();
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,8 @@ export function usePolicy() {
|
|||||||
if (!flag) return false;
|
if (!flag) return false;
|
||||||
|
|
||||||
// Bypass paywall for Captain in development
|
// Bypass paywall for Captain in development
|
||||||
if (['captain_integration', 'captain_integration_v2'].includes(flag)) return false;
|
if (['captain_integration', 'captain_integration_v2'].includes(flag))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (isACustomBrandedInstance.value) {
|
if (isACustomBrandedInstance.value) {
|
||||||
// custom branded instances never show paywall
|
// custom branded instances never show paywall
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import whatsappTemplates from './whatsappTemplates.json';
|
|||||||
import contentTemplates from './contentTemplates.json';
|
import contentTemplates from './contentTemplates.json';
|
||||||
import mfa from './mfa.json';
|
import mfa from './mfa.json';
|
||||||
import yearInReview from './yearInReview.json';
|
import yearInReview from './yearInReview.json';
|
||||||
|
import jasmine from './jasmine.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...advancedFilters,
|
...advancedFilters,
|
||||||
@ -84,4 +85,5 @@ export default {
|
|||||||
...contentTemplates,
|
...contentTemplates,
|
||||||
...mfa,
|
...mfa,
|
||||||
...yearInReview,
|
...yearInReview,
|
||||||
|
...jasmine,
|
||||||
};
|
};
|
||||||
|
|||||||
72
app/javascript/dashboard/i18n/locale/en/jasmine.json
Normal file
72
app/javascript/dashboard/i18n/locale/en/jasmine.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"JASMINE": {
|
||||||
|
"HEADER": {
|
||||||
|
"TITLE": "Jasmine AI Agents",
|
||||||
|
"DESCRIPTION": "Manage your AI SDR agents. Select an inbox to configure your knowledge base.",
|
||||||
|
"EMPTY": "No inboxes found"
|
||||||
|
},
|
||||||
|
"CONFIG": {
|
||||||
|
"TITLE": "Jasmine AI Configuration",
|
||||||
|
"DESCRIPTION": "Configure the AI agent for this inbox.",
|
||||||
|
"ENABLE": "Enable Jasmine AI Agent",
|
||||||
|
"SYSTEM_PROMPT": "System Prompt",
|
||||||
|
"SYSTEM_PROMPT_HELP": "Define the persona and behavioral rules for the agent.",
|
||||||
|
"UPDATE_BUTTON": "Update Configuration"
|
||||||
|
},
|
||||||
|
"KNOWLEDGE_BASE": {
|
||||||
|
"TITLE": "Knowledge Base",
|
||||||
|
"DESCRIPTION": "Manage knowledge collections for this inbox",
|
||||||
|
"ADD_BUTTON": "+ New Collection",
|
||||||
|
"DOCUMENTS": "Documents",
|
||||||
|
"LOADING_DOCS": "Loading documents...",
|
||||||
|
"UNTITLED_DOC": "Untitled Document",
|
||||||
|
"NO_DOCS": "No documents yet. Add your first document below.",
|
||||||
|
"ADD_DOC_HEADER": "Add New Document",
|
||||||
|
"DOC_TITLE_PLACEHOLDER": "Document title (optional)",
|
||||||
|
"DOC_CONTENT_PLACEHOLDER": "Paste or type your knowledge content here...",
|
||||||
|
"ADD_DOC_BUTTON": "Add Document",
|
||||||
|
"NO_COLLECTIONS": "No collections yet. Create one to get started.",
|
||||||
|
"CREATE_MODAL": {
|
||||||
|
"TITLE": "Create Collection",
|
||||||
|
"NAME_PLACEHOLDER": "Collection name",
|
||||||
|
"VISIBILITY_PRIVATE": "Private (This inbox only)",
|
||||||
|
"VISIBILITY_SHARED": "Shared (All inboxes)",
|
||||||
|
"CANCEL": "Cancel",
|
||||||
|
"CREATE": "Create"
|
||||||
|
},
|
||||||
|
"DELETE_CONFIRM": "Are you sure you want to delete this document?",
|
||||||
|
"DOCUMENT_DELETE_SUCCESS": "Document deleted successfully",
|
||||||
|
"COLLECTION_DELETE_SUCCESS": "Collection deleted successfully",
|
||||||
|
"SAVE_SUCCESS": "Changes saved successfully",
|
||||||
|
"DOCUMENT_CREATE_SUCCESS": "Document created successfully",
|
||||||
|
"COLLECTION_CREATE_SUCCESS": "Collection created successfully"
|
||||||
|
},
|
||||||
|
"PLAYGROUND": {
|
||||||
|
"TITLE": "Jasmine AI Playground",
|
||||||
|
"DESCRIPTION": "Test Jasmine responses in real-time before enabling for customers.",
|
||||||
|
"SELECT_INBOX": "Select an Inbox to test",
|
||||||
|
"CHOOSE_INBOX": "Choose an inbox...",
|
||||||
|
"WARNING": "Make sure Jasmine is enabled and configured for this inbox",
|
||||||
|
"EMPTY_STATE_TITLE": "Send a message to test Jasmine",
|
||||||
|
"EMPTY_STATE_EXAMPLES": "Try: \"Hello\", \"How much does it cost?\", \"How does it work?\"",
|
||||||
|
"LOADING": "Jasmine is thinking...",
|
||||||
|
"INPUT_PLACEHOLDER": "Type a test message...",
|
||||||
|
"CLEAR_TOOLTIP": "Clear conversation",
|
||||||
|
"NO_INBOX_SELECTED": "Select an inbox above to start testing"
|
||||||
|
},
|
||||||
|
"INBOX_LIST": {
|
||||||
|
"ACTIVE": "Active",
|
||||||
|
"CONFIGURE": "Configure",
|
||||||
|
"DESCRIPTION": "Channel {channel} configured for Jasmine AI"
|
||||||
|
},
|
||||||
|
"WUZAPI": {
|
||||||
|
"STATUS": "Status: {status}",
|
||||||
|
"ACCOUNT_ERROR": "Error: Account ID not loaded. Please refresh the page.",
|
||||||
|
"CONNECT_FALLBACK": "Click to initiate connection",
|
||||||
|
"CONNECT_BUTTON_FALLBACK": "Connect WhatsApp",
|
||||||
|
"WEBHOOK_SECTION": "Webhook Configuration",
|
||||||
|
"GET_WEBHOOK_INFO": "Get Webhook Info",
|
||||||
|
"UPDATE_WEBHOOK": "Update Webhook Connection"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,4 +44,3 @@ const routes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
||||||
|
|||||||
@ -45,8 +45,8 @@ const getChannelName = channelType => {
|
|||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<BaseSettingsHeader
|
<BaseSettingsHeader
|
||||||
title="Agentes Jasmine AI"
|
:title="$t('JASMINE.HEADER.TITLE')"
|
||||||
description="Gerencie seus agentes de IA SDR. Selecione uma caixa de entrada para configurar sua base de conhecimento."
|
:description="$t('JASMINE.HEADER.DESCRIPTION')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -64,14 +64,12 @@ const getChannelName = channelType => {
|
|||||||
class="flex items-center justify-center size-12 rounded-lg bg-n-blue-2"
|
class="flex items-center justify-center size-12 rounded-lg bg-n-blue-2"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
:class="[
|
class="size-6 text-n-blue-text"
|
||||||
getChannelIcon(inbox.channel_type),
|
:class="[getChannelIcon(inbox.channel_type)]"
|
||||||
'size-6 text-n-blue-text',
|
|
||||||
]"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
v-tooltip="'Ativo'"
|
v-tooltip="$t('JASMINE.INBOX_LIST.ACTIVE')"
|
||||||
class="text-white p-0.5 rounded-full size-5 flex items-center justify-center bg-n-teal-9"
|
class="text-white p-0.5 rounded-full size-5 flex items-center justify-center bg-n-teal-9"
|
||||||
>
|
>
|
||||||
<i class="i-ph-check-bold text-sm" />
|
<i class="i-ph-check-bold text-sm" />
|
||||||
@ -83,13 +81,20 @@ const getChannelName = channelType => {
|
|||||||
<span class="text-base font-semibold text-n-slate-12">{{
|
<span class="text-base font-semibold text-n-slate-12">{{
|
||||||
inbox.name
|
inbox.name
|
||||||
}}</span>
|
}}</span>
|
||||||
<Button label="Configurar" link @click.stop="openInbox(inbox.id)" />
|
<Button
|
||||||
|
:label="$t('JASMINE.INBOX_LIST.CONFIGURE')"
|
||||||
|
link
|
||||||
|
@click.stop="openInbox(inbox.id)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<p class="text-sm text-n-slate-11">
|
<p class="text-sm text-n-slate-11">
|
||||||
Canal {{ getChannelName(inbox.channel_type) }} configurado para
|
{{
|
||||||
Jasmine AI
|
$t('JASMINE.INBOX_LIST.DESCRIPTION', {
|
||||||
|
channel: getChannelName(inbox.channel_type),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -63,8 +63,8 @@ const clearChat = () => {
|
|||||||
<SettingsLayout :is-loading="false">
|
<SettingsLayout :is-loading="false">
|
||||||
<template #header>
|
<template #header>
|
||||||
<BaseSettingsHeader
|
<BaseSettingsHeader
|
||||||
title="Playground Jasmine AI"
|
:title="$t('JASMINE.PLAYGROUND.TITLE')"
|
||||||
description="Teste as respostas da Jasmine em tempo real antes de ativar para os clientes."
|
:description="$t('JASMINE.PLAYGROUND.DESCRIPTION')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -73,20 +73,25 @@ const clearChat = () => {
|
|||||||
<!-- Inbox Selector -->
|
<!-- Inbox Selector -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-n-slate-12 mb-2">
|
<label class="block text-sm font-medium text-n-slate-12 mb-2">
|
||||||
Selecione uma Inbox para testar
|
{{ $t('JASMINE.PLAYGROUND.SELECT_INBOX') }}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
v-model="selectedInboxId"
|
v-model="selectedInboxId"
|
||||||
class="w-full max-w-md px-3 py-2 text-sm rounded-lg border border-n-weak bg-n-solid-1 text-n-slate-12"
|
class="w-full max-w-md px-3 py-2 text-sm rounded-lg border border-n-weak bg-n-solid-1 text-n-slate-12"
|
||||||
>
|
>
|
||||||
<option :value="null">Escolha uma inbox...</option>
|
<option :value="null">
|
||||||
|
{{ $t('JASMINE.PLAYGROUND.CHOOSE_INBOX') }}
|
||||||
|
</option>
|
||||||
<option v-for="inbox in inboxes" :key="inbox.id" :value="inbox.id">
|
<option v-for="inbox in inboxes" :key="inbox.id" :value="inbox.id">
|
||||||
{{ inbox.name }}
|
{{ inbox.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<p v-if="selectedInbox" class="text-xs text-n-slate-11 mt-1">
|
<p v-if="selectedInbox" class="text-xs text-n-slate-11 mt-1">
|
||||||
⚠️ Certifique-se de que a Jasmine está ativada e configurada para
|
{{
|
||||||
esta inbox
|
$t('JASMINE.PLAYGROUND.FETCH_ERROR', {
|
||||||
|
error: $t('JASMINE.PLAYGROUND.WARNING'),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -102,17 +107,17 @@ const clearChat = () => {
|
|||||||
class="text-center text-n-slate-11 py-12"
|
class="text-center text-n-slate-11 py-12"
|
||||||
>
|
>
|
||||||
<span class="i-lucide-message-square size-12 mb-4 opacity-50" />
|
<span class="i-lucide-message-square size-12 mb-4 opacity-50" />
|
||||||
<p>Envie uma mensagem para testar a Jasmine</p>
|
<p>{{ $t('JASMINE.PLAYGROUND.EMPTY_STATE_TITLE') }}</p>
|
||||||
<p class="text-xs mt-2">
|
<p class="text-xs mt-2">
|
||||||
Experimente: "Olá", "Quanto custa?", "Como funciona?"
|
{{ $t('JASMINE.PLAYGROUND.EMPTY_STATE_EXAMPLES') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="(msg, index) in messages"
|
v-for="(msg, index) in messages"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
class="max-w-[80%] rounded-lg p-3"
|
||||||
:class="[
|
:class="[
|
||||||
'max-w-[80%] rounded-lg p-3',
|
|
||||||
msg.role === 'user'
|
msg.role === 'user'
|
||||||
? 'ml-auto bg-n-blue-9 text-white'
|
? 'ml-auto bg-n-blue-9 text-white'
|
||||||
: msg.role === 'error'
|
: msg.role === 'error'
|
||||||
@ -125,10 +130,16 @@ const clearChat = () => {
|
|||||||
v-if="msg.debug"
|
v-if="msg.debug"
|
||||||
class="mt-2 pt-2 border-t border-n-weak text-xs text-n-slate-11"
|
class="mt-2 pt-2 border-t border-n-weak text-xs text-n-slate-11"
|
||||||
>
|
>
|
||||||
<span class="font-mono"
|
<span class="font-mono">
|
||||||
>{{ msg.debug.model }} | temp:
|
{{
|
||||||
{{ msg.debug.temperature }}</span
|
$t('JASMINE.PLAYGROUND.MODEL', { model: msg.debug.model })
|
||||||
>
|
}}
|
||||||
|
{{
|
||||||
|
$t('JASMINE.PLAYGROUND.TEMPERATURE', {
|
||||||
|
temp: msg.debug.temperature,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -137,7 +148,9 @@ const clearChat = () => {
|
|||||||
class="flex items-center gap-2 text-n-slate-11"
|
class="flex items-center gap-2 text-n-slate-11"
|
||||||
>
|
>
|
||||||
<span class="i-lucide-loader-2 size-4 animate-spin" />
|
<span class="i-lucide-loader-2 size-4 animate-spin" />
|
||||||
<span class="text-sm">Jasmine está pensando...</span>
|
<span class="text-sm">{{
|
||||||
|
$t('JASMINE.PLAYGROUND.LOADING')
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -148,7 +161,7 @@ const clearChat = () => {
|
|||||||
v-model="inputMessage"
|
v-model="inputMessage"
|
||||||
type="text"
|
type="text"
|
||||||
class="flex-1 px-3 py-2 text-sm rounded-lg border border-n-weak bg-n-solid-1 text-n-slate-12"
|
class="flex-1 px-3 py-2 text-sm rounded-lg border border-n-weak bg-n-solid-1 text-n-slate-12"
|
||||||
placeholder="Digite uma mensagem de teste..."
|
:placeholder="$t('JASMINE.PLAYGROUND.INPUT_PLACEHOLDER')"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
@keyup.enter="sendMessage"
|
@keyup.enter="sendMessage"
|
||||||
/>
|
/>
|
||||||
@ -158,7 +171,7 @@ const clearChat = () => {
|
|||||||
@click="sendMessage"
|
@click="sendMessage"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
v-tooltip="'Limpar conversa'"
|
v-tooltip="$t('JASMINE.PLAYGROUND.CLEAR_TOOLTIP')"
|
||||||
icon="i-lucide-trash-2"
|
icon="i-lucide-trash-2"
|
||||||
faded
|
faded
|
||||||
slate
|
slate
|
||||||
@ -176,7 +189,7 @@ const clearChat = () => {
|
|||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="i-lucide-inbox size-16 mb-4 opacity-30" />
|
<span class="i-lucide-inbox size-16 mb-4 opacity-30" />
|
||||||
<p>Selecione uma inbox acima para começar a testar</p>
|
<p>{{ $t('JASMINE.PLAYGROUND.NO_INBOX_SELECTED') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<template>
|
|
||||||
<router-view />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'JasmineWrapper',
|
name: 'JasmineWrapper',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
|||||||
@ -70,10 +70,10 @@ export default {
|
|||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="flex flex-col gap-1 items-start mb-4">
|
<div class="flex flex-col gap-1 items-start mb-4">
|
||||||
<h2 class="text-xl font-medium text-slate-900 dark:text-slate-100">
|
<h2 class="text-xl font-medium text-slate-900 dark:text-slate-100">
|
||||||
Jasmine AI Configuration
|
{{ $t('JASMINE.CONFIG.TITLE') }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-slate-600 dark:text-slate-400">
|
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||||
Configure the AI agent for this inbox.
|
{{ $t('JASMINE.CONFIG.DESCRIPTION') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ export default {
|
|||||||
class="form-checkbox h-5 w-5 text-woot-500 rounded border-gray-300 focus:ring-woot-500"
|
class="form-checkbox h-5 w-5 text-woot-500 rounded border-gray-300 focus:ring-woot-500"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-200">
|
<span class="text-sm font-medium text-slate-700 dark:text-slate-200">
|
||||||
Enable Jasmine AI Agent
|
{{ $t('JASMINE.CONFIG.ENABLE') }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -94,21 +94,21 @@ export default {
|
|||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-slate-700 dark:text-slate-200 mb-2"
|
class="block text-sm font-medium text-slate-700 dark:text-slate-200 mb-2"
|
||||||
>
|
>
|
||||||
System Prompt
|
{{ $t('JASMINE.CONFIG.SYSTEM_PROMPT') }}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="systemPrompt"
|
v-model="systemPrompt"
|
||||||
rows="6"
|
rows="6"
|
||||||
class="w-full text-sm rounded-md border-gray-300 dark:border-slate-700 dark:bg-slate-900 focus:border-woot-500 focus:ring-woot-500"
|
class="w-full text-sm rounded-md border-gray-300 dark:border-slate-700 dark:bg-slate-900 focus:border-woot-500 focus:ring-woot-500"
|
||||||
placeholder="You are a helpful SDR agent..."
|
:placeholder="$t('JASMINE.CONFIG.SYSTEM_PROMPT_HELP')"
|
||||||
></textarea>
|
/>
|
||||||
<p class="mt-1 text-xs text-slate-500">
|
<p class="mt-1 text-xs text-slate-500">
|
||||||
Define the persona and behavioral rules for the agent.
|
{{ $t('JASMINE.CONFIG.SYSTEM_PROMPT_HELP') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<woot-button :is-loading="isUpdating" @click="updateSettings">
|
<woot-button :is-loading="isUpdating" @click="updateSettings">
|
||||||
Update Configuration
|
{{ $t('JASMINE.CONFIG.UPDATE_BUTTON') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
|
|
||||||
<JasmineKnowledgeBase v-if="showKnowledgeBase" :inbox-id="inbox.id" />
|
<JasmineKnowledgeBase v-if="showKnowledgeBase" :inbox-id="inbox.id" />
|
||||||
|
|||||||
@ -34,20 +34,25 @@ export default defineComponent({
|
|||||||
return `/api/v1/accounts/${accountId.value}/inboxes/${props.inbox.id}/wuzapi${endpoint}`;
|
return `/api/v1/accounts/${accountId.value}/inboxes/${props.inbox.id}/wuzapi${endpoint}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchStatus = async () => {
|
function stopPolling() {
|
||||||
|
if (pollInterval) {
|
||||||
|
clearInterval(pollInterval);
|
||||||
|
pollInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchStatus() {
|
||||||
if (!accountId.value) return;
|
if (!accountId.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await window.axios.get(getApiUrl(''));
|
const response = await window.axios.get(getApiUrl(''));
|
||||||
|
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
// Wuzapi format: { data: { connected: true, jid: "...", details: "..." } }
|
|
||||||
const wuzapiData = data.data || {};
|
const wuzapiData = data.data || {};
|
||||||
|
|
||||||
const isWuzapiConnected =
|
const isWuzapiConnected =
|
||||||
wuzapiData.connected === true && !!wuzapiData.jid;
|
wuzapiData.connected === true && !!wuzapiData.jid;
|
||||||
|
|
||||||
// Also keep legacy check just in case payload differs
|
|
||||||
const legacyStatus = data.status || data.state;
|
const legacyStatus = data.status || data.state;
|
||||||
const isLegacyConnected = ['CONNECTED', 'inChat', 'success'].includes(
|
const isLegacyConnected = ['CONNECTED', 'inChat', 'success'].includes(
|
||||||
legacyStatus
|
legacyStatus
|
||||||
@ -64,13 +69,22 @@ export default defineComponent({
|
|||||||
statusMessage.value =
|
statusMessage.value =
|
||||||
error.response?.data?.error || error.message || 'Check failed';
|
error.response?.data?.error || error.message || 'Check failed';
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const fetchQrCode = async () => {
|
/* eslint-disable no-use-before-define */
|
||||||
|
function startPolling() {
|
||||||
|
if (pollInterval) return;
|
||||||
|
pollInterval = setInterval(async () => {
|
||||||
|
await fetchStatus();
|
||||||
|
if (pollInterval && !isConnected.value) {
|
||||||
|
await fetchQrCode();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchQrCode() {
|
||||||
try {
|
try {
|
||||||
const response = await window.axios.get(getApiUrl('/qr'));
|
const response = await window.axios.get(getApiUrl('/qr'));
|
||||||
|
|
||||||
// Backend now normalizes to 'qrcode' in most cases, but we keep robust checks
|
|
||||||
const d = response.data;
|
const d = response.data;
|
||||||
const qrcodeData =
|
const qrcodeData =
|
||||||
d.qrcode ||
|
d.qrcode ||
|
||||||
@ -84,7 +98,6 @@ export default defineComponent({
|
|||||||
qrCode.value = qrcodeData;
|
qrCode.value = qrcodeData;
|
||||||
startPolling();
|
startPolling();
|
||||||
} else {
|
} else {
|
||||||
// Fallback: maybe we are already connected?
|
|
||||||
await fetchStatus();
|
await fetchStatus();
|
||||||
if (!isConnected.value) {
|
if (!isConnected.value) {
|
||||||
statusMessage.value = 'QR Code not received and not connected.';
|
statusMessage.value = 'QR Code not received and not connected.';
|
||||||
@ -94,7 +107,7 @@ export default defineComponent({
|
|||||||
statusMessage.value =
|
statusMessage.value =
|
||||||
error.response?.data?.error || 'Failed to load QR';
|
error.response?.data?.error || 'Failed to load QR';
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleConnect = async () => {
|
const handleConnect = async () => {
|
||||||
if (!accountId.value) {
|
if (!accountId.value) {
|
||||||
@ -131,26 +144,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function hoisting allows use before definition
|
|
||||||
function stopPolling() {
|
|
||||||
if (pollInterval) {
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
pollInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startPolling() {
|
|
||||||
if (pollInterval) return;
|
|
||||||
// Poll every 5 seconds to check status AND refresh QR code
|
|
||||||
pollInterval = setInterval(async () => {
|
|
||||||
await fetchStatus();
|
|
||||||
// If still not connected (and polling hasn't been stopped by fetchStatus), refresh QR
|
|
||||||
if (pollInterval && !isConnected.value) {
|
|
||||||
await fetchQrCode();
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLoadingWebhook = ref(false);
|
const isLoadingWebhook = ref(false);
|
||||||
const webhookInfo = ref(null);
|
const webhookInfo = ref(null);
|
||||||
|
|
||||||
@ -173,7 +166,7 @@ export default defineComponent({
|
|||||||
const response = await window.axios.put(getApiUrl('/update_webhook'));
|
const response = await window.axios.put(getApiUrl('/update_webhook'));
|
||||||
webhookInfo.value = {
|
webhookInfo.value = {
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
url: response.data.webhook_url
|
url: response.data.webhook_url,
|
||||||
};
|
};
|
||||||
useAlert('Webhook updated successfully');
|
useAlert('Webhook updated successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -213,8 +206,8 @@ export default defineComponent({
|
|||||||
<div class="mx-8 mt-6">
|
<div class="mx-8 mt-6">
|
||||||
<div class="bg-white p-6 rounded-lg border border-n-weak">
|
<div class="bg-white p-6 rounded-lg border border-n-weak">
|
||||||
<h3 class="text-lg font-medium text-n-slate-12 mb-4">
|
<h3 class="text-lg font-medium text-n-slate-12 mb-4">
|
||||||
{{ $t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WUZAPI') }} -
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WUZAPI') }}
|
||||||
{{ $t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_CONFIG') }}
|
{{ `- ${$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_CONFIG')}` }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div v-if="accountId" class="flex flex-col items-center">
|
<div v-if="accountId" class="flex flex-col items-center">
|
||||||
@ -264,29 +257,29 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 text-xs text-n-slate-10">
|
<div class="mt-4 text-xs text-n-slate-10">
|
||||||
Status: {{ statusMessage }}
|
{{ $t('JASMINE.WUZAPI.STATUS', { status: statusMessage }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="text-red-600 p-4">
|
<div v-else class="text-red-600 p-4">
|
||||||
Error: Account ID not loaded. Please refresh the page.
|
{{ $t('JASMINE.WUZAPI.ACCOUNT_ERROR') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8 pt-6 border-t border-n-weak w-full">
|
<div class="mt-8 pt-6 border-t border-n-weak w-full">
|
||||||
<h4 class="text-md font-medium text-n-slate-12 mb-4">
|
<h4 class="text-md font-medium text-n-slate-12 mb-4">
|
||||||
Webhook Configuration
|
{{ $t('JASMINE.WUZAPI.WEBHOOK_SECTION') }}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="flex gap-4 mb-4">
|
<div class="flex gap-4 mb-4">
|
||||||
<NextButton
|
<NextButton
|
||||||
icon="i-woot-refresh"
|
icon="i-woot-refresh"
|
||||||
:is-loading="isLoadingWebhook"
|
:is-loading="isLoadingWebhook"
|
||||||
label="Get Webhook Info"
|
:label="$t('JASMINE.WUZAPI.GET_WEBHOOK_INFO')"
|
||||||
@click="fetchWebhookInfo"
|
@click="fetchWebhookInfo"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<NextButton
|
||||||
icon="i-woot-upload"
|
icon="i-woot-upload"
|
||||||
:is-loading="isLoadingWebhook"
|
:is-loading="isLoadingWebhook"
|
||||||
label="Update Webhook Connection"
|
:label="$t('JASMINE.WUZAPI.UPDATE_WEBHOOK')"
|
||||||
@click="updateWebhook"
|
@click="updateWebhook"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -101,11 +101,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async deleteDocument(collectionId, documentId) {
|
async deleteDocument(collectionId, documentId) {
|
||||||
if (!confirm('Are you sure you want to delete this document?')) return;
|
// eslint-disable-next-line no-alert
|
||||||
|
if (!window.confirm(this.$t('JASMINE.KNOWLEDGE_BASE.DELETE_CONFIRM')))
|
||||||
|
return;
|
||||||
this.isDeletingDocument = documentId;
|
this.isDeletingDocument = documentId;
|
||||||
try {
|
try {
|
||||||
await JasmineAPI.deleteDocument(collectionId, documentId);
|
await JasmineAPI.deleteDocument(collectionId, documentId);
|
||||||
useAlert('Document deleted successfully');
|
useAlert(this.$t('JASMINE.KNOWLEDGE_BASE.DOCUMENT_DELETE_SUCCESS'));
|
||||||
this.fetchDocuments(collectionId);
|
this.fetchDocuments(collectionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
useAlert('Failed to delete document');
|
useAlert('Failed to delete document');
|
||||||
@ -143,14 +145,14 @@ export default {
|
|||||||
<div class="flex justify-between items-center mb-6">
|
<div class="flex justify-between items-center mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100">
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100">
|
||||||
Knowledge Base
|
{{ $t('JASMINE.KNOWLEDGE_BASE.TITLE') }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||||
Manage knowledge collections for this inbox
|
{{ $t('JASMINE.KNOWLEDGE_BASE.DESCRIPTION') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<woot-button size="small" @click="showCreateCollectionModal = true">
|
<woot-button size="small" @click="showCreateCollectionModal = true">
|
||||||
+ New Collection
|
{{ $t('JASMINE.KNOWLEDGE_BASE.ADD_BUTTON') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -173,8 +175,8 @@ export default {
|
|||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span
|
<span
|
||||||
|
class="i-lucide-chevron-right size-4 transition-transform text-slate-400"
|
||||||
:class="[
|
:class="[
|
||||||
'i-lucide-chevron-right size-4 transition-transform text-slate-400',
|
|
||||||
expandedCollectionId === collection.id ? 'rotate-90' : '',
|
expandedCollectionId === collection.id ? 'rotate-90' : '',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
@ -197,7 +199,7 @@ export default {
|
|||||||
class="border-t border-slate-100 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/30 p-4"
|
class="border-t border-slate-100 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/30 p-4"
|
||||||
>
|
>
|
||||||
<h5 class="text-xs font-semibold uppercase text-slate-500 mb-3">
|
<h5 class="text-xs font-semibold uppercase text-slate-500 mb-3">
|
||||||
Documents
|
{{ $t('JASMINE.KNOWLEDGE_BASE.DOCUMENTS') }}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<!-- Loading Documents -->
|
<!-- Loading Documents -->
|
||||||
@ -206,7 +208,7 @@ export default {
|
|||||||
class="flex items-center gap-2 text-sm text-slate-400 py-2"
|
class="flex items-center gap-2 text-sm text-slate-400 py-2"
|
||||||
>
|
>
|
||||||
<span class="i-lucide-loader-2 size-4 animate-spin" />
|
<span class="i-lucide-loader-2 size-4 animate-spin" />
|
||||||
Loading documents...
|
{{ $t('JASMINE.KNOWLEDGE_BASE.LOADING_DOCS') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Documents List -->
|
<!-- Documents List -->
|
||||||
@ -224,7 +226,7 @@ export default {
|
|||||||
<p
|
<p
|
||||||
class="font-medium text-sm text-slate-800 dark:text-slate-200 truncate"
|
class="font-medium text-sm text-slate-800 dark:text-slate-200 truncate"
|
||||||
>
|
>
|
||||||
{{ doc.title || 'Untitled Document' }}
|
{{ doc.title || $t('JASMINE.KNOWLEDGE_BASE.UNTITLED_DOC') }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-slate-400 truncate">
|
<p class="text-xs text-slate-400 truncate">
|
||||||
{{ new Date(doc.created_at).toLocaleDateString() }}
|
{{ new Date(doc.created_at).toLocaleDateString() }}
|
||||||
@ -234,10 +236,8 @@ export default {
|
|||||||
<div class="flex items-center gap-3 shrink-0">
|
<div class="flex items-center gap-3 shrink-0">
|
||||||
<!-- Status Badge -->
|
<!-- Status Badge -->
|
||||||
<span
|
<span
|
||||||
:class="[
|
class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded-full"
|
||||||
'inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded-full',
|
:class="[getStatusClass(doc.status)]"
|
||||||
getStatusClass(doc.status),
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="isProcessing(doc.status)"
|
v-if="isProcessing(doc.status)"
|
||||||
@ -264,7 +264,7 @@ export default {
|
|||||||
v-if="documents.length === 0"
|
v-if="documents.length === 0"
|
||||||
class="text-center py-6 text-sm text-slate-400"
|
class="text-center py-6 text-sm text-slate-400"
|
||||||
>
|
>
|
||||||
No documents yet. Add your first document below.
|
{{ $t('JASMINE.KNOWLEDGE_BASE.NO_DOCS') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -273,19 +273,21 @@ export default {
|
|||||||
class="border-t border-slate-200 dark:border-slate-700 pt-4 mt-4"
|
class="border-t border-slate-200 dark:border-slate-700 pt-4 mt-4"
|
||||||
>
|
>
|
||||||
<h6 class="text-xs font-semibold uppercase text-slate-500 mb-3">
|
<h6 class="text-xs font-semibold uppercase text-slate-500 mb-3">
|
||||||
Add New Document
|
{{ $t('JASMINE.KNOWLEDGE_BASE.ADD_DOC_HEADER') }}
|
||||||
</h6>
|
</h6>
|
||||||
<input
|
<input
|
||||||
v-model="newDocTitle"
|
v-model="newDocTitle"
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full mb-2 px-3 py-2 text-sm rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900"
|
class="w-full mb-2 px-3 py-2 text-sm rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900"
|
||||||
placeholder="Document title (optional)"
|
:placeholder="$t('JASMINE.KNOWLEDGE_BASE.DOC_TITLE_PLACEHOLDER')"
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="newDocContent"
|
v-model="newDocContent"
|
||||||
rows="4"
|
rows="4"
|
||||||
class="w-full mb-3 px-3 py-2 text-sm rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 resize-none"
|
class="w-full mb-3 px-3 py-2 text-sm rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 resize-none"
|
||||||
placeholder="Paste or type your knowledge content here..."
|
:placeholder="
|
||||||
|
$t('JASMINE.KNOWLEDGE_BASE.DOC_CONTENT_PLACEHOLDER')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<woot-button
|
<woot-button
|
||||||
@ -294,7 +296,7 @@ export default {
|
|||||||
:disabled="!newDocContent.trim()"
|
:disabled="!newDocContent.trim()"
|
||||||
@click="addDocument(collection.id)"
|
@click="addDocument(collection.id)"
|
||||||
>
|
>
|
||||||
Add Document
|
{{ $t('JASMINE.KNOWLEDGE_BASE.ADD_DOC_BUTTON') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -307,7 +309,7 @@ export default {
|
|||||||
class="text-center py-12 text-slate-400"
|
class="text-center py-12 text-slate-400"
|
||||||
>
|
>
|
||||||
<span class="i-lucide-folder-open size-12 mx-auto mb-3 opacity-50" />
|
<span class="i-lucide-folder-open size-12 mx-auto mb-3 opacity-50" />
|
||||||
<p class="text-sm">No collections yet. Create one to get started.</p>
|
<p class="text-sm">{{ $t('JASMINE.KNOWLEDGE_BASE.NO_COLLECTIONS') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -319,34 +321,40 @@ export default {
|
|||||||
>
|
>
|
||||||
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl w-96 shadow-2xl">
|
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl w-96 shadow-2xl">
|
||||||
<h3 class="text-lg font-semibold mb-4 text-slate-900 dark:text-white">
|
<h3 class="text-lg font-semibold mb-4 text-slate-900 dark:text-white">
|
||||||
Create Collection
|
{{ $t('JASMINE.KNOWLEDGE_BASE.CREATE_MODAL.TITLE') }}
|
||||||
</h3>
|
</h3>
|
||||||
<input
|
<input
|
||||||
v-model="newCollectionName"
|
v-model="newCollectionName"
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full mb-4 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800"
|
class="w-full mb-4 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800"
|
||||||
placeholder="Collection name"
|
:placeholder="
|
||||||
|
$t('JASMINE.KNOWLEDGE_BASE.CREATE_MODAL.NAME_PLACEHOLDER')
|
||||||
|
"
|
||||||
@keyup.enter="createCollection"
|
@keyup.enter="createCollection"
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
v-model="newCollectionVisibility"
|
v-model="newCollectionVisibility"
|
||||||
class="w-full mb-4 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800"
|
class="w-full mb-4 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800"
|
||||||
>
|
>
|
||||||
<option value="private">Private (This inbox only)</option>
|
<option value="private">
|
||||||
<option value="shared">Shared (All inboxes)</option>
|
{{ $t('JASMINE.KNOWLEDGE_BASE.CREATE_MODAL.VISIBILITY_PRIVATE') }}
|
||||||
|
</option>
|
||||||
|
<option value="shared">
|
||||||
|
{{ $t('JASMINE.KNOWLEDGE_BASE.CREATE_MODAL.VISIBILITY_SHARED') }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<woot-button
|
<woot-button
|
||||||
variant="clear"
|
variant="clear"
|
||||||
@click="showCreateCollectionModal = false"
|
@click="showCreateCollectionModal = false"
|
||||||
>
|
>
|
||||||
Cancel
|
{{ $t('JASMINE.KNOWLEDGE_BASE.CREATE_MODAL.CANCEL') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
<woot-button
|
<woot-button
|
||||||
:disabled="!newCollectionName.trim()"
|
:disabled="!newCollectionName.trim()"
|
||||||
@click="createCollection"
|
@click="createCollection"
|
||||||
>
|
>
|
||||||
Create
|
{{ $t('JASMINE.KNOWLEDGE_BASE.CREATE_MODAL.CREATE') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -77,7 +77,7 @@ class Inbox < ApplicationRecord
|
|||||||
has_one :agent_bot, through: :agent_bot_inbox
|
has_one :agent_bot, through: :agent_bot_inbox
|
||||||
has_many :webhooks, dependent: :destroy_async
|
has_many :webhooks, dependent: :destroy_async
|
||||||
has_many :hooks, dependent: :destroy_async, class_name: 'Integrations::Hook'
|
has_many :hooks, dependent: :destroy_async, class_name: 'Integrations::Hook'
|
||||||
has_many :inbox_automations, dependent: :destroy_async, class_name: 'Captain::InboxAutomation'
|
has_many :inbox_automations, dependent: :destroy_async, class_name: '::Captain::InboxAutomation'
|
||||||
|
|
||||||
# Jasmine
|
# Jasmine
|
||||||
has_one :jasmine_inbox_config, class_name: 'Jasmine::InboxConfig', dependent: :destroy
|
has_one :jasmine_inbox_config, class_name: 'Jasmine::InboxConfig', dependent: :destroy
|
||||||
|
|||||||
@ -12,7 +12,8 @@ Rails.application.config.to_prepare do
|
|||||||
|
|
||||||
def ensure_content_presence_defensive
|
def ensure_content_presence_defensive
|
||||||
# If content is present, or we have attachments, we are good.
|
# If content is present, or we have attachments, we are good.
|
||||||
return if content.present? || attachments.any?
|
# We check .any? and .size to be robust against unsaved attachments in some contexts.
|
||||||
|
return if content.present? || attachments.any? || attachments.size > 0 || attachments.to_a.any?
|
||||||
|
|
||||||
# Identifica a origem para um fallback mais inteligente
|
# Identifica a origem para um fallback mais inteligente
|
||||||
if incoming?
|
if incoming?
|
||||||
|
|||||||
@ -5,9 +5,9 @@ module Enterprise::Concerns::Inbox
|
|||||||
has_one :captain_inbox, dependent: :destroy, class_name: 'CaptainInbox'
|
has_one :captain_inbox, dependent: :destroy, class_name: 'CaptainInbox'
|
||||||
has_one :captain_assistant,
|
has_one :captain_assistant,
|
||||||
through: :captain_inbox,
|
through: :captain_inbox,
|
||||||
class_name: 'Captain::Assistant'
|
class_name: '::Captain::Assistant'
|
||||||
has_one :captain_inbox_reminder_setting, dependent: :destroy, class_name: 'Captain::InboxReminderSetting'
|
has_one :captain_inbox_reminder_setting, dependent: :destroy, class_name: '::Captain::InboxReminderSetting'
|
||||||
has_many :captain_inbox_automations, dependent: :destroy, class_name: 'Captain::InboxAutomation'
|
has_many :captain_inbox_automations, dependent: :destroy, class_name: '::Captain::InboxAutomation'
|
||||||
has_many :inbox_capacity_limits, dependent: :destroy
|
has_many :inbox_capacity_limits, dependent: :destroy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
1
eslint_report.json
Normal file
1
eslint_report.json
Normal file
File diff suppressed because one or more lines are too long
1
eslint_report_v2.json
Normal file
1
eslint_report_v2.json
Normal file
File diff suppressed because one or more lines are too long
38
progresso/2025-05-lint-global-fix.md
Normal file
38
progresso/2025-05-lint-global-fix.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Nota de Resolução: Correção Global de Lint e Qualidade (Maio 2025)
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Resolver mais de 100 erros de linting (Frontend e Backend) e estabelecer padrões de qualidade para os módulos Jasmine e Wuzapi.
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
O projeto apresentava débitos técnicos acumulados nas novas rotas de IA (Jasmine), incluindo falta de internacionalização, erros de sintaxe no Vue (hoisting/circular dependencies) e riscos de segurança no backend Ruby.
|
||||||
|
|
||||||
|
## Passos Realizados
|
||||||
|
|
||||||
|
1. **Frontend (Vue/ESLint)**:
|
||||||
|
- Criação do sistema de i18n para Jasmine em `en/jasmine.json`.
|
||||||
|
- Refatoração completa de 5 componentes (`JasmineInboxes`, `JasminePlayground`, `JasmineConfiguration`, `JasmineKnowledgeBase`, `WuzapiConfiguration`).
|
||||||
|
- Resolução de erros de Prettier e sintaxe.
|
||||||
|
2. **Backend (Ruby/RuboCop)**:
|
||||||
|
- Criação de `.rubocop_todo.yml` para congelar débitos antigos.
|
||||||
|
- Refatoração de controllers e remoção de credenciais hardcoded.
|
||||||
|
- Identificação de vulnerabilidade crítica de SSL em `lib/wuzapi/client.rb`.
|
||||||
|
|
||||||
|
## Arquivos Principais Alterados
|
||||||
|
|
||||||
|
- `app/javascript/dashboard/i18n/locale/en/jasmine.json` (Novo sistema i18n)
|
||||||
|
- `app/javascript/dashboard/routes/dashboard/settings/inbox/channels/wuzapi/WuzapiConfiguration.vue` (Fix Hosting)
|
||||||
|
- `lib/wuzapi/client.rb` (Identificado risco SSL)
|
||||||
|
- `.rubocop_todo.yml` (Gestão de débito técnico)
|
||||||
|
|
||||||
|
## Variáveis de Ambiente
|
||||||
|
|
||||||
|
- `DEFAULT_JASMINE_DISTANCE_THRESHOLD`: Configura limite de busca RAG (Padrão: 0.35).
|
||||||
|
- `JASMINE_LLM_MODEL`: Define modelo de IA (Padrão: gpt-4o-mini).
|
||||||
|
|
||||||
|
## Como Validar ou Reverter
|
||||||
|
|
||||||
|
- **Validar Frontend**: Executar `npx eslint --ext .js,.vue [arquivos]`. Resultado esperado: 0 erros.
|
||||||
|
- **Validar Backend**: Executar `bundle exec rubocop`. Resultado esperado: Sucesso (via todo).
|
||||||
|
- **Reverter**: `git checkout` nos arquivos mencionados.
|
||||||
@ -129,7 +129,7 @@ has been assigned to you"
|
|||||||
|
|
||||||
it 'returns appropriate body suited for the notification type assigned_conversation_new_message when attachment message' do
|
it 'returns appropriate body suited for the notification type assigned_conversation_new_message when attachment message' do
|
||||||
conversation = create(:conversation)
|
conversation = create(:conversation)
|
||||||
message = create(:message, sender: create(:user), content: nil, conversation: conversation)
|
message = build(:message, sender: create(:user), content: nil, conversation: conversation)
|
||||||
attachment = message.attachments.new(file_type: :image, account_id: message.account_id)
|
attachment = message.attachments.new(file_type: :image, account_id: message.account_id)
|
||||||
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||||
message.save!
|
message.save!
|
||||||
|
|||||||
@ -41,7 +41,7 @@ end
|
|||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.include FactoryBot::Syntax::Methods
|
config.include FactoryBot::Syntax::Methods
|
||||||
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
||||||
config.fixture_path = Rails.root.join('spec/fixtures')
|
config.fixture_paths = [Rails.root.join('spec/fixtures')]
|
||||||
|
|
||||||
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
||||||
# examples within a transaction, remove the following line or assign false
|
# examples within a transaction, remove the following line or assign false
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user