@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 +157 -38
- package/package.json +1 -1
- package/src/components/rich-text-editor/index.tsx +187 -44
- package/src/lib/ai-providers.ts +3 -3
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 = `
|
|
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
|
|
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(`
|
|
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
|
-
|
|
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
|
-
|
|
50811
|
+
const fullText = editor.getText();
|
|
50812
|
+
const textToProcess = selectedText || fullText;
|
|
50813
|
+
if (!textToProcess && action !== "complete") {
|
|
50758
50814
|
toast({
|
|
50759
|
-
title: "No
|
|
50760
|
-
description: "Please
|
|
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,
|
|
50828
|
+
const result = await callAI(action, textToProcess, targetLanguage);
|
|
50773
50829
|
processingToast.dismiss();
|
|
50774
50830
|
if (result) {
|
|
50775
|
-
|
|
50776
|
-
|
|
50777
|
-
|
|
50778
|
-
|
|
50779
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
745
|
-
description: "Please
|
|
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,
|
|
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
|
-
//
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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"
|
package/src/lib/ai-providers.ts
CHANGED
|
@@ -104,7 +104,7 @@ export class GeminiProvider implements AIProvider {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
async summarize(text: string): Promise<string> {
|
|
107
|
-
const prompt = `
|
|
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
|
|
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(`
|
|
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> {
|