feat: add case creation endpoint and auto-title from first message

POST /api/cases lets the frontend actually create a new dava dosyası
(no working "Yeni Dosya" flow existed before). chatMessage now
generates a short title from a case's first message (like Claude/
ChatGPT auto-titling) and renames the case, returned as `newTitle`.
This commit is contained in:
mstfyldz
2026-08-09 10:34:55 +03:00
parent 7b354d4f96
commit 0e40aaef96
3 changed files with 75 additions and 2 deletions
+20
View File
@@ -2,6 +2,26 @@ import { Response } from 'express';
import { supabase } from '../lib/supabase'; import { supabase } from '../lib/supabase';
import { AuthenticatedRequest } from '../middleware/auth'; import { AuthenticatedRequest } from '../middleware/auth';
export const createCase = async (req: AuthenticatedRequest, res: Response) => {
try {
const userId = req.user?.id;
const title = String(req.body?.title || 'Yeni Sohbet').slice(0, 200);
// requireAuth doğrudan kullanıcının kendi id'sini set ediyor; başkasının hesabına
// dosya oluşturulamaz. Yazma frontend'in anon client'ı yerine burada (service_role
// ile) yapılıyor, cases için de RLS engeli yaşanmasın diye.
const { data, error } = await supabase
.from('cases')
.insert([{ title, user_id: userId }])
.select('id, title')
.single();
if (error) throw error;
res.json({ case: data });
} catch (error: any) {
console.error('Create Case Error:', error);
res.status(500).json({ error: error.message || 'Internal Server Error' });
}
};
export const deleteCase = async (req: AuthenticatedRequest, res: Response) => { export const deleteCase = async (req: AuthenticatedRequest, res: Response) => {
try { try {
const { caseId } = req.params; const { caseId } = req.params;
+53 -1
View File
@@ -2,6 +2,40 @@ import { Response } from 'express';
import { supabase } from '../lib/supabase'; import { supabase } from '../lib/supabase';
import { AuthenticatedRequest } from '../middleware/auth'; 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<string | null> {
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) => { export const deleteChatHistory = async (req: AuthenticatedRequest, res: Response) => {
try { try {
const { caseId } = req.params; const { caseId } = req.params;
@@ -139,7 +173,25 @@ export const chatMessage = async (req: AuthenticatedRequest, res: Response) => {
.select().single(); .select().single();
if (aiMsgError) throw aiMsgError; if (aiMsgError) throw aiMsgError;
res.json({ message: aiMessage }); // 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) { } catch (error: any) {
console.error('Chat Error:', error); console.error('Chat Error:', error);
res.status(500).json({ error: error.message || 'Internal Server Error' }); res.status(500).json({ error: error.message || 'Internal Server Error' });
+2 -1
View File
@@ -1,9 +1,10 @@
import { Router } from 'express'; import { Router } from 'express';
import { requireAuth } from '../middleware/auth'; import { requireAuth } from '../middleware/auth';
import { deleteCase } from '../controllers/case.controller'; import { createCase, deleteCase } from '../controllers/case.controller';
const router = Router(); const router = Router();
router.post('/', requireAuth, createCase);
router.delete('/:caseId', requireAuth, deleteCase); router.delete('/:caseId', requireAuth, deleteCase);
export default router; export default router;