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
103 lines
3.3 KiB
TypeScript
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()
|