Refactor project structure and dependencies; rename package to azaion-ui, update version to 0.0.1, and remove unused files. Introduce new routing and authentication features in App component.

This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-03-25 03:10:15 +02:00
parent e407308284
commit 157a33096a
112 changed files with 6530 additions and 17843 deletions
@@ -0,0 +1,109 @@
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { api } from '../../api/client'
import { createSSE } from '../../api/sse'
import type { Media, AnnotationListItem, PaginatedResponse } from '../../types'
interface Props {
media: Media | null
annotations: AnnotationListItem[]
selectedAnnotation: AnnotationListItem | null
onSelect: (ann: AnnotationListItem) => void
onAnnotationsUpdate: (anns: AnnotationListItem[]) => void
}
export default function AnnotationsSidebar({ media, annotations, selectedAnnotation, onSelect, onAnnotationsUpdate }: Props) {
const { t } = useTranslation()
const [detecting, setDetecting] = useState(false)
const [detectLog, setDetectLog] = useState<string[]>([])
useEffect(() => {
if (!media) return
return createSSE<{ annotationId: string; mediaId: string; status: number }>('/api/annotations/annotations/events', (event) => {
if (event.mediaId === media.id) {
api.get<PaginatedResponse<AnnotationListItem>>(
`/api/annotations/annotations?mediaId=${media.id}&pageSize=1000`
).then(res => onAnnotationsUpdate(res.items)).catch(() => {})
}
})
}, [media, onAnnotationsUpdate])
const handleDetect = async () => {
if (!media) return
setDetecting(true)
setDetectLog(['Starting AI detection...'])
try {
await api.post(`/api/detect/${media.id}`)
setDetectLog(prev => [...prev, 'Detection complete.'])
} catch (e: any) {
setDetectLog(prev => [...prev, `Error: ${e.message}`])
}
}
const getRowGradient = (ann: AnnotationListItem) => {
if (ann.detections.length === 0) return 'rgba(221,221,221,0.25)'
const stops = ann.detections.map((d, i) => {
const pct = (i / Math.max(ann.detections.length - 1, 1)) * 100
const alpha = Math.min(1, d.confidence)
return `${d.label ? getClassColor(d.classNum) : '#888'}${Math.round(alpha * 40).toString(16).padStart(2, '0')} ${pct}%`
})
return `linear-gradient(to right, ${stops.join(', ')})`
}
const classColors: Record<number, string> = {}
const getClassColor = (classNum: number) => {
if (classColors[classNum]) return classColors[classNum]
const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF', '#188021', '#800000', '#008000', '#000080']
return colors[classNum % colors.length]
}
return (
<div className="flex flex-col h-full">
<div className="p-2 border-b border-az-border flex items-center justify-between">
<span className="text-xs font-semibold text-az-muted">{t('annotations.title')}</span>
<button
onClick={handleDetect}
disabled={!media}
className="text-xs bg-az-blue text-white px-2 py-0.5 rounded disabled:opacity-50"
>
{t('annotations.detect')}
</button>
</div>
<div className="flex-1 overflow-y-auto">
{annotations.map(ann => (
<div
key={ann.id}
onClick={() => onSelect(ann)}
className={`px-2 py-1 cursor-pointer border-b border-az-border text-xs ${
selectedAnnotation?.id === ann.id ? 'ring-1 ring-az-orange ring-inset' : ''
}`}
style={{ background: getRowGradient(ann) }}
>
<div className="flex items-center justify-between">
<span className="text-az-text font-mono">{ann.time || '—'}</span>
<span className="text-az-muted">{ann.detections.length > 0 ? ann.detections[0].label : '—'}</span>
</div>
</div>
))}
{annotations.length === 0 && (
<div className="p-2 text-az-muted text-xs text-center">{t('common.noData')}</div>
)}
</div>
{detecting && (
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-[100]">
<div className="bg-az-panel border border-az-border rounded-lg p-4 w-96 max-h-80 flex flex-col">
<h3 className="text-white font-semibold mb-2">{t('annotations.detect')}</h3>
<div className="flex-1 overflow-y-auto bg-az-bg rounded p-2 text-xs text-az-text font-mono space-y-0.5 mb-2">
{detectLog.map((line, i) => <div key={i}>{line}</div>)}
</div>
<button onClick={() => setDetecting(false)} className="self-end text-xs bg-az-border text-az-text px-3 py-1 rounded">
Close
</button>
</div>
</div>
)}
</div>
)
}