Files
Momento/memento-note/components/editor-connections-section.tsx
sepehr 153c921960
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 1m7s
fix: comprehensive i18n — replace hardcoded French/English strings with t() calls
Replaced ~100+ hardcoded French and English text strings across 30+ components
with proper i18n t() calls. Added 57 new translation keys to all 15 locale files
(ar, de, en, es, fa, fr, hi, it, ja, ko, nl, pl, pt, ru, zh).

Key changes:
- contextual-ai-chat.tsx: 30 French strings → t() (actions, toasts, labels, placeholders)
- ai-chat.tsx: 15 French/English strings → t() (header, tabs, welcome, insights, history)
- note-inline-editor.tsx: 20 French fallbacks removed (toolbar, save status, checklist)
- lab-skeleton.tsx: French loading text → t()
- admin-header.tsx, header.tsx, editor-connections-section.tsx: French fallbacks removed
- New AI chat component, agent cards, sidebar, settings panel i18n cleanup

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 21:14:45 +02:00

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') }
>
<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>
)
}