import { Response } from 'express'; import { supabase } from '../lib/supabase'; import { AuthenticatedRequest } from '../middleware/auth'; // Bir dosyanın ilk mesajından kısa bir başlık üretir (Claude/ChatGPT'nin yaptığı gibi). // Başarısız olursa null döner — çağıran taraf bu durumda "Yeni Sohbet" başlığını korur, // asıl sohbet cevabını etkilemez. async function generateCaseTitle(firstMessage: string): Promise { try { 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}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: aiModel, messages: [ { role: 'system', content: 'Kullanıcının hukuki sorusunu en fazla 5 kelimelik, kısa ve açıklayıcı bir Türkçe başlığa çevir. Sadece başlığı yaz; tırnak işareti, noktalama veya başka açıklama ekleme.' }, { role: 'user', content: firstMessage.slice(0, 2000) }, ], think: false, stream: false, }), }); if (!response.ok) return null; const data = await response.json(); const raw = (data.message?.content || '').toString().trim().replace(/^["'“”]+|["'“”]+$/g, ''); if (!raw) return null; return raw.slice(0, 80); } catch (err) { console.error('Generate Case Title Error:', err); return null; } } export const deleteChatHistory = async (req: AuthenticatedRequest, res: Response) => { try { const { caseId } = req.params; if (!caseId || caseId === 'general') { return res.status(400).json({ error: 'Geçersiz caseId' }); } // requireAuth zaten bu caseId'nin isteği yapan kullanıcıya ait olduğunu doğruladı. // Silme işlemi burada (backend'de, service_role ile) yapılıyor — frontend'in anon // client'ı chat_messages'a RLS nedeniyle yazamıyor/silemiyor olabilir. const { error } = await supabase.from('chat_messages').delete().eq('case_id', caseId); if (error) throw error; res.json({ ok: true }); } catch (error: any) { console.error('Delete Chat History Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; 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' }); } // "general" = dosyasız/genel sohbet. Şemada chat_messages.case_id NOT NULL bir // cases FK'si olduğundan bu moddaki mesajları veritabanına yazmıyoruz — sadece // AI'ye iletip yanıtı doğrudan döndürüyoruz. if (caseId === 'general') { const generalSystemPrompt = 'Sen bir hukuk asistanısın. Belirli bir dava dosyasına bağlı olmayan genel hukuki soruları cevaplıyorsun. Emin olmadığın noktalarda bunu açıkça belirt, uydurma bilgi verme.'; 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}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: aiModel, messages: [ { role: 'system', content: generalSystemPrompt }, { role: 'user', content }, ], 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ı.'; return res.json({ message: { id: `general-${Date.now()}`, case_id: 'general', role: 'assistant', content: aiContent }, }); } 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; // Bu dosyanın ilk mesajıysa (kullanıcının bu isteklen önce hiç mesajı yoktu), // ilk mesajdan kısa bir başlık üretip dosyayı yeniden adlandır. let newTitle: string | null = null; const isFirstMessage = !pastMessages || pastMessages.length <= 1; if (isFirstMessage) { newTitle = await generateCaseTitle(content); if (newTitle) { const { error: titleError } = await supabase .from('cases') .update({ title: newTitle }) .eq('id', caseId); if (titleError) { console.error('Case Title Update Error:', titleError); newTitle = null; } } } res.json({ message: aiMessage, newTitle }); } catch (error: any) { console.error('Chat Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } };