write message with ai + summerize

This commit is contained in:
hamid zarghami
2025-07-26 14:36:04 +03:30
parent 28911317cd
commit d324935836
8 changed files with 108 additions and 30 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ VITE_SOCKET_URL = 'wss://dmail-api.danakcorp.com/email'
# VITE_SOCKET_URL = 'ws://192.168.1.118:4000/email' # VITE_SOCKET_URL = 'ws://192.168.1.118:4000/email'
VITE_BASE_URL = 'https://dmail-api.danakcorp.com' VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
# VITE_BASE_URL = 'http://192.168.1.118:4000' # VITE_BASE_URL = 'http://192.168.1.101:4000'
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e' VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
+2 -1
View File
@@ -5,7 +5,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script>var s = document.createElement("script"); s.src = "https://van.najva.com/static/js/main-script.js"; s.defer = !0; s.id = "najva-mini-script"; s.setAttribute("data-najva-id", "7b4774d2-ef79-4341-8632-96556f8b0db5"); document.head.appendChild(s);</script> <script>var s = document.createElement("script"); s.src = "https://van.najva.com/static/js/main-script.js"; s.defer = !0; s.id = "najva-mini-script"; s.setAttribute("data-najva-id", "9c582bcc-1a29-490c-b0e8-70ab404ad258"); document.head.appendChild(s);</script>
<title>DMail | داناک</title> <title>DMail | داناک</title>
</head> </head>
+46 -7
View File
@@ -9,6 +9,8 @@ import EmailAttachments from './EmailAttachments'
import EmailFormActions from './EmailFormActions' import EmailFormActions from './EmailFormActions'
import { useNewMessage } from './hooks/useNewMessage' import { useNewMessage } from './hooks/useNewMessage'
import Intelligense from '@/assets/images/intelligense.svg' import Intelligense from '@/assets/images/intelligense.svg'
import { useGenerateEmail } from '@/pages/received/hooks/useEmailData'
import { clx } from '@/helpers/utils'
const NewMessage: FC = () => { const NewMessage: FC = () => {
const { t } = useTranslation('global') const { t } = useTranslation('global')
@@ -16,6 +18,8 @@ const NewMessage: FC = () => {
const [isClosing, setIsClosing] = useState(false) const [isClosing, setIsClosing] = useState(false)
const [shouldRender, setShouldRender] = useState(false) const [shouldRender, setShouldRender] = useState(false)
const [isOpening, setIsOpening] = useState(false) const [isOpening, setIsOpening] = useState(false)
const [isIntelligenseOpen, setIsIntelligenseOpen] = useState(false)
const [prompt, setPrompt] = useState('')
const { const {
// Form state // Form state
@@ -46,6 +50,7 @@ const NewMessage: FC = () => {
handleSaveDraft, handleSaveDraft,
resetForm resetForm
} = useNewMessage() } = useNewMessage()
const { mutate: generateEmail, isPending: isGeneratingEmail } = useGenerateEmail()
useEffect(() => { useEffect(() => {
if (openNewMessage) { if (openNewMessage) {
@@ -87,6 +92,21 @@ const NewMessage: FC = () => {
} }
} }
const handlePrompt = () => {
if (isIntelligenseOpen && prompt.length > 0) {
generateEmail({ prompt }, {
onSuccess: (data) => {
setContent(data?.data?.assistance?.suggestion?.content)
if (subject === '') {
setSubject(data?.data?.assistance?.suggestion?.subject)
}
}
})
} else {
setIsIntelligenseOpen(!isIntelligenseOpen)
}
}
return ( return (
<> <>
{shouldRender && ( {shouldRender && (
@@ -126,17 +146,36 @@ const NewMessage: FC = () => {
</div> </div>
<div className='flex gap-2 items-end'> <div className='flex gap-2 items-end'>
<Input <div className='flex flex-1 items-end gap-2'>
label={t('new_message.subject')} <Input
value={subject} label={t('new_message.subject')}
onChange={(e) => setSubject(e.target.value)} value={subject}
/> onChange={(e) => setSubject(e.target.value)}
className='w-full'
/>
<div className='border border-border rounded-xl h-10 px-2 flex items-center justify-center'> {
<img src={Intelligense} alt='intelligense' className='w-6' /> isIntelligenseOpen && (
<Input
placeholder={t('new_message.prompt')}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
className='w-full'
/>
)
}
</div>
<div onClick={isGeneratingEmail ? undefined : handlePrompt} className='border border-border rounded-xl h-10 px-2 flex items-center justify-center'>
<img src={Intelligense} alt='intelligense' className={clx(
'w-6',
isGeneratingEmail && 'animate-spin spin-in'
)} />
</div> </div>
</div> </div>
<div> <div>
<EmailEditor <EmailEditor
value={content} value={content}
+2 -1
View File
@@ -159,7 +159,8 @@
"recipients_placeholder": "نام گیرنده", "recipients_placeholder": "نام گیرنده",
"subject_placeholder": "موضوع پیام", "subject_placeholder": "موضوع پیام",
"message_placeholder": "متن پیام", "message_placeholder": "متن پیام",
"select_priority": "انتخاب اولویت" "select_priority": "انتخاب اولویت",
"prompt": "پرامپت"
}, },
"mail": { "mail": {
"download": "دانلود", "download": "دانلود",
+40 -19
View File
@@ -4,25 +4,26 @@ import DefaulModal from '@/components/DefaulModal';
import Button from '@/components/Button'; import Button from '@/components/Button';
import { InboxMessage } from '@/pages/draft/types/DraftTypes'; import { InboxMessage } from '@/pages/draft/types/DraftTypes';
import Intelligense from '@/assets/images/intelligense.svg' import Intelligense from '@/assets/images/intelligense.svg'
import Skeleton from 'react-loading-skeleton'
import { useNavigate } from 'react-router-dom'
const Summerize: FC<{ id: string, mailbox: string, item: InboxMessage }> = ({ id, mailbox, item }) => { const Summerize: FC<{ id: string, mailbox: string, item: InboxMessage }> = ({ id, mailbox, item }) => {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { mutate: summarizeEmail } = useSummarizeEmail(); const { mutate: summarizeEmail, isPending } = useSummarizeEmail();
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const [data, setData] = useState<any>() const [data, setData] = useState<any>()
const navigate = useNavigate()
const handleSummarizeEmail = () => { const handleSummarizeEmail = () => {
setOpen(true)
summarizeEmail({ messageId: Number(id), mailbox }, { summarizeEmail({ messageId: Number(id), mailbox }, {
onSuccess: (data) => { onSuccess: (data) => {
setData(data) setData(data)
} }
}); });
setOpen(true)
}; };
console.log('data', data);
return ( return (
<div> <div>
@@ -38,21 +39,43 @@ const Summerize: FC<{ id: string, mailbox: string, item: InboxMessage }> = ({ id
title_header='پیش نمایش ایمیل' title_header='پیش نمایش ایمیل'
> >
<div className='mt-5'> <div className='mt-5'>
<div className='text-xs'> {isPending ? (
موضوع: <div>
</div> <div className='text-xs'>
<div className='mt-1.5 text-xs'> موضوع:
{item.subject} </div>
</div> <div className='mt-1.5'>
<Skeleton height={16} />
</div>
<div className='mt-7'> <div className='mt-7'>
<div className='text-xs'> <div className='text-xs'>
خلاصه متن: خلاصه متن:
</div>
<div className='mt-3'>
<Skeleton height={16} count={4} />
</div>
</div>
</div> </div>
<div className='mt-3 leading-5 text-xs text-description'> ) : (
{data?.data?.summary} <div>
<div className='text-xs'>
موضوع:
</div>
<div className='mt-1.5 text-xs'>
{item.subject}
</div>
<div className='mt-7'>
<div className='text-xs'>
خلاصه متن:
</div>
<div className='mt-3 leading-5 text-xs text-description'>
{data?.data?.summary}
</div>
</div>
</div> </div>
</div> )}
<div className='mt-14 flex justify-end'> <div className='mt-14 flex justify-end'>
<div className='flex gap-3'> <div className='flex gap-3'>
@@ -65,9 +88,7 @@ const Summerize: FC<{ id: string, mailbox: string, item: InboxMessage }> = ({ id
<Button <Button
label='مشاهده' label='مشاهده'
className='w-fit !px-10' className='w-fit !px-10'
onClick={() => { onClick={() => navigate(`/mail/${id}/${mailbox}`)}
}}
/> />
</div> </div>
</div> </div>
+7 -1
View File
@@ -1,6 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as api from '../service/EmailService'; import * as api from '../service/EmailService';
import { SendEmailDto, MessageListQueryDto, SingleActionRequest, SummarizeEmailRequest } from '../types/Types'; import { SendEmailDto, MessageListQueryDto, SingleActionRequest, SummarizeEmailRequest, GenerateEmailRequest } from '../types/Types';
export const useSendEmail = () => { export const useSendEmail = () => {
return useMutation({ return useMutation({
@@ -124,4 +124,10 @@ export const useSummarizeEmail = () => {
return useMutation({ return useMutation({
mutationFn: (params: SummarizeEmailRequest) => api.summarizeEmail(params), mutationFn: (params: SummarizeEmailRequest) => api.summarizeEmail(params),
}); });
};
export const useGenerateEmail = () => {
return useMutation({
mutationFn: (params: GenerateEmailRequest) => api.generateEmail(params),
});
}; };
@@ -7,6 +7,7 @@ import {
MessageDetailResponse, MessageDetailResponse,
MessageDetail, MessageDetail,
SummarizeEmailRequest, SummarizeEmailRequest,
GenerateEmailRequest,
} from "../types/Types"; } from "../types/Types";
export const sendEmail = async (params: SendEmailDto) => { export const sendEmail = async (params: SendEmailDto) => {
@@ -14,6 +15,11 @@ export const sendEmail = async (params: SendEmailDto) => {
return data; return data;
}; };
export const generateEmail = async (params: GenerateEmailRequest) => {
const { data } = await axios.post(`/ai-assistant/write-email`, params);
return data;
};
export const saveDraft = async (params: SendEmailDto) => { export const saveDraft = async (params: SendEmailDto) => {
const { data } = await axios.post(`/email/messages/drafts`, params); const { data } = await axios.post(`/email/messages/drafts`, params);
return data; return data;
+4
View File
@@ -321,3 +321,7 @@ export interface SummarizeEmailRequest {
length?: "medium" | "short" | "long"; length?: "medium" | "short" | "long";
tone?: "professional" | "casual" | "formal"; tone?: "professional" | "casual" | "formal";
} }
export interface GenerateEmailRequest {
prompt: string;
}