108 lines
4.1 KiB
TypeScript
108 lines
4.1 KiB
TypeScript
import { tool } from 'ai'
|
|
import { z } from 'zod'
|
|
import { toolRegistry } from './registry'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { withAiProviderFallback } from '@/lib/ai/fallback'
|
|
import { getSystemConfig } from '@/lib/config'
|
|
|
|
toolRegistry.register({
|
|
name: 'task_extract',
|
|
description: 'Extract action items (TODOs) from notes in a notebook. Reads all notes, identifies tasks with assignees and deadlines, and creates a synthesis note.',
|
|
isInternal: true,
|
|
buildTool: (ctx) =>
|
|
tool({
|
|
description: 'Extract action items from notes in a notebook. Creates a new note with all identified tasks.',
|
|
inputSchema: z.object({
|
|
notebookId: z.string().optional().describe('Notebook ID to scan. If omitted, scans all user notes.'),
|
|
noteIds: z.array(z.string()).optional().describe('Specific note IDs to scan instead of a whole notebook.'),
|
|
locale: z.string().optional().describe('Language for the output (fr, en, es, de, etc.)'),
|
|
}),
|
|
execute: async ({ notebookId, noteIds, locale }) => {
|
|
try {
|
|
const where: any = { userId: ctx.userId, trashedAt: null }
|
|
if (noteIds && noteIds.length > 0) {
|
|
where.id = { in: noteIds }
|
|
} else if (notebookId) {
|
|
where.notebookId = notebookId
|
|
}
|
|
|
|
const notes = await prisma.note.findMany({
|
|
where,
|
|
select: { id: true, title: true, content: true },
|
|
orderBy: { updatedAt: 'desc' },
|
|
take: 50,
|
|
})
|
|
|
|
if (notes.length === 0) {
|
|
return { error: 'No notes found to analyze' }
|
|
}
|
|
|
|
const notesContext = notes.map(n =>
|
|
`[ID: ${n.id}] "${n.title}":\n${(n.content || '').slice(0, 800)}`
|
|
).join('\n\n---\n\n')
|
|
|
|
const lang = locale === 'fr' ? 'français' : locale === 'es' ? 'espagnol' : locale === 'de' ? 'allemand' : locale === 'it' ? 'italien' : locale === 'pt' ? 'portugais' : locale === 'nl' ? 'néerlandais' : locale === 'ru' ? 'russe' : locale === 'zh' ? 'chinois' : locale === 'ja' ? 'japonais' : locale === 'ar' ? 'arabe' : locale === 'fa' ? 'persan' : locale === 'hi' ? 'hindi' : 'English'
|
|
|
|
const config = await getSystemConfig()
|
|
|
|
const prompt = `You are a task extraction specialist. Analyze the following notes and extract ALL action items, tasks, and TODOs.
|
|
|
|
For each task identified, provide:
|
|
- **Task**: Clear, actionable description
|
|
- **Source**: The note title where it was found
|
|
- **Assignee**: If mentioned (otherwise "Unassigned")
|
|
- **Deadline**: If mentioned (otherwise "No deadline")
|
|
- **Priority**: High/Medium/Low based on urgency signals in the text
|
|
- **Status**: If already completed or in-progress based on context
|
|
|
|
NOTES TO ANALYZE:
|
|
${notesContext}
|
|
|
|
Respond in ${lang}. Structure the output as a clean Markdown document with:
|
|
1. A summary paragraph
|
|
2. Tasks grouped by priority (High → Medium → Low)
|
|
3. A summary table at the end
|
|
|
|
Format each task as:
|
|
### [Priority] Task Title
|
|
- **Description**: ...
|
|
- **Source note**: ...
|
|
- **Assignee**: ...
|
|
- **Deadline**: ...
|
|
- **Status**: ...`
|
|
|
|
const result = await withAiProviderFallback('tags', config, (provider) =>
|
|
provider.generateText(prompt)
|
|
)
|
|
|
|
const summaryTitle = locale === 'fr'
|
|
? `Action Items — ${new Date().toLocaleDateString('fr-FR')}`
|
|
: `Action Items — ${new Date().toLocaleDateString('en-US')}`
|
|
|
|
const createdNote = await prisma.note.create({
|
|
data: {
|
|
title: summaryTitle,
|
|
content: result,
|
|
type: 'markdown',
|
|
isMarkdown: true,
|
|
autoGenerated: true,
|
|
userId: ctx.userId,
|
|
notebookId: notebookId || null,
|
|
},
|
|
select: { id: true, title: true },
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
noteId: createdNote.id,
|
|
title: createdNote.title,
|
|
notesAnalyzed: notes.length,
|
|
tasksNoteUrl: `/notes/${createdNote.id}`,
|
|
}
|
|
} catch (e: any) {
|
|
return { error: `Task extraction failed: ${e.message}` }
|
|
}
|
|
},
|
|
}),
|
|
})
|