feat: RTL/i18n, AI translate+undo, no-refresh saves, settings perf
- RTL: force dir=rtl on LabelFilter, NotesViewToggle, LabelManagementDialog - i18n: add missing keys (notifications, privacy, edit/preview, AI translate/undo) - Settings pages: convert to Server Components (general, appearance) + loading skeleton - AI menu: add Translate option (10 languages) + Undo AI button in toolbar - Fix: saveInline uses REST API instead of Server Action → eliminates all implicit refreshes in list mode - Fix: NotesTabsView notes sync effect preserves selected note on content changes - Fix: auto-tag suggestions now filter already-assigned labels - Fix: color change in card view uses local state (no refresh) - Fix: nav links use <Link> for prefetching (Settings, Admin) - Fix: suppress duplicate label suggestions already on note - Route: add /api/ai/translate endpoint
This commit is contained in:
@@ -45,7 +45,7 @@ export async function POST(req: NextRequest) {
|
||||
// Otherwise, use legacy auto-tagging (generates new tags)
|
||||
const config = await getSystemConfig();
|
||||
const provider = getAIProvider(config);
|
||||
const tags = await provider.generateTags(content);
|
||||
const tags = await provider.generateTags(content, language);
|
||||
|
||||
return NextResponse.json({ tags });
|
||||
} catch (error: any) {
|
||||
|
||||
30
keep-notes/app/api/ai/translate/route.ts
Normal file
30
keep-notes/app/api/ai/translate/route.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { getTagsProvider } from '@/lib/ai/factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { text, targetLanguage } = await request.json()
|
||||
|
||||
if (!text || !targetLanguage) {
|
||||
return NextResponse.json({ error: 'text and targetLanguage are required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const config = await getSystemConfig()
|
||||
const provider = getTagsProvider(config)
|
||||
|
||||
const prompt = `Translate the following text to ${targetLanguage}. Return ONLY the translated text, no explanation, no preamble, no quotes:\n\n${text}`
|
||||
|
||||
const translatedText = await provider.generateText(prompt)
|
||||
|
||||
return NextResponse.json({ translatedText: translatedText.trim() })
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message || 'Translation failed' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -101,10 +101,11 @@ export async function PUT(
|
||||
const newName = name ? name.trim() : currentLabel.name
|
||||
|
||||
// For backward compatibility, update old label field in notes if renaming
|
||||
if (name && name.trim() !== currentLabel.name && currentLabel.userId) {
|
||||
const targetUserIdPut = currentLabel.userId || currentLabel.notebook?.userId || session.user.id;
|
||||
if (name && name.trim() !== currentLabel.name && targetUserIdPut) {
|
||||
const allNotes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: currentLabel.userId,
|
||||
userId: targetUserIdPut,
|
||||
labels: { not: null }
|
||||
},
|
||||
select: { id: true, labels: true }
|
||||
@@ -197,10 +198,11 @@ export async function DELETE(
|
||||
}
|
||||
|
||||
// For backward compatibility, remove from old label field in notes
|
||||
if (label.userId) {
|
||||
const targetUserIdDel = label.userId || label.notebook?.userId || session.user.id;
|
||||
if (targetUserIdDel) {
|
||||
const allNotes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: label.userId,
|
||||
userId: targetUserIdDel,
|
||||
labels: { not: null }
|
||||
},
|
||||
select: { id: true, labels: true }
|
||||
|
||||
Reference in New Issue
Block a user