From 0e40aaef968fa167e896d2311bb995b9cc5c3071 Mon Sep 17 00:00:00 2001 From: mstfyldz Date: Sun, 9 Aug 2026 10:34:55 +0300 Subject: [PATCH] feat: add case creation endpoint and auto-title from first message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- src/controllers/case.controller.ts | 20 +++++++++++ src/controllers/chat.controller.ts | 54 +++++++++++++++++++++++++++++- src/routes/case.routes.ts | 3 +- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/controllers/case.controller.ts b/src/controllers/case.controller.ts index 5db9516..95a9900 100644 --- a/src/controllers/case.controller.ts +++ b/src/controllers/case.controller.ts @@ -2,6 +2,26 @@ import { Response } from 'express'; import { supabase } from '../lib/supabase'; 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) => { try { const { caseId } = req.params; diff --git a/src/controllers/chat.controller.ts b/src/controllers/chat.controller.ts index 46d7a5a..44b5ff9 100644 --- a/src/controllers/chat.controller.ts +++ b/src/controllers/chat.controller.ts @@ -2,6 +2,40 @@ 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; @@ -139,7 +173,25 @@ export const chatMessage = async (req: AuthenticatedRequest, res: Response) => { .select().single(); 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) { console.error('Chat Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); diff --git a/src/routes/case.routes.ts b/src/routes/case.routes.ts index d70b812..707ebfe 100644 --- a/src/routes/case.routes.ts +++ b/src/routes/case.routes.ts @@ -1,9 +1,10 @@ import { Router } from 'express'; import { requireAuth } from '../middleware/auth'; -import { deleteCase } from '../controllers/case.controller'; +import { createCase, deleteCase } from '../controllers/case.controller'; const router = Router(); +router.post('/', requireAuth, createCase); router.delete('/:caseId', requireAuth, deleteCase); export default router;