From af0510e486be08dca3df86499e72a23e31baab41 Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Mon, 10 Nov 2025 21:13:58 +0300 Subject: [PATCH] feat: Add Ollama AI integration for automatic mail template generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ 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! --- OLLAMA_SETUP.md | 307 +++++++++++++++++++ README.md | 6 +- backend/src/app.js | 1 + backend/src/controllers/ollama.controller.js | 188 ++++++++++++ backend/src/routes/ollama.routes.js | 25 ++ backend/src/services/ollama.service.js | 221 +++++++++++++ frontend/src/pages/Settings.jsx | 125 +++++++- frontend/src/pages/Templates.jsx | 255 ++++++++++++++- 8 files changed, 1121 insertions(+), 7 deletions(-) create mode 100644 OLLAMA_SETUP.md create mode 100644 backend/src/controllers/ollama.controller.js create mode 100644 backend/src/routes/ollama.routes.js create mode 100644 backend/src/services/ollama.service.js diff --git a/OLLAMA_SETUP.md b/OLLAMA_SETUP.md new file mode 100644 index 0000000..d8c865a --- /dev/null +++ b/OLLAMA_SETUP.md @@ -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": "...", + "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": "Mail içeriği" +} +``` + +## 🛠️ 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. + diff --git a/README.md b/README.md index efd358c..e7763c9 100644 --- a/README.md +++ b/README.md @@ -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 - 📧 **Gmail Entegrasyonu** - App Password ile kolay mail gönderimi - 💬 **Telegram Bildirimleri** - Gerçek zamanlı tıklama bildirimleri +- 🤖 **Ollama AI Entegrasyonu** - AI ile otomatik mail şablonu oluşturma - 📊 **Detaylı İstatistikler** - IP, konum, cihaz bilgileri - 💾 **SQLite** - Tek dosya, kolay yedekleme - 🎨 **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ıç @@ -47,7 +48,7 @@ oltalama/ │ │ ├── controllers/ ✅ 7 dosya (auth, company, token, vb.) │ │ ├── models/ ✅ 6 model + ilişkiler │ │ ├── routes/ ✅ 7 route dosyası -│ │ ├── services/ ✅ Mail, Telegram, Token +│ │ ├── services/ ✅ Mail, Telegram, Token, Ollama AI │ │ ├── utils/ ✅ GeoIP, User-Agent, Token Generator │ │ └── app.js ✅ │ └── database/ ✅ oltalama.db (3 şirket, 2 şablon) @@ -239,6 +240,7 @@ node scripts/change-password.js - **Ana Doküman:** `README.md` (bu dosya) - **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) - **Nginx Proxy Manager:** `docs/NGINX_PROXY_MANAGER.md` 🔄 (Reverse proxy) - **Güvenlik Rehberi:** `SECURITY.md` 🔒 (Güvenlik en iyi uygulamaları) diff --git a/backend/src/app.js b/backend/src/app.js index 06daec0..362d614 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -87,6 +87,7 @@ app.use('/api/companies', require('./routes/company.routes')); app.use('/api/tokens', require('./routes/token.routes')); app.use('/api/templates', require('./routes/template.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')); // Public tracking route (no rate limit on this specific route) diff --git a/backend/src/controllers/ollama.controller.js b/backend/src/controllers/ollama.controller.js new file mode 100644 index 0000000..62f7e1f --- /dev/null +++ b/backend/src/controllers/ollama.controller.js @@ -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); + } +}; + diff --git a/backend/src/routes/ollama.routes.js b/backend/src/routes/ollama.routes.js new file mode 100644 index 0000000..fb659cc --- /dev/null +++ b/backend/src/routes/ollama.routes.js @@ -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; + diff --git a/backend/src/services/ollama.service.js b/backend/src/services/ollama.service.js new file mode 100644 index 0000000..e93247e --- /dev/null +++ b/backend/src/services/ollama.service.js @@ -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": "Mail içeriği buraya" +}`; + + 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(); + diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index cf74fbb..30ed8ed 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -27,10 +27,13 @@ function Settings() { gmail_from_name: '', telegram_bot_token: '', telegram_chat_id: '', + ollama_server_url: '', + ollama_model: '', }); const [loading, setLoading] = useState(true); - const [testLoading, setTestLoading] = useState({ mail: false, telegram: false }); - const [alerts, setAlerts] = useState({ mail: null, telegram: null }); + const [testLoading, setTestLoading] = useState({ mail: false, telegram: false, ollama: false }); + const [alerts, setAlerts] = useState({ mail: null, telegram: null, ollama: null }); + const [ollamaModels, setOllamaModels] = useState([]); useEffect(() => { loadSettings(); @@ -60,6 +63,8 @@ function Settings() { gmail_from_name: settingsObj.gmail_from_name || '', telegram_bot_token: settingsObj.telegram_bot_token || '', telegram_chat_id: settingsObj.telegram_chat_id || '', + ollama_server_url: settingsObj.ollama_server_url || '', + ollama_model: settingsObj.ollama_model || '', }); } catch (error) { console.error('Failed to load settings:', error); @@ -85,6 +90,10 @@ function Settings() { telegram_bot_token: settings.telegram_bot_token, telegram_chat_id: settings.telegram_chat_id, }, { 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!'); } 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) { return ( @@ -333,6 +380,80 @@ function Settings() { + + {/* Ollama Settings */} + + + + 🤖 Ollama AI Ayarları + + + AI ile mail şablonu oluşturmak için Ollama yapılandırması + + + + setSettings({ ...settings, ollama_server_url: e.target.value }) + } + helperText="Ollama sunucu adresi (varsayılan: http://localhost:11434)" + /> + + + setSettings({ ...settings, ollama_model: e.target.value }) + } + helperText="Kullanılacak Ollama model adı (örn: llama3.2, mistral, gemma)" + /> + + {alerts.ollama && ( + + {alerts.ollama.message} + + )} + + {ollamaModels.length > 0 && ( + + + Mevcut Modeller: + + {ollamaModels.map((model, idx) => ( + + • {model.name} ({(model.size / 1024 / 1024 / 1024).toFixed(1)} GB) + + ))} + + )} + + + + + + + diff --git a/frontend/src/pages/Templates.jsx b/frontend/src/pages/Templates.jsx index 49fd8ee..05321cc 100644 --- a/frontend/src/pages/Templates.jsx +++ b/frontend/src/pages/Templates.jsx @@ -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() { Mail Şablonları - + + + + @@ -273,6 +363,18 @@ function Templates() { > Önizleme + + + {/* AI Generation Dialog */} + setAiDialogOpen(false)} maxWidth="md" fullWidth> + 🤖 AI ile Mail Şablonu Oluştur + + + Ollama AI kullanarak otomatik mail şablonu oluşturun. Aşağıdaki bilgileri girin: + + + setAiForm({ ...aiForm, template_name: e.target.value })} + helperText="Şablona verilecek isim" + /> + + setAiForm({ ...aiForm, template_type: e.target.value })} + helperText="Örn: bank, hr, it_support, management" + /> + + setAiForm({ ...aiForm, company_name: e.target.value })} + helperText="Hedef şirket adı (örn: Acme Corporation)" + /> + + setAiForm({ ...aiForm, scenario: e.target.value })} + helperText="Mail senaryosu (örn: 'Şifre sıfırlama maili', 'Yeni güvenlik politikası', 'Ödül programı duyurusu')" + /> + + setAiForm({ ...aiForm, employee_info: e.target.value })} + helperText="Hedef çalışan hakkında bilgi (örn: 'İK departmanı çalışanı', 'Yönetici')" + /> + + setAiForm({ ...aiForm, custom_prompt: e.target.value })} + helperText="AI'ya özel talimatlar (örn: 'Resmi dil kullan', 'Aciliyet vurgusu yap')" + /> + + + + + + + + {/* Test Mail Dialog */} + setTestMailDialogOpen(false)} maxWidth="sm" fullWidth> + 📧 Test Maili Gönder + + + Seçili şablon için test maili gönderin. + + + {selectedTemplate && ( + + + Şablon: {selectedTemplate.name} + + + Tip: {selectedTemplate.template_type} + + + )} + + setTestMailAddress(e.target.value)} + helperText="Test mailinin gönderileceği adres" + /> + + setCompanyPlaceholder(e.target.value)} + helperText="{{company_name}} yerine kullanılacak" + /> + + setEmployeePlaceholder(e.target.value)} + helperText="{{employee_name}} yerine kullanılacak" + /> + + + + + + ); }