feat(insights): fix DBSCAN, Persian embeddings crash, D3 physics layouts, and D3 node not found runtime error
This commit is contained in:
86
memento-note/lib/clip/analyze-clip.ts
Normal file
86
memento-note/lib/clip/analyze-clip.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import { getChatProvider } from '@/lib/ai/factory'
|
||||
|
||||
export interface ClipAnalysis {
|
||||
title: string
|
||||
summary: string
|
||||
tags: string[]
|
||||
readingTimeMinutes: number
|
||||
}
|
||||
|
||||
function parseAnalysisJson(raw: string): ClipAnalysis | null {
|
||||
const trimmed = raw.trim()
|
||||
const jsonMatch = trimmed.match(/\{[\s\S]*\}/)
|
||||
if (!jsonMatch) return null
|
||||
try {
|
||||
const parsed = JSON.parse(jsonMatch[0]) as Partial<ClipAnalysis>
|
||||
const tags = Array.isArray(parsed.tags)
|
||||
? parsed.tags.filter((t): t is string => typeof t === 'string').slice(0, 5)
|
||||
: []
|
||||
const readingTime = typeof parsed.readingTimeMinutes === 'number'
|
||||
? Math.max(1, Math.min(120, Math.round(parsed.readingTimeMinutes)))
|
||||
: 5
|
||||
return {
|
||||
title: typeof parsed.title === 'string' && parsed.title.trim() ? parsed.title.trim().slice(0, 200) : 'Web clip',
|
||||
summary: typeof parsed.summary === 'string' ? parsed.summary.trim().slice(0, 800) : '',
|
||||
tags,
|
||||
readingTimeMinutes: readingTime,
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function estimateReadingMinutes(text: string): number {
|
||||
const words = text.split(/\s+/).filter(Boolean).length
|
||||
return Math.max(1, Math.round(words / 200))
|
||||
}
|
||||
|
||||
export async function analyzeClipContent(params: {
|
||||
url: string
|
||||
title: string
|
||||
textContent: string
|
||||
}): Promise<ClipAnalysis> {
|
||||
const excerpt = params.textContent.slice(0, 6000)
|
||||
const fallbackReading = estimateReadingMinutes(params.textContent)
|
||||
|
||||
try {
|
||||
const config = await getSystemConfig()
|
||||
const provider = getChatProvider(config)
|
||||
const prompt = `You analyze web articles for a personal knowledge base. URL: ${params.url}
|
||||
Page title: ${params.title}
|
||||
|
||||
Content excerpt:
|
||||
${excerpt}
|
||||
|
||||
Respond with ONLY valid JSON (no markdown):
|
||||
{
|
||||
"title": "concise improved title",
|
||||
"summary": "max 3 sentences in the same language as the content",
|
||||
"tags": ["tag1", "tag2"],
|
||||
"readingTimeMinutes": ${fallbackReading}
|
||||
}
|
||||
|
||||
Rules: tags max 5, short lowercase labels, summary factual.`
|
||||
|
||||
const raw = await provider.generateText(prompt)
|
||||
const parsed = parseAnalysisJson(raw)
|
||||
if (parsed) {
|
||||
if (!parsed.title) parsed.title = params.title || 'Web clip'
|
||||
if (!parsed.summary && params.textContent) {
|
||||
parsed.summary = params.textContent.slice(0, 400)
|
||||
}
|
||||
if (parsed.tags.length === 0) parsed.tags = []
|
||||
return parsed
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClipAnalyze] AI failed:', error)
|
||||
}
|
||||
|
||||
return {
|
||||
title: params.title || 'Web clip',
|
||||
summary: params.textContent.slice(0, 400),
|
||||
tags: [],
|
||||
readingTimeMinutes: fallbackReading,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user