Files
tower/apps/web/app/my/ask/page.tsx
T
maaz519 f61c070428 feat: member portal Sprint 9 — Ask AI (RAG over message archive)
- AskAIQuery model + migration (logs question/answer/citations per member)
- AskAIService: RAG pipeline — retrieve via tenant-isolated SearchService (Meili),
  ground LLM in top-8 snippets with inline [n] citations, degrade gracefully when
  OPENROUTER_API_KEY absent or no context (snippet fallback)
- Shared api/common/llm-client.ts (mirrors worker's OpenRouter client)
- SearchModule now exports SearchService
- Member endpoints: GET/POST /my/ask (history + ask)
- /my/ask page + AskChat client component: question box, answer cards with sources
- Ask AI nav item (top of member nav)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 17:03:01 +05:30

50 lines
1.3 KiB
TypeScript

import { getMemberToken, getApiBaseUrl } from '../../_lib/api';
import { AskChat } from './AskChat';
import Link from 'next/link';
interface Citation {
messageId: string;
snippet: string;
senderName: string;
sourceGroupName: string;
approvedAt: number;
}
interface QA {
id: string;
question: string;
answer: string;
citations: Citation[];
createdAt: string;
}
async function fetchHistory(token: string): Promise<QA[]> {
const res = await fetch(`${getApiBaseUrl()}/my/ask`, {
headers: { Authorization: `Bearer ${token}` },
cache: 'no-store',
}).catch(() => null);
if (!res || !res.ok) return [];
return (await res.json()) as QA[];
}
export default async function AskPage() {
const token = await getMemberToken();
if (!token) return null;
const history = await fetchHistory(token);
return (
<div className="max-w-2xl mx-auto space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-xl font-semibold text-gray-900">Ask AI</h1>
<p className="text-sm text-gray-500 mt-0.5">Answers from your community&apos;s knowledge.</p>
</div>
<Link href="/my" className="text-sm text-indigo-600 hover:underline"> Dashboard</Link>
</div>
<AskChat initialHistory={history} />
</div>
);
}