diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index e566edb..31e5550 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -21,6 +21,7 @@ import { getCustomOpenaiModelName, } from '@/lib/config'; import { searchHandlers } from '@/lib/search'; +import { cleanupOldHistory } from '@/lib/utils/historyCleanup'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -50,6 +51,7 @@ type Body = { chatModel: ChatModel; embeddingModel: EmbeddingModel; systemInstructions: string; + isIncognito?: boolean; }; const handleEmitterEvents = async ( @@ -58,6 +60,7 @@ const handleEmitterEvents = async ( encoder: TextEncoder, aiMessageId: string, chatId: string, + isIncognito: boolean = false, ) => { let recievedMessage = ''; let sources: any[] = []; @@ -101,18 +104,21 @@ const handleEmitterEvents = async ( ); writer.close(); - db.insert(messagesSchema) - .values({ - content: recievedMessage, - chatId: chatId, - messageId: aiMessageId, - role: 'assistant', - metadata: JSON.stringify({ - createdAt: new Date(), - ...(sources && sources.length > 0 && { sources }), - }), - }) - .execute(); + // 在無痕模式下不保存助手回應到數據庫 + if (!isIncognito) { + db.insert(messagesSchema) + .values({ + content: recievedMessage, + chatId: chatId, + messageId: aiMessageId, + role: 'assistant', + metadata: JSON.stringify({ + createdAt: new Date(), + ...(sources && sources.length > 0 && { sources }), + }), + }) + .execute(); + } }); stream.on('error', (data) => { const parsedData = JSON.parse(data); @@ -149,6 +155,11 @@ const handleHistorySave = async ( files: files.map(getFileDetails), }) .execute(); + + // Trigger history cleanup for new chats (run in background) + cleanupOldHistory().catch(err => { + console.error('Background history cleanup failed:', err); + }); } const messageExists = await db.query.messages.findFirst({ @@ -286,8 +297,12 @@ export const POST = async (req: Request) => { const writer = responseStream.writable.getWriter(); const encoder = new TextEncoder(); - handleEmitterEvents(stream, writer, encoder, aiMessageId, message.chatId); - handleHistorySave(message, humanMessageId, body.focusMode, body.files); + handleEmitterEvents(stream, writer, encoder, aiMessageId, message.chatId, body.isIncognito); + + // 在無痕模式下不保存聊天記錄 + if (!body.isIncognito) { + handleHistorySave(message, humanMessageId, body.focusMode, body.files); + } return new Response(responseStream.readable, { headers: { diff --git a/src/app/api/cleanup-history/route.ts b/src/app/api/cleanup-history/route.ts new file mode 100644 index 0000000..435e940 --- /dev/null +++ b/src/app/api/cleanup-history/route.ts @@ -0,0 +1,19 @@ +import { cleanupOldHistory } from '@/lib/utils/historyCleanup'; + +export const POST = async (req: Request) => { + try { + const result = await cleanupOldHistory(); + + return Response.json({ + message: result.message, + deletedChats: result.deletedChats + }, { status: 200 }); + + } catch (err) { + console.error('An error occurred while cleaning up history:', err); + return Response.json( + { message: 'An error occurred while cleaning up history' }, + { status: 500 }, + ); + } +}; diff --git a/src/app/api/config/route.ts b/src/app/api/config/route.ts index c1e5bbd..bad18ae 100644 --- a/src/app/api/config/route.ts +++ b/src/app/api/config/route.ts @@ -9,6 +9,7 @@ import { getOpenaiApiKey, getDeepseekApiKey, getLMStudioApiEndpoint, + getHistoryRetentionDays, updateConfig, } from '@/lib/config'; import { @@ -60,6 +61,7 @@ export const GET = async (req: Request) => { config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl(); config['customOpenaiApiKey'] = getCustomOpenaiApiKey(); config['customOpenaiModelName'] = getCustomOpenaiModelName(); + config['historyRetentionDays'] = getHistoryRetentionDays(); return Response.json({ ...config }, { status: 200 }); } catch (err) { @@ -76,6 +78,9 @@ export const POST = async (req: Request) => { const config = await req.json(); const updatedConfig = { + HISTORY: { + RETENTION_DAYS: config.historyRetentionDays, + }, MODELS: { OPENAI: { API_KEY: config.openaiApiKey, diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 6f20f01..984af14 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -26,6 +26,7 @@ interface SettingsType { customOpenaiApiKey: string; customOpenaiApiUrl: string; customOpenaiModelName: string; + historyRetentionDays: number; } interface InputProps extends React.InputHTMLAttributes { @@ -512,6 +513,33 @@ const Page = () => { + +
+
+

+ History Retention (Days) +

+

+ Number of days to keep chat history when incognito mode is off (0 = keep forever) +

+ { + setConfig((prev) => ({ + ...prev!, + historyRetentionDays: parseInt(e.target.value) || 0, + })); + }} + onSave={(value) => saveConfig('historyRetentionDays', parseInt(value) || 0)} + /> +
+
+
+