@moontra/moonui-pro 2.8.11 → 2.8.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +148 -31
- package/package.json +1 -1
- package/src/components/rich-text-editor/index.tsx +179 -40
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
3
3
|
import * as t from 'react';
|
|
4
4
|
import t__default, { useState, useRef, useCallback, forwardRef, createContext, useEffect, useContext, useMemo, useLayoutEffect, useDebugValue, Component } from 'react';
|
|
5
5
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
6
|
-
import { ChevronDown, Info, AlertCircle, AlertTriangle, Check, X, MoreHorizontal, Loader2, Minus, Search, ChevronRight, Circle, ChevronUp, Lock, Sparkles, Plus, CreditCard, Globe, CheckCircle2, XCircle, RotateCcw, Download, Clock, HelpCircle, ChevronLeft, Calendar as Calendar$1, Edit, Trash2, MapPin, User, GripVertical, MessageCircle, Paperclip, Bold as Bold$1, Italic as Italic$1, Underline as Underline$1, Strikethrough, Code as Code$1, Type, Heading1, Heading2, Heading3, AlignLeft, AlignCenter, AlignRight, AlignJustify, List, ListOrdered, CheckSquare, Quote, Palette, Highlighter, Link2, Image as Image$1, Table as Table$1, Settings, Undo, Redo, Eye, RefreshCw, Wand2, Maximize, FileText, Briefcase, MessageSquare, Heart, GraduationCap, Zap, Languages, Lightbulb, TrendingUp, TrendingDown, ZoomOut, ZoomIn, FileSpreadsheet, FileJson, Maximize2, Move, Menu, Bell, CheckCheck, CheckCircle, Settings2, LogOut, Edit3, LayoutGrid, Upload, Share2, Save, Filter, FileDown, ArrowUp, ArrowDown, ArrowUpDown, ChevronsLeft, ChevronsRight, Pin, Sun, Moon, Monitor, Star, ExternalLink, CalendarIcon, DollarSign, Users, Github, GitFork, Activity, Server, EyeOff, RotateCw, Timer, Cpu, MemoryStick, HardDrive, Network, BarChart3, Video, Music, Archive, File, Columns, Grip, Unlock, Minimize2, Map as Map$1, Target, MoreVertical, BellOff, ArrowDownRight, ArrowUpRight } from 'lucide-react';
|
|
6
|
+
import { ChevronDown, Info, AlertCircle, AlertTriangle, Check, X, MoreHorizontal, Loader2, Minus, Search, ChevronRight, Circle, ChevronUp, Lock, Sparkles, Plus, CreditCard, Globe, CheckCircle2, XCircle, RotateCcw, Download, Clock, HelpCircle, ChevronLeft, Calendar as Calendar$1, Edit, Trash2, MapPin, User, GripVertical, MessageCircle, Paperclip, Bold as Bold$1, Italic as Italic$1, Underline as Underline$1, Strikethrough, Code as Code$1, Type, Heading1, Heading2, Heading3, AlignLeft, AlignCenter, AlignRight, AlignJustify, List, ListOrdered, CheckSquare, Quote, Palette, Highlighter, Link2, Image as Image$1, Table as Table$1, Settings, Undo, Redo, Eye, RefreshCw, Wand2, Maximize, FileText, Briefcase, MessageSquare, Heart, GraduationCap, Zap, Languages, Lightbulb, Copy, TrendingUp, TrendingDown, ZoomOut, ZoomIn, FileSpreadsheet, FileJson, Maximize2, Move, Menu, Bell, CheckCheck, CheckCircle, Settings2, LogOut, Edit3, LayoutGrid, Upload, Share2, Save, Filter, FileDown, ArrowUp, ArrowDown, ArrowUpDown, ChevronsLeft, ChevronsRight, Pin, Sun, Moon, Monitor, Star, ExternalLink, CalendarIcon, DollarSign, Users, Github, GitFork, Activity, Server, EyeOff, RotateCw, Timer, Cpu, MemoryStick, HardDrive, Network, BarChart3, Video, Music, Archive, File, Columns, Grip, Unlock, Minimize2, Map as Map$1, Target, MoreVertical, BellOff, ArrowDownRight, ArrowUpRight } from 'lucide-react';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
8
|
import { cva } from 'class-variance-authority';
|
|
9
9
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
@@ -50471,6 +50471,10 @@ function RichTextEditor({
|
|
|
50471
50471
|
}
|
|
50472
50472
|
return "en";
|
|
50473
50473
|
});
|
|
50474
|
+
const [isAiPreviewOpen, setIsAiPreviewOpen] = useState(false);
|
|
50475
|
+
const [previewContent, setPreviewContent] = useState("");
|
|
50476
|
+
const [previewAction, setPreviewAction] = useState("");
|
|
50477
|
+
const [previewOriginalText, setPreviewOriginalText] = useState("");
|
|
50474
50478
|
useEffect(() => {
|
|
50475
50479
|
return () => {
|
|
50476
50480
|
if (typingIntervalRef.current) {
|
|
@@ -50492,6 +50496,48 @@ function RichTextEditor({
|
|
|
50492
50496
|
description: "The AI response was interrupted."
|
|
50493
50497
|
});
|
|
50494
50498
|
};
|
|
50499
|
+
const shouldUseModal = (action) => {
|
|
50500
|
+
const modalActions = ["expand", "summarize", "ideas", "continue"];
|
|
50501
|
+
return modalActions.includes(action);
|
|
50502
|
+
};
|
|
50503
|
+
const applyAIContentToEditor = (content, replaceSelection = true) => {
|
|
50504
|
+
if (!editor)
|
|
50505
|
+
return;
|
|
50506
|
+
setIsTyping(true);
|
|
50507
|
+
isTypingRef.current = true;
|
|
50508
|
+
setTypingText("");
|
|
50509
|
+
if (replaceSelection) {
|
|
50510
|
+
const selection = editor.state.selection;
|
|
50511
|
+
const selectedText = editor.state.doc.textBetween(selection.from, selection.to, " ");
|
|
50512
|
+
if (selectedText) {
|
|
50513
|
+
editor.chain().focus().deleteSelection().run();
|
|
50514
|
+
}
|
|
50515
|
+
}
|
|
50516
|
+
let currentIndex = 0;
|
|
50517
|
+
const typeSpeed = 30;
|
|
50518
|
+
const typeNextChar = () => {
|
|
50519
|
+
if (currentIndex < content.length && isTypingRef.current) {
|
|
50520
|
+
const nextChar = content[currentIndex];
|
|
50521
|
+
setTypingText((prev) => prev + nextChar);
|
|
50522
|
+
editor.chain().focus().insertContent(nextChar).run();
|
|
50523
|
+
currentIndex++;
|
|
50524
|
+
setRemainingText(content.substring(currentIndex));
|
|
50525
|
+
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
50526
|
+
} else {
|
|
50527
|
+
setIsTyping(false);
|
|
50528
|
+
isTypingRef.current = false;
|
|
50529
|
+
setTypingText("");
|
|
50530
|
+
setRemainingText("");
|
|
50531
|
+
if (currentIndex >= content.length) {
|
|
50532
|
+
toast({
|
|
50533
|
+
title: "AI action completed",
|
|
50534
|
+
description: "Your text has been updated successfully."
|
|
50535
|
+
});
|
|
50536
|
+
}
|
|
50537
|
+
}
|
|
50538
|
+
};
|
|
50539
|
+
typeNextChar();
|
|
50540
|
+
};
|
|
50495
50541
|
const slashCommands = [
|
|
50496
50542
|
{
|
|
50497
50543
|
id: "rewrite",
|
|
@@ -50629,10 +50675,18 @@ function RichTextEditor({
|
|
|
50629
50675
|
const { from: from2, to } = editor.state.selection;
|
|
50630
50676
|
const selectedText = editor.state.doc.textBetween(from2, to, " ");
|
|
50631
50677
|
setIsProcessing(true);
|
|
50678
|
+
setCurrentAction(command2.id);
|
|
50632
50679
|
try {
|
|
50633
50680
|
const response = await command2.action(selectedText || editor.getText());
|
|
50634
50681
|
if (response.text) {
|
|
50635
|
-
|
|
50682
|
+
if (shouldUseModal(command2.id)) {
|
|
50683
|
+
setPreviewContent(response.text);
|
|
50684
|
+
setPreviewAction(command2.id);
|
|
50685
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
50686
|
+
setIsAiPreviewOpen(true);
|
|
50687
|
+
} else {
|
|
50688
|
+
applyAIContentToEditor(response.text, !!selectedText);
|
|
50689
|
+
}
|
|
50636
50690
|
} else if (response.error) {
|
|
50637
50691
|
toast({
|
|
50638
50692
|
title: "AI Error",
|
|
@@ -50772,36 +50826,14 @@ function RichTextEditor({
|
|
|
50772
50826
|
const result = await callAI(action, selectedText || editor.getText(), targetLanguage);
|
|
50773
50827
|
processingToast.dismiss();
|
|
50774
50828
|
if (result) {
|
|
50775
|
-
|
|
50776
|
-
|
|
50777
|
-
|
|
50778
|
-
|
|
50779
|
-
|
|
50829
|
+
if (shouldUseModal(action)) {
|
|
50830
|
+
setPreviewContent(result);
|
|
50831
|
+
setPreviewAction(action);
|
|
50832
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
50833
|
+
setIsAiPreviewOpen(true);
|
|
50834
|
+
} else {
|
|
50835
|
+
applyAIContentToEditor(result, !!selectedText);
|
|
50780
50836
|
}
|
|
50781
|
-
let currentIndex = 0;
|
|
50782
|
-
const typeSpeed = 30;
|
|
50783
|
-
const typeNextChar = () => {
|
|
50784
|
-
if (currentIndex < result.length && isTypingRef.current) {
|
|
50785
|
-
const nextChar = result[currentIndex];
|
|
50786
|
-
setTypingText((prev) => prev + nextChar);
|
|
50787
|
-
editor.chain().focus().insertContent(nextChar).run();
|
|
50788
|
-
currentIndex++;
|
|
50789
|
-
setRemainingText(result.substring(currentIndex));
|
|
50790
|
-
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
50791
|
-
} else {
|
|
50792
|
-
setIsTyping(false);
|
|
50793
|
-
isTypingRef.current = false;
|
|
50794
|
-
setTypingText("");
|
|
50795
|
-
setRemainingText("");
|
|
50796
|
-
if (currentIndex >= result.length) {
|
|
50797
|
-
toast({
|
|
50798
|
-
title: "AI action completed",
|
|
50799
|
-
description: "Your text has been updated successfully."
|
|
50800
|
-
});
|
|
50801
|
-
}
|
|
50802
|
-
}
|
|
50803
|
-
};
|
|
50804
|
-
typeNextChar();
|
|
50805
50837
|
}
|
|
50806
50838
|
};
|
|
50807
50839
|
const getActionDescription = (action, targetLanguage) => {
|
|
@@ -51887,6 +51919,91 @@ function RichTextEditor({
|
|
|
51887
51919
|
}, children: "Apply Borders" })
|
|
51888
51920
|
] })
|
|
51889
51921
|
] }) }),
|
|
51922
|
+
/* @__PURE__ */ jsx(MoonUIDialogPro, { open: isAiPreviewOpen, onOpenChange: setIsAiPreviewOpen, children: /* @__PURE__ */ jsxs(MoonUIDialogContentPro, { className: "max-w-4xl max-h-[80vh] overflow-hidden flex flex-col", children: [
|
|
51923
|
+
/* @__PURE__ */ jsxs(MoonUIDialogHeaderPro, { children: [
|
|
51924
|
+
/* @__PURE__ */ jsxs(MoonUIDialogTitlePro, { className: "flex items-center gap-2", children: [
|
|
51925
|
+
/* @__PURE__ */ jsx(Wand2, { className: "w-5 h-5 text-purple-600 dark:text-purple-400" }),
|
|
51926
|
+
"AI Preview - ",
|
|
51927
|
+
getActionDescription(previewAction).replace("...", "")
|
|
51928
|
+
] }),
|
|
51929
|
+
/* @__PURE__ */ jsx(MoonUIDialogDescriptionPro, { children: "Review the AI-generated content before applying it to your editor." })
|
|
51930
|
+
] }),
|
|
51931
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex flex-col gap-4", children: [
|
|
51932
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-shrink-0", children: [
|
|
51933
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground mb-2", children: "Original Text:" }),
|
|
51934
|
+
/* @__PURE__ */ jsx("div", { className: "p-3 bg-muted/50 rounded-md border max-h-32 overflow-auto", children: /* @__PURE__ */ jsx("p", { className: "text-sm whitespace-pre-wrap", children: previewOriginalText }) })
|
|
51935
|
+
] }),
|
|
51936
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex flex-col", children: [
|
|
51937
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm font-medium text-muted-foreground mb-2 flex items-center gap-2", children: [
|
|
51938
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "w-4 h-4" }),
|
|
51939
|
+
"AI Generated Content:"
|
|
51940
|
+
] }),
|
|
51941
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 p-4 bg-gradient-to-br from-purple-50 to-blue-50 dark:from-purple-950/50 dark:to-blue-950/50 rounded-md border-2 border-purple-200 dark:border-purple-800 overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "prose prose-sm max-w-none dark:prose-invert", children: /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap text-sm leading-relaxed", children: previewContent }) }) })
|
|
51942
|
+
] })
|
|
51943
|
+
] }),
|
|
51944
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 flex justify-between items-center pt-4 border-t", children: [
|
|
51945
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
51946
|
+
/* @__PURE__ */ jsxs(
|
|
51947
|
+
MoonUIButtonPro,
|
|
51948
|
+
{
|
|
51949
|
+
variant: "outline",
|
|
51950
|
+
size: "sm",
|
|
51951
|
+
onClick: () => {
|
|
51952
|
+
navigator.clipboard.writeText(previewContent);
|
|
51953
|
+
toast({
|
|
51954
|
+
title: "Copied to clipboard",
|
|
51955
|
+
description: "The AI-generated content has been copied."
|
|
51956
|
+
});
|
|
51957
|
+
},
|
|
51958
|
+
children: [
|
|
51959
|
+
/* @__PURE__ */ jsx(Copy, { className: "w-4 h-4 mr-2" }),
|
|
51960
|
+
"Copy"
|
|
51961
|
+
]
|
|
51962
|
+
}
|
|
51963
|
+
),
|
|
51964
|
+
/* @__PURE__ */ jsxs(
|
|
51965
|
+
MoonUIButtonPro,
|
|
51966
|
+
{
|
|
51967
|
+
variant: "outline",
|
|
51968
|
+
size: "sm",
|
|
51969
|
+
onClick: () => {
|
|
51970
|
+
setIsAiPreviewOpen(false);
|
|
51971
|
+
handleAIAction(previewAction);
|
|
51972
|
+
},
|
|
51973
|
+
children: [
|
|
51974
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "w-4 h-4 mr-2" }),
|
|
51975
|
+
"Regenerate"
|
|
51976
|
+
]
|
|
51977
|
+
}
|
|
51978
|
+
)
|
|
51979
|
+
] }),
|
|
51980
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
51981
|
+
/* @__PURE__ */ jsx(
|
|
51982
|
+
MoonUIButtonPro,
|
|
51983
|
+
{
|
|
51984
|
+
variant: "outline",
|
|
51985
|
+
onClick: () => setIsAiPreviewOpen(false),
|
|
51986
|
+
children: "Cancel"
|
|
51987
|
+
}
|
|
51988
|
+
),
|
|
51989
|
+
/* @__PURE__ */ jsxs(
|
|
51990
|
+
MoonUIButtonPro,
|
|
51991
|
+
{
|
|
51992
|
+
onClick: () => {
|
|
51993
|
+
setIsAiPreviewOpen(false);
|
|
51994
|
+
const replaceSelection = previewOriginalText !== editor?.getText();
|
|
51995
|
+
applyAIContentToEditor(previewContent, replaceSelection);
|
|
51996
|
+
},
|
|
51997
|
+
className: "bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700",
|
|
51998
|
+
children: [
|
|
51999
|
+
/* @__PURE__ */ jsx(Check, { className: "w-4 h-4 mr-2" }),
|
|
52000
|
+
"Apply to Editor"
|
|
52001
|
+
]
|
|
52002
|
+
}
|
|
52003
|
+
)
|
|
52004
|
+
] })
|
|
52005
|
+
] })
|
|
52006
|
+
] }) }),
|
|
51890
52007
|
/* @__PURE__ */ jsx(
|
|
51891
52008
|
"div",
|
|
51892
52009
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.12",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -74,7 +74,8 @@ import {
|
|
|
74
74
|
GraduationCap,
|
|
75
75
|
Zap,
|
|
76
76
|
Lightbulb,
|
|
77
|
-
X
|
|
77
|
+
X,
|
|
78
|
+
Copy
|
|
78
79
|
} from 'lucide-react';
|
|
79
80
|
import { cn } from '../../lib/utils';
|
|
80
81
|
import { Button } from '../ui/button';
|
|
@@ -442,6 +443,12 @@ export function RichTextEditor({
|
|
|
442
443
|
return 'en';
|
|
443
444
|
});
|
|
444
445
|
|
|
446
|
+
// AI Preview Modal states
|
|
447
|
+
const [isAiPreviewOpen, setIsAiPreviewOpen] = useState(false);
|
|
448
|
+
const [previewContent, setPreviewContent] = useState('');
|
|
449
|
+
const [previewAction, setPreviewAction] = useState('');
|
|
450
|
+
const [previewOriginalText, setPreviewOriginalText] = useState('');
|
|
451
|
+
|
|
445
452
|
// Clean up typewriter effect on unmount
|
|
446
453
|
useEffect(() => {
|
|
447
454
|
return () => {
|
|
@@ -466,6 +473,61 @@ export function RichTextEditor({
|
|
|
466
473
|
description: "The AI response was interrupted.",
|
|
467
474
|
});
|
|
468
475
|
};
|
|
476
|
+
|
|
477
|
+
// Check if action should use modal preview
|
|
478
|
+
const shouldUseModal = (action: string): boolean => {
|
|
479
|
+
const modalActions = ['expand', 'summarize', 'ideas', 'continue'];
|
|
480
|
+
return modalActions.includes(action);
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// Apply AI content to editor with typewriter effect
|
|
484
|
+
const applyAIContentToEditor = (content: string, replaceSelection: boolean = true) => {
|
|
485
|
+
if (!editor) return;
|
|
486
|
+
|
|
487
|
+
// Start typewriter effect
|
|
488
|
+
setIsTyping(true);
|
|
489
|
+
isTypingRef.current = true;
|
|
490
|
+
setTypingText('');
|
|
491
|
+
|
|
492
|
+
if (replaceSelection) {
|
|
493
|
+
const selection = editor.state.selection;
|
|
494
|
+
const selectedText = editor.state.doc.textBetween(selection.from, selection.to, ' ');
|
|
495
|
+
if (selectedText) {
|
|
496
|
+
editor.chain().focus().deleteSelection().run();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let currentIndex = 0;
|
|
501
|
+
const typeSpeed = 30; // ms per character
|
|
502
|
+
|
|
503
|
+
const typeNextChar = () => {
|
|
504
|
+
if (currentIndex < content.length && isTypingRef.current) {
|
|
505
|
+
const nextChar = content[currentIndex];
|
|
506
|
+
setTypingText(prev => prev + nextChar);
|
|
507
|
+
editor.chain().focus().insertContent(nextChar).run();
|
|
508
|
+
currentIndex++;
|
|
509
|
+
setRemainingText(content.substring(currentIndex));
|
|
510
|
+
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
511
|
+
} else {
|
|
512
|
+
// Typing complete or stopped
|
|
513
|
+
setIsTyping(false);
|
|
514
|
+
isTypingRef.current = false;
|
|
515
|
+
setTypingText('');
|
|
516
|
+
setRemainingText('');
|
|
517
|
+
|
|
518
|
+
if (currentIndex >= content.length) {
|
|
519
|
+
// Success toast only if completed
|
|
520
|
+
toast({
|
|
521
|
+
title: "AI action completed",
|
|
522
|
+
description: "Your text has been updated successfully.",
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// Start typing
|
|
529
|
+
typeNextChar();
|
|
530
|
+
};
|
|
469
531
|
|
|
470
532
|
// Slash commands tanımları
|
|
471
533
|
const slashCommands: SlashCommand[] = [
|
|
@@ -606,10 +668,22 @@ export function RichTextEditor({
|
|
|
606
668
|
const selectedText = editor.state.doc.textBetween(from, to, ' ');
|
|
607
669
|
|
|
608
670
|
setIsProcessing(true);
|
|
671
|
+
setCurrentAction(command.id);
|
|
672
|
+
|
|
609
673
|
try {
|
|
610
674
|
const response = await command.action(selectedText || editor.getText());
|
|
611
675
|
if (response.text) {
|
|
612
|
-
|
|
676
|
+
// Check if this command should use modal preview
|
|
677
|
+
if (shouldUseModal(command.id)) {
|
|
678
|
+
// Open preview modal
|
|
679
|
+
setPreviewContent(response.text);
|
|
680
|
+
setPreviewAction(command.id);
|
|
681
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
682
|
+
setIsAiPreviewOpen(true);
|
|
683
|
+
} else {
|
|
684
|
+
// Apply directly with typewriter effect
|
|
685
|
+
applyAIContentToEditor(response.text, !!selectedText);
|
|
686
|
+
}
|
|
613
687
|
} else if (response.error) {
|
|
614
688
|
toast({
|
|
615
689
|
title: "AI Error",
|
|
@@ -764,45 +838,17 @@ export function RichTextEditor({
|
|
|
764
838
|
processingToast.dismiss();
|
|
765
839
|
|
|
766
840
|
if (result) {
|
|
767
|
-
//
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
841
|
+
// Check if this action should use modal preview
|
|
842
|
+
if (shouldUseModal(action)) {
|
|
843
|
+
// Open preview modal instead of directly applying
|
|
844
|
+
setPreviewContent(result);
|
|
845
|
+
setPreviewAction(action);
|
|
846
|
+
setPreviewOriginalText(selectedText || editor.getText());
|
|
847
|
+
setIsAiPreviewOpen(true);
|
|
848
|
+
} else {
|
|
849
|
+
// Apply directly with typewriter effect (for rewrite, fix grammar, tone changes, etc.)
|
|
850
|
+
applyAIContentToEditor(result, !!selectedText);
|
|
774
851
|
}
|
|
775
|
-
|
|
776
|
-
let currentIndex = 0;
|
|
777
|
-
const typeSpeed = 30; // ms per character
|
|
778
|
-
|
|
779
|
-
const typeNextChar = () => {
|
|
780
|
-
if (currentIndex < result.length && isTypingRef.current) {
|
|
781
|
-
const nextChar = result[currentIndex];
|
|
782
|
-
setTypingText(prev => prev + nextChar);
|
|
783
|
-
editor.chain().focus().insertContent(nextChar).run();
|
|
784
|
-
currentIndex++;
|
|
785
|
-
setRemainingText(result.substring(currentIndex));
|
|
786
|
-
typingIntervalRef.current = setTimeout(typeNextChar, typeSpeed);
|
|
787
|
-
} else {
|
|
788
|
-
// Typing complete or stopped
|
|
789
|
-
setIsTyping(false);
|
|
790
|
-
isTypingRef.current = false;
|
|
791
|
-
setTypingText('');
|
|
792
|
-
setRemainingText('');
|
|
793
|
-
|
|
794
|
-
if (currentIndex >= result.length) {
|
|
795
|
-
// Success toast only if completed
|
|
796
|
-
toast({
|
|
797
|
-
title: "AI action completed",
|
|
798
|
-
description: "Your text has been updated successfully.",
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
};
|
|
803
|
-
|
|
804
|
-
// Start typing
|
|
805
|
-
typeNextChar();
|
|
806
852
|
}
|
|
807
853
|
};
|
|
808
854
|
|
|
@@ -1970,6 +2016,99 @@ export function RichTextEditor({
|
|
|
1970
2016
|
</DialogContent>
|
|
1971
2017
|
</Dialog>
|
|
1972
2018
|
|
|
2019
|
+
{/* AI Preview Modal */}
|
|
2020
|
+
<Dialog open={isAiPreviewOpen} onOpenChange={setIsAiPreviewOpen}>
|
|
2021
|
+
<DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
|
|
2022
|
+
<DialogHeader>
|
|
2023
|
+
<DialogTitle className="flex items-center gap-2">
|
|
2024
|
+
<Wand2 className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
|
2025
|
+
AI Preview - {getActionDescription(previewAction).replace('...', '')}
|
|
2026
|
+
</DialogTitle>
|
|
2027
|
+
<DialogDescription>
|
|
2028
|
+
Review the AI-generated content before applying it to your editor.
|
|
2029
|
+
</DialogDescription>
|
|
2030
|
+
</DialogHeader>
|
|
2031
|
+
|
|
2032
|
+
<div className="flex-1 overflow-hidden flex flex-col gap-4">
|
|
2033
|
+
{/* Original Text Section */}
|
|
2034
|
+
<div className="flex-shrink-0">
|
|
2035
|
+
<div className="text-sm font-medium text-muted-foreground mb-2">Original Text:</div>
|
|
2036
|
+
<div className="p-3 bg-muted/50 rounded-md border max-h-32 overflow-auto">
|
|
2037
|
+
<p className="text-sm whitespace-pre-wrap">{previewOriginalText}</p>
|
|
2038
|
+
</div>
|
|
2039
|
+
</div>
|
|
2040
|
+
|
|
2041
|
+
{/* AI Generated Content */}
|
|
2042
|
+
<div className="flex-1 overflow-hidden flex flex-col">
|
|
2043
|
+
<div className="text-sm font-medium text-muted-foreground mb-2 flex items-center gap-2">
|
|
2044
|
+
<Sparkles className="w-4 h-4" />
|
|
2045
|
+
AI Generated Content:
|
|
2046
|
+
</div>
|
|
2047
|
+
<div className="flex-1 p-4 bg-gradient-to-br from-purple-50 to-blue-50 dark:from-purple-950/50 dark:to-blue-950/50 rounded-md border-2 border-purple-200 dark:border-purple-800 overflow-auto">
|
|
2048
|
+
<div className="prose prose-sm max-w-none dark:prose-invert">
|
|
2049
|
+
<div className="whitespace-pre-wrap text-sm leading-relaxed">
|
|
2050
|
+
{previewContent}
|
|
2051
|
+
</div>
|
|
2052
|
+
</div>
|
|
2053
|
+
</div>
|
|
2054
|
+
</div>
|
|
2055
|
+
</div>
|
|
2056
|
+
|
|
2057
|
+
{/* Action Buttons */}
|
|
2058
|
+
<div className="flex-shrink-0 flex justify-between items-center pt-4 border-t">
|
|
2059
|
+
<div className="flex gap-2">
|
|
2060
|
+
<Button
|
|
2061
|
+
variant="outline"
|
|
2062
|
+
size="sm"
|
|
2063
|
+
onClick={() => {
|
|
2064
|
+
navigator.clipboard.writeText(previewContent);
|
|
2065
|
+
toast({
|
|
2066
|
+
title: "Copied to clipboard",
|
|
2067
|
+
description: "The AI-generated content has been copied.",
|
|
2068
|
+
});
|
|
2069
|
+
}}
|
|
2070
|
+
>
|
|
2071
|
+
<Copy className="w-4 h-4 mr-2" />
|
|
2072
|
+
Copy
|
|
2073
|
+
</Button>
|
|
2074
|
+
<Button
|
|
2075
|
+
variant="outline"
|
|
2076
|
+
size="sm"
|
|
2077
|
+
onClick={() => {
|
|
2078
|
+
// Regenerate content
|
|
2079
|
+
setIsAiPreviewOpen(false);
|
|
2080
|
+
handleAIAction(previewAction);
|
|
2081
|
+
}}
|
|
2082
|
+
>
|
|
2083
|
+
<RefreshCw className="w-4 h-4 mr-2" />
|
|
2084
|
+
Regenerate
|
|
2085
|
+
</Button>
|
|
2086
|
+
</div>
|
|
2087
|
+
|
|
2088
|
+
<div className="flex gap-2">
|
|
2089
|
+
<Button
|
|
2090
|
+
variant="outline"
|
|
2091
|
+
onClick={() => setIsAiPreviewOpen(false)}
|
|
2092
|
+
>
|
|
2093
|
+
Cancel
|
|
2094
|
+
</Button>
|
|
2095
|
+
<Button
|
|
2096
|
+
onClick={() => {
|
|
2097
|
+
setIsAiPreviewOpen(false);
|
|
2098
|
+
// Apply with typewriter effect
|
|
2099
|
+
const replaceSelection = previewOriginalText !== editor?.getText();
|
|
2100
|
+
applyAIContentToEditor(previewContent, replaceSelection);
|
|
2101
|
+
}}
|
|
2102
|
+
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
|
|
2103
|
+
>
|
|
2104
|
+
<Check className="w-4 h-4 mr-2" />
|
|
2105
|
+
Apply to Editor
|
|
2106
|
+
</Button>
|
|
2107
|
+
</div>
|
|
2108
|
+
</div>
|
|
2109
|
+
</DialogContent>
|
|
2110
|
+
</Dialog>
|
|
2111
|
+
|
|
1973
2112
|
{/* Editor */}
|
|
1974
2113
|
<div
|
|
1975
2114
|
className="overflow-auto relative"
|