## 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>
256 lines
8.2 KiB
TypeScript
256 lines
8.2 KiB
TypeScript
'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>
|
|
)
|
|
}
|