From 4de9619bd946fb5d28f79ce4253d5f21b1494f76 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 11 Feb 2025 11:28:18 -0500 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A7=A0=20fix:=20Handle=20Reasoning=20?= =?UTF-8?q?Chunk=20Edge=20Cases=20(#5800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: better reasoning parsing * style: better model selector mobile styling * chore: bump vite --- api/app/clients/OpenAIClient.js | 37 +- api/package.json | 2 +- client/package.json | 2 +- .../Chat/Messages/Content/ContentParts.tsx | 21 +- .../Chat/Messages/Content/MessageContent.tsx | 4 +- .../Chat/Messages/Content/Parts/Reasoning.tsx | 9 +- .../Input/ModelSelect/TemporaryChat.tsx | 12 +- .../src/components/ui/SelectDropDownPop.tsx | 15 +- package-lock.json | 2013 ++++++++++++----- 9 files changed, 1515 insertions(+), 600 deletions(-) diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 9334f1c28..2c075fdf7 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -506,9 +506,8 @@ class OpenAIClient extends BaseClient { if (promptPrefix && this.isOmni === true) { const lastUserMessageIndex = payload.findLastIndex((message) => message.role === 'user'); if (lastUserMessageIndex !== -1) { - payload[ - lastUserMessageIndex - ].content = `${promptPrefix}\n${payload[lastUserMessageIndex].content}`; + payload[lastUserMessageIndex].content = + `${promptPrefix}\n${payload[lastUserMessageIndex].content}`; } } @@ -1072,10 +1071,24 @@ ${convo} return ''; } - const reasoningTokens = - this.streamHandler.reasoningTokens.length > 0 - ? `:::thinking\n${this.streamHandler.reasoningTokens.join('')}\n:::\n` - : ''; + let thinkMatch; + let remainingText; + let reasoningText = ''; + + if (this.streamHandler.reasoningTokens.length > 0) { + reasoningText = this.streamHandler.reasoningTokens.join(''); + thinkMatch = reasoningText.match(/([\s\S]*?)<\/think>/)?.[1]?.trim(); + if (thinkMatch != null && thinkMatch) { + const reasoningTokens = `:::thinking\n${thinkMatch}\n:::\n`; + remainingText = reasoningText.split(/<\/think>/)?.[1]?.trim() || ''; + return `${reasoningTokens}${remainingText}${this.streamHandler.tokens.join('')}`; + } else if (thinkMatch === '') { + remainingText = reasoningText.split(/<\/think>/)?.[1]?.trim() || ''; + return `${remainingText}${this.streamHandler.tokens.join('')}`; + } + } + + const reasoningTokens = reasoningText.length > 0 ? `:::thinking\n${reasoningText}\n:::\n` : ''; return `${reasoningTokens}${this.streamHandler.tokens.join('')}`; } @@ -1449,7 +1462,7 @@ ${convo} this.options.context !== 'title' && message.content.startsWith('') ) { - return message.content.replace('', ':::thinking').replace('', ':::'); + return this.getStreamText(); } return message.content; @@ -1473,13 +1486,17 @@ ${convo} (err instanceof OpenAI.OpenAIError && err?.message?.includes('missing finish_reason')) ) { logger.error('[OpenAIClient] Known OpenAI error:', err); - if (intermediateReply.length > 0) { + if (this.streamHandler && this.streamHandler.reasoningTokens.length) { + return this.getStreamText(); + } else if (intermediateReply.length > 0) { return intermediateReply.join(''); } else { throw err; } } else if (err instanceof OpenAI.APIError) { - if (intermediateReply.length > 0) { + if (this.streamHandler && this.streamHandler.reasoningTokens.length) { + return this.getStreamText(); + } else if (intermediateReply.length > 0) { return intermediateReply.join(''); } else { throw err; diff --git a/api/package.json b/api/package.json index e5d830a39..820022876 100644 --- a/api/package.json +++ b/api/package.json @@ -45,7 +45,7 @@ "@langchain/google-genai": "^0.1.7", "@langchain/google-vertexai": "^0.1.8", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.0.3", + "@librechat/agents": "^2.0.4", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "1.7.8", "bcryptjs": "^2.4.3", diff --git a/client/package.json b/client/package.json index 10ceb258e..c0ff976e6 100644 --- a/client/package.json +++ b/client/package.json @@ -136,7 +136,7 @@ "tailwindcss": "^3.4.1", "ts-jest": "^29.2.5", "typescript": "^5.3.3", - "vite": "^5.4.14", + "vite": "^6.1.0", "vite-plugin-node-polyfills": "^0.17.0", "vite-plugin-pwa": "^0.21.1" } diff --git a/client/src/components/Chat/Messages/Content/ContentParts.tsx b/client/src/components/Chat/Messages/Content/ContentParts.tsx index ce77e3bfd..b997060c6 100644 --- a/client/src/components/Chat/Messages/Content/ContentParts.tsx +++ b/client/src/components/Chat/Messages/Content/ContentParts.tsx @@ -50,11 +50,24 @@ const ContentParts = memo( [attachments, messageAttachmentsMap, messageId], ); - const hasReasoningParts = useMemo( - () => content?.some((part) => part?.type === ContentTypes.THINK && part.think) ?? false, - [content], - ); + const hasReasoningParts = useMemo(() => { + const hasThinkPart = content?.some((part) => part?.type === ContentTypes.THINK) ?? false; + const allThinkPartsHaveContent = + content?.every((part) => { + if (part?.type !== ContentTypes.THINK) { + return true; + } + if (typeof part.think === 'string') { + const cleanedContent = part.think.replace(/<\/?think>/g, '').trim(); + return cleanedContent.length > 0; + } + + return false; + }) ?? false; + + return hasThinkPart && allThinkPartsHaveContent; + }, [content]); if (!content) { return null; } diff --git a/client/src/components/Chat/Messages/Content/MessageContent.tsx b/client/src/components/Chat/Messages/Content/MessageContent.tsx index 21bbe231e..1547a01d8 100644 --- a/client/src/components/Chat/Messages/Content/MessageContent.tsx +++ b/client/src/components/Chat/Messages/Content/MessageContent.tsx @@ -159,7 +159,9 @@ const MessageContent = ({ return ( <> - {thinkingContent && {thinkingContent}} + {thinkingContent.length > 0 && ( + {thinkingContent} + )} { const { isExpanded, nextType } = useMessageContext(); const reasoningText = useMemo(() => { - return reasoning.replace(/^\s*/, '').replace(/\s*<\/think>$/, ''); + return reasoning + .replace(/^\s*/, '') + .replace(/\s*<\/think>$/, '') + .trim(); }, [reasoning]); + if (!reasoningText) { + return null; + } + return (
{ }; return ( -
-
-
+
+
+
-
+
-
+
{showCode && ( @@ -105,9 +107,7 @@ export default function ExecuteCode({ )}
)} - {attachments?.map((attachment, index) => ( - - ))} + {attachments?.map((attachment, index) => )} ); } diff --git a/client/src/components/Chat/Messages/Content/ProgressText.tsx b/client/src/components/Chat/Messages/Content/ProgressText.tsx index aaeecd600..11ec4b291 100644 --- a/client/src/components/Chat/Messages/Content/ProgressText.tsx +++ b/client/src/components/Chat/Messages/Content/ProgressText.tsx @@ -42,6 +42,7 @@ export default function ProgressText({ authText, hasInput = true, popover = false, + isExpanded = false, }: { progress: number; onClick?: () => void; @@ -50,8 +51,9 @@ export default function ProgressText({ authText?: string; hasInput?: boolean; popover?: boolean; + isExpanded?: boolean; }) { - const text = progress < 1 ? authText ?? inProgressText : finishedText; + const text = progress < 1 ? (authText ?? inProgressText) : finishedText; return (
); - if (useSidePanel && !hideSidePanel && interfaceConfig.sidePanel === true) { - return ( - - 0 ? ( - - - - ) : null - } - > -
- {children} -
-
-
- ); - } - return ( - {layout()} - {panel != null && panel} + 0 ? ( + + + + ) : null + } + > +
+ {children} +
+
); } diff --git a/client/src/components/SidePanel/Agents/AgentConfig.tsx b/client/src/components/SidePanel/Agents/AgentConfig.tsx index fe90ebd91..8cdca08e8 100644 --- a/client/src/components/SidePanel/Agents/AgentConfig.tsx +++ b/client/src/components/SidePanel/Agents/AgentConfig.tsx @@ -10,7 +10,7 @@ import { AgentCapabilities, } from 'librechat-data-provider'; import type { TPlugin } from 'librechat-data-provider'; -import type { AgentForm, AgentPanelProps } from '~/common'; +import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common'; import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils'; import { useCreateAgentMutation, useUpdateAgentMutation } from '~/data-provider'; import { useLocalize, useAuthContext, useHasAccess } from '~/hooks'; @@ -26,6 +26,7 @@ import AgentAvatar from './AgentAvatar'; import { Spinner } from '~/components'; import FileSearch from './FileSearch'; import ShareAgent from './ShareAgent'; +import Artifacts from './Artifacts'; import AgentTool from './AgentTool'; import CodeForm from './Code/Form'; import { Panel } from '~/common'; @@ -77,6 +78,10 @@ export default function AgentConfig({ () => agentsConfig?.capabilities.includes(AgentCapabilities.actions), [agentsConfig], ); + const artifactsEnabled = useMemo( + () => agentsConfig?.capabilities.includes(AgentCapabilities.artifacts) ?? false, + [agentsConfig], + ); const fileSearchEnabled = useMemo( () => agentsConfig?.capabilities.includes(AgentCapabilities.file_search) ?? false, [agentsConfig], @@ -150,7 +155,7 @@ export default function AgentConfig({ onSuccess: (data) => { setCurrentAgentId(data.id); showToast({ - message: `${localize('com_assistants_create_success ')} ${ + message: `${localize('com_assistants_create_success')} ${ data.name ?? localize('com_ui_agent') }`, }); @@ -178,18 +183,10 @@ export default function AgentConfig({ }, [agent_id, setActivePanel, showToast, localize]); const providerValue = typeof provider === 'string' ? provider : provider?.value; + let Icon: IconComponentTypes | null | undefined; let endpointType: EModelEndpoint | undefined; let endpointIconURL: string | undefined; let iconKey: string | undefined; - let Icon: - | React.ComponentType< - React.SVGProps & { - endpoint: string; - endpointType: EModelEndpoint | undefined; - iconURL: string | undefined; - } - > - | undefined; if (providerValue !== undefined) { endpointType = getEndpointField(endpointsConfig, providerValue as string, 'type'); @@ -346,6 +343,8 @@ export default function AgentConfig({ {codeEnabled && } {/* File Search */} {fileSearchEnabled && } + {/* Artifacts */} + {artifactsEnabled && }
)} {/* Agent Tools & Actions */} diff --git a/client/src/components/SidePanel/Agents/AgentPanel.tsx b/client/src/components/SidePanel/Agents/AgentPanel.tsx index 0f49dc30e..cea3265eb 100644 --- a/client/src/components/SidePanel/Agents/AgentPanel.tsx +++ b/client/src/components/SidePanel/Agents/AgentPanel.tsx @@ -120,6 +120,7 @@ export default function AgentPanel({ const { name, + artifacts, description, instructions, model: _model, @@ -139,6 +140,7 @@ export default function AgentPanel({ agent_id, data: { name, + artifacts, description, instructions, model, @@ -162,6 +164,7 @@ export default function AgentPanel({ create.mutate({ name, + artifacts, description, instructions, model, @@ -184,7 +187,7 @@ export default function AgentPanel({ const canEditAgent = useMemo(() => { const canEdit = - agentQuery.data?.isCollaborative ?? false + (agentQuery.data?.isCollaborative ?? false) ? true : agentQuery.data?.author === user?.id || user?.role === SystemRoles.ADMIN; diff --git a/client/src/components/SidePanel/Agents/AgentSelect.tsx b/client/src/components/SidePanel/Agents/AgentSelect.tsx index 5d406c48b..caeb0457e 100644 --- a/client/src/components/SidePanel/Agents/AgentSelect.tsx +++ b/client/src/components/SidePanel/Agents/AgentSelect.tsx @@ -55,8 +55,8 @@ export default function AgentSelect({ }; const capabilities: TAgentCapabilities = { - [AgentCapabilities.execute_code]: false, [AgentCapabilities.file_search]: false, + [AgentCapabilities.execute_code]: false, [AgentCapabilities.end_after_tools]: false, [AgentCapabilities.hide_sequential_outputs]: false, }; diff --git a/client/src/components/SidePanel/Agents/Artifacts.tsx b/client/src/components/SidePanel/Agents/Artifacts.tsx new file mode 100644 index 000000000..2a814cc7f --- /dev/null +++ b/client/src/components/SidePanel/Agents/Artifacts.tsx @@ -0,0 +1,124 @@ +import { useFormContext } from 'react-hook-form'; +import { ArtifactModes, AgentCapabilities } from 'librechat-data-provider'; +import type { AgentForm } from '~/common'; +import { + Switch, + HoverCard, + HoverCardPortal, + HoverCardContent, + HoverCardTrigger, +} from '~/components/ui'; +import { useLocalize } from '~/hooks'; +import { CircleHelpIcon } from '~/components/svg'; +import { ESide } from '~/common'; + +export default function Artifacts() { + const localize = useLocalize(); + const methods = useFormContext(); + const { setValue, watch } = methods; + + const artifactsMode = watch(AgentCapabilities.artifacts); + + const handleArtifactsChange = (value: boolean) => { + setValue(AgentCapabilities.artifacts, value ? ArtifactModes.DEFAULT : '', { + shouldDirty: true, + }); + }; + + const handleShadcnuiChange = (value: boolean) => { + setValue(AgentCapabilities.artifacts, value ? ArtifactModes.SHADCNUI : ArtifactModes.DEFAULT, { + shouldDirty: true, + }); + }; + + const handleCustomModeChange = (value: boolean) => { + setValue(AgentCapabilities.artifacts, value ? ArtifactModes.CUSTOM : ArtifactModes.DEFAULT, { + shouldDirty: true, + }); + }; + + const isEnabled = artifactsMode !== undefined && artifactsMode !== ''; + const isCustomEnabled = artifactsMode === ArtifactModes.CUSTOM; + const isShadcnEnabled = artifactsMode === ArtifactModes.SHADCNUI; + + return ( +
+
+ + + +
+
+ + + +
+
+ ); +} + +function SwitchItem({ + id, + label, + checked, + onCheckedChange, + hoverCardText, + disabled = false, +}: { + id: string; + label: string; + checked: boolean; + onCheckedChange: (value: boolean) => void; + hoverCardText: string; + disabled?: boolean; +}) { + return ( + +
+
+
{label}
+ + + +
+ + +
+

{hoverCardText}

+
+
+
+ +
+
+ ); +} diff --git a/client/src/components/SidePanel/Agents/Code/Action.tsx b/client/src/components/SidePanel/Agents/Code/Action.tsx index a2a16d441..e655101b7 100644 --- a/client/src/components/SidePanel/Agents/Code/Action.tsx +++ b/client/src/components/SidePanel/Agents/Code/Action.tsx @@ -86,7 +86,7 @@ export default function Action({ authType = '', isToolAuthenticated = false }) { )} - +
diff --git a/client/src/components/SidePanel/Agents/FileSearchCheckbox.tsx b/client/src/components/SidePanel/Agents/FileSearchCheckbox.tsx index 5d827dd81..f006d9769 100644 --- a/client/src/components/SidePanel/Agents/FileSearchCheckbox.tsx +++ b/client/src/components/SidePanel/Agents/FileSearchCheckbox.tsx @@ -29,7 +29,7 @@ export default function FileSearchCheckbox() { {...field} checked={field.value} onCheckedChange={field.onChange} - className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer" + className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer" value={field.value.toString()} /> )} @@ -38,7 +38,6 @@ export default function FileSearchCheckbox() { type="button" className="flex items-center space-x-2" onClick={() => - setValue(AgentCapabilities.file_search, !getValues(AgentCapabilities.file_search), { shouldDirty: true, }) @@ -51,7 +50,7 @@ export default function FileSearchCheckbox() { {localize('com_agents_enable_file_search')} - + diff --git a/client/src/components/SidePanel/SidePanel.tsx b/client/src/components/SidePanel/SidePanel.tsx index 51977ab15..7660e8b27 100644 --- a/client/src/components/SidePanel/SidePanel.tsx +++ b/client/src/components/SidePanel/SidePanel.tsx @@ -1,78 +1,58 @@ -import throttle from 'lodash/throttle'; -import { getConfigDefaults } from 'librechat-data-provider'; +import { useState, useCallback, useMemo, memo } from 'react'; import { useUserKeyQuery } from 'librechat-data-provider/react-query'; -import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react'; import type { TEndpointsConfig, TInterfaceConfig } from 'librechat-data-provider'; import type { ImperativePanelHandle } from 'react-resizable-panels'; -import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable'; -import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider'; +import { ResizableHandleAlt, ResizablePanel } from '~/components/ui/Resizable'; import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks'; import useSideNavLinks from '~/hooks/Nav/useSideNavLinks'; +import { useGetEndpointsQuery } from '~/data-provider'; import NavToggle from '~/components/Nav/NavToggle'; import { cn, getEndpointField } from '~/utils'; import { useChatContext } from '~/Providers'; import Switcher from './Switcher'; import Nav from './Nav'; -interface SidePanelProps { - defaultLayout?: number[] | undefined; - defaultCollapsed?: boolean; - navCollapsedSize?: number; - fullPanelCollapse?: boolean; - artifacts?: React.ReactNode; - children: React.ReactNode; -} - const defaultMinSize = 20; -const defaultInterface = getConfigDefaults().interface; - -const normalizeLayout = (layout: number[]) => { - const sum = layout.reduce((acc, size) => acc + size, 0); - if (Math.abs(sum - 100) < 0.01) { - return layout.map((size) => Number(size.toFixed(2))); - } - - const factor = 100 / sum; - const normalizedLayout = layout.map((size) => Number((size * factor).toFixed(2))); - - const adjustedSum = normalizedLayout.reduce( - (acc, size, index) => (index === layout.length - 1 ? acc : acc + size), - 0, - ); - normalizedLayout[normalizedLayout.length - 1] = Number((100 - adjustedSum).toFixed(2)); - - return normalizedLayout; -}; const SidePanel = ({ - defaultLayout = [97, 3], - defaultCollapsed = false, - fullPanelCollapse = false, + defaultSize, + panelRef, navCollapsedSize = 3, - artifacts, - children, -}: SidePanelProps) => { + hasArtifacts, + minSize, + setMinSize, + collapsedSize, + setCollapsedSize, + isCollapsed, + setIsCollapsed, + fullCollapse, + setFullCollapse, + interfaceConfig, +}: { + defaultSize?: number; + hasArtifacts: boolean; + navCollapsedSize?: number; + minSize: number; + setMinSize: React.Dispatch>; + collapsedSize: number; + setCollapsedSize: React.Dispatch>; + isCollapsed: boolean; + setIsCollapsed: React.Dispatch>; + fullCollapse: boolean; + setFullCollapse: React.Dispatch>; + panelRef: React.RefObject; + interfaceConfig: TInterfaceConfig; +}) => { const localize = useLocalize(); const [isHovering, setIsHovering] = useState(false); - const [minSize, setMinSize] = useState(defaultMinSize); const [newUser, setNewUser] = useLocalStorage('newUser', true); - const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); - const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse); - const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize); const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery(); - const { data: startupConfig } = useGetStartupConfig(); - const interfaceConfig = useMemo( - () => (startupConfig?.interface ?? defaultInterface) as Partial, - [startupConfig], - ); const isSmallScreen = useMediaQuery('(max-width: 767px)'); const { conversation } = useChatContext(); const { endpoint } = conversation ?? {}; const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(endpoint ?? ''); - const panelRef = useRef(null); - const defaultActive = useMemo(() => { const activePanel = localStorage.getItem('side:active-panel'); return typeof activePanel === 'string' ? activePanel : undefined; @@ -113,46 +93,6 @@ const SidePanel = ({ interfaceConfig, }); - const calculateLayout = useCallback(() => { - if (artifacts == null) { - const navSize = defaultLayout.length === 2 ? defaultLayout[1] : defaultLayout[2]; - return [100 - navSize, navSize]; - } else { - const navSize = 0; - const remainingSpace = 100 - navSize; - const newMainSize = Math.floor(remainingSpace / 2); - const artifactsSize = remainingSpace - newMainSize; - return [newMainSize, artifactsSize, navSize]; - } - }, [artifacts, defaultLayout]); - - const currentLayout = useMemo(() => normalizeLayout(calculateLayout()), [calculateLayout]); - - // eslint-disable-next-line react-hooks/exhaustive-deps - const throttledSaveLayout = useCallback( - throttle((sizes: number[]) => { - const normalizedSizes = normalizeLayout(sizes); - localStorage.setItem('react-resizable-panels:layout', JSON.stringify(normalizedSizes)); - }, 350), - [], - ); - - useEffect(() => { - if (isSmallScreen) { - setIsCollapsed(true); - setCollapsedSize(0); - setMinSize(defaultMinSize); - setFullCollapse(true); - localStorage.setItem('fullPanelCollapse', 'true'); - panelRef.current?.collapse(); - return; - } else { - setIsCollapsed(defaultCollapsed); - setCollapsedSize(navCollapsedSize); - setMinSize(defaultMinSize); - } - }, [isSmallScreen, defaultCollapsed, navCollapsedSize, fullPanelCollapse]); - const toggleNavVisible = useCallback(() => { if (newUser) { setNewUser(false); @@ -173,127 +113,84 @@ const SidePanel = ({ } }, [isCollapsed, newUser, setNewUser, navCollapsedSize]); - const minSizeMain = useMemo(() => (artifacts != null ? 15 : 30), [artifacts]); - return ( <> - throttledSaveLayout(sizes)} - className="transition-width relative h-full w-full flex-1 overflow-auto bg-presentation" +
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + className="relative flex w-px items-center justify-center" > - - {children} - - {artifacts != null && ( - <> - - - {artifacts} - - - )} -
setIsHovering(true)} - onMouseLeave={() => setIsHovering(false)} - className="relative flex w-px items-center justify-center" - > - -
- {(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && ( - - )} - { - setIsCollapsed(false); - localStorage.setItem('react-resizable-panels:collapsed', 'false'); - }} - onCollapse={() => { - setIsCollapsed(true); - localStorage.setItem('react-resizable-panels:collapsed', 'true'); - }} + - {interfaceConfig.modelSelect === true && ( -
- -
- )} -
+ {(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && ( + + )} + + onExpand={() => { + setIsCollapsed(false); + localStorage.setItem('react-resizable-panels:collapsed', 'false'); + }} + onCollapse={() => { + setIsCollapsed(true); + localStorage.setItem('react-resizable-panels:collapsed', 'true'); + }} + className={cn( + 'sidenav hide-scrollbar border-l border-border-light bg-background transition-opacity', + isCollapsed ? 'min-w-[50px]' : 'min-w-[340px] sm:min-w-[352px]', + (isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse + ? 'hidden min-w-0' + : 'opacity-100', + )} + > + {interfaceConfig.modelSelect === true && ( +
+ +
+ )} +
- {(codeEnabled || fileSearchEnabled) && ( + {(codeEnabled || fileSearchEnabled || artifactsEnabled) && (