Compare commits

..

3 Commits

Author SHA1 Message Date
Raunak Bhagat
3bf53495f3 refactor: foldable model list in ModelSelectionField (#9996) 2026-04-08 18:32:58 +00:00
Wenxi
e4cfcda0bf fix: initialize tracing in Slack bot service (#9993)
Co-authored-by: Adam Serafin <aserafin@match-trade.com>
2026-04-08 17:46:56 +00:00
Raunak Bhagat
475e8f6cdc refactor: remove auto-refresh from LLM provider model selection (#9995) 2026-04-08 17:45:19 +00:00
9 changed files with 89 additions and 150 deletions

View File

@@ -90,6 +90,7 @@ from onyx.onyxbot.slack.utils import respond_in_thread_or_channel
from onyx.onyxbot.slack.utils import TenantSocketModeClient
from onyx.redis.redis_pool import get_redis_client
from onyx.server.manage.models import SlackBotTokens
from onyx.tracing.setup import setup_tracing
from onyx.utils.logger import setup_logger
from onyx.utils.variable_functionality import fetch_ee_implementation_or_noop
from onyx.utils.variable_functionality import set_is_ee_based_on_env_variable
@@ -1206,6 +1207,7 @@ if __name__ == "__main__":
tenant_handler = SlackbotHandler()
set_is_ee_based_on_env_variable()
setup_tracing()
try:
# Keep the main thread alive

View File

@@ -34,7 +34,6 @@ import { SvgAlertCircle } from "@opal/icons";
import { Content } from "@opal/layouts";
import { toast } from "@/hooks/useToast";
import { refreshLlmProviderCaches } from "@/lib/llmConfig/cache";
import useOnMount from "@/hooks/useOnMount";
const AWS_REGION_OPTIONS = [
{ name: "us-east-1", value: "us-east-1" },
@@ -123,17 +122,6 @@ function BedrockModalInternals({
formikProps.setFieldValue("model_configurations", models);
};
// Auto-fetch models on initial load when editing an existing provider
useOnMount(() => {
if (existingLlmProvider && !isFetchDisabled) {
handleFetchModels().catch((err) => {
toast.error(
err instanceof Error ? err.message : "Failed to fetch models"
);
});
}
});
return (
<>
<InputLayouts.FieldPadder>

View File

@@ -1,6 +1,5 @@
"use client";
import { useEffect } from "react";
import { markdown } from "@opal/utils";
import { useSWRConfig } from "swr";
import { useFormikContext } from "formik";
@@ -59,19 +58,6 @@ function BifrostModalInternals({
formikProps.setFieldValue("model_configurations", models);
};
// Auto-fetch models on initial load when editing an existing provider
useEffect(() => {
if (existingLlmProvider && !isFetchDisabled) {
handleFetchModels().catch((err) => {
console.error("Failed to fetch Bifrost models:", err);
toast.error(
err instanceof Error ? err.message : "Failed to fetch models"
);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<APIBaseField

View File

@@ -1,6 +1,5 @@
"use client";
import { useCallback, useEffect, useMemo } from "react";
import { useSWRConfig } from "swr";
import { useFormikContext } from "formik";
import * as InputLayouts from "@/layouts/input-layouts";
@@ -25,7 +24,6 @@ import {
ModalWrapper,
} from "@/sections/modals/llmConfig/shared";
import { fetchModels } from "@/app/admin/configuration/llm/utils";
import debounce from "lodash/debounce";
import { toast } from "@/hooks/useToast";
import { refreshLlmProviderCaches } from "@/lib/llmConfig/cache";
@@ -48,54 +46,23 @@ function LMStudioModalInternals({
isOnboarding,
}: LMStudioModalInternalsProps) {
const formikProps = useFormikContext<LMStudioModalValues>();
const initialApiKey = existingLlmProvider?.custom_config?.LM_STUDIO_API_KEY;
const doFetchModels = useCallback(
(apiBase: string, apiKey: string | undefined, signal: AbortSignal) => {
fetchModels(
LLMProviderName.LM_STUDIO,
{
api_base: apiBase,
custom_config: apiKey ? { LM_STUDIO_API_KEY: apiKey } : {},
api_key_changed: apiKey !== initialApiKey,
name: existingLlmProvider?.name,
},
signal
).then((data) => {
if (signal.aborted) return;
if (data.error) {
toast.error(data.error);
formikProps.setFieldValue("model_configurations", []);
return;
}
formikProps.setFieldValue("model_configurations", data.models);
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[existingLlmProvider?.name, initialApiKey]
);
const isFetchDisabled = !formikProps.values.api_base;
const debouncedFetchModels = useMemo(
() => debounce(doFetchModels, 500),
[doFetchModels]
);
const apiBase = formikProps.values.api_base;
const apiKey = formikProps.values.custom_config?.LM_STUDIO_API_KEY;
useEffect(() => {
if (apiBase) {
const controller = new AbortController();
debouncedFetchModels(apiBase, apiKey, controller.signal);
return () => {
debouncedFetchModels.cancel();
controller.abort();
};
} else {
formikProps.setFieldValue("model_configurations", []);
const handleFetchModels = async () => {
const apiKey = formikProps.values.custom_config?.LM_STUDIO_API_KEY;
const initialApiKey = existingLlmProvider?.custom_config?.LM_STUDIO_API_KEY;
const data = await fetchModels(LLMProviderName.LM_STUDIO, {
api_base: formikProps.values.api_base,
custom_config: apiKey ? { LM_STUDIO_API_KEY: apiKey } : {},
api_key_changed: apiKey !== initialApiKey,
name: existingLlmProvider?.name,
});
if (data.error) {
throw new Error(data.error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [apiBase, apiKey, debouncedFetchModels]);
formikProps.setFieldValue("model_configurations", data.models);
};
return (
<>
@@ -118,7 +85,10 @@ function LMStudioModalInternals({
)}
<InputLayouts.FieldSeparator />
<ModelSelectionField shouldShowAutoUpdateToggle={false} />
<ModelSelectionField
shouldShowAutoUpdateToggle={false}
onRefetch={isFetchDisabled ? undefined : handleFetchModels}
/>
{!isOnboarding && (
<>

View File

@@ -1,6 +1,5 @@
"use client";
import { useEffect } from "react";
import { useSWRConfig } from "swr";
import { useFormikContext } from "formik";
import * as InputLayouts from "@/layouts/input-layouts";
@@ -61,18 +60,6 @@ function LiteLLMProxyModalInternals({
formikProps.setFieldValue("model_configurations", models);
};
// Auto-fetch models on initial load when editing an existing provider
useEffect(() => {
if (existingLlmProvider && !isFetchDisabled) {
handleFetchModels().catch((err) => {
toast.error(
err instanceof Error ? err.message : "Failed to fetch models"
);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<APIBaseField

View File

@@ -30,7 +30,6 @@ import { Card } from "@opal/components";
import { toast } from "@/hooks/useToast";
import { refreshLlmProviderCaches } from "@/lib/llmConfig/cache";
import InputTypeInField from "@/refresh-components/form/InputTypeInField";
import useOnMount from "@/hooks/useOnMount";
const DEFAULT_API_BASE = "http://127.0.0.1:11434";
const CLOUD_API_BASE = "https://ollama.com";
@@ -87,17 +86,6 @@ function OllamaModalInternals({
formikProps.setFieldValue("model_configurations", models);
};
// Auto-fetch models on initial load when editing an existing provider
useOnMount(() => {
if (existingLlmProvider) {
handleFetchModels().catch((err) => {
toast.error(
err instanceof Error ? err.message : "Failed to fetch models"
);
});
}
});
return (
<>
<Card background="light" border="none" padding="sm">

View File

@@ -1,6 +1,5 @@
"use client";
import { useEffect } from "react";
import { markdown } from "@opal/utils";
import { useSWRConfig } from "swr";
import { useFormikContext } from "formik";
@@ -59,18 +58,6 @@ function OpenAICompatibleModalInternals({
formikProps.setFieldValue("model_configurations", models);
};
// Auto-fetch models on initial load when editing an existing provider
useEffect(() => {
if (existingLlmProvider && !isFetchDisabled) {
handleFetchModels().catch((err) => {
toast.error(
err instanceof Error ? err.message : "Failed to fetch models"
);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<APIBaseField

View File

@@ -1,6 +1,5 @@
"use client";
import { useEffect } from "react";
import { useSWRConfig } from "swr";
import { useFormikContext } from "formik";
import * as InputLayouts from "@/layouts/input-layouts";
@@ -61,18 +60,6 @@ function OpenRouterModalInternals({
formikProps.setFieldValue("model_configurations", models);
};
// Auto-fetch models on initial load when editing an existing provider
useEffect(() => {
if (existingLlmProvider && !isFetchDisabled) {
handleFetchModels().catch((err) => {
toast.error(
err instanceof Error ? err.message : "Failed to fetch models"
);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<APIBaseField

View File

@@ -3,6 +3,8 @@
import React, { useEffect, useRef, useState } from "react";
import { Formik, Form, useFormikContext } from "formik";
import type { FormikConfig } from "formik";
import { cn } from "@/lib/utils";
import { Interactive } from "@opal/core";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
import { useAgents } from "@/hooks/useAgents";
import { useUserGroups } from "@/lib/hooks";
@@ -23,6 +25,7 @@ import { Section } from "@/layouts/general-layouts";
import { Content } from "@opal/layouts";
import {
SvgArrowExchange,
SvgChevronDown,
SvgOnyxOctagon,
SvgOrganization,
SvgPlusCircle,
@@ -407,6 +410,8 @@ function RefetchButton({ onRefetch }: RefetchButtonProps) {
// ─── ModelsField ─────────────────────────────────────────────────────
const FOLD_THRESHOLD = 3;
export interface ModelSelectionFieldProps {
shouldShowAutoUpdateToggle: boolean;
onRefetch?: (signal: AbortSignal) => Promise<void> | void;
@@ -420,6 +425,7 @@ export function ModelSelectionField({
}: ModelSelectionFieldProps) {
const formikProps = useFormikContext<BaseLLMFormValues>();
const [newModelName, setNewModelName] = useState("");
const [isExpanded, setIsExpanded] = useState(false);
const isAutoMode = formikProps.values.is_auto_mode;
const models = formikProps.values.model_configurations;
@@ -487,7 +493,7 @@ export function ModelSelectionField({
size="md"
onClick={handleToggleSelectAll}
>
{allSelected ? "Unselect All" : "Select All"}
{allSelected ? "Deselect All" : "Select All"}
</Button>
{onRefetch && <RefetchButton onRefetch={onRefetch} />}
</Section>
@@ -497,30 +503,68 @@ export function ModelSelectionField({
<EmptyMessageCard title="No models available." padding="sm" />
) : (
<Section gap={0.25}>
{isAutoMode
? visibleModels.map((model) => (
<LineItemButton
key={model.name}
variant="section"
sizePreset="main-ui"
selectVariant="select-heavy"
state="selected"
icon={() => <Checkbox checked />}
title={model.display_name || model.name}
/>
))
: models.map((model) => (
<LineItemButton
key={model.name}
variant="section"
sizePreset="main-ui"
selectVariant="select-heavy"
state={model.is_visible ? "selected" : "empty"}
icon={() => <Checkbox checked={model.is_visible} />}
title={model.name}
onClick={() => setVisibility(model.name, !model.is_visible)}
/>
))}
{(() => {
const displayModels = isAutoMode ? visibleModels : models;
const isFoldable = displayModels.length > FOLD_THRESHOLD;
const shownModels =
isFoldable && !isExpanded
? displayModels.slice(0, FOLD_THRESHOLD)
: displayModels;
return (
<>
{shownModels.map((model) =>
isAutoMode ? (
<LineItemButton
key={model.name}
variant="section"
sizePreset="main-ui"
selectVariant="select-heavy"
state="selected"
icon={() => <Checkbox checked />}
title={model.display_name || model.name}
/>
) : (
<LineItemButton
key={model.name}
variant="section"
sizePreset="main-ui"
selectVariant="select-heavy"
state={model.is_visible ? "selected" : "empty"}
icon={() => <Checkbox checked={model.is_visible} />}
title={model.name}
onClick={() =>
setVisibility(model.name, !model.is_visible)
}
/>
)
)}
{isFoldable && (
<Interactive.Stateless
prominence="tertiary"
onClick={() => setIsExpanded(!isExpanded)}
>
<Interactive.Container type="button" widthVariant="full">
<Content
sizePreset="secondary"
variant="body"
title={isExpanded ? "Fold Models" : "More Models"}
icon={() => (
<SvgChevronDown
className={cn(
"transition-transform",
isExpanded && "-rotate-180"
)}
size={14}
/>
)}
/>
</Interactive.Container>
</Interactive.Stateless>
)}
</>
);
})()}
</Section>
)}