Initial commit

This commit is contained in:
mstfyldz
2026-08-08 17:19:28 +03:00
commit df70520ede
19 changed files with 3926 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
import { Response } from 'express';
import { supabase } from '../lib/supabase';
import { AuthenticatedRequest } from '../middleware/auth';
export const chatMessage = async (req: AuthenticatedRequest, res: Response) => {
try {
const { caseId } = req.params;
const { content } = req.body;
const userId = req.user?.id;
if (!content || !caseId) {
return res.status(400).json({ error: 'Missing caseId or content' });
}
const { data: userMessage, error: userMsgError } = await supabase
.from('chat_messages')
.insert([{ case_id: caseId, user_id: userId, role: 'user', content }])
.select().single();
if (userMsgError) throw userMsgError;
// 1. Dosyaya ait belge analizlerini çek (EKSİK OLAN KISIM)
const { data: analyses } = await supabase
.from('analyses')
.select('summary_json')
.eq('case_id', caseId);
const { data: documents } = await supabase
.from('documents')
.select('filename, extracted_text')
.eq('case_id', caseId);
// 2. Geçmiş sohbet
const { data: pastMessages } = await supabase
.from('chat_messages')
.select('role, content')
.eq('case_id', caseId)
.order('created_at', { ascending: true })
.limit(10);
// 3. Belge bağlamını sistem promptuna göm
let documentContext = '';
if (documents && documents.length > 0) {
documentContext = '\n\nDOSYAYA AİT BELGELER:\n' +
documents.map(d => `--- ${d.filename} ---\n${d.extracted_text}`).join('\n\n');
} else {
documentContext = '\n\nBu dosyaya henüz hiçbir belge yüklenmemiştir. Genel hukuki bilgiyle cevap ver, ama bunu kullanıcıya açıkça belirt.';
}
if (analyses && analyses.length > 0) {
documentContext += '\n\nÖNCEKİ ANALİZ ÖZETİ:\n' + JSON.stringify(analyses[analyses.length - 1].summary_json);
}
const systemPrompt = `Sen bir hukuk asistanısın. Bu dava dosyasıyla ilgili kullanıcının sorularını, aşağıda verilen belge içeriğine dayanarak cevaplıyorsun. Eğer belge yoksa veya soruyu cevaplamaya yetmiyorsa, bunu açıkça belirt, uydurma bilgi verme.${documentContext}`;
const messages: any[] = [{ role: 'system', content: systemPrompt }];
if (pastMessages) {
pastMessages.forEach(m => messages.push({ role: m.role, content: m.content }));
} else {
messages.push({ role: 'user', content });
}
const aiModel = process.env.AI_MODEL || 'mizan-fixed';
// Native endpoint'e geçiş
const runpodUrl = process.env.RUNPOD_API_BASE_URL || 'https://q190env94stwis-11434.proxy.runpod.net';
const response = await fetch(`${runpodUrl}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: aiModel,
messages: messages,
think: false,
stream: false,
}),
});
if (!response.ok) {
throw new Error(`AI Model request failed: ${response.statusText}`);
}
const data = await response.json();
const aiContent = data.message?.content || 'Yanıt alınamadı.';
const { data: aiMessage, error: aiMsgError } = await supabase
.from('chat_messages')
.insert([{ case_id: caseId, user_id: userId, role: 'assistant', content: aiContent }])
.select().single();
if (aiMsgError) throw aiMsgError;
res.json({ message: aiMessage });
} catch (error: any) {
console.error('Chat Error:', error);
res.status(500).json({ error: error.message || 'Internal Server Error' });
}
};
+149
View File
@@ -0,0 +1,149 @@
import { Response } from 'express';
import { supabase } from '../lib/supabase';
import { AuthenticatedRequest } from '../middleware/auth';
import * as officeParser from 'officeparser';
import Tesseract from 'tesseract.js';
const heicConvert = require('heic-convert');
export const uploadDocument = async (req: AuthenticatedRequest, res: Response) => {
try {
const file = req.file;
const { case_id } = req.body;
const user_id = req.user?.id;
if (!file || !case_id || !user_id) {
return res.status(400).json({ error: 'Missing file or case_id' });
}
let extractedText = '';
const mimeType = file.mimetype.toLowerCase();
// IMAGE & OCR LOGIC
if (mimeType.includes('image/')) {
let imageBuffer = file.buffer;
// Convert HEIC to JPEG
if (mimeType === 'image/heic' || file.originalname.toLowerCase().endsWith('.heic')) {
imageBuffer = await heicConvert({
buffer: file.buffer,
format: 'JPEG',
quality: 1
});
}
// Run Tesseract OCR (Turkish language)
const { data: { text } } = await Tesseract.recognize(imageBuffer, 'tur');
extractedText = text;
// DOCUMENT LOGIC
} else {
try {
// officeParser natively supports pdf, docx, doc, rtf, txt, etc.
extractedText = await officeParser.parseOffice(file.buffer);
} catch (err: any) {
console.error('officeParser Error:', err);
return res.status(400).json({ error: 'Dosya formatı okunamadı veya desteklenmiyor.' });
}
}
// 2. Upload File to Supabase Storage
const filePath = `${user_id}/${Date.now()}_${file.originalname}`;
const { error: uploadError } = await supabase.storage
.from('case-documents')
.upload(filePath, file.buffer, {
contentType: file.mimetype,
});
if (uploadError) {
console.error('Storage Upload Error:', uploadError);
return res.status(500).json({ error: 'Failed to upload document to storage' });
}
// 3. Veritabanına Dosya Bilgisini Kaydet (documents tablosu)
const { data: documentRecord, error: docError } = await supabase
.from('documents')
.insert([{
case_id,
user_id,
storage_path: filePath,
filename: file.originalname,
mime_type: file.mimetype,
file_size: file.size,
ocr_status: 'done',
extracted_text: extractedText
}])
.select()
.single();
if (docError) throw docError;
// 3. DeepSeek (OpenAI) ile Hukuki Analiz Çıkarımı
// Promptu tasarlıyoruz. JSON dönmesini istiyoruz.
const systemPrompt = `
Sen uzman bir Türk avukatısın. Sana verilen dava dosyasını/belgeyi okuyup analiz edeceksin.
Çıktıyı KESİNLİKLE geçerli bir JSON formatında vermelisin. JSON şu anahtarları içermelidir:
- "taraflar": (string) Davacı ve Davalı bilgileri
- "tarihler": (string) Belgedeki kritik tarihler (Dava tarihi, tebliğ vb.)
- "hukukiDayanak": (string) Belgedeki ilgili kanun maddeleri ve hukuki temeller
- "ilkDegerlendirme": (string) Dosyaya dair ilk stratejik ve hukuki değerlendirmen
Sadece ve sadece JSON döndür, başka hiçbir açıklama ekleme.
`;
const aiModel = process.env.AI_MODEL || 'mizan-fixed';
const runpodUrl = process.env.RUNPOD_API_BASE_URL || 'https://q190env94stwis-11434.proxy.runpod.net';
const response = await fetch(`${runpodUrl}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: aiModel,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: extractedText.substring(0, 15000) }
],
think: false,
stream: false,
}),
});
if (!response.ok) {
throw new Error(`AI Model request failed: ${response.statusText}`);
}
const data = await response.json();
// Support both custom (data.message.content) and OpenAI standard (data.choices[0].message.content) formats just in case
const aiContent = data.message?.content || (data.choices && data.choices[0]?.message?.content);
let summaryJson = {};
try {
summaryJson = JSON.parse(aiContent || '{}');
} catch (e) {
console.error('Failed to parse AI response as JSON:', aiContent);
summaryJson = { error: 'AI did not return valid JSON', raw: aiContent };
}
// 4. Analiz sonucunu veritabanına kaydet (analyses tablosu)
const { data: analysisRecord, error: analysisError } = await supabase
.from('analyses')
.insert([{
case_id,
document_id: documentRecord.id,
summary_json: summaryJson,
model_used: aiModel
}])
.select()
.single();
if (analysisError) throw analysisError;
// Başarıyla frontend'e döndür
res.json({
message: 'Document processed successfully',
document: documentRecord,
analysis: analysisRecord
});
} catch (error: any) {
console.error('Upload Error:', error);
res.status(500).json({ error: error.message || 'Internal Server Error' });
}
};
+104
View File
@@ -0,0 +1,104 @@
import { Request, Response } from 'express';
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 aiModel = process.env.AI_MODEL || 'mizan-fixed';
const runpodUrl = process.env.RUNPOD_API_BASE_URL || 'https://q190env94stwis-11434.proxy.runpod.net';
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`;
try {
const aiResponse = await fetch(`${runpodUrl}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: aiModel,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: phrase }
],
think: false,
stream: false,
}),
});
if (aiResponse.ok) {
const data = await aiResponse.json();
const aiContent = data.message?.content || (data.choices && data.choices[0]?.message?.content);
if (aiContent) {
optimizedPhrase = aiContent.trim().replace(/["'\n]/g, '');
console.log(`[AI Search] Orijinal: "${phrase}" -> Optimize: "${optimizedPhrase}"`);
}
} else {
console.error('AI Query Optimization API returned status:', aiResponse.status);
}
} catch (err) {
console.error('AI Query Optimization Fetch 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' });
}
};
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' });
}
};