feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -96,3 +96,39 @@ export const endpointKeyGenerator =
const baseKey = userKeyGenerator(c);
return `${baseKey}:${endpoint}`;
};
// ─── Agent Document Keys ─────────────────────────────────────────────────────
/**
* Redis key for storing extracted document text from AI assistant uploads.
* TTL: 3600 seconds (1 hour).
*/
export const agentDocumentKey = (documentId: string): string => {
return `agent:document:${documentId}`;
};
/**
* Rate limit key for agent chat endpoint (per-user).
*/
export const agentChatKeyGenerator = (c: Context): string => {
const baseKey = userKeyGenerator(c);
return `${baseKey}:agent:chat`;
};
// ─── Dashboard / analytics cache ─────────────────────────────────────────────
/**
* Redis key for `getStudentLoginActivity` (day-of-week chart per org and window).
* Value: JSON array `{ day, count }[]`. TTL: 24h.
*/
export function dashLoginActivityKey(orgId: string, days: number): string {
return `dash:login-activity:${orgId}:${days}`;
}
/**
* Rate limit key for agent upload endpoint (per-user).
*/
export const agentUploadKeyGenerator = (c: Context): string => {
const baseKey = userKeyGenerator(c);
return `${baseKey}:agent:upload`;
};
+40
View File
@@ -0,0 +1,40 @@
/**
* Tinybird event tracking for AI assistant observability.
* Fire-and-forget: never blocks the agent, silently skipped when not configured.
*/
const TINYBIRD_TOKEN = process.env.TINYBIRD_TOKEN;
const TINYBIRD_BASE_URL = process.env.TINYBIRD_BASE_URL || 'https://api.tinybird.co';
/**
* Send an event to Tinybird for analytics and debugging.
* Safe to call without awaiting — errors are swallowed.
*/
export function trackAgentEvent(eventName: string, data: Record<string, unknown>): void {
if (!TINYBIRD_TOKEN) return;
fetch(`${TINYBIRD_BASE_URL}/v0/events?name=${eventName}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${TINYBIRD_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ...data, timestamp: new Date().toISOString() })
}).catch(() => {
// Intentionally swallowed — observability must never break the agent
});
}
// ─── Event Names ─────────────────────────────────────────────────────────────
export const AgentEvent = {
CHAT_STARTED: 'agent.chat.started',
CHAT_COMPLETED: 'agent.chat.completed',
CHAT_ERROR: 'agent.chat.error',
TOOL_CALLED: 'agent.tool.called',
TOOL_COMPLETED: 'agent.tool.completed',
TOOL_FAILED: 'agent.tool.failed',
DOCUMENT_UPLOADED: 'agent.document.uploaded',
PLAN_GENERATED: 'agent.plan.generated',
PLAN_EXECUTED: 'agent.plan.executed'
} as const;