llm 동작 수정
This commit is contained in:
@@ -100,17 +100,35 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
||||
const [reportModel, setReportModel] = useState<BacktestAnalysisReportModel | null>(null);
|
||||
const [reportLoading, setReportLoading] = useState(false);
|
||||
const [aiVerifyOpen, setAiVerifyOpen] = useState(false);
|
||||
const [aiVerifyLoading, setAiVerifyLoading] = useState(false);
|
||||
const [aiVerifyRunning, setAiVerifyRunning] = useState(false);
|
||||
const [aiVerifyError, setAiVerifyError] = useState<string | null>(null);
|
||||
const [aiVerifyResult, setAiVerifyResult] = useState<StrategyEvaluationAiVerifyResponse | null>(null);
|
||||
const [aiVerifyContext, setAiVerifyContext] = useState<import('../utils/strategyEvaluationAiVerify').StrategyEvaluationAiVerifyContext | null>(null);
|
||||
const [aiVerifySummary, setAiVerifySummary] = useState<string | null>(null);
|
||||
const [aiVerifyCompleteNotice, setAiVerifyCompleteNotice] = useState(false);
|
||||
const [aiVerifyErrorNotice, setAiVerifyErrorNotice] = useState<string | null>(null);
|
||||
const aiVerifyGenRef = useRef(0);
|
||||
const aiVerifyOpenRef = useRef(aiVerifyOpen);
|
||||
const aiVerifyPollAbortRef = useRef<AbortController | null>(null);
|
||||
const reportCacheKeyRef = useRef<string | null>(null);
|
||||
const reportGenRef = useRef(0);
|
||||
const evalSessionRef = useRef(0);
|
||||
const paramsRevisionRef = useRef(paramsRevision);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
aiVerifyPollAbortRef.current?.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
aiVerifyOpenRef.current = aiVerifyOpen;
|
||||
if (aiVerifyOpen) {
|
||||
setAiVerifyCompleteNotice(false);
|
||||
setAiVerifyErrorNotice(null);
|
||||
}
|
||||
}, [aiVerifyOpen]);
|
||||
|
||||
const getEvalParams = useMemo(
|
||||
() => mergeEvalGetParams(baseGetParams, appliedIndicatorParams),
|
||||
[baseGetParams, appliedIndicatorParams],
|
||||
@@ -392,12 +410,18 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
||||
const runAiVerify = useCallback(async () => {
|
||||
if (aiVerifyDisabled || !selectedStrategyId || !selectedStrategy || selectedBarTimeSec == null) return;
|
||||
|
||||
const gen = ++aiVerifyGenRef.current;
|
||||
aiVerifyPollAbortRef.current?.abort();
|
||||
const pollAbort = new AbortController();
|
||||
aiVerifyPollAbortRef.current = pollAbort;
|
||||
|
||||
const runId = ++aiVerifyGenRef.current;
|
||||
setAiVerifyOpen(true);
|
||||
setAiVerifyLoading(true);
|
||||
setAiVerifyRunning(true);
|
||||
setAiVerifyError(null);
|
||||
setAiVerifyResult(null);
|
||||
setAiVerifyContext(null);
|
||||
setAiVerifyCompleteNotice(false);
|
||||
setAiVerifyErrorNotice(null);
|
||||
|
||||
try {
|
||||
const indicatorParams = buildEvalParamsFromStrategy(selectedStrategy, getEvalParams);
|
||||
@@ -419,15 +443,25 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
||||
setAiVerifySummary(summarizeVerifyContext(context));
|
||||
setAiVerifyContext(context);
|
||||
|
||||
const result = await requestStrategyEvaluationAiVerify(context);
|
||||
if (gen !== aiVerifyGenRef.current) return;
|
||||
const result = await requestStrategyEvaluationAiVerify(context, {
|
||||
signal: pollAbort.signal,
|
||||
});
|
||||
if (runId !== aiVerifyGenRef.current) return;
|
||||
setAiVerifyResult(result);
|
||||
if (!aiVerifyOpenRef.current) {
|
||||
setAiVerifyCompleteNotice(true);
|
||||
}
|
||||
} catch (e) {
|
||||
if (gen !== aiVerifyGenRef.current) return;
|
||||
setAiVerifyError(e instanceof Error ? e.message : 'AI 검증 요청 실패');
|
||||
if (runId !== aiVerifyGenRef.current) return;
|
||||
if (e instanceof DOMException && e.name === 'AbortError') return;
|
||||
const msg = e instanceof Error ? e.message : 'AI 검증 요청 실패';
|
||||
setAiVerifyError(msg);
|
||||
if (!aiVerifyOpenRef.current) {
|
||||
setAiVerifyErrorNotice(msg);
|
||||
}
|
||||
} finally {
|
||||
if (gen === aiVerifyGenRef.current) {
|
||||
setAiVerifyLoading(false);
|
||||
if (runId === aiVerifyGenRef.current) {
|
||||
setAiVerifyRunning(false);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@@ -448,13 +482,15 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
||||
]);
|
||||
|
||||
const handleOpenAiVerify = useCallback(() => {
|
||||
if (aiVerifyRunning) {
|
||||
setAiVerifyOpen(true);
|
||||
return;
|
||||
}
|
||||
void runAiVerify();
|
||||
}, [runAiVerify]);
|
||||
}, [aiVerifyRunning, runAiVerify]);
|
||||
|
||||
const handleCloseAiVerify = useCallback(() => {
|
||||
++aiVerifyGenRef.current;
|
||||
setAiVerifyOpen(false);
|
||||
setAiVerifyLoading(false);
|
||||
}, []);
|
||||
|
||||
const handleOpenReport = useCallback(async () => {
|
||||
@@ -708,7 +744,7 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
||||
reportLoading={reportLoading}
|
||||
onOpenAiVerify={handleOpenAiVerify}
|
||||
aiVerifyDisabled={aiVerifyDisabled}
|
||||
aiVerifyLoading={aiVerifyLoading && aiVerifyOpen}
|
||||
aiVerifyLoading={aiVerifyRunning}
|
||||
marketTickers={marketFeed.tickers}
|
||||
marketInfos={marketFeed.marketInfos}
|
||||
marketLoading={marketFeed.loading}
|
||||
@@ -806,7 +842,7 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
||||
<StrategyEvaluationAiVerifyModal
|
||||
open={aiVerifyOpen}
|
||||
onClose={handleCloseAiVerify}
|
||||
loading={aiVerifyLoading}
|
||||
loading={aiVerifyRunning}
|
||||
error={aiVerifyError}
|
||||
contextSummary={aiVerifySummary}
|
||||
verifyContext={aiVerifyContext}
|
||||
@@ -814,6 +850,67 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
||||
theme={theme}
|
||||
onRetry={() => { void runAiVerify(); }}
|
||||
/>
|
||||
|
||||
{aiVerifyRunning && !aiVerifyOpen && (
|
||||
<div className="seval-ai-verify-bg-pill" role="status" aria-live="polite">
|
||||
<span className="btd-run-spinner seval-ai-verify-bg-pill-spinner" aria-hidden />
|
||||
<span>AI 분석 진행 중…</span>
|
||||
<button
|
||||
type="button"
|
||||
className="seval-ai-verify-bg-pill-btn"
|
||||
onClick={() => setAiVerifyOpen(true)}
|
||||
>
|
||||
진행 보기
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(aiVerifyCompleteNotice || aiVerifyErrorNotice) && !aiVerifyOpen && (
|
||||
<div
|
||||
className={`seval-ai-verify-notice${aiVerifyErrorNotice ? ' seval-ai-verify-notice--error' : ''}`}
|
||||
role="status"
|
||||
>
|
||||
<span className="seval-ai-verify-notice-text">
|
||||
{aiVerifyErrorNotice ?? 'AI 분석이 완료되었습니다.'}
|
||||
</span>
|
||||
<div className="seval-ai-verify-notice-actions">
|
||||
{!aiVerifyErrorNotice && (
|
||||
<button
|
||||
type="button"
|
||||
className="seval-ai-verify-notice-btn seval-ai-verify-notice-btn--primary"
|
||||
onClick={() => {
|
||||
setAiVerifyCompleteNotice(false);
|
||||
setAiVerifyOpen(true);
|
||||
}}
|
||||
>
|
||||
결과 보기
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="seval-ai-verify-notice-btn"
|
||||
onClick={() => {
|
||||
setAiVerifyCompleteNotice(false);
|
||||
setAiVerifyErrorNotice(null);
|
||||
}}
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
{aiVerifyErrorNotice && (
|
||||
<button
|
||||
type="button"
|
||||
className="seval-ai-verify-notice-btn seval-ai-verify-notice-btn--primary"
|
||||
onClick={() => {
|
||||
setAiVerifyErrorNotice(null);
|
||||
setAiVerifyOpen(true);
|
||||
}}
|
||||
>
|
||||
상세
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -299,7 +299,10 @@ const StrategyEvaluationAiVerifyModal: React.FC<Props> = ({
|
||||
<div className="seval-ai-verify-loading" aria-live="polite">
|
||||
<span className="btd-run-spinner seval-ai-verify-spinner" aria-hidden />
|
||||
<p>LLM이 평가 결과와 차트 시그널을 분석 중입니다…</p>
|
||||
<p className="seval-ai-verify-loading-hint">Ta4j 재평가와 LLM 분석에 최대 2~3분 걸릴 수 있습니다.</p>
|
||||
<p className="seval-ai-verify-loading-hint">
|
||||
Ta4j 재평가와 LLM 분석에 최대 2~3분 걸릴 수 있습니다.
|
||||
모달을 닫아도 백그라운드에서 분석이 계속됩니다.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -335,7 +338,9 @@ const StrategyEvaluationAiVerifyModal: React.FC<Props> = ({
|
||||
|
||||
<footer className="seval-ai-verify-foot">
|
||||
<p className="seval-ai-verify-footnote">
|
||||
※ AI 분석은 참고용입니다. 수정 포인트는 Cursor 등에 붙여넣어 코드 수정을 요청할 수 있습니다.
|
||||
{loading
|
||||
? '※ 분석 중 닫기를 눌러도 작업은 백그라운드에서 계속됩니다.'
|
||||
: '※ AI 분석은 참고용입니다. 수정 포인트는 Cursor 등에 붙여넣어 코드 수정을 요청할 수 있습니다.'}
|
||||
</p>
|
||||
<button type="button" className="seval-ai-verify-close-btn" onClick={onClose}>
|
||||
닫기
|
||||
|
||||
Reference in New Issue
Block a user