fix: resolve TypeScript error in ComfyUIConfigModal handleInputChange function
- Updated handleInputChange function signature to accept undefined values - Fixes 'Argument of type number | undefined is not assignable' error on line 248 - Allows proper handling of optional numeric fields in ComfyuiConfig interface
This commit is contained in:
parent
42ae580034
commit
dccbb7cda6
|
|
@ -0,0 +1,335 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Save, TestTube, AlertCircle, CheckCircle } from 'lucide-react';
|
||||
import ComfyuiService from '../services/comfyuiService';
|
||||
import type { ComfyuiConfig } from '../types/comfyui';
|
||||
|
||||
interface ComfyUIConfigModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
currentConfig: ComfyuiConfig;
|
||||
onConfigUpdate: (config: ComfyuiConfig) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* ComfyUI 配置设置模态框
|
||||
*/
|
||||
const ComfyUIConfigModal: React.FC<ComfyUIConfigModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
currentConfig,
|
||||
onConfigUpdate,
|
||||
}) => {
|
||||
const [config, setConfig] = useState<ComfyuiConfig>(currentConfig);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setConfig(currentConfig);
|
||||
setTestResult(null);
|
||||
setErrors({});
|
||||
}
|
||||
}, [isOpen, currentConfig]);
|
||||
|
||||
// ============================================================================
|
||||
// 表单验证
|
||||
// ============================================================================
|
||||
|
||||
const validateConfig = (configToValidate: ComfyuiConfig): Record<string, string> => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
// 验证 base_url
|
||||
if (!configToValidate.base_url.trim()) {
|
||||
newErrors.base_url = 'API 基础 URL 不能为空';
|
||||
} else {
|
||||
try {
|
||||
new URL(configToValidate.base_url);
|
||||
} catch {
|
||||
newErrors.base_url = '请输入有效的 URL 格式';
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 timeout
|
||||
if (configToValidate.timeout !== undefined) {
|
||||
if (configToValidate.timeout < 1 || configToValidate.timeout > 300) {
|
||||
newErrors.timeout = '超时时间应在 1-300 秒之间';
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 retry_attempts
|
||||
if (configToValidate.retry_attempts !== undefined) {
|
||||
if (configToValidate.retry_attempts < 0 || configToValidate.retry_attempts > 10) {
|
||||
newErrors.retry_attempts = '重试次数应在 0-10 次之间';
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 max_concurrency
|
||||
if (configToValidate.max_concurrency !== undefined) {
|
||||
if (configToValidate.max_concurrency < 1 || configToValidate.max_concurrency > 50) {
|
||||
newErrors.max_concurrency = '最大并发数应在 1-50 之间';
|
||||
}
|
||||
}
|
||||
|
||||
return newErrors;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 事件处理
|
||||
// ============================================================================
|
||||
|
||||
const handleInputChange = (field: keyof ComfyuiConfig, value: string | number | boolean | undefined) => {
|
||||
setConfig(prev => ({ ...prev, [field]: value }));
|
||||
|
||||
// 清除对应字段的错误
|
||||
if (errors[field]) {
|
||||
setErrors(prev => {
|
||||
const newErrors = { ...prev };
|
||||
delete newErrors[field];
|
||||
return newErrors;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestConnection = async () => {
|
||||
const validationErrors = validateConfig(config);
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
setErrors(validationErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
setTesting(true);
|
||||
setTestResult(null);
|
||||
|
||||
try {
|
||||
// 临时更新配置进行测试
|
||||
await ComfyuiService.updateConfig(config);
|
||||
const isConnected = await ComfyuiService.testConnection();
|
||||
|
||||
setTestResult({
|
||||
success: isConnected,
|
||||
message: isConnected ? '连接成功!' : '连接失败,请检查配置',
|
||||
});
|
||||
} catch (error) {
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: `连接测试失败: ${error}`,
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const validationErrors = validateConfig(config);
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
setErrors(validationErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
await ComfyuiService.updateConfig(config);
|
||||
onConfigUpdate(config);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: `保存配置失败: ${error}`,
|
||||
});
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
const defaultConfig: ComfyuiConfig = {
|
||||
base_url: 'https://bowongai-dev--waas-demo-fastapi-webapp.modal.run',
|
||||
timeout: 30,
|
||||
retry_attempts: 3,
|
||||
enable_cache: true,
|
||||
max_concurrency: 8,
|
||||
};
|
||||
setConfig(defaultConfig);
|
||||
setErrors({});
|
||||
setTestResult(null);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg shadow-xl w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
|
||||
{/* 标题栏 */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<h2 className="text-xl font-semibold text-gray-900">ComfyUI 配置设置</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 表单内容 */}
|
||||
<div className="p-6 space-y-6">
|
||||
{/* API 基础 URL */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
API 基础 URL *
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
value={config.base_url}
|
||||
onChange={(e) => handleInputChange('base_url', e.target.value)}
|
||||
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||
errors.base_url ? 'border-red-300' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="https://api.example.com"
|
||||
/>
|
||||
{errors.base_url && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.base_url}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 超时设置 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
请求超时时间(秒)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="300"
|
||||
value={config.timeout || ''}
|
||||
onChange={(e) => handleInputChange('timeout', parseInt(e.target.value) || undefined)}
|
||||
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||
errors.timeout ? 'border-red-300' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="30"
|
||||
/>
|
||||
{errors.timeout && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.timeout}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 重试次数 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
重试次数
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="10"
|
||||
value={config.retry_attempts || ''}
|
||||
onChange={(e) => handleInputChange('retry_attempts', parseInt(e.target.value) || undefined)}
|
||||
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||
errors.retry_attempts ? 'border-red-300' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="3"
|
||||
/>
|
||||
{errors.retry_attempts && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.retry_attempts}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 最大并发数 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
最大并发数
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="50"
|
||||
value={config.max_concurrency || ''}
|
||||
onChange={(e) => handleInputChange('max_concurrency', parseInt(e.target.value) || undefined)}
|
||||
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||
errors.max_concurrency ? 'border-red-300' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="8"
|
||||
/>
|
||||
{errors.max_concurrency && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.max_concurrency}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 启用缓存 */}
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="enable_cache"
|
||||
checked={config.enable_cache || false}
|
||||
onChange={(e) => handleInputChange('enable_cache', e.target.checked)}
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label htmlFor="enable_cache" className="ml-2 block text-sm text-gray-700">
|
||||
启用缓存
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 测试结果 */}
|
||||
{testResult && (
|
||||
<div className={`p-4 rounded-md ${
|
||||
testResult.success ? 'bg-green-50 border border-green-200' : 'bg-red-50 border border-red-200'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
{testResult.success ? (
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
) : (
|
||||
<AlertCircle className="w-5 h-5 text-red-500" />
|
||||
)}
|
||||
<span className={`text-sm ${
|
||||
testResult.success ? 'text-green-700' : 'text-red-700'
|
||||
}`}>
|
||||
{testResult.message}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex items-center justify-between p-6 border-t border-gray-200">
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors"
|
||||
>
|
||||
重置为默认值
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={handleTestConnection}
|
||||
disabled={testing}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-yellow-600 text-white rounded-md hover:bg-yellow-700 transition-colors disabled:opacity-50"
|
||||
>
|
||||
<TestTube className={`w-4 h-4 ${testing ? 'animate-pulse' : ''}`} />
|
||||
{testing ? '测试中...' : '测试连接'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50"
|
||||
>
|
||||
<Save className={`w-4 h-4 ${saving ? 'animate-pulse' : ''}`} />
|
||||
{saving ? '保存中...' : '保存'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComfyUIConfigModal;
|
||||
Loading…
Reference in New Issue