mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 09:49:32 +08:00
feat: Model Provider Input Patches for Design (#11218)
* feat: add IBM icon and reorganize IBM WatsonX icon structure - Add new IBM logo icon (IBM.jsx, ibm.svg) for generic IBM provider branding - Reorganize icon directory structure: move IBMWatsonx to IBM parent folder with watsonx subfolder - Export both WatsonxAiIcon and IBMIcon from consolidated IBM index - Update icon mapping in use-get-model-providers to support both 'IBM WatsonX' and 'IBM watsonx.ai' provider names - Standardize quote style from double to single quotes across use-get-model-providers. * added doc link support * refactor: standardize IBM WatsonX icon references and quote style - Update all WatsonX model metadata to use generic "IBM" icon instead of "WatsonxAI" - Standardize quote style from double to single quotes in ModelProvidersPage imports * refactor: standardize quote style and update LLM section label in ModelSelection - Change all double quotes to single quotes in ModelSelection component and tests - Update "LLM Models" label to "Language Models" for consistency - Standardize arrow function formatting across component * fixes the model provider streching the page * fixes border not being fully hidden * updated api doc url * fix: improve model field default value logic and provider field visibility - Update default value setting logic to check current model value from build_config instead of field_value parameter - Only set default when field_name is None (initial load) or when model field is being set and is empty - Fix provider-specific field visibility to use current model value from build_config when field_name is not model - Ensure provider fields are shown/hidden correctly regardless of which field triggere * fix: improve model validation and refresh logic in model provider modal - Add client-side filtering of disabled models in ModelInputComponent using enabled models data - Implement validateModelValue function to ensure selected models are available and valid - Move refreshAllModelInputs call from ModelProvidersContent cleanup to ModelProviderModal handleClose with 1000ms delay to ensure database transactions complete - Remove race condition between cleanup effect and debounced mutateTemplate by not * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * revery lru cache * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * updated component_index * updated templates * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * refactor: convert use-refresh-model-inputs and api types to single quotes and add ModelOptionType - Convert double quotes to single quotes throughout use-refresh-model-inputs.ts and api/index.ts - Add ModelOptionType interface with name, id, icon, provider, and metadata properties - Update validateModelValue to use typed ModelOptionType instead of any for option filtering * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fixed tests * removed sticky property --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2028,6 +2028,10 @@
|
||||
"limit": 1,
|
||||
"name": "storage_location",
|
||||
"options": [
|
||||
{
|
||||
"icon": "hard-drive",
|
||||
"name": "Local"
|
||||
},
|
||||
{
|
||||
"icon": "Amazon",
|
||||
"name": "AWS"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -47,6 +47,13 @@ jest.mock("@/controllers/API/queries/models/use-get-model-providers", () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock("@/controllers/API/queries/models/use-get-enabled-models", () => ({
|
||||
useGetEnabledModels: jest.fn(() => ({
|
||||
data: { enabled_models: {} },
|
||||
isLoading: false,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock("@/controllers/API/queries/nodes/use-post-template-value", () => ({
|
||||
usePostTemplateValue: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
@ -267,18 +274,6 @@ describe("ModelInputComponent", () => {
|
||||
});
|
||||
|
||||
describe("Footer Buttons", () => {
|
||||
it("should render Refresh List button in dropdown", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInputComponent {...defaultProps} />);
|
||||
|
||||
const trigger = screen.getByRole("combobox");
|
||||
await user.click(trigger);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Refresh List")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render Manage Model Providers button", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInputComponent {...defaultProps} />);
|
||||
|
||||
@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
|
||||
import LoadingTextComponent from "@/components/common/loadingTextComponent";
|
||||
import { RECEIVING_INPUT_VALUE } from "@/constants/constants";
|
||||
import { useGetEnabledModels } from "@/controllers/API/queries/models/use-get-enabled-models";
|
||||
import { useGetModelProviders } from "@/controllers/API/queries/models/use-get-model-providers";
|
||||
import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
|
||||
import ModelProviderModal from "@/modals/modelProviderModal";
|
||||
@ -82,6 +83,7 @@ export default function ModelInputComponent({
|
||||
: "embeddings";
|
||||
|
||||
const { data: providersData = [] } = useGetModelProviders({});
|
||||
const { data: enabledModelsData } = useGetEnabledModels();
|
||||
|
||||
// Determines if we should show the model selector or the "Setup Provider" button
|
||||
const hasEnabledProviders = useMemo(() => {
|
||||
@ -89,16 +91,26 @@ export default function ModelInputComponent({
|
||||
}, [providersData]);
|
||||
|
||||
// Groups models by their provider name for sectioned display in dropdown.
|
||||
// Filters out models from disabled providers.
|
||||
// Filters out models from disabled providers AND disabled models.
|
||||
const groupedOptions = useMemo(() => {
|
||||
const grouped: Record<string, ModelOption[]> = {};
|
||||
for (const option of options) {
|
||||
if (option.metadata?.is_disabled_provider) continue;
|
||||
const provider = option.provider || "Unknown";
|
||||
|
||||
// Filter out disabled models using client-side enabled models data
|
||||
// This provides a reliable fallback when backend filtering fails
|
||||
if (enabledModelsData?.enabled_models) {
|
||||
const providerModels = enabledModelsData.enabled_models[provider];
|
||||
if (providerModels && providerModels[option.name] === false) {
|
||||
continue; // Skip disabled models
|
||||
}
|
||||
}
|
||||
|
||||
(grouped[provider] ??= []).push(option);
|
||||
}
|
||||
return grouped;
|
||||
}, [options]);
|
||||
}, [options, enabledModelsData]);
|
||||
|
||||
// Flattened array of all enabled options for efficient lookups by name
|
||||
const flatOptions = useMemo(
|
||||
@ -221,8 +233,11 @@ export default function ModelInputComponent({
|
||||
|
||||
const handleManageProvidersDialogClose = useCallback(() => {
|
||||
setOpenManageProvidersDialog(false);
|
||||
handleRefreshButtonPress();
|
||||
}, [handleRefreshButtonPress]);
|
||||
// Note: Don't call handleRefreshButtonPress here - the cleanup effect in
|
||||
// ModelProvidersContent triggers refreshAllModelInputs which properly validates
|
||||
// model values against available options. Calling both causes a race condition
|
||||
// where the debounced mutateTemplate overwrites the validated value.
|
||||
}, []);
|
||||
|
||||
const handleExternalOptions = useCallback(
|
||||
async (optionValue: string) => {
|
||||
@ -476,13 +491,13 @@ export default function ModelInputComponent({
|
||||
);
|
||||
|
||||
const renderManageProvidersButton = () => (
|
||||
<div className="sticky bottom-0 bg-background">
|
||||
{renderFooterButton(
|
||||
<div className="bottom-0 bg-background">
|
||||
{/* {renderFooterButton(
|
||||
"Refresh List",
|
||||
"RefreshCw",
|
||||
handleRefreshButtonPress,
|
||||
"external-option-button",
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{renderFooterButton(
|
||||
"Manage Model Providers",
|
||||
|
||||
@ -10,6 +10,7 @@ export interface ModelProviderInfo {
|
||||
metadata: Record<string, any>;
|
||||
}>;
|
||||
is_enabled: boolean;
|
||||
api_docs_url?: string;
|
||||
}
|
||||
|
||||
export interface ModelProviderWithStatus extends ModelProviderInfo {
|
||||
@ -78,7 +79,7 @@ const getProviderIcon = (providerName: string): string => {
|
||||
const iconMap: Record<string, string> = {
|
||||
OpenAI: "OpenAI",
|
||||
Anthropic: "Anthropic",
|
||||
"Google Generative AI": "Google",
|
||||
"Google Generative AI": "GoogleGenerativeAI",
|
||||
Groq: "Groq",
|
||||
"Amazon Bedrock": "Bedrock",
|
||||
NVIDIA: "NVIDIA",
|
||||
@ -86,6 +87,8 @@ const getProviderIcon = (providerName: string): string => {
|
||||
"Azure OpenAI": "AzureOpenAI",
|
||||
SambaNova: "SambaNova",
|
||||
Ollama: "Ollama",
|
||||
"IBM WatsonX": "IBM",
|
||||
"IBM watsonx.ai": "IBM",
|
||||
};
|
||||
|
||||
return iconMap[providerName] || "Bot";
|
||||
|
||||
@ -383,7 +383,7 @@ describe("refreshAllModelInputs", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should skip node update when API returns empty options", async () => {
|
||||
it("should clear model value when API returns empty options", async () => {
|
||||
mockNodes = [createMockModelNode("node-1")];
|
||||
|
||||
(api.post as jest.Mock).mockResolvedValue({
|
||||
@ -404,7 +404,8 @@ describe("refreshAllModelInputs", () => {
|
||||
|
||||
await refreshAllModelInputs(mockQueryClient as any);
|
||||
|
||||
expect(mockSetNode).not.toHaveBeenCalled();
|
||||
// Should still call setNode to clear the invalid value
|
||||
expect(mockSetNode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle API errors gracefully", async () => {
|
||||
|
||||
@ -5,7 +5,11 @@ import { getURL } from "@/controllers/API/helpers/constants";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import type { APIClassType, APITemplateType } from "@/types/api";
|
||||
import type {
|
||||
APIClassType,
|
||||
APITemplateType,
|
||||
ModelOptionType,
|
||||
} from "@/types/api";
|
||||
import type { AllNodeType } from "@/types/flow";
|
||||
|
||||
export interface RefreshOptions {
|
||||
@ -120,6 +124,71 @@ export async function refreshAllModelInputs(
|
||||
}
|
||||
}
|
||||
|
||||
/** Validates and corrects model value against available options */
|
||||
function validateModelValue(
|
||||
template: APITemplateType,
|
||||
modelFieldKey: string,
|
||||
): APITemplateType {
|
||||
const modelField = template[modelFieldKey];
|
||||
if (!modelField) return template;
|
||||
|
||||
const options = modelField.options || [];
|
||||
const currentValue = modelField.value;
|
||||
|
||||
// Filter out disabled provider placeholders to get actual available models
|
||||
const availableOptions = options.filter(
|
||||
(opt: ModelOptionType) => !opt?.metadata?.is_disabled_provider,
|
||||
);
|
||||
|
||||
// Get current model name from value
|
||||
const currentModelName = Array.isArray(currentValue)
|
||||
? currentValue[0]?.name
|
||||
: currentValue?.name;
|
||||
|
||||
// Check if current model is still available
|
||||
const isCurrentModelValid =
|
||||
currentModelName &&
|
||||
availableOptions.some(
|
||||
(opt: ModelOptionType) => opt.name === currentModelName,
|
||||
);
|
||||
|
||||
if (isCurrentModelValid) {
|
||||
// Current value is valid, no changes needed
|
||||
return template;
|
||||
}
|
||||
|
||||
// Current value is invalid - need to update it
|
||||
if (availableOptions.length > 0) {
|
||||
// Select the first available model
|
||||
const firstOption = availableOptions[0];
|
||||
const newValue = [
|
||||
{
|
||||
...(firstOption.id && { id: firstOption.id }),
|
||||
name: firstOption.name,
|
||||
icon: firstOption.icon || "Bot",
|
||||
provider: firstOption.provider || "Unknown",
|
||||
metadata: firstOption.metadata ?? {},
|
||||
},
|
||||
];
|
||||
return {
|
||||
...template,
|
||||
[modelFieldKey]: {
|
||||
...modelField,
|
||||
value: newValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// No available options - clear the value
|
||||
return {
|
||||
...template,
|
||||
[modelFieldKey]: {
|
||||
...modelField,
|
||||
value: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Refreshes a single node's model field via API */
|
||||
async function refreshSingleNode(
|
||||
node: AllNodeType,
|
||||
@ -156,18 +225,16 @@ async function refreshSingleNode(
|
||||
const responseData = response.data;
|
||||
if (!responseData?.template) return;
|
||||
|
||||
// Skip if no options (prevents infinite loops)
|
||||
const newModelOptions = responseData.template[modelFieldKey]?.options || [];
|
||||
if (newModelOptions.length === 0) return;
|
||||
// Validate and correct the model value against available options
|
||||
const validatedTemplate = validateModelValue(
|
||||
responseData.template,
|
||||
modelFieldKey,
|
||||
);
|
||||
|
||||
setNode(
|
||||
node.id,
|
||||
(currentNode) =>
|
||||
createUpdatedNode(
|
||||
currentNode,
|
||||
responseData.template,
|
||||
responseData.outputs,
|
||||
),
|
||||
createUpdatedNode(currentNode, validatedTemplate, responseData.outputs),
|
||||
false,
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
162
src/frontend/src/icons/IBM/ibm/IBM.jsx
Normal file
162
src/frontend/src/icons/IBM/ibm/IBM.jsx
Normal file
@ -0,0 +1,162 @@
|
||||
const SvgIBM = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={16}
|
||||
height={7}
|
||||
viewBox="0 0 16 7"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_1341_6038)">
|
||||
<path
|
||||
d="M3.11111 5.93939H0V6.34343H3.11111V5.93939Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.11111 5.49495H0V5.09091H3.11111V5.49495Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M2.22222 4.64647H0.888889V4.24242H2.22222V4.64647Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M2.22222 3.79798H0.888889V3.39394H2.22222V3.79798Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M2.22222 2.9495H0.888889V2.54545H2.22222V2.9495Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M0.888889 1.69697H2.22222V2.10101H0.888889V1.69697Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.11111 1.25253H0V0.848485H3.11111V1.25253Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M3.11111 0.40404H0V0H3.11111V0.40404Z" fill="currentColor" />
|
||||
<path
|
||||
d="M6.82828 6.34343H3.55556V5.93939H7.92687C7.63071 6.19111 7.24727 6.34343 6.82828 6.34343Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.29778 5.49495H3.55556V5.09091H8.46626C8.42747 5.23394 8.37051 5.3697 8.29778 5.49495Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M5.77778 4.64647H4.44444V4.24242H5.77778V4.64647Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M4.44444 2.9495V2.54545H8.29737C8.2097 2.69657 8.09939 2.83232 7.97131 2.9495H4.44444Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.29778 0.848485C8.37051 0.973737 8.42747 1.1095 8.46626 1.25253H3.55556V0.848485H8.29778Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M5.77778 2.10101H4.44444V1.69697H5.77778V2.10101Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.47596 2.10101H7.0303V1.69697H8.52525C8.52525 1.83636 8.50747 1.97131 8.47596 2.10101Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M7.0303 4.64647V4.24242H8.47596C8.50747 4.37212 8.52525 4.50707 8.52525 4.64647H7.0303Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M4.44444 3.39394H7.97131C8.09939 3.51111 8.2097 3.64687 8.29737 3.79798H4.44444V3.39394Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M6.82828 0C7.24727 0 7.63071 0.152323 7.92687 0.40404H3.55556V0H6.82828Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M11.1111 6.34343H8.88889V5.93939H11.1111V6.34343Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M11.1111 5.49495H8.88889V5.09091H11.1111V5.49495Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M11.1111 4.64647H9.77778V4.24242H11.1111V4.64647Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M11.1111 3.79798H9.77778V3.39394H11.1111V3.79798Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M16 6.34343H13.7778V5.93939H16V6.34343Z" fill="currentColor" />
|
||||
<path d="M16 5.49495H13.7778V5.09091H16V5.49495Z" fill="currentColor" />
|
||||
<path
|
||||
d="M15.1111 4.64647H13.7778V4.24242H15.1111V4.64647Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M13.7778 3.39394H15.1111V3.79798H13.7778V3.39394Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M15.1111 2.9495H13.7774V2.56485L13.6416 2.9495H12.4715L12.6117 2.54545H15.1111V2.9495Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12.9063 1.69697H15.1111V2.10101H12.7661L12.9063 1.69697Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M13.3547 0.40404L13.4949 0H16V0.40404H13.3547Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12.5863 5.93939L12.4444 6.34343L12.3026 5.93939H12.5863Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12.7434 5.49495H12.1459L12.0032 5.09091H12.8861L12.7434 5.49495Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M13.0428 4.64647H11.8461L11.7034 4.24242H13.1855L13.0428 4.64647Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M13.3426 3.79798H11.5467L11.404 3.39394H13.4853L13.3426 3.79798Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12.1228 2.10101H9.77778V1.69697H11.9826L12.1228 2.10101Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M16 1.25253H13.0602L13.2004 0.848485H16V1.25253Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.88889 0.848485H11.6881L11.8283 1.25253H8.88889V0.848485Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.88889 0H11.3939L11.5341 0.40404H8.88889V0Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12.2772 2.54545L12.4174 2.9495H11.2469L11.1111 2.56606V2.9495H9.77778V2.54545H12.2772Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1341_6038">
|
||||
<rect width={16} height={6.38384} fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
export default SvgIBM;
|
||||
47
src/frontend/src/icons/IBM/ibm/ibm.svg
Normal file
47
src/frontend/src/icons/IBM/ibm/ibm.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="7" viewBox="0 0 16 7" fill="none">
|
||||
<g clip-path="url(#clip0_1341_6038)">
|
||||
<path d="M3.11111 5.93939H0V6.34343H3.11111V5.93939Z" fill="white"/>
|
||||
<path d="M3.11111 5.49495H0V5.09091H3.11111V5.49495Z" fill="white"/>
|
||||
<path d="M2.22222 4.64647H0.888889V4.24242H2.22222V4.64647Z" fill="white"/>
|
||||
<path d="M2.22222 3.79798H0.888889V3.39394H2.22222V3.79798Z" fill="white"/>
|
||||
<path d="M2.22222 2.9495H0.888889V2.54545H2.22222V2.9495Z" fill="white"/>
|
||||
<path d="M0.888889 1.69697H2.22222V2.10101H0.888889V1.69697Z" fill="white"/>
|
||||
<path d="M3.11111 1.25253H0V0.848485H3.11111V1.25253Z" fill="white"/>
|
||||
<path d="M3.11111 0.40404H0V0H3.11111V0.40404Z" fill="white"/>
|
||||
<path d="M6.82828 6.34343H3.55556V5.93939H7.92687C7.63071 6.19111 7.24727 6.34343 6.82828 6.34343Z" fill="white"/>
|
||||
<path d="M8.29778 5.49495H3.55556V5.09091H8.46626C8.42747 5.23394 8.37051 5.3697 8.29778 5.49495Z" fill="white"/>
|
||||
<path d="M5.77778 4.64647H4.44444V4.24242H5.77778V4.64647Z" fill="white"/>
|
||||
<path d="M4.44444 2.9495V2.54545H8.29737C8.2097 2.69657 8.09939 2.83232 7.97131 2.9495H4.44444Z" fill="white"/>
|
||||
<path d="M8.29778 0.848485C8.37051 0.973737 8.42747 1.1095 8.46626 1.25253H3.55556V0.848485H8.29778Z" fill="white"/>
|
||||
<path d="M5.77778 2.10101H4.44444V1.69697H5.77778V2.10101Z" fill="white"/>
|
||||
<path d="M8.47596 2.10101H7.0303V1.69697H8.52525C8.52525 1.83636 8.50747 1.97131 8.47596 2.10101Z" fill="white"/>
|
||||
<path d="M7.0303 4.64647V4.24242H8.47596C8.50747 4.37212 8.52525 4.50707 8.52525 4.64647H7.0303Z" fill="white"/>
|
||||
<path d="M4.44444 3.39394H7.97131C8.09939 3.51111 8.2097 3.64687 8.29737 3.79798H4.44444V3.39394Z" fill="white"/>
|
||||
<path d="M6.82828 0C7.24727 0 7.63071 0.152323 7.92687 0.40404H3.55556V0H6.82828Z" fill="white"/>
|
||||
<path d="M11.1111 6.34343H8.88889V5.93939H11.1111V6.34343Z" fill="white"/>
|
||||
<path d="M11.1111 5.49495H8.88889V5.09091H11.1111V5.49495Z" fill="white"/>
|
||||
<path d="M11.1111 4.64647H9.77778V4.24242H11.1111V4.64647Z" fill="white"/>
|
||||
<path d="M11.1111 3.79798H9.77778V3.39394H11.1111V3.79798Z" fill="white"/>
|
||||
<path d="M16 6.34343H13.7778V5.93939H16V6.34343Z" fill="white"/>
|
||||
<path d="M16 5.49495H13.7778V5.09091H16V5.49495Z" fill="white"/>
|
||||
<path d="M15.1111 4.64647H13.7778V4.24242H15.1111V4.64647Z" fill="white"/>
|
||||
<path d="M13.7778 3.39394H15.1111V3.79798H13.7778V3.39394Z" fill="white"/>
|
||||
<path d="M15.1111 2.9495H13.7774V2.56485L13.6416 2.9495H12.4715L12.6117 2.54545H15.1111V2.9495Z" fill="white"/>
|
||||
<path d="M12.9063 1.69697H15.1111V2.10101H12.7661L12.9063 1.69697Z" fill="white"/>
|
||||
<path d="M13.3547 0.40404L13.4949 0H16V0.40404H13.3547Z" fill="white"/>
|
||||
<path d="M12.5863 5.93939L12.4444 6.34343L12.3026 5.93939H12.5863Z" fill="white"/>
|
||||
<path d="M12.7434 5.49495H12.1459L12.0032 5.09091H12.8861L12.7434 5.49495Z" fill="white"/>
|
||||
<path d="M13.0428 4.64647H11.8461L11.7034 4.24242H13.1855L13.0428 4.64647Z" fill="white"/>
|
||||
<path d="M13.3426 3.79798H11.5467L11.404 3.39394H13.4853L13.3426 3.79798Z" fill="white"/>
|
||||
<path d="M12.1228 2.10101H9.77778V1.69697H11.9826L12.1228 2.10101Z" fill="white"/>
|
||||
<path d="M16 1.25253H13.0602L13.2004 0.848485H16V1.25253Z" fill="white"/>
|
||||
<path d="M8.88889 0.848485H11.6881L11.8283 1.25253H8.88889V0.848485Z" fill="white"/>
|
||||
<path d="M8.88889 0H11.3939L11.5341 0.40404H8.88889V0Z" fill="white"/>
|
||||
<path d="M12.2772 2.54545L12.4174 2.9495H11.2469L11.1111 2.56606V2.9495H9.77778V2.54545H12.2772Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1341_6038">
|
||||
<rect width="16" height="6.38384" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
16
src/frontend/src/icons/IBM/index.tsx
Normal file
16
src/frontend/src/icons/IBM/index.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import SvgIBM from "./ibm/IBM";
|
||||
import SvgWatsonxAI from "./watsonx/WatsonxAI";
|
||||
|
||||
export const WatsonxAiIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <SvgWatsonxAI ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export const IBMIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||
(props, ref) => {
|
||||
return <SvgIBM ref={ref} {...props} />;
|
||||
},
|
||||
);
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -1,9 +0,0 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import SvgWatsonxAI from "./WatsonxAI";
|
||||
|
||||
export const WatsonxAiIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <SvgWatsonxAI ref={ref} {...props} />;
|
||||
});
|
||||
@ -52,7 +52,7 @@ import { HCDIcon } from "@/icons/HCD";
|
||||
import { HomeAssistantIcon } from "@/icons/HomeAssistant";
|
||||
import { HuggingFaceIcon } from "@/icons/HuggingFace";
|
||||
import { HackerNewsIcon } from "@/icons/hackerNews";
|
||||
import { WatsonxAiIcon } from "@/icons/IBMWatsonx";
|
||||
import { IBMIcon, WatsonxAiIcon } from "@/icons/IBM";
|
||||
import { IcosaIcon } from "@/icons/Icosa";
|
||||
import { IFixIcon } from "@/icons/IFixIt";
|
||||
import { JSIcon } from "@/icons/JSicon";
|
||||
@ -106,8 +106,8 @@ import { UnstructuredIcon } from "@/icons/Unstructured";
|
||||
import { UpstashSvgIcon } from "@/icons/Upstash";
|
||||
import { VectaraIcon } from "@/icons/VectaraIcon";
|
||||
import { VertexAIIcon } from "@/icons/VertexAI";
|
||||
import { VllmIcon } from "@/icons/vLLM";
|
||||
import { VLMRunIcon } from "@/icons/VLMRun";
|
||||
import { VllmIcon } from "@/icons/vLLM";
|
||||
import { WeaviateIcon } from "@/icons/Weaviate";
|
||||
import { WikipediaIcon } from "@/icons/Wikipedia";
|
||||
import { WolframIcon } from "@/icons/Wolfram";
|
||||
@ -172,6 +172,7 @@ export const eagerIconsMapping = {
|
||||
HCD: HCDIcon,
|
||||
HomeAssistant: HomeAssistantIcon,
|
||||
HuggingFace: HuggingFaceIcon,
|
||||
IBM: IBMIcon,
|
||||
Icosa: IcosaIcon,
|
||||
IFixIt: IFixIcon,
|
||||
javascript: JSIcon,
|
||||
|
||||
@ -278,6 +278,14 @@ export const lazyIconsMapping = {
|
||||
import("@/icons/HuggingFace").then((mod) => ({
|
||||
default: mod.HuggingFaceIcon,
|
||||
})),
|
||||
IBM: () =>
|
||||
import("@/icons/IBM").then((mod) => ({
|
||||
default: mod.IBMIcon,
|
||||
})),
|
||||
WatsonxAI: () =>
|
||||
import("@/icons/IBM").then((mod) => ({
|
||||
default: mod.WatsonxAiIcon,
|
||||
})),
|
||||
Icosa: () =>
|
||||
import("@/icons/Icosa").then((mod) => ({ default: mod.IcosaIcon })),
|
||||
IFixIt: () =>
|
||||
@ -467,19 +475,6 @@ export const lazyIconsMapping = {
|
||||
import("@/icons/vectorstores").then((mod) => ({
|
||||
default: mod.VectorStoresIcon,
|
||||
})),
|
||||
VertexAI: () =>
|
||||
import("@/icons/VertexAI").then((mod) => ({ default: mod.VertexAIIcon })),
|
||||
vLLM: () => import("@/icons/vLLM").then((mod) => ({ default: mod.VllmIcon })),
|
||||
WatsonxAI: () =>
|
||||
import("@/icons/IBMWatsonx").then((mod) => ({
|
||||
default: mod.WatsonxAiIcon,
|
||||
})),
|
||||
Weaviate: () =>
|
||||
import("@/icons/Weaviate").then((mod) => ({ default: mod.WeaviateIcon })),
|
||||
Wikipedia: () =>
|
||||
import("@/icons/Wikipedia/Wikipedia").then((mod) => ({
|
||||
default: mod.default,
|
||||
})),
|
||||
Windsurf: () =>
|
||||
import("@/icons/Windsurf").then((mod) => ({ default: mod.WindsurfIcon })),
|
||||
Wolfram: () =>
|
||||
|
||||
@ -74,7 +74,7 @@ describe("ModelSelection", () => {
|
||||
render(<ModelSelection {...defaultProps} />);
|
||||
|
||||
expect(screen.getByTestId("llm-models-section")).toBeInTheDocument();
|
||||
expect(screen.getByText("LLM Models")).toBeInTheDocument();
|
||||
expect(screen.getByText("Language Models")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render Embeddings section when modelType is all", () => {
|
||||
|
||||
@ -14,7 +14,6 @@ import {
|
||||
usePostGlobalVariables,
|
||||
} from "@/controllers/API/queries/variables";
|
||||
import { useDebounce } from "@/hooks/use-debounce";
|
||||
import { useRefreshModelInputs } from "@/hooks/use-refresh-model-inputs";
|
||||
import ProviderList from "@/modals/modelProviderModal/components/ProviderList";
|
||||
import { Provider } from "@/modals/modelProviderModal/components/types";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
@ -49,7 +48,6 @@ const ModelProvidersContent = ({
|
||||
usePatchGlobalVariables();
|
||||
const { data: globalVariables = [] } = useGetGlobalVariables();
|
||||
const { mutate: updateEnabledModels } = useUpdateEnabledModels();
|
||||
const { refreshAllModelInputs } = useRefreshModelInputs();
|
||||
|
||||
const isPending = isCreating || isUpdating;
|
||||
|
||||
@ -239,20 +237,15 @@ const ModelProvidersContent = ({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (onClose) {
|
||||
refreshAllModelInputs({ silent: true });
|
||||
}
|
||||
};
|
||||
}, [onClose, refreshAllModelInputs]);
|
||||
// Note: refreshAllModelInputs is now called in ModelProviderModal's handleClose
|
||||
// to ensure reliable execution when the modal closes
|
||||
|
||||
return (
|
||||
<div className="flex flex-row w-full overflow-hidden">
|
||||
<div className="flex flex-row w-full h-full overflow-hidden">
|
||||
<div
|
||||
className={cn(
|
||||
"flex border-r p-2 flex-col transition-all duration-300 ease-in-out",
|
||||
selectedProvider ? "w-1/3" : "w-full",
|
||||
"flex p-2 flex-col transition-all duration-300 ease-in-out",
|
||||
selectedProvider ? "w-1/3 border-r" : "w-full",
|
||||
)}
|
||||
>
|
||||
<ProviderList
|
||||
@ -282,7 +275,18 @@ const ModelProvidersContent = ({
|
||||
{requiresApiKey ? (
|
||||
<>
|
||||
Add your{" "}
|
||||
<span className="underline cursor-pointer hover:text-primary">
|
||||
<span
|
||||
className="underline cursor-pointer hover:text-primary"
|
||||
onClick={() => {
|
||||
if (selectedProvider?.api_docs_url) {
|
||||
window.open(
|
||||
selectedProvider.api_docs_url,
|
||||
"_blank",
|
||||
"noopener,noreferrer",
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{selectedProvider?.provider} API key
|
||||
</span>{" "}
|
||||
to enable these models
|
||||
|
||||
@ -3,6 +3,7 @@ import { Switch } from "@/components/ui/switch";
|
||||
import { useGetEnabledModels } from "@/controllers/API/queries/models/use-get-enabled-models";
|
||||
|
||||
import { Model } from "@/modals/modelProviderModal/components/types";
|
||||
import { cn } from "@/utils/utils";
|
||||
|
||||
export interface ModelProviderSelectionProps {
|
||||
availableModels: Model[];
|
||||
@ -32,9 +33,13 @@ const ModelRow = ({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ForwardedIconComponent
|
||||
name={model.metadata?.icon || "Bot"}
|
||||
className="w-5 h-5"
|
||||
className={cn("w-5 h-5", { grayscale: !isEnabledModel })}
|
||||
/>
|
||||
<span className="text-sm">{model.model_name}</span>
|
||||
<span
|
||||
className={cn("text-sm", { "text-muted-foreground": !isEnabledModel })}
|
||||
>
|
||||
{model.model_name}
|
||||
</span>
|
||||
</div>
|
||||
{isEnabledModel && (
|
||||
<Switch
|
||||
@ -102,7 +107,7 @@ const ModelSelection = ({
|
||||
<div data-testid="model-provider-selection" className="flex flex-col gap-6">
|
||||
{modelType === "all" ? (
|
||||
<>
|
||||
{renderModelSection("LLM Models", llmModels, "llm")}
|
||||
{renderModelSection("Language Models", llmModels, "llm")}
|
||||
{renderModelSection(
|
||||
"Embedding Models",
|
||||
embeddingModels,
|
||||
@ -110,7 +115,7 @@ const ModelSelection = ({
|
||||
)}
|
||||
</>
|
||||
) : modelType === "llm" ? (
|
||||
renderModelSection("LLM Models", llmModels, "llm")
|
||||
renderModelSection("Language Models", llmModels, "llm")
|
||||
) : (
|
||||
renderModelSection("Embedding Models", embeddingModels, "embeddings")
|
||||
)}
|
||||
|
||||
@ -40,6 +40,7 @@ const ProviderList = ({
|
||||
is_enabled: provider.is_enabled,
|
||||
model_count: matchingModels.length,
|
||||
models: matchingModels,
|
||||
api_docs_url: provider.api_docs_url,
|
||||
};
|
||||
})
|
||||
.filter((provider) => provider.model_count > 0);
|
||||
|
||||
@ -12,6 +12,7 @@ export type Provider = {
|
||||
is_enabled: boolean;
|
||||
model_count?: number;
|
||||
models?: Model[];
|
||||
api_docs_url?: string;
|
||||
};
|
||||
|
||||
/** Map of provider -> model_name -> enabled status */
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Dialog, DialogContent, DialogHeader } from "@/components/ui/dialog";
|
||||
import { useRefreshModelInputs } from "@/hooks/use-refresh-model-inputs";
|
||||
import ModelProvidersContent from "./components/ModelProvidersContent";
|
||||
|
||||
interface ModelProviderModalProps {
|
||||
@ -12,8 +13,16 @@ const ModelProviderModal = ({
|
||||
onClose,
|
||||
modelType,
|
||||
}: ModelProviderModalProps) => {
|
||||
const { refreshAllModelInputs } = useRefreshModelInputs();
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
// Refresh after a delay to allow pending API operations to complete
|
||||
// This ensures model toggles are persisted before we fetch updated options
|
||||
// Using 1000ms to ensure database transactions complete for both LLM and embedding models
|
||||
setTimeout(() => {
|
||||
refreshAllModelInputs({ silent: true });
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -3,7 +3,7 @@ import ModelProvidersContent from "@/modals/modelProviderModal/components/ModelP
|
||||
|
||||
export default function ModelProvidersPage() {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col gap-6">
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<div className="flex w-full items-start justify-between gap-6">
|
||||
<div className="flex flex-col">
|
||||
<h2
|
||||
@ -21,7 +21,7 @@ export default function ModelProvidersPage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full border rounded-lg overflow-hidden">
|
||||
<div className="flex w-full h-[calc(100vh-305px)] border rounded-lg overflow-hidden">
|
||||
<ModelProvidersContent modelType="all" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -67,6 +67,17 @@ export type APIClassType = {
|
||||
| Array<{ types: Array<string>; selected?: string }>;
|
||||
};
|
||||
|
||||
export type ModelOptionType = {
|
||||
name: string;
|
||||
id?: string;
|
||||
icon?: string;
|
||||
provider?: string;
|
||||
metadata?: {
|
||||
is_disabled_provider?: boolean;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type InputFieldType = {
|
||||
type: string;
|
||||
required: boolean;
|
||||
|
||||
@ -68,7 +68,9 @@ test(
|
||||
await page.waitForTimeout(300);
|
||||
try {
|
||||
// [cmdk-item] targets dropdown options, not the search input
|
||||
const optionItem = page.locator(`[cmdk-item]:has-text("${fieldName}")`);
|
||||
const optionItem = page.locator(
|
||||
`[cmdk-item]:has-text("${fieldName}")`,
|
||||
);
|
||||
await optionItem.waitFor({ state: "visible", timeout: 2000 });
|
||||
await optionItem.click();
|
||||
return true;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -62,22 +62,27 @@ def get_model_provider_metadata():
|
||||
"OpenAI": {
|
||||
"icon": "OpenAI",
|
||||
"variable_name": "OPENAI_API_KEY",
|
||||
"api_docs_url": "https://platform.openai.com/docs/overview",
|
||||
},
|
||||
"Anthropic": {
|
||||
"icon": "Anthropic",
|
||||
"variable_name": "ANTHROPIC_API_KEY",
|
||||
"api_docs_url": "https://console.anthropic.com/docs",
|
||||
},
|
||||
"Google Generative AI": {
|
||||
"icon": "GoogleGenerativeAI",
|
||||
"variable_name": "GOOGLE_API_KEY",
|
||||
"api_docs_url": "https://aistudio.google.com/app/apikey",
|
||||
},
|
||||
"Ollama": {
|
||||
"icon": "Ollama",
|
||||
"variable_name": "OLLAMA_BASE_URL", # Ollama is local but can have custom URL
|
||||
"variable_name": "OLLAMA_BASE_URL",
|
||||
"api_docs_url": "https://ollama.com/",
|
||||
},
|
||||
"IBM WatsonX": {
|
||||
"icon": "WatsonxAI",
|
||||
"icon": "IBM",
|
||||
"variable_name": "WATSONX_APIKEY",
|
||||
"api_docs_url": "https://www.ibm.com/products/watsonx",
|
||||
},
|
||||
}
|
||||
|
||||
@ -1019,6 +1024,9 @@ def update_model_options_in_build_config(
|
||||
time_since_cache = time.time() - component.cache[cache_timestamp_key]
|
||||
cache_expired = time_since_cache > cache_ttl
|
||||
|
||||
# Check if is_refresh flag is set in build_config (from frontend refresh request)
|
||||
is_refresh_request = build_config.get("is_refresh", False)
|
||||
|
||||
# Check if we need to refresh
|
||||
should_refresh = (
|
||||
field_name == "api_key" # API key changed
|
||||
@ -1026,6 +1034,7 @@ def update_model_options_in_build_config(
|
||||
or field_name == "model" # Model field refresh button clicked
|
||||
or cache_key not in component.cache # Cache miss
|
||||
or cache_expired # Cache expired
|
||||
or is_refresh_request # Frontend requested a refresh
|
||||
)
|
||||
|
||||
if should_refresh:
|
||||
@ -1045,9 +1054,13 @@ def update_model_options_in_build_config(
|
||||
cached = component.cache.get(cache_key, {"options": []})
|
||||
build_config["model"]["options"] = cached["options"]
|
||||
|
||||
# Set default value on initial load when field is empty
|
||||
# Fetch from user's default model setting in the database
|
||||
if not field_value or field_value == "":
|
||||
# Set default value on initial load when model field is empty
|
||||
# Only set default when: initial load (field_name is None) or model field is being set and is empty
|
||||
# Get the current model value to check if it's empty
|
||||
current_model_value = build_config.get("model", {}).get("value")
|
||||
model_is_empty = not current_model_value or current_model_value == "" or current_model_value == []
|
||||
should_set_default = field_name is None or (field_name == "model" and model_is_empty)
|
||||
if should_set_default:
|
||||
options = cached.get("options", [])
|
||||
if options:
|
||||
# Determine model type based on cache_key_prefix
|
||||
|
||||
@ -4,7 +4,7 @@ WATSONX_DEFAULT_LLM_MODELS = [
|
||||
create_model_metadata(
|
||||
provider="IBM WatsonX",
|
||||
name="ibm/granite-3-2b-instruct",
|
||||
icon="WatsonxAI",
|
||||
icon="IBM",
|
||||
model_type="llm",
|
||||
tool_calling=True,
|
||||
default=True,
|
||||
@ -12,7 +12,7 @@ WATSONX_DEFAULT_LLM_MODELS = [
|
||||
create_model_metadata(
|
||||
provider="IBM WatsonX",
|
||||
name="ibm/granite-3-8b-instruct",
|
||||
icon="WatsonxAI",
|
||||
icon="IBM",
|
||||
model_type="llm",
|
||||
tool_calling=True,
|
||||
default=True,
|
||||
@ -20,7 +20,7 @@ WATSONX_DEFAULT_LLM_MODELS = [
|
||||
create_model_metadata(
|
||||
provider="IBM WatsonX",
|
||||
name="ibm/granite-13b-instruct-v2",
|
||||
icon="WatsonxAI",
|
||||
icon="IBM",
|
||||
model_type="llm",
|
||||
tool_calling=True,
|
||||
default=True,
|
||||
@ -31,7 +31,7 @@ WATSONX_DEFAULT_EMBEDDING_MODELS = [
|
||||
create_model_metadata(
|
||||
provider="IBM WatsonX",
|
||||
name="sentence-transformers/all-minilm-l12-v2",
|
||||
icon="WatsonxAI",
|
||||
icon="IBM",
|
||||
model_type="embeddings",
|
||||
tool_calling=True,
|
||||
default=True,
|
||||
@ -39,7 +39,7 @@ WATSONX_DEFAULT_EMBEDDING_MODELS = [
|
||||
create_model_metadata(
|
||||
provider="IBM WatsonX",
|
||||
name="ibm/slate-125m-english-rtrvr-v2",
|
||||
icon="WatsonxAI",
|
||||
icon="IBM",
|
||||
model_type="embeddings",
|
||||
tool_calling=True,
|
||||
default=True,
|
||||
@ -47,7 +47,7 @@ WATSONX_DEFAULT_EMBEDDING_MODELS = [
|
||||
create_model_metadata(
|
||||
provider="IBM WatsonX",
|
||||
name="ibm/slate-30m-english-rtrvr-v2",
|
||||
icon="WatsonxAI",
|
||||
icon="IBM",
|
||||
model_type="embeddings",
|
||||
tool_calling=True,
|
||||
default=True,
|
||||
@ -55,7 +55,7 @@ WATSONX_DEFAULT_EMBEDDING_MODELS = [
|
||||
create_model_metadata(
|
||||
provider="IBM WatsonX",
|
||||
name="intfloat/multilingual-e5-large",
|
||||
icon="WatsonxAI",
|
||||
icon="IBM",
|
||||
model_type="embeddings",
|
||||
tool_calling=True,
|
||||
default=True,
|
||||
|
||||
@ -116,8 +116,10 @@ class LanguageModelComponent(LCModelComponent):
|
||||
)
|
||||
|
||||
# Show/hide provider-specific fields based on selected model
|
||||
if field_name == "model" and isinstance(field_value, list) and len(field_value) > 0:
|
||||
selected_model = field_value[0]
|
||||
# Get current model value - from field_value if model is being changed, otherwise from build_config
|
||||
current_model_value = field_value if field_name == "model" else build_config.get("model", {}).get("value")
|
||||
if isinstance(current_model_value, list) and len(current_model_value) > 0:
|
||||
selected_model = current_model_value[0]
|
||||
provider = selected_model.get("provider", "")
|
||||
|
||||
# Show/hide watsonx fields
|
||||
|
||||
Reference in New Issue
Block a user