2026-09-01 16:05:29 +03:30
|
|
|
|
<script setup lang="ts">
|
2026-09-23 15:49:44 +03:30
|
|
|
|
import { ref } from 'vue';
|
2026-09-01 16:05:29 +03:30
|
|
|
|
import { RouterLink } from 'vue-router';
|
2026-09-23 15:49:44 +03:30
|
|
|
|
import axios from 'axios';
|
2026-09-01 16:05:29 +03:30
|
|
|
|
import UiIcon from '@/components/UiIcon.vue';
|
2026-09-23 15:49:44 +03:30
|
|
|
|
import VoiceToTextButton from '@/components/VoiceToTextButton.vue';
|
|
|
|
|
|
import { api } from '@/lib/api';
|
|
|
|
|
|
import { brand, type ActivityType, type AskMentoSourceDto, type PersonDto, type VoiceCommandDraft } from '@mento/shared';
|
2026-09-01 16:05:29 +03:30
|
|
|
|
|
2026-09-23 15:49:44 +03:30
|
|
|
|
const props = defineProps<{
|
2026-09-01 16:05:29 +03:30
|
|
|
|
askQuery: string;
|
2026-09-03 14:20:55 +03:30
|
|
|
|
asking?: boolean;
|
|
|
|
|
|
answer?: string;
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
sources?: AskMentoSourceDto[];
|
2026-09-01 16:05:29 +03:30
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
'update:askQuery': [value: string];
|
2026-09-03 14:20:55 +03:30
|
|
|
|
ask: [preset?: string];
|
2026-09-01 16:05:29 +03:30
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
const shortcuts = [
|
2026-09-03 14:20:55 +03:30
|
|
|
|
{ label: 'معاملات باز و در خطر را بگو', icon: 'warning' as const },
|
|
|
|
|
|
{ label: 'فعالیتهای عقبافتاده چیست؟', icon: 'calendar' as const },
|
|
|
|
|
|
{ label: 'مهمترین پیگیری امروز چیست؟', icon: 'send' as const },
|
|
|
|
|
|
{ label: 'خلاصه وضعیت فروش را بده', icon: 'chart' as const },
|
2026-09-01 16:05:29 +03:30
|
|
|
|
];
|
2026-09-03 14:20:55 +03:30
|
|
|
|
|
|
|
|
|
|
function sourceTo(source: AskMentoSourceDto) {
|
|
|
|
|
|
if (source.type === 'person') return { name: 'person-detail', params: { id: source.id } };
|
|
|
|
|
|
if (source.type === 'activity') return { name: 'activity-detail', params: { id: source.id } };
|
|
|
|
|
|
if (source.type === 'deal') return { name: 'deal-detail', params: { id: source.id } };
|
|
|
|
|
|
return { name: 'notes' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pick(label: string) {
|
|
|
|
|
|
emit('update:askQuery', label);
|
|
|
|
|
|
emit('ask', label);
|
|
|
|
|
|
}
|
2026-09-23 15:49:44 +03:30
|
|
|
|
|
|
|
|
|
|
const ACTIVITY_LABEL: Record<ActivityType, string> = {
|
|
|
|
|
|
call: 'تماس',
|
|
|
|
|
|
meeting: 'جلسه',
|
|
|
|
|
|
task: 'کار',
|
|
|
|
|
|
note: 'یادداشت',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const voiceBuf = ref('');
|
|
|
|
|
|
const voiceBusy = ref(false);
|
|
|
|
|
|
const voiceError = ref('');
|
|
|
|
|
|
const voiceDone = ref('');
|
|
|
|
|
|
const draft = ref<VoiceCommandDraft | null>(null);
|
|
|
|
|
|
const confirming = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
function apiMessage(e: unknown, fallback: string) {
|
|
|
|
|
|
if (!axios.isAxiosError(e)) return fallback;
|
|
|
|
|
|
const msg = (e.response?.data as { message?: string | string[] })?.message;
|
|
|
|
|
|
return Array.isArray(msg) ? msg.join('، ') : (msg ?? fallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onVoiceTranscript(text: string) {
|
|
|
|
|
|
voiceBuf.value = `${voiceBuf.value} ${text}`.trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function looksLikeAction(text: string) {
|
|
|
|
|
|
return /بساز|بسازید|ثبت کن|ثبت کنید|اضافه کن|ایجاد کن|یک مشتری|مشتری جدید|یک فعالیت|فعالیت جدید/.test(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function parseCommand(text: string, fallbackToAsk: boolean) {
|
|
|
|
|
|
voiceBusy.value = true;
|
|
|
|
|
|
voiceError.value = '';
|
|
|
|
|
|
voiceDone.value = '';
|
|
|
|
|
|
draft.value = null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.post<VoiceCommandDraft>('/ai/voice-command', { text });
|
|
|
|
|
|
if (data.intent === 'none') {
|
|
|
|
|
|
if (fallbackToAsk) {
|
|
|
|
|
|
emit('ask', text);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
voiceError.value = data.summary;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
draft.value = data;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
voiceError.value = apiMessage(e, 'فهمیدن دستور ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
voiceBusy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function onVoiceRecording(active: boolean) {
|
|
|
|
|
|
if (active) {
|
|
|
|
|
|
voiceBuf.value = '';
|
|
|
|
|
|
voiceError.value = '';
|
|
|
|
|
|
voiceDone.value = '';
|
|
|
|
|
|
draft.value = null;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const text = voiceBuf.value.trim();
|
|
|
|
|
|
voiceBuf.value = '';
|
|
|
|
|
|
if (!text) return;
|
|
|
|
|
|
emit('update:askQuery', text);
|
|
|
|
|
|
await parseCommand(text, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function onTypedSubmit() {
|
|
|
|
|
|
const text = props.askQuery.trim();
|
|
|
|
|
|
if (!text || props.asking || voiceBusy.value || confirming.value) return;
|
|
|
|
|
|
if (looksLikeAction(text)) {
|
|
|
|
|
|
await parseCommand(text, true);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
emit('ask');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function confirmDraft() {
|
|
|
|
|
|
const current = draft.value;
|
|
|
|
|
|
if (!current?.canConfirm || confirming.value) return;
|
|
|
|
|
|
confirming.value = true;
|
|
|
|
|
|
voiceError.value = '';
|
|
|
|
|
|
voiceDone.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (current.intent === 'create_person' && current.person) {
|
|
|
|
|
|
const p = current.person;
|
|
|
|
|
|
const { data } = await api.post<PersonDto>('/people', {
|
|
|
|
|
|
firstName: p.firstName,
|
|
|
|
|
|
lastName: p.lastName || undefined,
|
|
|
|
|
|
mobile: p.mobile || undefined,
|
|
|
|
|
|
phone: p.phone || undefined,
|
|
|
|
|
|
email: p.email || undefined,
|
|
|
|
|
|
website: p.website || undefined,
|
|
|
|
|
|
referrer: p.referrer || undefined,
|
|
|
|
|
|
position: p.position || undefined,
|
|
|
|
|
|
notes: p.notes || undefined,
|
|
|
|
|
|
});
|
|
|
|
|
|
voiceDone.value = `مشتری «${data.fullName}» ساخته شد.`;
|
|
|
|
|
|
} else if (current.intent === 'create_activity' && current.activity) {
|
|
|
|
|
|
const a = current.activity;
|
|
|
|
|
|
await api.post('/activities', {
|
|
|
|
|
|
title: a.title,
|
|
|
|
|
|
type: a.type,
|
|
|
|
|
|
description: a.description || undefined,
|
|
|
|
|
|
dueAt: a.dueAt,
|
|
|
|
|
|
personId: a.personId,
|
|
|
|
|
|
});
|
|
|
|
|
|
voiceDone.value = `فعالیت «${a.title}» ثبت شد.`;
|
|
|
|
|
|
}
|
|
|
|
|
|
draft.value = null;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
voiceError.value = apiMessage(e, 'انجام دستور ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
confirming.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function cancelDraft() {
|
|
|
|
|
|
draft.value = null;
|
|
|
|
|
|
}
|
2026-09-01 16:05:29 +03:30
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-09-21 16:58:49 +03:30
|
|
|
|
<article id="home-ai-assistant" class="panel">
|
2026-09-01 16:05:29 +03:30
|
|
|
|
<header class="panel-head">
|
|
|
|
|
|
<h2>
|
2026-09-02 16:06:17 +03:30
|
|
|
|
<span class="spark"><UiIcon name="sparkle" :size="15" /></span>
|
2026-09-07 11:49:54 +03:30
|
|
|
|
{{ brand.aiNameFa }}
|
2026-09-01 16:05:29 +03:30
|
|
|
|
</h2>
|
2026-09-23 15:49:44 +03:30
|
|
|
|
<VoiceToTextButton
|
|
|
|
|
|
variant="toolbar"
|
|
|
|
|
|
hide-error
|
|
|
|
|
|
:disabled="asking || voiceBusy || confirming"
|
|
|
|
|
|
@transcript="onVoiceTranscript"
|
|
|
|
|
|
@recording="onVoiceRecording"
|
|
|
|
|
|
@error="voiceError = $event"
|
|
|
|
|
|
/>
|
2026-09-01 16:05:29 +03:30
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<p class="ai-lead">چه کمکی میتونم بکنم؟</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="ai-shortcuts">
|
2026-09-03 14:20:55 +03:30
|
|
|
|
<button
|
|
|
|
|
|
v-for="item in shortcuts"
|
|
|
|
|
|
:key="item.label"
|
|
|
|
|
|
class="ai-chip"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="asking"
|
|
|
|
|
|
@click="pick(item.label)"
|
|
|
|
|
|
>
|
2026-09-02 16:06:17 +03:30
|
|
|
|
<span class="chip-ico"><UiIcon :name="item.icon" :size="14" /></span>
|
2026-09-01 16:05:29 +03:30
|
|
|
|
<span>{{ item.label }}</span>
|
2026-09-03 14:20:55 +03:30
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-23 15:49:44 +03:30
|
|
|
|
<div v-if="voiceBusy" class="ai-answer"><p>در حال فهمیدن دستور…</p></div>
|
|
|
|
|
|
<div v-else-if="draft && draft.intent !== 'none'" class="confirm">
|
|
|
|
|
|
<p>{{ draft.summary }}</p>
|
|
|
|
|
|
<ul v-if="draft.person">
|
|
|
|
|
|
<li>نام: {{ draft.person.firstName }} {{ draft.person.lastName }}</li>
|
|
|
|
|
|
<li v-if="draft.person.mobile">موبایل: {{ draft.person.mobile }}</li>
|
|
|
|
|
|
<li v-if="draft.person.phone">تلفن: {{ draft.person.phone }}</li>
|
|
|
|
|
|
<li v-if="draft.person.email">ایمیل: {{ draft.person.email }}</li>
|
|
|
|
|
|
<li v-if="draft.person.website">وبسایت: {{ draft.person.website }}</li>
|
|
|
|
|
|
<li v-if="draft.person.referrer">معرف: {{ draft.person.referrer }}</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<ul v-else-if="draft.activity">
|
|
|
|
|
|
<li>عنوان: {{ draft.activity.title }}</li>
|
|
|
|
|
|
<li>نوع: {{ ACTIVITY_LABEL[draft.activity.type] }}</li>
|
|
|
|
|
|
<li v-if="draft.activity.personName">
|
|
|
|
|
|
مشتری: {{ draft.activity.personName }}
|
|
|
|
|
|
<span v-if="!draft.activity.personId"> (لینک نشد)</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<p v-if="draft.missing.length" class="ai-error">ناقص است: {{ draft.missing.join('، ') }}</p>
|
|
|
|
|
|
<div class="confirm-actions">
|
|
|
|
|
|
<button type="button" class="ok" :disabled="!draft.canConfirm || confirming" @click="confirmDraft">
|
|
|
|
|
|
{{ confirming ? 'در حال ثبت…' : 'تأیید و انجام' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button type="button" class="no" :disabled="confirming" @click="cancelDraft">انصراف</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="voiceDone" class="ai-answer"><p>{{ voiceDone }}</p></div>
|
|
|
|
|
|
<p v-if="voiceError" class="ai-error">{{ voiceError }}</p>
|
2026-09-03 14:20:55 +03:30
|
|
|
|
<p v-if="error" class="ai-error">{{ error }}</p>
|
2026-09-23 15:49:44 +03:30
|
|
|
|
<div v-if="asking" class="ai-answer">
|
2026-09-21 16:58:49 +03:30
|
|
|
|
<p>در حال پاسخ…</p>
|
|
|
|
|
|
</div>
|
2026-09-03 14:20:55 +03:30
|
|
|
|
<div v-else-if="answer" class="ai-answer">
|
|
|
|
|
|
<p>{{ answer }}</p>
|
|
|
|
|
|
<div v-if="sources?.length" class="sources">
|
|
|
|
|
|
<RouterLink
|
|
|
|
|
|
v-for="source in sources"
|
|
|
|
|
|
:key="`${source.type}-${source.id}`"
|
|
|
|
|
|
:to="sourceTo(source)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ source.label }}
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
</div>
|
2026-09-01 16:05:29 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-23 15:49:44 +03:30
|
|
|
|
<form class="ai-input" @submit.prevent="onTypedSubmit">
|
2026-09-01 16:05:29 +03:30
|
|
|
|
<input
|
|
|
|
|
|
:value="askQuery"
|
|
|
|
|
|
type="search"
|
2026-09-07 11:49:54 +03:30
|
|
|
|
:placeholder="brand.askPlaceholderFa"
|
2026-09-23 15:49:44 +03:30
|
|
|
|
:disabled="asking || voiceBusy || confirming"
|
2026-09-01 16:05:29 +03:30
|
|
|
|
@input="emit('update:askQuery', ($event.target as HTMLInputElement).value)"
|
|
|
|
|
|
/>
|
2026-09-23 15:49:44 +03:30
|
|
|
|
<button type="submit" aria-label="ارسال" :disabled="asking || voiceBusy || confirming">
|
2026-09-02 16:06:17 +03:30
|
|
|
|
<UiIcon name="send" :size="15" />
|
2026-09-01 16:05:29 +03:30
|
|
|
|
</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.panel {
|
|
|
|
|
|
background: #fff;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 16:06:17 +03:30
|
|
|
|
padding: 1rem 1rem 0.95rem;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
|
|
|
|
|
|
border: 1px solid #eef2f7;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-head {
|
|
|
|
|
|
margin-bottom: 0.55rem;
|
2026-09-23 15:49:44 +03:30
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm {
|
|
|
|
|
|
background: #fffbeb;
|
|
|
|
|
|
border: 1px solid #fde68a;
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 0.7rem 0.8rem;
|
|
|
|
|
|
margin-bottom: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm p,
|
|
|
|
|
|
.confirm li {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #78350f;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm ul {
|
|
|
|
|
|
margin: 0.35rem 0 0;
|
|
|
|
|
|
padding: 0 1rem 0 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
margin-top: 0.55rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-actions button {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 0.4rem 0.7rem;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-actions .ok {
|
|
|
|
|
|
background: #0f766e;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-actions .ok:disabled,
|
|
|
|
|
|
.confirm-actions .no:disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-actions .no {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: #64748b;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h2 {
|
|
|
|
|
|
margin: 0;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
font-size: 0.92rem;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
gap: 0.4rem;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 16:06:17 +03:30
|
|
|
|
.spark {
|
|
|
|
|
|
color: #eab308;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-01 16:05:29 +03:30
|
|
|
|
.ai-lead {
|
2026-09-02 16:06:17 +03:30
|
|
|
|
margin: 0 0 0.75rem;
|
|
|
|
|
|
padding: 0.55rem 0.75rem;
|
|
|
|
|
|
width: fit-content;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
background: #d1fae5;
|
|
|
|
|
|
color: #115e59;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
border-radius: 14px 14px 4px 14px;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-shortcuts {
|
|
|
|
|
|
display: grid;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
margin-bottom: 0.8rem;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-chip {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
gap: 0.55rem;
|
|
|
|
|
|
padding: 0.58rem 0.7rem;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 16:06:17 +03:30
|
|
|
|
background: #fff;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
border: 1px solid #eef2f7;
|
|
|
|
|
|
color: #334155;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
font-size: 0.76rem;
|
2026-09-03 14:20:55 +03:30
|
|
|
|
text-align: start;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
cursor: pointer;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
|
2026-09-01 16:05:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 14:20:55 +03:30
|
|
|
|
.ai-chip:hover:not(:disabled) {
|
2026-09-02 16:06:17 +03:30
|
|
|
|
background: #f0fdfa;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
border-color: #99f6e4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 14:20:55 +03:30
|
|
|
|
.ai-chip:disabled {
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
cursor: wait;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-01 16:05:29 +03:30
|
|
|
|
.chip-ico {
|
2026-09-02 16:06:17 +03:30
|
|
|
|
width: 26px;
|
|
|
|
|
|
height: 26px;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-01 16:05:29 +03:30
|
|
|
|
background: #ecfdf8;
|
|
|
|
|
|
color: #14b8a6;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-input {
|
|
|
|
|
|
display: flex;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
gap: 0.4rem;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
margin-top: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-input input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 16:06:17 +03:30
|
|
|
|
padding: 0.58rem 0.75rem;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
background: #f8fafc;
|
2026-09-02 16:06:17 +03:30
|
|
|
|
font-size: 0.8rem;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-input button {
|
2026-09-02 16:06:17 +03:30
|
|
|
|
width: 40px;
|
2026-09-01 16:05:29 +03:30
|
|
|
|
border: 0;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-01 16:05:29 +03:30
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
}
|
2026-09-03 14:20:55 +03:30
|
|
|
|
|
|
|
|
|
|
.ai-input button:disabled {
|
|
|
|
|
|
opacity: 0.55;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-answer {
|
|
|
|
|
|
background: #f0fdfa;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-03 14:20:55 +03:30
|
|
|
|
padding: 0.7rem 0.8rem;
|
|
|
|
|
|
margin-bottom: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-answer p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #134e4a;
|
|
|
|
|
|
font-size: 0.82rem;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sources {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
margin-top: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sources a {
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
padding: 0.15rem 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-error {
|
|
|
|
|
|
margin: 0 0 0.7rem;
|
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
}
|
2026-09-01 16:05:29 +03:30
|
|
|
|
</style>
|