File size: 9,000 Bytes
654bfe6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import React, { useState } from 'react';
import { Code, Terminal, Play, CheckCircle, Copy, FileText, Server } from 'lucide-react';

export const ApiDocsPanel: React.FC = () => {
  const [activeEndpoint, setActiveEndpoint] = useState<'/predict' | '/explain' | '/health' | '/organs'>('/predict');
  const [testSmiles, setTestSmiles] = useState<string>('CC(=O)NC1=CC=C(O)C=C1');
  const [useTissueConditioning, setUseTissueConditioning] = useState<boolean>(true);
  const [apiResponse, setApiResponse] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [copied, setCopied] = useState<boolean>(false);

  const handleRunRequest = async () => {
    setIsLoading(true);
    setApiResponse(null);

    await new Promise((r) => setTimeout(r, 400));

    if (activeEndpoint === '/predict') {
      setApiResponse(JSON.stringify({
        compound: "Acetaminophen",
        smiles: testSmiles,
        tissueConditioning: useTissueConditioning,
        organScores: {
          liver: { meanRisk: 0.92, uncertaintySigma: 0.03, riskLevel: "Severe" },
          heart: { meanRisk: 0.12, uncertaintySigma: 0.02, riskLevel: "Low" },
          kidney: { meanRisk: 0.54, uncertaintySigma: 0.04, riskLevel: "Moderate" },
          brain: { meanRisk: 0.15, uncertaintySigma: 0.02, riskLevel: "Low" },
          lung: { meanRisk: 0.10, uncertaintySigma: 0.02, riskLevel: "Low" }
        },
        tanimotoDomain: {
          score: 0.88,
          status: "High Confidence (In-Domain)"
        }
      }, null, 2));
    } else if (activeEndpoint === '/explain') {
      setApiResponse(JSON.stringify({
        smiles: testSmiles,
        atomicHotspots: [
          { atomIndex: 0, symbol: "C", attentionWeight: 0.15 },
          { atomIndex: 1, symbol: "O", attentionWeight: 0.72, toxicophore: "Reactive Carbonyl" },
          { atomIndex: 2, symbol: "N", attentionWeight: 0.81, toxicophore: "Amide Center" }
        ],
        primaryToxicophores: ["Reactive Carbonyl Center", "Substituted Amine Core"]
      }, null, 2));
    } else if (activeEndpoint === '/organs') {
      setApiResponse(JSON.stringify({
        organs: [
          { id: "liver", markerGenes: ["CYP3A4", "ALB"], expressionDim: 128 },
          { id: "heart", markerGenes: ["MYH6", "TNNT2"], expressionDim: 128 },
          { id: "kidney", markerGenes: ["SLC22A2", "UMOD"], expressionDim: 128 }
        ]
      }, null, 2));
    } else {
      setApiResponse(JSON.stringify({
        status: "ok",
        version: "2.1.0-EpiADR-Net",
        model: "EpiADR-Net Tissue-Conditioned Graph Transformer",
        dataset: "SIDER 4.1 & GTEx V8"
      }, null, 2));
    }

    setIsLoading(false);
  };

  const pythonSnippet = `import requests

url = "http://localhost:8000/predict"
payload = {
    "smiles": "${testSmiles}",
    "use_tissue_conditioning": ${useTissueConditioning ? "True" : "False"},
    "mc_dropout_passes": 30
}

response = requests.post(url, json=payload)
print(response.json())`;

  const curlSnippet = `curl -X POST "http://localhost:8000/predict" \\
  -H "Content-Type: application/json" \\
  -d '{"smiles": "${testSmiles}", "use_tissue_conditioning": ${useTissueConditioning}}'`;

  const copyToClipboard = (text: string) => {
    navigator.clipboard.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className="space-y-6">
      
      {/* Header */}
      <div className="bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-lg space-y-3">
        <div className="flex items-center space-x-2 border-b border-slate-800 pb-3">
          <Terminal className="w-5 h-5 text-indigo-400" />
          <div>
            <h2 className="text-base font-bold text-white">FastAPI REST Microservice & Swagger UI Simulator</h2>
            <p className="text-xs text-slate-400">Enterprise REST Endpoints for High-Throughput Preclinical Screening</p>
          </div>
        </div>

        {/* Endpoints Nav */}
        <div className="flex space-x-2 overflow-x-auto no-scrollbar">
          {(['/predict', '/explain', '/organs', '/health'] as const).map((ep) => (
            <button
              key={ep}
              onClick={() => {
                setActiveEndpoint(ep);
                setApiResponse(null);
              }}
              className={`px-3.5 py-1.5 rounded-xl text-xs font-mono font-bold transition-all border ${
                activeEndpoint === ep
                  ? 'bg-indigo-600 text-white border-indigo-400 shadow-md'
                  : 'bg-slate-950 border-slate-800 text-slate-400 hover:bg-slate-800'
              }`}
            >
              {ep}
            </button>
          ))}
        </div>
      </div>

      {/* Main Interactive Testing Grid */}
      <div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
        
        {/* Left Column: Request Body & Test Form */}
        <div className="lg:col-span-5 bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-lg space-y-4">
          <h3 className="text-sm font-bold text-white flex items-center space-x-2">
            <Server className="w-4 h-4 text-indigo-400" />
            <span>Request Simulator ({activeEndpoint})</span>
          </h3>

          {(activeEndpoint === '/predict' || activeEndpoint === '/explain') && (
            <div className="space-y-3 text-xs">
              <div className="space-y-1">
                <label className="text-slate-400 font-semibold block">Target SMILES String:</label>
                <input
                  type="text"
                  value={testSmiles}
                  onChange={(e) => setTestSmiles(e.target.value)}
                  className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-indigo-300 font-mono"
                />
              </div>

              {activeEndpoint === '/predict' && (
                <div className="flex items-center justify-between bg-slate-950 p-2.5 rounded-xl border border-slate-800">
                  <span className="text-slate-300 font-semibold">use_tissue_conditioning</span>
                  <input
                    type="checkbox"
                    checked={useTissueConditioning}
                    onChange={(e) => setUseTissueConditioning(e.target.checked)}
                    className="accent-indigo-500 w-4 h-4 cursor-pointer"
                  />
                </div>
              )}
            </div>
          )}

          <button
            onClick={handleRunRequest}
            disabled={isLoading}
            className="w-full py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl font-bold text-xs shadow-lg flex items-center justify-center space-x-2 transition-all active:scale-95"
          >
            <Play className="w-4 h-4 fill-white" />
            <span>Execute API Request</span>
          </button>

          {/* Code Snippets */}
          <div className="pt-2 border-t border-slate-800 space-y-2">
            <div className="flex justify-between items-center text-xs">
              <span className="font-bold text-slate-300">Python Integration Snippet</span>
              <button
                onClick={() => copyToClipboard(pythonSnippet)}
                className="text-indigo-400 hover:text-indigo-300 flex items-center space-x-1"
              >
                <Copy className="w-3.5 h-3.5" />
                <span>{copied ? 'Copied!' : 'Copy'}</span>
              </button>
            </div>
            <pre className="bg-slate-950 p-3 rounded-xl border border-slate-800 text-[11px] font-mono text-emerald-300 overflow-x-auto leading-normal">
              {pythonSnippet}
            </pre>
          </div>

        </div>

        {/* Right Column: JSON Response Output */}
        <div className="lg:col-span-7 bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-lg space-y-3">
          <div className="flex items-center justify-between border-b border-slate-800 pb-2">
            <span className="text-xs font-bold text-slate-300 font-mono">Response Payload (200 OK)</span>
            <span className="text-[11px] text-emerald-400 font-mono">application/json</span>
          </div>

          <div className="bg-slate-950 p-4 rounded-xl border border-slate-800/80 min-h-[280px]">
            {isLoading ? (
              <div className="flex items-center justify-center h-48 text-indigo-400 font-mono text-xs animate-pulse">
                <span>Executing forward pass...</span>
              </div>
            ) : apiResponse ? (
              <pre className="text-xs font-mono text-indigo-200 overflow-x-auto leading-relaxed">
                {apiResponse}
              </pre>
            ) : (
              <div className="flex flex-col items-center justify-center h-48 text-slate-500 text-xs">
                <span>Click "Execute API Request" above to view response payload</span>
              </div>
            )}
          </div>
        </div>

      </div>

    </div>
  );
};