- Fix ChatBubble to handle non-string content with String() wrapper - Fix API route to use generateText for non-streaming requests - Add @ai-sdk/openai-compatible for non-OpenAI providers (DeepSeek, etc.) - Use Chat Completions API instead of Responses API for compatible providers - Update ChatBubble tests and fix component exports to kebab-case - Remove stale PascalCase ChatBubble.tsx file
146 lines
4.6 KiB
TypeScript
146 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { Trash2 } from 'lucide-react';
|
|
import { useChatStore } from '@/lib/store/chat-store';
|
|
import { Sheet } from './Sheet';
|
|
import { DraftContent } from './DraftContent';
|
|
import { DraftActions } from './DraftActions';
|
|
import { CopySuccessToast } from '@/components/features/feedback/CopySuccessToast';
|
|
|
|
import { DeleteConfirmDialog } from '../journal/DeleteConfirmDialog';
|
|
|
|
/**
|
|
* DraftViewSheet - Main draft view component
|
|
*
|
|
* Story 2.4: Updated to use completeDraft action with toast feedback.
|
|
* Story 3.2: Added delete functionality with confirmation dialog.
|
|
*
|
|
* Combines Sheet, DraftContent, and DraftActions to display
|
|
* the Ghostwriter's draft in a polished, reading-focused interface.
|
|
*
|
|
* Auto-opens when currentDraft transitions from null to populated.
|
|
* Integrates with ChatStore for approval/rejection actions.
|
|
*/
|
|
export function DraftViewSheet() {
|
|
const currentDraft = useChatStore((s) => s.currentDraft);
|
|
const showDraftView = useChatStore((s) => s.showDraftView);
|
|
const closeDraftView = useChatStore((s) => s.closeDraftView);
|
|
const completeDraft = useChatStore((s) => s.completeDraft);
|
|
|
|
const copyDraftToClipboard = useChatStore((s) => s.copyDraftToClipboard);
|
|
const rejectDraft = useChatStore((s) => s.rejectDraft);
|
|
// Story 3.2: Use store action for architecture compliance
|
|
const deleteDraft = useChatStore((s) => s.deleteDraft);
|
|
|
|
// Story 3.2: Delete dialog state
|
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
|
|
// Toast state for copy feedback
|
|
const [toastShow, setToastShow] = useState(false);
|
|
const [toastMessage, setToastMessage] = useState('');
|
|
|
|
// Fix: Reset toast when opening a new draft
|
|
useEffect(() => {
|
|
setToastShow(false);
|
|
}, [currentDraft, showDraftView]);
|
|
|
|
const showCopyToast = (message: string = 'Copied to clipboard!') => {
|
|
setToastMessage(message);
|
|
setToastShow(true);
|
|
};
|
|
|
|
const handleClose = () => {
|
|
closeDraftView();
|
|
};
|
|
|
|
const handleApprove = async () => {
|
|
if (currentDraft) {
|
|
// Story 2.4: Use completeDraft which copies + marks completed
|
|
await completeDraft(currentDraft.id);
|
|
showCopyToast('Copied and saved to history!');
|
|
}
|
|
};
|
|
|
|
const handleCopyOnly = async () => {
|
|
if (currentDraft) {
|
|
// Story 2.4: Copy without closing sheet or marking as completed
|
|
await copyDraftToClipboard(currentDraft.id);
|
|
showCopyToast('Copied to clipboard!');
|
|
}
|
|
};
|
|
|
|
const handleReject = () => {
|
|
if (currentDraft) {
|
|
rejectDraft(currentDraft.id);
|
|
// Add system message to chat: "What should we change?"
|
|
// This will be handled by the ChatService in story 2.3
|
|
}
|
|
};
|
|
|
|
// Story 3.2: Delete handler
|
|
const handleDelete = async () => {
|
|
if (currentDraft) {
|
|
// Use store action
|
|
const success = await deleteDraft(currentDraft.id);
|
|
|
|
if (success) {
|
|
// Close the dialog (Sheet closed by store action if current draft verified)
|
|
setShowDeleteDialog(false);
|
|
showCopyToast('Post deleted successfully!');
|
|
} else {
|
|
// Handle error - close dialog but keep sheet open
|
|
setShowDeleteDialog(false);
|
|
showCopyToast('Failed to delete post');
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!currentDraft) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Sheet open={showDraftView} onClose={handleClose}>
|
|
<DraftContent draft={currentDraft} />
|
|
{/* Story 3.2: Extended footer with delete button */}
|
|
<nav className="sticky bottom-0 flex gap-3 p-4 bg-white border-t border-slate-200">
|
|
{/* Delete button (Story 3.2) */}
|
|
<button
|
|
onClick={() => setShowDeleteDialog(true)}
|
|
type="button"
|
|
className="min-h-[44px] px-4 py-3 border border-destructive text-destructive rounded-md hover:bg-destructive/10 transition-colors flex items-center justify-center gap-2"
|
|
aria-label="Delete this draft"
|
|
>
|
|
<Trash2 className="w-5 h-5" aria-hidden="true" />
|
|
<span>Delete</span>
|
|
</button>
|
|
|
|
{/* Draft actions from original component */}
|
|
<DraftActions
|
|
onApprove={handleApprove}
|
|
onReject={handleReject}
|
|
onCopyOnly={handleCopyOnly}
|
|
/>
|
|
</nav>
|
|
</Sheet>
|
|
|
|
{/* Story 3.2: Delete confirmation dialog */}
|
|
<DeleteConfirmDialog
|
|
open={showDeleteDialog}
|
|
onOpenChange={setShowDeleteDialog}
|
|
onConfirm={handleDelete}
|
|
draftTitle={currentDraft.title}
|
|
/>
|
|
|
|
{/* Toast for copy feedback (Story 2.4) */}
|
|
<CopySuccessToast
|
|
show={toastShow}
|
|
message={toastMessage}
|
|
onClose={() => setToastShow(false)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|