lot of messs
This commit is contained in:
@@ -47,11 +47,7 @@ const GSCIcon = () => (
|
||||
interface IntegrationConfig {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
apiKey?: string;
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
authBaseUri?: string;
|
||||
appName?: string;
|
||||
// Note: API keys are configured platform-wide in GlobalIntegrationSettings (not user-editable)
|
||||
model?: string;
|
||||
// Image generation service settings (separate from API integrations)
|
||||
service?: string; // 'openai' or 'runware'
|
||||
@@ -74,13 +70,12 @@ export default function Integration() {
|
||||
openai: {
|
||||
id: 'openai',
|
||||
enabled: false,
|
||||
apiKey: '',
|
||||
model: 'gpt-4.1',
|
||||
model: 'gpt-4o-mini',
|
||||
},
|
||||
runware: {
|
||||
id: 'runware',
|
||||
enabled: false,
|
||||
apiKey: '',
|
||||
model: 'runware:97@1',
|
||||
},
|
||||
image_generation: {
|
||||
id: 'image_generation',
|
||||
@@ -105,6 +100,17 @@ export default function Integration() {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
|
||||
// Available models from AIModelConfig
|
||||
const [availableModels, setAvailableModels] = useState<{
|
||||
openai_text: Array<{ value: string; label: string }>;
|
||||
openai_image: Array<{ value: string; label: string }>;
|
||||
runware_image: Array<{ value: string; label: string }>;
|
||||
}>({
|
||||
openai_text: [],
|
||||
openai_image: [],
|
||||
runware_image: [],
|
||||
});
|
||||
|
||||
// Validation status for each integration: 'not_configured' | 'pending' | 'success' | 'error'
|
||||
const [validationStatuses, setValidationStatuses] = useState<Record<string, 'not_configured' | 'pending' | 'success' | 'error'>>({
|
||||
openai: 'not_configured',
|
||||
@@ -124,16 +130,22 @@ export default function Integration() {
|
||||
) => {
|
||||
const API_BASE_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.igny8.com/api';
|
||||
|
||||
// Only validate OpenAI and Runware (GSC doesn't have validation endpoint)
|
||||
// Image generation doesn't have a test endpoint - just set status based on enabled
|
||||
if (integrationId === 'image_generation') {
|
||||
setValidationStatuses(prev => ({
|
||||
...prev,
|
||||
[integrationId]: enabled ? 'success' : 'not_configured',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Only validate OpenAI and Runware (they have test endpoints)
|
||||
if (!['openai', 'runware'].includes(integrationId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if integration is enabled and has API key configured
|
||||
const hasApiKey = apiKey && apiKey.trim() !== '';
|
||||
|
||||
if (!hasApiKey || !enabled) {
|
||||
// Not configured or disabled - set status accordingly
|
||||
// If disabled, mark as not_configured (not error!)
|
||||
if (!enabled) {
|
||||
setValidationStatuses(prev => ({
|
||||
...prev,
|
||||
[integrationId]: 'not_configured',
|
||||
@@ -141,40 +153,29 @@ export default function Integration() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set pending status
|
||||
// Integration is enabled - test the connection
|
||||
// Set pending status while testing
|
||||
setValidationStatuses(prev => ({
|
||||
...prev,
|
||||
[integrationId]: 'pending',
|
||||
}));
|
||||
|
||||
// Test connection asynchronously
|
||||
// Test connection asynchronously - send empty body, backend will use global settings
|
||||
try {
|
||||
// Build request body based on integration type
|
||||
const requestBody: any = {
|
||||
apiKey: apiKey,
|
||||
};
|
||||
|
||||
// OpenAI needs model in config, Runware doesn't
|
||||
if (integrationId === 'openai') {
|
||||
requestBody.config = {
|
||||
model: model || 'gpt-4.1',
|
||||
with_response: false, // Simple connection test for status validation
|
||||
};
|
||||
}
|
||||
|
||||
const data = await fetchAPI(`/v1/system/settings/integrations/${integrationId}/test/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(requestBody),
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
// fetchAPI extracts the data field and throws on error
|
||||
// If we get here without error, validation was successful
|
||||
console.log(`✅ Validation successful for ${integrationId}`);
|
||||
setValidationStatuses(prev => ({
|
||||
...prev,
|
||||
[integrationId]: 'success',
|
||||
}));
|
||||
} catch (error: any) {
|
||||
console.error(`Error validating ${integrationId}:`, error);
|
||||
console.error(`❌ Validation failed for ${integrationId}:`, error);
|
||||
setValidationStatuses(prev => ({
|
||||
...prev,
|
||||
[integrationId]: 'error',
|
||||
@@ -189,17 +190,16 @@ export default function Integration() {
|
||||
const validateEnabledIntegrations = useCallback(async () => {
|
||||
// Use functional update to read latest state without adding dependencies
|
||||
setIntegrations((currentIntegrations) => {
|
||||
// Validate each integration
|
||||
['openai', 'runware'].forEach((id) => {
|
||||
// Validate each integration (including image_generation)
|
||||
['openai', 'runware', 'image_generation'].forEach((id) => {
|
||||
const integration = currentIntegrations[id];
|
||||
if (!integration) return;
|
||||
|
||||
const enabled = integration.enabled === true;
|
||||
const apiKey = integration.apiKey;
|
||||
const model = integration.model;
|
||||
|
||||
// Validate with current state (fire and forget - don't await)
|
||||
validateIntegration(id, enabled, apiKey, model);
|
||||
validateIntegration(id, enabled, undefined, model);
|
||||
});
|
||||
|
||||
// Return unchanged - we're just reading state
|
||||
@@ -207,16 +207,30 @@ export default function Integration() {
|
||||
});
|
||||
}, [validateIntegration]);
|
||||
|
||||
// Load integration settings on mount
|
||||
// Load available models from backend
|
||||
const loadAvailableModels = async () => {
|
||||
try {
|
||||
const data = await fetchAPI('/v1/system/settings/integrations/available-models/');
|
||||
if (data) {
|
||||
setAvailableModels(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading available models:', error);
|
||||
// Keep default empty arrays
|
||||
}
|
||||
};
|
||||
|
||||
// Load integration settings and available models on mount
|
||||
useEffect(() => {
|
||||
loadIntegrationSettings();
|
||||
loadAvailableModels();
|
||||
}, []);
|
||||
|
||||
// Validate integrations after settings are loaded or changed (debounced to prevent excessive validation)
|
||||
useEffect(() => {
|
||||
// Only validate if integrations have been loaded (not initial empty state)
|
||||
const hasLoadedData = Object.values(integrations).some(integ =>
|
||||
integ.apiKey !== undefined || integ.enabled !== undefined
|
||||
integ.enabled !== undefined
|
||||
);
|
||||
if (!hasLoadedData) return;
|
||||
|
||||
@@ -227,7 +241,7 @@ export default function Integration() {
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [integrations.openai.enabled, integrations.runware.enabled, integrations.openai.apiKey, integrations.runware.apiKey]);
|
||||
}, [integrations.openai.enabled, integrations.runware.enabled, integrations.openai.model, integrations.runware.model]);
|
||||
|
||||
const loadIntegrationSettings = async () => {
|
||||
try {
|
||||
@@ -294,12 +308,6 @@ export default function Integration() {
|
||||
}
|
||||
|
||||
const config = integrations[selectedIntegration];
|
||||
const apiKey = config.apiKey;
|
||||
|
||||
if (!apiKey) {
|
||||
toast.error('Please enter an API key first');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsTesting(true);
|
||||
|
||||
@@ -423,13 +431,12 @@ export default function Integration() {
|
||||
if (integrationId === 'openai') {
|
||||
return [
|
||||
{ label: 'App Name', value: 'OpenAI API' },
|
||||
{ label: 'API Key', value: config.apiKey ? `${config.apiKey.substring(0, 20)}...` : 'Not configured' },
|
||||
{ label: 'Model', value: config.model || 'Not set' },
|
||||
];
|
||||
} else if (integrationId === 'runware') {
|
||||
return [
|
||||
{ label: 'App Name', value: 'Runware API' },
|
||||
{ label: 'API Key', value: config.apiKey ? `${config.apiKey.substring(0, 20)}...` : 'Not configured' },
|
||||
{ label: 'Model', value: config.model || 'Not set' },
|
||||
];
|
||||
} else if (integrationId === 'image_generation') {
|
||||
const service = config.service || 'openai';
|
||||
@@ -477,55 +484,48 @@ export default function Integration() {
|
||||
|
||||
if (integrationId === 'openai') {
|
||||
return [
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'OpenAI API Key',
|
||||
type: 'password',
|
||||
value: config.apiKey || '',
|
||||
onChange: (value) => {
|
||||
setIntegrations({
|
||||
...integrations,
|
||||
[integrationId]: { ...config, apiKey: value },
|
||||
});
|
||||
},
|
||||
placeholder: 'Enter your OpenAI API key',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
label: 'AI Model',
|
||||
type: 'select',
|
||||
value: config.model || 'gpt-4.1',
|
||||
value: config.model || 'gpt-4o-mini',
|
||||
onChange: (value) => {
|
||||
setIntegrations({
|
||||
...integrations,
|
||||
[integrationId]: { ...config, model: value },
|
||||
});
|
||||
},
|
||||
options: [
|
||||
{ value: 'gpt-4.1', label: 'GPT-4.1 - $2.00 / $8.00 per 1M tokens' },
|
||||
{ value: 'gpt-4o-mini', label: 'GPT-4o mini - $0.15 / $0.60 per 1M tokens' },
|
||||
{ value: 'gpt-4o', label: 'GPT-4o - $2.50 / $10.00 per 1M tokens' },
|
||||
{ value: 'gpt-5.1', label: 'GPT-5.1 - $1.25 / $10.00 per 1M tokens (16K)' },
|
||||
{ value: 'gpt-5.2', label: 'GPT-5.2 - $1.75 / $14.00 per 1M tokens (16K)' },
|
||||
],
|
||||
options: availableModels?.openai_text?.length > 0
|
||||
? availableModels.openai_text
|
||||
: [
|
||||
{ value: 'gpt-4.1', label: 'GPT-4.1 - $2.00 / $8.00 per 1M tokens' },
|
||||
{ value: 'gpt-4o-mini', label: 'GPT-4o mini - $0.15 / $0.60 per 1M tokens' },
|
||||
{ value: 'gpt-4o', label: 'GPT-4o - $2.50 / $10.00 per 1M tokens' },
|
||||
{ value: 'gpt-5.1', label: 'GPT-5.1 - $1.25 / $10.00 per 1M tokens (16K)' },
|
||||
{ value: 'gpt-5.2', label: 'GPT-5.2 - $1.75 / $14.00 per 1M tokens (16K)' },
|
||||
],
|
||||
},
|
||||
];
|
||||
} else if (integrationId === 'runware') {
|
||||
return [
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'Runware API Key',
|
||||
type: 'password',
|
||||
value: config.apiKey || '',
|
||||
key: 'model',
|
||||
label: 'Runware Model',
|
||||
type: 'select',
|
||||
value: config.model || 'runware:97@1',
|
||||
onChange: (value) => {
|
||||
setIntegrations({
|
||||
...integrations,
|
||||
[integrationId]: { ...config, apiKey: value },
|
||||
[integrationId]: { ...config, model: value },
|
||||
});
|
||||
},
|
||||
placeholder: 'Enter your Runware API key',
|
||||
required: true,
|
||||
options: availableModels?.runware_image?.length > 0
|
||||
? availableModels.runware_image
|
||||
: [
|
||||
{ value: 'runware:97@1', label: 'Runware 97@1 - Versatile Model' },
|
||||
{ value: 'runware:100@1', label: 'Runware 100@1 - High Quality' },
|
||||
{ value: 'runware:101@1', label: 'Runware 101@1 - Fast Generation' },
|
||||
],
|
||||
},
|
||||
];
|
||||
} else if (integrationId === 'image_generation') {
|
||||
@@ -569,13 +569,12 @@ export default function Integration() {
|
||||
[integrationId]: { ...config, model: value },
|
||||
});
|
||||
},
|
||||
options: [
|
||||
{ value: 'dall-e-3', label: 'DALL·E 3 - $0.040 per image' },
|
||||
{ value: 'dall-e-2', label: 'DALL·E 2 - $0.020 per image' },
|
||||
// Note: gpt-image-1 and gpt-image-1-mini are not valid for OpenAI's /v1/images/generations endpoint
|
||||
// They are not currently supported by OpenAI's image generation API
|
||||
// Only dall-e-3 and dall-e-2 are supported
|
||||
],
|
||||
options: availableModels?.openai_image?.length > 0
|
||||
? availableModels.openai_image
|
||||
: [
|
||||
{ value: 'dall-e-3', label: 'DALL·E 3 - $0.040 per image' },
|
||||
{ value: 'dall-e-2', label: 'DALL·E 2 - $0.020 per image' },
|
||||
],
|
||||
});
|
||||
} else if (service === 'runware') {
|
||||
fields.push({
|
||||
@@ -589,11 +588,13 @@ export default function Integration() {
|
||||
[integrationId]: { ...config, runwareModel: value },
|
||||
});
|
||||
},
|
||||
options: [
|
||||
{ value: 'runware:97@1', label: 'HiDream-I1 Full - $0.009 per image' },
|
||||
{ value: 'runware:gen3a_turbo', label: 'Gen3a Turbo - $0.009 per image' },
|
||||
{ value: 'runware:gen3a', label: 'Gen3a - $0.009 per image' },
|
||||
],
|
||||
options: availableModels?.runware_image?.length > 0
|
||||
? availableModels.runware_image
|
||||
: [
|
||||
{ value: 'runware:97@1', label: 'HiDream-I1 Full - $0.009 per image' },
|
||||
{ value: 'runware:100@1', label: 'Runware 100@1 - High Quality' },
|
||||
{ value: 'runware:101@1', label: 'Runware 101@1 - Fast Generation' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -905,7 +906,7 @@ export default function Integration() {
|
||||
console.error('Error rendering image generation form:', error);
|
||||
return <div className="text-error-500">Error loading form. Please refresh the page.</div>;
|
||||
}
|
||||
}, [selectedIntegration, integrations, showSettingsModal, getSettingsFields]);
|
||||
}, [selectedIntegration, integrations, showSettingsModal, availableModels]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -951,15 +952,15 @@ export default function Integration() {
|
||||
validationStatus={validationStatuses.runware}
|
||||
integrationId="runware"
|
||||
modelName={
|
||||
integrations.image_generation?.service === 'runware' && integrations.image_generation.runwareModel
|
||||
integrations.runware?.enabled && integrations.runware?.model
|
||||
? (() => {
|
||||
// Map model ID to display name
|
||||
const modelDisplayNames: Record<string, string> = {
|
||||
'runware:97@1': 'HiDream-I1 Full',
|
||||
'runware:gen3a_turbo': 'Gen3a Turbo',
|
||||
'runware:gen3a': 'Gen3a',
|
||||
'runware:100@1': 'Runware 100@1',
|
||||
'runware:101@1': 'Runware 101@1',
|
||||
};
|
||||
return modelDisplayNames[integrations.image_generation.runwareModel] || integrations.image_generation.runwareModel;
|
||||
return modelDisplayNames[integrations.runware.model] || integrations.runware.model;
|
||||
})()
|
||||
: undefined
|
||||
}
|
||||
@@ -996,6 +997,12 @@ export default function Integration() {
|
||||
title="Image Generation Service"
|
||||
description="Default image generation service and model selection for app-wide use"
|
||||
validationStatus={validationStatuses.image_generation}
|
||||
onToggleSuccess={(enabled, data) => {
|
||||
// Validate when toggle changes - same pattern as openai/runware
|
||||
const provider = data?.provider || data?.service || 'openai';
|
||||
const model = data?.model || (provider === 'openai' ? 'dall-e-3' : 'runware:97@1');
|
||||
validateIntegration('image_generation', enabled, null, model);
|
||||
}}
|
||||
onSettings={() => handleSettings('image_generation')}
|
||||
onDetails={() => handleDetails('image_generation')}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user