Initial commit: HR Performance System

- Database schema with 31 tables for 4-card system
- LLM API integration (Gemini, DeepSeek, OpenAI)
- Error handling system with modal component
- Connection test UI for LLM services
- Environment configuration files
- Complete database documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
donald
2025-12-03 23:34:13 +08:00
commit c24634f4b7
18 changed files with 8179 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
/**
* LLM 連線測試元件
* 測試 Gemini, DeepSeek, OpenAI 三種 LLM API 的連線狀態
*/
import React, { useState } from 'react';
import axios from 'axios';
import './LLMConnectionTest.css';
const LLMConnectionTest = () => {
const [testResults, setTestResults] = useState({
gemini: null,
deepseek: null,
openai: null,
});
const [testing, setTesting] = useState({
gemini: false,
deepseek: false,
openai: false,
});
const [testingAll, setTestingAll] = useState(false);
const providers = [
{
id: 'gemini',
name: 'Google Gemini',
icon: '🤖',
color: '#4285f4',
},
{
id: 'deepseek',
name: 'DeepSeek',
icon: '🧠',
color: '#7c3aed',
},
{
id: 'openai',
name: 'OpenAI',
icon: '✨',
color: '#10a37f',
},
];
/**
* 測試單一 LLM 連線
*/
const testConnection = async (provider) => {
setTesting((prev) => ({ ...prev, [provider]: true }));
setTestResults((prev) => ({ ...prev, [provider]: null }));
try {
const response = await axios.post(`/api/llm/test/${provider}`);
setTestResults((prev) => ({ ...prev, [provider]: response.data }));
} catch (error) {
const errorData = error.response?.data || {
success: false,
message: error.message || '連線測試失敗',
provider,
};
setTestResults((prev) => ({ ...prev, [provider]: errorData }));
} finally {
setTesting((prev) => ({ ...prev, [provider]: false }));
}
};
/**
* 測試所有 LLM 連線
*/
const testAllConnections = async () => {
setTestingAll(true);
setTestResults({
gemini: null,
deepseek: null,
openai: null,
});
try {
const response = await axios.post('/api/llm/test/all');
setTestResults(response.data);
} catch (error) {
console.error('測試所有連線失敗:', error);
} finally {
setTestingAll(false);
}
};
/**
* 取得測試結果樣式
*/
const getResultClass = (result) => {
if (!result) return '';
return result.success ? 'success' : 'failure';
};
/**
* 取得測試結果圖示
*/
const getResultIcon = (result) => {
if (!result) return '⏳';
return result.success ? '✅' : '❌';
};
return (
<div className="llm-connection-test">
<div className="test-header">
<h2>LLM API 連線測試</h2>
<p className="test-description">
測試與外部 LLM 服務的連線狀態確保 API 金鑰配置正確
</p>
</div>
<div className="test-actions">
<button
className="test-all-button"
onClick={testAllConnections}
disabled={testingAll || Object.values(testing).some(Boolean)}
>
{testingAll ? (
<>
<span className="spinner"></span>
測試中...
</>
) : (
<>
<span>🔄</span>
測試所有連線
</>
)}
</button>
</div>
<div className="providers-grid">
{providers.map((provider) => {
const result = testResults[provider.id];
const isTesting = testing[provider.id];
return (
<div
key={provider.id}
className={`provider-card ${getResultClass(result)}`}
style={{ borderColor: provider.color }}
>
<div className="provider-header">
<div className="provider-info">
<span className="provider-icon" style={{ color: provider.color }}>
{provider.icon}
</span>
<h3>{provider.name}</h3>
</div>
<span className="result-icon">{getResultIcon(result)}</span>
</div>
<div className="provider-body">
{result && (
<div className="result-details">
<div className={`status-badge ${result.success ? 'success' : 'failure'}`}>
{result.success ? '連線成功' : '連線失敗'}
</div>
<p className="result-message">{result.message}</p>
{result.model && (
<div className="result-meta">
<span className="meta-label">模型:</span>
<span className="meta-value">{result.model}</span>
</div>
)}
{result.error && (
<details className="error-details">
<summary>錯誤詳情</summary>
<pre>{result.error}</pre>
</details>
)}
</div>
)}
{!result && !isTesting && (
<p className="no-result">尚未測試</p>
)}
</div>
<div className="provider-footer">
<button
className="test-button"
onClick={() => testConnection(provider.id)}
disabled={isTesting || testingAll}
style={{ borderColor: provider.color, color: provider.color }}
>
{isTesting ? (
<>
<span className="spinner small"></span>
測試中...
</>
) : (
<>
<span>🔌</span>
測試連線
</>
)}
</button>
</div>
</div>
);
})}
</div>
<div className="test-footer">
<div className="info-box">
<span className="info-icon"></span>
<div className="info-content">
<strong>提示:</strong>
<p>請確保在 .env 文件中正確配置了對應的 API 金鑰</p>
<ul>
<li>GEMINI_API_KEY - Google Gemini API 金鑰</li>
<li>DEEPSEEK_API_KEY - DeepSeek API 金鑰</li>
<li>OPENAI_API_KEY - OpenAI API 金鑰</li>
</ul>
</div>
</div>
</div>
</div>
);
};
export default LLMConnectionTest;