ui: auto update card upon config (#1610)
This commit is contained in:
@@ -11,9 +11,9 @@ use http::{HeaderMap, StatusCode};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
use crate::routes::utils::check_provider_configured;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
fn verify_secret_key(headers: &HeaderMap, state: &AppState) -> Result<StatusCode, StatusCode> {
|
fn verify_secret_key(headers: &HeaderMap, state: &AppState) -> Result<StatusCode, StatusCode> {
|
||||||
@@ -123,7 +123,7 @@ pub async fn remove_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post, // Change from get to post
|
post,
|
||||||
path = "/config/read",
|
path = "/config/read",
|
||||||
request_body = ConfigKeyQuery, // Switch back to request_body
|
request_body = ConfigKeyQuery, // Switch back to request_body
|
||||||
responses(
|
responses(
|
||||||
@@ -335,31 +335,6 @@ pub async fn providers(
|
|||||||
Ok(Json(providers_response))
|
Ok(Json(providers_response))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_provider_configured(metadata: &ProviderMetadata) -> bool {
|
|
||||||
let config = Config::global();
|
|
||||||
|
|
||||||
// Check all required keys for the provider
|
|
||||||
for key in &metadata.config_keys {
|
|
||||||
if key.required {
|
|
||||||
let key_name = &key.name;
|
|
||||||
|
|
||||||
// First, check if the key is set in the environment
|
|
||||||
let is_set_in_env = env::var(key_name).is_ok();
|
|
||||||
|
|
||||||
// If not set in environment, check the config file based on whether it's a secret or not
|
|
||||||
let is_set_in_config = config.get(key_name, key.secret).is_ok();
|
|
||||||
|
|
||||||
// If the key is neither in the environment nor in the config, the provider is not configured
|
|
||||||
if !is_set_in_env && !is_set_in_config {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all required keys are accounted for, the provider is considered configured
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn routes(state: AppState) -> Router {
|
pub fn routes(state: AppState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/config", get(read_all_config))
|
.route("/config", get(read_all_config))
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub mod extension;
|
|||||||
pub mod health;
|
pub mod health;
|
||||||
pub mod reply;
|
pub mod reply;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
pub mod utils;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
|
||||||
// Function to configure all routes
|
// Function to configure all routes
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::error::Error;
|
|
||||||
use goose::config::Config;
|
use goose::config::Config;
|
||||||
|
use goose::providers::base::{ConfigKey, ProviderMetadata};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::env;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum KeyLocation {
|
pub enum KeyLocation {
|
||||||
Environment,
|
Environment,
|
||||||
ConfigFile,
|
ConfigFile,
|
||||||
Keychain,
|
Keychain,
|
||||||
NotFound
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -20,10 +22,8 @@ pub struct KeyInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Inspects a configuration key to determine if it's set, its location, and value (for non-secret keys)
|
/// Inspects a configuration key to determine if it's set, its location, and value (for non-secret keys)
|
||||||
pub fn inspect_key(
|
#[allow(dead_code)]
|
||||||
key_name: &str,
|
pub fn inspect_key(key_name: &str, is_secret: bool) -> Result<KeyInfo, Box<dyn Error>> {
|
||||||
is_secret: bool,
|
|
||||||
) -> Result<KeyInfo, Box<dyn Error>> {
|
|
||||||
let config = Config::global();
|
let config = Config::global();
|
||||||
|
|
||||||
// Check environment variable first
|
// Check environment variable first
|
||||||
@@ -44,7 +44,7 @@ pub fn inspect_key(
|
|||||||
let config_result = if is_secret {
|
let config_result = if is_secret {
|
||||||
config.get_secret(key_name).map(|v| (v, true))
|
config.get_secret(key_name).map(|v| (v, true))
|
||||||
} else {
|
} else {
|
||||||
config.get(key_name).map(|v| (v, false))
|
config.get_param(key_name).map(|v| (v, false))
|
||||||
};
|
};
|
||||||
|
|
||||||
match config_result {
|
match config_result {
|
||||||
@@ -64,20 +64,19 @@ pub fn inspect_key(
|
|||||||
// Only include value for non-secret keys
|
// Only include value for non-secret keys
|
||||||
value: if !is_secret_actual { Some(value) } else { None },
|
value: if !is_secret_actual { Some(value) } else { None },
|
||||||
})
|
})
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
Ok(KeyInfo {
|
|
||||||
name: key_name.to_string(),
|
|
||||||
is_set: false,
|
|
||||||
location: KeyLocation::NotFound,
|
|
||||||
is_secret,
|
|
||||||
value: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
Err(_) => Ok(KeyInfo {
|
||||||
|
name: key_name.to_string(),
|
||||||
|
is_set: false,
|
||||||
|
location: KeyLocation::NotFound,
|
||||||
|
is_secret,
|
||||||
|
value: None,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspects multiple keys at once
|
/// Inspects multiple keys at once
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn inspect_keys(
|
pub fn inspect_keys(
|
||||||
keys: &[(String, bool)], // (name, is_secret) pairs
|
keys: &[(String, bool)], // (name, is_secret) pairs
|
||||||
) -> Result<Vec<KeyInfo>, Box<dyn Error>> {
|
) -> Result<Vec<KeyInfo>, Box<dyn Error>> {
|
||||||
@@ -89,4 +88,53 @@ pub fn inspect_keys(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_provider_configured(metadata: &ProviderMetadata) -> bool {
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
// Get all required keys
|
||||||
|
let required_keys: Vec<&ConfigKey> = metadata
|
||||||
|
.config_keys
|
||||||
|
.iter()
|
||||||
|
.filter(|key| key.required)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Special case: If a provider has exactly one required key and that key
|
||||||
|
// has a default value, check if it's explicitly set
|
||||||
|
if required_keys.len() == 1 && required_keys[0].default.is_some() {
|
||||||
|
let key = &required_keys[0];
|
||||||
|
|
||||||
|
// Check if the key is explicitly set (either in env or config)
|
||||||
|
let is_set_in_env = env::var(&key.name).is_ok();
|
||||||
|
let is_set_in_config = config.get(&key.name, key.secret).is_ok();
|
||||||
|
|
||||||
|
return is_set_in_env || is_set_in_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For providers with multiple keys or keys without defaults:
|
||||||
|
// Find required keys that don't have default values
|
||||||
|
let required_non_default_keys: Vec<&ConfigKey> = required_keys
|
||||||
|
.iter()
|
||||||
|
.filter(|key| key.default.is_none())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// If there are no non-default keys, this provider needs at least one key explicitly set
|
||||||
|
if required_non_default_keys.is_empty() {
|
||||||
|
return required_keys.iter().any(|key| {
|
||||||
|
let is_set_in_env = env::var(&key.name).is_ok();
|
||||||
|
let is_set_in_config = config.get(&key.name, key.secret).is_ok();
|
||||||
|
|
||||||
|
is_set_in_env || is_set_in_config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, all non-default keys must be set
|
||||||
|
required_non_default_keys.iter().all(|key| {
|
||||||
|
let is_set_in_env = env::var(&key.name).is_ok();
|
||||||
|
let is_set_in_config = config.get(&key.name, key.secret).is_ok();
|
||||||
|
|
||||||
|
is_set_in_env || is_set_in_config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ const GridLayout = memo(function GridLayout({ children }: { children: React.Reac
|
|||||||
const ProviderCards = memo(function ProviderCards({
|
const ProviderCards = memo(function ProviderCards({
|
||||||
providers,
|
providers,
|
||||||
isOnboarding,
|
isOnboarding,
|
||||||
|
refreshProviders,
|
||||||
}: {
|
}: {
|
||||||
providers: ProviderDetails[];
|
providers: ProviderDetails[];
|
||||||
isOnboarding: boolean;
|
isOnboarding: boolean;
|
||||||
|
refreshProviders?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { openModal } = useProviderModal();
|
const { openModal } = useProviderModal();
|
||||||
|
|
||||||
@@ -27,13 +29,16 @@ const ProviderCards = memo(function ProviderCards({
|
|||||||
const configureProviderViaModal = useCallback(
|
const configureProviderViaModal = useCallback(
|
||||||
(provider: ProviderDetails) => {
|
(provider: ProviderDetails) => {
|
||||||
openModal(provider, {
|
openModal(provider, {
|
||||||
onSubmit: (values: any) => {
|
onSubmit: () => {
|
||||||
// Your logic to save the configuration
|
// Only refresh if the function is provided
|
||||||
|
if (refreshProviders) {
|
||||||
|
refreshProviders();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
formProps: {},
|
formProps: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[openModal]
|
[openModal, refreshProviders]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLaunch = useCallback(() => {
|
const handleLaunch = useCallback(() => {
|
||||||
@@ -56,48 +61,28 @@ const ProviderCards = memo(function ProviderCards({
|
|||||||
return <>{providerCards}</>;
|
return <>{providerCards}</>;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fix the ProviderModalProvider
|
|
||||||
export const OptimizedProviderModalProvider = memo(function OptimizedProviderModalProvider({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const contextValue = useMemo(
|
|
||||||
() => ({
|
|
||||||
isOpen: false,
|
|
||||||
currentProvider: null,
|
|
||||||
modalProps: {},
|
|
||||||
openModal: (provider, additionalProps = {}) => {
|
|
||||||
// Implementation
|
|
||||||
},
|
|
||||||
closeModal: () => {
|
|
||||||
// Implementation
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return <ProviderModalProvider>{children}</ProviderModalProvider>;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default memo(function ProviderGrid({
|
export default memo(function ProviderGrid({
|
||||||
providers,
|
providers,
|
||||||
isOnboarding,
|
isOnboarding,
|
||||||
|
refreshProviders,
|
||||||
}: {
|
}: {
|
||||||
providers: ProviderDetails[];
|
providers: ProviderDetails[];
|
||||||
isOnboarding: boolean;
|
isOnboarding: boolean;
|
||||||
|
refreshProviders?: () => void;
|
||||||
}) {
|
}) {
|
||||||
// Remove the console.log
|
|
||||||
console.log('provider grid');
|
|
||||||
// Memoize the modal provider and its children to avoid recreating on every render
|
// Memoize the modal provider and its children to avoid recreating on every render
|
||||||
const modalProviderContent = useMemo(
|
const modalProviderContent = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<ProviderModalProvider>
|
<ProviderModalProvider>
|
||||||
<ProviderCards providers={providers} isOnboarding={isOnboarding} />
|
<ProviderCards
|
||||||
|
providers={providers}
|
||||||
|
isOnboarding={isOnboarding}
|
||||||
|
refreshProviders={refreshProviders}
|
||||||
|
/>
|
||||||
<ProviderConfigurationModal />
|
<ProviderConfigurationModal />
|
||||||
</ProviderModalProvider>
|
</ProviderModalProvider>
|
||||||
),
|
),
|
||||||
[providers, isOnboarding]
|
[providers, isOnboarding, refreshProviders]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <GridLayout>{modalProviderContent}</GridLayout>;
|
return <GridLayout>{modalProviderContent}</GridLayout>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
||||||
import { ScrollArea } from '../../ui/scroll-area';
|
import { ScrollArea } from '../../ui/scroll-area';
|
||||||
import BackButton from '../../ui/BackButton';
|
import BackButton from '../../ui/BackButton';
|
||||||
import ProviderGrid from './ProviderGrid';
|
import ProviderGrid from './ProviderGrid';
|
||||||
@@ -9,37 +9,40 @@ export default function ProviderSettings({ onClose }: { onClose: () => void }) {
|
|||||||
const { getProviders } = useConfig();
|
const { getProviders } = useConfig();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [providers, setProviders] = useState<ProviderDetails[]>([]);
|
const [providers, setProviders] = useState<ProviderDetails[]>([]);
|
||||||
|
const initialLoadDone = useRef(false);
|
||||||
|
|
||||||
|
// Create a function to load providers that can be called multiple times
|
||||||
|
const loadProviders = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// Only force refresh when explicitly requested, not on initial load
|
||||||
|
const result = await getProviders(!initialLoadDone.current);
|
||||||
|
if (result) {
|
||||||
|
setProviders(result);
|
||||||
|
initialLoadDone.current = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load providers:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [getProviders]);
|
||||||
|
|
||||||
// Load providers only once when component mounts
|
// Load providers only once when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
|
||||||
|
|
||||||
const loadProviders = async () => {
|
|
||||||
try {
|
|
||||||
// Force refresh to ensure we have the latest data
|
|
||||||
const result = await getProviders(true);
|
|
||||||
// Only update state if component is still mounted
|
|
||||||
if (isMounted && result) {
|
|
||||||
setProviders(result);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load providers:', error);
|
|
||||||
} finally {
|
|
||||||
if (isMounted) {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadProviders();
|
loadProviders();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []); // Intentionally not including loadProviders in deps to prevent reloading
|
||||||
|
|
||||||
// Cleanup function to prevent state updates on unmounted component
|
// This function will be passed to ProviderGrid for manual refreshes after config changes
|
||||||
return () => {
|
const refreshProviders = useCallback(() => {
|
||||||
isMounted = false;
|
if (initialLoadDone.current) {
|
||||||
};
|
getProviders(true).then((result) => {
|
||||||
}, []); // Empty dependency array ensures this only runs once
|
if (result) setProviders(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [getProviders]);
|
||||||
|
|
||||||
console.log(providers);
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-full">
|
<div className="h-screen w-full">
|
||||||
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div>
|
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div>
|
||||||
@@ -61,7 +64,11 @@ export default function ProviderSettings({ onClose }: { onClose: () => void }) {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<div>Loading providers...</div>
|
<div>Loading providers...</div>
|
||||||
) : (
|
) : (
|
||||||
<ProviderGrid providers={providers} isOnboarding={false} />
|
<ProviderGrid
|
||||||
|
providers={providers}
|
||||||
|
isOnboarding={false}
|
||||||
|
refreshProviders={refreshProviders}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+17
-7
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Modal from '../../../../components/Modal';
|
import Modal from '../../../../components/Modal';
|
||||||
import ProviderSetupHeader from './subcomponents/ProviderSetupHeader';
|
import ProviderSetupHeader from './subcomponents/ProviderSetupHeader';
|
||||||
import DefaultProviderSetupForm from './subcomponents/forms/DefaultProviderSetupForm';
|
import DefaultProviderSetupForm from './subcomponents/forms/DefaultProviderSetupForm';
|
||||||
@@ -20,7 +20,7 @@ const customFormsMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function ProviderConfigurationModal() {
|
export default function ProviderConfigurationModal() {
|
||||||
const { upsert } = useConfig();
|
const { upsert, getProviders } = useConfig();
|
||||||
const { isOpen, currentProvider, modalProps, closeModal } = useProviderModal();
|
const { isOpen, currentProvider, modalProps, closeModal } = useProviderModal();
|
||||||
const [configValues, setConfigValues] = useState({});
|
const [configValues, setConfigValues] = useState({});
|
||||||
|
|
||||||
@@ -32,15 +32,25 @@ export default function ProviderConfigurationModal() {
|
|||||||
const SubmitHandler = customSubmitHandlerMap[currentProvider.name] || DefaultSubmitHandler;
|
const SubmitHandler = customSubmitHandlerMap[currentProvider.name] || DefaultSubmitHandler;
|
||||||
const FormComponent = customFormsMap[currentProvider.name] || DefaultProviderSetupForm;
|
const FormComponent = customFormsMap[currentProvider.name] || DefaultProviderSetupForm;
|
||||||
|
|
||||||
const handleSubmitForm = (e) => {
|
const handleSubmitForm = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log('Form submitted for:', currentProvider.name);
|
console.log('Form submitted for:', currentProvider.name);
|
||||||
|
|
||||||
SubmitHandler(upsert, currentProvider, configValues);
|
try {
|
||||||
|
// Wait for the submission to complete
|
||||||
|
await SubmitHandler(upsert, currentProvider, configValues);
|
||||||
|
|
||||||
// Close the modal unless the custom handler explicitly returns false
|
// Close the modal before triggering refreshes to avoid UI issues
|
||||||
// This gives custom handlers the ability to keep the modal open if needed
|
closeModal();
|
||||||
closeModal();
|
|
||||||
|
// Call onSubmit callback if provided (from modal props)
|
||||||
|
if (modalProps.onSubmit) {
|
||||||
|
modalProps.onSubmit(configValues);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save configuration:', error);
|
||||||
|
// Keep modal open if there's an error
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
|||||||
@@ -1,60 +1,56 @@
|
|||||||
import React, { createContext, useContext, useState, useMemo, useCallback } from 'react';
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
import { ProviderDetails } from '../../../../api';
|
import { ProviderDetails } from '../../../../api/types.gen';
|
||||||
|
|
||||||
|
interface ModalProps {
|
||||||
|
onSubmit?: (values: any) => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
formProps?: any;
|
||||||
|
}
|
||||||
|
|
||||||
interface ProviderModalContextType {
|
interface ProviderModalContextType {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
currentProvider: ProviderDetails | null;
|
currentProvider: ProviderDetails | null;
|
||||||
modalProps: any;
|
modalProps: ModalProps;
|
||||||
openModal: (provider: ProviderDetails, additionalProps: any) => void;
|
openModal: (provider: ProviderDetails, additionalProps?: ModalProps) => void;
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultContext: ProviderModalContextType = {
|
const ProviderModalContext = createContext<ProviderModalContextType | undefined>(undefined);
|
||||||
isOpen: false,
|
|
||||||
currentProvider: null,
|
|
||||||
modalProps: {},
|
|
||||||
openModal: () => {},
|
|
||||||
closeModal: () => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProviderModalContext = createContext<ProviderModalContextType>(defaultContext);
|
|
||||||
|
|
||||||
export const useProviderModal = () => useContext<ProviderModalContextType>(ProviderModalContext);
|
|
||||||
|
|
||||||
export const ProviderModalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const ProviderModalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [currentProvider, setCurrentProvider] = useState<ProviderDetails | null>(null);
|
const [currentProvider, setCurrentProvider] = useState<ProviderDetails | null>(null);
|
||||||
const [modalProps, setModalProps] = useState({});
|
const [modalProps, setModalProps] = useState<ModalProps>({});
|
||||||
|
|
||||||
// Use useCallback to prevent function recreation on each render
|
const openModal = (provider: ProviderDetails, additionalProps: ModalProps = {}) => {
|
||||||
const openModal = useCallback((provider: ProviderDetails, additionalProps = {}) => {
|
|
||||||
setCurrentProvider(provider);
|
setCurrentProvider(provider);
|
||||||
setModalProps(additionalProps);
|
setModalProps(additionalProps);
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
// Use a small timeout to prevent UI flicker
|
};
|
||||||
setTimeout(() => {
|
|
||||||
setCurrentProvider(null);
|
|
||||||
setModalProps({});
|
|
||||||
}, 200);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Memoize the context value to prevent unnecessary re-renders
|
|
||||||
const contextValue = useMemo(
|
|
||||||
() => ({
|
|
||||||
isOpen,
|
|
||||||
currentProvider,
|
|
||||||
modalProps,
|
|
||||||
openModal,
|
|
||||||
closeModal,
|
|
||||||
}),
|
|
||||||
[isOpen, currentProvider, modalProps, openModal, closeModal]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProviderModalContext.Provider value={contextValue}>{children}</ProviderModalContext.Provider>
|
<ProviderModalContext.Provider
|
||||||
|
value={{
|
||||||
|
isOpen,
|
||||||
|
currentProvider,
|
||||||
|
modalProps,
|
||||||
|
openModal,
|
||||||
|
closeModal,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ProviderModalContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useProviderModal = () => {
|
||||||
|
const context = useContext(ProviderModalContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useProviderModal must be used within a ProviderModalProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|||||||
+1
-2
@@ -25,12 +25,11 @@ export default function DefaultProviderSetupForm({
|
|||||||
|
|
||||||
// Try to load actual values from config for each parameter that is not secret
|
// Try to load actual values from config for each parameter that is not secret
|
||||||
for (const parameter of parameters) {
|
for (const parameter of parameters) {
|
||||||
if (parameter.required && !parameter.secret) {
|
if (parameter.required) {
|
||||||
try {
|
try {
|
||||||
// Check if there's a stored value in the config system
|
// Check if there's a stored value in the config system
|
||||||
const configKey = `${parameter.name}`;
|
const configKey = `${parameter.name}`;
|
||||||
const configResponse = await read(configKey, parameter.secret || false);
|
const configResponse = await read(configKey, parameter.secret || false);
|
||||||
console.log('configResponse', configResponse);
|
|
||||||
|
|
||||||
if (configResponse) {
|
if (configResponse) {
|
||||||
// Use the value from the config provider
|
// Use the value from the config provider
|
||||||
|
|||||||
+1
-2
@@ -101,8 +101,7 @@ export const DefaultSubmitHandler = async (upsertFn, provider, configValues) =>
|
|||||||
// Create the provider-specific config key
|
// Create the provider-specific config key
|
||||||
const configKey = `${parameter.name}`;
|
const configKey = `${parameter.name}`;
|
||||||
|
|
||||||
// Explicitly define is_secret as a boolean (true/false) or null
|
// Explicitly define is_secret as a boolean (true/false)
|
||||||
// This is critical for Rust's Option<bool> type
|
|
||||||
const isSecret = parameter.secret === true;
|
const isSecret = parameter.secret === true;
|
||||||
|
|
||||||
// Pass the is_secret flag from the parameter definition
|
// Pass the is_secret flag from the parameter definition
|
||||||
|
|||||||
Reference in New Issue
Block a user