Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ai-pdf-chatbot/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ INSFORGE_JWT_SECRET=replace-with-output-of-cli-secrets-get-JWT_SECRET
# crashing. Embeddings + chat still route through InsForge AI without
# this key.
OPENAI_API_KEY=

# ─── PostHog Analytics (optional) ──────────────────────────────────────
# Visitor / event analytics. Leave blank to disable; the app still
# working, no events are sent. Get a project token from
# https://us.posthog.com/project/<your-id>/settings/project (or your
# self-hosted instance).
NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
14 changes: 14 additions & 0 deletions ai-pdf-chatbot/app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { retrieveForQuestion, toCitations } from '@/lib/rag/retrieve';
import { RAG_SYSTEM_PROMPT, buildUserPrompt } from '@/lib/ai/prompts';
import { CHAT_MODEL } from '@/lib/ai/constants';
import { encodeNdjson } from '@/lib/stream/ndjson';
import { getPostHogClient } from '@/lib/posthog-server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
Expand Down Expand Up @@ -99,6 +100,19 @@ export async function POST(req: Request) {
const resolvedChatId = chatId;
const inputText = body.input.trim();
const docIds = body.documentIds;
const isNewChat = !body.chatId;

const posthog = getPostHogClient();
posthog.capture({
distinctId: ownerId,
event: 'chat_message_sent',
properties: {
chat_id: resolvedChatId,
workspace_id: workspaceId,
is_new_chat: isNewChat,
},
});
await posthog.shutdown();
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

const stream = new ReadableStream<Uint8Array>({
async start(controller) {
Expand Down
16 changes: 16 additions & 0 deletions ai-pdf-chatbot/app/api/documents/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { createInsforgeServerClient } from '@/lib/insforge';
import { getCurrentAuthState } from '@/lib/auth-state';
import { ingestPdf } from '@/lib/rag/ingest';
import { getPostHogClient } from '@/lib/posthog-server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
Expand Down Expand Up @@ -86,6 +87,21 @@ export async function POST(req: Request) {
buffer,
});

const posthog = getPostHogClient();
posthog.capture({
distinctId: auth.viewer.id,
event: 'document_uploaded',
properties: {
document_id: doc.id,
file_name: file.name,
file_size_bytes: file.size,
workspace_id: workspaceId,
ingest_status: ingestResult.status,
chunk_count: ingestResult.status === 'ready' ? ingestResult.chunkCount : 0,
},
});
await posthog.shutdown();
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

return NextResponse.json({
document: { id: doc.id, file_name: file.name, file_size: file.size, status: ingestResult.status },
ingest: ingestResult,
Expand Down
15 changes: 15 additions & 0 deletions ai-pdf-chatbot/app/api/flashcards/[id]/grade/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { createInsforgeServerClient } from '@/lib/insforge';
import { getCurrentAuthState } from '@/lib/auth-state';
import { schedule, type Grade } from '@/lib/srs/schedule';
import { getPostHogClient } from '@/lib/posthog-server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
Expand Down Expand Up @@ -53,6 +54,20 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
.eq('id', id);

if (upd.error) return NextResponse.json({ error: upd.error.message }, { status: 500 });

const posthog = getPostHogClient();
posthog.capture({
distinctId: auth.viewer.id ?? 'anonymous',
event: 'flashcard_graded',
properties: {
card_id: id,
grade,
next_interval_days: next.interval_days,
reps: next.reps,
},
});
await posthog.shutdown();
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

return NextResponse.json({
ok: true,
next: {
Expand Down
9 changes: 9 additions & 0 deletions ai-pdf-chatbot/app/api/workspaces/[id]/audio/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createInsforgeServerClient } from '@/lib/insforge';
import { getCurrentAuthState } from '@/lib/auth-state';
import { generateAudioScript } from '@/lib/ai/audio-script';
import { synthesizeScript } from '@/lib/audio/tts';
import { getPostHogClient } from '@/lib/posthog-server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
Expand Down Expand Up @@ -98,5 +99,13 @@ export async function POST(_req: Request, { params }: { params: Promise<{ id: st
.eq('id', id);
if (upd.error) return NextResponse.json({ error: upd.error.message }, { status: 500 });

const posthog = getPostHogClient();
posthog.capture({
distinctId: auth.viewer.id,
event: 'audio_overview_generated',
properties: { workspace_id: id, workspace_name: ws.name, document_count: docs.length, script_turn_count: script.length },
});
await posthog.shutdown();

return NextResponse.json({ audio_url: audioUrl, script, generated_at: now });
}
9 changes: 9 additions & 0 deletions ai-pdf-chatbot/app/api/workspaces/[id]/mindmap/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { createInsforgeServerClient } from '@/lib/insforge';
import { getCurrentAuthState } from '@/lib/auth-state';
import { UTILITY_MODEL } from '@/lib/ai/constants';
import { getPostHogClient } from '@/lib/posthog-server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
Expand Down Expand Up @@ -90,5 +91,13 @@ export async function POST(_req: Request, { params }: { params: Promise<{ id: st
.eq('id', id);
if (upd.error) return NextResponse.json({ error: upd.error.message }, { status: 500 });

const posthog = getPostHogClient();
posthog.capture({
distinctId: auth.viewer.id ?? 'anonymous',
event: 'mindmap_generated',
properties: { workspace_id: id, workspace_name: ws.name, document_count: docs.length },
});
await posthog.shutdown();

return NextResponse.json({ markdown, generated_at: now });
}
11 changes: 11 additions & 0 deletions ai-pdf-chatbot/app/api/workspaces/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { createInsforgeServerClient } from '@/lib/insforge';
import { getCurrentAuthState } from '@/lib/auth-state';
import { getPostHogClient } from '@/lib/posthog-server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
Expand Down Expand Up @@ -48,5 +49,15 @@ export async function POST(req: Request) {
.single();

if (error || !data) return NextResponse.json({ error: error?.message ?? 'Insert failed' }, { status: 500 });

const posthog = getPostHogClient();
const ws = data as { id: string };
posthog.capture({
distinctId: auth.viewer.id,
event: 'workspace_created',
properties: { workspace_id: ws.id, workspace_name: name },
});
await posthog.shutdown();

return NextResponse.json({ workspace: data });
}
5 changes: 4 additions & 1 deletion ai-pdf-chatbot/components/flashcards-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ChevronLeft, ChevronRight, Loader2, RefreshCw, Sparkles, X } from 'luci
import { useCallback, useEffect, useState } from 'react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import posthog from 'posthog-js';

type Card = { id: string; question: string; answer: string; sort_order: number };

Expand Down Expand Up @@ -49,9 +50,11 @@ export function FlashcardsModal({
toast.error(data.error ?? 'Failed to generate flashcards');
return;
}
setCards(data.flashcards ?? []);
const cards = data.flashcards ?? [];
setCards(cards);
setIndex(0);
setRevealed(false);
posthog.capture('flashcards_generated', { document_id: documentId, card_count: cards.length });
} catch {
toast.error('Failed to generate flashcards');
} finally {
Expand Down
6 changes: 6 additions & 0 deletions ai-pdf-chatbot/components/share-chat-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Check, Copy, Link2, Loader2, X } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import posthog from 'posthog-js';

export function ShareChatButton({
chatId,
Expand Down Expand Up @@ -56,6 +57,11 @@ export function ShareChatButton({
const next = data.chat?.share_token ?? null;
setShareToken(next);
onShareTokenChange?.(next);
if (enable) {
posthog.capture('chat_share_link_created', { chat_id: chatId });
} else {
posthog.capture('chat_share_link_revoked', { chat_id: chatId });
}
toast.success(enable ? 'Share link created' : 'Share link revoked');
} catch {
toast.error('Failed to update share state');
Expand Down
7 changes: 6 additions & 1 deletion ai-pdf-chatbot/components/sign-in-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useState } from 'react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { authClient } from '@/lib/auth-client';
import posthog from 'posthog-js';

export function SignInForm() {
const [email, setEmail] = useState('');
Expand All @@ -15,17 +16,21 @@ export function SignInForm() {
e.preventDefault();
setIsLoading(true);

const { error } = await authClient.signIn.email({
const { data, error } = await authClient.signIn.email({
email: email.trim(),
password,
});

if (error) {
toast.error(error.message ?? 'Sign in failed');
posthog.captureException(new Error(error.message ?? 'Sign in failed'));
setIsLoading(false);
return;
}

const userId = (data as { user?: { id?: string } } | null)?.user?.id ?? email.trim();
posthog.identify(userId, { email: email.trim() });
posthog.capture('user_signed_in', { email: email.trim() });
window.location.href = '/chat';
}

Expand Down
7 changes: 6 additions & 1 deletion ai-pdf-chatbot/components/sign-up-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useState } from 'react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { authClient } from '@/lib/auth-client';
import posthog from 'posthog-js';

export function SignUpForm() {
const [name, setName] = useState('');
Expand All @@ -16,18 +17,22 @@ export function SignUpForm() {
e.preventDefault();
setIsLoading(true);

const { error } = await authClient.signUp.email({
const { data, error } = await authClient.signUp.email({
email: email.trim(),
password,
name: name.trim(),
});

if (error) {
toast.error(error.message ?? 'Sign up failed');
posthog.captureException(new Error(error.message ?? 'Sign up failed'));
setIsLoading(false);
return;
}

const userId = (data as { user?: { id?: string } } | null)?.user?.id ?? email.trim();
posthog.identify(userId, { email: email.trim(), name: name.trim() });
posthog.capture('user_signed_up', { email: email.trim(), name: name.trim() });
window.location.href = '/chat';
}

Expand Down
9 changes: 9 additions & 0 deletions ai-pdf-chatbot/instrumentation-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import posthog from 'posthog-js';

posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
api_host: '/ingest',
ui_host: 'https://us.posthog.com',
defaults: '2026-01-30',
capture_exceptions: true,
debug: process.env.NODE_ENV === 'development',
});
9 changes: 9 additions & 0 deletions ai-pdf-chatbot/lib/posthog-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PostHog } from 'posthog-node';

export function getPostHogClient(): PostHog {
return new PostHog(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0,
});
}
17 changes: 17 additions & 0 deletions ai-pdf-chatbot/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ const nextConfig = {
{ protocol: 'https', hostname: 'cdn.insforge.dev' },
],
},
async rewrites() {
return [
{
source: '/ingest/static/:path*',
destination: 'https://us-assets.i.posthog.com/static/:path*',
},
{
source: '/ingest/array/:path*',
destination: 'https://us-assets.i.posthog.com/array/:path*',
},
{
source: '/ingest/:path*',
destination: 'https://us.i.posthog.com/:path*',
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
},
];
},
skipTrailingSlashRedirect: true,
};

module.exports = nextConfig;
Loading
Loading