Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions client/src/Providers/MessagePartsHostContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import { showThinkingAtom } from '~/store/showThinking';
import { fontSizeAtom } from '~/store/fontSize';

function useAppMessage(): MessagePartMessage {
const { messageId, isSubmitting, isLatestMessage, nextType } = useMessageContext();
return { messageId, isSubmitting, isLatestMessage, nextType };
const { messageId, isSubmitting, isLatestMessage, nextType, conversationId, partIndex } =
useMessageContext();
return { messageId, isSubmitting, isLatestMessage, nextType, conversationId, partIndex };
}

function useAppAgent(agentId: string): Agent | undefined {
Expand Down Expand Up @@ -111,6 +112,7 @@ export const appMessagePartsHost: MessagePartsHost = {
useFontSize: () => useAtomValue(fontSizeAtom),
useShowThinking: () => useAtomValue(showThinkingAtom),
useAutoExpandTools: () => useRecoilValue<boolean>(store.autoExpandTools),
useLaTeXParsing: () => useRecoilValue<boolean>(store.LaTeXParsing),
useUserTextPreferences: useAppUserTextPreferences,
/* The atom rather than the auth context: AuthContextProvider mirrors the user into it, and the
* public share route mounts outside that provider. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import {
} from '~/data-provider';
import { pendingApprovalActionFamily } from '~/components/Chat/approval/state';
import { askSubmitStatusAtom } from '~/components/Chat/ask/state';
import { useChatSettings } from '~/Providers/ChatSettingsContext';
import { resolveAskUserQuestionPart } from '~/utils/approval';
import { ChatContext } from '~/Providers/ChatContext';
import { useGetEphemeralAgent } from '~/store/agents';

/** Shared read/write for the ask-answer submit status. */
export function useAskSubmitStatus(): {
Expand Down Expand Up @@ -428,7 +428,7 @@ export function useResumeSubmit() {
const jotaiStore = useStore();
const chatContext = useContext(ChatContext);
const conversation = chatContext?.conversation;
const getEphemeralAgent = useGetEphemeralAgent();
const { getEphemeralAgent } = useChatSettings();
const approvalMutation = useSubmitToolApprovalMutation();
const askMutation = useSubmitAskAnswerMutation();
const { getDecisions, isReady, setStatus, beginToolSubmission, endToolSubmission } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Button } from '@librechat/client';
import type { BackgroundTaskStatus, BackgroundTaskView } from './Parts/background';
import type { TranslationKeys } from '~/hooks';
import { backgroundTaskMessageKey, backgroundTaskNoteKey } from './Parts/guidance';
import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext';
import { ToolIcon, getToolIconType, OutputRenderer } from './ToolOutput';
import { useSubagentTaskPanel } from '~/components/Chat/Subagents/task';
import { formatBackgroundCodeOutput } from './Parts/background';
import { parseToolName } from '~/utils/toolLabels';
import { getToolDisplayLabel, cn } from '~/utils';
import SubagentProgress from './SubagentProgress';
import { useMessageContext } from '~/Providers';
import { useLocalize } from '~/hooks';

const STATUS: Record<BackgroundTaskStatus, { label: TranslationKeys; dot: string }> = {
Expand Down Expand Up @@ -40,7 +40,7 @@ export default function BackgroundTaskCard({
mcpServerNames?: readonly string[];
}) {
const localize = useLocalize();
const { conversationId } = useMessageContext();
const { conversationId } = useMessagePartsHost().useMessage();
const isSubagent = task.toolName === 'subagent';
const durableTask = useMemo(
() =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { Profiler } from 'react';
import { RecoilRoot } from 'recoil';
import { act, render } from '@testing-library/react';
import { ContentTypes, Tools } from 'librechat-data-provider';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { TMessageContentParts } from 'librechat-data-provider';
import ContentParts from './ContentParts';
import { AppStores } from 'test/harness';

/**
* Render benchmark for a live agent run: one recorded-shape stream (batches of
Expand Down Expand Up @@ -157,7 +157,7 @@ function replay(snapshots: Snapshot[]): Result {
const queryClient = new QueryClient();
const tree = (content: Snapshot, isSubmitting: boolean) => (
<QueryClientProvider client={queryClient}>
<RecoilRoot>
<AppStores>
<Profiler
id="run"
onRender={(_id, _phase, actualDuration) => {
Expand All @@ -176,7 +176,7 @@ function replay(snapshots: Snapshot[]): Result {
showThinking={false}
/>
</Profiler>
</RecoilRoot>
</AppStores>
</QueryClientProvider>
);
jest.useFakeTimers();
Expand Down
9 changes: 4 additions & 5 deletions client/src/components/Chat/Messages/Content/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import React, { memo, useRef, useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { getRemarkPlugins, getRehypePlugins, getMarkdownComponents } from './markdownConfig';
import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext';
import useSmoothStreaming from '~/hooks/Messages/useSmoothStreaming';
import MarkdownErrorBoundary from './MarkdownErrorBoundary';
import { FADE_HYDRATION_THRESHOLD } from './animate';
import { useMessageContext } from '~/Providers';
import MarkdownBlocks from './MarkdownBlocks';
import store from '~/store';

type TContentProps = {
content: string;
isLatestMessage: boolean;
};

const Markdown = memo(function Markdown({ content = '', isLatestMessage }: TContentProps) {
const { isSubmitting = false } = useMessageContext() ?? {};
const { useMessage, useLaTeXParsing } = useMessagePartsHost();
const { isSubmitting = false } = useMessage();
const smoothStreaming = useSmoothStreaming();
const LaTeXParsing = useRecoilValue<boolean>(store.LaTeXParsing);
const LaTeXParsing = useLaTeXParsing();
const isInitializing = content === '';

const streaming = isLatestMessage && isSubmitting;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { Profiler } from 'react';
import { RecoilRoot } from 'recoil';
import ReactMarkdown from 'react-markdown';
import { render } from '@testing-library/react';
import { getRemarkPlugins, getRehypePlugins, getMarkdownComponents } from './markdownConfig';
import { splitMarkdownIntoBlocks, splitMarkdownIntoBlocksUncached } from './splitMarkdown';
import { ArtifactProvider, CodeBlockProvider, MessageContext } from '~/Providers';
import CodeBlock from '~/components/Messages/Content/CodeBlock';
import MarkdownBlocks from './MarkdownBlocks';
import { AppStores } from 'test/harness';
import Markdown from './Markdown';

/**
Expand Down Expand Up @@ -126,9 +126,9 @@ const measure = (
};
const tree = (content: string) => (
<Profiler id="bench" onRender={onRender}>
<RecoilRoot>
<AppStores>
<Component content={content} />
</RecoilRoot>
</AppStores>
</Profiler>
);
const { rerender, unmount } = render(tree(prefixes[0]));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import React, { memo, useMemo, useRef, useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { useToastContext } from '@librechat/client';
import { PermissionTypes, Permissions, apiBaseUrl } from 'librechat-data-provider';
import {
handleDoubleClick,
Expand All @@ -9,12 +7,12 @@ import {
toAbsoluteFilePath,
} from '~/utils';
import Mermaid, { MermaidErrorBoundary } from '~/components/Messages/Content/Mermaid';
import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext';
import { useCodeBlockContext, useMediaContext } from '~/Providers';
import CodeBlock from '~/components/Messages/Content/CodeBlock';
import useHasAccess from '~/hooks/Roles/useHasAccess';
import { useFileDownload } from '~/data-provider';
import { useLocalize } from '~/hooks';
import store from '~/store';

type TCodeProps = {
inline?: boolean;
Expand Down Expand Up @@ -115,8 +113,9 @@ type TAnchorProps = {
};

export const a: React.ElementType = memo(function MarkdownAnchor({ href, children }: TAnchorProps) {
const user = useRecoilValue(store.user);
const { showToast } = useToastContext();
const { useUser, useToast } = useMessagePartsHost();
const user = useUser();
const showToast = useToast();
const localize = useLocalize();

const {
Expand Down
10 changes: 4 additions & 6 deletions client/src/components/Chat/Messages/Content/MessageContent.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { memo, Suspense, useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { Alert, DelayedRender } from '@librechat/client';
import { EmptyText, CollapsibleText } from '@librechat/chat/components';
import { Constants, parseThinkingContent } from 'librechat-data-provider';
import type { TMessage } from 'librechat-data-provider';
import type { TMessageContentProps, TDisplayProps } from '~/common';
import AttachmentRecoveryActions from '~/components/Messages/Content/Error/recovery';
import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext';
import useSmoothStreaming from '~/hooks/Messages/useSmoothStreaming';
import Error from '~/components/Messages/Content/Error';
import ToolCallLimitNotice from './ToolCallLimitNotice';
import { useMessageContext } from '~/Providers';
import MarkdownLite from './MarkdownLite';
import EditMessage from './EditMessage';
import Thinking from './Parts/Thinking';
import { useLocalize } from '~/hooks';
import Container from './Container';
import Markdown from './Markdown';
import { cn } from '~/utils';
import store from '~/store';

const ERROR_CONNECTION_TEXT = 'Error connecting to server, try refreshing the page.';
const DELAYED_ERROR_TIMEOUT = 5500;
Expand Down Expand Up @@ -92,9 +90,9 @@ export const ErrorMessage = ({
};

export const DisplayMessage = ({ text, isCreatedByUser, message, showCursor }: TDisplayProps) => {
const { isSubmitting = false, isLatestMessage = false } = useMessageContext();
const enableUserMsgMarkdown = useRecoilValue(store.enableUserMsgMarkdown);
const collapseLongUserMessages = useRecoilValue(store.collapseLongUserMessages);
const { useMessage, useUserTextPreferences } = useMessagePartsHost();
const { isSubmitting = false, isLatestMessage = false } = useMessage();
const { enableUserMsgMarkdown, collapseLongUserMessages } = useUserTextPreferences();
const smoothStreaming = useSmoothStreaming();

// The word fade itself indicates streaming, so the trailing block cursor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function createHost(overrides: Partial<MessagePartsHost> = {}): MessagePartsHost
useFontSize: () => 'text-base',
useShowThinking: () => false,
useAutoExpandTools: () => false,
useLaTeXParsing: () => false,
useUserTextPreferences: () => ({
usernameDisplay: false,
enableUserMsgMarkdown: false,
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Chat/Messages/Content/SiblingHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useMemo } from 'react';
import { GitBranchPlus } from 'lucide-react';
import { useToastContext } from '@librechat/client';
import { EModelEndpoint, parseEphemeralAgentId, stripAgentIdSuffix } from 'librechat-data-provider';
import type { TMessage, Agent } from 'librechat-data-provider';
import MessageTimestamp from '~/components/Chat/Messages/ui/MessageTimestamp';
import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext';
import { useBranchMessageMutation } from '~/data-provider/Messages';
import MessageIcon from '~/components/Share/MessageIcon';
import { useAgentsMapContext } from '~/Providers';
Expand Down Expand Up @@ -36,7 +36,7 @@ export default function SiblingHeader({
}: SiblingHeaderProps) {
const agentsMap = useAgentsMapContext();
const localize = useLocalize();
const { showToast } = useToastContext();
const showToast = useMessagePartsHost().useToast();

const branchMessage = useBranchMessageMutation(conversationId ?? null, {
onSuccess: () => {
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Chat/Messages/Content/hydration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
import type { FullToolCall, TMessageContentParts } from 'librechat-data-provider';
import type { ReactNode } from 'react';
import { ToolContentRequestContext, ToolContentPendingContext } from './disclosure';
import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext';
import { useToolCallPartQuery } from '~/data-provider';
import { useMessageContext } from '~/Providers';
import { useLocalize } from '~/hooks';

type ToolCallPart = Extract<TMessageContentParts, { type: ContentTypes.TOOL_CALL }>;
Expand Down Expand Up @@ -66,7 +66,7 @@ export function PreviewedToolCallPart({
children: (part: TMessageContentParts) => ReactNode;
}) {
const localize = useLocalize();
const { messageId, conversationId, partIndex } = useMessageContext();
const { messageId, conversationId, partIndex } = useMessagePartsHost().useMessage();
const [requested, setRequested] = useState(false);
const request = useCallback(() => setRequested(true), []);
const toolCall = part.tool_call as FullToolCall;
Expand Down
6 changes: 6 additions & 0 deletions client/test/harness.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { useState } from 'react';
import { RecoilRoot } from 'recoil';
import { createStore, Provider as JotaiProvider } from 'jotai';
import type { ChatSurface } from '~/components/Chat/Subagents/surface';
import type { ReactNode } from 'react';
import { ChatSurfaceProvider } from '~/components/Chat/Subagents/surface';

export type JotaiStore = ReturnType<typeof createStore>;

/** The app's Recoil root, for renders that use the app's default hosts, which read it. */
export function AppStores({ children }: { children: ReactNode }) {
return <RecoilRoot>{children}</RecoilRoot>;
}

/** The application's own defaults, so a test that does not care about a
* preference reads what a reader who never changed one would see. */
export const testChatSurface = (overrides: Partial<ChatSurface> = {}): ChatSurface => ({
Expand Down
1 change: 1 addition & 0 deletions packages/chat/src/components/__tests__/host.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function createHost(enableUserMsgMarkdown = false): MessagePartsHost {
useFontSize: () => 'text-base',
useShowThinking: () => false,
useAutoExpandTools: () => false,
useLaTeXParsing: () => false,
useUserTextPreferences: () => ({
usernameDisplay: false,
enableUserMsgMarkdown,
Expand Down
1 change: 1 addition & 0 deletions packages/chat/src/react/__tests__/host.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const createHost = (fontSize: string): MessagePartsHost => ({
useFontSize: () => fontSize,
useShowThinking: () => false,
useAutoExpandTools: () => false,
useLaTeXParsing: () => false,
useUserTextPreferences: () => ({
usernameDisplay: false,
enableUserMsgMarkdown: false,
Expand Down
8 changes: 7 additions & 1 deletion packages/chat/src/react/host.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export type MessagePartMessage = {
isLatestMessage?: boolean;
/** Content type of the part after this one, when known. */
nextType?: string;
/** Conversation of that message, when it has one. */
conversationId?: string | null;
/** Position of the part in the message's content, when it renders one part. */
partIndex?: number;
};

/** How the viewer prefers user-authored text to display. */
Expand All @@ -26,7 +30,7 @@ export type MessagePartsUserTextPreferences = {
};

/** The signed-in user as the parts label it; `undefined` on a public share. */
export type MessagePartsUser = { name?: string; username?: string } | undefined;
export type MessagePartsUser = { id?: string; name?: string; username?: string } | undefined;

/** Shows a transient notification. */
export type MessagePartsToast = (toast: TShowToast) => void;
Expand Down Expand Up @@ -70,6 +74,8 @@ export interface MessagePartsHost {
useShowThinking: () => boolean;
/** Whether the viewer wants tool cards and tool groups expanded by default. */
useAutoExpandTools: () => boolean;
/** Whether the viewer wants LaTeX in markdown rendered as math. */
useLaTeXParsing: () => boolean;
/** How the viewer prefers user-authored text to display. */
useUserTextPreferences: () => MessagePartsUserTextPreferences;
/** The signed-in user. */
Expand Down
Loading