tahamajs/IE / Projects /front /src /components /authoring /AIWritingAssistantPanel.tsx
tahamajs's picture
download
raw
6.28 kB
// src/components/authoring/AIWritingAssistantPanel.tsx
import { useState } from "react";
import { Sparkles, Loader2, Check, X, ChevronDown, Wand2, List, RefreshCw, FileText, Table2, Lightbulb } from "lucide-react";
import type { AISuggestionType } from "@/types/authoring";
interface AIWritingAssistantPanelProps {
suggestions: any[];
loading: boolean;
activeType: AISuggestionType | null;
appliedCount: number;
requestSuggestion: (type: AISuggestionType, prompt?: string) => void;
applySuggestion: (s: any) => void;
dismissSuggestion: (id: string) => void;
clearSuggestions: () => void;
}
const QUICK_ACTIONS: { type: AISuggestionType; icon: typeof Wand2; label: string; description: string }[] = [
{ type: "expand_bullets", icon: List, label: "Expand Bullets", description: "Convert bullet points to full paragraphs" },
{ type: "rephrase", icon: RefreshCw, label: "Rephrase", description: "Improve clarity of selected text" },
{ type: "generate_table", icon: Table2, label: "Generate Table", description: "Create a LaTeX table from description" },
{ type: "improve_clarity", icon: Wand2, label: "Improve Clarity", description: "Polish language and flow" },
{ type: "suggest_title", icon: Lightbulb, label: "Suggest Title", description: "Generate alternative titles" },
{ type: "generate_abstract", icon: FileText, label: "Generate Abstract", description: "Create an abstract from content" },
];
export function AIWritingAssistantPanel({ suggestions, loading, activeType, appliedCount, requestSuggestion, applySuggestion, dismissSuggestion, clearSuggestions }: AIWritingAssistantPanelProps) {
const [customPrompt, setCustomPrompt] = useState("");
const [selectedType, setSelectedType] = useState<AISuggestionType>("rephrase");
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 h-full">
{/* Quick actions */}
<div className="space-y-2">
<h4 className="text-sm font-semibold flex items-center gap-2"><Wand2 className="w-4 h-4 text-purple-500"/> Quick Actions</h4>
<div className="grid gap-1.5">
{QUICK_ACTIONS.map(action => (
<button key={action.type} onClick={() => requestSuggestion(action.type)} disabled={loading}
className="flex items-start gap-3 p-2.5 rounded-xl text-left hover:bg-white/10 transition disabled:opacity-50 border border-white/10">
<action.icon className="w-4 h-4 text-purple-500 mt-0.5 shrink-0"/>
<div>
<p className="text-sm font-medium">{action.label}</p>
<p className="text-[10px] text-gray-500">{action.description}</p>
</div>
</button>
))}
</div>
<div className="flex gap-1.5 pt-2">
<input type="text" value={customPrompt} onChange={e => setCustomPrompt(e.target.value)}
placeholder="Custom instruction..." className="flex-1 text-xs px-3 py-1.5 bg-white/10 rounded-lg" />
<button onClick={() => { if (customPrompt.trim()) requestSuggestion("rephrase", customPrompt.trim()); setCustomPrompt(""); }}
disabled={!customPrompt.trim() || loading} className="btn btn-primary btn-xs gap-1">
{loading ? <Loader2 className="w-3 h-3 animate-spin"/> : <Wand2 className="w-3 h-3"/>} Go
</button>
</div>
</div>
{/* Suggestions list */}
<div className="lg:col-span-2 space-y-2">
<div className="flex items-center justify-between">
<h4 className="text-sm font-semibold flex items-center gap-2">
<Sparkles className="w-4 h-4 text-purple-500"/> Suggestions ({suggestions.length})
{appliedCount > 0 && <span className="text-[10px] text-green-500">({appliedCount} applied)</span>}
</h4>
{suggestions.length > 0 && (
<button onClick={clearSuggestions} className="text-xs text-gray-400 hover:text-red-500">Clear all</button>
)}
</div>
{loading && (
<div className="flex items-center gap-3 p-4 rounded-xl bg-purple-50/30 dark:bg-purple-900/10 border border-purple-200 dark:border-purple-800">
<Loader2 className="w-5 h-5 animate-spin text-purple-500"/>
<div>
<p className="text-sm font-medium text-purple-700 dark:text-purple-400">AI is thinking...</p>
<p className="text-xs text-purple-500">{activeType ? activeType.replace(/_/g, " ") : "Processing"}</p>
</div>
</div>
)}
<div className="space-y-2 max-h-64 overflow-y-auto">
{suggestions.map(s => (
<div key={s.id} className={`rounded-xl border p-3 transition ${s.applied ? "opacity-60 border-green-200 dark:border-green-800 bg-green-50/30 dark:bg-green-900/5" : "border-white/20 bg-white/5"}`}>
<div className="flex items-center justify-between mb-2">
<span className="text-[10px] px-2 py-0.5 rounded-full bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400">{s.type.replace(/_/g, " ")}</span>
<span className="text-[9px] text-gray-400">{(s.confidence * 100).toFixed(0)}% confidence</span>
</div>
{s.originalText && (
<p className="text-xs text-gray-500 line-through mb-1.5">{s.originalText.substring(0, 100)}...</p>
)}
<p className="text-sm text-gray-800 dark:text-gray-200">{s.suggestedText.substring(0, 200)}</p>
{!s.applied && (
<div className="flex gap-1.5 mt-2">
<button onClick={() => applySuggestion(s)} className="btn btn-primary btn-xs gap-1"><Check className="w-3 h-3"/> Apply</button>
<button onClick={() => dismissSuggestion(s.id)} className="btn btn-ghost btn-xs gap-1"><X className="w-3 h-3"/> Dismiss</button>
</div>
)}
</div>
))}
{suggestions.length === 0 && !loading && (
<div className="text-center py-8 text-gray-400">
<Sparkles className="w-8 h-8 mx-auto mb-2 opacity-30"/>
<p className="text-sm">No suggestions yet. Select an action or select text and ask for help.</p>
</div>
)}
</div>
</div>
</div>
);
}

Xet Storage Details

Size:
6.28 kB
·
Xet hash:
16f390af603af69d364c3223cd34e8f014125073e68fd6646a402c0fc413dbb4

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.