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:
@@ -232,8 +232,7 @@ const StockTrade = () => {
|
||||
const [reportSortDir, setReportSortDir] = useState('desc');
|
||||
|
||||
/* AI Coach */
|
||||
const [aiApiKey, setAiApiKey] = useState('');
|
||||
const [aiModel, setAiModel] = useState('claude-haiku-4-5-20251001');
|
||||
const [aiModel, setAiModel] = useState(() => localStorage.getItem('ai_coach_model') ?? 'claude-haiku-4-5-20251001');
|
||||
const [aiResult, setAiResult] = useState(null);
|
||||
const [aiLoading, setAiLoading] = useState(false);
|
||||
const [aiError, setAiError] = useState('');
|
||||
@@ -377,12 +376,8 @@ const StockTrade = () => {
|
||||
}
|
||||
}, [activeTab, assetHistoryDays, loadAssetHistory]);
|
||||
|
||||
/* AI Coach: 마운트 시 localStorage에서 API Key + 오늘 캐시 복원 */
|
||||
/* AI Coach: 마운트 시 오늘 캐시 복원 */
|
||||
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 cached = localStorage.getItem(`ai_coach_${today}`);
|
||||
if (cached) {
|
||||
@@ -707,7 +702,7 @@ const StockTrade = () => {
|
||||
/* ── AI coach ────────────────────────────────────────────────── */
|
||||
|
||||
const handleAiCoach = async () => {
|
||||
if (!aiApiKey.trim() || portfolioHoldings.length === 0) return;
|
||||
if (portfolioHoldings.length === 0) return;
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const cacheKey = `ai_coach_${today}`;
|
||||
@@ -755,24 +750,15 @@ ${holdingsText}${marketText}
|
||||
}`;
|
||||
|
||||
try {
|
||||
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
const res = await fetch('/api/stock/ai-coach', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'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 }],
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ model: aiModel, prompt, max_tokens: 1024 }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
throw new Error(`Claude API 오류 (${res.status}): ${text.slice(0, 200)}`);
|
||||
const errData = await res.json().catch(() => ({}));
|
||||
throw new Error(errData.error || `AI Coach 오류 (${res.status})`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
@@ -2467,30 +2453,8 @@ ${cashLines}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* API Key 설정 */}
|
||||
{/* 모델 선택 */}
|
||||
<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>
|
||||
AI 모델
|
||||
<select
|
||||
@@ -2511,7 +2475,7 @@ ${cashLines}
|
||||
className="button primary"
|
||||
type="button"
|
||||
onClick={handleAiCoach}
|
||||
disabled={aiLoading || !aiApiKey.trim() || portfolioHoldings.length === 0}
|
||||
disabled={aiLoading || portfolioHoldings.length === 0}
|
||||
>
|
||||
{aiLoading ? 'AI 분석 중...' : '오늘 투자 평가 받기'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user