Add Ask Mento, customer mood, and Pipecat speech-to-text.

Ship assistant answers, contact moods, voice dictation, and the related CRM UI so these can be deployed to production.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-03 14:20:55 +03:30
co-authored by Cursor
parent 0dd2966f3c
commit 7757c0837d
43 changed files with 5156 additions and 1741 deletions
+37 -7
View File
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
import { api } from '@/lib/api';
import { formatMoney } from '@/lib/money';
@@ -13,21 +12,27 @@ import HomeAiAssistant from '@/components/dashboard/HomeAiAssistant.vue';
import HomeRecentActivities from '@/components/dashboard/HomeRecentActivities.vue';
import HomeMiniPipeline from '@/components/dashboard/HomeMiniPipeline.vue';
import HomeTopCustomers from '@/components/dashboard/HomeTopCustomers.vue';
import axios from 'axios';
import type {
ActivityDto,
AskMentoResponse,
AskMentoSourceDto,
HomeDashboardDto,
PaginatedResponse,
PipelineBoardDto,
} from '@mento/shared';
const auth = useAuthStore();
const router = useRouter();
const loading = ref(true);
const home = ref<HomeDashboardDto | null>(null);
const activities = ref<ActivityDto[]>([]);
const board = ref<PipelineBoardDto | null>(null);
const askQuery = ref('');
const asking = ref(false);
const askAnswer = ref('');
const askError = ref('');
const askSources = ref<AskMentoSourceDto[]>([]);
const firstName = computed(() => {
const parts = auth.user?.fullName?.trim().split(/\s+/) ?? [];
@@ -72,10 +77,28 @@ function formatTrendAmount(value: number) {
return formatMoney(value, home.value?.currency ?? 'IRR');
}
function submitAsk() {
const q = askQuery.value.trim();
if (!q) return;
void router.push({ name: 'notes', query: { q } });
async function submitAsk(preset?: string) {
const q = (preset || askQuery.value).trim();
if (!q || asking.value) return;
if (preset) askQuery.value = preset;
asking.value = true;
askError.value = '';
askAnswer.value = '';
askSources.value = [];
try {
const { data } = await api.post<AskMentoResponse>('/ai/ask', { question: q });
askAnswer.value = data.answer;
askSources.value = data.sources ?? [];
} catch (e) {
if (axios.isAxiosError(e)) {
const msg = (e.response?.data as { message?: string | string[] })?.message;
askError.value = Array.isArray(msg) ? msg.join('، ') : (msg ?? 'پرسش ناموفق بود');
} else {
askError.value = 'پرسش ناموفق بود';
}
} finally {
asking.value = false;
}
}
onMounted(async () => {
@@ -128,7 +151,14 @@ onMounted(async () => {
:format-amount="formatTrendAmount"
/>
<HomeSourcesChart :sources="home?.sources ?? []" />
<HomeAiAssistant v-model:ask-query="askQuery" @ask="submitAsk" />
<HomeAiAssistant
v-model:ask-query="askQuery"
:asking="asking"
:answer="askAnswer"
:error="askError"
:sources="askSources"
@ask="submitAsk"
/>
</div>
<div class="bottom-grid">