@moontra/moonui-pro 2.8.11 → 2.8.13

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 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';
@@ -49975,7 +49975,7 @@ ${text}`;
49975
49975
  return this.callGeminiAPI(prompt);
49976
49976
  }
49977
49977
  async summarize(text) {
49978
- const prompt = `Summarize the following text concisely while keeping the main points. IMPORTANT: Respond in the SAME LANGUAGE as the input text. Only return the summary, nothing else:
49978
+ const prompt = `Create a comprehensive and detailed summary of the following text. Include all key points, important details, main arguments, and significant examples. The summary should be thorough enough to understand the full context and meaning of the original text, not just a brief overview. IMPORTANT: Respond in the SAME LANGUAGE as the input text. Only return the detailed summary, nothing else:
49979
49979
 
49980
49980
  ${text}`;
49981
49981
  return this.callGeminiAPI(prompt);
@@ -50083,7 +50083,7 @@ var OpenAIProvider = class {
50083
50083
  }
50084
50084
  async summarize(text) {
50085
50085
  return this.callOpenAI(
50086
- "You are a summarization expert. Create concise summaries. Always respond in the same language as the input text.",
50086
+ "You are a summarization expert. Create comprehensive and detailed summaries that include all key points, important details, main arguments, and significant examples. The summary should be thorough enough to understand the full context and meaning of the original text, not just a brief overview. Always respond in the same language as the input text.",
50087
50087
  text
50088
50088
  );
50089
50089
  }
@@ -50179,7 +50179,7 @@ ${text}`);
50179
50179
  ${text}`);
50180
50180
  }
