@moontra/moonui-pro 2.8.4 → 2.8.6
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.d.ts +12 -9
- package/dist/index.mjs +643 -128
- package/package.json +1 -1
- package/src/components/phone-number-input/index.tsx +1 -1
- package/src/components/rich-text-editor/index.tsx +294 -111
- package/src/lib/ai-providers.ts +377 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.6",
|
|
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",
|
|
@@ -250,7 +250,7 @@ export const MoonUIPhoneNumberInputPro = React.forwardRef<HTMLDivElement, PhoneN
|
|
|
250
250
|
onPaste={handlePaste}
|
|
251
251
|
onFocus={() => setIsFocused(true)}
|
|
252
252
|
onBlur={() => setIsFocused(false)}
|
|
253
|
-
placeholder={selectedCountry
|
|
253
|
+
placeholder={selectedCountry?.format?.replace(/x/g, '•') || 'Enter phone number'}
|
|
254
254
|
className={cn(
|
|
255
255
|
"pr-10",
|
|
256
256
|
error && "border-destructive",
|
|
@@ -22,6 +22,7 @@ import Typography from '@tiptap/extension-typography';
|
|
|
22
22
|
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
|
|
23
23
|
import { common, createLowlight } from 'lowlight';
|
|
24
24
|
import { SlashCommandsExtension } from './slash-commands-extension';
|
|
25
|
+
import { motion } from 'framer-motion';
|
|
25
26
|
|
|
26
27
|
// Import pro access hooks
|
|
27
28
|
// Note: DocsProAccess should be handled by consuming application
|
|
@@ -66,7 +67,13 @@ import {
|
|
|
66
67
|
Palette,
|
|
67
68
|
Eye,
|
|
68
69
|
Edit,
|
|
69
|
-
Lock
|
|
70
|
+
Lock,
|
|
71
|
+
Briefcase,
|
|
72
|
+
MessageSquare,
|
|
73
|
+
Heart,
|
|
74
|
+
GraduationCap,
|
|
75
|
+
Zap,
|
|
76
|
+
Lightbulb
|
|
70
77
|
} from 'lucide-react';
|
|
71
78
|
import { cn } from '../../lib/utils';
|
|
72
79
|
import { Button } from '../ui/button';
|
|
@@ -94,7 +101,8 @@ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
|
|
94
101
|
import { MoonUIColorPickerPro as ColorPicker } from '../ui/color-picker';
|
|
95
102
|
import { Slider } from '../ui/slider';
|
|
96
103
|
import { toast } from '../ui/toast';
|
|
97
|
-
|
|
104
|
+
import { Checkbox } from '../ui/checkbox';
|
|
105
|
+
import { createAIProvider, type AIProvider as AIProviderInterface } from '../../lib/ai-providers';
|
|
98
106
|
|
|
99
107
|
// Type definitions for AI functionality
|
|
100
108
|
type AIProvider = 'openai' | 'anthropic' | 'gemini' | 'claude' | 'cohere'
|
|
@@ -116,64 +124,45 @@ interface SlashCommand {
|
|
|
116
124
|
action: (text: string) => Promise<{ text: string; error?: string }> | void
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
//
|
|
120
|
-
const getAIProvider = (settings: AISettingsType) => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
fixGrammar: async (text: string) => {
|
|
145
|
-
const result = await generateWithPrompt('Fix grammar and spelling', text)
|
|
146
|
-
return result
|
|
147
|
-
},
|
|
148
|
-
translate: async (text: string, targetLang: string) => {
|
|
149
|
-
const result = await generateWithPrompt(`Translate to ${targetLang}`, text)
|
|
150
|
-
return result
|
|
151
|
-
},
|
|
152
|
-
changeTone: async (text: string, tone: string) => {
|
|
153
|
-
const result = await generateWithPrompt(`Change tone to ${tone}`, text)
|
|
154
|
-
return result
|
|
155
|
-
},
|
|
156
|
-
continueWriting: async (text: string) => {
|
|
157
|
-
const result = await generateWithPrompt('Continue writing', text)
|
|
158
|
-
return result
|
|
159
|
-
},
|
|
160
|
-
improveWriting: async (text: string) => {
|
|
161
|
-
const result = await generateWithPrompt('Improve writing quality', text)
|
|
162
|
-
return result
|
|
163
|
-
},
|
|
164
|
-
generateIdeas: async (text: string) => {
|
|
165
|
-
const result = await generateWithPrompt('Generate writing ideas', text)
|
|
166
|
-
return result
|
|
167
|
-
},
|
|
168
|
-
complete: async (text: string) => {
|
|
169
|
-
const result = await generateWithPrompt('Complete this text', text)
|
|
170
|
-
return result
|
|
171
|
-
}
|
|
127
|
+
// Get AI provider instance
|
|
128
|
+
const getAIProvider = (settings: AISettingsType): AIProviderInterface | null => {
|
|
129
|
+
if (!settings.apiKey) return null;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
// Map provider names to supported ones
|
|
133
|
+
const providerMap: Record<string, 'openai' | 'gemini' | 'claude'> = {
|
|
134
|
+
'openai': 'openai',
|
|
135
|
+
'gemini': 'gemini',
|
|
136
|
+
'claude': 'claude',
|
|
137
|
+
'anthropic': 'claude', // Map anthropic to claude
|
|
138
|
+
'cohere': 'openai' // Use OpenAI as fallback for unsupported providers
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const mappedProvider = providerMap[settings.provider] || 'openai';
|
|
142
|
+
|
|
143
|
+
return createAIProvider(mappedProvider, {
|
|
144
|
+
apiKey: settings.apiKey,
|
|
145
|
+
model: settings.model,
|
|
146
|
+
temperature: settings.temperature,
|
|
147
|
+
maxTokens: settings.maxTokens
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('Failed to create AI provider:', error);
|
|
151
|
+
return null;
|
|
172
152
|
}
|
|
173
153
|
}
|
|
174
154
|
import './slash-commands.css';
|
|
175
155
|
import './table-styles.css';
|
|
176
156
|
|
|
157
|
+
export interface AIConfig {
|
|
158
|
+
provider?: 'openai' | 'claude' | 'gemini' | 'cohere';
|
|
159
|
+
apiKey?: string;
|
|
160
|
+
model?: string;
|
|
161
|
+
temperature?: number;
|
|
162
|
+
maxTokens?: number;
|
|
163
|
+
endpoint?: string; // For server-side proxy
|
|
164
|
+
}
|
|
165
|
+
|
|
177
166
|
interface RichTextEditorProps {
|
|
178
167
|
value?: string;
|
|
179
168
|
onChange?: (value: string) => void;
|
|
@@ -196,13 +185,8 @@ interface RichTextEditorProps {
|
|
|
196
185
|
color?: boolean;
|
|
197
186
|
ai?: boolean;
|
|
198
187
|
};
|
|
199
|
-
aiConfig?:
|
|
200
|
-
|
|
201
|
-
apiKey?: string;
|
|
202
|
-
model?: string;
|
|
203
|
-
temperature?: number;
|
|
204
|
-
maxTokens?: number;
|
|
205
|
-
};
|
|
188
|
+
aiConfig?: AIConfig;
|
|
189
|
+
persistAISettings?: boolean;
|
|
206
190
|
}
|
|
207
191
|
|
|
208
192
|
|
|
@@ -298,10 +282,11 @@ export function RichTextEditor({
|
|
|
298
282
|
aiConfig = {
|
|
299
283
|
provider: 'openai',
|
|
300
284
|
apiKey: '',
|
|
301
|
-
model: 'gpt-
|
|
285
|
+
model: 'gpt-3.5-turbo',
|
|
302
286
|
temperature: 0.7,
|
|
303
287
|
maxTokens: 1000,
|
|
304
288
|
},
|
|
289
|
+
persistAISettings = true,
|
|
305
290
|
}: RichTextEditorProps) {
|
|
306
291
|
// Pro access kontrolü
|
|
307
292
|
const { hasProAccess, isLoading } = useSubscription();
|
|
@@ -337,12 +322,35 @@ export function RichTextEditor({
|
|
|
337
322
|
);
|
|
338
323
|
}
|
|
339
324
|
|
|
340
|
-
const [aiSettings, setAiSettings] = useState<AISettingsType>({
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
325
|
+
const [aiSettings, setAiSettings] = useState<AISettingsType>(() => {
|
|
326
|
+
// Öncelik sırası: Props > LocalStorage > Varsayılan
|
|
327
|
+
if (persistAISettings) {
|
|
328
|
+
try {
|
|
329
|
+
const stored = localStorage.getItem('moonui-ai-settings');
|
|
330
|
+
if (stored) {
|
|
331
|
+
const parsed = JSON.parse(stored);
|
|
332
|
+
// Props'tan gelen değerler her zaman öncelikli
|
|
333
|
+
return {
|
|
334
|
+
provider: aiConfig.provider || parsed.provider || 'openai',
|
|
335
|
+
apiKey: aiConfig.apiKey || parsed.apiKey || '',
|
|
336
|
+
model: aiConfig.model || parsed.model || 'gpt-3.5-turbo',
|
|
337
|
+
temperature: aiConfig.temperature ?? parsed.temperature ?? 0.7,
|
|
338
|
+
maxTokens: aiConfig.maxTokens ?? parsed.maxTokens ?? 1000,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.error('Failed to load AI settings from localStorage:', e);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// LocalStorage yoksa veya persist kapalıysa props/varsayılan değerleri kullan
|
|
347
|
+
return {
|
|
348
|
+
provider: aiConfig.provider || 'openai',
|
|
349
|
+
apiKey: aiConfig.apiKey || '',
|
|
350
|
+
model: aiConfig.model || 'gpt-3.5-turbo',
|
|
351
|
+
temperature: aiConfig.temperature ?? 0.7,
|
|
352
|
+
maxTokens: aiConfig.maxTokens ?? 1000,
|
|
353
|
+
};
|
|
346
354
|
});
|
|
347
355
|
const [isAiSettingsOpen, setIsAiSettingsOpen] = useState(false);
|
|
348
356
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
@@ -557,7 +565,11 @@ export function RichTextEditor({
|
|
|
557
565
|
setIsProcessing(true);
|
|
558
566
|
try {
|
|
559
567
|
const provider = getAIProvider(aiSettings);
|
|
560
|
-
|
|
568
|
+
if (!provider) {
|
|
569
|
+
throw new Error('Failed to initialize AI provider');
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
let response: string;
|
|
561
573
|
|
|
562
574
|
switch (action) {
|
|
563
575
|
case 'rewrite':
|
|
@@ -573,7 +585,6 @@ export function RichTextEditor({
|
|
|
573
585
|
response = await provider.fixGrammar(text);
|
|
574
586
|
break;
|
|
575
587
|
case 'translate':
|
|
576
|
-
// TODO: Kullanıcıdan hedef dil seçmesini iste
|
|
577
588
|
response = await provider.translate(text, 'Turkish');
|
|
578
589
|
break;
|
|
579
590
|
case 'tone_professional':
|
|
@@ -601,20 +612,11 @@ export function RichTextEditor({
|
|
|
601
612
|
response = await provider.complete(text);
|
|
602
613
|
}
|
|
603
614
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
title: "AI Error",
|
|
607
|
-
description: response.error,
|
|
608
|
-
variant: "destructive",
|
|
609
|
-
});
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
return response.text;
|
|
614
|
-
} catch {
|
|
615
|
+
return response;
|
|
616
|
+
} catch (error) {
|
|
615
617
|
toast({
|
|
616
618
|
title: "AI Error",
|
|
617
|
-
description: "Failed to process with AI
|
|
619
|
+
description: error instanceof Error ? error.message : "Failed to process with AI",
|
|
618
620
|
variant: "destructive",
|
|
619
621
|
});
|
|
620
622
|
return null;
|
|
@@ -638,17 +640,52 @@ export function RichTextEditor({
|
|
|
638
640
|
return;
|
|
639
641
|
}
|
|
640
642
|
|
|
643
|
+
// Show processing toast
|
|
644
|
+
const processingToast = toast({
|
|
645
|
+
title: "Processing with AI...",
|
|
646
|
+
description: getActionDescription(action),
|
|
647
|
+
duration: 60000, // Long duration
|
|
648
|
+
});
|
|
649
|
+
|
|
641
650
|
const result = await callAI(action, selectedText || editor.getText());
|
|
642
651
|
|
|
652
|
+
// Dismiss processing toast
|
|
653
|
+
processingToast.dismiss();
|
|
654
|
+
|
|
643
655
|
if (result) {
|
|
644
656
|
if (selectedText) {
|
|
645
657
|
editor.chain().focus().deleteSelection().insertContent(result).run();
|
|
646
658
|
} else {
|
|
647
659
|
editor.chain().focus().insertContent(result).run();
|
|
648
660
|
}
|
|
661
|
+
|
|
662
|
+
// Success toast
|
|
663
|
+
toast({
|
|
664
|
+
title: "AI action completed",
|
|
665
|
+
description: "Your text has been updated successfully.",
|
|
666
|
+
});
|
|
649
667
|
}
|
|
650
668
|
};
|
|
651
669
|
|
|
670
|
+
const getActionDescription = (action: string): string => {
|
|
671
|
+
const descriptions: Record<string, string> = {
|
|
672
|
+
rewrite: "Rewriting your text...",
|
|
673
|
+
improve: "Improving your writing...",
|
|
674
|
+
expand: "Expanding your text...",
|
|
675
|
+
summarize: "Creating a summary...",
|
|
676
|
+
fix: "Fixing grammar and spelling...",
|
|
677
|
+
translate: "Translating to Turkish...",
|
|
678
|
+
tone_professional: "Making text professional...",
|
|
679
|
+
tone_casual: "Making text casual...",
|
|
680
|
+
tone_friendly: "Making text friendly...",
|
|
681
|
+
tone_formal: "Making text formal...",
|
|
682
|
+
continue: "Continuing your writing...",
|
|
683
|
+
ideas: "Generating ideas...",
|
|
684
|
+
complete: "Completing your text..."
|
|
685
|
+
};
|
|
686
|
+
return descriptions[action] || "Processing...";
|
|
687
|
+
};
|
|
688
|
+
|
|
652
689
|
const [linkUrl, setLinkUrl] = useState('');
|
|
653
690
|
const [imageUrl, setImageUrl] = useState('');
|
|
654
691
|
const [isLinkDialogOpen, setIsLinkDialogOpen] = useState(false);
|
|
@@ -1250,7 +1287,7 @@ export function RichTextEditor({
|
|
|
1250
1287
|
<Button
|
|
1251
1288
|
variant="ghost"
|
|
1252
1289
|
size="sm"
|
|
1253
|
-
className="h-8 px-3 bg-purple-100 hover:bg-purple-200 dark:bg-purple-900 dark:hover:bg-purple-800"
|
|
1290
|
+
className="h-8 px-3 bg-purple-100 hover:bg-purple-200 dark:bg-purple-900 dark:hover:bg-purple-800 transition-colors"
|
|
1254
1291
|
disabled={isProcessing}
|
|
1255
1292
|
>
|
|
1256
1293
|
{isProcessing ? (
|
|
@@ -1261,58 +1298,134 @@ export function RichTextEditor({
|
|
|
1261
1298
|
AI Tools
|
|
1262
1299
|
</Button>
|
|
1263
1300
|
</DropdownMenuTrigger>
|
|
1264
|
-
<DropdownMenuContent className="w-
|
|
1265
|
-
<
|
|
1301
|
+
<DropdownMenuContent className="w-64">
|
|
1302
|
+
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground flex items-center gap-2">
|
|
1303
|
+
<Wand2 className="w-3 h-3" />
|
|
1304
|
+
Writing Improvements
|
|
1305
|
+
</div>
|
|
1306
|
+
<DropdownMenuItem
|
|
1307
|
+
onClick={() => handleAIAction('rewrite')}
|
|
1308
|
+
disabled={isProcessing}
|
|
1309
|
+
>
|
|
1266
1310
|
<RefreshCw className="w-4 h-4 mr-2" />
|
|
1267
1311
|
Rewrite Selection
|
|
1312
|
+
<span className="ml-auto text-xs text-muted-foreground">Alt+R</span>
|
|
1268
1313
|
</DropdownMenuItem>
|
|
1269
|
-
<DropdownMenuItem
|
|
1270
|
-
|
|
1314
|
+
<DropdownMenuItem
|
|
1315
|
+
onClick={() => handleAIAction('improve')}
|
|
1316
|
+
disabled={isProcessing}
|
|
1317
|
+
>
|
|
1318
|
+
<Sparkles className="w-4 h-4 mr-2" />
|
|
1271
1319
|
Improve Writing
|
|
1272
1320
|
</DropdownMenuItem>
|
|
1273
|
-
<DropdownMenuItem
|
|
1321
|
+
<DropdownMenuItem
|
|
1322
|
+
onClick={() => handleAIAction('expand')}
|
|
1323
|
+
disabled={isProcessing}
|
|
1324
|
+
>
|
|
1274
1325
|
<Maximize className="w-4 h-4 mr-2" />
|
|
1275
1326
|
Expand Text
|
|
1276
1327
|
</DropdownMenuItem>
|
|
1277
|
-
<DropdownMenuItem
|
|
1328
|
+
<DropdownMenuItem
|
|
1329
|
+
onClick={() => handleAIAction('summarize')}
|
|
1330
|
+
disabled={isProcessing}
|
|
1331
|
+
>
|
|
1278
1332
|
<FileText className="w-4 h-4 mr-2" />
|
|
1279
1333
|
Summarize
|
|
1280
1334
|
</DropdownMenuItem>
|
|
1281
|
-
<DropdownMenuItem
|
|
1335
|
+
<DropdownMenuItem
|
|
1336
|
+
onClick={() => handleAIAction('continue')}
|
|
1337
|
+
disabled={isProcessing}
|
|
1338
|
+
>
|
|
1282
1339
|
<Plus className="w-4 h-4 mr-2" />
|
|
1283
1340
|
Continue Writing
|
|
1284
1341
|
</DropdownMenuItem>
|
|
1342
|
+
|
|
1285
1343
|
<DropdownMenuSeparator />
|
|
1286
|
-
<
|
|
1287
|
-
<
|
|
1288
|
-
|
|
1289
|
-
</
|
|
1290
|
-
<
|
|
1291
|
-
|
|
1292
|
-
|
|
1344
|
+
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground flex items-center gap-2">
|
|
1345
|
+
<Palette className="w-3 h-3" />
|
|
1346
|
+
Tone Adjustments
|
|
1347
|
+
</div>
|
|
1348
|
+
<DropdownMenuItem
|
|
1349
|
+
onClick={() => handleAIAction('tone_professional')}
|
|
1350
|
+
disabled={isProcessing}
|
|
1351
|
+
>
|
|
1352
|
+
<Briefcase className="w-4 h-4 mr-2" />
|
|
1293
1353
|
Make Professional
|
|
1294
1354
|
</DropdownMenuItem>
|
|
1295
|
-
<DropdownMenuItem
|
|
1296
|
-
|
|
1355
|
+
<DropdownMenuItem
|
|
1356
|
+
onClick={() => handleAIAction('tone_casual')}
|
|
1357
|
+
disabled={isProcessing}
|
|
1358
|
+
>
|
|
1359
|
+
<MessageSquare className="w-4 h-4 mr-2" />
|
|
1297
1360
|
Make Casual
|
|
1298
1361
|
</DropdownMenuItem>
|
|
1299
|
-
<DropdownMenuItem
|
|
1300
|
-
|
|
1362
|
+
<DropdownMenuItem
|
|
1363
|
+
onClick={() => handleAIAction('tone_friendly')}
|
|
1364
|
+
disabled={isProcessing}
|
|
1365
|
+
>
|
|
1366
|
+
<Heart className="w-4 h-4 mr-2" />
|
|
1301
1367
|
Make Friendly
|
|
1302
1368
|
</DropdownMenuItem>
|
|
1303
|
-
<DropdownMenuItem
|
|
1304
|
-
|
|
1369
|
+
<DropdownMenuItem
|
|
1370
|
+
onClick={() => handleAIAction('tone_formal')}
|
|
1371
|
+
disabled={isProcessing}
|
|
1372
|
+
>
|
|
1373
|
+
<GraduationCap className="w-4 h-4 mr-2" />
|
|
1305
1374
|
Make Formal
|
|
1306
1375
|
</DropdownMenuItem>
|
|
1376
|
+
|
|
1307
1377
|
<DropdownMenuSeparator />
|
|
1308
|
-
<
|
|
1378
|
+
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground flex items-center gap-2">
|
|
1379
|
+
<Zap className="w-3 h-3" />
|
|
1380
|
+
Other Actions
|
|
1381
|
+
</div>
|
|
1382
|
+
<DropdownMenuItem
|
|
1383
|
+
onClick={() => handleAIAction('fix')}
|
|
1384
|
+
disabled={isProcessing}
|
|
1385
|
+
>
|
|
1386
|
+
<Check className="w-4 h-4 mr-2" />
|
|
1387
|
+
Fix Grammar & Spelling
|
|
1388
|
+
<span className="ml-auto text-xs text-muted-foreground">F7</span>
|
|
1389
|
+
</DropdownMenuItem>
|
|
1390
|
+
<DropdownMenuItem
|
|
1391
|
+
onClick={() => handleAIAction('translate')}
|
|
1392
|
+
disabled={isProcessing}
|
|
1393
|
+
>
|
|
1309
1394
|
<Languages className="w-4 h-4 mr-2" />
|
|
1310
1395
|
Translate to Turkish
|
|
1311
1396
|
</DropdownMenuItem>
|
|
1312
|
-
<DropdownMenuItem
|
|
1313
|
-
|
|
1397
|
+
<DropdownMenuItem
|
|
1398
|
+
onClick={() => handleAIAction('ideas')}
|
|
1399
|
+
disabled={isProcessing}
|
|
1400
|
+
>
|
|
1401
|
+
<Lightbulb className="w-4 h-4 mr-2" />
|
|
1314
1402
|
Generate Ideas
|
|
1315
1403
|
</DropdownMenuItem>
|
|
1404
|
+
|
|
1405
|
+
{!aiSettings.apiKey && (
|
|
1406
|
+
<>
|
|
1407
|
+
<DropdownMenuSeparator />
|
|
1408
|
+
<div className="px-2 py-2">
|
|
1409
|
+
<motion.div
|
|
1410
|
+
initial={{ opacity: 0, y: -10 }}
|
|
1411
|
+
animate={{ opacity: 1, y: 0 }}
|
|
1412
|
+
className="text-xs text-muted-foreground bg-yellow-100 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-md p-3"
|
|
1413
|
+
>
|
|
1414
|
+
<div className="flex items-start gap-2">
|
|
1415
|
+
<Settings className="w-3 h-3 mt-0.5 text-yellow-600 dark:text-yellow-400" />
|
|
1416
|
+
<div>
|
|
1417
|
+
<div className="font-medium text-yellow-900 dark:text-yellow-200 mb-1">
|
|
1418
|
+
API Key Required
|
|
1419
|
+
</div>
|
|
1420
|
+
<div className="text-yellow-800 dark:text-yellow-300">
|
|
1421
|
+
Click the settings icon to configure your AI provider and API key.
|
|
1422
|
+
</div>
|
|
1423
|
+
</div>
|
|
1424
|
+
</div>
|
|
1425
|
+
</motion.div>
|
|
1426
|
+
</div>
|
|
1427
|
+
</>
|
|
1428
|
+
)}
|
|
1316
1429
|
</DropdownMenuContent>
|
|
1317
1430
|
</DropdownMenu>
|
|
1318
1431
|
|
|
@@ -1338,7 +1451,20 @@ export function RichTextEditor({
|
|
|
1338
1451
|
<Label htmlFor="provider">Provider</Label>
|
|
1339
1452
|
<Select
|
|
1340
1453
|
value={aiSettings.provider}
|
|
1341
|
-
onValueChange={(value: 'openai' | 'claude' | 'gemini' | 'cohere') =>
|
|
1454
|
+
onValueChange={(value: 'openai' | 'claude' | 'gemini' | 'cohere') => {
|
|
1455
|
+
// Update model when provider changes
|
|
1456
|
+
const defaultModels = {
|
|
1457
|
+
openai: 'gpt-3.5-turbo',
|
|
1458
|
+
claude: 'claude-3-sonnet-20240229',
|
|
1459
|
+
gemini: 'gemini-2.0-flash',
|
|
1460
|
+
cohere: 'command'
|
|
1461
|
+
};
|
|
1462
|
+
setAiSettings({
|
|
1463
|
+
...aiSettings,
|
|
1464
|
+
provider: value,
|
|
1465
|
+
model: defaultModels[value] || 'gpt-3.5-turbo'
|
|
1466
|
+
});
|
|
1467
|
+
}}
|
|
1342
1468
|
>
|
|
1343
1469
|
<SelectTrigger>
|
|
1344
1470
|
<SelectValue />
|
|
@@ -1386,8 +1512,9 @@ export function RichTextEditor({
|
|
|
1386
1512
|
)}
|
|
1387
1513
|
{aiSettings.provider === 'gemini' && (
|
|
1388
1514
|
<>
|
|
1389
|
-
<SelectItem value="gemini-
|
|
1390
|
-
<SelectItem value="gemini-
|
|
1515
|
+
<SelectItem value="gemini-2.0-flash">Gemini 2.0 Flash</SelectItem>
|
|
1516
|
+
<SelectItem value="gemini-1.5-flash">Gemini 1.5 Flash</SelectItem>
|
|
1517
|
+
<SelectItem value="gemini-1.5-pro">Gemini 1.5 Pro</SelectItem>
|
|
1391
1518
|
</>
|
|
1392
1519
|
)}
|
|
1393
1520
|
{aiSettings.provider === 'cohere' && (
|
|
@@ -1425,8 +1552,64 @@ export function RichTextEditor({
|
|
|
1425
1552
|
</div>
|
|
1426
1553
|
</div>
|
|
1427
1554
|
</div>
|
|
1428
|
-
|
|
1555
|
+
{persistAISettings && (
|
|
1556
|
+
<div className="space-y-4">
|
|
1557
|
+
<div className="flex items-start space-x-2">
|
|
1558
|
+
<Checkbox
|
|
1559
|
+
id="rememberSettings"
|
|
1560
|
+
defaultChecked
|
|
1561
|
+
onCheckedChange={(checked) => {
|
|
1562
|
+
if (!checked) {
|
|
1563
|
+
localStorage.removeItem('moonui-ai-settings');
|
|
1564
|
+
}
|
|
1565
|
+
}}
|
|
1566
|
+
/>
|
|
1567
|
+
<div className="grid gap-1.5 leading-none">
|
|
1568
|
+
<Label
|
|
1569
|
+
htmlFor="rememberSettings"
|
|
1570
|
+
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
1571
|
+
>
|
|
1572
|
+
Remember my settings
|
|
1573
|
+
</Label>
|
|
1574
|
+
<p className="text-xs text-muted-foreground">
|
|
1575
|
+
Save settings locally for future sessions
|
|
1576
|
+
</p>
|
|
1577
|
+
</div>
|
|
1578
|
+
</div>
|
|
1579
|
+
|
|
1580
|
+
{aiSettings.apiKey && (
|
|
1581
|
+
<div className="rounded-md bg-yellow-50 dark:bg-yellow-900/10 p-3">
|
|
1582
|
+
<div className="flex">
|
|
1583
|
+
<div className="flex-shrink-0">
|
|
1584
|
+
<Settings className="h-4 w-4 text-yellow-400" />
|
|
1585
|
+
</div>
|
|
1586
|
+
<div className="ml-3">
|
|
1587
|
+
<p className="text-xs text-yellow-800 dark:text-yellow-200">
|
|
1588
|
+
<strong>Security Notice:</strong> API keys will be stored in your browser's local storage.
|
|
1589
|
+
For production use, consider using a server-side proxy.
|
|
1590
|
+
</p>
|
|
1591
|
+
</div>
|
|
1592
|
+
</div>
|
|
1593
|
+
</div>
|
|
1594
|
+
)}
|
|
1595
|
+
</div>
|
|
1596
|
+
)}
|
|
1597
|
+
|
|
1598
|
+
<div className="flex justify-end gap-2">
|
|
1599
|
+
<Button
|
|
1600
|
+
variant="outline"
|
|
1601
|
+
onClick={() => setIsAiSettingsOpen(false)}
|
|
1602
|
+
>
|
|
1603
|
+
Cancel
|
|
1604
|
+
</Button>
|
|
1429
1605
|
<Button onClick={() => {
|
|
1606
|
+
// LocalStorage'a kaydet
|
|
1607
|
+
if (persistAISettings) {
|
|
1608
|
+
const toStore = { ...aiSettings };
|
|
1609
|
+
// Güvenlik için API key'i opsiyonel olarak kaydet
|
|
1610
|
+
localStorage.setItem('moonui-ai-settings', JSON.stringify(toStore));
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1430
1613
|
setIsAiSettingsOpen(false);
|
|
1431
1614
|
toast({
|
|
1432
1615
|
title: "Settings saved",
|