feat: Complete internationalization and code cleanup
## Translation Files - Add 11 new language files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ missing translation keys across all 15 languages - New sections: notebook, pagination, ai.batchOrganization, ai.autoLabels - Update nav section with workspace, quickAccess, myLibrary keys ## Component Updates - Update 15+ components to use translation keys instead of hardcoded text - Components: notebook dialogs, sidebar, header, note-input, ghost-tags, etc. - Replace 80+ hardcoded English/French strings with t() calls - Ensure consistent UI across all supported languages ## Code Quality - Remove 77+ console.log statements from codebase - Clean up API routes, components, hooks, and services - Keep only essential error handling (no debugging logs) ## UI/UX Improvements - Update Keep logo to yellow post-it style (from-yellow-400 to-amber-500) - Change selection colors to #FEF3C6 (notebooks) and #EFB162 (nav items) - Make "+" button permanently visible in notebooks section - Fix grammar and syntax errors in multiple components ## Bug Fixes - Fix JSON syntax errors in it.json, nl.json, pl.json, zh.json - Fix syntax errors in notebook-suggestion-toast.tsx - Fix syntax errors in use-auto-tagging.ts - Fix syntax errors in paragraph-refactor.service.ts - Fix duplicate "fusion" section in nl.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Ou une version plus courte si vous préférez : feat(i18n): Add 15 languages, remove logs, update UI components - Create 11 new translation files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ translation keys: notebook, pagination, AI features - Update 15+ components to use translations (80+ strings) - Remove 77+ console.log statements from codebase - Fix JSON syntax errors in 4 translation files - Fix component syntax errors (toast, hooks, services) - Update logo to yellow post-it style - Change selection colors (#FEF3C6, #EFB162) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
255
keep-notes/components/editor-connections-section.tsx
Normal file
255
keep-notes/components/editor-connections-section.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { ChevronDown, ChevronUp, Sparkles, Eye, ArrowRight, Link2, X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useLanguage } from '@/lib/i18n/LanguageProvider'
|
||||
|
||||
interface ConnectionData {
|
||||
noteId: string
|
||||
title: string | null
|
||||
content: string
|
||||
createdAt: Date
|
||||
similarity: number
|
||||
daysApart: number
|
||||
}
|
||||
|
||||
interface ConnectionsResponse {
|
||||
connections: ConnectionData[]
|
||||
pagination: {
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
totalPages: number
|
||||
hasNext: boolean
|
||||
hasPrev: boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface EditorConnectionsSectionProps {
|
||||
noteId: string
|
||||
onOpenNote?: (noteId: string) => void
|
||||
onCompareNotes?: (noteIds: string[]) => void
|
||||
onMergeNotes?: (noteIds: string[]) => void
|
||||
}
|
||||
|
||||
export function EditorConnectionsSection({
|
||||
noteId,
|
||||
onOpenNote,
|
||||
onCompareNotes,
|
||||
onMergeNotes
|
||||
}: EditorConnectionsSectionProps) {
|
||||
const { t } = useLanguage()
|
||||
const [connections, setConnections] = useState<ConnectionData[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isExpanded, setIsExpanded] = useState(true)
|
||||
const [isVisible, setIsVisible] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchConnections = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const res = await fetch(`/api/ai/echo/connections?noteId=${noteId}&limit=10`)
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to fetch connections')
|
||||
}
|
||||
|
||||
const data: ConnectionsResponse = await res.json()
|
||||
setConnections(data.connections)
|
||||
|
||||
// Show section if there are connections
|
||||
if (data.connections.length > 0) {
|
||||
setIsVisible(true)
|
||||
} else {
|
||||
setIsVisible(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[EditorConnectionsSection] Failed to fetch:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchConnections()
|
||||
}, [noteId])
|
||||
|
||||
// Don't render if no connections or if dismissed
|
||||
if (!isVisible || (connections.length === 0 && !isLoading)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-6 border-t dark:border-zinc-700 pt-4">
|
||||
{/* Header with toggle */}
|
||||
<div
|
||||
className="flex items-center justify-between cursor-pointer select-none group"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1.5 bg-amber-100 dark:bg-amber-900/30 rounded-full">
|
||||
<Sparkles className="h-4 w-4 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||
{t('memoryEcho.editorSection.title', { count: connections.length })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
// Dismiss all connections for this note
|
||||
try {
|
||||
await Promise.all(
|
||||
connections.map(conn =>
|
||||
fetch('/api/ai/echo/dismiss', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
noteId: noteId,
|
||||
connectedNoteId: conn.noteId
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
setIsVisible(false)
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to dismiss connections:', error)
|
||||
}
|
||||
}}
|
||||
title={t('memoryEcho.editorSection.close') || 'Fermer'}
|
||||
>
|
||||
<X className="h-4 w-4 text-gray-500" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsExpanded(!isExpanded)
|
||||
}}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="h-4 w-4 text-gray-500" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4 text-gray-500" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connections list */}
|
||||
{isExpanded && (
|
||||
<div className="mt-3 space-y-2 max-h-[300px] overflow-y-auto">
|
||||
{isLoading ? (
|
||||
<div className="text-center py-4 text-sm text-gray-500">
|
||||
{t('memoryEcho.editorSection.loading')}
|
||||
</div>
|
||||
) : (
|
||||
connections.map((conn) => {
|
||||
const similarityPercentage = Math.round(conn.similarity * 100)
|
||||
const title = conn.title || t('memoryEcho.comparison.untitled')
|
||||
|
||||
return (
|
||||
<div
|
||||
key={conn.noteId}
|
||||
className="border dark:border-zinc-700 rounded-lg p-3 hover:bg-gray-50 dark:hover:bg-zinc-800/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 flex-1">
|
||||
{title}
|
||||
</h4>
|
||||
<span className="ml-2 text-xs font-medium px-2 py-0.5 rounded-full bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400">
|
||||
{similarityPercentage}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 line-clamp-2 mb-2">
|
||||
{conn.content}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 text-xs flex-1"
|
||||
onClick={() => onOpenNote?.(conn.noteId)}
|
||||
>
|
||||
<Eye className="h-3 w-3 mr-1" />
|
||||
{t('memoryEcho.editorSection.view')}
|
||||
</Button>
|
||||
|
||||
{onCompareNotes && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 text-xs flex-1"
|
||||
onClick={() => onCompareNotes([noteId, conn.noteId])}
|
||||
>
|
||||
<ArrowRight className="h-3 w-3 mr-1" />
|
||||
{t('memoryEcho.editorSection.compare')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onMergeNotes && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 text-xs flex-1"
|
||||
onClick={() => onMergeNotes([noteId, conn.noteId])}
|
||||
>
|
||||
<Link2 className="h-3 w-3 mr-1" />
|
||||
{t('memoryEcho.editorSection.merge')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer actions */}
|
||||
{isExpanded && connections.length > 1 && (
|
||||
<div className="mt-3 flex items-center gap-2 pt-2 border-t dark:border-zinc-700">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex-1 text-xs"
|
||||
onClick={() => {
|
||||
if (onCompareNotes) {
|
||||
const allIds = connections.slice(0, Math.min(3, connections.length)).map(c => c.noteId)
|
||||
onCompareNotes([noteId, ...allIds])
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('memoryEcho.editorSection.compareAll')}
|
||||
</Button>
|
||||
|
||||
{onMergeNotes && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex-1 text-xs"
|
||||
onClick={() => {
|
||||
const allIds = connections.map(c => c.noteId)
|
||||
onMergeNotes([noteId, ...allIds])
|
||||
}}
|
||||
>
|
||||
{t('memoryEcho.editorSection.mergeAll')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user