Merge branch 'main' of https://git.igny8.com/salman/igny8
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',
|
||||
@@ -119,18 +125,27 @@ export default function Integration() {
|
||||
const validateIntegration = useCallback(async (
|
||||
integrationId: string,
|
||||
enabled: boolean,
|
||||
apiKey?: string,
|
||||
model?: string
|
||||
) => {
|
||||
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
|
||||
// If disabled, mark as not_configured (not error!)
|
||||
if (!enabled) {
|
||||
// Not configured or disabled - set status accordingly
|
||||
setValidationStatuses(prev => ({
|
||||
...prev,
|
||||
[integrationId]: 'not_configured',
|
||||
@@ -138,38 +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 (uses platform API key)
|
||||
// Test connection asynchronously - send empty body, backend will use global settings
|
||||
try {
|
||||
// Build request body based on integration type
|
||||
const requestBody: any = {};
|
||||
|
||||
// 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',
|
||||
@@ -184,8 +190,8 @@ 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;
|
||||
|
||||
@@ -193,7 +199,7 @@ export default function Integration() {
|
||||
const model = integration.model;
|
||||
|
||||
// Validate with current state (fire and forget - don't await)
|
||||
validateIntegration(id, enabled, model);
|
||||
validateIntegration(id, enabled, undefined, model);
|
||||
});
|
||||
|
||||
// Return unchanged - we're just reading state
|
||||
@@ -201,9 +207,23 @@ 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)
|
||||
@@ -221,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.enabled, integrations.runware.enabled, integrations.openai.model, integrations.runware.model]);
|
||||
|
||||
const loadIntegrationSettings = async () => {
|
||||
try {
|
||||
@@ -300,12 +320,12 @@ export default function Integration() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Test uses platform API key (no apiKey parameter needed)
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So data is the extracted response payload
|
||||
const data = await fetchAPI(`/v1/system/settings/integrations/${selectedIntegration}/test/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
apiKey,
|
||||
config: config,
|
||||
}),
|
||||
});
|
||||
@@ -411,24 +431,22 @@ export default function Integration() {
|
||||
if (integrationId === 'openai') {
|
||||
return [
|
||||
{ label: 'App Name', value: 'OpenAI API' },
|
||||
{ label: 'Model', value: config.model || 'gpt-4o-mini' },
|
||||
{ label: 'Status', value: config.using_global ? 'Using platform defaults' : 'Custom settings' },
|
||||
{ label: 'Model', value: config.model || 'Not set' },
|
||||
];
|
||||
} else if (integrationId === 'runware') {
|
||||
return [
|
||||
{ label: 'App Name', value: 'Runware API' },
|
||||
{ label: 'Status', value: config.using_global ? 'Using platform defaults' : 'Custom settings' },
|
||||
{ label: 'Model', value: config.model || 'Not set' },
|
||||
];
|
||||
} else if (integrationId === 'image_generation') {
|
||||
const service = config.service || 'openai';
|
||||
const modelDisplay = service === 'openai'
|
||||
? (config.model || config.imageModel || 'dall-e-3')
|
||||
: (config.runwareModel || 'runware:97@1');
|
||||
? (config.model || 'Not set')
|
||||
: (config.runwareModel || 'Not set');
|
||||
|
||||
return [
|
||||
{ label: 'Service', value: service === 'openai' ? 'OpenAI DALL-E' : 'Runware' },
|
||||
{ label: 'Service', value: service === 'openai' ? 'OpenAI' : 'Runware' },
|
||||
{ label: 'Model', value: modelDisplay },
|
||||
{ label: 'Status', value: config.using_global ? 'Using platform defaults' : 'Custom settings' },
|
||||
];
|
||||
}
|
||||
return [];
|
||||
@@ -470,25 +488,45 @@ export default function Integration() {
|
||||
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 [
|
||||
// Runware doesn't have model selection, just using platform API key
|
||||
{
|
||||
key: 'model',
|
||||
label: 'Runware Model',
|
||||
type: 'select',
|
||||
value: config.model || 'runware:97@1',
|
||||
onChange: (value) => {
|
||||
setIntegrations({
|
||||
...integrations,
|
||||
[integrationId]: { ...config, model: value },
|
||||
});
|
||||
},
|
||||
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') {
|
||||
const service = config.service || 'openai';
|
||||
@@ -531,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({
|
||||
@@ -551,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' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -867,20 +906,13 @@ 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 (
|
||||
<>
|
||||
<PageMeta title="API Integration - IGNY8" description="External integrations" />
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Platform API Keys Info */}
|
||||
<Alert
|
||||
variant="info"
|
||||
title="Platform API Keys"
|
||||
message="API keys are managed at the platform level by administrators. You can customize which AI models and parameters to use for your account. Free plan users can view settings but cannot customize them."
|
||||
/>
|
||||
|
||||
{/* Integration Cards with Validation Cards */}
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{/* OpenAI Integration + Validation */}
|
||||
@@ -893,10 +925,12 @@ export default function Integration() {
|
||||
integrationId="openai"
|
||||
onToggleSuccess={(enabled, data) => {
|
||||
// Refresh status circle when toggle changes
|
||||
// Use API key from hook's data (most up-to-date) or fallback to integrations state
|
||||
const apiKey = data?.apiKey || integrations.openai.apiKey;
|
||||
const model = data?.model || integrations.openai.model;
|
||||
|
||||
// Validate with current enabled state and model
|
||||
validateIntegration('openai', enabled, model);
|
||||
// Validate with current enabled state and API key
|
||||
validateIntegration('openai', enabled, apiKey, model);
|
||||
}}
|
||||
onSettings={() => handleSettings('openai')}
|
||||
onDetails={() => handleDetails('openai')}
|
||||
@@ -918,22 +952,25 @@ 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
|
||||
}
|
||||
onToggleSuccess={(enabled, data) => {
|
||||
// Refresh status circle when toggle changes
|
||||
// Validate with current enabled state
|
||||
validateIntegration('runware', enabled);
|
||||
// Use API key from hook's data (most up-to-date) or fallback to integrations state
|
||||
const apiKey = data?.apiKey || integrations.runware.apiKey;
|
||||
|
||||
// Validate with current enabled state and API key
|
||||
validateIntegration('runware', enabled, apiKey);
|
||||
}}
|
||||
onSettings={() => handleSettings('runware')}
|
||||
onDetails={() => handleDetails('runware')}
|
||||
@@ -960,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')}
|
||||
/>
|
||||
@@ -967,7 +1010,11 @@ export default function Integration() {
|
||||
<Alert
|
||||
variant="info"
|
||||
title="AI Integration & Image Generation Testing"
|
||||
message="Test your AI integrations and image generation on this page. The platform provides API keys - you can customize model preferences and parameters based on your plan. Test connections to verify everything is working correctly."
|
||||
message="Configure and test your AI integrations on this page.
|
||||
Set up OpenAI and Runware API keys, validate connections, and test image generation with different models and parameters.
|
||||
Before you start, please read the documentation for each integration.
|
||||
|
||||
Make sure to use the correct API keys and models for each integration."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1053,7 +1100,7 @@ export default function Integration() {
|
||||
onClick={() => {
|
||||
handleTestConnection();
|
||||
}}
|
||||
disabled={isTesting || isSaving}
|
||||
disabled={isTesting || isSaving || !integrations[selectedIntegration]?.apiKey}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{isTesting ? 'Testing...' : 'Test Connection'}
|
||||
|
||||
Reference in New Issue
Block a user