- 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>
229 lines
6.3 KiB
JavaScript
229 lines
6.3 KiB
JavaScript
/**
|
||
* 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;
|