mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-04-11 18:02:42 +00:00
Compare commits
2 Commits
edge
...
nikg/multi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7baad34266 | ||
|
|
9af9148ca7 |
@@ -1,6 +1,7 @@
|
||||
import Modal from "@/refresh-components/Modal";
|
||||
import { Button } from "@opal/components";
|
||||
import { CloudEmbeddingModel } from "../../../../components/embedding/interfaces";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { SvgCheck } from "@opal/icons";
|
||||
|
||||
export interface AlreadyPickedModalProps {
|
||||
@@ -17,7 +18,7 @@ export default function AlreadyPickedModal({
|
||||
<Modal.Content width="sm" height="sm">
|
||||
<Modal.Header
|
||||
icon={SvgCheck}
|
||||
title={`${model.model_name} already chosen`}
|
||||
title={markdown(`*${model.model_name}* already chosen`)}
|
||||
description="You can select a different one if you want!"
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getFormattedProviderName,
|
||||
} from "@/components/embedding/interfaces";
|
||||
import { EMBEDDING_PROVIDERS_ADMIN_URL } from "@/lib/llmConfig/constants";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { mutate } from "swr";
|
||||
import { SWR_KEYS } from "@/lib/swr-keys";
|
||||
import { testEmbedding } from "@/app/admin/embeddings/pages/utils";
|
||||
@@ -172,9 +173,11 @@ export default function ChangeCredentialsModal({
|
||||
<Modal.Content>
|
||||
<Modal.Header
|
||||
icon={SvgSettings}
|
||||
title={`Modify your ${getFormattedProviderName(
|
||||
provider.provider_type
|
||||
)} ${isProxy ? "Configuration" : "key"}`}
|
||||
title={markdown(
|
||||
`Modify your *${getFormattedProviderName(
|
||||
provider.provider_type
|
||||
)}* ${isProxy ? "configuration" : "key"}`
|
||||
)}
|
||||
onClose={onCancel}
|
||||
/>
|
||||
<Modal.Body>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getFormattedProviderName,
|
||||
} from "../../../../components/embedding/interfaces";
|
||||
import { SvgTrash } from "@opal/icons";
|
||||
import { markdown } from "@opal/utils";
|
||||
|
||||
export interface DeleteCredentialsModalProps {
|
||||
modelProvider: CloudEmbeddingProvider;
|
||||
@@ -24,9 +25,11 @@ export default function DeleteCredentialsModal({
|
||||
<Modal.Content width="sm" height="sm">
|
||||
<Modal.Header
|
||||
icon={SvgTrash}
|
||||
title={`Delete ${getFormattedProviderName(
|
||||
modelProvider.provider_type
|
||||
)} Credentials?`}
|
||||
title={markdown(
|
||||
`Delete *${getFormattedProviderName(
|
||||
modelProvider.provider_type
|
||||
)}* credentials?`
|
||||
)}
|
||||
onClose={onCancel}
|
||||
/>
|
||||
<Modal.Body>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "@/components/embedding/interfaces";
|
||||
import { EMBEDDING_PROVIDERS_ADMIN_URL } from "@/lib/llmConfig/constants";
|
||||
import Modal from "@/refresh-components/Modal";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { SvgSettings } from "@opal/icons";
|
||||
import SimpleLoader from "@/refresh-components/loaders/SimpleLoader";
|
||||
export interface ProviderCreationModalProps {
|
||||
@@ -185,9 +186,11 @@ export default function ProviderCreationModal({
|
||||
<Modal.Content width="sm" height="sm">
|
||||
<Modal.Header
|
||||
icon={SvgSettings}
|
||||
title={`Configure ${getFormattedProviderName(
|
||||
selectedProvider.provider_type
|
||||
)}`}
|
||||
title={markdown(
|
||||
`Configure *${getFormattedProviderName(
|
||||
selectedProvider.provider_type
|
||||
)}*`
|
||||
)}
|
||||
onClose={onCancel}
|
||||
/>
|
||||
<Modal.Body>
|
||||
|
||||
@@ -2,6 +2,7 @@ import Modal from "@/refresh-components/Modal";
|
||||
import { Button } from "@opal/components";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import { CloudEmbeddingModel } from "@/components/embedding/interfaces";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { SvgServer } from "@opal/icons";
|
||||
|
||||
export interface SelectModelModalProps {
|
||||
@@ -20,7 +21,7 @@ export default function SelectModelModal({
|
||||
<Modal.Content width="sm" height="sm">
|
||||
<Modal.Header
|
||||
icon={SvgServer}
|
||||
title={`Select ${model.model_name}`}
|
||||
title={markdown(`Select *${model.model_name}*`)}
|
||||
onClose={onCancel}
|
||||
/>
|
||||
<Modal.Body>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { toast } from "@/hooks/useToast";
|
||||
import { markdown } from "@opal/utils";
|
||||
|
||||
import EmbeddingModelSelection from "../EmbeddingModelSelectionForm";
|
||||
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
||||
@@ -538,7 +539,9 @@ export default function EmbeddingForm() {
|
||||
<Modal.Content>
|
||||
<Modal.Header
|
||||
icon={SvgAlertTriangle}
|
||||
title={`Are you sure you want to select ${selectedProvider.model_name}?`}
|
||||
title={markdown(
|
||||
`Are you sure you want to select *${selectedProvider.model_name}*?`
|
||||
)}
|
||||
onClose={() => setShowPoorModel(false)}
|
||||
/>
|
||||
<Modal.Body>
|
||||
|
||||
@@ -210,8 +210,10 @@ export default function MultiModelResponseView({
|
||||
const response = responses.find((r) => r.modelIndex === modelIndex);
|
||||
if (!response) return;
|
||||
|
||||
// Persist preferred response to backend + update local tree so the
|
||||
// input bar unblocks (awaitingPreferredSelection clears).
|
||||
// Persist preferred response + sync `latestChildNodeId`. Backend's
|
||||
// `set_preferred_response` updates `latest_child_message_id`; if the
|
||||
// frontend chain walk disagrees, the next follow-up fails with
|
||||
// "not on the latest mainline".
|
||||
if (parentMessage?.messageId && response.messageId && currentSessionId) {
|
||||
setPreferredResponse(parentMessage.messageId, response.messageId).catch(
|
||||
(err) => console.error("Failed to persist preferred response:", err)
|
||||
@@ -227,6 +229,7 @@ export default function MultiModelResponseView({
|
||||
updated.set(parentMessage.nodeId, {
|
||||
...userMsg,
|
||||
preferredResponseId: response.messageId,
|
||||
latestChildNodeId: response.nodeId,
|
||||
});
|
||||
updateSessionMessageTree(currentSessionId, updated);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ function DeleteConfirmModal({ hook, onDelete }: DeleteConfirmModalProps) {
|
||||
<Modal.Header
|
||||
// TODO(@raunakab): replace the colour of this SVG with red.
|
||||
icon={SvgTrash}
|
||||
title={`Delete ${hook.name}`}
|
||||
title={markdown(`Delete *${hook.name}*`)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
<Modal.Body>
|
||||
|
||||
@@ -694,6 +694,25 @@ export function useLlmManager(
|
||||
prevAgentIdRef.current = liveAgent?.id;
|
||||
}, [liveAgent?.id]);
|
||||
|
||||
// Clear manual override when arriving at a *different* existing session
|
||||
// from any previously-seen defined session. Tracks only the last
|
||||
// *defined* session id so a round-trip through new-chat (A → undefined
|
||||
// → B) still resets, while A → undefined (new-chat) preserves it.
|
||||
const prevDefinedSessionIdRef = useRef<string | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
const nextId = currentChatSession?.id;
|
||||
if (
|
||||
nextId !== undefined &&
|
||||
prevDefinedSessionIdRef.current !== undefined &&
|
||||
nextId !== prevDefinedSessionIdRef.current
|
||||
) {
|
||||
setUserHasManuallyOverriddenLLM(false);
|
||||
}
|
||||
if (nextId !== undefined) {
|
||||
prevDefinedSessionIdRef.current = nextId;
|
||||
}
|
||||
}, [currentChatSession?.id]);
|
||||
|
||||
function getValidLlmDescriptor(
|
||||
modelName: string | null | undefined
|
||||
): LlmDescriptor {
|
||||
@@ -715,8 +734,9 @@ export function useLlmManager(
|
||||
|
||||
if (llmProviders === undefined || llmProviders === null) {
|
||||
resolved = manualLlm;
|
||||
} else if (userHasManuallyOverriddenLLM && !currentChatSession) {
|
||||
// User has overridden in this session and switched to a new session
|
||||
} else if (userHasManuallyOverriddenLLM) {
|
||||
// Manual override wins over session's `current_alternate_model`.
|
||||
// Cleared on cross-session navigation by the effect above.
|
||||
resolved = manualLlm;
|
||||
} else if (currentChatSession?.current_alternate_model) {
|
||||
resolved = getValidLlmDescriptorForProviders(
|
||||
@@ -728,8 +748,6 @@ export function useLlmManager(
|
||||
liveAgent.llm_model_version_override,
|
||||
llmProviders
|
||||
);
|
||||
} else if (userHasManuallyOverriddenLLM) {
|
||||
resolved = manualLlm;
|
||||
} else if (user?.preferences?.default_model) {
|
||||
resolved = getValidLlmDescriptorForProviders(
|
||||
user.preferences.default_model,
|
||||
|
||||
@@ -159,9 +159,12 @@ export default function ModelSelector({
|
||||
);
|
||||
|
||||
if (!isMultiModel) {
|
||||
// Stable key — keying on model would unmount the pill
|
||||
// on change and leave Radix's anchorRef detached,
|
||||
// flashing the closing popover at (0,0).
|
||||
return (
|
||||
<OpenButton
|
||||
key={modelKey(model.provider, model.modelName)}
|
||||
key="single-model-pill"
|
||||
icon={ProviderIcon}
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
handlePillClick(index, e.currentTarget as HTMLElement)
|
||||
|
||||
@@ -425,17 +425,32 @@ export default function AppPage({ firstMessage }: ChatPageProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [multiModel.isMultiModelActive]);
|
||||
|
||||
// Sync single-model selection to llmManager so the submission path
|
||||
// uses the correct provider/version (replaces the old LLMPopover sync).
|
||||
// Sync single-model selection to llmManager so the submission path uses
|
||||
// the correct provider/version. Guard against echoing derived state back
|
||||
// — only call updateCurrentLlm when the selection actually differs from
|
||||
// currentLlm, otherwise the initial [] → [currentLlmModel] sync would
|
||||
// pin `userHasManuallyOverriddenLLM=true` with whatever was resolved
|
||||
// first (often the default model before the session's alt_model loads).
|
||||
useEffect(() => {
|
||||
if (multiModel.selectedModels.length === 1) {
|
||||
const model = multiModel.selectedModels[0]!;
|
||||
llmManager.updateCurrentLlm({
|
||||
name: model.name,
|
||||
provider: model.provider,
|
||||
modelName: model.modelName,
|
||||
});
|
||||
const current = llmManager.currentLlm;
|
||||
if (
|
||||
model.provider !== current.provider ||
|
||||
model.modelName !== current.modelName ||
|
||||
model.name !== current.name
|
||||
) {
|
||||
llmManager.updateCurrentLlm({
|
||||
name: model.name,
|
||||
provider: model.provider,
|
||||
modelName: model.modelName,
|
||||
});
|
||||
}
|
||||
}
|
||||
// llmManager intentionally omitted from deps — including it would
|
||||
// re-run this effect whenever currentLlm changes, creating the
|
||||
// feedback loop the guard above is designed to break.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [multiModel.selectedModels]);
|
||||
|
||||
const {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { usePathname, useRouter } from "next/navigation";
|
||||
import * as InputLayouts from "@/layouts/input-layouts";
|
||||
import { Section, AttachmentItemLayout } from "@/layouts/general-layouts";
|
||||
import { Content, ContentAction } from "@opal/layouts";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { Formik, Form } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import {
|
||||
@@ -1556,7 +1557,7 @@ function FederatedConnectorCard({
|
||||
{showDisconnectConfirmation && (
|
||||
<ConfirmationModalLayout
|
||||
icon={SvgUnplug}
|
||||
title={`Disconnect ${sourceMetadata.displayName}`}
|
||||
title={markdown(`Disconnect *${sourceMetadata.displayName}*`)}
|
||||
onClose={() => setShowDisconnectConfirmation(false)}
|
||||
submit={
|
||||
<Button
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useCallback, useState } from "react";
|
||||
import { Button } from "@opal/components";
|
||||
// TODO(@raunakab): migrate to Opal LineItemButton once it supports danger variant
|
||||
import LineItem from "@/refresh-components/buttons/LineItem";
|
||||
import { cn } from "@opal/utils";
|
||||
import { cn, markdown } from "@opal/utils";
|
||||
import {
|
||||
SvgMoreHorizontal,
|
||||
SvgEdit,
|
||||
@@ -341,7 +341,7 @@ export default function AgentRowActions({
|
||||
{unlistOpen && (
|
||||
<ConfirmationModalLayout
|
||||
icon={SvgEyeOff}
|
||||
title={`Unlist ${agent.name}`}
|
||||
title={markdown(`Unlist *${agent.name}*`)}
|
||||
onClose={isSubmitting ? undefined : () => setUnlistOpen(false)}
|
||||
submit={
|
||||
<Button
|
||||
|
||||
@@ -347,7 +347,7 @@ export default function ImageGenerationContent() {
|
||||
{disconnectProvider && (
|
||||
<ConfirmationModalLayout
|
||||
icon={SvgUnplug}
|
||||
title={`Disconnect ${disconnectProvider.title}`}
|
||||
title={markdown(`Disconnect *${disconnectProvider.title}*`)}
|
||||
description="This will remove the stored credentials for this provider."
|
||||
onClose={() => {
|
||||
setDisconnectProvider(null);
|
||||
|
||||
@@ -201,7 +201,7 @@ function VoiceDisconnectModal({
|
||||
return (
|
||||
<ConfirmationModalLayout
|
||||
icon={SvgUnplug}
|
||||
title={`Disconnect ${disconnectTarget.providerLabel}`}
|
||||
title={markdown(`Disconnect *${disconnectTarget.providerLabel}*`)}
|
||||
description="Voice models"
|
||||
onClose={onClose}
|
||||
submit={
|
||||
|
||||
@@ -9,6 +9,7 @@ import Modal from "@/refresh-components/Modal";
|
||||
import { Button } from "@opal/components";
|
||||
|
||||
import { SvgArrowExchange } from "@opal/icons";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { SvgOnyxLogo } from "@opal/logos";
|
||||
import type { IconProps } from "@opal/types";
|
||||
|
||||
@@ -81,7 +82,7 @@ export const WebProviderSetupModal = memo(
|
||||
<Modal.Content width="sm" preventAccidentalClose>
|
||||
<Modal.Header
|
||||
icon={LogoArrangement}
|
||||
title={`Set up ${providerLabel}`}
|
||||
title={markdown(`Set up *${providerLabel}*`)}
|
||||
description={description}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Text from "@/refresh-components/texts/Text";
|
||||
import { Section } from "@/layouts/general-layouts";
|
||||
import * as SettingsLayouts from "@/layouts/settings-layouts";
|
||||
import { Content, Card } from "@opal/layouts";
|
||||
import { markdown } from "@opal/utils";
|
||||
import useSWR from "swr";
|
||||
import { errorHandlingFetcher, FetchError } from "@/lib/fetcher";
|
||||
import { SWR_KEYS } from "@/lib/swr-keys";
|
||||
@@ -146,7 +147,7 @@ function WebSearchDisconnectModal({
|
||||
return (
|
||||
<ConfirmationModalLayout
|
||||
icon={SvgUnplug}
|
||||
title={`Disconnect ${disconnectTarget.label}`}
|
||||
title={markdown(`Disconnect *${disconnectTarget.label}*`)}
|
||||
description="This will remove the stored credentials for this provider."
|
||||
onClose={onClose}
|
||||
submit={
|
||||
|
||||
@@ -5,6 +5,7 @@ import Modal from "@/refresh-components/Modal";
|
||||
import { Button } from "@opal/components";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { SvgUnplug } from "@opal/icons";
|
||||
interface DisconnectEntityModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -51,7 +52,7 @@ export default function DisconnectEntityModal({
|
||||
icon={({ className }) => (
|
||||
<SvgUnplug className={cn(className, "stroke-action-danger-05")} />
|
||||
)}
|
||||
title={`Disconnect ${name}`}
|
||||
title={markdown(`Disconnect *${name}*`)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import InputSelect from "@/refresh-components/inputs/InputSelect";
|
||||
import InputTypeIn from "@/refresh-components/inputs/InputTypeIn";
|
||||
import PasswordInputTypeIn from "@/refresh-components/inputs/PasswordInputTypeIn";
|
||||
import { Button } from "@opal/components";
|
||||
import { markdown } from "@opal/utils";
|
||||
import CopyIconButton from "@/refresh-components/buttons/CopyIconButton";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import { Formik, Form } from "formik";
|
||||
@@ -317,7 +318,11 @@ export default function MCPAuthenticationModal({
|
||||
<Modal.Content width="sm" height="lg" skipOverlay={skipOverlay}>
|
||||
<Modal.Header
|
||||
icon={SvgArrowExchange}
|
||||
title={`Authenticate ${mcpServer?.name || "MCP Server"}`}
|
||||
title={
|
||||
mcpServer
|
||||
? markdown(`Authenticate *${mcpServer.name}*`)
|
||||
: "Authenticate MCP Server"
|
||||
}
|
||||
description="Authenticate your connection to start using the MCP server."
|
||||
/>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import { Formik, Form, useFormikContext } from "formik";
|
||||
import type { FormikConfig } from "formik";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { Interactive } from "@opal/core";
|
||||
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||
import { useAgents } from "@/hooks/useAgents";
|
||||
@@ -720,7 +721,7 @@ function ModalWrapperInner({
|
||||
} = getProvider(providerName);
|
||||
|
||||
const title = llmProvider
|
||||
? `Configure "${llmProvider.name}"`
|
||||
? markdown(`Configure *${llmProvider.name}*`)
|
||||
: `Set up ${providerProductName}`;
|
||||
const description =
|
||||
descriptionOverride ??
|
||||
|
||||
Reference in New Issue
Block a user