'use client'; import { useState } from 'react'; import type { BlastHitSummary } from '@/types/pipeline'; interface AlignmentViewProps { hit: BlastHitSummary; } const COLORS: Record = { A: 'text-emerald-400', R: 'text-blue-400', N: 'text-purple-400', D: 'text-red-400', C: 'text-yellow-400', Q: 'text-indigo-400', E: 'text-red-400', G: 'text-text-secondary', H: 'text-blue-400', I: 'text-emerald-400', L: 'text-emerald-400', K: 'text-blue-400', M: 'text-emerald-400', F: 'text-emerald-400', P: 'text-orange-400', S: 'text-green-400', T: 'text-green-400', W: 'text-emerald-400', Y: 'text-blue-400', V: 'text-emerald-400', }; function colorChar(c: string): string { return COLORS[c.toUpperCase()] || 'text-text-secondary'; } function renderLine(label: string, seq: string, start: number, isMidline: boolean = false): JSX.Element { const chars = seq.split(''); const groups: JSX.Element[] = []; for (let i = 0; i < chars.length; i += 60) { const chunk = chars.slice(i, i + 60); groups.push(
{start + i} {chunk.map((c, j) => ( {c} ))} {start + i + chunk.length}
); } return (
{label}
{groups}
); } export function AlignmentView({ hit }: AlignmentViewProps) { const [showAll, setShowAll] = useState(false); if (!hit.query_alignment || !hit.hit_alignment) { return null; } const qSeq = hit.query_alignment; const hSeq = hit.hit_alignment; const mid = hit.midline || ''; const alignLen = hit.alignment_length || qSeq.length; const identities = mid.split('').filter(c => c === '|').length; const pctId = alignLen > 0 ? Math.round((identities / alignLen) * 100) : 0; const positives = hit.positive || 0; const gaps = hit.gaps || 0; const mismatches = Math.max(0, alignLen - identities - gaps); const totalLines = Math.ceil(qSeq.length / 60); const displayLines = showAll ? totalLines : Math.min(totalLines, 3); const truncate = (seq: string, lines: number) => seq.slice(0, lines * 60); const displayQ = showAll ? qSeq : truncate(qSeq, displayLines); const displayH = showAll ? hSeq : truncate(hSeq, displayLines); const displayM = showAll ? mid : truncate(mid, displayLines); return (
Identities: {identities}/{alignLen} ({pctId}%) Positives: {positives} Mismatches: {mismatches} {gaps > 0 && Gaps: {gaps}} Length: {alignLen} bp
{renderLine('Query', displayQ, hit.query_from || 0)} {renderLine('', displayM, 0, true)} {renderLine('Sbjct', displayH, hit.hit_from || 0)}
{!showAll && totalLines > 3 && ( )}
); }