File size: 3,785 Bytes
ace64e6
 
 
 
 
 
 
 
c83d9b9
ec313db
 
773cf91
ace64e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ec313db
c83d9b9
ace64e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c83d9b9
 
ec313db
ace64e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
'use client';

import { useMemo } from 'react';
import { Download } from '@phosphor-icons/react';
import type { PairwiseAlignResult } from '@/types/pipeline';
import PhyloTreeViewer from '@/components/phylo/PhyloTreeViewer';
import { ConservationTrack } from '@/components/alignment/ConservationTrack';
import { PairwiseResultDisplay } from '@/components/alignment/PairwiseResultDisplay';
import { AlignmentStatsBar } from '@/components/alignment/AlignmentStatsBar';
import { AlignmentBlock } from '@/components/alignment/AlignmentBlock';
import { computeAlignmentStats, parseAlignedFasta } from '@/lib/alignment-stats';

interface MSAResultPanelProps {
  alnFasta: string;
  phylotree?: string | null;
  sequenceCount?: number;
  alignmentMode?: 'global' | 'local';
  pairwise?: PairwiseAlignResult | null;
  pairwiseSubject?: string | null;
  jobId?: string;
}

export function MSAResultPanel({
  alnFasta,
  phylotree,
  sequenceCount,
  alignmentMode,
  pairwise,
  pairwiseSubject,
  jobId,
}: MSAResultPanelProps) {
  const { seqs } = useMemo(() => parseAlignedFasta(alnFasta), [alnFasta]);
  const stats = useMemo(() => computeAlignmentStats(seqs), [seqs]);

  const download = () => {
    const blob = new Blob([alnFasta], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `msa-${jobId?.slice(0, 8) ?? 'result'}.fasta`;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div className="space-y-4">
      <div className="data-card p-5">
        <div className="flex items-center justify-between mb-3">
          <div>
            <h3 className="text-sm font-semibold text-text-primary">Multiple Sequence Alignment</h3>
            <p className="text-xs text-text-muted mt-0.5">
              {sequenceCount ?? seqs.length} sequences aligned via Clustal Omega
              {alignmentMode === 'local' && (
                <span className="ml-2 px-2 py-0.5 rounded-full bg-accent-amber/10 border border-accent-amber/30 text-accent-amber font-medium">
                  local refinement
                </span>
              )}
            </p>
          </div>
          <button
            onClick={download}
            className="px-3 py-1.5 rounded-lg border border-glass-border text-xs text-text-secondary hover:bg-surface-1 hover:border-accent-cyan/40 transition"
          >
            <Download className="w-3.5 h-3.5 inline mr-1" />FASTA
          </button>
        </div>

        <AlignmentStatsBar stats={stats} className="mb-3" />

        <AlignmentBlock alnFasta={alnFasta} />
      </div>

      {seqs.length >= 2 && (
        <div className="data-card p-5">
          <ConservationTrack alignedSeqs={seqs} />
        </div>
      )}

      {pairwise && (
        <div className="data-card p-5">
          <div className="flex items-center justify-between mb-3">
            <h3 className="text-sm font-semibold text-text-primary">
              Query vs Top Hit — {pairwise.mode === 'local' ? 'Smith-Waterman (local)' : 'Needleman-Wunsch (global)'}
            </h3>
            <span className="text-[10px] px-2 py-0.5 rounded-full bg-accent-amber/10 border border-accent-amber/30 text-accent-amber font-medium">
              {pairwise.mode} refinement
            </span>
          </div>
          <PairwiseResultDisplay
            result={pairwise}
            queryLabel="Query"
            subjectLabel={pairwiseSubject || 'Top hit'}
          />
        </div>
      )}

      {phylotree && (
        <div className="data-card p-5">
          <h3 className="text-sm font-semibold text-text-primary mb-2">Phylogenetic Tree</h3>
          <PhyloTreeViewer newick={phylotree} alignment={alnFasta} />
        </div>
      )}
    </div>
  );
}