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