fix(editor): custom image width parsing, fix image paste, add AI submenu features
@@ -17,7 +17,7 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Feature disabled' }, { status: 403 })
|
||||
}
|
||||
|
||||
const { text, option, format } = await request.json()
|
||||
const { text, option, format, language } = await request.json()
|
||||
|
||||
// Validation
|
||||
if (!text || typeof text !== 'string') {
|
||||
@@ -25,16 +25,19 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Map option to refactor mode
|
||||
const modeMap: Record<string, 'clarify' | 'shorten' | 'improveStyle'> = {
|
||||
const modeMap: Record<string, 'clarify' | 'shorten' | 'improveStyle' | 'fix_grammar' | 'translate' | 'explain'> = {
|
||||
'clarify': 'clarify',
|
||||
'shorten': 'shorten',
|
||||
'improve': 'improveStyle'
|
||||
'improve': 'improveStyle',
|
||||
'fix_grammar': 'fix_grammar',
|
||||
'translate': 'translate',
|
||||
'explain': 'explain'
|
||||
}
|
||||
|
||||
const mode = modeMap[option]
|
||||
if (!mode) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid option. Use: clarify, shorten, or improve' },
|
||||
{ error: 'Invalid option. Use: clarify, shorten, improve, fix_grammar, translate, or explain' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
@@ -50,7 +53,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Use the ParagraphRefactorService
|
||||
const result = await paragraphRefactorService.refactor(text, mode, format === 'html' ? 'html' : 'markdown')
|
||||
const result = await paragraphRefactorService.refactor(text, mode, format === 'html' ? 'html' : 'markdown', language)
|
||||
|
||||
return NextResponse.json({
|
||||
originalText: result.original,
|
||||
@@ -66,3 +69,4 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1366,6 +1366,9 @@ html.font-system * {
|
||||
color: var(--foreground);
|
||||
transition: background 0.1s ease, transform 0.08s ease;
|
||||
}
|
||||
[dir="rtl"] .notion-slash-item {
|
||||
text-align: right;
|
||||
}
|
||||
.notion-slash-item:hover,
|
||||
.notion-slash-item-selected {
|
||||
background: var(--accent);
|
||||
|
||||
@@ -124,8 +124,8 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
|
||||
|
||||
|
||||
<FeatureToggle
|
||||
name="IA Note"
|
||||
description="Active le bouton de chat IA et les outils d'amélioration du texte"
|
||||
name={t('aiSettings.aiNote')}
|
||||
description={t('aiSettings.aiNoteDesc')}
|
||||
checked={settings.paragraphRefactor}
|
||||
onChange={(checked) => handleToggle('paragraphRefactor', checked)}
|
||||
/>
|
||||
@@ -167,37 +167,37 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
|
||||
|
||||
{/* Language Detection Toggle */}
|
||||
<FeatureToggle
|
||||
name="Détection de langue"
|
||||
description="Détecte automatiquement la langue de vos notes"
|
||||
name={t('aiSettings.languageDetection')}
|
||||
description={t('aiSettings.languageDetectionDesc')}
|
||||
checked={settings.languageDetection ?? true}
|
||||
onChange={(checked) => handleToggle('languageDetection', checked)}
|
||||
/>
|
||||
|
||||
{/* Auto Labeling Toggle */}
|
||||
<FeatureToggle
|
||||
name="Suggestion des labels"
|
||||
description="Suggère et applique des étiquettes automatiquement à vos notes"
|
||||
name={t('aiSettings.autoLabeling')}
|
||||
description={t('aiSettings.autoLabelingDesc')}
|
||||
checked={settings.autoLabeling ?? true}
|
||||
onChange={(checked) => handleToggle('autoLabeling', checked)}
|
||||
/>
|
||||
|
||||
<FeatureToggle
|
||||
name="Historique des notes"
|
||||
description="Active les snapshots de versions et la restauration depuis History"
|
||||
name={t('aiSettings.noteHistory')}
|
||||
description={t('aiSettings.noteHistoryDesc')}
|
||||
checked={settings.noteHistory ?? false}
|
||||
onChange={(checked) => handleToggle('noteHistory', checked)}
|
||||
/>
|
||||
|
||||
{settings.noteHistory && (
|
||||
<div className="space-y-2 rounded-lg border border-border/50 bg-muted/30 p-3">
|
||||
<p className="text-sm font-medium">{t('notes.historyMode') || 'Mode d\'historique'}</p>
|
||||
<p className="text-sm font-medium">{t('notes.historyMode')}</p>
|
||||
<RadioGroup
|
||||
value={settings.noteHistoryMode ?? 'manual'}
|
||||
onValueChange={(value) => {
|
||||
const mode = value as 'manual' | 'auto'
|
||||
setSettings((s) => ({ ...s, noteHistoryMode: mode }))
|
||||
updateAISettings({ noteHistoryMode: mode }).then(() => {
|
||||
toast.success(t('settings.settingsSaved') || 'Saved')
|
||||
toast.success(t('settings.settingsSaved'))
|
||||
})
|
||||
}}
|
||||
className="space-y-2"
|
||||
@@ -206,10 +206,10 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
|
||||
<RadioGroupItem value="manual" id="history-manual" />
|
||||
<div className="grid gap-0.5 leading-none">
|
||||
<Label htmlFor="history-manual" className="text-sm font-medium">
|
||||
{t('notes.historyModeManual') || 'Manuel'}
|
||||
{t('notes.historyModeManual')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('notes.historyModeManualDesc') || 'Créer des snapshots avec le bouton commit'}
|
||||
{t('notes.historyModeManualDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,10 +217,10 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
|
||||
<RadioGroupItem value="auto" id="history-auto" />
|
||||
<div className="grid gap-0.5 leading-none">
|
||||
<Label htmlFor="history-auto" className="text-sm font-medium">
|
||||
{t('notes.historyModeAuto') || 'Automatique'}
|
||||
{t('notes.historyModeAuto')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('notes.historyModeAutoDesc') || 'Snapshots automatiques avec détection intelligente'}
|
||||
{t('notes.historyModeAutoDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Lightbulb, Minimize2, AlignLeft, Wand2,
|
||||
Globe, BookOpen, FileText, RotateCcw, Check,
|
||||
Maximize2, ImageIcon, Link2, Download, ArrowDownToLine,
|
||||
GitMerge, PlusCircle, Eye, Code,
|
||||
GitMerge, PlusCircle, Eye, Code, Languages,
|
||||
} from 'lucide-react'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { MarkdownContent } from '@/components/markdown-content'
|
||||
@@ -64,6 +64,7 @@ const ACTION_IDS = [
|
||||
{ id: 'clarify', icon: Lightbulb, apiPath: '/api/ai/reformulate', body: (content: string) => ({ text: content, option: 'clarify' }), resultKey: 'reformulatedText', i18nKey: 'ai.action.clarify' },
|
||||
{ id: 'shorten', icon: Minimize2, apiPath: '/api/ai/reformulate', body: (content: string) => ({ text: content, option: 'shorten' }), resultKey: 'reformulatedText', i18nKey: 'ai.action.shorten' },
|
||||
{ id: 'improve', icon: AlignLeft, apiPath: '/api/ai/reformulate', body: (content: string) => ({ text: content, option: 'improve' }), resultKey: 'reformulatedText', i18nKey: 'ai.action.improve' },
|
||||
{ id: 'translate', icon: Languages, apiPath: '/api/ai/reformulate', body: (content: string, _images?: string[], lang?: string) => ({ text: content, option: 'translate', language: lang || 'fr' }), resultKey: 'reformulatedText', i18nKey: 'ai.action.translate' },
|
||||
{ id: 'markdown', icon: Wand2, apiPath: '/api/ai/transform-markdown', body: (content: string) => ({ text: content }), resultKey: 'transformedText', i18nKey: 'ai.action.toMarkdown' },
|
||||
{ id: 'describe-images', icon: ImageIcon, apiPath: '/api/ai/describe-image', body: (_content: string, images?: string[], lang?: string) => ({ imageUrls: images || [], mode: 'description', language: lang || 'fr' }), resultKey: 'descriptions', i18nKey: 'ai.action.describeImages', isImageAction: true },
|
||||
]
|
||||
@@ -250,18 +251,18 @@ export function ContextualAIChat({
|
||||
setResourceScraping(true)
|
||||
try {
|
||||
const result = await scrapePageText(resourceUrl.trim())
|
||||
if (!result) { toast.error('Impossible de charger cette URL'); return }
|
||||
if (!result) { toast.error(t('ai.resource.failedToLoadUrl')); return }
|
||||
setResourceText(result.text)
|
||||
toast.success(`Page chargée : ${result.title.slice(0, 40)}`)
|
||||
toast.success(t('ai.resource.pageLoaded', { title: result.title.slice(0, 40) }))
|
||||
} catch {
|
||||
toast.error('Erreur lors du chargement de la page')
|
||||
toast.error(t('ai.resource.pageLoadError'))
|
||||
} finally {
|
||||
setResourceScraping(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleResourcePreview = async () => {
|
||||
if (!resourceText.trim()) { toast.error('Collez du texte ou chargez une URL d\'abord'); return }
|
||||
if (!resourceText.trim()) { toast.error(t('ai.resource.pasteOrUrlFirst')); return }
|
||||
if (resourceMode === 'replace') {
|
||||
setResourcePreview({ text: resourceText, source: 'paste' })
|
||||
return
|
||||
@@ -279,10 +280,10 @@ export function ContextualAIChat({
|
||||
}),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.error || 'Erreur IA')
|
||||
if (!res.ok) throw new Error(data.error || t('ai.resource.enrichError'))
|
||||
setResourcePreview({ text: data.enrichedContent, source: resourceMode })
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || 'Erreur lors de l\'enrichissement')
|
||||
toast.error(e.message || t('ai.resource.enrichError'))
|
||||
} finally {
|
||||
setResourceEnriching(false)
|
||||
}
|
||||
@@ -294,7 +295,7 @@ export function ContextualAIChat({
|
||||
setResourcePreview(null)
|
||||
setResourceText('')
|
||||
setResourceUrl('')
|
||||
toast.success('Contenu appliqué à la note ✓')
|
||||
toast.success(t('ai.resource.contentApplied'))
|
||||
}
|
||||
|
||||
/** Called from chat hover-actions: inject a chat message into the note */
|
||||
@@ -326,7 +327,7 @@ export function ContextualAIChat({
|
||||
if (!res.ok) throw new Error(data.error || 'Erreur IA')
|
||||
setResourcePreview({ text: data.enrichedContent, source: mode })
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || 'Erreur enrichissement')
|
||||
toast.error(e.message || t('ai.resource.enrichErrorShort'))
|
||||
} finally {
|
||||
setResourceEnriching(false)
|
||||
}
|
||||
@@ -351,7 +352,7 @@ export function ContextualAIChat({
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-base font-semibold text-foreground flex items-center gap-2">
|
||||
<Sparkles className="h-4 w-4 text-primary shrink-0" />
|
||||
IA Note
|
||||
{t('ai.aiNoteTitle')}
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{noteTitle ? `"${noteTitle}"` : t('ai.currentNote')}
|
||||
@@ -390,7 +391,7 @@ export function ContextualAIChat({
|
||||
{([
|
||||
{ id: 'chat', icon: Bot, label: t('ai.chatTab') },
|
||||
{ id: 'actions', icon: Wand2, label: t('ai.noteActions') },
|
||||
{ id: 'resource', icon: ArrowDownToLine, label: 'Ressource' },
|
||||
{ id: 'resource', icon: ArrowDownToLine, label: t('ai.resourceTab') },
|
||||
] as const).map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -466,23 +467,23 @@ export function ContextualAIChat({
|
||||
<button
|
||||
onClick={() => handleInjectFromChat(content, 'replace')}
|
||||
className="flex items-center gap-1 px-2 py-1 rounded-md text-[10px] font-medium bg-muted hover:bg-primary/10 hover:text-primary border border-border/40 transition-colors"
|
||||
title="Remplacer le contenu de la note par ce message"
|
||||
title={t('ai.injectReplaceTitle')}
|
||||
>
|
||||
<Download className="h-2.5 w-2.5" /> Remplacer
|
||||
<Download className="h-2.5 w-2.5" /> {t('ai.injectReplace')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleInjectFromChat(content, 'complete')}
|
||||
className="flex items-center gap-1 px-2 py-1 rounded-md text-[10px] font-medium bg-muted hover:bg-emerald-50 hover:text-emerald-600 dark:hover:bg-emerald-950/30 border border-border/40 transition-colors"
|
||||
title="Compléter la note avec ce message (IA)"
|
||||
title={t('ai.injectCompleteTitle')}
|
||||
>
|
||||
<PlusCircle className="h-2.5 w-2.5" /> Compléter
|
||||
<PlusCircle className="h-2.5 w-2.5" /> {t('ai.injectComplete')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleInjectFromChat(content, 'merge')}
|
||||
className="flex items-center gap-1 px-2 py-1 rounded-md text-[10px] font-medium bg-muted hover:bg-violet-50 hover:text-violet-600 dark:hover:bg-violet-950/30 border border-border/40 transition-colors"
|
||||
title="Fusionner avec la note (IA)"
|
||||
title={t('ai.injectMergeTitle')}
|
||||
>
|
||||
<GitMerge className="h-2.5 w-2.5" /> Fusionner
|
||||
<GitMerge className="h-2.5 w-2.5" /> {t('ai.injectMerge')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -695,7 +696,7 @@ export function ContextualAIChat({
|
||||
<div className="flex flex-col">
|
||||
<span>{t(action.i18nKey)}</span>
|
||||
{noteImages.length > 1 && (
|
||||
<span className="text-[10px] text-muted-foreground">{noteImages.length} images</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t('ai.imagesCount', { count: noteImages.length })}</span>
|
||||
)}
|
||||
</div>
|
||||
{loading && <span className="ml-auto text-[10px] text-muted-foreground">{t('ai.processingAction')}</span>}
|
||||
@@ -760,10 +761,10 @@ export function ContextualAIChat({
|
||||
<div className="px-4 py-2.5 border-b border-border/40 flex items-center justify-between shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-xs font-semibold text-foreground">
|
||||
{resourcePreview.source === 'chat' ? '💬 Depuis le chat'
|
||||
: resourcePreview.source === 'replace' ? '↓ Remplacement'
|
||||
: resourcePreview.source === 'complete' ? '✦ Complété par IA'
|
||||
: '⟳ Fusionné par IA'}
|
||||
{resourcePreview.source === 'chat' ? t('ai.resource.fromChat')
|
||||
: resourcePreview.source === 'replace' ? t('ai.resource.replacement')
|
||||
: resourcePreview.source === 'complete' ? t('ai.resource.completedByAI')
|
||||
: t('ai.resource.mergedByAI')}
|
||||
</p>
|
||||
{/* Format toggle */}
|
||||
<div className="flex rounded-md border border-border/40 overflow-hidden">
|
||||
@@ -776,7 +777,7 @@ export function ContextualAIChat({
|
||||
: 'bg-card text-muted-foreground hover:bg-muted',
|
||||
)}
|
||||
>
|
||||
<Eye className="h-2.5 w-2.5" /> Rendu
|
||||
<Eye className="h-2.5 w-2.5" /> {t('ai.resource.rendered')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setResourcePreviewFormat('markdown')}
|
||||
@@ -814,7 +815,7 @@ export function ContextualAIChat({
|
||||
className="flex-1 text-xs gap-1.5"
|
||||
onClick={() => setResourcePreview(null)}
|
||||
>
|
||||
<X className="h-3.5 w-3.5" /> Annuler
|
||||
<X className="h-3.5 w-3.5" /> {t('ai.resource.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -822,7 +823,7 @@ export function ContextualAIChat({
|
||||
onClick={handleApplyResourcePreview}
|
||||
disabled={!onApplyToNote}
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" /> Appliquer à la note
|
||||
<Check className="h-3.5 w-3.5" /> {t('ai.resource.applyToNote')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -832,7 +833,7 @@ export function ContextualAIChat({
|
||||
{/* URL loader */}
|
||||
<div>
|
||||
<label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground block mb-1.5">
|
||||
<Link2 className="h-3 w-3 inline mr-1" />URL (optionnel)
|
||||
<Link2 className="h-3 w-3 inline mr-1" />{t('ai.resource.urlLabel')}
|
||||
</label>
|
||||
<div className="flex gap-1.5">
|
||||
<input
|
||||
@@ -860,18 +861,18 @@ export function ContextualAIChat({
|
||||
{/* Text area */}
|
||||
<div>
|
||||
<label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground block mb-1.5">
|
||||
<FileText className="h-3 w-3 inline mr-1" />Texte de la ressource
|
||||
<FileText className="h-3 w-3 inline mr-1" />{t('ai.resource.resourceText')}
|
||||
</label>
|
||||
<textarea
|
||||
value={resourceText}
|
||||
onChange={e => setResourceText(e.target.value)}
|
||||
placeholder="Collez votre texte ici (markdown, HTML, texte brut…)"
|
||||
placeholder={t('ai.resource.resourcePlaceholder')}
|
||||
rows={8}
|
||||
className="w-full px-2.5 py-2 text-xs bg-card border border-border/60 rounded-lg focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20 resize-none transition-all font-mono leading-relaxed"
|
||||
/>
|
||||
{resourceText && (
|
||||
<p className="text-[9px] text-muted-foreground mt-0.5 text-right">
|
||||
{resourceText.split(/\s+/).filter(Boolean).length} mots
|
||||
{resourceText.split(/\s+/).filter(Boolean).length} {t('ai.resource.words')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -879,13 +880,13 @@ export function ContextualAIChat({
|
||||
{/* Mode selector */}
|
||||
<div>
|
||||
<label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground block mb-1.5">
|
||||
Mode d'intégration
|
||||
{t('ai.resource.integrationMode')}
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-1.5">
|
||||
{([
|
||||
{ id: 'replace', label: 'Remplacer', desc: 'Direct, sans IA', icon: Download, color: 'primary' },
|
||||
{ id: 'complete', label: 'Compléter', desc: 'Ajoute sans réécrire', icon: PlusCircle, color: 'emerald' },
|
||||
{ id: 'merge', label: 'Fusionner', desc: 'Réécrit et intègre', icon: GitMerge, color: 'violet' },
|
||||
{ id: 'replace', label: t('ai.resource.modeReplace'), desc: t('ai.resource.modeReplaceDesc'), icon: Download, color: 'primary' },
|
||||
{ id: 'complete', label: t('ai.resource.modeComplete'), desc: t('ai.resource.modeCompleteDesc'), icon: PlusCircle, color: 'emerald' },
|
||||
{ id: 'merge', label: t('ai.resource.modeMerge'), desc: t('ai.resource.modeMergeDesc'), icon: GitMerge, color: 'violet' },
|
||||
] as const).map(m => {
|
||||
const Icon = m.icon
|
||||
const sel = resourceMode === m.id
|
||||
@@ -920,17 +921,17 @@ export function ContextualAIChat({
|
||||
disabled={!resourceText.trim() || resourceEnriching}
|
||||
>
|
||||
{resourceEnriching
|
||||
? <><Loader2 className="h-4 w-4 animate-spin" /> IA en cours…</>
|
||||
? <><Loader2 className="h-4 w-4 animate-spin" /> {t('ai.resource.aiProcessing')}</>
|
||||
: resourceMode === 'replace'
|
||||
? <><Eye className="h-4 w-4" /> Aperçu</>
|
||||
: <><Sparkles className="h-4 w-4" /> Générer l'aperçu</>
|
||||
? <><Eye className="h-4 w-4" /> {t('ai.resource.preview')}</>
|
||||
: <><Sparkles className="h-4 w-4" /> {t('ai.resource.generatePreview')}</>
|
||||
}
|
||||
</Button>
|
||||
|
||||
{/* Hint */}
|
||||
{!noteContent && resourceMode !== 'replace' && (
|
||||
<p className="text-[10px] text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-800 rounded-lg px-3 py-2">
|
||||
💡 La note est vide — le contenu de la ressource sera intégré directement.
|
||||
{t('ai.resource.emptyNoteHint')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -941,3 +942,6 @@ export function ContextualAIChat({
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -221,6 +221,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
|
||||
// Paste handler: upload clipboard images
|
||||
useEffect(() => {
|
||||
const handlePaste = async (e: ClipboardEvent) => {
|
||||
if (noteType === 'richtext' && (e.target as HTMLElement)?.closest('.notion-editor')) return;
|
||||
const items = e.clipboardData?.items
|
||||
if (!items) return
|
||||
for (const item of Array.from(items)) {
|
||||
@@ -237,8 +238,8 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener('paste', handlePaste)
|
||||
return () => document.removeEventListener('paste', handlePaste)
|
||||
document.addEventListener('paste', handlePaste, { capture: true })
|
||||
return () => document.removeEventListener('paste', handlePaste, { capture: true } as any)
|
||||
}, [t])
|
||||
|
||||
const handleRemoveImage = (index: number) => {
|
||||
@@ -276,6 +277,11 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
|
||||
setLinks(links.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
const allImages = useMemo(() => {
|
||||
const extracted = noteType === 'richtext' ? extractImagesFromHTML(content) : [];
|
||||
return Array.from(new Set([...images, ...extracted]));
|
||||
}, [images, content, noteType]);
|
||||
|
||||
const handleGenerateTitles = async () => {
|
||||
// Combine content and link metadata for AI
|
||||
const fullContentForAI = [
|
||||
@@ -304,7 +310,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
|
||||
const response = await fetch('/api/ai/title-suggestions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content: fullContent }),
|
||||
body: JSON.stringify({ content: fullContentForAI }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -733,6 +739,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
|
||||
content={content}
|
||||
onChange={setContent}
|
||||
className="min-h-[200px]"
|
||||
onImageUpload={uploadImageFile}
|
||||
/>
|
||||
) : noteType === 'text' || noteType === 'markdown' ? (
|
||||
<div className="space-y-2">
|
||||
@@ -1158,3 +1165,7 @@ export function NoteEditor({ note, readOnly = false, onClose }: NoteEditorProps)
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -394,6 +394,7 @@ export function NoteInlineEditor({
|
||||
// Paste handler: upload clipboard images
|
||||
useEffect(() => {
|
||||
const handlePaste = async (e: ClipboardEvent) => {
|
||||
if (noteType === 'richtext' && (e.target as HTMLElement)?.closest('.notion-editor')) return;
|
||||
const items = e.clipboardData?.items
|
||||
if (!items) return
|
||||
for (const item of Array.from(items)) {
|
||||
@@ -412,8 +413,8 @@ export function NoteInlineEditor({
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener('paste', handlePaste)
|
||||
return () => document.removeEventListener('paste', handlePaste)
|
||||
document.addEventListener('paste', handlePaste, { capture: true })
|
||||
return () => document.removeEventListener('paste', handlePaste, { capture: true } as any)
|
||||
}, [note.id, note.images, onChange, t])
|
||||
|
||||
const handleRemoveImage = async (index: number) => {
|
||||
@@ -938,3 +939,5 @@ export function NoteInlineEditor({
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -452,6 +452,7 @@ export function NoteInput({
|
||||
// Paste handler: upload clipboard images
|
||||
useEffect(() => {
|
||||
const handlePaste = async (e: ClipboardEvent) => {
|
||||
if (type === 'richtext' && (e.target as HTMLElement)?.closest('.notion-editor')) return;
|
||||
const items = e.clipboardData?.items
|
||||
if (!items) return
|
||||
for (const item of Array.from(items)) {
|
||||
@@ -468,8 +469,8 @@ export function NoteInput({
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener('paste', handlePaste)
|
||||
return () => document.removeEventListener('paste', handlePaste)
|
||||
document.addEventListener('paste', handlePaste, { capture: true })
|
||||
return () => document.removeEventListener('paste', handlePaste, { capture: true } as any)
|
||||
}, [t])
|
||||
|
||||
// AI title from images
|
||||
@@ -774,6 +775,7 @@ export function NoteInput({
|
||||
content={content}
|
||||
onChange={setContent}
|
||||
className="min-h-[120px]"
|
||||
onImageUpload={uploadImageFile}
|
||||
/>
|
||||
) : type === 'text' || type === 'markdown' ? (
|
||||
<>
|
||||
@@ -1101,3 +1103,4 @@ export function NoteInput({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ import {
|
||||
Sparkles, Wand2, Scissors, Lightbulb, X, Check, ExternalLink,
|
||||
FileText, Pilcrow, MessageSquare, AlignLeft, AlignCenter, AlignRight,
|
||||
Superscript as SuperscriptIcon, Subscript as SubscriptIcon, Expand, Plus,
|
||||
} from 'lucide-react'
|
||||
SpellCheck, Languages, BookOpen } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export interface RichTextEditorHandle {
|
||||
getEditor: () => Editor | null
|
||||
@@ -38,6 +39,7 @@ interface RichTextEditorProps {
|
||||
onChange?: (content: string) => void
|
||||
className?: string
|
||||
placeholder?: string
|
||||
onImageUpload?: (file: File) => Promise<string>
|
||||
}
|
||||
|
||||
type SlashItem = {
|
||||
@@ -58,6 +60,7 @@ const CustomImage = Image.extend({
|
||||
...this.parent?.(),
|
||||
width: {
|
||||
default: '100%',
|
||||
parseHTML: element => element.style.width || element.getAttribute('width') || '100%',
|
||||
renderHTML: attributes => {
|
||||
if (!attributes.width) return {}
|
||||
return { style: `width: ${attributes.width}; max-width: 100%; height: auto;` }
|
||||
@@ -92,11 +95,11 @@ const slashCommands: SlashItem[] = [
|
||||
{ title: 'Développer', description: 'Élaborer et enrichir le texte', icon: Expand, category: 'IA Note', isAi: true, aiOption: 'clarify', command: () => {} },
|
||||
]
|
||||
|
||||
async function aiReformulate(text: string, option: 'clarify' | 'shorten' | 'improve'): Promise<string> {
|
||||
async function aiReformulate(text: string, option: string, language?: string): Promise<string> {
|
||||
const res = await fetch('/api/ai/reformulate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, option, format: 'html' }),
|
||||
body: JSON.stringify({ text, option, format: 'html', language }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.error || 'AI failed')
|
||||
@@ -129,7 +132,7 @@ function useImageInsert() {
|
||||
}
|
||||
|
||||
export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(
|
||||
function RichTextEditor({ content, onChange, className, placeholder }, ref) {
|
||||
function RichTextEditor({ content, onChange, className, placeholder, onImageUpload }, ref) {
|
||||
const { t } = useLanguage()
|
||||
const imageInsert = useImageInsert()
|
||||
|
||||
@@ -152,6 +155,27 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
||||
immediatelyRender: false,
|
||||
editorProps: {
|
||||
attributes: { class: 'notion-editor' },
|
||||
handlePaste: (view, event, slice) => {
|
||||
if (!onImageUpload) return false;
|
||||
const items = Array.from(event.clipboardData?.items || []);
|
||||
const hasImage = items.some(item => item.type.startsWith('image/'));
|
||||
if (!hasImage) return false;
|
||||
event.preventDefault();
|
||||
const images = items.filter(item => item.type.startsWith('image/')).map(item => item.getAsFile()).filter(f => f !== null) as File[];
|
||||
images.forEach(async (file) => {
|
||||
try {
|
||||
toast.info(t('notes.uploading'));
|
||||
const url = await onImageUpload(file);
|
||||
const { schema } = view.state;
|
||||
const node = schema.nodes.image.create({ src: url });
|
||||
const tr = view.state.tr.replaceSelectionWith(node);
|
||||
view.dispatch(tr);
|
||||
} catch (err) {
|
||||
toast.error(t('notes.uploadFailed'));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
},
|
||||
onUpdate: ({ editor: e }) => {
|
||||
const html = e.getHTML()
|
||||
@@ -253,7 +277,7 @@ function ImageModal({ onConfirm, onCancel }: { onConfirm: (url: string) => void;
|
||||
}
|
||||
|
||||
function BubbleToolbar({ editor }: { editor: Editor | null }) {
|
||||
const { t } = useLanguage()
|
||||
const { t, language } = useLanguage()
|
||||
const [, setTick] = useState(0)
|
||||
const [aiOpen, setAiOpen] = useState(false)
|
||||
const [aiLoading, setAiLoading] = useState(false)
|
||||
@@ -286,15 +310,19 @@ function BubbleToolbar({ editor }: { editor: Editor | null }) {
|
||||
{ icon: SubscriptIcon, active: editor.isActive('subscript'), action: () => editor.chain().focus().toggleSubscript().run(), title: t('richTextEditor.subscript') },
|
||||
]
|
||||
|
||||
const handleAI = async (option: 'clarify' | 'shorten' | 'improve') => {
|
||||
const handleAI = async (option: 'clarify' | 'shorten' | 'improve' | 'fix_grammar' | 'translate' | 'explain') => {
|
||||
const { from, to } = editor.state.selection
|
||||
const text = editor.state.doc.textBetween(from, to, ' ')
|
||||
if (!text || text.split(/\s+/).length < 5) return
|
||||
if (!text || text.split(/\s+/).length < 2) return
|
||||
setAiLoading(true)
|
||||
setAiOpen(false)
|
||||
try {
|
||||
const result = await aiReformulate(text, option)
|
||||
const result = await aiReformulate(text, option, language)
|
||||
if (option === 'explain') {
|
||||
toast.message(t('ai.action.explain'), { description: result, duration: 10000 })
|
||||
} else {
|
||||
editor.chain().focus().insertContentAt({ from, to }, result).run()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('AI error:', err)
|
||||
} finally {
|
||||
@@ -365,6 +393,9 @@ function BubbleToolbar({ editor }: { editor: Editor | null }) {
|
||||
<button className="notion-ai-subitem" onClick={() => handleAI('clarify')}><Lightbulb className="w-3.5 h-3.5 text-amber-500" /><span>{t('richTextEditor.slashClarify')}</span></button>
|
||||
<button className="notion-ai-subitem" onClick={() => handleAI('shorten')}><Scissors className="w-3.5 h-3.5 text-blue-500" /><span>{t('richTextEditor.slashShorten')}</span></button>
|
||||
<button className="notion-ai-subitem" onClick={() => handleAI('improve')}><Wand2 className="w-3.5 h-3.5 text-purple-500" /><span>{t('richTextEditor.slashImprove')}</span></button>
|
||||
<button className="notion-ai-subitem" onClick={() => handleAI('fix_grammar')}><SpellCheck className="w-3.5 h-3.5 text-green-500" /><span>{t('ai.action.fixGrammar')}</span></button>
|
||||
<button className="notion-ai-subitem" onClick={() => handleAI('translate')}><Languages className="w-3.5 h-3.5 text-indigo-500" /><span>{t('ai.action.translate')}</span></button>
|
||||
<button className="notion-ai-subitem" onClick={() => handleAI('explain')}><BookOpen className="w-3.5 h-3.5 text-orange-500" /><span>{t('ai.action.explain')}</span></button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -631,3 +662,7 @@ function SlashCommandMenu({ editor, onInsertImage }: { editor: Editor; onInsertI
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
@@ -10,7 +10,7 @@ import { LanguageDetectionService } from './language-detection.service'
|
||||
import { getTagsProvider } from '../factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
|
||||
export type RefactorMode = 'clarify' | 'shorten' | 'improveStyle'
|
||||
export type RefactorMode = 'clarify' | 'shorten' | 'improveStyle' | 'fix_grammar' | 'translate' | 'explain'
|
||||
|
||||
export interface RefactorOption {
|
||||
mode: RefactorMode
|
||||
@@ -68,7 +68,8 @@ export class ParagraphRefactorService {
|
||||
async refactor(
|
||||
content: string,
|
||||
mode: RefactorMode,
|
||||
format: 'html' | 'markdown' = 'markdown'
|
||||
format: 'html' | 'markdown' = 'markdown',
|
||||
targetLanguage?: string
|
||||
): Promise<RefactorResult> {
|
||||
// Validate word count
|
||||
const wordCount = content.split(/\s+/).length
|
||||
@@ -78,8 +79,9 @@ export class ParagraphRefactorService {
|
||||
)
|
||||
}
|
||||
|
||||
// Detect language
|
||||
const { language } = await this.languageDetection.detectLanguage(content)
|
||||
// Detect language or use provided target language
|
||||
const { language: detectedLanguage } = await this.languageDetection.detectLanguage(content)
|
||||
const language = targetLanguage || detectedLanguage
|
||||
|
||||
try {
|
||||
// Build prompts
|
||||
@@ -233,7 +235,24 @@ Remove fluff, repetition, and unnecessary words, but keep the substance.${format
|
||||
Your goal: Enhance the text's style, vocabulary, sentence structure, and overall quality.
|
||||
|
||||
CRITICAL LANGUAGE RULE: You MUST respond in the EXACT SAME LANGUAGE as the input text. If input is French, output MUST be French. If input is German, output MUST be German. NEVER translate to English.
|
||||
Maintain similar length but make it sound more professional and polished.${formatInstruction}`
|
||||
Maintain similar length but make it sound more professional and polished.${formatInstruction}`,
|
||||
|
||||
fix_grammar: `You are an expert proofreader.
|
||||
Your goal: Fix spelling, grammar, and punctuation errors in the text without changing its meaning, tone, or style.
|
||||
|
||||
CRITICAL LANGUAGE RULE: You MUST respond in the EXACT SAME LANGUAGE as the input text. NEVER translate to English.
|
||||
Make minimal changes, only correcting errors.${formatInstruction}`,
|
||||
|
||||
translate: `You are a professional translator.
|
||||
Your goal: Translate the text perfectly into the target language requested by the user.
|
||||
Ensure the translation sounds natural and preserves the original tone and formatting.${formatInstruction}`,
|
||||
|
||||
explain: `You are an expert teacher and encyclopedia.
|
||||
Your goal: Explain the selected text, concept, or word clearly and concisely.
|
||||
Provide context, definitions, or relevant information to help the user understand it.
|
||||
|
||||
CRITICAL LANGUAGE RULE: You MUST explain it in the requested language (which is the user's interface language).
|
||||
Keep it concise but informative.${formatInstruction}`
|
||||
}
|
||||
|
||||
return prompts[mode]
|
||||
@@ -254,7 +273,17 @@ Please shorten this ${language} text while keeping all key information:`,
|
||||
|
||||
improveStyle: `IMPORTANT: The text below is in ${language}. Your response MUST be in ${language}. Do NOT translate to English.
|
||||
|
||||
Please improve the style and readability of this ${language} text:`
|
||||
Please improve the style and readability of this ${language} text:`,
|
||||
|
||||
fix_grammar: `IMPORTANT: The text below is in ${language}. Your response MUST be in ${language}. Do NOT translate to English.
|
||||
|
||||
Please fix any spelling, grammar, or punctuation errors in this ${language} text:`,
|
||||
|
||||
translate: `Please translate the following text into ${language}.
|
||||
Only return the translated text, nothing else.`,
|
||||
|
||||
explain: `Please explain the following text/concept in ${language}.
|
||||
Keep the explanation clear, educational, and concise.`
|
||||
}
|
||||
|
||||
return `${instructions[mode]}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"resetPassword": "Reset Password",
|
||||
"resetPasswordInstructions": "Enter your email to reset password",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"noAccount": "Don't have an account?",
|
||||
"noAccount": "Don\u0027t have an account?",
|
||||
"hasAccount": "Already have an account?",
|
||||
"signInToAccount": "Sign in to your account",
|
||||
"createAccount": "Create your account",
|
||||
@@ -22,7 +22,7 @@
|
||||
"resetEmailSent": "We have sent a password reset link to your email address if it exists in our system.",
|
||||
"returnToLogin": "Return to Login",
|
||||
"forgotPasswordTitle": "Forgot Password",
|
||||
"forgotPasswordDescription": "Enter your email address and we'll send you a link to reset your password.",
|
||||
"forgotPasswordDescription": "Enter your email address and we\u0027ll send you a link to reset your password.",
|
||||
"sending": "Sending...",
|
||||
"sendResetLink": "Send Reset Link",
|
||||
"backToLogin": "Back to login",
|
||||
@@ -347,7 +347,7 @@
|
||||
"created": "{count} labels created successfully",
|
||||
"analyzing": "Analyzing your notes...",
|
||||
"title": "New Label Suggestions",
|
||||
"description": "I've detected recurring themes in \"{notebookName}\" ({totalNotes} notes). Create labels for them?",
|
||||
"description": "I\u0027ve detected recurring themes in \"{notebookName}\" ({totalNotes} notes). Create labels for them?",
|
||||
"note": "note",
|
||||
"notes": "notes",
|
||||
"typeContent": "Type content to get label suggestions...",
|
||||
@@ -431,11 +431,14 @@
|
||||
"shorten": "Shorten",
|
||||
"improve": "Improve",
|
||||
"toMarkdown": "To Markdown",
|
||||
"describeImages": "Describe images"
|
||||
"describeImages": "Describe images",
|
||||
"fixGrammar": "Fix Grammar",
|
||||
"translate": "Translate",
|
||||
"explain": "Explain"
|
||||
},
|
||||
"openAssistant": "Open AI Assistant",
|
||||
"poweredByMomento": "Powered by Momento AI",
|
||||
"welcomeMsg": "Hello! I'm your AI assistant. How can I help you with your notes today? I can help refine tone, expand messaging, or summarize content.",
|
||||
"welcomeMsg": "Hello! I\u0027m your AI assistant. How can I help you with your notes today? I can help refine tone, expand messaging, or summarize content.",
|
||||
"summaryLast5": "Summary of your last 5 notes",
|
||||
"analyzingProgress": "Analyzing...",
|
||||
"generateInsightsBtn": "Generate Insights",
|
||||
@@ -448,7 +451,47 @@
|
||||
"aiCopilot": "AI Copilot",
|
||||
"suggestTitle": "AI title suggestion",
|
||||
"generateTitleFromImage": "Generate title from image",
|
||||
"titleGenerated": "Title generated from image"
|
||||
"titleGenerated": "Title generated from image",
|
||||
"resourceTab": "Resource",
|
||||
"aiNoteTitle": "AI Note",
|
||||
"injectReplace": "Replace",
|
||||
"injectReplaceTitle": "Replace note content with this message",
|
||||
"injectComplete": "Complete",
|
||||
"injectCompleteTitle": "Complete note with this message (AI)",
|
||||
"injectMerge": "Merge",
|
||||
"injectMergeTitle": "Merge with note (AI)",
|
||||
"imagesCount": "{count} images",
|
||||
"resource": {
|
||||
"failedToLoadUrl": "Failed to load this URL",
|
||||
"pageLoaded": "Page loaded: {title}",
|
||||
"pageLoadError": "Error loading the page",
|
||||
"pasteOrUrlFirst": "Paste text or load a URL first",
|
||||
"enrichError": "Enrichment error",
|
||||
"enrichErrorShort": "Enrichment error",
|
||||
"contentApplied": "Content applied to note ✓",
|
||||
"fromChat": "💬 From chat",
|
||||
"replacement": "↓ Replacement",
|
||||
"completedByAI": "✦ Completed by AI",
|
||||
"mergedByAI": "⟳ Merged by AI",
|
||||
"rendered": "Rendered",
|
||||
"cancel": "Cancel",
|
||||
"applyToNote": "Apply to note",
|
||||
"urlLabel": "URL (optional)",
|
||||
"resourceText": "Resource text",
|
||||
"resourcePlaceholder": "Paste your text here (markdown, HTML, plain text…)",
|
||||
"words": "words",
|
||||
"integrationMode": "Integration mode",
|
||||
"modeReplace": "Replace",
|
||||
"modeReplaceDesc": "Direct, no AI",
|
||||
"modeComplete": "Complete",
|
||||
"modeCompleteDesc": "Adds without rewriting",
|
||||
"modeMerge": "Merge",
|
||||
"modeMergeDesc": "Rewrites and integrates",
|
||||
"aiProcessing": "AI processing…",
|
||||
"preview": "Preview",
|
||||
"generatePreview": "Generate preview",
|
||||
"emptyNoteHint": "💡 The note is empty — the resource content will be integrated directly."
|
||||
}
|
||||
},
|
||||
"titleSuggestions": {
|
||||
"available": "Title suggestions",
|
||||
@@ -480,7 +523,7 @@
|
||||
"notHelpful": "Not Helpful",
|
||||
"dismiss": "Dismiss for now",
|
||||
"thanksFeedback": "Thanks for your feedback!",
|
||||
"thanksFeedbackImproving": "Thanks! We'll use this to improve.",
|
||||
"thanksFeedbackImproving": "Thanks! We\u0027ll use this to improve.",
|
||||
"connections": "Connections",
|
||||
"connection": "connection",
|
||||
"connectionsBadge": "{count} connection{plural}",
|
||||
@@ -524,7 +567,7 @@
|
||||
"mergeNotes": "Merge {count} note(s)",
|
||||
"notesToMerge": "📝 Notes to merge",
|
||||
"optionalPrompt": "💬 Fusion prompt (optional)",
|
||||
"promptPlaceholder": "Optional instructions for AI (e.g., 'Keep the formal style of note 1')...",
|
||||
"promptPlaceholder": "Optional instructions for AI (e.g., \u0027Keep the formal style of note 1\u0027)...",
|
||||
"generateFusion": "Generate the fusion",
|
||||
"generating": "Generating...",
|
||||
"previewTitle": "📝 Preview of merged note",
|
||||
@@ -702,7 +745,15 @@
|
||||
"providerDesc": "Choose your preferred AI provider",
|
||||
"providerAutoDesc": "Ollama when available, OpenAI fallback",
|
||||
"providerOllamaDesc": "100% private, runs locally on your machine",
|
||||
"providerOpenAIDesc": "Most accurate, requires API key"
|
||||
"providerOpenAIDesc": "Most accurate, requires API key",
|
||||
"aiNote": "AI Note",
|
||||
"aiNoteDesc": "Enable AI chat button and text improvement tools",
|
||||
"languageDetection": "Language detection",
|
||||
"languageDetectionDesc": "Automatically detects the language of your notes",
|
||||
"autoLabeling": "Label suggestions",
|
||||
"autoLabelingDesc": "Automatically suggests and applies labels to your notes",
|
||||
"noteHistory": "Note history",
|
||||
"noteHistoryDesc": "Enable version snapshots and restoration from History"
|
||||
},
|
||||
"general": {
|
||||
"loading": "Loading...",
|
||||
@@ -845,7 +896,7 @@
|
||||
"updateFailed": "Failed to update AI settings",
|
||||
"providerTagsRequired": "AI_PROVIDER_TAGS is required",
|
||||
"providerEmbeddingRequired": "AI_PROVIDER_EMBEDDING is required",
|
||||
"providerOllamaOption": "🦙 Ollama (Local & Free)",
|
||||
"providerOllamaOption": "🦙 Ollama (Local \u0026 Free)",
|
||||
"providerOpenAIOption": "🤖 OpenAI (GPT-5, GPT-4)",
|
||||
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
|
||||
"providerDeepSeekOption": "🔍 DeepSeek",
|
||||
@@ -1062,7 +1113,7 @@
|
||||
"paragraphReformulation": "Paragraph reformulation",
|
||||
"memoryEcho": "Memory Echo daily insights",
|
||||
"notebookOrganization": "Notebook organization",
|
||||
"dragDrop": "Drag & drop note management",
|
||||
"dragDrop": "Drag \u0026 drop note management",
|
||||
"labelSystem": "Label system",
|
||||
"multipleProviders": "Multiple AI providers (OpenAI, Ollama)"
|
||||
},
|
||||
@@ -1099,9 +1150,9 @@
|
||||
"directImpact": "Direct Impact",
|
||||
"sponsorPerks": "Sponsor Perks",
|
||||
"transparency": "Transparency",
|
||||
"transparencyDescription": "I believe in complete transparency. Here's how donations are used:",
|
||||
"hostingServers": "Hosting & servers:",
|
||||
"domainSSL": "Domain & SSL:",
|
||||
"transparencyDescription": "I believe in complete transparency. Here\u0027s how donations are used:",
|
||||
"hostingServers": "Hosting \u0026 servers:",
|
||||
"domainSSL": "Domain \u0026 SSL:",
|
||||
"aiApiCosts": "AI API costs:",
|
||||
"totalExpenses": "Total expenses:",
|
||||
"otherWaysTitle": "Other Ways to Support",
|
||||
@@ -1132,7 +1183,7 @@
|
||||
"confirmNewPassword": "Confirm New Password",
|
||||
"resetting": "Resetting...",
|
||||
"resetPassword": "Reset Password",
|
||||
"passwordMismatch": "Passwords don't match",
|
||||
"passwordMismatch": "Passwords don\u0027t match",
|
||||
"success": "Password reset successfully. You can now login.",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
@@ -1209,7 +1260,7 @@
|
||||
"openingConnection": "Opening connection...",
|
||||
"openConnectionFailed": "Failed to open connection",
|
||||
"thanksFeedback": "Thanks for your feedback!",
|
||||
"thanksFeedbackImproving": "Thanks! We'll use this to improve.",
|
||||
"thanksFeedbackImproving": "Thanks! We\u0027ll use this to improve.",
|
||||
"feedbackFailed": "Failed to submit feedback",
|
||||
"notesFusionSuccess": "Notes merged successfully!"
|
||||
},
|
||||
@@ -1358,7 +1409,7 @@
|
||||
"generating": "Generating...",
|
||||
"generate": "Generate",
|
||||
"successTitle": "API Key Generated",
|
||||
"successDescription": "Copy your API key now. You won't be able to see it again.",
|
||||
"successDescription": "Copy your API key now. You won\u0027t be able to see it again.",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied!",
|
||||
"done": "Done"
|
||||
@@ -1416,7 +1467,7 @@
|
||||
"targetNotebook": "Target notebook",
|
||||
"inbox": "Inbox",
|
||||
"instructions": "AI Instructions",
|
||||
"instructionsPlaceholder": "Describe the agent's behavior...",
|
||||
"instructionsPlaceholder": "Describe the agent\u0027s behavior...",
|
||||
"frequency": "Frequency",
|
||||
"cancel": "Cancel",
|
||||
"saving": "Saving...",
|
||||
@@ -1431,7 +1482,7 @@
|
||||
"researchTopic": "Research topic",
|
||||
"researchTopicPlaceholder": "e.g. Latest advances in quantum computing",
|
||||
"notifyEmail": "Email notification",
|
||||
"notifyEmailHint": "Receive an email with the agent's results after each run",
|
||||
"notifyEmailHint": "Receive an email with the agent\u0027s results after each run",
|
||||
"includeImages": "Include images",
|
||||
"includeImagesHint": "Extract images from scraped pages and attach them to the generated note"
|
||||
},
|
||||
@@ -1552,26 +1603,26 @@
|
||||
"howToUse": "How to use an agent?",
|
||||
"howToUseContent": "1. Click **\"New Agent\"** (or start from a **Template** at the bottom of the page)\n2. Choose an **agent type** (Researcher, Monitor, Observer, Custom)\n3. Give it a **name** and fill in the type-specific fields\n4. Optionally pick a **target notebook** where results will be saved\n5. Choose a **frequency** (Manual = you trigger it yourself)\n6. Click **Create**, then hit the **Run** button on the agent card\n7. Once finished, a new note appears in your target notebook",
|
||||
"types": "Agent types",
|
||||
"typesContent": "### Researcher\nSearches the web on a **topic you define** and creates a structured note with sources and references.\n\n- **Fields:** name, research topic (e.g. \"Latest advances in quantum computing\")\n- **Default tools:** web search, web scraping, note search, note creation\n- **Requirements:** a web search provider must be configured (SearXNG or Brave Search)\n\n### Monitor (Scraper)\nScrapes a **list of URLs** you specify and produces a summary of their content.\n\n- **Fields:** name, list of URLs (websites or RSS feeds)\n- **Default tools:** web scraping, note creation\n- **RSS tip:** Use RSS feed URLs (e.g. `site.com/feed`) to automatically scrape individual articles instead of listing pages\n- **Use case:** weekly tech watch, competitor monitoring, blog roundups\n\n### Observer (Notebook Monitor)\nReads notes from a **notebook you select** and produces analysis, connections, and suggestions.\n\n- **Fields:** name, source notebook (the one to analyze)\n- **Default tools:** note search, note read, note creation\n- **Use case:** find connections between your notes, get reading suggestions, detect recurring themes\n\n### Custom\nA blank canvas: you write your own **prompt** and pick your own **tools**.\n\n- **Fields:** name, description, custom instructions (in Advanced mode)\n- **No default tools** — you choose exactly what the agent needs\n- **Use case:** anything creative or specific that doesn't fit the other types",
|
||||
"typesContent": "### Researcher\nSearches the web on a **topic you define** and creates a structured note with sources and references.\n\n- **Fields:** name, research topic (e.g. \"Latest advances in quantum computing\")\n- **Default tools:** web search, web scraping, note search, note creation\n- **Requirements:** a web search provider must be configured (SearXNG or Brave Search)\n\n### Monitor (Scraper)\nScrapes a **list of URLs** you specify and produces a summary of their content.\n\n- **Fields:** name, list of URLs (websites or RSS feeds)\n- **Default tools:** web scraping, note creation\n- **RSS tip:** Use RSS feed URLs (e.g. `site.com/feed`) to automatically scrape individual articles instead of listing pages\n- **Use case:** weekly tech watch, competitor monitoring, blog roundups\n\n### Observer (Notebook Monitor)\nReads notes from a **notebook you select** and produces analysis, connections, and suggestions.\n\n- **Fields:** name, source notebook (the one to analyze)\n- **Default tools:** note search, note read, note creation\n- **Use case:** find connections between your notes, get reading suggestions, detect recurring themes\n\n### Custom\nA blank canvas: you write your own **prompt** and pick your own **tools**.\n\n- **Fields:** name, description, custom instructions (in Advanced mode)\n- **No default tools** — you choose exactly what the agent needs\n- **Use case:** anything creative or specific that doesn\u0027t fit the other types",
|
||||
"advanced": "Advanced mode (AI Instructions, Max iterations)",
|
||||
"advancedContent": "Click **\"Advanced mode\"** at the bottom of the form to access additional settings.\n\n### AI Instructions\n\nThis field lets you **replace the default system prompt** for the agent. If left empty, the agent uses an automatic prompt adapted to its type.\n\n**Why use it?** You want to control exactly how the agent behaves. For example:\n- \"Write the summary in English, even if sources are in French\"\n- \"Structure the note with sections: Context, Key Points, Personal Opinion\"\n- \"Ignore articles older than 30 days and focus on recent news\"\n- \"For each detected theme, suggest 3 follow-up leads with links\"\n\n> **Note:** Your instructions replace the defaults, they don't add to them.\n\n### Max iterations\n\nThis is the **maximum number of cycles** the agent can perform. One cycle = the agent thinks, calls a tool, reads the result, then decides the next action.\n\n- **3-5 iterations:** for simple tasks (scraping a single page)\n- **10 iterations (default):** good balance for most cases\n- **15-25 iterations:** for deep research where the agent needs to explore multiple leads\n\n> **Warning:** More iterations = more time and potentially higher API costs.",
|
||||
"advancedContent": "Click **\"Advanced mode\"** at the bottom of the form to access additional settings.\n\n### AI Instructions\n\nThis field lets you **replace the default system prompt** for the agent. If left empty, the agent uses an automatic prompt adapted to its type.\n\n**Why use it?** You want to control exactly how the agent behaves. For example:\n- \"Write the summary in English, even if sources are in French\"\n- \"Structure the note with sections: Context, Key Points, Personal Opinion\"\n- \"Ignore articles older than 30 days and focus on recent news\"\n- \"For each detected theme, suggest 3 follow-up leads with links\"\n\n\u003e **Note:** Your instructions replace the defaults, they don\u0027t add to them.\n\n### Max iterations\n\nThis is the **maximum number of cycles** the agent can perform. One cycle = the agent thinks, calls a tool, reads the result, then decides the next action.\n\n- **3-5 iterations:** for simple tasks (scraping a single page)\n- **10 iterations (default):** good balance for most cases\n- **15-25 iterations:** for deep research where the agent needs to explore multiple leads\n\n\u003e **Warning:** More iterations = more time and potentially higher API costs.",
|
||||
"tools": "Available tools (full details)",
|
||||
"toolsContent": "When advanced mode is enabled, you can choose exactly which tools the agent can use.\n\n### Web Search\nAllows the agent to **search the internet** via SearXNG or Brave Search.\n\n- **What it does:** The agent formulates a query, gets search results, and can then scrape the most relevant pages.\n- **When to enable:** When the agent needs to find information on a topic (Researcher or Custom type).\n- **Configuration required:** SearXNG (with JSON format enabled) or a Brave Search API key. Configurable in **Admin > Agent Tools**.\n- **Example:** The agent searches \"React Server Components best practices 2025\", gets 10 results, then scrapes the top 3.\n\n### Web Scrape\nAllows the agent to **extract text content from a web page** given its URL.\n\n- **What it does:** The agent visits a URL and retrieves the structured text (headings, paragraphs, lists). Ads, menus and footers are typically filtered out.\n- **RSS/Atom support:** If the URL is an RSS feed, the tool automatically detects it, parses the feed and scrapes the 5 latest articles individually. Use RSS feed URLs for much richer content than listing pages.\n- **When to enable:** For the Monitor type (mandatory), or any agent that needs to read web pages.\n- **Configuration:** Works out of the box, but a **Jina Reader API key** improves quality and removes rate limits. Configurable in **Admin > Agent Tools**.\n- **Example:** The agent scrapes the RSS feed at `techcrunch.com/feed/` and gets the 5 latest full articles.\n\n### Note Search\nAllows the agent to **search your existing notes**.\n\n- **What it does:** The agent performs a text search across all your notes (or a specific notebook).\n- **When to enable:** For Observer-type agents, or any agent that needs to cross-reference information with your notes.\n- **Configuration:** None — works immediately.\n- **Example:** The agent searches all notes containing \"machine learning\" to see what you've already written on the topic.\n\n### Read Note\nAllows the agent to **read the full content of a specific note**.\n\n- **What it does:** After finding a note (via Note Search), the agent can read its entire content to analyze or use it.\n- **When to enable:** As a companion to Note Search. Enable both together so the agent can search AND read.\n- **Configuration:** None.\n- **Example:** The agent finds 5 notes about \"productivity\", reads them all, and writes a synthesis.\n\n### Create Note\nAllows the agent to **write a new note** in your target notebook.\n\n- **What it does:** The agent creates a note with a title and content. This is how results end up in your notebooks.\n- **When to enable:** Almost always — without this tool, the agent cannot save its results. **Leave it enabled by default.**\n- **Configuration:** None.\n- **Example:** The agent creates a note \"Tech Watch - Week 16\" with a summary of 5 articles.\n\n### Fetch URL\nAllows the agent to **download the raw content of a URL** (HTML, JSON, text...).\n\n- **What it does:** Unlike scraping which extracts clean text, Fetch URL retrieves raw content. Useful for APIs, JSON files, or non-standard pages.\n- **When to enable:** When the agent needs to query REST APIs, read RSS feeds, or access raw data.\n- **Configuration:** None.\n- **Example:** The agent queries the GitHub API to list the latest commits of a project.\n\n### Memory\nAllows the agent to **access its previous execution history**.\n\n- **What it does:** The agent can search through results from past runs. This lets it compare, track changes, or avoid repeating the same information.\n- **When to enable:** For agents that run regularly and need to maintain continuity between executions.\n- **Configuration:** None.\n- **Example:** The agent compares this week's news with last week's and highlights what's new.",
|
||||
"frequency": "Frequency & scheduling",
|
||||
"frequencyContent": "| Frequency | Behavior\n|-----------|----------\n| **Manual** | You click \"Run\" yourself — no automatic scheduling\n| **Hourly** | Runs every hour\n| **Daily** | Runs once per day\n| **Weekly** | Runs once per week\n| **Monthly** | Runs once per month\n\n> **Tip:** Start with \"Manual\" to test your agent, then switch to an automatic frequency once you're satisfied with the results.",
|
||||
"toolsContent": "When advanced mode is enabled, you can choose exactly which tools the agent can use.\n\n### Web Search\nAllows the agent to **search the internet** via SearXNG or Brave Search.\n\n- **What it does:** The agent formulates a query, gets search results, and can then scrape the most relevant pages.\n- **When to enable:** When the agent needs to find information on a topic (Researcher or Custom type).\n- **Configuration required:** SearXNG (with JSON format enabled) or a Brave Search API key. Configurable in **Admin \u003e Agent Tools**.\n- **Example:** The agent searches \"React Server Components best practices 2025\", gets 10 results, then scrapes the top 3.\n\n### Web Scrape\nAllows the agent to **extract text content from a web page** given its URL.\n\n- **What it does:** The agent visits a URL and retrieves the structured text (headings, paragraphs, lists). Ads, menus and footers are typically filtered out.\n- **RSS/Atom support:** If the URL is an RSS feed, the tool automatically detects it, parses the feed and scrapes the 5 latest articles individually. Use RSS feed URLs for much richer content than listing pages.\n- **When to enable:** For the Monitor type (mandatory), or any agent that needs to read web pages.\n- **Configuration:** Works out of the box, but a **Jina Reader API key** improves quality and removes rate limits. Configurable in **Admin \u003e Agent Tools**.\n- **Example:** The agent scrapes the RSS feed at `techcrunch.com/feed/` and gets the 5 latest full articles.\n\n### Note Search\nAllows the agent to **search your existing notes**.\n\n- **What it does:** The agent performs a text search across all your notes (or a specific notebook).\n- **When to enable:** For Observer-type agents, or any agent that needs to cross-reference information with your notes.\n- **Configuration:** None — works immediately.\n- **Example:** The agent searches all notes containing \"machine learning\" to see what you\u0027ve already written on the topic.\n\n### Read Note\nAllows the agent to **read the full content of a specific note**.\n\n- **What it does:** After finding a note (via Note Search), the agent can read its entire content to analyze or use it.\n- **When to enable:** As a companion to Note Search. Enable both together so the agent can search AND read.\n- **Configuration:** None.\n- **Example:** The agent finds 5 notes about \"productivity\", reads them all, and writes a synthesis.\n\n### Create Note\nAllows the agent to **write a new note** in your target notebook.\n\n- **What it does:** The agent creates a note with a title and content. This is how results end up in your notebooks.\n- **When to enable:** Almost always — without this tool, the agent cannot save its results. **Leave it enabled by default.**\n- **Configuration:** None.\n- **Example:** The agent creates a note \"Tech Watch - Week 16\" with a summary of 5 articles.\n\n### Fetch URL\nAllows the agent to **download the raw content of a URL** (HTML, JSON, text...).\n\n- **What it does:** Unlike scraping which extracts clean text, Fetch URL retrieves raw content. Useful for APIs, JSON files, or non-standard pages.\n- **When to enable:** When the agent needs to query REST APIs, read RSS feeds, or access raw data.\n- **Configuration:** None.\n- **Example:** The agent queries the GitHub API to list the latest commits of a project.\n\n### Memory\nAllows the agent to **access its previous execution history**.\n\n- **What it does:** The agent can search through results from past runs. This lets it compare, track changes, or avoid repeating the same information.\n- **When to enable:** For agents that run regularly and need to maintain continuity between executions.\n- **Configuration:** None.\n- **Example:** The agent compares this week\u0027s news with last week\u0027s and highlights what\u0027s new.",
|
||||
"frequency": "Frequency \u0026 scheduling",
|
||||
"frequencyContent": "| Frequency | Behavior\n|-----------|----------\n| **Manual** | You click \"Run\" yourself — no automatic scheduling\n| **Hourly** | Runs every hour\n| **Daily** | Runs once per day\n| **Weekly** | Runs once per week\n| **Monthly** | Runs once per month\n\n\u003e **Tip:** Start with \"Manual\" to test your agent, then switch to an automatic frequency once you\u0027re satisfied with the results.",
|
||||
"targetNotebook": "Target notebook",
|
||||
"targetNotebookContent": "When an agent finishes its task, it **creates a note**. The **target notebook** determines where that note goes:\n\n- **Inbox** (default) — the note goes to your general notes\n- **Specific notebook** — choose a notebook to keep agent results organized\n\n> **Tip:** Create a dedicated notebook like \"Agent Reports\" to keep all automated content in one place.",
|
||||
"targetNotebookContent": "When an agent finishes its task, it **creates a note**. The **target notebook** determines where that note goes:\n\n- **Inbox** (default) — the note goes to your general notes\n- **Specific notebook** — choose a notebook to keep agent results organized\n\n\u003e **Tip:** Create a dedicated notebook like \"Agent Reports\" to keep all automated content in one place.",
|
||||
"templates": "Templates",
|
||||
"templatesContent": "Templates are pre-configured agents ready to install in one click. You'll find them at the **bottom of the Agents page**.\n\nAvailable templates:\n\n- **AI Watch** — weekly roundup via RSS feeds from 6 AI sites (The Verge, TechCrunch, Ars Technica, MIT Tech Review, WIRED, Korben)\n- **Tech Watch** — daily summary via RSS feeds from Hacker News, DEV Community, Product Hunt\n- **Dev Watch** — developer news via RSS feeds from DEV (JavaScript, TypeScript, React)\n- **Note Observer** — analyzes a notebook and suggests connections\n- **Topic Researcher** — deep research on a specific topic\n\nTemplates come with the right tools pre-configured. You can customize them after installation.",
|
||||
"tips": "Tips & troubleshooting",
|
||||
"tipsContent": "- **Start with a template** and customize it — it's the fastest way to get a working agent\n- **Test with \"Manual\"** frequency before enabling automatic scheduling\n- **Use RSS feed URLs** instead of listing pages for much richer content (e.g. `techcrunch.com/feed/` instead of `techcrunch.com/category/ai/`)\n- **A \"Researcher\" agent requires a web search provider** — configure SearXNG (JSON format) or Brave Search in **Admin > Agent Tools**\n- **If an agent fails**, click on its card then **History** to see the execution log and tool traces\n- **The \"Enabled/Disabled\" toggle** lets you pause an agent without deleting it\n- **Web scraping quality** improves with a Jina Reader API key (optional, in Admin > Agent Tools)\n- **Combine \"Note Search\" + \"Read Note\"** so the agent can find AND analyze your notes' content\n- **Enable \"Memory\"** if your agent runs regularly — it will avoid repeating the same information across runs\n- **Agents respond in your language** — switch between French and English in settings",
|
||||
"templatesContent": "Templates are pre-configured agents ready to install in one click. You\u0027ll find them at the **bottom of the Agents page**.\n\nAvailable templates:\n\n- **AI Watch** — weekly roundup via RSS feeds from 6 AI sites (The Verge, TechCrunch, Ars Technica, MIT Tech Review, WIRED, Korben)\n- **Tech Watch** — daily summary via RSS feeds from Hacker News, DEV Community, Product Hunt\n- **Dev Watch** — developer news via RSS feeds from DEV (JavaScript, TypeScript, React)\n- **Note Observer** — analyzes a notebook and suggests connections\n- **Topic Researcher** — deep research on a specific topic\n\nTemplates come with the right tools pre-configured. You can customize them after installation.",
|
||||
"tips": "Tips \u0026 troubleshooting",
|
||||
"tipsContent": "- **Start with a template** and customize it — it\u0027s the fastest way to get a working agent\n- **Test with \"Manual\"** frequency before enabling automatic scheduling\n- **Use RSS feed URLs** instead of listing pages for much richer content (e.g. `techcrunch.com/feed/` instead of `techcrunch.com/category/ai/`)\n- **A \"Researcher\" agent requires a web search provider** — configure SearXNG (JSON format) or Brave Search in **Admin \u003e Agent Tools**\n- **If an agent fails**, click on its card then **History** to see the execution log and tool traces\n- **The \"Enabled/Disabled\" toggle** lets you pause an agent without deleting it\n- **Web scraping quality** improves with a Jina Reader API key (optional, in Admin \u003e Agent Tools)\n- **Combine \"Note Search\" + \"Read Note\"** so the agent can find AND analyze your notes\u0027 content\n- **Enable \"Memory\"** if your agent runs regularly — it will avoid repeating the same information across runs\n- **Agents respond in your language** — switch between French and English in settings",
|
||||
"tooltips": {
|
||||
"agentType": "Choose the type of task the agent will perform. Each type has different capabilities and fields.",
|
||||
"researchTopic": "The subject the agent will research on the web. Be specific for better results.",
|
||||
"description": "A short description of what this agent does. Helps you remember its purpose.",
|
||||
"urls": "List of URLs to scrape. Supports RSS feeds — use feed URLs for richer content (e.g. site.com/feed).",
|
||||
"sourceNotebook": "The notebook the agent will analyze. It reads notes from this notebook to find connections and themes.",
|
||||
"targetNotebook": "Where the agent's result note will be saved. Choose Inbox or a specific notebook.",
|
||||
"targetNotebook": "Where the agent\u0027s result note will be saved. Choose Inbox or a specific notebook.",
|
||||
"frequency": "How often the agent runs automatically. Start with Manual to test.",
|
||||
"instructions": "Custom instructions that replace the default AI prompt. Leave empty to use the automatic prompt.",
|
||||
"tools": "Select which tools the agent can use. Each tool gives the agent a specific capability.",
|
||||
@@ -1598,7 +1649,7 @@
|
||||
"deleteError": "Error deleting",
|
||||
"renamed": "Conversation renamed",
|
||||
"renameError": "Error renaming",
|
||||
"welcome": "I'm here to help you synthesize your notes, generate new ideas, or discuss your notebooks.",
|
||||
"welcome": "I\u0027m here to help you synthesize your notes, generate new ideas, or discuss your notebooks.",
|
||||
"searching": "Searching...",
|
||||
"noNotesFoundForContext": "No relevant notes found for this question. Answer with your general knowledge.",
|
||||
"webSearch": "Web Search",
|
||||
@@ -1625,5 +1676,76 @@
|
||||
"lab": {
|
||||
"initializing": "Initializing workspace",
|
||||
"loadingIdeas": "Loading your ideas..."
|
||||
},
|
||||
"richTextEditor": {
|
||||
"slashHint": "↑↓ navigate · Enter insert · Tab switch section",
|
||||
"slashLoading": "AI thinking...",
|
||||
"slashTabAll": "All",
|
||||
"slashCatBasic": "Basic blocks",
|
||||
"slashCatMedia": "Media",
|
||||
"slashCatFormatting": "Formatting",
|
||||
"slashCatAi": "AI Note",
|
||||
"insertImage": "Insert image",
|
||||
"imageUrlPlaceholder": "https://example.com/image.png",
|
||||
"preview": "Preview",
|
||||
"cancel": "Cancel",
|
||||
"insert": "Insert",
|
||||
"slashText": "Text",
|
||||
"slashTextDesc": "Simple paragraph",
|
||||
"slashH1": "Heading 1",
|
||||
"slashH1Desc": "Large section heading",
|
||||
"slashH2": "Heading 2",
|
||||
"slashH2Desc": "Medium section heading",
|
||||
"slashH3": "Heading 3",
|
||||
"slashH3Desc": "Small section heading",
|
||||
"slashBullet": "Bullet List",
|
||||
"slashBulletDesc": "Unordered list",
|
||||
"slashNumbered": "Numbered List",
|
||||
"slashNumberedDesc": "Ordered numbered list",
|
||||
"slashTodo": "Task List",
|
||||
"slashTodoDesc": "Checkbox tasks",
|
||||
"slashQuote": "Quote",
|
||||
"slashQuoteDesc": "Capture a quote",
|
||||
"slashCode": "Code Block",
|
||||
"slashCodeDesc": "Code snippet",
|
||||
"slashDivider": "Divider",
|
||||
"slashDividerDesc": "Horizontal separator",
|
||||
"slashImage": "Image",
|
||||
"slashImageDesc": "Embed an image from URL",
|
||||
"slashAlignLeft": "Align Left",
|
||||
"slashAlignLeftDesc": "Align text to the left",
|
||||
"slashAlignCenter": "Center",
|
||||
"slashAlignCenterDesc": "Center the text",
|
||||
"slashAlignRight": "Align Right",
|
||||
"slashAlignRightDesc": "Align text to the right",
|
||||
"slashSuperscript": "Superscript",
|
||||
"slashSuperscriptDesc": "Text above the baseline",
|
||||
"slashSubscript": "Subscript",
|
||||
"slashSubscriptDesc": "Text below the baseline",
|
||||
"slashClarify": "Clarify",
|
||||
"slashClarifyDesc": "Make the text clearer",
|
||||
"slashShorten": "Shorten",
|
||||
"slashShortenDesc": "Condense the text",
|
||||
"slashImprove": "Improve",
|
||||
"slashImproveDesc": "Enhance the style",
|
||||
"slashExpand": "Expand",
|
||||
"slashExpandDesc": "Elaborate and enrich the text",
|
||||
"imageModalTitle": "Insert image",
|
||||
"imageModalPreview": "Preview",
|
||||
"imageModalCancel": "Cancel",
|
||||
"imageModalInsert": "Insert",
|
||||
"imageModalInvalidUrl": "Please enter a valid URL",
|
||||
"imageModalLoadFailed": "Failed to load image",
|
||||
"linkPlaceholder": "Paste or type a link...",
|
||||
"bold": "Bold",
|
||||
"italic": "Italic",
|
||||
"underline": "Underline",
|
||||
"strike": "Strikethrough",
|
||||
"code": "Code",
|
||||
"highlight": "Highlight",
|
||||
"superscript": "Superscript",
|
||||
"subscript": "Subscript",
|
||||
"addBlock": "Add block",
|
||||
"placeholder": "Type \u0027/\u0027 for commands..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"noLabelsInNotebook": "هنوز برچسبی در این دفترچه وجود ندارد",
|
||||
"archive": "بایگانی",
|
||||
"trash": "زبالهدان",
|
||||
"clearFilter": "Remove filter"
|
||||
"clearFilter": "حذف فیلتر"
|
||||
},
|
||||
"notes": {
|
||||
"title": "یادداشتها",
|
||||
@@ -88,8 +88,8 @@
|
||||
"itemOrMediaRequired": "لطفا حداقل یک آیتم یا رسانه اضافه کنید",
|
||||
"noteCreated": "یادداشت با موفقیت ایجاد شد",
|
||||
"noteCreateFailed": "ایجاد یادداشت شکست خورد",
|
||||
"deleted": "Note deleted",
|
||||
"deleteFailed": "Failed to delete note",
|
||||
"deleted": "یادداشت حذف شد",
|
||||
"deleteFailed": "حذف یادداشت ناموفق بود",
|
||||
"aiAssistant": "دستیار هوش مصنوعی",
|
||||
"changeSize": "تغییر اندازه",
|
||||
"backgroundOptions": "گزینههای پسزمینه",
|
||||
@@ -176,10 +176,10 @@
|
||||
"history": "تاریخچه",
|
||||
"historyRestored": "نسخه بازیابی شد",
|
||||
"historyEnabled": "تاریخچه فعال شد",
|
||||
"historyDisabledTitle": "Version history",
|
||||
"historyDisabledTitle": "تاریخچه نسخهها",
|
||||
"historyDisabledDesc": "تاریخچه برای حساب شما غیرفعال است.",
|
||||
"historyEnabledTitle": "History enabled!",
|
||||
"historyEnabledDesc": "Versions of this note will now be recorded.",
|
||||
"historyEnabledTitle": "تاریخچه فعال شد!",
|
||||
"historyEnabledDesc": "نسخههای این یادداشت از این پس ثبت خواهد شد.",
|
||||
"enableHistory": "فعالسازی تاریخچه",
|
||||
"historyEmpty": "نسخهای موجود نیست",
|
||||
"historySelectVersion": "نسخهای را برای پیشنمایش انتخاب کنید",
|
||||
@@ -188,32 +188,37 @@
|
||||
"sortDateAsc": "تاریخ (قدیمیترین)",
|
||||
"sortTitleAsc": "عنوان الف ← ی",
|
||||
"sortTitleDesc": "عنوان ی ← الف",
|
||||
"suggestTitle": "AI title",
|
||||
"generateTitleFromImage": "Generate title from image",
|
||||
"titleGenerated": "Title generated",
|
||||
"content": "Content",
|
||||
"restore": "Restore",
|
||||
"createFailed": "Failed to create note",
|
||||
"updateFailed": "Failed to update note",
|
||||
"archived": "Note archived",
|
||||
"archiveFailed": "Failed to archive",
|
||||
"sort": "Sort",
|
||||
"confirmDeleteTitle": "Delete note",
|
||||
"leftShare": "Share removed",
|
||||
"dismissed": "Note dismissed from recent",
|
||||
"generalNotes": "General Notes",
|
||||
"suggestTitle": "عنوان هوش مصنوعی",
|
||||
"generateTitleFromImage": "تولید عنوان از تصویر",
|
||||
"titleGenerated": "عنوان تولید شد",
|
||||
"content": "محتوا",
|
||||
"restore": "بازیابی",
|
||||
"createFailed": "ایجاد یادداشت ناموفق بود",
|
||||
"updateFailed": "بهروزرسانی یادداشت ناموفق بود",
|
||||
"archived": "یادداشت بایگانی شد",
|
||||
"archiveFailed": "بایگانی ناموفق بود",
|
||||
"sort": "مرتبسازی",
|
||||
"confirmDeleteTitle": "حذف یادداشت",
|
||||
"leftShare": "اشتراکگذاری حذف شد",
|
||||
"dismissed": "یادداشت از اخیرها حذف شد",
|
||||
"generalNotes": "یادداشتهای عمومی",
|
||||
"noteType": "نوع یادداشت",
|
||||
"typeText": "متن",
|
||||
"typeMarkdown": "مارکداون",
|
||||
"typeRichText": "متن غنی",
|
||||
"typeChecklist": "چکلیست",
|
||||
"convertedToRichText": "Converted to rich text",
|
||||
"conversionFailed": "Conversion failed, staying in Markdown",
|
||||
"convertedToRichText": "به متن غنی تبدیل شد",
|
||||
"conversionFailed": "تبدیل ناموفق بود، در مارکداون باقی میماند",
|
||||
"richTextPlaceholder": "یادداشتی بنویسید...",
|
||||
"switchTypeTitle": "نوع یادداشت تغییر کند؟",
|
||||
"switchTypeWarning": "هنگام تغییر به {type} ممکن است برخی قالببندیها از بین بروند.",
|
||||
"switchTypeContentPreserved": "محتوای شما به عنوان متن ساده حفظ میشود.",
|
||||
"switchType": "تغییر به {type}"
|
||||
"switchType": "تغییر به {type}",
|
||||
"deleteVersionDesc": "این عمل قابل بازگشت نیست. این نسخه برای همیشه از تاریخچه حذف خواهد شد.",
|
||||
"currentVersion": "فعلی",
|
||||
"compareVersions": "مقایسه",
|
||||
"diffTitle": "مقایسه",
|
||||
"diffSelectHint": "برای مقایسه روی ۲ نسخه در لیست کلیک کنید"
|
||||
},
|
||||
"pagination": {
|
||||
"previous": "←",
|
||||
@@ -221,35 +226,35 @@
|
||||
"next": "→"
|
||||
},
|
||||
"labels": {
|
||||
"title": "Labels",
|
||||
"filter": "Filter by Label",
|
||||
"manage": "Manage Labels",
|
||||
"manageTooltip": "Manage Labels",
|
||||
"title": "برچسبها",
|
||||
"filter": "فیلتر بر اساس برچسب",
|
||||
"manage": "مدیریت برچسبها",
|
||||
"manageTooltip": "مدیریت برچسبها",
|
||||
"changeColor": "تغییر رنگ",
|
||||
"changeColorTooltip": "تغییر رنگ",
|
||||
"delete": "Delete",
|
||||
"deleteTooltip": "Delete label",
|
||||
"delete": "حذف",
|
||||
"deleteTooltip": "حذف برچسب",
|
||||
"confirmDelete": "آیا مطمئن هستید که میخواهید این برچسب را حذف کنید؟",
|
||||
"newLabelPlaceholder": "Create new label",
|
||||
"namePlaceholder": "Enter label name",
|
||||
"newLabelPlaceholder": "ایجاد برچسب جدید",
|
||||
"namePlaceholder": "نام برچسب را وارد کنید",
|
||||
"addLabel": "افزودن برچسب",
|
||||
"createLabel": "Create label",
|
||||
"labelName": "Label name",
|
||||
"labelColor": "Label color",
|
||||
"manageLabels": "Manage labels",
|
||||
"manageLabelsDescription": "Add or remove labels for this note. Click on a label to change its color.",
|
||||
"selectedLabels": "Selected Labels",
|
||||
"createLabel": "ایجاد برچسب",
|
||||
"labelName": "نام برچسب",
|
||||
"labelColor": "رنگ برچسب",
|
||||
"manageLabels": "مدیریت برچسبها",
|
||||
"manageLabelsDescription": "برچسبهای این یادداشت را اضافه یا حذف کنید. برای تغییر رنگ روی برچسب کلیک کنید.",
|
||||
"selectedLabels": "برچسبهای انتخاب شده",
|
||||
"allLabels": "همه برچسبها",
|
||||
"clearAll": "پاک کردن همه",
|
||||
"filterByLabel": "Filter by label",
|
||||
"tagAdded": "Tag \"{tag}\" added",
|
||||
"showLess": "Show less",
|
||||
"showMore": "Show more",
|
||||
"editLabels": "Edit Labels",
|
||||
"editLabelsDescription": "Create, edit colors, or delete labels.",
|
||||
"noLabelsFound": "No labels found.",
|
||||
"loading": "Loading...",
|
||||
"notebookRequired": "⚠️ Labels are only available in notebooks. Move this note to a notebook first.",
|
||||
"filterByLabel": "فیلتر بر اساس برچسب",
|
||||
"tagAdded": "برچسب «{tag}» اضافه شد",
|
||||
"showLess": "نمایش کمتر",
|
||||
"showMore": "نمایش بیشتر",
|
||||
"editLabels": "ویرایش برچسبها",
|
||||
"editLabelsDescription": "ایجاد، تغییر رنگ یا حذف برچسبها.",
|
||||
"noLabelsFound": "برچسبی یافت نشد.",
|
||||
"loading": "در حال بارگذاری...",
|
||||
"notebookRequired": "⚠️ برچسبها فقط در دفترچهها موجود هستند. ابتدا این یادداشت را به یک دفترچه منتقل کنید.",
|
||||
"count": "{count} برچسب",
|
||||
"noLabels": "بدون برچسب",
|
||||
"confirmDeleteShort": "تأیید؟",
|
||||
@@ -417,14 +422,14 @@
|
||||
"transformationsDesc": "تبدیلها — مستقیماً در یادداشت اعمال میشوند",
|
||||
"writeMinWordsAction": "حداقل ۵ کلمه بنویسید تا عملیات هوش مصنوعی فعال شود.",
|
||||
"processingAction": "در حال پردازش...",
|
||||
"noImagesError": "No images in this note",
|
||||
"overview": "Overview",
|
||||
"noImagesError": "تصویری در این یادداشت وجود ندارد",
|
||||
"overview": "نمای کلی",
|
||||
"action": {
|
||||
"clarify": "روشن کردن",
|
||||
"shorten": "خلاصه کردن",
|
||||
"improve": "بهبود",
|
||||
"toMarkdown": "به مارکداون",
|
||||
"describeImages": "Describe images"
|
||||
"describeImages": "توصیف تصاویر"
|
||||
},
|
||||
"openAssistant": "باز کردن دستیار هوش مصنوعی",
|
||||
"poweredByMomento": "پشتیبانی شده توسط Momento AI",
|
||||
@@ -440,8 +445,50 @@
|
||||
"insightsTab": "بینشها",
|
||||
"aiCopilot": "دستیار هوشمند",
|
||||
"suggestTitle": "پیشنهاد عنوان با هوش مصنوعی",
|
||||
"generateTitleFromImage": "Generate title from image",
|
||||
"titleGenerated": "Title generated from image"
|
||||
"generateTitleFromImage": "تولید عنوان از تصویر",
|
||||
"titleGenerated": "عنوان از تصویر تولید شد",
|
||||
"wordCountMin": "حداقل {min} کلمه برای بازنویسی انتخاب کنید (فعلاً {current} کلمه)",
|
||||
"wordCountMax": "حداکثر {max} کلمه برای بازنویسی انتخاب کنید (فعلاً {current} کلمه)",
|
||||
"resourceTab": "منبع",
|
||||
"aiNoteTitle": "یادداشت هوش مصنوعی",
|
||||
"injectReplace": "جایگزینی",
|
||||
"injectReplaceTitle": "محتوای یادداشت را با این پیام جایگزین کنید",
|
||||
"injectComplete": "تکمیل",
|
||||
"injectCompleteTitle": "یادداشت را با این پیام تکمیل کنید (هوش مصنوعی)",
|
||||
"injectMerge": "ادغام",
|
||||
"injectMergeTitle": "با یادداشت ادغام کنید (هوش مصنوعی)",
|
||||
"imagesCount": "{count} تصویر",
|
||||
"resource": {
|
||||
"failedToLoadUrl": "بارگذاری این URL ناموفق بود",
|
||||
"pageLoaded": "صفحه بارگذاری شد: {title}",
|
||||
"pageLoadError": "خطا در بارگذاری صفحه",
|
||||
"pasteOrUrlFirst": "ابتدا متن را بچسبانید یا یک URL بارگذاری کنید",
|
||||
"enrichError": "خطا در غنیسازی",
|
||||
"enrichErrorShort": "خطای غنیسازی",
|
||||
"contentApplied": "محتوا در یادداشت اعمال شد ✓",
|
||||
"fromChat": "💬 از چت",
|
||||
"replacement": "↓ جایگزینی",
|
||||
"completedByAI": "✦ تکمیل شده توسط هوش مصنوعی",
|
||||
"mergedByAI": "⟳ ادغام شده توسط هوش مصنوعی",
|
||||
"rendered": "رندر شده",
|
||||
"cancel": "لغو",
|
||||
"applyToNote": "اعمال در یادداشت",
|
||||
"urlLabel": "URL (اختیاری)",
|
||||
"resourceText": "متن منبع",
|
||||
"resourcePlaceholder": "متن خود را اینجا بچسبانید (مارکداون، HTML، متن ساده…)",
|
||||
"words": "کلمه",
|
||||
"integrationMode": "حالت یکپارچهسازی",
|
||||
"modeReplace": "جایگزینی",
|
||||
"modeReplaceDesc": "مستقیم، بدون هوش مصنوعی",
|
||||
"modeComplete": "تکمیل",
|
||||
"modeCompleteDesc": "بدون بازنویسی اضافه میکند",
|
||||
"modeMerge": "ادغام",
|
||||
"modeMergeDesc": "بازنویسی و یکپارچه میکند",
|
||||
"aiProcessing": "هوش مصنوعی در حال پردازش…",
|
||||
"preview": "پیشنمایش",
|
||||
"generatePreview": "تولید پیشنمایش",
|
||||
"emptyNoteHint": "💡 یادداشت خالی است — محتوای منبع مستقیماً یکپارچه خواهد شد."
|
||||
}
|
||||
},
|
||||
"titleSuggestions": {
|
||||
"available": "پیشنهادات عنوان",
|
||||
@@ -539,10 +586,10 @@
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"accept": "Accept",
|
||||
"accepted": "Share accepted",
|
||||
"decline": "Decline",
|
||||
"noNotifications": "No new notifications",
|
||||
"accept": "پذیرش",
|
||||
"accepted": "اشتراک پذیرفته شد",
|
||||
"decline": "رد کردن",
|
||||
"noNotifications": "اعلان جدیدی نیست",
|
||||
"shared": "\"{title}\" به اشتراک گذاشته شد",
|
||||
"untitled": "بدون عنوان",
|
||||
"notifications": "اعلانها",
|
||||
@@ -603,11 +650,11 @@
|
||||
"about": "درباره",
|
||||
"version": "نسخه",
|
||||
"settingsSaved": "تنظیمات ذخیره شد",
|
||||
"cardSizeMode": "Note Size",
|
||||
"cardSizeModeDescription": "Choose between variable sizes or uniform size",
|
||||
"selectCardSizeMode": "Select display mode",
|
||||
"cardSizeVariable": "Variable sizes (small/medium/large)",
|
||||
"cardSizeUniform": "Uniform size",
|
||||
"cardSizeMode": "اندازه یادداشت",
|
||||
"cardSizeModeDescription": "انتخاب بین اندازه متغیر یا یکنواخت",
|
||||
"selectCardSizeMode": "انتخاب حالت نمایش",
|
||||
"cardSizeVariable": "اندازه متغیر (کوچک/متوسط/بزرگ)",
|
||||
"cardSizeUniform": "اندازه یکنواخت",
|
||||
"settingsError": "خطا در ذخیره تنظیمات",
|
||||
"maintenance": "نگهداری",
|
||||
"maintenanceDescription": "ابزارهایی برای حفظ سلامت پایگاه داده",
|
||||
@@ -695,7 +742,15 @@
|
||||
"providerDesc": "فروشنده هوش مصنوعی مورد نظر خود را انتخاب کنید",
|
||||
"providerAutoDesc": "Ollama در صورت وجود، در غیر این صورت OpenAI",
|
||||
"providerOllamaDesc": "۱۰۰٪ خصوصی، به صورت محلی اجرا میشود",
|
||||
"providerOpenAIDesc": "دقیقترین، نیاز به کلید API دارد"
|
||||
"providerOpenAIDesc": "دقیقترین، نیاز به کلید API دارد",
|
||||
"aiNote": "یادداشت هوش مصنوعی",
|
||||
"aiNoteDesc": "فعالسازی دکمه چت هوش مصنوعی و ابزارهای بهبود متن",
|
||||
"languageDetection": "تشخیص زبان",
|
||||
"languageDetectionDesc": "به طور خودکار زبان یادداشتهای شما را تشخیص میدهد",
|
||||
"autoLabeling": "پیشنهاد برچسب",
|
||||
"autoLabelingDesc": "به طور خودکار برچسبها را به یادداشتهای شما پیشنهاد و اعمال میکند",
|
||||
"noteHistory": "تاریخچه یادداشت",
|
||||
"noteHistoryDesc": "فعالسازی اسنپشات نسخهها و بازیابی از تاریخچه"
|
||||
},
|
||||
"general": {
|
||||
"loading": "در حال بارگذاری...",
|
||||
@@ -752,7 +807,9 @@
|
||||
"markDone": "علامتگذاری به عنوان انجام شده",
|
||||
"markUndone": "علامتگذاری به عنوان انجام نشده",
|
||||
"todayAt": "امروز ساعت {time}",
|
||||
"tomorrowAt": "فردا ساعت {time}"
|
||||
"tomorrowAt": "فردا ساعت {time}",
|
||||
"clearCompleted": "پاک کردن تکمیل شدهها",
|
||||
"viewAll": "مشاهده همه یادآوریها"
|
||||
},
|
||||
"notebook": {
|
||||
"create": "ایجاد دفترچه",
|
||||
@@ -783,7 +840,7 @@
|
||||
"confidence": "اطمینان",
|
||||
"savingReminder": "شکست در ذخیره یادآوری",
|
||||
"removingReminder": "شکست در حذف یادآوری",
|
||||
"generatingDescription": "Please wait..."
|
||||
"generatingDescription": "لطفاً صبر کنید..."
|
||||
},
|
||||
"notebookSuggestion": {
|
||||
"title": "انتقال به {name}؟",
|
||||
@@ -896,14 +953,14 @@
|
||||
"description": "پیکربندی ارسال ایمیل برای اعلانهای عامل و بازنشانی رمز عبور.",
|
||||
"provider": "ارائهدهنده ایمیل",
|
||||
"saveSettings": "ذخیره تنظیمات ایمیل",
|
||||
"status": "Service Status",
|
||||
"keySet": "key configured",
|
||||
"activeAuto": "Auto mode: Resend will be used first, SMTP as fallback.",
|
||||
"activeSmtp": "Auto mode: SMTP will be used (Resend not configured).",
|
||||
"noneConfigured": "No email service configured. Set up Resend or SMTP.",
|
||||
"activeProvider": "Active provider",
|
||||
"testOk": "test passed",
|
||||
"testFail": "test failed"
|
||||
"status": "وضعیت سرویس",
|
||||
"keySet": "کلید پیکربندی شده",
|
||||
"activeAuto": "حالت خودکار: ابتدا Resend استفاده میشود، SMTP به عنوان جایگزین.",
|
||||
"activeSmtp": "حالت خودکار: SMTP استفاده میشود (Resend پیکربندی نشده).",
|
||||
"noneConfigured": "هیچ سرویس ایمیلی پیکربندی نشده. Resend یا SMTP را تنظیم کنید.",
|
||||
"activeProvider": "ارائهدهنده فعال",
|
||||
"testOk": "تست موفق",
|
||||
"testFail": "تست ناموفق"
|
||||
},
|
||||
"smtp": {
|
||||
"title": "پیکربندی SMTP",
|
||||
@@ -1182,7 +1239,7 @@
|
||||
"notesViewLabel": "چیدمان یادداشتها",
|
||||
"notesViewTabs": "زبانهها (سبک OneNote)",
|
||||
"notesViewMasonry": "کارتها (شبکهای)",
|
||||
"selectTheme": "Select theme",
|
||||
"selectTheme": "انتخاب تم",
|
||||
"fontFamilyLabel": "خانواده فونت",
|
||||
"fontFamilyDescription": "فونت استفاده شده در سراسر برنامه را انتخاب کنید",
|
||||
"selectFontFamily": "Inter برای خوانایی بهینه شده است، سیستم از فونت بومی سیستمعامل شما استفاده میکند",
|
||||
@@ -1376,10 +1433,10 @@
|
||||
"subtitle": "خودکارسازی وظایف پایش و تحقیق شما",
|
||||
"newAgent": "عامل جدید",
|
||||
"myAgents": "عاملهای من",
|
||||
"searchPlaceholder": "Search agents...",
|
||||
"filterAll": "All",
|
||||
"newBadge": "New",
|
||||
"noResults": "No agents match your search.",
|
||||
"searchPlaceholder": "جستجوی عاملها...",
|
||||
"filterAll": "همه",
|
||||
"newBadge": "جدید",
|
||||
"noResults": "هیچ عاملی با جستجوی شما مطابقت ندارد.",
|
||||
"noAgents": "بدون عامل",
|
||||
"noAgentsDescription": "اولین عامل خود را بسازید یا قالب زیر را نصب کنید تا وظایف پایش خود را خودکار کنید.",
|
||||
"types": {
|
||||
@@ -1423,8 +1480,8 @@
|
||||
"researchTopicPlaceholder": "مثال: آخرین پیشرفتها در هوش مصنوعی",
|
||||
"notifyEmail": "اعلان ایمیل",
|
||||
"notifyEmailHint": "پس از هر اجرا، ایمیل حاوی نتایج عامل دریافت کنید",
|
||||
"includeImages": "Include images",
|
||||
"includeImagesHint": "Extract images from scraped pages and attach them to the generated note"
|
||||
"includeImages": "شامل تصاویر",
|
||||
"includeImagesHint": "استخراج تصاویر از صفحات استخراج شده و پیوست به یادداشت تولید شده"
|
||||
},
|
||||
"frequencies": {
|
||||
"manual": "دستی",
|
||||
@@ -1434,19 +1491,19 @@
|
||||
"monthly": "ماهانه"
|
||||
},
|
||||
"schedule": {
|
||||
"nextRun": "Next run",
|
||||
"pending": "Pending trigger",
|
||||
"time": "Time",
|
||||
"dayOfWeek": "Day of week",
|
||||
"dayOfMonth": "Day of month",
|
||||
"nextRun": "اجرای بعدی",
|
||||
"pending": "در انتظار راهاندازی",
|
||||
"time": "زمان",
|
||||
"dayOfWeek": "روز هفته",
|
||||
"dayOfMonth": "روز ماه",
|
||||
"days": {
|
||||
"mon": "Monday",
|
||||
"tue": "Tuesday",
|
||||
"wed": "Wednesday",
|
||||
"thu": "Thursday",
|
||||
"fri": "Friday",
|
||||
"sat": "Saturday",
|
||||
"sun": "Sunday"
|
||||
"mon": "دوشنبه",
|
||||
"tue": "سهشنبه",
|
||||
"wed": "چهارشنبه",
|
||||
"thu": "پنجشنبه",
|
||||
"fri": "جمعه",
|
||||
"sat": "شنبه",
|
||||
"sun": "یکشنبه"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
@@ -1478,8 +1535,8 @@
|
||||
"installSuccess": "\"{name}\" نصب شد",
|
||||
"installError": "خطا در حین نصب",
|
||||
"saveError": "خطا در ذخیره",
|
||||
"autoRunSuccess": "Agent \"{name}\" executed automatically with success",
|
||||
"autoRunError": "Agent \"{name}\" failed during automatic execution"
|
||||
"autoRunSuccess": "عامل «{name}» با موفقیت به صورت خودکار اجرا شد",
|
||||
"autoRunError": "عامل «{name}» در اجرای خودکار شکست خورد"
|
||||
},
|
||||
"templates": {
|
||||
"title": "قالبها",
|
||||
@@ -1593,7 +1650,7 @@
|
||||
"searching": "در حال جستجو...",
|
||||
"noNotesFoundForContext": "هیچ یادداشت مرتبطی برای این سوال یافت نشد. با دانش عمومی خود پاسخ دهید.",
|
||||
"webSearch": "جستجوی وب",
|
||||
"timeoutWarning": "Response is taking longer than expected..."
|
||||
"timeoutWarning": "پاسخ بیشتر از حد انتظار طول میکشد..."
|
||||
},
|
||||
"labHeader": {
|
||||
"title": "آزمایشگاه",
|
||||
@@ -1611,10 +1668,81 @@
|
||||
"deleteSpace": "حذف فضا",
|
||||
"deleted": "فضا حذف شد",
|
||||
"deleteError": "خطا در حذف",
|
||||
"rename": "Rename"
|
||||
"rename": "تغییر نام"
|
||||
},
|
||||
"lab": {
|
||||
"initializing": "راهاندازی فضای کاری",
|
||||
"loadingIdeas": "بارگذاری ایدههای شما..."
|
||||
},
|
||||
"richTextEditor": {
|
||||
"slashHint": "↑↓ ناوبری · Enter درج · Tab تغییر بخش",
|
||||
"slashLoading": "هوش مصنوعی در حال فکر کردن...",
|
||||
"slashTabAll": "همه",
|
||||
"slashCatBasic": "بلوکهای پایه",
|
||||
"slashCatMedia": "رسانه",
|
||||
"slashCatFormatting": "قالببندی",
|
||||
"slashCatAi": "هوش مصنوعی",
|
||||
"insertImage": "درج تصویر",
|
||||
"imageUrlPlaceholder": "https://example.com/image.png",
|
||||
"preview": "پیشنمایش",
|
||||
"cancel": "لغو",
|
||||
"insert": "درج",
|
||||
"slashText": "متن",
|
||||
"slashTextDesc": "پاراگراف ساده",
|
||||
"slashH1": "عنوان ۱",
|
||||
"slashH1Desc": "عنوان بخش بزرگ",
|
||||
"slashH2": "عنوان ۲",
|
||||
"slashH2Desc": "عنوان بخش متوسط",
|
||||
"slashH3": "عنوان ۳",
|
||||
"slashH3Desc": "عنوان بخش کوچک",
|
||||
"slashBullet": "فهرست نقطهای",
|
||||
"slashBulletDesc": "فهرست بدون ترتیب",
|
||||
"slashNumbered": "فهرست شمارهدار",
|
||||
"slashNumberedDesc": "فهرست مرتب شمارهدار",
|
||||
"slashTodo": "فهرست وظایف",
|
||||
"slashTodoDesc": "وظایف با چکباکس",
|
||||
"slashQuote": "نقل قول",
|
||||
"slashQuoteDesc": "ثبت یک نقل قول",
|
||||
"slashCode": "بلوک کد",
|
||||
"slashCodeDesc": "قطعه کد",
|
||||
"slashDivider": "جداکننده",
|
||||
"slashDividerDesc": "جداکننده افقی",
|
||||
"slashImage": "تصویر",
|
||||
"slashImageDesc": "درج تصویر از URL",
|
||||
"slashAlignLeft": "تراز چپ",
|
||||
"slashAlignLeftDesc": "تراز متن به چپ",
|
||||
"slashAlignCenter": "مرکز",
|
||||
"slashAlignCenterDesc": "مرکز کردن متن",
|
||||
"slashAlignRight": "تراز راست",
|
||||
"slashAlignRightDesc": "تراز متن به راست",
|
||||
"slashSuperscript": "بالانویس",
|
||||
"slashSuperscriptDesc": "متن بالاتر از خط اصلی",
|
||||
"slashSubscript": "زیرنویس",
|
||||
"slashSubscriptDesc": "متن پایینتر از خط اصلی",
|
||||
"slashClarify": "شفافسازی",
|
||||
"slashClarifyDesc": "روشنتر کردن متن",
|
||||
"slashShorten": "خلاصه کردن",
|
||||
"slashShortenDesc": "فشرده کردن متن",
|
||||
"slashImprove": "بهبود",
|
||||
"slashImproveDesc": "بهبود سبک نوشتار",
|
||||
"slashExpand": "بسط دادن",
|
||||
"slashExpandDesc": "توسعه و غنیسازی متن",
|
||||
"imageModalTitle": "درج تصویر",
|
||||
"imageModalPreview": "پیشنمایش",
|
||||
"imageModalCancel": "لغو",
|
||||
"imageModalInsert": "درج",
|
||||
"imageModalInvalidUrl": "لطفاً یک URL معتبر وارد کنید",
|
||||
"imageModalLoadFailed": "بارگذاری تصویر ناموفق بود",
|
||||
"linkPlaceholder": "یک لینک بچسبانید یا تایپ کنید...",
|
||||
"bold": "درشت",
|
||||
"italic": "کج",
|
||||
"underline": "زیرخط",
|
||||
"strike": "خطخورده",
|
||||
"code": "کد",
|
||||
"highlight": "برجسته",
|
||||
"superscript": "بالانویس",
|
||||
"subscript": "زیرنویس",
|
||||
"addBlock": "افزودن بلوک",
|
||||
"placeholder": "برای دستورات '/' تایپ کنید..."
|
||||
}
|
||||
}
|
||||
|
||||