This commit is contained in:
h z
2026-02-13 03:42:04 +00:00
commit 29ae2ba1f4
15 changed files with 2840 additions and 0 deletions

View File

@@ -0,0 +1,496 @@
import React, { useState, useEffect } from 'react';
import './../styles/App.css';
const DebateConfiguration = ({ onCreateDebate, onViewSessions, onViewSettings, isGuest }) => {
const [formData, setFormData] = useState({
topic: '',
proProvider: 'openai',
proModel: 'gpt-4',
conProvider: 'claude',
conModel: 'claude-3-opus',
maxRounds: 5,
maxTokens: 500,
webSearchEnabled: false,
webSearchMode: 'auto'
});
const [availableModels, setAvailableModels] = useState({
openai: [],
claude: [],
qwen: [],
deepseek: []
});
const [availableProviders, setAvailableProviders] = useState([
{ provider: 'openai', display_name: 'OpenAI' },
{ provider: 'claude', display_name: 'Claude' },
{ provider: 'qwen', display_name: 'Qwen' },
{ provider: 'deepseek', display_name: 'DeepSeek' }
]);
const [isLoading, setIsLoading] = useState(false);
const [modelsLoading, setModelsLoading] = useState({});
useEffect(() => {
// Load available providers when component mounts
loadAvailableProviders();
}, []);
const loadAvailableProviders = async () => {
try {
// Get all API keys from backend to determine which providers are available
const providers = ['openai', 'claude', 'qwen', 'deepseek'];
const available = [];
for (const provider of providers) {
try {
const response = await fetch(`http://localhost:8000/api-keys/${provider}`);
if (response.ok) {
const data = await response.json();
if (data.api_key) {
// For Qwen and DeepSeek, we can check if the key is valid
if (provider === 'qwen' || provider === 'deepseek') {
// For now, just check if key exists - we'll implement validation later if needed
available.push({
provider: provider,
display_name: provider.charAt(0).toUpperCase() + provider.slice(1)
});
} else {
// For OpenAI and Claude, we're skipping validation as requested
available.push({
provider: provider,
display_name: provider.charAt(0).toUpperCase() + provider.slice(1)
});
}
}
}
} catch (error) {
// If there's an error getting the API key, skip this provider
console.error(`Error getting API key for ${provider}:`, error);
}
}
if (available.length > 0) {
setAvailableProviders(available);
// Update form data to use valid providers
if (!available.some(p => p.provider === formData.proProvider)) {
setFormData(prev => ({
...prev,
proProvider: available[0]?.provider || prev.proProvider
}));
}
if (!available.some(p => p.provider === formData.conProvider)) {
setFormData(prev => ({
...prev,
conProvider: available[0]?.provider || prev.conProvider
}));
}
} else {
// If no providers have API keys, show all providers but disable functionality
setAvailableProviders([
{ provider: 'openai', display_name: 'OpenAI' },
{ provider: 'claude', display_name: 'Claude' },
{ provider: 'qwen', display_name: 'Qwen' },
{ provider: 'deepseek', display_name: 'DeepSeek' }
]);
}
} catch (error) {
console.error('Error loading available providers:', error);
}
};
useEffect(() => {
// Update model options when provider changes
if (formData.proProvider) {
loadModelsForProvider(formData.proProvider);
}
if (formData.conProvider) {
loadModelsForProvider(formData.conProvider);
}
}, [formData.proProvider, formData.conProvider]);
const loadModelsForProvider = async (provider) => {
setModelsLoading(prev => ({ ...prev, [provider]: true }));
try {
// Special handling for Qwen - fetch directly from Qwen API
if (provider === 'qwen') {
try {
// Call backend to get Qwen models (backend will fetch from Qwen API)
const qwenResponse = await fetch(`http://localhost:8000/models/${provider}`);
if (qwenResponse.ok) {
const qwenData = await qwenResponse.json();
console.log('Backend Qwen models response:', qwenData); // Debug log
const models = qwenData.models || [];
console.log('Fetched Qwen models:', models); // Debug log
setAvailableModels(prev => ({
...prev,
[provider]: models
}));
// Update selected model if current selection is not in the new list
if (provider === formData.proProvider && models.length > 0) {
const currentModelExists = models.some(model => model.model_identifier === formData.proModel);
if (!currentModelExists) {
setFormData(prev => ({
...prev,
proModel: models[0].model_identifier
}));
}
}
if (provider === formData.conProvider && models.length > 0) {
const currentModelExists = models.some(model => model.model_identifier === formData.conModel);
if (!currentModelExists) {
setFormData(prev => ({
...prev,
conModel: models[0].model_identifier
}));
}
}
} else {
console.error('Error fetching models from backend for Qwen:', qwenResponse.status, await qwenResponse.text());
// Use fallback models if API call fails
const fallbackModels = [
{ model_identifier: 'qwen3-max', display_name: 'Qwen3 Max' },
{ model_identifier: 'qwen3-plus', display_name: 'Qwen3 Plus' },
{ model_identifier: 'qwen3-flash', display_name: 'Qwen3 Flash' },
{ model_identifier: 'qwen-max', display_name: 'Qwen Max' },
{ model_identifier: 'qwen-plus', display_name: 'Qwen Plus' },
{ model_identifier: 'qwen-turbo', display_name: 'Qwen Turbo' }
];
setAvailableModels(prev => ({
...prev,
[provider]: fallbackModels
}));
if (provider === formData.proProvider) {
setFormData(prev => ({
...prev,
proModel: fallbackModels[0].model_identifier
}));
}
if (provider === formData.conProvider) {
setFormData(prev => ({
...prev,
conModel: fallbackModels[0].model_identifier
}));
}
}
} catch (error) {
console.error('Network error when fetching Qwen models from backend:', error);
// Use fallback models if network request fails
const fallbackModels = [
{ model_identifier: 'qwen-max', display_name: 'Qwen Max' },
{ model_identifier: 'qwen-plus', display_name: 'Qwen Plus' },
{ model_identifier: 'qwen-turbo', display_name: 'Qwen Turbo' }
];
setAvailableModels(prev => ({
...prev,
[provider]: fallbackModels
}));
if (provider === formData.proProvider) {
setFormData(prev => ({
...prev,
proModel: fallbackModels[0].model_identifier
}));
}
if (provider === formData.conProvider) {
setFormData(prev => ({
...prev,
conModel: fallbackModels[0].model_identifier
}));
}
}
} else {
// For other providers, use the backend API
const response = await fetch(`http://localhost:8000/models/${provider}`);
if (response.ok) {
const data = await response.json();
setAvailableModels(prev => ({
...prev,
[provider]: data.models || []
}));
// Update selected model if current selection is not in the new list
if (provider === formData.proProvider && data.models && data.models.length > 0) {
const currentModelExists = data.models.some(model => model.model_identifier === formData.proModel);
if (!currentModelExists) {
setFormData(prev => ({
...prev,
proModel: data.models[0].model_identifier
}));
}
}
if (provider === formData.conProvider && data.models && data.models.length > 0) {
const currentModelExists = data.models.some(model => model.model_identifier === formData.conModel);
if (!currentModelExists) {
setFormData(prev => ({
...prev,
conModel: data.models[0].model_identifier
}));
}
}
}
}
} catch (error) {
console.error(`Error loading models for ${provider}:`, error);
} finally {
setModelsLoading(prev => ({ ...prev, [provider]: false }));
}
};
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
// Validate inputs
if (!formData.topic.trim()) {
alert('请输入辩论主题');
setIsLoading(false);
return;
}
// Create debate request object
const debateRequest = {
topic: formData.topic,
participants: [
{
model_identifier: formData.proModel,
provider: formData.proProvider,
stance: "pro"
},
{
model_identifier: formData.conModel,
provider: formData.conProvider,
stance: "con"
}
],
constraints: {
max_rounds: parseInt(formData.maxRounds),
max_tokens_per_turn: parseInt(formData.maxTokens),
web_search_enabled: formData.webSearchEnabled,
web_search_mode: formData.webSearchMode
}
};
try {
const response = await fetch('http://localhost:8000/debate/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(debateRequest)
});
if (!response.ok) {
throw new Error(`创建辩论失败: ${response.statusText}`);
}
const result = await response.json();
alert(`辩论创建成功会话ID: ${result.session_id}`);
// Pass the session ID to the parent component
onCreateDebate(result.session_id);
} catch (error) {
console.error('Error creating debate:', error);
alert(`创建辩论失败: ${error.message}`);
} finally {
setIsLoading(false);
}
};
return (
<section className="section">
<h2>辩论配置</h2>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="topic" className="label">辩论主题:</label>
<input
type="text"
id="topic"
name="topic"
className="input"
value={formData.topic}
onChange={handleChange}
required
placeholder="请输入辩论主题"
/>
</div>
<div className="form-group">
<label className="label">参与模型:</label>
<div className="model-selection">
<div className="model-item">
<select
className="select"
name="proProvider"
value={formData.proProvider}
onChange={handleChange}
>
{availableProviders.map((provider) => (
<option key={provider.provider} value={provider.provider}>
{provider.display_name}
</option>
))}
</select>
{modelsLoading[formData.proProvider] ? (
<div className="input">加载模型中...</div>
) : (
<select
className="select"
name="proModel"
value={formData.proModel}
onChange={handleChange}
>
{availableModels[formData.proProvider]?.map((model) => (
<option key={model.model_identifier} value={model.model_identifier}>
{model.display_name || model.model_identifier}
</option>
))}
</select>
)}
<span className="stance-label">正方</span>
</div>
<div className="model-item">
<select
className="select"
name="conProvider"
value={formData.conProvider}
onChange={handleChange}
>
{availableProviders.map((provider) => (
<option key={provider.provider} value={provider.provider}>
{provider.display_name}
</option>
))}
</select>
{modelsLoading[formData.conProvider] ? (
<div className="input">加载模型中...</div>
) : (
<select
className="select"
name="conModel"
value={formData.conModel}
onChange={handleChange}
>
{availableModels[formData.conProvider]?.map((model) => (
<option key={model.model_identifier} value={model.model_identifier}>
{model.display_name || model.model_identifier}
</option>
))}
</select>
)}
<span className="stance-label">反方</span>
</div>
</div>
</div>
<div className="form-group">
<label htmlFor="maxRounds" className="label">最大轮数:</label>
<input
type="number"
id="maxRounds"
name="maxRounds"
className="input"
min="1"
max="10"
value={formData.maxRounds}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label htmlFor="maxTokens" className="label">每轮最大Token数:</label>
<input
type="number"
id="maxTokens"
name="maxTokens"
className="input"
min="100"
max="2000"
value={formData.maxTokens}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label className="label checkbox-label">
<input
type="checkbox"
name="webSearchEnabled"
checked={formData.webSearchEnabled}
onChange={handleChange}
/>
启用网络搜索
</label>
{formData.webSearchEnabled && (
<div className="search-mode-selector" style={{ marginTop: '0.5rem' }}>
<label htmlFor="webSearchMode" className="label">搜索模式:</label>
<select
className="select"
name="webSearchMode"
id="webSearchMode"
value={formData.webSearchMode}
onChange={handleChange}
>
<option value="auto">自动搜索 (每轮自动检索)</option>
<option value="tool">工具调用 (模型决定是否搜索)</option>
<option value="both">两者结合</option>
</select>
</div>
)}
</div>
<button
type="submit"
className={`button create-debate-button`}
disabled={isLoading || isGuest}
>
{isLoading ? '创建中...' : '创建辩论'}
</button>
{isGuest && (
<p style={{ color: '#e53e3e', marginTop: '0.5rem', textAlign: 'center' }}>
请先登录后再创建辩论
</p>
)}
</form>
<div style={{ marginTop: '1rem', textAlign: 'center' }}>
<button className="button view-sessions-button" onClick={onViewSessions}>
查看历史辩论
</button>
{onViewSettings && (
<button className="button view-sessions-button" style={{ marginLeft: '1rem' }} onClick={onViewSettings}>
设置 API 密钥
</button>
)}
</div>
</section>
);
};
export default DebateConfiguration;