feat: Implement AI-powered comment rewriting and a collapsible comment form for user feedback.

This commit is contained in:
Hördle Bot
2025-12-04 08:54:25 +01:00
parent b204a35628
commit 7db4e26b2c
6 changed files with 933 additions and 768 deletions

View File

@@ -10,7 +10,7 @@ export async function POST(request: NextRequest) {
if (rateLimitError) return rateLimitError;
try {
const { puzzleId, genreId, message, playerIdentifier } = await request.json();
const { puzzleId, genreId, message, playerIdentifier, originalMessage } = await request.json();
// Validate required fields
if (!puzzleId || !message || !playerIdentifier) {
@@ -28,9 +28,9 @@ export async function POST(request: NextRequest) {
{ status: 400 }
);
}
if (trimmedMessage.length > 2000) {
if (trimmedMessage.length > 300) {
return NextResponse.json(
{ error: 'Message too long. Maximum 2000 characters allowed.' },
{ error: 'Message too long. Maximum 300 characters allowed.' },
{ status: 400 }
);
}
@@ -170,13 +170,26 @@ export async function POST(request: NextRequest) {
return comment;
});
// Send Gotify notification (fire and forget)
const { sendCommentNotification } = await import('@/app/actions');
// originalMessage is already available from the initial request.json() call
// Determine genre name for notification
let genreName: string | null = null;
if (finalGenreId) {
const genreObj = await prisma.genre.findUnique({ where: { id: finalGenreId } });
if (genreObj) genreName = genreObj.name as string;
}
sendCommentNotification(Number(puzzleId), trimmedMessage, originalMessage, genreName || null);
return NextResponse.json({
success: true,
commentId: result.id
});
} catch (error) {
console.error('Error creating curator comment:', error);
// Handle unique constraint violation (shouldn't happen due to our check, but just in case)
if (error instanceof Error && error.message.includes('Unique constraint')) {
return NextResponse.json(

View File

@@ -0,0 +1,73 @@
import { NextRequest, NextResponse } from 'next/server';
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
const OPENROUTER_MODEL = 'anthropic/claude-3.5-haiku';
export async function POST(request: NextRequest) {
try {
const { message } = await request.json();
if (!message || typeof message !== 'string') {
return NextResponse.json(
{ error: 'Message is required and must be a string' },
{ status: 400 }
);
}
if (!OPENROUTER_API_KEY) {
console.error('OPENROUTER_API_KEY is not configured');
// Fallback: return original message if API key is missing
return NextResponse.json({ rewrittenMessage: message });
}
const prompt = `Rewrite the following message to express the COMPLETE OPPOSITE meaning and opinion.
If the message is negative or critical, rewrite it to be overwhelmingly positive, praising, and appreciative.
If the message is positive, rewrite it to be critical or negative.
Maintain the original language (German or English).
Return ONLY the rewritten message text, nothing else.
Message: "${message}"`;
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://hoerdle.elpatron.me',
'X-Title': 'Hördle Message Rewriter'
},
body: JSON.stringify({
model: OPENROUTER_MODEL,
messages: [
{
role: 'user',
content: prompt
}
],
temperature: 0.7,
max_tokens: 500
})
});
if (!response.ok) {
console.error('OpenRouter API error:', await response.text());
// Fallback: return original message
return NextResponse.json({ rewrittenMessage: message });
}
const data = await response.json();
let rewrittenMessage = data.choices?.[0]?.message?.content?.trim() || message;
// Add suffix
rewrittenMessage += " (autocorrected by Polite-Bot)";
return NextResponse.json({ rewrittenMessage });
} catch (error) {
console.error('Error rewriting message:', error);
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
);
}
}