mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-10-19 01:50:51 -05:00
fix(web): address review follow-ups from the lib/languageModels cleanup (#15127)
This commit is contained in:
1 parent
656c5ac77c
commit
0371b66696
21 files changed
+153
-103
No files matched your search
@@ -5,7 +5,7 @@ import { Button } from "@opal/components";
|
||||
import { Text } from "@opal/components";
|
||||
import { ContentAction } from "@opal/layouts";
|
||||
import { SvgChevronLeft, SvgChevronRight, SvgEyeOff, SvgX } from "@opal/icons";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
import AgentMessage, {
|
||||
AgentMessageProps,
|
||||
} from "@/app/app/message/messageComponents/AgentMessage";
|
||||
|
||||
@@ -35,7 +35,7 @@ import TTSButton from "@/app/app/message/messageComponents/TTSButton";
|
||||
import { useVoiceMode } from "@/providers/VoiceModeProvider";
|
||||
import { useVoiceStatus } from "@/hooks/useVoiceStatus";
|
||||
import { findModelConfigId } from "@/lib/languageModels/options";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
|
||||
interface SouurcesTagWrapperProps {
|
||||
citations: StreamingCitation[];
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
setStoredRecommendedModelsOnly,
|
||||
} from "@/app/craft/utils/llmPreferences";
|
||||
import { useUser } from "@/providers/UserProvider";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
import { AGGREGATOR_PROVIDERS } from "@/lib/languageModels/svc";
|
||||
import { Section } from "@/layouts/general-layouts";
|
||||
import {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SelectButton } from "@opal/components";
|
||||
import { cn } from "@opal/utils";
|
||||
import { BuildLLMPopover } from "@/app/craft/components/BuildLLMPopover";
|
||||
import { useLanguageModels } from "@/lib/languageModels/hooks";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
import { BuildLlmSelection } from "@/app/craft/onboarding/constants";
|
||||
import { getPreferredLlmSelection } from "@/app/craft/utils/llmPreferences";
|
||||
import { useUser } from "@/providers/UserProvider";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { SvgCpu } from "@opal/icons";
|
||||
import { Divider } from "@opal/components";
|
||||
import { ContentAction } from "@opal/layouts";
|
||||
import LLMProviderCard from "@/sections/onboarding/components/LLMProviderCard";
|
||||
import { getProvider } from "@/lib/languageModels";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import { useLLMProviderOptions } from "@/lib/hooks/useLLMProviderOptions";
|
||||
import {
|
||||
CRAFT_PROVIDERS,
|
||||
|
||||
@@ -6,6 +6,7 @@ import useSWR from "swr";
|
||||
import {
|
||||
useLanguageModels,
|
||||
useLanguageModelsForAgent,
|
||||
useVisionLanguageModels,
|
||||
} from "@/lib/languageModels/hooks";
|
||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||
|
||||
@@ -75,6 +76,48 @@ describe("useLanguageModels", () => {
|
||||
expect(mockMutate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("useVisionLanguageModels keeps only visible image-input models", () => {
|
||||
const model = (
|
||||
name: string,
|
||||
is_visible: boolean,
|
||||
supports_image_input: boolean
|
||||
) => ({ name, is_visible, supports_image_input });
|
||||
const providers = [
|
||||
{
|
||||
name: "Mixed",
|
||||
model_configurations: [
|
||||
model("vision-visible", true, true),
|
||||
model("vision-hidden", false, true),
|
||||
model("text-only", true, false),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Text Only",
|
||||
model_configurations: [model("text-a", true, false)],
|
||||
},
|
||||
{
|
||||
name: "Hidden Vision",
|
||||
model_configurations: [model("vision-b", false, true)],
|
||||
},
|
||||
];
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: { providers, default_text: null, default_vision: null },
|
||||
error: undefined,
|
||||
mutate: jest.fn(),
|
||||
isValidating: false,
|
||||
} as any);
|
||||
|
||||
const { result } = renderHook(() => useVisionLanguageModels());
|
||||
|
||||
expect(result.current.llmProviders).toHaveLength(1);
|
||||
expect(result.current.llmProviders?.[0]?.name).toBe("Mixed");
|
||||
expect(
|
||||
result.current.llmProviders?.[0]?.model_configurations.map(
|
||||
(mc) => mc.name
|
||||
)
|
||||
).toEqual(["vision-visible"]);
|
||||
});
|
||||
|
||||
test("reports not loading when SWR returns an error", () => {
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: undefined,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { defaultTailwindCSS } from "@/components/icons/icons";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { IconProps } from "@opal/types";
|
||||
import type { ModelIconProps } from "@/lib/languageModels/types";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
import type { IconProps } from "@opal/types";
|
||||
|
||||
export interface ModelIconProps extends IconProps {
|
||||
provider: string;
|
||||
modelName?: string;
|
||||
}
|
||||
|
||||
export function ModelIcon({
|
||||
provider,
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export { ModelIcon } from "@/lib/languageModels/components/ModelIcon";
|
||||
export {
|
||||
ModelIcon,
|
||||
type ModelIconProps,
|
||||
} from "@/lib/languageModels/components/ModelIcon";
|
||||
@@ -21,11 +21,7 @@ import {
|
||||
SvgVercel,
|
||||
} from "@opal/logos";
|
||||
import { ZAIIcon } from "@/components/icons/icons";
|
||||
import {
|
||||
LLMProviderFormProps,
|
||||
LLMProviderName,
|
||||
} from "@/lib/languageModels/types";
|
||||
import type { LLMProviderView } from "@/lib/languageModels/types";
|
||||
import { LLMProviderName } from "@/lib/languageModels/types";
|
||||
import OpenAIModal from "@/sections/modals/languageModels/OpenAIModal";
|
||||
import AnthropicModal from "@/sections/modals/languageModels/AnthropicModal";
|
||||
import OllamaModal from "@/sections/modals/languageModels/OllamaModal";
|
||||
@@ -45,7 +41,7 @@ import type { ProviderEntry } from "@/lib/languageModels/types";
|
||||
|
||||
// ─── Text (LLM) providers ────────────────────────────────────────────────────
|
||||
|
||||
const PROVIDERS: Record<string, ProviderEntry> = {
|
||||
export const PROVIDERS: Record<string, ProviderEntry> = {
|
||||
[LLMProviderName.OPENAI]: {
|
||||
icon: SvgOpenai,
|
||||
productName: "GPT",
|
||||
@@ -144,7 +140,7 @@ const PROVIDERS: Record<string, ProviderEntry> = {
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_ENTRY: ProviderEntry = {
|
||||
export const DEFAULT_ENTRY: ProviderEntry = {
|
||||
icon: SvgCpu,
|
||||
productName: "",
|
||||
companyName: "",
|
||||
@@ -153,37 +149,13 @@ const DEFAULT_ENTRY: ProviderEntry = {
|
||||
|
||||
// Providers that don't use custom_config themselves, so a non-empty
|
||||
// custom_config means the provider was originally created via CustomModal.
|
||||
const CUSTOM_CONFIG_OVERRIDES = new Set<string>([
|
||||
export const CUSTOM_CONFIG_OVERRIDES = new Set<string>([
|
||||
LLMProviderName.OPENAI,
|
||||
LLMProviderName.ANTHROPIC,
|
||||
LLMProviderName.AZURE,
|
||||
LLMProviderName.OPENROUTER,
|
||||
]);
|
||||
|
||||
export function getProvider(
|
||||
providerName: string,
|
||||
existingProvider?: LLMProviderView
|
||||
): ProviderEntry {
|
||||
const entry = PROVIDERS[providerName] ?? {
|
||||
...DEFAULT_ENTRY,
|
||||
productName: providerName,
|
||||
companyName: providerName,
|
||||
};
|
||||
|
||||
// An empty custom_config carries no signal of origin. Only a non-empty map
|
||||
// marks a provider created via the custom form.
|
||||
const customConfig = existingProvider?.custom_config;
|
||||
if (
|
||||
customConfig != null &&
|
||||
Object.keys(customConfig).length > 0 &&
|
||||
CUSTOM_CONFIG_OVERRIDES.has(providerName)
|
||||
) {
|
||||
return { ...entry, Modal: CustomModal };
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// ─── Aggregator providers ────────────────────────────────────────────────────
|
||||
// Providers that host models from multiple vendors (e.g. Bedrock hosts Claude,
|
||||
// Llama, etc.) Used by the model-icon resolver to prioritise vendor icons.
|
||||
@@ -203,9 +175,9 @@ export const AGGREGATOR_PROVIDERS = new Set([
|
||||
LLMProviderName.VERTEX_AI,
|
||||
]);
|
||||
|
||||
// ─── Model-aware icon resolver ───────────────────────────────────────────────
|
||||
// ─── Model icons ─────────────────────────────────────────────────────────────
|
||||
|
||||
const MODEL_ICON_MAP: Record<string, IconFunctionComponent> = {
|
||||
export const MODEL_ICON_MAP: Record<string, IconFunctionComponent> = {
|
||||
[LLMProviderName.OPENAI]: SvgOpenai,
|
||||
[LLMProviderName.ANTHROPIC]: SvgClaude,
|
||||
[LLMProviderName.OLLAMA_CHAT]: SvgOllama,
|
||||
@@ -239,45 +211,3 @@ const MODEL_ICON_MAP: Record<string, IconFunctionComponent> = {
|
||||
zai: ZAIIcon,
|
||||
bedrock_converse: SvgAws,
|
||||
};
|
||||
|
||||
/**
|
||||
* Model-aware icon resolver that checks both provider name and model name
|
||||
* to pick the most specific icon (e.g. Claude icon for a Bedrock Claude model).
|
||||
*/
|
||||
export function getModelIcon(
|
||||
providerName: string,
|
||||
modelName?: string
|
||||
): IconFunctionComponent {
|
||||
const lowerProviderName = providerName.toLowerCase();
|
||||
|
||||
// For aggregator providers, prioritise showing the vendor icon based on model name
|
||||
if (AGGREGATOR_PROVIDERS.has(lowerProviderName) && modelName) {
|
||||
const lowerModelName = modelName.toLowerCase();
|
||||
for (const [key, icon] of Object.entries(MODEL_ICON_MAP)) {
|
||||
if (lowerModelName.includes(key)) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if provider name directly matches an icon
|
||||
if (lowerProviderName in MODEL_ICON_MAP) {
|
||||
const icon = MODEL_ICON_MAP[lowerProviderName];
|
||||
if (icon) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
// For non-aggregator providers, check if model name contains any of the keys
|
||||
if (modelName) {
|
||||
const lowerModelName = modelName.toLowerCase();
|
||||
for (const [key, icon] of Object.entries(MODEL_ICON_MAP)) {
|
||||
if (lowerModelName.includes(key)) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to CPU icon if no matches
|
||||
return SvgCpu;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import type { IconProps } from "@opal/types";
|
||||
import { getModelIcon, getProvider } from "@/lib/languageModels";
|
||||
import { getModelIcon, getProvider } from "@/lib/languageModels/utils";
|
||||
import { AGGREGATOR_PROVIDERS } from "@/lib/languageModels/svc";
|
||||
import type {
|
||||
LLMOption,
|
||||
|
||||
@@ -422,8 +422,3 @@ export interface ProviderEntry {
|
||||
companyName: string;
|
||||
Modal: React.ComponentType<LLMProviderFormProps>;
|
||||
}
|
||||
|
||||
export interface ModelIconProps extends IconProps {
|
||||
provider: string;
|
||||
modelName?: string;
|
||||
}
|
||||
@@ -1,8 +1,20 @@
|
||||
import type { IconFunctionComponent } from "@opal/types";
|
||||
import { SvgCpu } from "@opal/icons";
|
||||
import { MinimalAgent } from "@/lib/agents/types";
|
||||
import {
|
||||
AGGREGATOR_PROVIDERS,
|
||||
CUSTOM_CONFIG_OVERRIDES,
|
||||
DEFAULT_ENTRY,
|
||||
MODEL_ICON_MAP,
|
||||
PROVIDERS,
|
||||
} from "@/lib/languageModels/constants";
|
||||
import CustomModal from "@/sections/modals/languageModels/CustomModal";
|
||||
import type {
|
||||
DefaultModel,
|
||||
LLMProviderDescriptor,
|
||||
LLMProviderView,
|
||||
ModelConfiguration,
|
||||
ProviderEntry,
|
||||
} from "@/lib/languageModels/types";
|
||||
import { LlmDescriptor } from "@/lib/hooks";
|
||||
|
||||
@@ -207,3 +219,69 @@ export function getDisplayName(
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getProvider(
|
||||
providerName: string,
|
||||
existingProvider?: LLMProviderView
|
||||
): ProviderEntry {
|
||||
const entry = PROVIDERS[providerName] ?? {
|
||||
...DEFAULT_ENTRY,
|
||||
productName: providerName,
|
||||
companyName: providerName,
|
||||
};
|
||||
|
||||
// An empty custom_config carries no signal of origin. Only a non-empty map
|
||||
// marks a provider created via the custom form.
|
||||
const customConfig = existingProvider?.custom_config;
|
||||
if (
|
||||
customConfig != null &&
|
||||
Object.keys(customConfig).length > 0 &&
|
||||
CUSTOM_CONFIG_OVERRIDES.has(providerName)
|
||||
) {
|
||||
return { ...entry, Modal: CustomModal };
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model-aware icon resolver that checks both provider name and model name
|
||||
* to pick the most specific icon (e.g. Claude icon for a Bedrock Claude model).
|
||||
*/
|
||||
export function getModelIcon(
|
||||
providerName: string,
|
||||
modelName?: string
|
||||
): IconFunctionComponent {
|
||||
const lowerProviderName = providerName.toLowerCase();
|
||||
|
||||
// For aggregator providers, prioritise showing the vendor icon based on model name
|
||||
if (AGGREGATOR_PROVIDERS.has(lowerProviderName) && modelName) {
|
||||
const lowerModelName = modelName.toLowerCase();
|
||||
for (const [key, icon] of Object.entries(MODEL_ICON_MAP)) {
|
||||
if (lowerModelName.includes(key)) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if provider name directly matches an icon
|
||||
if (lowerProviderName in MODEL_ICON_MAP) {
|
||||
const icon = MODEL_ICON_MAP[lowerProviderName];
|
||||
if (icon) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
// For non-aggregator providers, check if model name contains any of the keys
|
||||
if (modelName) {
|
||||
const lowerModelName = modelName.toLowerCase();
|
||||
for (const [key, icon] of Object.entries(MODEL_ICON_MAP)) {
|
||||
if (lowerModelName.includes(key)) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to CPU icon if no matches
|
||||
return SvgCpu;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { getProvider } from "@/lib/languageModels";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import { LLMProviderFormProps } from "@/lib/languageModels/types";
|
||||
|
||||
interface ProviderSetupModalProps extends Omit<
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
type ModelSettingsPatch,
|
||||
} from "@/sections/modals/languageModels/ModelSettingsPopover";
|
||||
import { setDefaultLlmModelAndRefresh } from "@/lib/languageModels/cache";
|
||||
import { modelDisplayName } from "@/lib/languageModels/utils";
|
||||
import { getProvider, modelDisplayName } from "@/lib/languageModels/utils";
|
||||
import { useAdminLanguageModels } from "@/lib/languageModels/hooks";
|
||||
import { useSWRConfig } from "swr";
|
||||
import {
|
||||
@@ -65,7 +65,6 @@ import { SvgEdit } from "@opal/icons";
|
||||
import AgentAvatar from "@/refresh-components/avatars/AgentAvatar";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import { Modal } from "@opal/components";
|
||||
import { getProvider } from "@/lib/languageModels";
|
||||
import { useSettings } from "@/lib/settings/hooks";
|
||||
|
||||
// ─── DisplayNameField ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useState, useCallback, useMemo, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Popover, OpenButton } from "@opal/components";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
import { GLOBAL_DEFAULT_LLM_OPTION } from "@/lib/languageModels/options";
|
||||
import {
|
||||
useLanguageModels,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useMemo, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
import {
|
||||
Button,
|
||||
SelectButton,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
OnboardingStep,
|
||||
} from "@/interfaces/onboarding";
|
||||
import { WellKnownLLMProviderDescriptor } from "@/lib/languageModels/types";
|
||||
import { getProvider } from "@/lib/languageModels";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import ProviderSetupModal from "@/sections/modals/languageModels/ProviderSetupModal";
|
||||
import { Disabled } from "@opal/core";
|
||||
import { ModelIcon } from "@/lib/languageModels/components";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button, Modal, ProgressBar, Text, Tooltip } from "@opal/components";
|
||||
import { Section } from "@opal/layouts";
|
||||
import type { IconFunctionComponent } from "@opal/types";
|
||||
import { formatCalendarDay } from "@/lib/dateUtils";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
import type { UsageExportUser } from "@/lib/usage/userUsage";
|
||||
import { formatCost, formatTokens } from "@/lib/utils";
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Hoverable } from "@opal/core";
|
||||
import { SvgCheck, SvgEdit, SvgPlus, SvgTrash, SvgX } from "@opal/icons";
|
||||
import { markdown } from "@opal/utils";
|
||||
import ModelSelector from "@/sections/model-selector/ModelSelector";
|
||||
import { getProvider } from "@/lib/languageModels";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import type { LLMOption } from "@/lib/languageModels/types";
|
||||
import { useAdminLanguageModels } from "@/lib/languageModels/hooks";
|
||||
import * as GeneralLayouts from "@/layouts/general-layouts";
|
||||
|
||||
@@ -29,7 +29,7 @@ import { SvgSlash, SvgUnplug } from "@opal/icons";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { getImageGenForm } from "@/views/admin/ImageGenerationPage/forms";
|
||||
import ProviderCard from "@/sections/admin/ProviderCard";
|
||||
import { getModelIcon } from "@/lib/languageModels";
|
||||
import { getModelIcon } from "@/lib/languageModels/utils";
|
||||
|
||||
const NO_DEFAULT_VALUE = "__none__";
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { SvgArrowExchange, SvgSettings, SvgTrash } from "@opal/icons";
|
||||
import { SettingsLayouts } from "@opal/layouts";
|
||||
import { ADMIN_ROUTES } from "@/lib/admin-routes";
|
||||
import * as GeneralLayouts from "@/layouts/general-layouts";
|
||||
import { getProvider } from "@/lib/languageModels";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import {
|
||||
refreshLlmProviderCaches,
|
||||
setDefaultLlmModelAndRefresh,
|
||||
|
||||
Reference in new issue
Block a user