import { Request, Response } from 'express'; import { AuthenticatedRequest } from '../middleware/auth'; import { callOllama } from '../lib/aiClient'; import { supabase } from '../lib/supabase'; import fs from 'fs'; import path from 'path'; export const searchPrecedents = async (req: Request, res: Response) => { try { const { phrase, court_types, page_number } = req.body; if (!phrase) { return res.status(400).json({ error: 'Arama metni (phrase) zorunludur.' }); } let optimizedPhrase = phrase; // Eğer sorgu 3 kelimeden uzunsa (doğal dil sorusu ise), AI ile anahtar kelimelere dönüştür if (phrase.split(' ').length > 3) { const systemPrompt = `Sen uzman bir Türk avukatısın. Kullanıcının uzun ve doğal dille yazdığı hukuki soruyu, Yargıtay içtihat arama motorunda en iyi sonucu verecek şekilde 2 ila en fazla 4 temel hukuki terime/anahtar kelimeye çevir. SADECE anahtar kelimeleri aralarına boşluk koyarak yaz. Noktalama işareti, açıklama veya ek kelimeler (ve, ile, vb.) kullanma. Örnek Kullanıcı: İşveren tarafından performans düşüklüğü gerekçesiyle iş sözleşmesi feshedilen işçinin işe iade davası açması halinde ispat yükü kime aittir Örnek Cevap: performans düşüklüğü ispat yükü işe iade`; // AI ile sorgu optimizasyonu isteğe bağlı bir iyileştirme — config eksikse veya // istek başarısız olursa arama yine de optimize edilmemiş phrase ile devam eder. // Çıktı birkaç kelimelik bir anahtar kelime listesi olduğu için num_predict düşük // tutuluyor — hem daha hızlı yanıt hem modelin gereksiz uzatmasını önler. try { const aiContent = await callOllama([ { role: 'system', content: systemPrompt }, { role: 'user', content: phrase }, ], { numPredict: 40, timeoutMs: 20_000 }); if (aiContent) { optimizedPhrase = aiContent.trim().replace(/["'\n]/g, ''); console.log(`[AI Search] Orijinal: "${phrase}" -> Optimize: "${optimizedPhrase}"`); } } catch (err) { console.error('AI Query Optimization Error:', err); } } // Yargı MCP REST API'sine (8001 portu veya YARGI_MCP_URL) optimize edilmiş sorgu ile istek at const mcpBaseUrl = process.env.YARGI_MCP_URL || 'http://localhost:8001'; const mcpResponse = await fetch(`${mcpBaseUrl}/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phrase: optimizedPhrase, court_types: court_types || ["YARGITAYKARARI", "DANISTAYKARAR"], page_number: page_number || 1 }) }); if (!mcpResponse.ok) { const errorData = await mcpResponse.text(); console.error('Yargı MCP API Error:', errorData); return res.status(mcpResponse.status).json({ error: 'Arama servisi hata döndürdü', details: errorData }); } const data = await mcpResponse.json(); res.json(data); } catch (error: any) { console.error('Search Precedents Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; interface PrecedentSummary { birimAdi?: string; mahkeme?: string; kararTarihiStr?: string; tarih?: string; esasNo?: string; esas_no?: string; kararNo?: string; karar_no?: string; [key: string]: unknown; } // Analiz modu: bulunan kararları kullanıcının anlattığı durumla birlikte AI'ye // göndererek kısa bir hukuki değerlendirme üretir. Maliyetli bir AI çağrısı olduğu // için requireAuth ile korunuyor (bkz. routes/precedent.routes.ts). export const analyzePrecedents = async (req: AuthenticatedRequest, res: Response) => { try { const { situation, results } = req.body as { situation?: string; results?: PrecedentSummary[] }; if (!situation || !Array.isArray(results) || results.length === 0) { return res.status(400).json({ error: 'situation ve results zorunludur.' }); } const listText = results.slice(0, 8).map((r, i) => { const mahkeme = r.birimAdi || r.mahkeme || 'Bilinmeyen Mahkeme'; const tarih = r.kararTarihiStr || r.tarih || ''; const esas = r.esasNo || r.esas_no || ''; const karar = r.kararNo || r.karar_no || ''; return `${i + 1}. ${mahkeme} (${tarih}) — Esas: ${esas}, Karar: ${karar}`; }).join('\n'); const systemPrompt = 'Sen uzman bir Türk avukatısın. Kullanıcının anlattığı hukuki durumu, aşağıda listelenen emsal karar arama sonuçları ışığında kısaca değerlendir. Hangi kararların duruma daha yakın olabileceğini belirt ve olası bir hukuki değerlendirme sun. Emin olmadığın noktalarda bunu açıkça belirt, uydurma bilgi verme. Cevabın yaklaşık 150-250 kelime olsun.'; const userPrompt = `DURUM:\n${String(situation).slice(0, 3000)}\n\nBULUNAN KARARLAR:\n${listText}`; const analysis = await callOllama([ { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }, ], { numPredict: 500 }) || 'Analiz üretilemedi.'; res.json({ analysis }); } catch (error: any) { console.error('Analyze Precedents Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; export const getPrecedentDocument = async (req: Request, res: Response) => { try { const documentId = String(req.params.documentId || ''); if (!documentId) { return res.status(400).json({ error: 'documentId zorunludur.' }); } const mcpBaseUrl = process.env.YARGI_MCP_URL || 'http://localhost:8001'; const mcpResponse = await fetch(`${mcpBaseUrl}/document/${encodeURIComponent(documentId)}`); if (!mcpResponse.ok) { const errorData = await mcpResponse.text(); console.error('Yargı MCP API Error:', errorData); return res.status(mcpResponse.status).json({ error: 'Belge servisi hata döndürdü', details: errorData }); } const data = await mcpResponse.json(); res.json(data); } catch (error: any) { console.error('Get Precedent Document Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; export const cachePrecedentDocument = async (req: Request, res: Response) => { try { const { item, content } = req.body; if (!item || !item.documentId || !content) { return res.status(400).json({ error: 'Eksik veri (item veya content)' }); } const documentId = String(item.documentId); const dir = path.join(process.cwd(), 'uploads', 'precedents'); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const filePath = path.join(dir, `${documentId}.txt`); // 1. Txt Olarak Kaydet (CDN / Sunucu depolama) if (!fs.existsSync(filePath)) { fs.writeFileSync(filePath, content, 'utf8'); // 2. Supabase DB'ye Metadata Kaydet (Lazy Caching & Meilisearch Indexing) try { const { error } = await supabase .from('cached_precedents') .upsert({ document_id: documentId, mahkeme: item.mahkeme || '', karar_no: item.karar_no || '', esas_no: item.esas_no || '', tarih: item.tarih || '', konu: item.konu || '', ozet: item.ozet || '', created_at: new Date().toISOString() }, { onConflict: 'document_id' }); if (error) { console.error('[Cache] Supabase kayıt hatası:', error.message); } else { console.log(`[Cache] Başarıyla kaydedildi: ${documentId}`); } } catch (dbErr) { console.error('[Cache] Supabase try-catch hatası:', dbErr); } } res.json({ success: true, message: 'Ön belleğe alındı' }); } catch (error: any) { console.error('Cache Precedent Document Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } };