llm 동작 수정

This commit is contained in:
Macbook
2026-06-17 15:37:23 +09:00
parent 301f45301f
commit edac7540cd
14 changed files with 901 additions and 63 deletions
@@ -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}>