50181
50181
  async summarize(text) {
50182
- return this.callClaude(`Summarize this text concisely. IMPORTANT: Respond in the SAME LANGUAGE as the input text:
50182
+ return this.callClaude(`Create a comprehensive and detailed summary of the following text. Include all key points, important details, main arguments, and significant examples. The summary should be thorough enough to understand the full context and meaning of the original text, not just a brief overview. IMPORTANT: Respond in the SAME LANGUAGE as the input text:
50183
50183
 
50184
50184
  ${text}`);
50185
50185
  }
@@ -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 = 5;
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
- editor.chain().focus().insertContent(response.text).run();
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",
@@ -50754,10 +50808,12 @@ function RichTextEditor({
50754
50808
  return;
50755
50809
  const selection = editor.state.selection;
50756
50810
  const selectedText = editor.state.doc.textBetween(selection.from, selection.to, " ");
50757
- if (!selectedText && action !== "complete") {
50811
+ const fullText = editor.getText();
50812
+ const textToProcess = selectedText || fullText;
50813
+ if (!textToProcess && action !== "complete") {
50758
50814
  toast({
50759
- title: "No text selected",
50760
- description: "Please select some text first.",
50815
+ title: "No content available",
50816
+ description: "Please write some text first.",
50761
50817
  variant: "destructive"
50762
50818
  });
50763
50819
  return;
@@ -50769,39 +50825,17 @@ function RichTextEditor({
50769
50825
  duration: 6e4
50770
50826
  // Long duration
50771
50827
  });
50772
- const result = await callAI(action, selectedText || editor.getText(), targetLanguage);
50828
+ const result = await callAI(action, textToProcess, targetLanguage);
50773
50829
  processingToast.dismiss();
50774
50830
  if (result) {
50775
- setIsTyping(true);
50776
- isTypingRef.current = true;
50777
- setTypingText("");
50778
- if (selectedText) {
50779
- editor.chain().focus().deleteSelection().run();
50831
+ if (shouldUseModal(action)) {
50832
+ setPreviewContent(result);
50833
+ setPreviewAction(action);
50834
+ setPreviewOriginalText(textToProcess);
50835
+ setIsAiPreviewOpen(true);
50836
+ } else {
50837
+ applyAIContentToEditor(result, !!selectedText);
50780
50838
  }
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
50839
  }
50806
50840
  };
50807
50841
  const getActionDescription = (action, targetLanguage) => {
@@ -51887,6 +51921,91 @@ function RichTextEditor({
51887
51921
  }, children: "Apply Borders" })
51888
51922
  ] })
51889
51923
  ] }) }),
51924
+ /* @__PURE__ */ jsx(MoonUIDialogPro, { open: isAiPreviewOpen, onOpenChange: setIsAiPreviewOpen, children: /* @__PURE__ */ jsxs(MoonUIDialogContentPro, { className: "max-w-4xl max-h-[80vh] overflow-hidden flex flex-col", children: [
51925
+ /* @__PURE__ */ jsxs(MoonUIDialogHeaderPro, { children: [
51926
+ /* @__PURE__ */ jsxs(MoonUIDialogTitlePro, { className: "flex items-center gap-2", children: [
51927
+ /* @__PURE__ */ jsx(Wand2, { className: "w-5 h-5 text-purple-600 dark:text-purple-400" }),
51928
+ "AI Preview - ",
51929
+ getActionDescription(previewAction).replace("...", "")
51930
+ ] }),
51931
+ /* @__PURE__ */ jsx(MoonUIDialogDescriptionPro, { children: "Review the AI-generated content before applying it to your editor." })
51932
+ ] }),
51933
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex flex-col gap-4", children: [
51934
+ /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0", children: [
51935
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-muted-foreground mb-2", children: "Original Text:" }),
51936
+ /* @__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 }) })
51937
+ ] }),
51938
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex flex-col", children: [
51939
+ /* @__PURE__ */ jsxs("div", { className: "text-sm font-medium text-muted-foreground mb-2 flex items-center gap-2", children: [
51940
+ /* @__PURE__ */ jsx(Sparkles, { className: "w-4 h-4" }),
51941
+ "AI Generated Content:"
51942
+ ] }),
51943
+ /* @__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 }) }) })
51944
+ ] })
51945
+ ] }),
51946
+ /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 flex justify-between items-center pt-4 border-t", children: [
51947
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
51948
+ /* @__PURE__ */ jsxs(
51949
+ MoonUIButtonPro,
51950
+ {
51951
+ variant: "outline",
51952
+ size: "sm",
51953
+ onClick: () => {
51954
+ navigator.clipboard.writeText(previewContent);
51955
+ toast({
51956
+ title: "Copied to clipboard",
51957
+ description: "The AI-generated content has been copied."
51958
+ });
51959
+ },
51960
+ children: [
51961
+ /* @__PURE__ */ jsx(Copy, { className: "w-4 h-4 mr-2" }),
51962
+ "Copy"
51963
+ ]
51964
+ }
51965
+ ),
51966
+ /* @__PURE__ */ jsxs(
51967
+ MoonUIButtonPro,
51968
+ {
51969
+ variant: "outline",
51970
+ size: "sm",
51971
+ onClick: () => {
51972
+ setIsAiPreviewOpen(false);
51973
+ handleAIAction(previewAction);
51974
+ },
51975
+ children: [
51976
+ /* @__PURE__ */ jsx(RefreshCw, { className: "w-4 h-4 mr-2" }),
51977
+ "Regenerate"
51978
+ ]
51979
+ }
51980
+ )
51981
+ ] }),
51982
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
51983
+ /* @__PURE__ */ jsx(
51984
+ MoonUIButtonPro,
51985
+ {
51986
+ variant: "outline",
51987
+ onClick: () => setIsAiPreviewOpen(false),
51988
+ children: "Cancel"
51989
+ }
51990
+ ),
51991
+ /* @__PURE__ */ jsxs(
51992
+ MoonUIButtonPro,
51993
+ {
51994
+ onClick: () => {
51995
+ setIsAiPreviewOpen(false);
51996
+ const replaceSelection = previewOriginalText !== editor?.getText();
51997
+ applyAIContentToEditor(previewContent, replaceSelection);
51998
+ },
51999
+ className: "bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700",
52000
+ children: [
52001
+ /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 mr-2" }),
52002
+ "Apply to Editor"
52003
+ ]
52004
+ }
52005
+ )
52006
+ ] })
52007
+ ] })
52008
+ ] }) }),
51890
52009
  /* @__PURE__ */ jsx(
51891
52010
  "div",
51892
52011
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.8.11",
3
+ "version": "2.8.13",
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 = 5; // ms per character - much faster for better UX
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
- editor.chain().focus().insertContent(response.text).run();
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",
@@ -738,11 +812,15 @@ export function RichTextEditor({
738
812
 
739
813
  const selection = editor.state.selection;
740
814
  const selectedText = editor.state.doc.textBetween(selection.from, selection.to, ' ');
815
+ const fullText = editor.getText();
741
816
 
742
- if (!selectedText && action !== 'complete') {
817
+ // If no text is selected, use the full editor content as prompt
818
+ const textToProcess = selectedText || fullText;
819
+
820
+ if (!textToProcess && action !== 'complete') {
743
821
  toast({
744
- title: "No text selected",
745
- description: "Please select some text first.",
822
+ title: "No content available",
823
+ description: "Please write some text first.",
746
824
  variant: "destructive",
747
825
  });
748
826
  return;
@@ -758,51 +836,23 @@ export function RichTextEditor({
758
836
  duration: 60000, // Long duration
759
837
  });
760
838
 
761
- const result = await callAI(action, selectedText || editor.getText(), targetLanguage);
839
+ const result = await callAI(action, textToProcess, targetLanguage);
762
840
 
763
841
  // Dismiss processing toast
764
842
  processingToast.dismiss();
765
843
 
766
844
  if (result) {
767
- // Start typewriter effect
768
- setIsTyping(true);
769
- isTypingRef.current = true;
770
- setTypingText('');
771
-
772
- if (selectedText) {
773
- editor.chain().focus().deleteSelection().run();
845
+ // Check if this action should use modal preview
846
+ if (shouldUseModal(action)) {
847
+ // Open preview modal instead of directly applying
848
+ setPreviewContent(result);
849
+ setPreviewAction(action);
850
+ setPreviewOriginalText(textToProcess);
851
+ setIsAiPreviewOpen(true);
852
+ } else {
853
+ // Apply directly with typewriter effect (for rewrite, fix grammar, tone changes, etc.)
854
+ applyAIContentToEditor(result, !!selectedText);
774
855
  }
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
856
  }
807
857
  };
808
858
 
@@ -1970,6 +2020,99 @@ export function RichTextEditor({
1970
2020
  </DialogContent>
1971
2021
  </Dialog>
1972
2022
 
2023
+ {/* AI Preview Modal */}
2024
+ <Dialog open={isAiPreviewOpen} onOpenChange={setIsAiPreviewOpen}>
2025
+ <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
2026
+ <DialogHeader>
2027
+ <DialogTitle className="flex items-center gap-2">
2028
+ <Wand2 className="w-5 h-5 text-purple-600 dark:text-purple-400" />
2029
+ AI Preview - {getActionDescription(previewAction).replace('...', '')}
2030
+ </DialogTitle>
2031
+ <DialogDescription>
2032
+ Review the AI-generated content before applying it to your editor.
2033
+ </DialogDescription>
2034
+ </DialogHeader>
2035
+
2036
+ <div className="flex-1 overflow-hidden flex flex-col gap-4">
2037
+ {/* Original Text Section */}
2038
+ <div className="flex-shrink-0">
2039
+ <div className="text-sm font-medium text-muted-foreground mb-2">Original Text:</div>
2040
+ <div className="p-3 bg-muted/50 rounded-md border max-h-32 overflow-auto">
2041
+ <p className="text-sm whitespace-pre-wrap">{previewOriginalText}</p>
2042
+ </div>
2043
+ </div>
2044
+
2045
+ {/* AI Generated Content */}
2046
+ <div className="flex-1 overflow-hidden flex flex-col">
2047
+ <div className="text-sm font-medium text-muted-foreground mb-2 flex items-center gap-2">
2048
+ <Sparkles className="w-4 h-4" />
2049
+ AI Generated Content:
2050
+ </div>
2051
+ <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">
2052
+ <div className="prose prose-sm max-w-none dark:prose-invert">
2053
+ <div className="whitespace-pre-wrap text-sm leading-relaxed">
2054
+ {previewContent}
2055
+ </div>
2056
+ </div>
2057
+ </div>
2058
+ </div>
2059
+ </div>
2060
+
2061
+ {/* Action Buttons */}
2062
+ <div className="flex-shrink-0 flex justify-between items-center pt-4 border-t">
2063
+ <div className="flex gap-2">
2064
+ <Button
2065
+ variant="outline"
2066
+ size="sm"
2067
+ onClick={() => {
2068
+ navigator.clipboard.writeText(previewContent);
2069
+ toast({
2070
+ title: "Copied to clipboard",
2071
+ description: "The AI-generated content has been copied.",
2072
+ });
2073
+ }}
2074
+ >
2075
+ <Copy className="w-4 h-4 mr-2" />
2076
+ Copy
2077
+ </Button>
2078
+ <Button
2079
+ variant="outline"
2080
+ size="sm"
2081
+ onClick={() => {
2082
+ // Regenerate content
2083
+ setIsAiPreviewOpen(false);
2084
+ handleAIAction(previewAction);
2085
+ }}
2086
+ >
2087
+ <RefreshCw className="w-4 h-4 mr-2" />
2088
+ Regenerate
2089
+ </Button>
2090
+ </div>
2091
+
2092
+ <div className="flex gap-2">
2093
+ <Button
2094
+ variant="outline"
2095
+ onClick={() => setIsAiPreviewOpen(false)}
2096
+ >
2097
+ Cancel
2098
+ </Button>
2099
+ <Button
2100
+ onClick={() => {
2101
+ setIsAiPreviewOpen(false);
2102
+ // Apply with typewriter effect
2103
+ const replaceSelection = previewOriginalText !== editor?.getText();
2104
+ applyAIContentToEditor(previewContent, replaceSelection);
2105
+ }}
2106
+ className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
2107
+ >
2108
+ <Check className="w-4 h-4 mr-2" />
2109
+ Apply to Editor
2110
+ </Button>
2111
+ </div>
2112
+ </div>
2113
+ </DialogContent>
2114
+ </Dialog>
2115
+
1973
2116
  {/* Editor */}
1974
2117
  <div
1975
2118
  className="overflow-auto relative"
@@ -104,7 +104,7 @@ export class GeminiProvider implements AIProvider {
104
104
  }
105
105
 
106
106
  async summarize(text: string): Promise<string> {
107
- const prompt = `Summarize the following text concisely while keeping the main points. IMPORTANT: Respond in the SAME LANGUAGE as the input text. Only return the summary, nothing else:\n\n${text}`;
107
+ const prompt = `Create a comprehensive and detailed summary of the following text. Include all key points, important details, main arguments, and significant examples. The summary should be thorough enough to understand the full context and meaning of the original text, not just a brief overview. IMPORTANT: Respond in the SAME LANGUAGE as the input text. Only return the detailed summary, nothing else:\n\n${text}`;
108
108
  return this.callGeminiAPI(prompt);
109
109
  }
110
110
 
@@ -218,7 +218,7 @@ export class OpenAIProvider implements AIProvider {
218
218
 
219
219
  async summarize(text: string): Promise<string> {
220
220
  return this.callOpenAI(
221
- 'You are a summarization expert. Create concise summaries. Always respond in the same language as the input text.',
221
+ 'You are a summarization expert. Create comprehensive and detailed summaries that include all key points, important details, main arguments, and significant examples. The summary should be thorough enough to understand the full context and meaning of the original text, not just a brief overview. Always respond in the same language as the input text.',
222
222
  text
223
223
  );
224
224
  }
@@ -327,7 +327,7 @@ export class ClaudeProvider implements AIProvider {
327
327
  }
328
328
 
329
329
  async summarize(text: string): Promise<string> {
330
- return this.callClaude(`Summarize this text concisely. IMPORTANT: Respond in the SAME LANGUAGE as the input text:\n\n${text}`);
330
+ return this.callClaude(`Create a comprehensive and detailed summary of the following text. Include all key points, important details, main arguments, and significant examples. The summary should be thorough enough to understand the full context and meaning of the original text, not just a brief overview. IMPORTANT: Respond in the SAME LANGUAGE as the input text:\n\n${text}`);
331
331
  }
332
332
 
333
333
  async fixGrammar(text: string): Promise<string> {