mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
🪄 feat: Artifacts Badge & Optimize Ephemeral Agent State (#8252)
* 🔧 fix: Update type annotations in useEventHandlers for better type safety * 🔧 refactor: `useToolToggle` for improved localStorage synchronization and allow string/falsy values for setting to storage * ✨ feat: Implement Artifacts badge to BadgeRow with toggle options and UI components - Added Artifacts component to manage artifacts state and options. - Introduced ArtifactsSubMenu for additional settings related to artifacts. - Integrated artifacts functionality into BadgeRow and ToolsDropdown components. - Updated localStorage handling for artifacts state persistence. - Enhanced localization for artifacts-related strings in translation files. - Refactored Agent model to include artifacts in the ephemeral agent response. * fix: set ephemeral agent state for conversation on finalization * chore: remove beta settings dialog tab * refactor: improve Ephemeral Agent statefulness * fix: update setValue parameter to use 'value' instead of 'isChecked' in CheckboxButton * refactor: update color classes for Artifact toggle and order of dropdown components * chore: remove unused i18n localization
This commit is contained in:
parent
458580ec87
commit
a288ad1d9c
23 changed files with 547 additions and 232 deletions
|
|
@ -90,7 +90,7 @@ const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _
|
||||||
}
|
}
|
||||||
|
|
||||||
const instructions = req.body.promptPrefix;
|
const instructions = req.body.promptPrefix;
|
||||||
return {
|
const result = {
|
||||||
id: agent_id,
|
id: agent_id,
|
||||||
instructions,
|
instructions,
|
||||||
provider: endpoint,
|
provider: endpoint,
|
||||||
|
|
@ -98,6 +98,11 @@ const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _
|
||||||
model,
|
model,
|
||||||
tools,
|
tools,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (ephemeralAgent?.artifacts != null && ephemeralAgent.artifacts) {
|
||||||
|
result.artifacts = ephemeralAgent.artifacts;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext, useEffect, useRef } from 'react';
|
||||||
import { Tools, LocalStorageKeys } from 'librechat-data-provider';
|
import { Tools, LocalStorageKeys, AgentCapabilities, Constants } from 'librechat-data-provider';
|
||||||
import { useMCPSelect, useToolToggle, useCodeApiKeyForm, useSearchApiKeyForm } from '~/hooks';
|
import { useMCPSelect, useToolToggle, useCodeApiKeyForm, useSearchApiKeyForm } from '~/hooks';
|
||||||
import { useGetStartupConfig } from '~/data-provider';
|
import { useGetStartupConfig } from '~/data-provider';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { ephemeralAgentByConvoId } from '~/store';
|
||||||
|
|
||||||
interface BadgeRowContextType {
|
interface BadgeRowContextType {
|
||||||
conversationId?: string | null;
|
conversationId?: string | null;
|
||||||
|
|
@ -9,6 +11,7 @@ interface BadgeRowContextType {
|
||||||
webSearch: ReturnType<typeof useToolToggle>;
|
webSearch: ReturnType<typeof useToolToggle>;
|
||||||
codeInterpreter: ReturnType<typeof useToolToggle>;
|
codeInterpreter: ReturnType<typeof useToolToggle>;
|
||||||
fileSearch: ReturnType<typeof useToolToggle>;
|
fileSearch: ReturnType<typeof useToolToggle>;
|
||||||
|
artifacts: ReturnType<typeof useToolToggle>;
|
||||||
codeApiKeyForm: ReturnType<typeof useCodeApiKeyForm>;
|
codeApiKeyForm: ReturnType<typeof useCodeApiKeyForm>;
|
||||||
searchApiKeyForm: ReturnType<typeof useSearchApiKeyForm>;
|
searchApiKeyForm: ReturnType<typeof useSearchApiKeyForm>;
|
||||||
startupConfig: ReturnType<typeof useGetStartupConfig>['data'];
|
startupConfig: ReturnType<typeof useGetStartupConfig>['data'];
|
||||||
|
|
@ -26,10 +29,87 @@ export function useBadgeRowContext() {
|
||||||
|
|
||||||
interface BadgeRowProviderProps {
|
interface BadgeRowProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
isSubmitting?: boolean;
|
||||||
conversationId?: string | null;
|
conversationId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BadgeRowProvider({ children, conversationId }: BadgeRowProviderProps) {
|
export default function BadgeRowProvider({
|
||||||
|
children,
|
||||||
|
isSubmitting,
|
||||||
|
conversationId,
|
||||||
|
}: BadgeRowProviderProps) {
|
||||||
|
const hasInitializedRef = useRef(false);
|
||||||
|
const lastKeyRef = useRef<string>('');
|
||||||
|
const key = conversationId ?? Constants.NEW_CONVO;
|
||||||
|
const setEphemeralAgent = useSetRecoilState(ephemeralAgentByConvoId(key));
|
||||||
|
|
||||||
|
/** Initialize ephemeralAgent from localStorage on mount and when conversation changes */
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSubmitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if this is a new conversation or the first load
|
||||||
|
if (!hasInitializedRef.current || lastKeyRef.current !== key) {
|
||||||
|
hasInitializedRef.current = true;
|
||||||
|
lastKeyRef.current = key;
|
||||||
|
|
||||||
|
// Load all localStorage values
|
||||||
|
const codeToggleKey = `${LocalStorageKeys.LAST_CODE_TOGGLE_}${key}`;
|
||||||
|
const webSearchToggleKey = `${LocalStorageKeys.LAST_WEB_SEARCH_TOGGLE_}${key}`;
|
||||||
|
const fileSearchToggleKey = `${LocalStorageKeys.LAST_FILE_SEARCH_TOGGLE_}${key}`;
|
||||||
|
const artifactsToggleKey = `${LocalStorageKeys.LAST_ARTIFACTS_TOGGLE_}${key}`;
|
||||||
|
|
||||||
|
const codeToggleValue = localStorage.getItem(codeToggleKey);
|
||||||
|
const webSearchToggleValue = localStorage.getItem(webSearchToggleKey);
|
||||||
|
const fileSearchToggleValue = localStorage.getItem(fileSearchToggleKey);
|
||||||
|
const artifactsToggleValue = localStorage.getItem(artifactsToggleKey);
|
||||||
|
|
||||||
|
const initialValues: Record<string, any> = {};
|
||||||
|
|
||||||
|
if (codeToggleValue !== null) {
|
||||||
|
try {
|
||||||
|
initialValues[Tools.execute_code] = JSON.parse(codeToggleValue);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse code toggle value:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webSearchToggleValue !== null) {
|
||||||
|
try {
|
||||||
|
initialValues[Tools.web_search] = JSON.parse(webSearchToggleValue);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse web search toggle value:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileSearchToggleValue !== null) {
|
||||||
|
try {
|
||||||
|
initialValues[Tools.file_search] = JSON.parse(fileSearchToggleValue);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse file search toggle value:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artifactsToggleValue !== null) {
|
||||||
|
try {
|
||||||
|
initialValues[AgentCapabilities.artifacts] = JSON.parse(artifactsToggleValue);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse artifacts toggle value:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always set values for all tools (use defaults if not in localStorage)
|
||||||
|
// If ephemeralAgent is null, create a new object with just our tool values
|
||||||
|
setEphemeralAgent((prev) => ({
|
||||||
|
...(prev || {}),
|
||||||
|
[Tools.execute_code]: initialValues[Tools.execute_code] ?? false,
|
||||||
|
[Tools.web_search]: initialValues[Tools.web_search] ?? false,
|
||||||
|
[Tools.file_search]: initialValues[Tools.file_search] ?? false,
|
||||||
|
[AgentCapabilities.artifacts]: initialValues[AgentCapabilities.artifacts] ?? false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [key, isSubmitting, setEphemeralAgent]);
|
||||||
|
|
||||||
/** Startup config */
|
/** Startup config */
|
||||||
const { data: startupConfig } = useGetStartupConfig();
|
const { data: startupConfig } = useGetStartupConfig();
|
||||||
|
|
||||||
|
|
@ -74,10 +154,19 @@ export default function BadgeRowProvider({ children, conversationId }: BadgeRowP
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Artifacts hook - using a custom key since it's not a Tool but a capability */
|
||||||
|
const artifacts = useToolToggle({
|
||||||
|
conversationId,
|
||||||
|
toolKey: AgentCapabilities.artifacts,
|
||||||
|
localStorageKey: LocalStorageKeys.LAST_ARTIFACTS_TOGGLE_,
|
||||||
|
isAuthenticated: true,
|
||||||
|
});
|
||||||
|
|
||||||
const value: BadgeRowContextType = {
|
const value: BadgeRowContextType = {
|
||||||
mcpSelect,
|
mcpSelect,
|
||||||
webSearch,
|
webSearch,
|
||||||
fileSearch,
|
fileSearch,
|
||||||
|
artifacts,
|
||||||
startupConfig,
|
startupConfig,
|
||||||
conversationId,
|
conversationId,
|
||||||
codeApiKeyForm,
|
codeApiKeyForm,
|
||||||
|
|
|
||||||
152
client/src/components/Chat/Input/Artifacts.tsx
Normal file
152
client/src/components/Chat/Input/Artifacts.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
import React, { memo, useState, useCallback, useMemo } from 'react';
|
||||||
|
import * as Ariakit from '@ariakit/react';
|
||||||
|
import { ArtifactModes } from 'librechat-data-provider';
|
||||||
|
import { WandSparkles, ChevronDown } from 'lucide-react';
|
||||||
|
import CheckboxButton from '~/components/ui/CheckboxButton';
|
||||||
|
import { useBadgeRowContext } from '~/Providers';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
interface ArtifactsToggleState {
|
||||||
|
enabled: boolean;
|
||||||
|
mode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Artifacts() {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { artifacts } = useBadgeRowContext();
|
||||||
|
const { toggleState, debouncedChange, isPinned } = artifacts;
|
||||||
|
|
||||||
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||||
|
|
||||||
|
const currentState = useMemo<ArtifactsToggleState>(() => {
|
||||||
|
if (typeof toggleState === 'string' && toggleState) {
|
||||||
|
return { enabled: true, mode: toggleState };
|
||||||
|
}
|
||||||
|
return { enabled: false, mode: '' };
|
||||||
|
}, [toggleState]);
|
||||||
|
|
||||||
|
const isEnabled = currentState.enabled;
|
||||||
|
const isShadcnEnabled = currentState.mode === ArtifactModes.SHADCNUI;
|
||||||
|
const isCustomEnabled = currentState.mode === ArtifactModes.CUSTOM;
|
||||||
|
|
||||||
|
const handleToggle = useCallback(() => {
|
||||||
|
if (isEnabled) {
|
||||||
|
debouncedChange({ value: '' });
|
||||||
|
} else {
|
||||||
|
debouncedChange({ value: ArtifactModes.DEFAULT });
|
||||||
|
}
|
||||||
|
}, [isEnabled, debouncedChange]);
|
||||||
|
|
||||||
|
const handleShadcnToggle = useCallback(() => {
|
||||||
|
if (isShadcnEnabled) {
|
||||||
|
debouncedChange({ value: ArtifactModes.DEFAULT });
|
||||||
|
} else {
|
||||||
|
debouncedChange({ value: ArtifactModes.SHADCNUI });
|
||||||
|
}
|
||||||
|
}, [isShadcnEnabled, debouncedChange]);
|
||||||
|
|
||||||
|
const handleCustomToggle = useCallback(() => {
|
||||||
|
if (isCustomEnabled) {
|
||||||
|
debouncedChange({ value: ArtifactModes.DEFAULT });
|
||||||
|
} else {
|
||||||
|
debouncedChange({ value: ArtifactModes.CUSTOM });
|
||||||
|
}
|
||||||
|
}, [isCustomEnabled, debouncedChange]);
|
||||||
|
|
||||||
|
if (!isEnabled && !isPinned) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex">
|
||||||
|
<CheckboxButton
|
||||||
|
className={cn('max-w-fit', isEnabled && 'rounded-r-none border-r-0')}
|
||||||
|
checked={isEnabled}
|
||||||
|
setValue={handleToggle}
|
||||||
|
label={localize('com_ui_artifacts')}
|
||||||
|
isCheckedClassName="border-amber-600/40 bg-amber-500/10 hover:bg-amber-700/10"
|
||||||
|
icon={<WandSparkles className="icon-md" />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isEnabled && (
|
||||||
|
<Ariakit.MenuProvider open={isPopoverOpen} setOpen={setIsPopoverOpen}>
|
||||||
|
<Ariakit.MenuButton
|
||||||
|
className={cn(
|
||||||
|
'w-7 rounded-l-none rounded-r-full border-b border-l-0 border-r border-t border-border-light md:w-6',
|
||||||
|
'border-amber-600/40 bg-amber-500/10 hover:bg-amber-700/10',
|
||||||
|
'transition-colors',
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<ChevronDown className="ml-1 h-4 w-4 text-text-secondary md:ml-0" />
|
||||||
|
</Ariakit.MenuButton>
|
||||||
|
|
||||||
|
<Ariakit.Menu
|
||||||
|
gutter={8}
|
||||||
|
className={cn(
|
||||||
|
'animate-popover z-50 flex max-h-[300px]',
|
||||||
|
'flex-col overflow-auto overscroll-contain rounded-xl',
|
||||||
|
'bg-surface-secondary px-1.5 py-1 text-text-primary shadow-lg',
|
||||||
|
'border border-border-light',
|
||||||
|
'min-w-[250px] outline-none',
|
||||||
|
)}
|
||||||
|
portal
|
||||||
|
>
|
||||||
|
<div className="px-2 py-1.5">
|
||||||
|
<div className="mb-2 text-xs font-medium text-text-secondary">
|
||||||
|
{localize('com_ui_artifacts_options')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Include shadcn/ui Option */}
|
||||||
|
<Ariakit.MenuItem
|
||||||
|
hideOnClick={false}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleShadcnToggle();
|
||||||
|
}}
|
||||||
|
disabled={isCustomEnabled}
|
||||||
|
className={cn(
|
||||||
|
'mb-1 flex items-center justify-between rounded-lg px-2 py-2',
|
||||||
|
'cursor-pointer outline-none transition-colors',
|
||||||
|
'hover:bg-black/[0.075] dark:hover:bg-white/10',
|
||||||
|
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
|
||||||
|
isCustomEnabled && 'cursor-not-allowed opacity-50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Ariakit.MenuItemCheck checked={isShadcnEnabled} />
|
||||||
|
<span className="text-sm">{localize('com_ui_include_shadcnui' as any)}</span>
|
||||||
|
</div>
|
||||||
|
</Ariakit.MenuItem>
|
||||||
|
|
||||||
|
{/* Custom Prompt Mode Option */}
|
||||||
|
<Ariakit.MenuItem
|
||||||
|
hideOnClick={false}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleCustomToggle();
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center justify-between rounded-lg px-2 py-2',
|
||||||
|
'cursor-pointer outline-none transition-colors',
|
||||||
|
'hover:bg-black/[0.075] dark:hover:bg-white/10',
|
||||||
|
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Ariakit.MenuItemCheck checked={isCustomEnabled} />
|
||||||
|
<span className="text-sm">{localize('com_ui_custom_prompt_mode' as any)}</span>
|
||||||
|
</div>
|
||||||
|
</Ariakit.MenuItem>
|
||||||
|
</div>
|
||||||
|
</Ariakit.Menu>
|
||||||
|
</Ariakit.MenuProvider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Artifacts);
|
||||||
147
client/src/components/Chat/Input/ArtifactsSubMenu.tsx
Normal file
147
client/src/components/Chat/Input/ArtifactsSubMenu.tsx
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
import React from 'react';
|
||||||
|
import * as Ariakit from '@ariakit/react';
|
||||||
|
import { ChevronRight, WandSparkles } from 'lucide-react';
|
||||||
|
import { ArtifactModes } from 'librechat-data-provider';
|
||||||
|
import { PinIcon } from '~/components/svg';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
interface ArtifactsSubMenuProps {
|
||||||
|
isArtifactsPinned: boolean;
|
||||||
|
setIsArtifactsPinned: (value: boolean) => void;
|
||||||
|
artifactsMode: string;
|
||||||
|
handleArtifactsToggle: () => void;
|
||||||
|
handleShadcnToggle: () => void;
|
||||||
|
handleCustomToggle: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArtifactsSubMenu = ({
|
||||||
|
isArtifactsPinned,
|
||||||
|
setIsArtifactsPinned,
|
||||||
|
artifactsMode,
|
||||||
|
handleArtifactsToggle,
|
||||||
|
handleShadcnToggle,
|
||||||
|
handleCustomToggle,
|
||||||
|
...props
|
||||||
|
}: ArtifactsSubMenuProps) => {
|
||||||
|
const localize = useLocalize();
|
||||||
|
|
||||||
|
const menuStore = Ariakit.useMenuStore({
|
||||||
|
focusLoop: true,
|
||||||
|
showTimeout: 100,
|
||||||
|
placement: 'right',
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEnabled = artifactsMode !== '' && artifactsMode !== undefined;
|
||||||
|
const isShadcnEnabled = artifactsMode === ArtifactModes.SHADCNUI;
|
||||||
|
const isCustomEnabled = artifactsMode === ArtifactModes.CUSTOM;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Ariakit.MenuProvider store={menuStore}>
|
||||||
|
<Ariakit.MenuItem
|
||||||
|
{...props}
|
||||||
|
hideOnClick={false}
|
||||||
|
render={
|
||||||
|
<Ariakit.MenuButton
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleArtifactsToggle();
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
if (isEnabled) {
|
||||||
|
menuStore.show();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex w-full cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-surface-hover"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<WandSparkles className="icon-md" />
|
||||||
|
<span>{localize('com_ui_artifacts')}</span>
|
||||||
|
{isEnabled && <ChevronRight className="ml-auto h-3 w-3" />}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsArtifactsPinned(!isArtifactsPinned);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'rounded p-1 transition-all duration-200',
|
||||||
|
'hover:bg-surface-tertiary hover:shadow-sm',
|
||||||
|
!isArtifactsPinned && 'text-text-secondary hover:text-text-primary',
|
||||||
|
)}
|
||||||
|
aria-label={isArtifactsPinned ? 'Unpin' : 'Pin'}
|
||||||
|
>
|
||||||
|
<div className="h-4 w-4">
|
||||||
|
<PinIcon unpin={isArtifactsPinned} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</Ariakit.MenuItem>
|
||||||
|
|
||||||
|
{isEnabled && (
|
||||||
|
<Ariakit.Menu
|
||||||
|
portal={true}
|
||||||
|
unmountOnHide={true}
|
||||||
|
className={cn(
|
||||||
|
'animate-popover-left z-50 ml-3 flex min-w-[250px] flex-col rounded-xl',
|
||||||
|
'border border-border-light bg-surface-secondary px-1.5 py-1 shadow-lg',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="px-2 py-1.5">
|
||||||
|
<div className="mb-2 text-xs font-medium text-text-secondary">
|
||||||
|
{localize('com_ui_artifacts_options')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Include shadcn/ui Option */}
|
||||||
|
<Ariakit.MenuItem
|
||||||
|
hideOnClick={false}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleShadcnToggle();
|
||||||
|
}}
|
||||||
|
disabled={isCustomEnabled}
|
||||||
|
className={cn(
|
||||||
|
'mb-1 flex items-center justify-between rounded-lg px-2 py-2',
|
||||||
|
'cursor-pointer text-text-primary outline-none transition-colors',
|
||||||
|
'hover:bg-black/[0.075] dark:hover:bg-white/10',
|
||||||
|
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
|
||||||
|
isCustomEnabled && 'cursor-not-allowed opacity-50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Ariakit.MenuItemCheck checked={isShadcnEnabled} />
|
||||||
|
<span className="text-sm">{localize('com_ui_include_shadcnui' as any)}</span>
|
||||||
|
</div>
|
||||||
|
</Ariakit.MenuItem>
|
||||||
|
|
||||||
|
{/* Custom Prompt Mode Option */}
|
||||||
|
<Ariakit.MenuItem
|
||||||
|
hideOnClick={false}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleCustomToggle();
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center justify-between rounded-lg px-2 py-2',
|
||||||
|
'cursor-pointer text-text-primary outline-none transition-colors',
|
||||||
|
'hover:bg-black/[0.075] dark:hover:bg-white/10',
|
||||||
|
'data-[active-item]:bg-black/[0.075] dark:data-[active-item]:bg-white/10',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Ariakit.MenuItemCheck checked={isCustomEnabled} />
|
||||||
|
<span className="text-sm">{localize('com_ui_custom_prompt_mode' as any)}</span>
|
||||||
|
</div>
|
||||||
|
</Ariakit.MenuItem>
|
||||||
|
</div>
|
||||||
|
</Ariakit.Menu>
|
||||||
|
)}
|
||||||
|
</Ariakit.MenuProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ArtifactsSubMenu);
|
||||||
|
|
@ -18,6 +18,7 @@ import { useChatBadges } from '~/hooks';
|
||||||
import { Badge } from '~/components/ui';
|
import { Badge } from '~/components/ui';
|
||||||
import ToolDialogs from './ToolDialogs';
|
import ToolDialogs from './ToolDialogs';
|
||||||
import FileSearch from './FileSearch';
|
import FileSearch from './FileSearch';
|
||||||
|
import Artifacts from './Artifacts';
|
||||||
import MCPSelect from './MCPSelect';
|
import MCPSelect from './MCPSelect';
|
||||||
import WebSearch from './WebSearch';
|
import WebSearch from './WebSearch';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
@ -27,6 +28,7 @@ interface BadgeRowProps {
|
||||||
onChange: (badges: Pick<BadgeItem, 'id'>[]) => void;
|
onChange: (badges: Pick<BadgeItem, 'id'>[]) => void;
|
||||||
onToggle?: (badgeId: string, currentActive: boolean) => void;
|
onToggle?: (badgeId: string, currentActive: boolean) => void;
|
||||||
conversationId?: string | null;
|
conversationId?: string | null;
|
||||||
|
isSubmitting?: boolean;
|
||||||
isInChat: boolean;
|
isInChat: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +142,7 @@ const dragReducer = (state: DragState, action: DragAction): DragState => {
|
||||||
function BadgeRow({
|
function BadgeRow({
|
||||||
showEphemeralBadges,
|
showEphemeralBadges,
|
||||||
conversationId,
|
conversationId,
|
||||||
|
isSubmitting,
|
||||||
onChange,
|
onChange,
|
||||||
onToggle,
|
onToggle,
|
||||||
isInChat,
|
isInChat,
|
||||||
|
|
@ -317,7 +320,7 @@ function BadgeRow({
|
||||||
}, [dragState.draggedBadge, handleMouseMove, handleMouseUp]);
|
}, [dragState.draggedBadge, handleMouseMove, handleMouseUp]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BadgeRowProvider conversationId={conversationId}>
|
<BadgeRowProvider conversationId={conversationId} isSubmitting={isSubmitting}>
|
||||||
<div ref={containerRef} className="relative flex flex-wrap items-center gap-2">
|
<div ref={containerRef} className="relative flex flex-wrap items-center gap-2">
|
||||||
{showEphemeralBadges === true && <ToolsDropdown />}
|
{showEphemeralBadges === true && <ToolsDropdown />}
|
||||||
{tempBadges.map((badge, index) => (
|
{tempBadges.map((badge, index) => (
|
||||||
|
|
@ -364,6 +367,7 @@ function BadgeRow({
|
||||||
<WebSearch />
|
<WebSearch />
|
||||||
<CodeInterpreter />
|
<CodeInterpreter />
|
||||||
<FileSearch />
|
<FileSearch />
|
||||||
|
<Artifacts />
|
||||||
<MCPSelect />
|
<MCPSelect />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
|
||||||
</div>
|
</div>
|
||||||
<BadgeRow
|
<BadgeRow
|
||||||
showEphemeralBadges={!isAgentsEndpoint(endpoint) && !isAssistantsEndpoint(endpoint)}
|
showEphemeralBadges={!isAgentsEndpoint(endpoint) && !isAssistantsEndpoint(endpoint)}
|
||||||
|
isSubmitting={isSubmitting || isSubmittingAdded}
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
onChange={setBadges}
|
onChange={setBadges}
|
||||||
isInChat={
|
isInChat={
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ import React, { useState, useMemo, useCallback } from 'react';
|
||||||
import * as Ariakit from '@ariakit/react';
|
import * as Ariakit from '@ariakit/react';
|
||||||
import { Globe, Settings, Settings2, TerminalSquareIcon } from 'lucide-react';
|
import { Globe, Settings, Settings2, TerminalSquareIcon } from 'lucide-react';
|
||||||
import type { MenuItemProps } from '~/common';
|
import type { MenuItemProps } from '~/common';
|
||||||
import { Permissions, PermissionTypes, AuthType } from 'librechat-data-provider';
|
import { Permissions, PermissionTypes, AuthType, ArtifactModes } from 'librechat-data-provider';
|
||||||
import { TooltipAnchor, DropdownPopup } from '~/components';
|
import { TooltipAnchor, DropdownPopup } from '~/components';
|
||||||
|
import ArtifactsSubMenu from '~/components/Chat/Input/ArtifactsSubMenu';
|
||||||
import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu';
|
import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu';
|
||||||
import { PinIcon, VectorIcon } from '~/components/svg';
|
import { PinIcon, VectorIcon } from '~/components/svg';
|
||||||
import { useLocalize, useHasAccess } from '~/hooks';
|
import { useLocalize, useHasAccess } from '~/hooks';
|
||||||
|
|
@ -21,6 +22,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
||||||
const {
|
const {
|
||||||
webSearch,
|
webSearch,
|
||||||
mcpSelect,
|
mcpSelect,
|
||||||
|
artifacts,
|
||||||
fileSearch,
|
fileSearch,
|
||||||
startupConfig,
|
startupConfig,
|
||||||
codeApiKeyForm,
|
codeApiKeyForm,
|
||||||
|
|
@ -42,6 +44,7 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
||||||
authData: codeAuthData,
|
authData: codeAuthData,
|
||||||
} = codeInterpreter;
|
} = codeInterpreter;
|
||||||
const { isPinned: isFileSearchPinned, setIsPinned: setIsFileSearchPinned } = fileSearch;
|
const { isPinned: isFileSearchPinned, setIsPinned: setIsFileSearchPinned } = fileSearch;
|
||||||
|
const { isPinned: isArtifactsPinned, setIsPinned: setIsArtifactsPinned } = artifacts;
|
||||||
const {
|
const {
|
||||||
mcpValues,
|
mcpValues,
|
||||||
mcpServerNames,
|
mcpServerNames,
|
||||||
|
|
@ -72,19 +75,46 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
||||||
|
|
||||||
const handleWebSearchToggle = useCallback(() => {
|
const handleWebSearchToggle = useCallback(() => {
|
||||||
const newValue = !webSearch.toggleState;
|
const newValue = !webSearch.toggleState;
|
||||||
webSearch.debouncedChange({ isChecked: newValue });
|
webSearch.debouncedChange({ value: newValue });
|
||||||
}, [webSearch]);
|
}, [webSearch]);
|
||||||
|
|
||||||
const handleCodeInterpreterToggle = useCallback(() => {
|
const handleCodeInterpreterToggle = useCallback(() => {
|
||||||
const newValue = !codeInterpreter.toggleState;
|
const newValue = !codeInterpreter.toggleState;
|
||||||
codeInterpreter.debouncedChange({ isChecked: newValue });
|
codeInterpreter.debouncedChange({ value: newValue });
|
||||||
}, [codeInterpreter]);
|
}, [codeInterpreter]);
|
||||||
|
|
||||||
const handleFileSearchToggle = useCallback(() => {
|
const handleFileSearchToggle = useCallback(() => {
|
||||||
const newValue = !fileSearch.toggleState;
|
const newValue = !fileSearch.toggleState;
|
||||||
fileSearch.debouncedChange({ isChecked: newValue });
|
fileSearch.debouncedChange({ value: newValue });
|
||||||
}, [fileSearch]);
|
}, [fileSearch]);
|
||||||
|
|
||||||
|
const handleArtifactsToggle = useCallback(() => {
|
||||||
|
const currentState = artifacts.toggleState;
|
||||||
|
if (!currentState || currentState === '') {
|
||||||
|
artifacts.debouncedChange({ value: ArtifactModes.DEFAULT });
|
||||||
|
} else {
|
||||||
|
artifacts.debouncedChange({ value: '' });
|
||||||
|
}
|
||||||
|
}, [artifacts]);
|
||||||
|
|
||||||
|
const handleShadcnToggle = useCallback(() => {
|
||||||
|
const currentState = artifacts.toggleState;
|
||||||
|
if (currentState === ArtifactModes.SHADCNUI) {
|
||||||
|
artifacts.debouncedChange({ value: ArtifactModes.DEFAULT });
|
||||||
|
} else {
|
||||||
|
artifacts.debouncedChange({ value: ArtifactModes.SHADCNUI });
|
||||||
|
}
|
||||||
|
}, [artifacts]);
|
||||||
|
|
||||||
|
const handleCustomToggle = useCallback(() => {
|
||||||
|
const currentState = artifacts.toggleState;
|
||||||
|
if (currentState === ArtifactModes.CUSTOM) {
|
||||||
|
artifacts.debouncedChange({ value: ArtifactModes.DEFAULT });
|
||||||
|
} else {
|
||||||
|
artifacts.debouncedChange({ value: ArtifactModes.CUSTOM });
|
||||||
|
}
|
||||||
|
}, [artifacts]);
|
||||||
|
|
||||||
const handleMCPToggle = useCallback(
|
const handleMCPToggle = useCallback(
|
||||||
(serverName: string) => {
|
(serverName: string) => {
|
||||||
const currentValues = mcpSelect.mcpValues ?? [];
|
const currentValues = mcpSelect.mcpValues ?? [];
|
||||||
|
|
@ -238,6 +268,22 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Artifacts option
|
||||||
|
items.push({
|
||||||
|
hideOnClick: false,
|
||||||
|
render: (props) => (
|
||||||
|
<ArtifactsSubMenu
|
||||||
|
{...props}
|
||||||
|
isArtifactsPinned={isArtifactsPinned}
|
||||||
|
setIsArtifactsPinned={setIsArtifactsPinned}
|
||||||
|
artifactsMode={artifacts.toggleState as string}
|
||||||
|
handleArtifactsToggle={handleArtifactsToggle}
|
||||||
|
handleShadcnToggle={handleShadcnToggle}
|
||||||
|
handleCustomToggle={handleCustomToggle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
if (mcpServerNames && mcpServerNames.length > 0) {
|
if (mcpServerNames && mcpServerNames.length > 0) {
|
||||||
items.push({
|
items.push({
|
||||||
hideOnClick: false,
|
hideOnClick: false,
|
||||||
|
|
@ -271,15 +317,21 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => {
|
||||||
handleMCPToggle,
|
handleMCPToggle,
|
||||||
showCodeSettings,
|
showCodeSettings,
|
||||||
setIsSearchPinned,
|
setIsSearchPinned,
|
||||||
|
handleShadcnToggle,
|
||||||
|
handleCustomToggle,
|
||||||
isFileSearchPinned,
|
isFileSearchPinned,
|
||||||
|
isArtifactsPinned,
|
||||||
codeMenuTriggerRef,
|
codeMenuTriggerRef,
|
||||||
setIsCodeDialogOpen,
|
setIsCodeDialogOpen,
|
||||||
searchMenuTriggerRef,
|
searchMenuTriggerRef,
|
||||||
showWebSearchSettings,
|
showWebSearchSettings,
|
||||||
setIsFileSearchPinned,
|
setIsFileSearchPinned,
|
||||||
|
artifacts.toggleState,
|
||||||
|
setIsArtifactsPinned,
|
||||||
handleWebSearchToggle,
|
handleWebSearchToggle,
|
||||||
setIsSearchDialogOpen,
|
setIsSearchDialogOpen,
|
||||||
handleFileSearchToggle,
|
handleFileSearchToggle,
|
||||||
|
handleArtifactsToggle,
|
||||||
handleCodeInterpreterToggle,
|
handleCodeInterpreterToggle,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import {
|
||||||
General,
|
General,
|
||||||
Chat,
|
Chat,
|
||||||
Speech,
|
Speech,
|
||||||
Beta,
|
|
||||||
Commands,
|
Commands,
|
||||||
Data,
|
Data,
|
||||||
Account,
|
Account,
|
||||||
|
|
@ -233,9 +232,6 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
<Tabs.Content value={SettingsTabValues.CHAT}>
|
<Tabs.Content value={SettingsTabValues.CHAT}>
|
||||||
<Chat />
|
<Chat />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
<Tabs.Content value={SettingsTabValues.BETA}>
|
|
||||||
<Beta />
|
|
||||||
</Tabs.Content>
|
|
||||||
<Tabs.Content value={SettingsTabValues.COMMANDS}>
|
<Tabs.Content value={SettingsTabValues.COMMANDS}>
|
||||||
<Commands />
|
<Commands />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { memo } from 'react';
|
|
||||||
import CodeArtifacts from './CodeArtifacts';
|
|
||||||
import ChatBadges from './ChatBadges';
|
|
||||||
|
|
||||||
function Beta() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-3 p-1 text-sm text-text-primary">
|
|
||||||
<div className="pb-3">
|
|
||||||
<CodeArtifacts />
|
|
||||||
</div>
|
|
||||||
{/* <div className="pb-3">
|
|
||||||
<ChatBadges />
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(Beta);
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
import { Button } from '~/components/ui';
|
|
||||||
import { useLocalize } from '~/hooks';
|
|
||||||
import store from '~/store';
|
|
||||||
|
|
||||||
export default function ChatBadges() {
|
|
||||||
const setIsEditing = useSetRecoilState<boolean>(store.isEditingBadges);
|
|
||||||
const localize = useLocalize();
|
|
||||||
|
|
||||||
const handleEditChatBadges = () => {
|
|
||||||
setIsEditing(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>{localize('com_nav_edit_chat_badges')}</div>
|
|
||||||
<Button variant="outline" onClick={handleEditChatBadges}>
|
|
||||||
{localize('com_ui_edit')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings';
|
|
||||||
import { Switch } from '~/components/ui';
|
|
||||||
import { useLocalize } from '~/hooks';
|
|
||||||
import store from '~/store';
|
|
||||||
|
|
||||||
export default function CodeArtifacts() {
|
|
||||||
const [codeArtifacts, setCodeArtifacts] = useRecoilState<boolean>(store.codeArtifacts);
|
|
||||||
const [includeShadcnui, setIncludeShadcnui] = useRecoilState<boolean>(store.includeShadcnui);
|
|
||||||
const [customPromptMode, setCustomPromptMode] = useRecoilState<boolean>(store.customPromptMode);
|
|
||||||
const localize = useLocalize();
|
|
||||||
|
|
||||||
const handleCodeArtifactsChange = (value: boolean) => {
|
|
||||||
setCodeArtifacts(value);
|
|
||||||
if (!value) {
|
|
||||||
setIncludeShadcnui(false);
|
|
||||||
setCustomPromptMode(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIncludeShadcnuiChange = (value: boolean) => {
|
|
||||||
setIncludeShadcnui(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCustomPromptModeChange = (value: boolean) => {
|
|
||||||
setCustomPromptMode(value);
|
|
||||||
if (value) {
|
|
||||||
setIncludeShadcnui(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h3 className="text-lg font-medium">{localize('com_ui_artifacts')}</h3>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<SwitchItem
|
|
||||||
id="codeArtifacts"
|
|
||||||
label={localize('com_ui_artifacts_toggle')}
|
|
||||||
checked={codeArtifacts}
|
|
||||||
onCheckedChange={handleCodeArtifactsChange}
|
|
||||||
hoverCardText="com_nav_info_code_artifacts"
|
|
||||||
/>
|
|
||||||
<SwitchItem
|
|
||||||
id="includeShadcnui"
|
|
||||||
label={localize('com_ui_include_shadcnui')}
|
|
||||||
checked={includeShadcnui}
|
|
||||||
onCheckedChange={handleIncludeShadcnuiChange}
|
|
||||||
hoverCardText="com_nav_info_include_shadcnui"
|
|
||||||
disabled={!codeArtifacts || customPromptMode}
|
|
||||||
/>
|
|
||||||
<SwitchItem
|
|
||||||
id="customPromptMode"
|
|
||||||
label={localize('com_ui_custom_prompt_mode')}
|
|
||||||
checked={customPromptMode}
|
|
||||||
onCheckedChange={handleCustomPromptModeChange}
|
|
||||||
hoverCardText="com_nav_info_custom_prompt_mode"
|
|
||||||
disabled={!codeArtifacts}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SwitchItem({
|
|
||||||
id,
|
|
||||||
label,
|
|
||||||
checked,
|
|
||||||
onCheckedChange,
|
|
||||||
hoverCardText,
|
|
||||||
disabled = false,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
checked: boolean;
|
|
||||||
onCheckedChange: (value: boolean) => void;
|
|
||||||
hoverCardText: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<div className={disabled ? 'text-gray-400' : ''}>{label}</div>
|
|
||||||
<HoverCardSettings side="bottom" text={hoverCardText} />
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
id={id}
|
|
||||||
checked={checked}
|
|
||||||
onCheckedChange={onCheckedChange}
|
|
||||||
className="ml-4"
|
|
||||||
data-testid={id}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
export { default as General } from './General/General';
|
export { default as General } from './General/General';
|
||||||
export { default as Chat } from './Chat/Chat';
|
export { default as Chat } from './Chat/Chat';
|
||||||
export { default as Data } from './Data/Data';
|
export { default as Data } from './Data/Data';
|
||||||
export { default as Beta } from './Beta/Beta';
|
|
||||||
export { default as Commands } from './Commands/Commands';
|
export { default as Commands } from './Commands/Commands';
|
||||||
export { RevokeKeysButton } from './Data/RevokeKeysButton';
|
export { RevokeKeysButton } from './Data/RevokeKeysButton';
|
||||||
export { default as Account } from './Account/Account';
|
export { default as Account } from './Account/Account';
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export default function Artifacts() {
|
||||||
/>
|
/>
|
||||||
<SwitchItem
|
<SwitchItem
|
||||||
id="includeShadcnui"
|
id="includeShadcnui"
|
||||||
label={localize('com_ui_include_shadcnui_agent')}
|
label={localize('com_ui_include_shadcnui')}
|
||||||
checked={isShadcnEnabled}
|
checked={isShadcnEnabled}
|
||||||
onCheckedChange={handleShadcnuiChange}
|
onCheckedChange={handleShadcnuiChange}
|
||||||
hoverCardText={localize('com_nav_info_include_shadcnui')}
|
hoverCardText={localize('com_nav_info_include_shadcnui')}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ const CheckboxButton = React.forwardRef<
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
defaultChecked?: boolean;
|
defaultChecked?: boolean;
|
||||||
isCheckedClassName?: string;
|
isCheckedClassName?: string;
|
||||||
setValue?: (values: { e?: React.ChangeEvent<HTMLInputElement>; isChecked: boolean }) => void;
|
setValue?: (values: {
|
||||||
|
e?: React.ChangeEvent<HTMLInputElement>;
|
||||||
|
value: boolean | string;
|
||||||
|
}) => void;
|
||||||
}
|
}
|
||||||
>(({ icon, label, setValue, className, checked, defaultChecked, isCheckedClassName }, ref) => {
|
>(({ icon, label, setValue, className, checked, defaultChecked, isCheckedClassName }, ref) => {
|
||||||
const checkbox = useCheckboxStore();
|
const checkbox = useCheckboxStore();
|
||||||
|
|
@ -22,7 +25,7 @@ const CheckboxButton = React.forwardRef<
|
||||||
if (typeof isChecked !== 'boolean') {
|
if (typeof isChecked !== 'boolean') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setValue?.({ e, isChecked: !isChecked });
|
setValue?.({ e, value: !isChecked });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sync with controlled checked prop
|
// Sync with controlled checked prop
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import type { TAskFunction, ExtendedFile } from '~/common';
|
||||||
import useSetFilesToDelete from '~/hooks/Files/useSetFilesToDelete';
|
import useSetFilesToDelete from '~/hooks/Files/useSetFilesToDelete';
|
||||||
import useGetSender from '~/hooks/Conversations/useGetSender';
|
import useGetSender from '~/hooks/Conversations/useGetSender';
|
||||||
import store, { useGetEphemeralAgent } from '~/store';
|
import store, { useGetEphemeralAgent } from '~/store';
|
||||||
import { getArtifactsMode } from '~/utils/artifacts';
|
|
||||||
import { getEndpointField, logger } from '~/utils';
|
import { getEndpointField, logger } from '~/utils';
|
||||||
import useUserKey from '~/hooks/Input/useUserKey';
|
import useUserKey from '~/hooks/Input/useUserKey';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
@ -68,9 +67,6 @@ export default function useChatFunctions({
|
||||||
const setFilesToDelete = useSetFilesToDelete();
|
const setFilesToDelete = useSetFilesToDelete();
|
||||||
const getEphemeralAgent = useGetEphemeralAgent();
|
const getEphemeralAgent = useGetEphemeralAgent();
|
||||||
const isTemporary = useRecoilValue(store.isTemporary);
|
const isTemporary = useRecoilValue(store.isTemporary);
|
||||||
const codeArtifacts = useRecoilValue(store.codeArtifacts);
|
|
||||||
const includeShadcnui = useRecoilValue(store.includeShadcnui);
|
|
||||||
const customPromptMode = useRecoilValue(store.customPromptMode);
|
|
||||||
const { getExpiry } = useUserKey(immutableConversation?.endpoint ?? '');
|
const { getExpiry } = useUserKey(immutableConversation?.endpoint ?? '');
|
||||||
const setShowStopButton = useSetRecoilState(store.showStopButtonByIndex(index));
|
const setShowStopButton = useSetRecoilState(store.showStopButtonByIndex(index));
|
||||||
const resetLatestMultiMessage = useResetRecoilState(store.latestMessageFamily(index + 1));
|
const resetLatestMultiMessage = useResetRecoilState(store.latestMessageFamily(index + 1));
|
||||||
|
|
@ -187,10 +183,6 @@ export default function useChatFunctions({
|
||||||
endpointType,
|
endpointType,
|
||||||
overrideConvoId,
|
overrideConvoId,
|
||||||
overrideUserMessageId,
|
overrideUserMessageId,
|
||||||
artifacts:
|
|
||||||
endpoint !== EModelEndpoint.agents
|
|
||||||
? getArtifactsMode({ codeArtifacts, includeShadcnui, customPromptMode })
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
convo,
|
convo,
|
||||||
) as TEndpointOption;
|
) as TEndpointOption;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useRef, useEffect, useCallback, useMemo } from 'react';
|
import { useCallback, useMemo, useEffect } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
import { Constants, LocalStorageKeys } from 'librechat-data-provider';
|
import { Constants, LocalStorageKeys } from 'librechat-data-provider';
|
||||||
import type { VerifyToolAuthResponse } from 'librechat-data-provider';
|
import type { VerifyToolAuthResponse } from 'librechat-data-provider';
|
||||||
import type { UseQueryOptions } from '@tanstack/react-query';
|
import type { UseQueryOptions } from '@tanstack/react-query';
|
||||||
|
|
@ -19,9 +19,11 @@ const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value !== undefined && value !== null && value !== '' && value !== false;
|
return value !== undefined && value !== null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ToolValue = boolean | string;
|
||||||
|
|
||||||
interface UseToolToggleOptions {
|
interface UseToolToggleOptions {
|
||||||
conversationId?: string | null;
|
conversationId?: string | null;
|
||||||
toolKey: string;
|
toolKey: string;
|
||||||
|
|
@ -60,36 +62,52 @@ export function useToolToggle({
|
||||||
[externalIsAuthenticated, authConfig, authQuery.data?.authenticated],
|
[externalIsAuthenticated, authConfig, authQuery.data?.authenticated],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isToolEnabled = useMemo(() => {
|
// Keep localStorage in sync
|
||||||
return ephemeralAgent?.[toolKey] ?? false;
|
const [, setLocalStorageValue] = useLocalStorage<ToolValue>(
|
||||||
}, [ephemeralAgent, toolKey]);
|
|
||||||
|
|
||||||
/** Track previous value to prevent infinite loops */
|
|
||||||
const prevIsToolEnabled = useRef(isToolEnabled);
|
|
||||||
|
|
||||||
const [toggleState, setToggleState] = useLocalStorage<boolean>(
|
|
||||||
`${localStorageKey}${key}`,
|
`${localStorageKey}${key}`,
|
||||||
isToolEnabled,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
storageCondition,
|
storageCondition,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The actual current value comes from ephemeralAgent
|
||||||
|
const toolValue = useMemo(() => {
|
||||||
|
return ephemeralAgent?.[toolKey] ?? false;
|
||||||
|
}, [ephemeralAgent, toolKey]);
|
||||||
|
|
||||||
|
const isToolEnabled = useMemo(() => {
|
||||||
|
// For backward compatibility, treat truthy string values as enabled
|
||||||
|
if (typeof toolValue === 'string') {
|
||||||
|
return toolValue.length > 0;
|
||||||
|
}
|
||||||
|
return toolValue === true;
|
||||||
|
}, [toolValue]);
|
||||||
|
|
||||||
|
// Sync to localStorage when ephemeralAgent changes
|
||||||
|
useEffect(() => {
|
||||||
|
const value = ephemeralAgent?.[toolKey];
|
||||||
|
if (value !== undefined) {
|
||||||
|
setLocalStorageValue(value);
|
||||||
|
}
|
||||||
|
}, [ephemeralAgent, toolKey, setLocalStorageValue]);
|
||||||
|
|
||||||
const [isPinned, setIsPinned] = useLocalStorage<boolean>(`${localStorageKey}pinned`, false);
|
const [isPinned, setIsPinned] = useLocalStorage<boolean>(`${localStorageKey}pinned`, false);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
({ e, isChecked }: { e?: React.ChangeEvent<HTMLInputElement>; isChecked: boolean }) => {
|
({ e, value }: { e?: React.ChangeEvent<HTMLInputElement>; value: ToolValue }) => {
|
||||||
if (isAuthenticated !== undefined && !isAuthenticated && setIsDialogOpen) {
|
if (isAuthenticated !== undefined && !isAuthenticated && setIsDialogOpen) {
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
e?.preventDefault?.();
|
e?.preventDefault?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setToggleState(isChecked);
|
|
||||||
|
// Update ephemeralAgent (localStorage will sync automatically via effect)
|
||||||
setEphemeralAgent((prev) => ({
|
setEphemeralAgent((prev) => ({
|
||||||
...prev,
|
...(prev || {}),
|
||||||
[toolKey]: isChecked,
|
[toolKey]: value,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
[setToggleState, setIsDialogOpen, isAuthenticated, setEphemeralAgent, toolKey],
|
[setIsDialogOpen, isAuthenticated, setEphemeralAgent, toolKey],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debouncedChange = useMemo(
|
const debouncedChange = useMemo(
|
||||||
|
|
@ -97,18 +115,12 @@ export function useToolToggle({
|
||||||
[handleChange],
|
[handleChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (prevIsToolEnabled.current !== isToolEnabled) {
|
|
||||||
setToggleState(isToolEnabled);
|
|
||||||
}
|
|
||||||
prevIsToolEnabled.current = isToolEnabled;
|
|
||||||
}, [isToolEnabled, setToggleState]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toggleState,
|
toggleState: toolValue, // Return the actual value from ephemeralAgent
|
||||||
handleChange,
|
handleChange,
|
||||||
isToolEnabled,
|
isToolEnabled,
|
||||||
setToggleState,
|
toolValue,
|
||||||
|
setToggleState: (value: ToolValue) => handleChange({ value }), // Adapter for direct setting
|
||||||
ephemeralAgent,
|
ephemeralAgent,
|
||||||
debouncedChange,
|
debouncedChange,
|
||||||
setEphemeralAgent,
|
setEphemeralAgent,
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ const createErrorMessage = ({
|
||||||
errorMetadata?: Partial<TMessage>;
|
errorMetadata?: Partial<TMessage>;
|
||||||
submission: EventSubmission;
|
submission: EventSubmission;
|
||||||
error?: Error | unknown;
|
error?: Error | unknown;
|
||||||
}) => {
|
}): TMessage => {
|
||||||
const currentMessages = getMessages();
|
const currentMessages = getMessages();
|
||||||
const latestMessage = currentMessages?.[currentMessages.length - 1];
|
const latestMessage = currentMessages?.[currentMessages.length - 1];
|
||||||
let errorMessage: TMessage;
|
let errorMessage: TMessage;
|
||||||
|
|
@ -123,7 +123,7 @@ const createErrorMessage = ({
|
||||||
error: true,
|
error: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return tMessageSchema.parse(errorMessage);
|
return tMessageSchema.parse(errorMessage) as TMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConvoTitle = ({
|
export const getConvoTitle = ({
|
||||||
|
|
@ -374,9 +374,6 @@ export default function useEventHandlers({
|
||||||
});
|
});
|
||||||
|
|
||||||
let update = {} as TConversation;
|
let update = {} as TConversation;
|
||||||
if (conversationId) {
|
|
||||||
applyAgentTemplate(conversationId, submission.conversation.conversationId);
|
|
||||||
}
|
|
||||||
if (setConversation && !isAddedRequest) {
|
if (setConversation && !isAddedRequest) {
|
||||||
setConversation((prevState) => {
|
setConversation((prevState) => {
|
||||||
const parentId = isRegenerate ? userMessage.overrideParentMessageId : parentMessageId;
|
const parentId = isRegenerate ? userMessage.overrideParentMessageId : parentMessageId;
|
||||||
|
|
@ -411,6 +408,14 @@ export default function useEventHandlers({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conversationId) {
|
||||||
|
applyAgentTemplate(
|
||||||
|
conversationId,
|
||||||
|
submission.conversation.conversationId,
|
||||||
|
submission.ephemeralAgent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (resetLatestMessage) {
|
if (resetLatestMessage) {
|
||||||
resetLatestMessage();
|
resetLatestMessage();
|
||||||
}
|
}
|
||||||
|
|
@ -513,6 +518,15 @@ export default function useEventHandlers({
|
||||||
}
|
}
|
||||||
return update;
|
return update;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (conversation.conversationId && submission.ephemeralAgent) {
|
||||||
|
applyAgentTemplate(
|
||||||
|
conversation.conversationId,
|
||||||
|
submissionConvo.conversationId,
|
||||||
|
submission.ephemeralAgent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (location.pathname === '/c/new') {
|
if (location.pathname === '/c/new') {
|
||||||
navigate(`/c/${conversation.conversationId}`, { replace: true });
|
navigate(`/c/${conversation.conversationId}`, { replace: true });
|
||||||
}
|
}
|
||||||
|
|
@ -521,18 +535,19 @@ export default function useEventHandlers({
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
setShowStopButton,
|
navigate,
|
||||||
setCompleted,
|
|
||||||
getMessages,
|
|
||||||
announcePolite,
|
|
||||||
genTitle,
|
genTitle,
|
||||||
setConversation,
|
getMessages,
|
||||||
isAddedRequest,
|
|
||||||
setIsSubmitting,
|
|
||||||
setMessages,
|
setMessages,
|
||||||
queryClient,
|
queryClient,
|
||||||
|
setCompleted,
|
||||||
|
isAddedRequest,
|
||||||
|
announcePolite,
|
||||||
|
setConversation,
|
||||||
|
setIsSubmitting,
|
||||||
|
setShowStopButton,
|
||||||
location.pathname,
|
location.pathname,
|
||||||
navigate,
|
applyAgentTemplate,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -550,7 +565,7 @@ export default function useEventHandlers({
|
||||||
queryClient.setQueryData<TMessage[]>([QueryKeys.messages, convoId], finalMessages);
|
queryClient.setQueryData<TMessage[]>([QueryKeys.messages, convoId], finalMessages);
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseErrorResponse = (data: TResData | Partial<TMessage>) => {
|
const parseErrorResponse = (data: TResData | Partial<TMessage>): TMessage => {
|
||||||
const metadata = data['responseMessage'] ?? data;
|
const metadata = data['responseMessage'] ?? data;
|
||||||
const errorMessage: Partial<TMessage> = {
|
const errorMessage: Partial<TMessage> = {
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
|
|
@ -563,7 +578,7 @@ export default function useEventHandlers({
|
||||||
errorMessage.messageId = v4();
|
errorMessage.messageId = v4();
|
||||||
}
|
}
|
||||||
|
|
||||||
return tMessageSchema.parse(errorMessage);
|
return tMessageSchema.parse(errorMessage) as TMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|
@ -613,7 +628,7 @@ export default function useEventHandlers({
|
||||||
...data,
|
...data,
|
||||||
error: true,
|
error: true,
|
||||||
parentMessageId: userMessage.messageId,
|
parentMessageId: userMessage.messageId,
|
||||||
});
|
}) as TMessage;
|
||||||
|
|
||||||
setErrorMessages(receivedConvoId, errorResponse);
|
setErrorMessages(receivedConvoId, errorResponse);
|
||||||
if (receivedConvoId && paramId === Constants.NEW_CONVO && newConversation) {
|
if (receivedConvoId && paramId === Constants.NEW_CONVO && newConversation) {
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,6 @@
|
||||||
"com_nav_delete_cache_storage": "Delete TTS cache storage",
|
"com_nav_delete_cache_storage": "Delete TTS cache storage",
|
||||||
"com_nav_delete_data_info": "All your data will be deleted.",
|
"com_nav_delete_data_info": "All your data will be deleted.",
|
||||||
"com_nav_delete_warning": "WARNING: This will permanently delete your account.",
|
"com_nav_delete_warning": "WARNING: This will permanently delete your account.",
|
||||||
"com_nav_edit_chat_badges": "Edit Chat Badges",
|
|
||||||
"com_nav_enable_cache_tts": "Enable cache TTS",
|
"com_nav_enable_cache_tts": "Enable cache TTS",
|
||||||
"com_nav_enable_cloud_browser_voice": "Use cloud-based voices",
|
"com_nav_enable_cloud_browser_voice": "Use cloud-based voices",
|
||||||
"com_nav_enabled": "Enabled",
|
"com_nav_enabled": "Enabled",
|
||||||
|
|
@ -578,6 +577,7 @@
|
||||||
"com_ui_artifacts": "Artifacts",
|
"com_ui_artifacts": "Artifacts",
|
||||||
"com_ui_artifacts_toggle": "Toggle Artifacts UI",
|
"com_ui_artifacts_toggle": "Toggle Artifacts UI",
|
||||||
"com_ui_artifacts_toggle_agent": "Enable Artifacts",
|
"com_ui_artifacts_toggle_agent": "Enable Artifacts",
|
||||||
|
"com_ui_artifacts_options": "Artifacts Options",
|
||||||
"com_ui_ascending": "Asc",
|
"com_ui_ascending": "Asc",
|
||||||
"com_ui_assistant": "Assistant",
|
"com_ui_assistant": "Assistant",
|
||||||
"com_ui_assistant_delete_error": "There was an error deleting the assistant",
|
"com_ui_assistant_delete_error": "There was an error deleting the assistant",
|
||||||
|
|
@ -819,8 +819,7 @@
|
||||||
"com_ui_import_conversation_file_type_error": "Unsupported import type",
|
"com_ui_import_conversation_file_type_error": "Unsupported import type",
|
||||||
"com_ui_import_conversation_info": "Import conversations from a JSON file",
|
"com_ui_import_conversation_info": "Import conversations from a JSON file",
|
||||||
"com_ui_import_conversation_success": "Conversations imported successfully",
|
"com_ui_import_conversation_success": "Conversations imported successfully",
|
||||||
"com_ui_include_shadcnui": "Include shadcn/ui components instructions",
|
"com_ui_include_shadcnui": "Include shadcn/ui",
|
||||||
"com_ui_include_shadcnui_agent": "Include shadcn/ui instructions",
|
|
||||||
"com_ui_input": "Input",
|
"com_ui_input": "Input",
|
||||||
"com_ui_instructions": "Instructions",
|
"com_ui_instructions": "Instructions",
|
||||||
"com_ui_key": "Key",
|
"com_ui_key": "Key",
|
||||||
|
|
@ -1075,4 +1074,4 @@
|
||||||
"com_ui_yes": "Yes",
|
"com_ui_yes": "Yes",
|
||||||
"com_ui_zoom": "Zoom",
|
"com_ui_zoom": "Zoom",
|
||||||
"com_user_message": "You"
|
"com_user_message": "You"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,11 @@ export const ephemeralAgentByConvoId = atomFamily<TEphemeralAgent | null, string
|
||||||
export function useApplyNewAgentTemplate() {
|
export function useApplyNewAgentTemplate() {
|
||||||
const applyTemplate = useRecoilCallback(
|
const applyTemplate = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
async (targetId: string, _sourceId: string | null = Constants.NEW_CONVO) => {
|
async (
|
||||||
|
targetId: string,
|
||||||
|
_sourceId: string | null = Constants.NEW_CONVO,
|
||||||
|
ephemeralAgentState?: TEphemeralAgent | null,
|
||||||
|
) => {
|
||||||
const sourceId = _sourceId || Constants.NEW_CONVO;
|
const sourceId = _sourceId || Constants.NEW_CONVO;
|
||||||
logger.log('agents', `Attempting to apply template from "${sourceId}" to "${targetId}"`);
|
logger.log('agents', `Attempting to apply template from "${sourceId}" to "${targetId}"`);
|
||||||
|
|
||||||
|
|
@ -35,7 +39,8 @@ export function useApplyNewAgentTemplate() {
|
||||||
try {
|
try {
|
||||||
// 1. Get the current agent state from the "new" conversation template using snapshot
|
// 1. Get the current agent state from the "new" conversation template using snapshot
|
||||||
// getPromise reads the value without subscribing
|
// getPromise reads the value without subscribing
|
||||||
const agentTemplate = await snapshot.getPromise(ephemeralAgentByConvoId(sourceId));
|
const agentTemplate =
|
||||||
|
ephemeralAgentState ?? (await snapshot.getPromise(ephemeralAgentByConvoId(sourceId)));
|
||||||
|
|
||||||
// 2. Check if a template state actually exists
|
// 2. Check if a template state actually exists
|
||||||
if (agentTemplate) {
|
if (agentTemplate) {
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,6 @@ const localStorageAtoms = {
|
||||||
// Beta features settings
|
// Beta features settings
|
||||||
modularChat: atomWithLocalStorage('modularChat', true),
|
modularChat: atomWithLocalStorage('modularChat', true),
|
||||||
LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true),
|
LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true),
|
||||||
codeArtifacts: atomWithLocalStorage('codeArtifacts', false),
|
|
||||||
includeShadcnui: atomWithLocalStorage('includeShadcnui', false),
|
|
||||||
customPromptMode: atomWithLocalStorage('customPromptMode', false),
|
|
||||||
centerFormOnLanding: atomWithLocalStorage('centerFormOnLanding', true),
|
centerFormOnLanding: atomWithLocalStorage('centerFormOnLanding', true),
|
||||||
showFooter: atomWithLocalStorage('showFooter', true),
|
showFooter: atomWithLocalStorage('showFooter', true),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,10 @@
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import { ArtifactModes, shadcnComponents } from 'librechat-data-provider';
|
import { shadcnComponents } from 'librechat-data-provider';
|
||||||
import type {
|
import type {
|
||||||
SandpackProviderProps,
|
SandpackProviderProps,
|
||||||
SandpackPredefinedTemplate,
|
SandpackPredefinedTemplate,
|
||||||
} from '@codesandbox/sandpack-react';
|
} from '@codesandbox/sandpack-react';
|
||||||
|
|
||||||
export const getArtifactsMode = ({
|
|
||||||
codeArtifacts,
|
|
||||||
includeShadcnui,
|
|
||||||
customPromptMode,
|
|
||||||
}: {
|
|
||||||
codeArtifacts: boolean;
|
|
||||||
includeShadcnui: boolean;
|
|
||||||
customPromptMode: boolean;
|
|
||||||
}): ArtifactModes | undefined => {
|
|
||||||
if (!codeArtifacts) {
|
|
||||||
return undefined;
|
|
||||||
} else if (customPromptMode) {
|
|
||||||
return ArtifactModes.CUSTOM;
|
|
||||||
} else if (includeShadcnui) {
|
|
||||||
return ArtifactModes.SHADCNUI;
|
|
||||||
}
|
|
||||||
return ArtifactModes.DEFAULT;
|
|
||||||
};
|
|
||||||
|
|
||||||
const artifactFilename = {
|
const artifactFilename = {
|
||||||
'application/vnd.mermaid': 'App.tsx',
|
'application/vnd.mermaid': 'App.tsx',
|
||||||
'application/vnd.react': 'App.tsx',
|
'application/vnd.react': 'App.tsx',
|
||||||
|
|
|
||||||
|
|
@ -1459,6 +1459,8 @@ export enum LocalStorageKeys {
|
||||||
LAST_WEB_SEARCH_TOGGLE_ = 'LAST_WEB_SEARCH_TOGGLE_',
|
LAST_WEB_SEARCH_TOGGLE_ = 'LAST_WEB_SEARCH_TOGGLE_',
|
||||||
/** Last checked toggle for File Search per conversation ID */
|
/** Last checked toggle for File Search per conversation ID */
|
||||||
LAST_FILE_SEARCH_TOGGLE_ = 'LAST_FILE_SEARCH_TOGGLE_',
|
LAST_FILE_SEARCH_TOGGLE_ = 'LAST_FILE_SEARCH_TOGGLE_',
|
||||||
|
/** Last checked toggle for Artifacts per conversation ID */
|
||||||
|
LAST_ARTIFACTS_TOGGLE_ = 'LAST_ARTIFACTS_TOGGLE_',
|
||||||
/** Key for the last selected agent provider */
|
/** Key for the last selected agent provider */
|
||||||
LAST_AGENT_PROVIDER = 'lastAgentProvider',
|
LAST_AGENT_PROVIDER = 'lastAgentProvider',
|
||||||
/** Key for the last selected agent model */
|
/** Key for the last selected agent model */
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,6 @@ export type TPayload = Partial<TMessage> &
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSubmission = {
|
export type TSubmission = {
|
||||||
artifacts?: string;
|
|
||||||
plugin?: TResPlugin;
|
plugin?: TResPlugin;
|
||||||
plugins?: TResPlugin[];
|
plugins?: TResPlugin[];
|
||||||
userMessage: TMessage;
|
userMessage: TMessage;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue