- Fix: Add sessionId tracking to useChatStore for draft saving - feat: Update Teacher prompt to "Funky Universal Sage (Conversational)" - feat: Update Ghostwriter prompt to "Personal Diary Reporter" with keywords - feat: Redesign DraftSheet to match history rendering style - feat: Add keywords/tags support to drafts - feat: Change draft accept behavior to redirect to homepage Co-Authored-By: Claude (glm-4.7) <noreply@anthropic.com>
177 lines
8.3 KiB
TypeScript
177 lines
8.3 KiB
TypeScript
'use client';
|
|
|
|
import { useChatStore } from '@/store/use-chat';
|
|
import { MessageCircle, Check } from 'lucide-react';
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet';
|
|
import ReactMarkdown from 'react-markdown';
|
|
import remarkGfm from 'remark-gfm';
|
|
import rehypeHighlight from 'rehype-highlight';
|
|
import rehypeRaw from 'rehype-raw';
|
|
|
|
export function DraftSheet() {
|
|
const { phase, currentDraft, setPhase, resetSession, sessionId } = useChatStore();
|
|
const isOpen = phase === 'review' && !!currentDraft;
|
|
|
|
const handleAccept = async () => {
|
|
if (!currentDraft) return;
|
|
|
|
if (!sessionId) {
|
|
console.error("No active session ID");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const { DraftService } = await import('@/lib/db/draft-service');
|
|
|
|
await DraftService.saveDraft({
|
|
sessionId,
|
|
title: currentDraft.title,
|
|
content: currentDraft.lesson,
|
|
createdAt: Date.now(),
|
|
status: 'completed',
|
|
completedAt: Date.now(),
|
|
tags: currentDraft.keywords || []
|
|
});
|
|
|
|
// Close conversation and redirect to homepage
|
|
resetSession();
|
|
window.location.href = '/';
|
|
} catch (error) {
|
|
console.error("Failed to save draft:", error);
|
|
}
|
|
};
|
|
|
|
const handleResume = () => {
|
|
// Close the sheet and go back to chat
|
|
setPhase('elicitation');
|
|
};
|
|
|
|
if (!currentDraft) return null;
|
|
|
|
// Convert DraftArtifact to Draft-like format for consistent rendering
|
|
const draftLike = {
|
|
title: currentDraft.title,
|
|
content: currentDraft.lesson,
|
|
tags: currentDraft.keywords || []
|
|
};
|
|
|
|
return (
|
|
<Sheet open={isOpen} onOpenChange={(open) => !open && handleResume()}>
|
|
<SheetContent side="right" className="w-full sm:max-w-xl overflow-y-auto p-0">
|
|
<SheetHeader className="sr-only">
|
|
<SheetTitle>Draft Review</SheetTitle>
|
|
<SheetDescription>Review your draft before saving</SheetDescription>
|
|
</SheetHeader>
|
|
|
|
<div className="p-6">
|
|
{/* Title */}
|
|
<h2 className="draft-title text-2xl sm:text-3xl font-bold text-foreground mb-6 font-serif leading-tight">
|
|
{currentDraft.title}
|
|
</h2>
|
|
|
|
{/* Insight */}
|
|
{currentDraft.insight && (
|
|
<p className="text-lg italic text-muted-foreground mb-6 border-l-4 border-primary/30 pl-4">
|
|
"{currentDraft.insight}"
|
|
</p>
|
|
)}
|
|
|
|
{/* Body content - Markdown with prose styling */}
|
|
<div className="draft-body prose prose-slate dark:prose-invert max-w-none font-serif">
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
rehypePlugins={[rehypeHighlight, rehypeRaw]}
|
|
components={{
|
|
h1: ({ node, ...props }) => (
|
|
<h1 className="text-2xl font-bold text-foreground mt-8 mb-4 first:mt-0" {...props} />
|
|
),
|
|
h2: ({ node, ...props }) => (
|
|
<h2 className="text-xl font-bold text-foreground mt-6 mb-3" {...props} />
|
|
),
|
|
h3: ({ node, ...props }) => (
|
|
<h3 className="text-lg font-semibold text-foreground mt-5 mb-2" {...props} />
|
|
),
|
|
p: ({ node, ...props }) => (
|
|
<p className="text-base leading-relaxed text-muted-foreground mb-4" {...props} />
|
|
),
|
|
code: ({ node, inline, className, children, ...props }: any) => {
|
|
if (inline) {
|
|
return (
|
|
<code
|
|
className="px-1.5 py-0.5 bg-muted text-foreground rounded text-sm font-mono"
|
|
{...props}
|
|
>
|
|
{children}
|
|
</code>
|
|
);
|
|
}
|
|
return (
|
|
<code
|
|
className={`block bg-muted text-foreground p-4 rounded-lg text-sm font-mono overflow-x-auto ${className || ''}`}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</code>
|
|
);
|
|
},
|
|
pre: ({ node, ...props }) => (
|
|
<pre className="bg-muted p-4 rounded-lg overflow-x-auto mb-4" {...props} />
|
|
),
|
|
a: ({ node, ...props }) => (
|
|
<a className="text-primary hover:underline" {...props} />
|
|
),
|
|
ul: ({ node, ...props }) => (
|
|
<ul className="list-disc list-inside mb-4 text-muted-foreground space-y-1" {...props} />
|
|
),
|
|
ol: ({ node, ...props }) => (
|
|
<ol className="list-decimal list-inside mb-4 text-muted-foreground space-y-1" {...props} />
|
|
),
|
|
blockquote: ({ node, ...props }) => (
|
|
<blockquote className="border-l-4 border-muted-foreground/30 pl-4 italic text-muted-foreground my-4" {...props} />
|
|
),
|
|
}}
|
|
>
|
|
{currentDraft.lesson}
|
|
</ReactMarkdown>
|
|
</div>
|
|
|
|
{/* Keywords/Tags */}
|
|
{currentDraft.keywords && currentDraft.keywords.length > 0 && (
|
|
<div className="flex flex-wrap gap-2 mt-6 pt-4 border-t border-border">
|
|
{currentDraft.keywords.map((keyword, index) => (
|
|
<span
|
|
key={index}
|
|
className="px-3 py-1 bg-secondary text-secondary-foreground rounded-full text-sm font-sans"
|
|
>
|
|
#{keyword}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer with buttons */}
|
|
<nav className="sticky bottom-0 flex gap-3 p-4 bg-white border-t border-slate-200 mt-auto dark:bg-zinc-950 dark:border-zinc-800">
|
|
<button
|
|
onClick={handleResume}
|
|
type="button"
|
|
className="flex-1 min-h-[44px] px-4 py-3 border border-slate-300 rounded-md text-slate-700 hover:bg-slate-50 transition-colors flex items-center justify-center gap-2 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
>
|
|
<MessageCircle className="w-5 h-5" />
|
|
<span>Resume the Talk</span>
|
|
</button>
|
|
|
|
<button
|
|
onClick={handleAccept}
|
|
type="button"
|
|
className="flex-1 min-h-[44px] px-4 py-3 bg-slate-800 text-white rounded-md hover:bg-slate-700 transition-colors flex items-center justify-center gap-2 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
|
>
|
|
<Check className="w-5 h-5" />
|
|
<span>Accept the draft</span>
|
|
</button>
|
|
</nav>
|
|
</SheetContent>
|
|
</Sheet>
|
|
);
|
|
}
|