Files
Momento/memento-note/lib/ai/services/notebook-publish-analyzer.service.ts
Antigravity 96e7902f01
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m22s
CI / Deploy production (on server) (push) Has been skipped
feat: publication IA (magazine/brief/essay) + fixes critique
Publication IA:
- 4 templates (magazine, brief, essay, simple) avec CSS riche
- Rewrite IA (article/exercises/tutorial/reference/mixed)
- Modération avec timeout 12s + fallback safe
- Quotas publish_enhance par tier (basic=2, pro=15, business=100)
- Détection contenu stale (hash)
- Migration DB publishedContent/publishedTemplate/publishedSourceHash

Fixes:
- cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast
- _isShared ajouté au type Note (champ virtuel serveur)
- callout colors PDF export: extraction fonction pure testable
- admin/published: guard note.userId null
- Cmd+S fonctionne en mode dialog (pas seulement fullPage)

i18n:
- 23 clés publish* traduites dans les 15 locales
- Extension Web Clipper: 13 locales mise à jour

Tests:
- callout-colors.test.ts (6 tests)
- note-visible-in-view.test.ts (5 tests)
- entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs
- 199/199 tests passent

Tracker: user-stories.md sync avec sprint-status.yaml
2026-06-28 07:32:57 +00:00

103 lines
3.3 KiB
TypeScript

import { getChatProvider } from '../factory'
import { getSystemConfig } from '@/lib/config'
export interface NoteAnalysisResult {
noteId: string
include: boolean
reason: string
}
export interface NotebookAnalysis {
notes: NoteAnalysisResult[]
description: string
}
export class NotebookPublishAnalyzerService {
async analyze(
notebookName: string,
notes: Array<{ id: string; title: string; wordCount: number; preview: string }>,
language: string,
): Promise<NotebookAnalysis> {
const lang = language || 'fr'
const langName = lang === 'fr' ? 'français' : lang === 'en' ? 'English' : lang === 'fa' ? 'فارسی' : lang === 'ar' ? 'العربية' : lang === 'de' ? 'Deutsch' : lang === 'es' ? 'Español' : lang
const notesList = notes.map((n, i) =>
`${i + 1}. [${n.id}] "${n.title || 'Sans titre'}" — ${n.wordCount} mots — "${n.preview}"`
).join('\n')
const prompt = `Tu es un éditeur web expert. Analyse ce carnet de notes "${notebookName}" et décide quelles notes méritent d'être publiées sur un site web professionnel.
NOTES DU CARNET :
${notesList}
CRITÈRES D'INCLUSION :
- Note complète (>150 mots), structurée, sans fautes évidentes
- Contenu utile pour un lecteur externe (pas juste des notes personnelles)
- Titre clair et représentatif du contenu
CRITÈRES D'EXCLUSION :
- Brouillon ou note vide (<50 mots)
- Contenu incomplet ("à compléter", "TODO", "...")
- Note purement personnelle ou de travail interne
Réponds en JSON strict :
{
"description": "Résumé du carnet en 1-2 phrases percutantes en ${langName}",
"notes": [
{ "noteId": "id_ici", "include": true, "reason": "Courte justification en ${langName}" }
]
}
Inclus toutes les notes dans le tableau "notes", même celles exclues.`
const config = await getSystemConfig()
const provider = getChatProvider(config)
const raw = await provider.generateText(prompt)
return this.parseResponse(raw, notes)
}
private parseResponse(
raw: string,
notes: Array<{ id: string }>,
): NotebookAnalysis {
const jsonMatch = raw.match(/```json\s*([\s\S]+?)\s*```/)
let jsonStr = jsonMatch ? jsonMatch[1] : raw
const s = jsonStr.indexOf('{')
const e = jsonStr.lastIndexOf('}')
if (s >= 0 && e > s) jsonStr = jsonStr.slice(s, e + 1)
try {
const parsed = JSON.parse(jsonStr)
const noteIds = new Set(notes.map(n => n.id))
const analysisNotes: NoteAnalysisResult[] = (parsed.notes || [])
.filter((n: any) => noteIds.has(n.noteId))
.map((n: any) => ({
noteId: String(n.noteId),
include: Boolean(n.include),
reason: String(n.reason || ''),
}))
// Include any notes missing from AI response with include=true as default
const analyzedIds = new Set(analysisNotes.map(n => n.noteId))
for (const note of notes) {
if (!analyzedIds.has(note.id)) {
analysisNotes.push({ noteId: note.id, include: true, reason: '' })
}
}
return {
notes: analysisNotes,
description: String(parsed.description || ''),
}
} catch {
return {
notes: notes.map(n => ({ noteId: n.id, include: true, reason: '' })),
description: '',
}
}
}
}
export const notebookPublishAnalyzerService = new NotebookPublishAnalyzerService()