Compare commits
No commits in common. "68bb1d62cd887615d86df5526de40d54fb66783c" and "d736e1ef7740ab16b2a1556bdca0ff1970dfdd2b" have entirely different histories.
68bb1d62cd
...
d736e1ef77
|
|
@ -5,21 +5,19 @@
|
||||||
* - "다른 병원 랜덤" 버튼으로 재선택 가능
|
* - "다른 병원 랜덤" 버튼으로 재선택 가능
|
||||||
* - DevOnly 가드 아래 등록되어 localhost에서만 접근
|
* - DevOnly 가드 아래 등록되어 localhost에서만 접근
|
||||||
*/
|
*/
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
import MultiChannelInput, { type AnalyzePayload } from '@/features/channels/components/MultiChannelInput'
|
import MultiChannelInput, { type AnalyzePayload } from '@/features/channels/components/MultiChannelInput'
|
||||||
import { CLINICS } from '../fixtures/mockUrls'
|
import { CLINICS } from '../fixtures/mockUrls'
|
||||||
|
|
||||||
// 현재는 뷰성형외과만 prefill 한다. CLINICS 의 다른 fixture 는 삭제하지 않고 보존.
|
function pickRandomIndex(): number {
|
||||||
const PINNED_LABEL = '뷰성형외과'
|
return Math.floor(Math.random() * CLINICS.length)
|
||||||
|
|
||||||
function pickIndex(): number {
|
|
||||||
const i = CLINICS.findIndex((c) => c.label === PINNED_LABEL)
|
|
||||||
return i >= 0 ? i : 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TestPrefillPage() {
|
export default function TestPrefillPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const index = pickIndex()
|
const [seed, setSeed] = useState(0)
|
||||||
|
const index = useMemo(() => pickRandomIndex(), [seed])
|
||||||
const clinic = CLINICS[index]
|
const clinic = CLINICS[index]
|
||||||
|
|
||||||
const handleAnalyze = (payload: AnalyzePayload) => {
|
const handleAnalyze = (payload: AnalyzePayload) => {
|
||||||
|
|
@ -47,11 +45,18 @@ export default function TestPrefillPage() {
|
||||||
{' '}<span className="text-slate-400">({index + 1} / {CLINICS.length})</span>
|
{' '}<span className="text-slate-400">({index + 1} / {CLINICS.length})</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* 현재 PINNED_LABEL 하나만 사용하므로 랜덤 버튼은 숨김.
|
<button
|
||||||
필요 시 pickIndex() 를 다시 randomize 하고 이 버튼을 복구. */}
|
type="button"
|
||||||
|
onClick={() => setSeed((s) => s + 1)}
|
||||||
|
className="px-4 py-2 rounded-lg bg-white border border-slate-200 text-sm font-medium text-slate-700 hover:bg-slate-50 shadow-sm"
|
||||||
|
>
|
||||||
|
다른 병원 랜덤
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* key={seed} 로 강제 remount — initialUrls는 mount 시점 1회만 반영되기 때문 */}
|
||||||
<MultiChannelInput
|
<MultiChannelInput
|
||||||
|
key={seed}
|
||||||
variant="hero"
|
variant="hero"
|
||||||
initialUrls={clinic.urls}
|
initialUrls={clinic.urls}
|
||||||
onAnalyze={handleAnalyze}
|
onAnalyze={handleAnalyze}
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,6 @@ export function useAnalysisPipeline(): UseAnalysisPipelineResult {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { reportId: urlReportId } = useParams<{ reportId?: string }>();
|
const { reportId: urlReportId } = useParams<{ reportId?: string }>();
|
||||||
|
|
||||||
// ?run_id= 쿼리로 진입하면 createClinic/startAnalysis 건너뛰고 폴링부터 시작.
|
|
||||||
// path 파라미터(/report/loading/:reportId) 와 동일 의미로 취급.
|
|
||||||
const queryRunId = new URLSearchParams(location.search).get('run_id');
|
|
||||||
const resumeRunId = urlReportId || queryRunId || undefined;
|
|
||||||
|
|
||||||
const locState = (location.state as { url?: string; manualChannels?: ManualChannels }) ?? {};
|
const locState = (location.state as { url?: string; manualChannels?: ManualChannels }) ?? {};
|
||||||
const url = locState.url;
|
const url = locState.url;
|
||||||
const manualChannels = locState.manualChannels;
|
const manualChannels = locState.manualChannels;
|
||||||
|
|
@ -142,9 +137,7 @@ export function useAnalysisPipeline(): UseAnalysisPipelineResult {
|
||||||
reportId = runId; // 백엔드: analysis_run_id 가 report 조회 키와 동일
|
reportId = runId; // 백엔드: analysis_run_id 가 report 조회 키와 동일
|
||||||
|
|
||||||
saveSession({ reportId, clinicId, runId, url: startUrl });
|
saveSession({ reportId, clinicId, runId, url: startUrl });
|
||||||
// 새로고침·공유 시 createClinic/startAnalysis 재호출 없이 폴링만 재개되도록
|
window.history.replaceState(null, '', `/report/loading/${reportId}`);
|
||||||
// run_id 를 쿼리 파라미터로 URL 에 박아둔다.
|
|
||||||
window.history.replaceState(null, '', `/report/loading?run_id=${reportId}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!runId) throw new Error('runId 없음');
|
if (!runId) throw new Error('runId 없음');
|
||||||
|
|
@ -210,17 +203,17 @@ export function useAnalysisPipeline(): UseAnalysisPipelineResult {
|
||||||
if (hasStarted.current) return;
|
if (hasStarted.current) return;
|
||||||
hasStarted.current = true;
|
hasStarted.current = true;
|
||||||
|
|
||||||
// 1. URL 파라미터/쿼리 resume (예: /report/loading/abc-123 또는 /report/loading?run_id=abc-123)
|
// 1. URL 파라미터 resume (예: /report/loading/abc-123)
|
||||||
if (resumeRunId) {
|
if (urlReportId) {
|
||||||
setPhase('resuming');
|
setPhase('resuming');
|
||||||
const session = loadSession();
|
const session = loadSession();
|
||||||
const runId = session.runId || resumeRunId;
|
const runId = session.runId || urlReportId;
|
||||||
getAnalysisStatus(runId)
|
getAnalysisStatus(runId)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status !== 200) throw new Error('status fetch failed');
|
if (res.status !== 200) throw new Error('status fetch failed');
|
||||||
const status = res.data;
|
const status = res.data;
|
||||||
if (status.status === AnalysisStatus.completed) {
|
if (status.status === AnalysisStatus.completed) {
|
||||||
navigate(`/report/${resumeRunId}`, { replace: true });
|
navigate(`/report/${urlReportId}`, { replace: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (status.status === AnalysisStatus.failed) {
|
if (status.status === AnalysisStatus.failed) {
|
||||||
|
|
@ -229,13 +222,13 @@ export function useAnalysisPipeline(): UseAnalysisPipelineResult {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
saveSession({
|
saveSession({
|
||||||
reportId: resumeRunId,
|
reportId: urlReportId,
|
||||||
clinicId: session.clinicId || undefined,
|
clinicId: session.clinicId || undefined,
|
||||||
runId,
|
runId,
|
||||||
url: session.url || undefined,
|
url: session.url || undefined,
|
||||||
});
|
});
|
||||||
runPipeline(undefined, {
|
runPipeline(undefined, {
|
||||||
reportId: resumeRunId,
|
reportId: urlReportId,
|
||||||
clinicId: session.clinicId || undefined,
|
clinicId: session.clinicId || undefined,
|
||||||
runId,
|
runId,
|
||||||
});
|
});
|
||||||
|
|
@ -281,7 +274,7 @@ export function useAnalysisPipeline(): UseAnalysisPipelineResult {
|
||||||
|
|
||||||
// 4. 신규 분석 시작
|
// 4. 신규 분석 시작
|
||||||
runPipeline(url);
|
runPipeline(url);
|
||||||
}, [url, resumeRunId, navigate, runPipeline]);
|
}, [url, urlReportId, navigate, runPipeline]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phase,
|
phase,
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@ import ky, { type KyInstance } from 'ky'
|
||||||
const API_BASE_URL = (__VITE_API_BASE_URL__ ?? '').replace(/\/$/, '')
|
const API_BASE_URL = (__VITE_API_BASE_URL__ ?? '').replace(/\/$/, '')
|
||||||
|
|
||||||
export const kyInstance: KyInstance = ky.create({
|
export const kyInstance: KyInstance = ky.create({
|
||||||
// createClinic·startAnalysis 처럼 백엔드가 외부 크롤·LLM 을 호출하는 동기 엔드포인트는
|
timeout: 60_000,
|
||||||
// 60s 안에 끝난다는 보장이 없어 클라이언트 타임아웃은 끈다. 폴링 호출은 어차피 짧다.
|
|
||||||
timeout: false,
|
|
||||||
retry: 1,
|
retry: 1,
|
||||||
// orval generated 타입이 4xx/5xx 응답도 data로 받아오므로 throw 비활성
|
// orval generated 타입이 4xx/5xx 응답도 data로 받아오므로 throw 비활성
|
||||||
throwHttpErrors: false,
|
throwHttpErrors: false,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue