- Next.js 14+ with App Router and TypeScript - Tailwind CSS and ShadCN UI styling - Zustand state management - Dexie.js for IndexedDB (local-first data) - Auth.js v5 for authentication - BMAD framework integration Co-Authored-By: Claude <noreply@anthropic.com>
119 lines
4.5 KiB
TypeScript
119 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from 'react';
|
|
import { ChatWindow } from '@/components/features/chat/chat-window';
|
|
import { ChatInput } from '@/components/features/chat/chat-input';
|
|
import { useSessionStore, useActiveSessionId, useTeacherStatus } from '@/store/use-session';
|
|
import { ChatService } from '@/services/chat-service';
|
|
import { toast } from 'sonner';
|
|
import { Button } from "@/components/ui/button";
|
|
import { DraftViewSheet } from "@/components/features/draft/DraftViewSheet";
|
|
import { useChatStore } from "@/lib/store/chat-store";
|
|
import { CheckCircle, Loader2, ArrowLeft, Sparkles } from "lucide-react";
|
|
import Link from "next/link";
|
|
|
|
export default function ChatPage() {
|
|
const activeSessionId = useActiveSessionId();
|
|
const teacherStatus = useTeacherStatus();
|
|
const { setActiveSession } = useSessionStore((s) => s.actions);
|
|
const isDrafting = useChatStore((s) => s.isDrafting);
|
|
|
|
// Initialize Session on Mount
|
|
useEffect(() => {
|
|
const initSession = async () => {
|
|
if (!activeSessionId) {
|
|
try {
|
|
const newSessionId = await ChatService.createSession();
|
|
setActiveSession(newSessionId);
|
|
} catch (error) {
|
|
console.error("Failed to create session:", error);
|
|
toast.error("Failed to start session. Check your database.");
|
|
}
|
|
}
|
|
};
|
|
initSession();
|
|
}, [activeSessionId, setActiveSession]);
|
|
|
|
const handleSend = async (message: string) => {
|
|
if (!activeSessionId) return;
|
|
|
|
try {
|
|
await ChatService.sendMessage(message, activeSessionId);
|
|
} catch (error: any) {
|
|
console.error(error);
|
|
if (error.message === 'AI Provider not configured') {
|
|
toast.error("Please configure your AI Provider in Settings", {
|
|
action: {
|
|
label: "Go to Settings",
|
|
onClick: () => window.location.href = '/settings'
|
|
}
|
|
});
|
|
} else {
|
|
toast.error("Failed to send message. Please check connection.");
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleFinishSession = async () => {
|
|
if (!activeSessionId) return;
|
|
|
|
try {
|
|
toast.info("Generating your learning summary...");
|
|
// Ensure store has latest messages for this session
|
|
await useChatStore.getState().hydrate(activeSessionId);
|
|
// Trigger Ghostwriter
|
|
await useChatStore.getState().generateDraft(activeSessionId);
|
|
} catch (error) {
|
|
console.error("Failed to generate draft:", error);
|
|
toast.error("Failed to generate summary. Please try again.");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-screen bg-background">
|
|
{/* Session Header */}
|
|
<div className="flex items-center justify-between px-4 py-3 bg-white border-b border-slate-200 shrink-0">
|
|
<div className="flex items-center gap-3">
|
|
<Link href="/" className="text-slate-500 hover:text-slate-700 transition-colors">
|
|
<ArrowLeft className="w-5 h-5" />
|
|
</Link>
|
|
<div className="font-medium text-slate-700">
|
|
Current Session
|
|
</div>
|
|
</div>
|
|
<Button
|
|
onClick={handleFinishSession}
|
|
disabled={isDrafting}
|
|
variant="default"
|
|
size="sm"
|
|
className="bg-indigo-600 hover:bg-indigo-700"
|
|
>
|
|
{isDrafting ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
Drafting...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Sparkles className="w-4 h-4 mr-2" />
|
|
Draft Post
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Chat Messages - Scrollable Area */}
|
|
<div className="flex-1 overflow-hidden">
|
|
<ChatWindow sessionId={activeSessionId} />
|
|
</div>
|
|
|
|
<DraftViewSheet />
|
|
|
|
{/* Chat Input - Fixed at Bottom */}
|
|
<div className="shrink-0 bg-white border-t border-slate-200">
|
|
<ChatInput onSend={handleSend} isLoading={teacherStatus !== 'idle'} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|