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:
307
OLLAMA_SETUP.md
Normal file
307
OLLAMA_SETUP.md
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# 🤖 Ollama AI Entegrasyonu
|
||||||
|
|
||||||
|
## Genel Bakış
|
||||||
|
|
||||||
|
Phishing Test Yönetim Paneli, **Ollama AI** entegrasyonu ile otomatik mail şablonları oluşturabilir. Bu özellik, gerçekçi ve özelleştirilebilir phishing test maillerini dakikalar içinde üretmenizi sağlar.
|
||||||
|
|
||||||
|
## 🚀 Özellikler
|
||||||
|
|
||||||
|
### 1. **AI ile Mail Şablonu Oluşturma**
|
||||||
|
- Şirket adı, senaryo ve hedef bilgilerine göre otomatik mail oluşturma
|
||||||
|
- HTML formatında profesyonel mail içeriği
|
||||||
|
- Özelleştirilebilir placeholder'lar (`{{company_name}}`, `{{employee_name}}`, `{{tracking_url}}`)
|
||||||
|
- Otomatik konu satırı (subject) oluşturma
|
||||||
|
|
||||||
|
### 2. **Test Mail Gönderimi**
|
||||||
|
- Oluşturulan şablonları test etme
|
||||||
|
- Placeholder'ları doldurarak gerçek görünüm
|
||||||
|
- Herhangi bir mail adresine test gönderimi
|
||||||
|
|
||||||
|
### 3. **Ollama Yönetimi**
|
||||||
|
- Ollama sunucu bağlantı testi
|
||||||
|
- Mevcut modelleri listeleme
|
||||||
|
- Model seçimi ve yönetimi
|
||||||
|
|
||||||
|
## 📋 Gereksinimler
|
||||||
|
|
||||||
|
### 1. Ollama Kurulumu
|
||||||
|
|
||||||
|
#### Linux
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://ollama.com/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### macOS
|
||||||
|
```bash
|
||||||
|
brew install ollama
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows
|
||||||
|
[Ollama Windows İndirme](https://ollama.com/download/windows)
|
||||||
|
|
||||||
|
### 2. Model İndirme
|
||||||
|
|
||||||
|
Önerilen modeller:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Llama 3.2 (Önerilen - hızlı ve etkili)
|
||||||
|
ollama pull llama3.2
|
||||||
|
|
||||||
|
# Alternatifler:
|
||||||
|
ollama pull mistral # Hızlı, compact
|
||||||
|
ollama pull gemma # Google'ın modeli
|
||||||
|
ollama pull llama2 # Eski ama stabil
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Ollama Servisini Başlatma
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Servis olarak başlat
|
||||||
|
ollama serve
|
||||||
|
|
||||||
|
# Veya systemd ile (Linux)
|
||||||
|
systemctl start ollama
|
||||||
|
systemctl enable ollama
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Yapılandırma
|
||||||
|
|
||||||
|
### 1. Panel Ayarları
|
||||||
|
|
||||||
|
1. **Ayarlar** sayfasına gidin
|
||||||
|
2. **🤖 Ollama AI Ayarları** bölümünde:
|
||||||
|
- **Ollama Server URL**: `http://localhost:11434` (varsayılan)
|
||||||
|
- **Model**: `llama3.2` (veya tercih ettiğiniz model)
|
||||||
|
3. **Bağlantıyı Test Et** butonuna tıklayın
|
||||||
|
4. Mevcut modelleri görün
|
||||||
|
5. **Kaydet** butonuna tıklayın
|
||||||
|
|
||||||
|
### 2. Ortam Değişkenleri (Opsiyonel)
|
||||||
|
|
||||||
|
`.env` dosyasında varsayılan değerleri ayarlayabilirsiniz:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OLLAMA_URL=http://localhost:11434
|
||||||
|
OLLAMA_MODEL=llama3.2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Kullanım
|
||||||
|
|
||||||
|
### AI ile Şablon Oluşturma
|
||||||
|
|
||||||
|
1. **Mail Şablonları** sayfasına gidin
|
||||||
|
2. **AI ile Oluştur** butonuna tıklayın
|
||||||
|
3. Formu doldurun:
|
||||||
|
- **Şablon Adı**: Şablona verilecek isim
|
||||||
|
- **Template Type**: Şablon tipi (örn: `bank`, `hr`, `it_support`)
|
||||||
|
- **Şirket Adı**: Hedef şirket (örn: "Acme Corporation")
|
||||||
|
- **Senaryo**: Mail senaryosu (örn: "Şifre sıfırlama maili")
|
||||||
|
- **Çalışan Bilgisi** (opsiyonel): Hedef çalışan hakkında bilgi
|
||||||
|
- **Ek Talimatlar** (opsiyonel): AI'ya özel talimatlar
|
||||||
|
4. **Oluştur** butonuna tıklayın
|
||||||
|
5. AI, birkaç saniye içinde profesyonel bir mail şablonu oluşturacak
|
||||||
|
6. Şablon otomatik olarak veritabanına kaydedilir
|
||||||
|
|
||||||
|
### Senaryo Örnekleri
|
||||||
|
|
||||||
|
#### Banka Şablonu
|
||||||
|
```
|
||||||
|
Senaryo: "Hesap güvenlik uyarısı - şüpheli aktivite tespit edildi ve doğrulama gerekiyor"
|
||||||
|
Şirket: Garanti Bankası
|
||||||
|
Çalışan Bilgisi: Bireysel müşteri
|
||||||
|
```
|
||||||
|
|
||||||
|
#### İK Şablonu
|
||||||
|
```
|
||||||
|
Senaryo: "Yıllık performans değerlendirme formu doldurulması gerekiyor"
|
||||||
|
Şirket: Acme Tech
|
||||||
|
Çalışan Bilgisi: Yazılım geliştirici
|
||||||
|
Ek Talimat: Resmi ve profesyonel dil kullan
|
||||||
|
```
|
||||||
|
|
||||||
|
#### IT Destek Şablonu
|
||||||
|
```
|
||||||
|
Senaryo: "Sistem güncellemesi için kullanıcı bilgilerinin doğrulanması"
|
||||||
|
Şirket: XYZ Corporation
|
||||||
|
Çalışan Bilgisi: Ofis çalışanı
|
||||||
|
Ek Talimat: Teknik terimler kullan, aciliyet vurgusu yap
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Mail Gönderme
|
||||||
|
|
||||||
|
1. Şablonlar listesinde istediğiniz şablonun yanındaki **Test Mail** butonuna tıklayın
|
||||||
|
2. Test mail adresini girin
|
||||||
|
3. Placeholder değerlerini düzenleyin:
|
||||||
|
- Şirket Adı
|
||||||
|
- Çalışan Adı
|
||||||
|
4. **Gönder** butonuna tıklayın
|
||||||
|
5. Test maili belirttiğiniz adrese gönderilir
|
||||||
|
|
||||||
|
## 🔧 Teknik Detaylar
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
#### Ollama Bağlantı Testi
|
||||||
|
```http
|
||||||
|
GET /api/ollama/test
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mevcut Modelleri Listeleme
|
||||||
|
```http
|
||||||
|
GET /api/ollama/models
|
||||||
|
```
|
||||||
|
|
||||||
|
#### AI ile Şablon Oluşturma
|
||||||
|
```http
|
||||||
|
POST /api/ollama/generate-template
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"company_name": "Acme Corp",
|
||||||
|
"scenario": "Şifre sıfırlama",
|
||||||
|
"employee_info": "IT personeli",
|
||||||
|
"custom_prompt": "Aciliyet vurgusu yap",
|
||||||
|
"template_name": "Acme Şifre Sıfırlama",
|
||||||
|
"template_type": "it_support"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test Mail Gönderme
|
||||||
|
```http
|
||||||
|
POST /api/ollama/send-test-mail
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"test_email": "test@example.com",
|
||||||
|
"subject": "Test Subject",
|
||||||
|
"body": "<html>...</html>",
|
||||||
|
"company_name": "Test Company",
|
||||||
|
"employee_name": "John Doe"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ollama Servisi Yapılandırması
|
||||||
|
|
||||||
|
Backend servisi Ollama'yı otomatik olarak yapılandırır:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// backend/src/services/ollama.service.js
|
||||||
|
class OllamaService {
|
||||||
|
constructor() {
|
||||||
|
this.serverUrl = 'http://localhost:11434';
|
||||||
|
this.model = 'llama3.2';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### AI Prompt Yapısı
|
||||||
|
|
||||||
|
AI'ya gönderilen prompt:
|
||||||
|
|
||||||
|
```
|
||||||
|
System: Sen profesyonel bir güvenlik uzmanısın ve phishing test maileri oluşturuyorsun.
|
||||||
|
|
||||||
|
User: Aşağıdaki bilgilere göre bir phishing test mail şablonu oluştur:
|
||||||
|
- Şirket: [company_name]
|
||||||
|
- Senaryo: [scenario]
|
||||||
|
- Çalışan Bilgisi: [employee_info]
|
||||||
|
- Ek Talimatlar: [custom_prompt]
|
||||||
|
|
||||||
|
Yanıt JSON formatında:
|
||||||
|
{
|
||||||
|
"subject": "Mail konusu",
|
||||||
|
"body": "<html>Mail içeriği</html>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Troubleshooting
|
||||||
|
|
||||||
|
### Ollama bağlantı hatası
|
||||||
|
|
||||||
|
**Hata**: `Ollama sunucusuna bağlanılamadı`
|
||||||
|
|
||||||
|
**Çözüm**:
|
||||||
|
```bash
|
||||||
|
# Ollama servisinin çalıştığını kontrol edin
|
||||||
|
curl http://localhost:11434/api/tags
|
||||||
|
|
||||||
|
# Servis çalışmıyorsa başlatın
|
||||||
|
ollama serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model bulunamadı hatası
|
||||||
|
|
||||||
|
**Hata**: `Model not found`
|
||||||
|
|
||||||
|
**Çözüm**:
|
||||||
|
```bash
|
||||||
|
# Modeli indirin
|
||||||
|
ollama pull llama3.2
|
||||||
|
|
||||||
|
# Mevcut modelleri listeleyin
|
||||||
|
ollama list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Yavaş yanıt süresi
|
||||||
|
|
||||||
|
**Çözüm**:
|
||||||
|
- Daha küçük bir model kullanın (`mistral`, `llama3.2:1b`)
|
||||||
|
- GPU kullanımını etkinleştirin (CUDA destekli sistem gerekir)
|
||||||
|
- RAM ve CPU kaynaklarını artırın
|
||||||
|
|
||||||
|
### Timeout hatası
|
||||||
|
|
||||||
|
**Hata**: `AI yanıt veremedi: timeout`
|
||||||
|
|
||||||
|
**Çözüm**:
|
||||||
|
- Backend `ollama.service.js` dosyasında timeout süresini artırın:
|
||||||
|
```javascript
|
||||||
|
timeout: 180000, // 3 dakika
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Performans
|
||||||
|
|
||||||
|
### Model Karşılaştırması
|
||||||
|
|
||||||
|
| Model | Boyut | Hız | Kalite | RAM |
|
||||||
|
|-------|-------|-----|--------|-----|
|
||||||
|
| llama3.2 | 2GB | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 8GB |
|
||||||
|
| mistral | 4GB | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 8GB |
|
||||||
|
| gemma | 2GB | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 6GB |
|
||||||
|
| llama2 | 4GB | ⭐⭐⭐ | ⭐⭐⭐⭐ | 8GB |
|
||||||
|
|
||||||
|
### Yanıt Süreleri (Ortalama)
|
||||||
|
|
||||||
|
- **CPU**: 30-60 saniye
|
||||||
|
- **GPU (CUDA)**: 5-15 saniye
|
||||||
|
- **Apple Silicon (M1/M2)**: 10-25 saniye
|
||||||
|
|
||||||
|
## 🔒 Güvenlik Notları
|
||||||
|
|
||||||
|
1. **Ollama Erişimi**: Ollama sunucusu varsayılan olarak `localhost:11434` üzerinde çalışır. Production'da bu portu dışarıya açmayın.
|
||||||
|
|
||||||
|
2. **API Rate Limiting**: Backend API'sinde rate limiting aktiftir, kötüye kullanımı önler.
|
||||||
|
|
||||||
|
3. **Model Seçimi**: Güvenilir ve test edilmiş modeller kullanın.
|
||||||
|
|
||||||
|
4. **Veri Gizliliği**: AI'ya gönderilen veriler Ollama sunucusunda işlenir, dışarıya gönderilmez (self-hosted).
|
||||||
|
|
||||||
|
## 📚 Kaynaklar
|
||||||
|
|
||||||
|
- [Ollama Resmi Dokümantasyon](https://github.com/ollama/ollama)
|
||||||
|
- [Ollama Model Library](https://ollama.com/library)
|
||||||
|
- [Llama 3.2 Model Bilgileri](https://ollama.com/library/llama3.2)
|
||||||
|
|
||||||
|
## 🆘 Destek
|
||||||
|
|
||||||
|
Sorun yaşıyorsanız:
|
||||||
|
|
||||||
|
1. Ollama servisinin çalıştığını kontrol edin
|
||||||
|
2. Model indirildiyse kontrol edin
|
||||||
|
3. Panel **Ayarlar** sayfasında bağlantı testini yapın
|
||||||
|
4. Backend loglarını kontrol edin: `backend/logs/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Not**: Ollama entegrasyonu tamamen opsiyoneldir. Sistemi Ollama olmadan da kullanabilir, manuel olarak mail şablonları oluşturabilirsiniz.
|
||||||
|
|
||||||
@@ -7,10 +7,11 @@ Güvenlik farkındalık eğitimleri için basit ve etkili phishing test yönetim
|
|||||||
- 🏢 **Şirket Bazlı Yönetim** - Her şirket için ayrı tracking
|
- 🏢 **Şirket Bazlı Yönetim** - Her şirket için ayrı tracking
|
||||||
- 📧 **Gmail Entegrasyonu** - App Password ile kolay mail gönderimi
|
- 📧 **Gmail Entegrasyonu** - App Password ile kolay mail gönderimi
|
||||||
- 💬 **Telegram Bildirimleri** - Gerçek zamanlı tıklama bildirimleri
|
- 💬 **Telegram Bildirimleri** - Gerçek zamanlı tıklama bildirimleri
|
||||||
|
- 🤖 **Ollama AI Entegrasyonu** - AI ile otomatik mail şablonu oluşturma
|
||||||
- 📊 **Detaylı İstatistikler** - IP, konum, cihaz bilgileri
|
- 📊 **Detaylı İstatistikler** - IP, konum, cihaz bilgileri
|
||||||
- 💾 **SQLite** - Tek dosya, kolay yedekleme
|
- 💾 **SQLite** - Tek dosya, kolay yedekleme
|
||||||
- 🎨 **Modern UI** - React ile responsive admin paneli
|
- 🎨 **Modern UI** - React ile responsive admin paneli
|
||||||
- ✉️ **Mail Şablonları** - HTML editör ve önizleme paneli
|
- ✉️ **Mail Şablonları** - HTML editör, önizleme ve test gönderimi
|
||||||
|
|
||||||
## 🚀 Hızlı Başlangıç
|
## 🚀 Hızlı Başlangıç
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ oltalama/
|
|||||||
│ │ ├── controllers/ ✅ 7 dosya (auth, company, token, vb.)
|
│ │ ├── controllers/ ✅ 7 dosya (auth, company, token, vb.)
|
||||||
│ │ ├── models/ ✅ 6 model + ilişkiler
|
│ │ ├── models/ ✅ 6 model + ilişkiler
|
||||||
│ │ ├── routes/ ✅ 7 route dosyası
|
│ │ ├── routes/ ✅ 7 route dosyası
|
||||||
│ │ ├── services/ ✅ Mail, Telegram, Token
|
│ │ ├── services/ ✅ Mail, Telegram, Token, Ollama AI
|
||||||
│ │ ├── utils/ ✅ GeoIP, User-Agent, Token Generator
|
│ │ ├── utils/ ✅ GeoIP, User-Agent, Token Generator
|
||||||
│ │ └── app.js ✅
|
│ │ └── app.js ✅
|
||||||
│ └── database/ ✅ oltalama.db (3 şirket, 2 şablon)
|
│ └── database/ ✅ oltalama.db (3 şirket, 2 şablon)
|
||||||
@@ -239,6 +240,7 @@ node scripts/change-password.js
|
|||||||
|
|
||||||
- **Ana Doküman:** `README.md` (bu dosya)
|
- **Ana Doküman:** `README.md` (bu dosya)
|
||||||
- **Sunucu Kurulumu:** `DEPLOYMENT.md` 🚀 (Production kurulum)
|
- **Sunucu Kurulumu:** `DEPLOYMENT.md` 🚀 (Production kurulum)
|
||||||
|
- **Ollama AI Entegrasyonu:** `OLLAMA_SETUP.md` 🤖 (AI mail şablon oluşturma)
|
||||||
- **Domain Yapılandırma:** `docs/DOMAIN_SETUP.md` 🌐 (Tek/İki domain)
|
- **Domain Yapılandırma:** `docs/DOMAIN_SETUP.md` 🌐 (Tek/İki domain)
|
||||||
- **Nginx Proxy Manager:** `docs/NGINX_PROXY_MANAGER.md` 🔄 (Reverse proxy)
|
- **Nginx Proxy Manager:** `docs/NGINX_PROXY_MANAGER.md` 🔄 (Reverse proxy)
|
||||||
- **Güvenlik Rehberi:** `SECURITY.md` 🔒 (Güvenlik en iyi uygulamaları)
|
- **Güvenlik Rehberi:** `SECURITY.md` 🔒 (Güvenlik en iyi uygulamaları)
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ app.use('/api/companies', require('./routes/company.routes'));
|
|||||||
app.use('/api/tokens', require('./routes/token.routes'));
|
app.use('/api/tokens', require('./routes/token.routes'));
|
||||||
app.use('/api/templates', require('./routes/template.routes'));
|
app.use('/api/templates', require('./routes/template.routes'));
|
||||||
app.use('/api/settings', require('./routes/settings.routes'));
|
app.use('/api/settings', require('./routes/settings.routes'));
|
||||||
|
app.use('/api/ollama', require('./routes/ollama.routes'));
|
||||||
app.use('/api/stats', require('./routes/stats.routes'));
|
app.use('/api/stats', require('./routes/stats.routes'));
|
||||||
|
|
||||||
// Public tracking route (no rate limit on this specific route)
|
// Public tracking route (no rate limit on this specific route)
|
||||||
|
|||||||
188
backend/src/controllers/ollama.controller.js
Normal file
188
backend/src/controllers/ollama.controller.js
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
const ollamaService = require('../services/ollama.service');
|
||||||
|
const { Settings, MailTemplate } = require('../models');
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Ollama connection
|
||||||
|
*/
|
||||||
|
exports.testConnection = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const result = await ollamaService.testConnection();
|
||||||
|
res.json({
|
||||||
|
success: result.success,
|
||||||
|
message: result.message,
|
||||||
|
data: {
|
||||||
|
models: result.models,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List available Ollama models
|
||||||
|
*/
|
||||||
|
exports.listModels = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const models = await ollamaService.listModels();
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: models,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Ollama settings
|
||||||
|
*/
|
||||||
|
exports.updateSettings = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { ollama_server_url, ollama_model } = req.body;
|
||||||
|
|
||||||
|
if (ollama_server_url !== undefined) {
|
||||||
|
if (ollama_server_url) {
|
||||||
|
// Validate URL format
|
||||||
|
const cleanUrl = ollama_server_url.trim().replace(/\/$/, '');
|
||||||
|
try {
|
||||||
|
new URL(cleanUrl);
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Geçersiz Ollama URL formatı. Örnek: http://localhost:11434',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Settings.upsert({
|
||||||
|
key: 'ollama_server_url',
|
||||||
|
value: cleanUrl,
|
||||||
|
is_encrypted: false,
|
||||||
|
description: 'Ollama server URL',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await Settings.destroy({ where: { key: 'ollama_server_url' } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ollama_model !== undefined) {
|
||||||
|
if (ollama_model) {
|
||||||
|
await Settings.upsert({
|
||||||
|
key: 'ollama_model',
|
||||||
|
value: ollama_model,
|
||||||
|
is_encrypted: false,
|
||||||
|
description: 'Ollama model name',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await Settings.destroy({ where: { key: 'ollama_model' } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize service with new settings
|
||||||
|
await ollamaService.initialize();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Ollama ayarları güncellendi',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate mail template with AI
|
||||||
|
*/
|
||||||
|
exports.generateTemplate = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { company_name, scenario, employee_info, custom_prompt, template_name, template_type } = req.body;
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!company_name || !scenario) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'company_name ve scenario zorunludur',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`AI template generation requested for: ${company_name} - ${scenario}`);
|
||||||
|
|
||||||
|
// Generate template using Ollama
|
||||||
|
const templateData = await ollamaService.generateMailTemplate({
|
||||||
|
company_name,
|
||||||
|
scenario,
|
||||||
|
employee_info,
|
||||||
|
custom_prompt,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save to database if template_name is provided
|
||||||
|
let savedTemplate = null;
|
||||||
|
if (template_name && template_type) {
|
||||||
|
savedTemplate = await MailTemplate.create({
|
||||||
|
name: template_name,
|
||||||
|
type: template_type,
|
||||||
|
subject_template: templateData.subject_template,
|
||||||
|
body_template: templateData.body_template,
|
||||||
|
description: `AI tarafından oluşturuldu - ${scenario}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`AI-generated template saved: ${template_name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: savedTemplate
|
||||||
|
? 'Template başarıyla oluşturuldu ve kaydedildi'
|
||||||
|
: 'Template başarıyla oluşturuldu',
|
||||||
|
data: {
|
||||||
|
template: savedTemplate || templateData,
|
||||||
|
generated_by: templateData.model,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('AI template generation failed:', error);
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send test mail with generated template
|
||||||
|
*/
|
||||||
|
exports.sendTestMail = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { test_email, subject, body, company_name, employee_name } = req.body;
|
||||||
|
|
||||||
|
if (!test_email || !subject || !body) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'test_email, subject ve body zorunludur',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mailService = require('../services/mail.service');
|
||||||
|
|
||||||
|
// Replace placeholders
|
||||||
|
const finalSubject = subject
|
||||||
|
.replace(/\{\{company_name\}\}/g, company_name || 'Test Şirketi')
|
||||||
|
.replace(/\{\{employee_name\}\}/g, employee_name || 'Test Kullanıcı');
|
||||||
|
|
||||||
|
const finalBody = body
|
||||||
|
.replace(/\{\{company_name\}\}/g, company_name || 'Test Şirketi')
|
||||||
|
.replace(/\{\{employee_name\}\}/g, employee_name || 'Test Kullanıcı')
|
||||||
|
.replace(/\{\{tracking_url\}\}/g, 'http://example.com/test-tracking-link');
|
||||||
|
|
||||||
|
await mailService.sendMail(test_email, finalSubject, finalBody);
|
||||||
|
|
||||||
|
logger.info(`Test mail sent to: ${test_email}`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Test maili başarıyla gönderildi',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Test mail sending failed:', error);
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
25
backend/src/routes/ollama.routes.js
Normal file
25
backend/src/routes/ollama.routes.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const ollamaController = require('../controllers/ollama.controller');
|
||||||
|
const { isAuthenticated } = require('../middleware/auth.middleware');
|
||||||
|
|
||||||
|
// All routes require authentication
|
||||||
|
router.use(isAuthenticated);
|
||||||
|
|
||||||
|
// Test Ollama connection
|
||||||
|
router.get('/test', ollamaController.testConnection);
|
||||||
|
|
||||||
|
// List available models
|
||||||
|
router.get('/models', ollamaController.listModels);
|
||||||
|
|
||||||
|
// Update Ollama settings
|
||||||
|
router.put('/settings', ollamaController.updateSettings);
|
||||||
|
|
||||||
|
// Generate template with AI
|
||||||
|
router.post('/generate-template', ollamaController.generateTemplate);
|
||||||
|
|
||||||
|
// Send test mail
|
||||||
|
router.post('/send-test-mail', ollamaController.sendTestMail);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
||||||
221
backend/src/services/ollama.service.js
Normal file
221
backend/src/services/ollama.service.js
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
const { Settings } = require('../models');
|
||||||
|
|
||||||
|
class OllamaService {
|
||||||
|
constructor() {
|
||||||
|
this.serverUrl = null;
|
||||||
|
this.model = null;
|
||||||
|
this.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Ollama service with settings from database
|
||||||
|
*/
|
||||||
|
async initialize() {
|
||||||
|
try {
|
||||||
|
const serverUrlSetting = await Settings.findOne({
|
||||||
|
where: { key: 'ollama_server_url' },
|
||||||
|
});
|
||||||
|
const modelSetting = await Settings.findOne({
|
||||||
|
where: { key: 'ollama_model' },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.serverUrl = serverUrlSetting?.value || process.env.OLLAMA_URL || 'http://localhost:11434';
|
||||||
|
this.model = modelSetting?.value || process.env.OLLAMA_MODEL || 'llama3.2';
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
logger.info(`Ollama service initialized: ${this.serverUrl} with model ${this.model}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to initialize Ollama service:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Ollama connection
|
||||||
|
*/
|
||||||
|
async testConnection() {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${this.serverUrl}/api/tags`, {
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
models: response.data.models || [],
|
||||||
|
message: 'Ollama bağlantısı başarılı',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ollama connection test failed:', error.message);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
message: 'Ollama sunucusuna bağlanılamadı',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List available models
|
||||||
|
*/
|
||||||
|
async listModels() {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${this.serverUrl}/api/tags`, {
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.models || [];
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to list Ollama models:', error.message);
|
||||||
|
throw new Error('Ollama model listesi alınamadı: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate mail template using Ollama
|
||||||
|
* @param {Object} params - Template generation parameters
|
||||||
|
* @param {string} params.company_name - Target company name
|
||||||
|
* @param {string} params.scenario - Phishing scenario type
|
||||||
|
* @param {string} params.employee_info - Employee information (optional)
|
||||||
|
* @param {string} params.custom_prompt - Custom instructions (optional)
|
||||||
|
*/
|
||||||
|
async generateMailTemplate(params) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { company_name, scenario, employee_info, custom_prompt } = params;
|
||||||
|
|
||||||
|
// Build the prompt
|
||||||
|
const systemPrompt = `Sen profesyonel bir güvenlik uzmanısın ve phishing test maileri oluşturuyorsun.
|
||||||
|
Amacın gerçekçi, ikna edici ancak zararsız test mailleri oluşturmak.
|
||||||
|
Mail şablonları HTML formatında olmalı ve modern, profesyonel görünmeli.
|
||||||
|
Şablon içinde {{company_name}} ve {{employee_name}} placeholder'ları kullan.`;
|
||||||
|
|
||||||
|
let userPrompt = `Aşağıdaki bilgilere göre bir phishing test mail şablonu oluştur:
|
||||||
|
|
||||||
|
Şirket: ${company_name}
|
||||||
|
Senaryo: ${scenario}`;
|
||||||
|
|
||||||
|
if (employee_info) {
|
||||||
|
userPrompt += `\nÇalışan Bilgisi: ${employee_info}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_prompt) {
|
||||||
|
userPrompt += `\nEk Talimatlar: ${custom_prompt}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
userPrompt += `
|
||||||
|
|
||||||
|
ÖNEMLI:
|
||||||
|
1. Yanıtını JSON formatında ver
|
||||||
|
2. İki alan olmalı: "subject" (konu) ve "body" (HTML mail içeriği)
|
||||||
|
3. Body HTML formatında, modern ve profesyonel olmalı
|
||||||
|
4. {{company_name}} ve {{employee_name}} placeholder'larını kullan
|
||||||
|
5. Gerçekçi ve ikna edici olmalı
|
||||||
|
6. Link için {{tracking_url}} placeholder'ını kullan
|
||||||
|
|
||||||
|
Örnek format:
|
||||||
|
{
|
||||||
|
"subject": "Mail konusu buraya",
|
||||||
|
"body": "<html>Mail içeriği buraya</html>"
|
||||||
|
}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info(`Generating template for company: ${company_name}, scenario: ${scenario}`);
|
||||||
|
|
||||||
|
const response = await axios.post(
|
||||||
|
`${this.serverUrl}/api/chat`,
|
||||||
|
{
|
||||||
|
model: this.model,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: systemPrompt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: userPrompt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stream: false,
|
||||||
|
format: 'json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 120000, // 2 minutes timeout for AI generation
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const aiResponse = response.data.message.content;
|
||||||
|
logger.info('Ollama response received');
|
||||||
|
|
||||||
|
// Parse JSON response
|
||||||
|
let templateData;
|
||||||
|
try {
|
||||||
|
templateData = JSON.parse(aiResponse);
|
||||||
|
} catch (parseError) {
|
||||||
|
// If JSON parsing fails, try to extract JSON from response
|
||||||
|
const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
|
||||||
|
if (jsonMatch) {
|
||||||
|
templateData = JSON.parse(jsonMatch[0]);
|
||||||
|
} else {
|
||||||
|
throw new Error('AI yanıtı JSON formatında değil');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!templateData.subject || !templateData.body) {
|
||||||
|
throw new Error('AI yanıtında subject veya body eksik');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subject_template: templateData.subject,
|
||||||
|
body_template: templateData.body,
|
||||||
|
generated_by: 'ollama',
|
||||||
|
model: this.model,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to generate template with Ollama:', error.message);
|
||||||
|
throw new Error('Template oluşturulamadı: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a simple text completion
|
||||||
|
*/
|
||||||
|
async generate(prompt) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${this.serverUrl}/api/generate`,
|
||||||
|
{
|
||||||
|
model: this.model,
|
||||||
|
prompt: prompt,
|
||||||
|
stream: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 60000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.response;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ollama generation failed:', error.message);
|
||||||
|
throw new Error('Ollama yanıt veremedi: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new OllamaService();
|
||||||
|
|
||||||
@@ -27,10 +27,13 @@ function Settings() {
|
|||||||
gmail_from_name: '',
|
gmail_from_name: '',
|
||||||
telegram_bot_token: '',
|
telegram_bot_token: '',
|
||||||
telegram_chat_id: '',
|
telegram_chat_id: '',
|
||||||
|
ollama_server_url: '',
|
||||||
|
ollama_model: '',
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [testLoading, setTestLoading] = useState({ mail: false, telegram: false });
|
const [testLoading, setTestLoading] = useState({ mail: false, telegram: false, ollama: false });
|
||||||
const [alerts, setAlerts] = useState({ mail: null, telegram: null });
|
const [alerts, setAlerts] = useState({ mail: null, telegram: null, ollama: null });
|
||||||
|
const [ollamaModels, setOllamaModels] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -60,6 +63,8 @@ function Settings() {
|
|||||||
gmail_from_name: settingsObj.gmail_from_name || '',
|
gmail_from_name: settingsObj.gmail_from_name || '',
|
||||||
telegram_bot_token: settingsObj.telegram_bot_token || '',
|
telegram_bot_token: settingsObj.telegram_bot_token || '',
|
||||||
telegram_chat_id: settingsObj.telegram_chat_id || '',
|
telegram_chat_id: settingsObj.telegram_chat_id || '',
|
||||||
|
ollama_server_url: settingsObj.ollama_server_url || '',
|
||||||
|
ollama_model: settingsObj.ollama_model || '',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load settings:', error);
|
console.error('Failed to load settings:', error);
|
||||||
@@ -85,6 +90,10 @@ function Settings() {
|
|||||||
telegram_bot_token: settings.telegram_bot_token,
|
telegram_bot_token: settings.telegram_bot_token,
|
||||||
telegram_chat_id: settings.telegram_chat_id,
|
telegram_chat_id: settings.telegram_chat_id,
|
||||||
}, { withCredentials: true }),
|
}, { withCredentials: true }),
|
||||||
|
axios.put(`${API_URL}/api/ollama/settings`, {
|
||||||
|
ollama_server_url: settings.ollama_server_url,
|
||||||
|
ollama_model: settings.ollama_model,
|
||||||
|
}, { withCredentials: true }),
|
||||||
]);
|
]);
|
||||||
alert('Ayarlar kaydedildi!');
|
alert('Ayarlar kaydedildi!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -134,6 +143,44 @@ function Settings() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTestOllama = async () => {
|
||||||
|
setTestLoading({ ...testLoading, ollama: true });
|
||||||
|
setAlerts({ ...alerts, ollama: null });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${API_URL}/api/ollama/test`,
|
||||||
|
{ withCredentials: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
setOllamaModels(response.data.data.models || []);
|
||||||
|
setAlerts({
|
||||||
|
...alerts,
|
||||||
|
ollama: {
|
||||||
|
severity: 'success',
|
||||||
|
message: `${response.data.message} - ${response.data.data.models.length} model bulundu`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setAlerts({
|
||||||
|
...alerts,
|
||||||
|
ollama: { severity: 'error', message: response.data.message },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setAlerts({
|
||||||
|
...alerts,
|
||||||
|
ollama: {
|
||||||
|
severity: 'error',
|
||||||
|
message: error.response?.data?.error || 'Ollama bağlantısı başarısız',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setTestLoading({ ...testLoading, ollama: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
||||||
@@ -333,6 +380,80 @@ function Settings() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
{/* Ollama Settings */}
|
||||||
|
<Grid size={12}>
|
||||||
|
<Paper sx={{ p: 3 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
🤖 Ollama AI Ayarları
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
|
AI ile mail şablonu oluşturmak için Ollama yapılandırması
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
label="Ollama Server URL"
|
||||||
|
type="url"
|
||||||
|
placeholder="http://localhost:11434"
|
||||||
|
value={settings.ollama_server_url}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings({ ...settings, ollama_server_url: e.target.value })
|
||||||
|
}
|
||||||
|
helperText="Ollama sunucu adresi (varsayılan: http://localhost:11434)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
label="Model"
|
||||||
|
placeholder="llama3.2"
|
||||||
|
value={settings.ollama_model}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings({ ...settings, ollama_model: e.target.value })
|
||||||
|
}
|
||||||
|
helperText="Kullanılacak Ollama model adı (örn: llama3.2, mistral, gemma)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{alerts.ollama && (
|
||||||
|
<Alert severity={alerts.ollama.severity} sx={{ mt: 2 }}>
|
||||||
|
{alerts.ollama.message}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ollamaModels.length > 0 && (
|
||||||
|
<Alert severity="info" sx={{ mt: 2 }}>
|
||||||
|
<Typography variant="body2" fontWeight="bold">
|
||||||
|
Mevcut Modeller:
|
||||||
|
</Typography>
|
||||||
|
{ollamaModels.map((model, idx) => (
|
||||||
|
<Typography key={idx} variant="body2">
|
||||||
|
• {model.name} ({(model.size / 1024 / 1024 / 1024).toFixed(1)} GB)
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box mt={2} display="flex" gap={2}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<Save />}
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
Kaydet
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<Send />}
|
||||||
|
onClick={handleTestOllama}
|
||||||
|
disabled={testLoading.ollama}
|
||||||
|
>
|
||||||
|
{testLoading.ollama ? <CircularProgress size={20} /> : 'Bağlantıyı Test Et'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Paper sx={{ p: 3, mt: 3 }}>
|
<Paper sx={{ p: 3, mt: 3 }}>
|
||||||
|
|||||||
@@ -28,9 +28,14 @@ import {
|
|||||||
Delete,
|
Delete,
|
||||||
Preview,
|
Preview,
|
||||||
ContentCopy,
|
ContentCopy,
|
||||||
|
AutoAwesome,
|
||||||
|
Send as SendIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { templateService } from '../services/templateService';
|
import { templateService } from '../services/templateService';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -54,6 +59,18 @@ function Templates() {
|
|||||||
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
||||||
const [companyPlaceholder, setCompanyPlaceholder] = useState('Örnek Şirket');
|
const [companyPlaceholder, setCompanyPlaceholder] = useState('Örnek Şirket');
|
||||||
const [employeePlaceholder, setEmployeePlaceholder] = useState('Ahmet Yılmaz');
|
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(() => {
|
useEffect(() => {
|
||||||
loadTemplates();
|
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 }) => {
|
const handlePreview = async (htmlOverride, options = { openModal: false }) => {
|
||||||
try {
|
try {
|
||||||
setPreviewLoading(true);
|
setPreviewLoading(true);
|
||||||
@@ -196,10 +276,20 @@ function Templates() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
|
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
|
||||||
<Typography variant="h4">Mail Şablonları</Typography>
|
<Typography variant="h4">Mail Şablonları</Typography>
|
||||||
|
<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}>
|
<Button variant="contained" startIcon={<Add />} onClick={handleOpenCreate}>
|
||||||
Yeni Şablon
|
Yeni Şablon
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table>
|
<Table>
|
||||||
@@ -273,6 +363,18 @@ function Templates() {
|
|||||||
>
|
>
|
||||||
Önizleme
|
Önizleme
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
color="info"
|
||||||
|
startIcon={<SendIcon />}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTemplate(template);
|
||||||
|
setTestMailDialogOpen(true);
|
||||||
|
}}
|
||||||
|
sx={{ mr: 1 }}
|
||||||
|
>
|
||||||
|
Test Mail
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
startIcon={<Edit />}
|
startIcon={<Edit />}
|
||||||
@@ -445,6 +547,153 @@ function Templates() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</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>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user