fix: auto-tagging provider resolution, email test flow, language detection
Auto-tagging: - CRITICAL FIX: contextual-auto-tag.service.ts was calling getAIProvider() (alias for getEmbeddingsProvider) instead of getTagsProvider(). This meant auto-tagging used the embeddings provider/model instead of the tags one. Now correctly uses getTagsProvider() in both suggestFromExistingLabels and suggestNewLabels methods. - Pass user's detected language to suggestLabels() for localized prompts (was hardcoded to 'en') Email: - Fix Resend "from" field: pass DB config to sendViaResend() instead of re-fetching from DB. Uses SMTP_FROM from config, with localhost-aware fallback. - Add "Sender email" field in admin Resend section so users can set SMTP_FROM - Save SMTP_FROM when Resend is selected (was only saved for SMTP mode) - Test email button now saves config to DB BEFORE testing, so unsaved form values are used (was reading stale DB values) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -277,6 +277,8 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
|||||||
if (emailProvider === 'resend') {
|
if (emailProvider === 'resend') {
|
||||||
const key = formData.get('RESEND_API_KEY') as string
|
const key = formData.get('RESEND_API_KEY') as string
|
||||||
if (key) data.RESEND_API_KEY = key
|
if (key) data.RESEND_API_KEY = key
|
||||||
|
const from = formData.get('SMTP_FROM') as string
|
||||||
|
if (from) data.SMTP_FROM = from
|
||||||
} else {
|
} else {
|
||||||
data.SMTP_HOST = formData.get('SMTP_HOST') as string
|
data.SMTP_HOST = formData.get('SMTP_HOST') as string
|
||||||
data.SMTP_PORT = formData.get('SMTP_PORT') as string
|
data.SMTP_PORT = formData.get('SMTP_PORT') as string
|
||||||
@@ -300,6 +302,32 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
|||||||
const handleTestEmail = async () => {
|
const handleTestEmail = async () => {
|
||||||
setIsTesting(true)
|
setIsTesting(true)
|
||||||
try {
|
try {
|
||||||
|
// Save email config to DB first so test uses the latest values
|
||||||
|
const emailForm = document.querySelector('form[name="email-form"]') as HTMLFormElement
|
||||||
|
if (emailForm) {
|
||||||
|
const formData = new FormData(emailForm)
|
||||||
|
const saveData: Record<string, string> = { EMAIL_PROVIDER: emailProvider }
|
||||||
|
if (emailProvider === 'resend') {
|
||||||
|
const key = formData.get('RESEND_API_KEY') as string
|
||||||
|
if (key) saveData.RESEND_API_KEY = key
|
||||||
|
const from = formData.get('SMTP_FROM') as string
|
||||||
|
if (from) saveData.SMTP_FROM = from
|
||||||
|
} else {
|
||||||
|
saveData.SMTP_HOST = formData.get('SMTP_HOST') as string
|
||||||
|
saveData.SMTP_PORT = formData.get('SMTP_PORT') as string
|
||||||
|
saveData.SMTP_USER = formData.get('SMTP_USER') as string
|
||||||
|
saveData.SMTP_PASS = formData.get('SMTP_PASS') as string
|
||||||
|
saveData.SMTP_FROM = formData.get('SMTP_FROM') as string
|
||||||
|
saveData.SMTP_IGNORE_CERT = smtpIgnoreCert ? 'true' : 'false'
|
||||||
|
saveData.SMTP_SECURE = smtpSecure ? 'true' : 'false'
|
||||||
|
}
|
||||||
|
const saveResult = await updateSystemConfig(saveData)
|
||||||
|
if (saveResult.error) {
|
||||||
|
toast.error('Failed to save settings before testing: ' + saveResult.error)
|
||||||
|
setIsTesting(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
const result: any = await testEmail(emailProvider)
|
const result: any = await testEmail(emailProvider)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success(t('admin.smtp.testSuccess'))
|
toast.success(t('admin.smtp.testSuccess'))
|
||||||
@@ -892,7 +920,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
|||||||
<CardTitle>{t('admin.email.title')}</CardTitle>
|
<CardTitle>{t('admin.email.title')}</CardTitle>
|
||||||
<CardDescription>{t('admin.email.description')}</CardDescription>
|
<CardDescription>{t('admin.email.description')}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<form onSubmit={(e) => { e.preventDefault(); handleSaveEmail(new FormData(e.currentTarget)) }}>
|
<form name="email-form" onSubmit={(e) => { e.preventDefault(); handleSaveEmail(new FormData(e.currentTarget)) }}>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">{t('admin.email.provider')}</label>
|
<label className="text-sm font-medium">{t('admin.email.provider')}</label>
|
||||||
@@ -957,6 +985,11 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
|||||||
<Input id="RESEND_API_KEY" name="RESEND_API_KEY" type="password" defaultValue={config.RESEND_API_KEY || ''} placeholder="re_..." />
|
<Input id="RESEND_API_KEY" name="RESEND_API_KEY" type="password" defaultValue={config.RESEND_API_KEY || ''} placeholder="re_..." />
|
||||||
<p className="text-xs text-muted-foreground">{t('admin.resend.apiKeyHint')}</p>
|
<p className="text-xs text-muted-foreground">{t('admin.resend.apiKeyHint')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="SMTP_FROM_RESEND" className="text-sm font-medium">Sender email</label>
|
||||||
|
<Input id="SMTP_FROM_RESEND" name="SMTP_FROM" defaultValue={config.SMTP_FROM || ''} placeholder="noreply@yourdomain.com" />
|
||||||
|
<p className="text-xs text-muted-foreground">Email address used as sender. Must be a domain verified in Resend.</p>
|
||||||
|
</div>
|
||||||
{config.RESEND_API_KEY && (
|
{config.RESEND_API_KEY && (
|
||||||
<div className="flex items-center gap-2 text-xs text-green-600">
|
<div className="flex items-center gap-2 text-xs text-green-600">
|
||||||
<span className="inline-block w-2 h-2 rounded-full bg-green-500" />
|
<span className="inline-block w-2 h-2 rounded-full bg-green-500" />
|
||||||
|
|||||||
@@ -509,10 +509,26 @@ export async function createNote(data: {
|
|||||||
console.log('[BG] Auto-labeling check: enabled=', autoLabelingEnabled, 'confidence=', autoLabelingConfidence, 'notebookId=', notebookId)
|
console.log('[BG] Auto-labeling check: enabled=', autoLabelingEnabled, 'confidence=', autoLabelingConfidence, 'notebookId=', notebookId)
|
||||||
|
|
||||||
if (autoLabelingEnabled) {
|
if (autoLabelingEnabled) {
|
||||||
|
// Detect user's language from their existing notes for localized prompts
|
||||||
|
let userLang = 'en'
|
||||||
|
try {
|
||||||
|
const langResult = await prisma.note.groupBy({
|
||||||
|
by: ['language'],
|
||||||
|
where: { userId, language: { not: null } },
|
||||||
|
_count: true,
|
||||||
|
orderBy: { _count: { language: 'desc' } },
|
||||||
|
take: 1,
|
||||||
|
})
|
||||||
|
if (langResult.length > 0 && langResult[0].language) {
|
||||||
|
userLang = langResult[0].language
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
const suggestions = await contextualAutoTagService.suggestLabels(
|
const suggestions = await contextualAutoTagService.suggestLabels(
|
||||||
content,
|
content,
|
||||||
notebookId,
|
notebookId,
|
||||||
userId
|
userId,
|
||||||
|
userLang
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('[BG] Auto-labeling suggestions:', suggestions.length, suggestions.map(s => s.label))
|
console.log('[BG] Auto-labeling suggestions:', suggestions.length, suggestions.map(s => s.label))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { prisma } from '@/lib/prisma'
|
import { prisma } from '@/lib/prisma'
|
||||||
import { getAIProvider } from '@/lib/ai/factory'
|
import { getTagsProvider } from '@/lib/ai/factory'
|
||||||
import { getSystemConfig } from '@/lib/config'
|
import { getSystemConfig } from '@/lib/config'
|
||||||
|
|
||||||
export interface LabelSuggestion {
|
export interface LabelSuggestion {
|
||||||
@@ -77,7 +77,7 @@ export class ContextualAutoTagService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const config = await getSystemConfig()
|
const config = await getSystemConfig()
|
||||||
const provider = getAIProvider(config)
|
const provider = getTagsProvider(config)
|
||||||
|
|
||||||
// Use generateText with JSON response
|
// Use generateText with JSON response
|
||||||
const response = await provider.generateText(prompt)
|
const response = await provider.generateText(prompt)
|
||||||
@@ -161,7 +161,7 @@ export class ContextualAutoTagService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const config = await getSystemConfig()
|
const config = await getSystemConfig()
|
||||||
const provider = getAIProvider(config)
|
const provider = getTagsProvider(config)
|
||||||
|
|
||||||
// Use generateText with JSON response
|
// Use generateText with JSON response
|
||||||
const response = await provider.generateText(prompt)
|
const response = await provider.generateText(prompt)
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ export async function sendEmail({ to, subject, html, attachments }: MailOptions,
|
|||||||
// Force Resend (no fallback)
|
// Force Resend (no fallback)
|
||||||
if (provider === 'resend') {
|
if (provider === 'resend') {
|
||||||
if (!resendKey) return { success: false, error: 'No Resend API key configured' };
|
if (!resendKey) return { success: false, error: 'No Resend API key configured' };
|
||||||
return sendViaResend(resendKey, { to, subject, html, attachments });
|
return sendViaResend(resendKey, config, { to, subject, html, attachments });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto: try Resend, fall back to SMTP
|
// Auto: try Resend, fall back to SMTP
|
||||||
if (resendKey) {
|
if (resendKey) {
|
||||||
const result = await sendViaResend(resendKey, { to, subject, html, attachments });
|
const result = await sendViaResend(resendKey, config, { to, subject, html, attachments });
|
||||||
if (result.success) return result;
|
if (result.success) return result;
|
||||||
|
|
||||||
console.warn('[Mail] Resend failed, falling back to SMTP:', result.error);
|
console.warn('[Mail] Resend failed, falling back to SMTP:', result.error);
|
||||||
@@ -56,14 +56,13 @@ export async function sendEmail({ to, subject, html, attachments }: MailOptions,
|
|||||||
return sendViaSMTP(config, { to, subject, html, attachments });
|
return sendViaSMTP(config, { to, subject, html, attachments });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendViaResend(apiKey: string, { to, subject, html, attachments }: MailOptions): Promise<MailResult> {
|
async function sendViaResend(apiKey: string, config: Record<string, string>, { to, subject, html, attachments }: MailOptions): Promise<MailResult> {
|
||||||
try {
|
try {
|
||||||
const { Resend } = await import('resend');
|
const { Resend } = await import('resend');
|
||||||
const resend = new Resend(apiKey);
|
const resend = new Resend(apiKey);
|
||||||
|
|
||||||
// Build a valid "from" address for Resend
|
// Build a valid "from" address for Resend
|
||||||
// Priority: SMTP_FROM from DB config > env var > derived from NEXTAUTH_URL > Resend default
|
// Priority: SMTP_FROM from DB config > env var > derived from NEXTAUTH_URL > Resend default
|
||||||
const config = await getSystemConfig();
|
|
||||||
const smtpFrom = config.SMTP_FROM || process.env.SMTP_FROM;
|
const smtpFrom = config.SMTP_FROM || process.env.SMTP_FROM;
|
||||||
let from: string;
|
let from: string;
|
||||||
if (smtpFrom) {
|
if (smtpFrom) {
|
||||||
|
|||||||
Reference in New Issue
Block a user