Spaces:
Paused
Paused
| import axios from 'axios'; | |
| import { NeuralEvent } from '../NeuralStream.js'; | |
| const NOTION_API_KEY = process.env.NOTION_API_TOKEN; | |
| // Use the ID from the user's link if env is missing, but prioritize env | |
| const NOTION_DB_ID = process.env.NOTION_BLACKBOARD_DB || '2cb12238-fd44-812e-84fc-e2eccb857517'; | |
| export class NotionService { | |
| private static instance: NotionService; | |
| private isEnabled: boolean = false; | |
| private constructor() { | |
| this.isEnabled = !!NOTION_API_KEY; | |
| if (this.isEnabled) { | |
| console.log('π [NOTION] Service Enabled'); | |
| } else { | |
| console.warn('β οΈ [NOTION] Service Disabled (Missing NOTION_API_TOKEN)'); | |
| } | |
| } | |
| public static getInstance(): NotionService { | |
| if (!NotionService.instance) { | |
| NotionService.instance = new NotionService(); | |
| } | |
| return NotionService.instance; | |
| } | |
| /** | |
| * Pushes a high-priority event to the Notion Command Center | |
| */ | |
| public async logEvent(event: NeuralEvent): Promise<void> { | |
| if (!this.isEnabled) return; | |
| // Rate limiting / Spam protection | |
| // Only log HIGH or CRITICAL events, or specific important types | |
| const importantTypes = ['SECURITY_THREAT', 'KNOWLEDGE_GAP', 'SYSTEM_ERROR', 'DELEGATION']; | |
| if (event.severity !== 'CRITICAL' && event.severity !== 'HIGH' && !importantTypes.includes(event.type)) { | |
| return; | |
| } | |
| try { | |
| const title = `[${event.type}] ${event.source || 'System'}`; | |
| const payloadString = typeof event.payload === 'string' | |
| ? event.payload | |
| : JSON.stringify(event.payload, null, 2); | |
| await axios.post( | |
| 'https://api.notion.com/v1/pages', | |
| { | |
| parent: { database_id: NOTION_DB_ID }, | |
| properties: { | |
| // "Name" or "title" is the standard title property in Notion databases. | |
| // We try "Name" first as it's the default. | |
| Name: { | |
| title: [ | |
| { | |
| text: { | |
| content: title.substring(0, 100) // Title limit | |
| } | |
| } | |
| ] | |
| }, | |
| // We attempt to set tags/status if they exist, but wrap in try/catch logic implicitly by API ignoring unknown props (usually throws 400) | |
| // To be safe, we'll just send the title and content blocks for now to avoid schema validation errors | |
| // unless we are sure about the schema. | |
| // Strategy: Send minimal valid payload. | |
| }, | |
| children: [ | |
| { | |
| object: 'block', | |
| type: 'callout', | |
| callout: { | |
| rich_text: [{ | |
| type: 'text', | |
| text: { content: `${event.severity || 'INFO'} | ${new Date().toLocaleTimeString()}` } | |
| }], | |
| icon: { emoji: this.getEmojiForEvent(event) } | |
| } | |
| }, | |
| { | |
| object: 'block', | |
| type: 'code', | |
| code: { | |
| language: 'json', | |
| rich_text: [{ | |
| type: 'text', | |
| text: { content: payloadString.substring(0, 2000) } | |
| }] | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| headers: { | |
| 'Authorization': `Bearer ${NOTION_API_KEY}`, | |
| 'Notion-Version': '2022-06-28', | |
| 'Content-Type': 'application/json' | |
| } | |
| } | |
| ); | |
| // console.log(`π [NOTION] Logged: ${title}`); | |
| } catch (error: any) { | |
| if (error.response) { | |
| console.warn(`β οΈ [NOTION] API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`); | |
| } | |
| } | |
| } | |
| private getEmojiForEvent(event: NeuralEvent): string { | |
| if (event.type === 'SECURITY_THREAT') return 'π¨'; | |
| if (event.type === 'SYSTEM_ERROR') return 'π₯'; | |
| if (event.type === 'KNOWLEDGE_GAP') return 'π§©'; | |
| if (event.type === 'THOUGHT') return 'π§ '; | |
| if (event.severity === 'CRITICAL') return 'β‘'; | |
| return 'π'; | |
| } | |
| } | |
| export const notionService = NotionService.getInstance(); | |