feat: Add Ollama AI integration for automatic mail template generation
✨ New Features: - 🤖 AI-powered mail template generation with Ollama - 📧 Test mail sending with preview - 🔧 Ollama server and model management - 🎨 Beautiful AI generation dialog in Templates page - ⚙️ Ollama settings panel with connection test Backend: - Add ollama.service.js - Ollama API integration - Add ollama.controller.js - Template generation endpoint - Add ollama.routes.js - /api/ollama/* routes - Support for multiple Ollama models (llama3.2, mistral, gemma) - JSON-formatted AI responses with subject + HTML body - Configurable server URL and model selection Frontend: - Settings: Ollama configuration panel - Server URL input - Model selection - Connection test with model listing - Templates: AI generation dialog - Company name, scenario, employee info inputs - Custom prompt for AI instructions - Auto-save to database - Test mail sending functionality Documentation: - OLLAMA_SETUP.md - Comprehensive setup guide - Installation instructions - Model recommendations - Usage examples - Troubleshooting Tech Stack: - Ollama API integration (REST) - Axios HTTP client - React dialogs with MUI - Self-hosted AI (privacy-friendly) - Zero external API dependencies Example Usage: Company: Garanti Bankası Scenario: Account security warning → AI generates professional phishing test mail in seconds!
This commit is contained in:
@@ -28,9 +28,14 @@ import {
|
||||
Delete,
|
||||
Preview,
|
||||
ContentCopy,
|
||||
AutoAwesome,
|
||||
Send as SendIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { templateService } from '../services/templateService';
|
||||
import { format } from 'date-fns';
|
||||
import axios from 'axios';
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL;
|
||||
|
||||
const defaultForm = {
|
||||
name: '',
|
||||
@@ -54,6 +59,18 @@ function Templates() {
|
||||
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
||||
const [companyPlaceholder, setCompanyPlaceholder] = useState('Örnek Şirket');
|
||||
const [employeePlaceholder, setEmployeePlaceholder] = useState('Ahmet Yılmaz');
|
||||
const [aiDialogOpen, setAiDialogOpen] = useState(false);
|
||||
const [aiGenerating, setAiGenerating] = useState(false);
|
||||
const [aiForm, setAiForm] = useState({
|
||||
company_name: '',
|
||||
scenario: '',
|
||||
employee_info: '',
|
||||
custom_prompt: '',
|
||||
template_name: '',
|
||||
template_type: '',
|
||||
});
|
||||
const [testMailDialogOpen, setTestMailDialogOpen] = useState(false);
|
||||
const [testMailAddress, setTestMailAddress] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadTemplates();
|
||||
@@ -138,6 +155,69 @@ function Templates() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateWithAI = async () => {
|
||||
if (!aiForm.company_name || !aiForm.scenario || !aiForm.template_name || !aiForm.template_type) {
|
||||
alert('Lütfen tüm zorunlu alanları doldurun');
|
||||
return;
|
||||
}
|
||||
|
||||
setAiGenerating(true);
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${API_URL}/api/ollama/generate-template`,
|
||||
aiForm,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
|
||||
alert(response.data.message);
|
||||
setAiDialogOpen(false);
|
||||
setAiForm({
|
||||
company_name: '',
|
||||
scenario: '',
|
||||
employee_info: '',
|
||||
custom_prompt: '',
|
||||
template_name: '',
|
||||
template_type: '',
|
||||
});
|
||||
loadTemplates();
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.error || 'AI ile şablon oluşturulamadı';
|
||||
alert(message);
|
||||
console.error('AI generation failed:', error);
|
||||
} finally {
|
||||
setAiGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendTestMail = async () => {
|
||||
if (!testMailAddress || !selectedTemplate) {
|
||||
alert('Lütfen mail adresi girin');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(
|
||||
`${API_URL}/api/ollama/send-test-mail`,
|
||||
{
|
||||
test_email: testMailAddress,
|
||||
subject: selectedTemplate.subject_template,
|
||||
body: selectedTemplate.body_html,
|
||||
company_name: companyPlaceholder,
|
||||
employee_name: employeePlaceholder,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
);
|
||||
|
||||
alert('Test maili gönderildi!');
|
||||
setTestMailDialogOpen(false);
|
||||
setTestMailAddress('');
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.error || 'Test maili gönderilemedi';
|
||||
alert(message);
|
||||
console.error('Test mail failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePreview = async (htmlOverride, options = { openModal: false }) => {
|
||||
try {
|
||||
setPreviewLoading(true);
|
||||
@@ -196,9 +276,19 @@ function Templates() {
|
||||
<Box>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
|
||||
<Typography variant="h4">Mail Şablonları</Typography>
|
||||
<Button variant="contained" startIcon={<Add />} onClick={handleOpenCreate}>
|
||||
Yeni Şablon
|
||||
</Button>
|
||||
<Box display="flex" gap={2}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<AutoAwesome />}
|
||||
onClick={() => setAiDialogOpen(true)}
|
||||
color="secondary"
|
||||
>
|
||||
AI ile Oluştur
|
||||
</Button>
|
||||
<Button variant="contained" startIcon={<Add />} onClick={handleOpenCreate}>
|
||||
Yeni Şablon
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
@@ -273,6 +363,18 @@ function Templates() {
|
||||
>
|
||||
Önizleme
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
color="info"
|
||||
startIcon={<SendIcon />}
|
||||
onClick={() => {
|
||||
setSelectedTemplate(template);
|
||||
setTestMailDialogOpen(true);
|
||||
}}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
Test Mail
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={<Edit />}
|
||||
@@ -445,6 +547,153 @@ function Templates() {
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* AI Generation Dialog */}
|
||||
<Dialog open={aiDialogOpen} onClose={() => setAiDialogOpen(false)} maxWidth="md" fullWidth>
|
||||
<DialogTitle>🤖 AI ile Mail Şablonu Oluştur</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
Ollama AI kullanarak otomatik mail şablonu oluşturun. Aşağıdaki bilgileri girin:
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Şablon Adı"
|
||||
required
|
||||
value={aiForm.template_name}
|
||||
onChange={(e) => setAiForm({ ...aiForm, template_name: e.target.value })}
|
||||
helperText="Şablona verilecek isim"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Template Type"
|
||||
required
|
||||
value={aiForm.template_type}
|
||||
onChange={(e) => setAiForm({ ...aiForm, template_type: e.target.value })}
|
||||
helperText="Örn: bank, hr, it_support, management"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Şirket Adı"
|
||||
required
|
||||
value={aiForm.company_name}
|
||||
onChange={(e) => setAiForm({ ...aiForm, company_name: e.target.value })}
|
||||
helperText="Hedef şirket adı (örn: Acme Corporation)"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Senaryo"
|
||||
required
|
||||
multiline
|
||||
rows={3}
|
||||
value={aiForm.scenario}
|
||||
onChange={(e) => setAiForm({ ...aiForm, scenario: e.target.value })}
|
||||
helperText="Mail senaryosu (örn: 'Şifre sıfırlama maili', 'Yeni güvenlik politikası', 'Ödül programı duyurusu')"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Çalışan Bilgisi (Opsiyonel)"
|
||||
value={aiForm.employee_info}
|
||||
onChange={(e) => setAiForm({ ...aiForm, employee_info: e.target.value })}
|
||||
helperText="Hedef çalışan hakkında bilgi (örn: 'İK departmanı çalışanı', 'Yönetici')"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Ek Talimatlar (Opsiyonel)"
|
||||
multiline
|
||||
rows={2}
|
||||
value={aiForm.custom_prompt}
|
||||
onChange={(e) => setAiForm({ ...aiForm, custom_prompt: e.target.value })}
|
||||
helperText="AI'ya özel talimatlar (örn: 'Resmi dil kullan', 'Aciliyet vurgusu yap')"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setAiDialogOpen(false)} disabled={aiGenerating}>
|
||||
İptal
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleGenerateWithAI}
|
||||
variant="contained"
|
||||
disabled={aiGenerating}
|
||||
startIcon={aiGenerating ? <CircularProgress size={20} /> : <AutoAwesome />}
|
||||
>
|
||||
{aiGenerating ? 'Oluşturuluyor...' : 'Oluştur'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Test Mail Dialog */}
|
||||
<Dialog open={testMailDialogOpen} onClose={() => setTestMailDialogOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>📧 Test Maili Gönder</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
Seçili şablon için test maili gönderin.
|
||||
</Typography>
|
||||
|
||||
{selectedTemplate && (
|
||||
<Box my={2} p={2} bgcolor="grey.100" borderRadius={1}>
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
Şablon: {selectedTemplate.name}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Tip: {selectedTemplate.template_type}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Test Mail Adresi"
|
||||
type="email"
|
||||
required
|
||||
value={testMailAddress}
|
||||
onChange={(e) => setTestMailAddress(e.target.value)}
|
||||
helperText="Test mailinin gönderileceği adres"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Şirket Adı (Placeholder)"
|
||||
value={companyPlaceholder}
|
||||
onChange={(e) => setCompanyPlaceholder(e.target.value)}
|
||||
helperText="{{company_name}} yerine kullanılacak"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Çalışan Adı (Placeholder)"
|
||||
value={employeePlaceholder}
|
||||
onChange={(e) => setEmployeePlaceholder(e.target.value)}
|
||||
helperText="{{employee_name}} yerine kullanılacak"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setTestMailDialogOpen(false)}>
|
||||
İptal
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSendTestMail}
|
||||
variant="contained"
|
||||
startIcon={<SendIcon />}
|
||||
>
|
||||
Gönder
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user