AI Coach: 클라이언트 API 키 제거, 백엔드 프록시로 전환

- Anthropic API 직접 호출 → /api/stock/ai-coach 백엔드 프록시로 변경
- API 키 입력 UI 제거 (서버에서 관리)
- aiApiKey 상태 변수 및 localStorage 저장 로직 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 01:12:37 +09:00
parent 8fcfb6b000
commit 314702cb66

View File

@@ -232,8 +232,7 @@ const StockTrade = () => {
const [reportSortDir, setReportSortDir] = useState('desc'); const [reportSortDir, setReportSortDir] = useState('desc');
/* AI Coach */ /* AI Coach */
const [aiApiKey, setAiApiKey] = useState(''); const [aiModel, setAiModel] = useState(() => localStorage.getItem('ai_coach_model') ?? 'claude-haiku-4-5-20251001');
const [aiModel, setAiModel] = useState('claude-haiku-4-5-20251001');
const [aiResult, setAiResult] = useState(null); const [aiResult, setAiResult] = useState(null);
const [aiLoading, setAiLoading] = useState(false); const [aiLoading, setAiLoading] = useState(false);
const [aiError, setAiError] = useState(''); const [aiError, setAiError] = useState('');
@@ -377,12 +376,8 @@ const StockTrade = () => {
} }
}, [activeTab, assetHistoryDays, loadAssetHistory]); }, [activeTab, assetHistoryDays, loadAssetHistory]);
/* AI Coach: 마운트 시 localStorage에서 API Key + 오늘 캐시 복원 */ /* AI Coach: 마운트 시 오늘 캐시 복원 */
useEffect(() => { useEffect(() => {
const savedKey = localStorage.getItem('ai_coach_key') ?? '';
const savedModel = localStorage.getItem('ai_coach_model') ?? 'claude-haiku-4-5-20251001';
setAiApiKey(savedKey);
setAiModel(savedModel);
const today = new Date().toISOString().slice(0, 10); const today = new Date().toISOString().slice(0, 10);
const cached = localStorage.getItem(`ai_coach_${today}`); const cached = localStorage.getItem(`ai_coach_${today}`);
if (cached) { if (cached) {
@@ -707,7 +702,7 @@ const StockTrade = () => {
/* ── AI coach ────────────────────────────────────────────────── */ /* ── AI coach ────────────────────────────────────────────────── */
const handleAiCoach = async () => { const handleAiCoach = async () => {
if (!aiApiKey.trim() || portfolioHoldings.length === 0) return; if (portfolioHoldings.length === 0) return;
const today = new Date().toISOString().slice(0, 10); const today = new Date().toISOString().slice(0, 10);
const cacheKey = `ai_coach_${today}`; const cacheKey = `ai_coach_${today}`;
@@ -755,24 +750,15 @@ ${holdingsText}${marketText}
}`; }`;
try { try {
const res = await fetch('https://api.anthropic.com/v1/messages', { const res = await fetch('/api/stock/ai-coach', {
method: 'POST', method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' },
'Content-Type': 'application/json', body: JSON.stringify({ model: aiModel, prompt, max_tokens: 1024 }),
'x-api-key': aiApiKey.trim(),
'anthropic-version': '2023-06-01',
'anthropic-dangerous-direct-browser-access': 'true',
},
body: JSON.stringify({
model: aiModel,
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
}),
}); });
if (!res.ok) { if (!res.ok) {
const text = await res.text().catch(() => ''); const errData = await res.json().catch(() => ({}));
throw new Error(`Claude API 오류 (${res.status}): ${text.slice(0, 200)}`); throw new Error(errData.error || `AI Coach 오류 (${res.status})`);
} }
const data = await res.json(); const data = await res.json();
@@ -2467,30 +2453,8 @@ ${cashLines}
</div> </div>
)} )}
{/* API Key 설정 */} {/* 모델 선택 */}
<div className="ai-coach-settings"> <div className="ai-coach-settings">
<label>
Anthropic API Key
<div className="ai-coach-key-row">
<input
type="password"
className="ai-coach-key-input"
value={aiApiKey}
onChange={(e) => setAiApiKey(e.target.value)}
placeholder="sk-ant-api03-..."
/>
<button
className="button ghost small"
type="button"
onClick={() => {
localStorage.setItem('ai_coach_key', aiApiKey);
localStorage.setItem('ai_coach_model', aiModel);
}}
>
저장
</button>
</div>
</label>
<label> <label>
AI 모델 AI 모델
<select <select
@@ -2511,7 +2475,7 @@ ${cashLines}
className="button primary" className="button primary"
type="button" type="button"
onClick={handleAiCoach} onClick={handleAiCoach}
disabled={aiLoading || !aiApiKey.trim() || portfolioHoldings.length === 0} disabled={aiLoading || portfolioHoldings.length === 0}
> >
{aiLoading ? 'AI 분석 중...' : '오늘 투자 평가 받기'} {aiLoading ? 'AI 분석 중...' : '오늘 투자 평가 받기'}
</button> </button>