fix: VMware Tanzu Platform provider - bug fixes, streaming, UI improvements (#8126)
Signed-off-by: Nick Kuhn <nick.kuhn@broadcom.com> Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -331,6 +331,11 @@ export type EnvVarConfig = {
|
||||
default?: string | null;
|
||||
description?: string | null;
|
||||
name: string;
|
||||
/**
|
||||
* When true, the field is shown prominently in the UI (not collapsed).
|
||||
* Defaults to the value of `required` if not specified.
|
||||
*/
|
||||
primary?: boolean | null;
|
||||
required?: boolean;
|
||||
secret?: boolean;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import OpenRouterLogo from './icons/openrouter@3x.png';
|
||||
import SnowflakeLogo from './icons/snowflake@3x.png';
|
||||
import XaiLogo from './icons/xai@3x.png';
|
||||
import MiniMaxLogo from './icons/minimax@3x.png';
|
||||
import TanzuLogo from './icons/tanzu@3x.png';
|
||||
import DefaultLogo from './icons/default@3x.png';
|
||||
|
||||
// Map provider names to their logos
|
||||
@@ -22,6 +23,7 @@ const providerLogos: Record<string, string> = {
|
||||
snowflake: SnowflakeLogo,
|
||||
xai: XaiLogo,
|
||||
minimax: MiniMaxLogo,
|
||||
tanzu_ai: TanzuLogo,
|
||||
default: DefaultLogo,
|
||||
};
|
||||
|
||||
|
||||
+100
-38
@@ -60,7 +60,7 @@ export default function DefaultProviderSetupForm({
|
||||
const configKey = `${parameter.name}`;
|
||||
const configValue = (await read(configKey, parameter.secret || false)) as ConfigValue;
|
||||
|
||||
if (configValue) {
|
||||
if (configValue !== undefined && configValue !== null) {
|
||||
values[parameter.name] = { serverValue: configValue };
|
||||
} else if (parameter.default !== undefined && parameter.default !== null) {
|
||||
values[parameter.name] = { value: parameter.default };
|
||||
@@ -127,47 +127,109 @@ export default function DefaultProviderSetupForm({
|
||||
return <div className="text-center py-4">Loading configuration values...</div>;
|
||||
}
|
||||
|
||||
function getRenderValue(parameter: ConfigKey): string | undefined {
|
||||
if (parameter.secret) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getRenderValue(parameter: ConfigKey): string {
|
||||
const entry = configValues[parameter.name];
|
||||
return entry?.value || (entry?.serverValue as string) || '';
|
||||
// If the user has edited the field (even to empty string), use their value.
|
||||
// This prevents the input from snapping back to the stored serverValue
|
||||
// when the user backspaces to clear the field.
|
||||
if (entry?.value !== undefined) {
|
||||
return entry.value;
|
||||
}
|
||||
if (parameter.secret) {
|
||||
return '';
|
||||
}
|
||||
// Convert serverValue to string explicitly — native booleans (false) would
|
||||
// be falsy and get collapsed to '' by the || operator, losing the value.
|
||||
if (entry?.serverValue !== undefined && entry?.serverValue !== null) {
|
||||
return String(entry.serverValue);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Detect boolean parameters (default is "true" or "false")
|
||||
function isBooleanParameter(parameter: ConfigKey): boolean {
|
||||
const def = parameter.default?.toLowerCase();
|
||||
return def === 'true' || def === 'false';
|
||||
}
|
||||
|
||||
function getBooleanValue(parameter: ConfigKey): boolean {
|
||||
const raw = getRenderValue(parameter);
|
||||
const val = String(raw).toLowerCase();
|
||||
if (val === '' && parameter.default) {
|
||||
return parameter.default.toLowerCase() === 'true';
|
||||
}
|
||||
return val === 'true';
|
||||
}
|
||||
|
||||
// Pretty label for boolean toggle (strip provider prefix, humanize)
|
||||
function getBooleanLabel(parameter: ConfigKey): string {
|
||||
let name = parameter.name.toUpperCase();
|
||||
const prefix = provider.name.toUpperCase().replace('-', '_') + '_';
|
||||
if (name.startsWith(prefix)) {
|
||||
name = name.slice(prefix.length);
|
||||
}
|
||||
return envToPrettyName(name);
|
||||
}
|
||||
|
||||
const renderParametersList = (parameters: ConfigKey[]) => {
|
||||
return parameters.map((parameter) => (
|
||||
<div key={parameter.name}>
|
||||
<label className="block text-sm font-medium text-text-primary mb-1">
|
||||
{getFieldLabel(parameter)}
|
||||
{parameter.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={getRenderValue(parameter)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConfigValues((prev) => {
|
||||
const newValue = { ...(prev[parameter.name] || {}), value: e.target.value };
|
||||
return {
|
||||
...prev,
|
||||
[parameter.name]: newValue,
|
||||
};
|
||||
});
|
||||
}}
|
||||
placeholder={getPlaceholder(parameter)}
|
||||
className={`w-full h-14 px-4 font-regular rounded-lg shadow-none ${
|
||||
validationErrors[parameter.name]
|
||||
? 'border-2 border-red-500'
|
||||
: 'border border-border-primary hover:border-border-primary'
|
||||
} bg-background-primary text-lg placeholder:text-text-secondary font-regular text-text-primary`}
|
||||
required={parameter.required}
|
||||
/>
|
||||
{validationErrors[parameter.name] && (
|
||||
<p className="text-red-500 text-sm mt-1">{validationErrors[parameter.name]}</p>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
return parameters.map((parameter) => {
|
||||
if (isBooleanParameter(parameter)) {
|
||||
return (
|
||||
<div key={parameter.name} className="flex items-center space-x-2 py-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`toggle-${parameter.name}`}
|
||||
checked={getBooleanValue(parameter)}
|
||||
onChange={(e) => {
|
||||
setConfigValues((prev) => ({
|
||||
...prev,
|
||||
[parameter.name]: {
|
||||
...(prev[parameter.name] || {}),
|
||||
value: e.target.checked ? 'true' : 'false',
|
||||
},
|
||||
}));
|
||||
}}
|
||||
className="rounded border-border-primary h-4 w-4"
|
||||
/>
|
||||
<label htmlFor={`toggle-${parameter.name}`} className="text-sm text-text-secondary">
|
||||
{getBooleanLabel(parameter)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={parameter.name}>
|
||||
<label className="block text-sm font-medium text-text-primary mb-1">
|
||||
{getFieldLabel(parameter)}
|
||||
{parameter.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={getRenderValue(parameter)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConfigValues((prev) => {
|
||||
const newValue = { ...(prev[parameter.name] || {}), value: e.target.value };
|
||||
return {
|
||||
...prev,
|
||||
[parameter.name]: newValue,
|
||||
};
|
||||
});
|
||||
}}
|
||||
placeholder={getPlaceholder(parameter)}
|
||||
className={`w-full h-14 px-4 font-regular rounded-lg shadow-none ${
|
||||
validationErrors[parameter.name]
|
||||
? 'border-2 border-red-500'
|
||||
: 'border border-border-primary hover:border-border-primary'
|
||||
} bg-background-primary text-lg placeholder:text-text-secondary font-regular text-text-primary`}
|
||||
required={parameter.required}
|
||||
/>
|
||||
{validationErrors[parameter.name] && (
|
||||
<p className="text-red-500 text-sm mt-1">{validationErrors[parameter.name]}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
let aboveFoldParameters = parameters.filter(
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Reference in New Issue
Block a user