💡 feat: Improve Reasoning Content UI, copy-to-clipboard, and error handling (#10278)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run

*  feat: Refactor error handling and improve loading states in MessageContent component

*  feat: Enhance Thinking and ContentParts components with improved hover functionality and clipboard support

* fix: Adjust padding in Thinking and ContentParts components for consistent layout

*  feat: Add response label and improve message editing UI with contextual indicators

*  feat: Add isEditing prop to Feedback and Fork components for improved editing state handling

* refactor: Remove isEditing prop from Feedback and Fork components for cleaner state management

* refactor: Migrate state management from Recoil to Jotai for font size and show thinking features

* refactor: Separate ToggleSwitch into RecoilToggle and JotaiToggle components for improved clarity and state management

* refactor: Remove unnecessary comments in ToggleSwitch and MessageContent components for cleaner code

* chore: reorder import statements in Thinking.tsx

* chore: reorder import statement in EditTextPart.tsx

* chore: reorder import statement

* chore: Reorganize imports in ToggleSwitch.tsx

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-10-30 22:14:38 +01:00 committed by GitHub
parent ea45d0b9c6
commit c0f1cfcaba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 528 additions and 186 deletions

View file

@ -1,54 +1,21 @@
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { applyFontSize } from '@librechat/client';
import { createStorageAtomWithEffect, initializeFromStorage } from './jotai-utils';
const DEFAULT_FONT_SIZE = 'text-base';
/**
* Base storage atom for font size
* This atom stores the user's font size preference
*/
const fontSizeStorageAtom = atomWithStorage<string>('fontSize', DEFAULT_FONT_SIZE, undefined, {
getOnInit: true,
});
/**
* Derived atom that applies font size changes to the DOM
* Read: returns the current font size
* Write: updates storage and applies the font size to the DOM
*/
export const fontSizeAtom = atom(
(get) => get(fontSizeStorageAtom),
(get, set, newValue: string) => {
set(fontSizeStorageAtom, newValue);
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
applyFontSize(newValue);
}
},
export const fontSizeAtom = createStorageAtomWithEffect<string>(
'fontSize',
DEFAULT_FONT_SIZE,
applyFontSize,
);
/**
* Initialize font size on app load
* This function applies the saved font size from localStorage to the DOM
*/
export const initializeFontSize = () => {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return;
}
const savedValue = localStorage.getItem('fontSize');
if (savedValue !== null) {
try {
const parsedValue = JSON.parse(savedValue);
applyFontSize(parsedValue);
} catch (error) {
console.error(
'Error parsing localStorage key "fontSize", resetting to default. Error:',
error,
);
localStorage.setItem('fontSize', JSON.stringify(DEFAULT_FONT_SIZE));
applyFontSize(DEFAULT_FONT_SIZE);
}
} else {
applyFontSize(DEFAULT_FONT_SIZE);
}
export const initializeFontSize = (): void => {
initializeFromStorage('fontSize', DEFAULT_FONT_SIZE, applyFontSize);
};

View file

@ -0,0 +1,88 @@
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
/**
* Create a simple atom with localStorage persistence
* Uses Jotai's atomWithStorage with getOnInit for proper SSR support
*
* @param key - localStorage key
* @param defaultValue - default value if no saved value exists
* @returns Jotai atom with localStorage persistence
*/
export function createStorageAtom<T>(key: string, defaultValue: T) {
return atomWithStorage<T>(key, defaultValue, undefined, {
getOnInit: true,
});
}
/**
* Create an atom with localStorage persistence and side effects
* Useful when you need to apply changes to the DOM or trigger other actions
*
* @param key - localStorage key
* @param defaultValue - default value if no saved value exists
* @param onWrite - callback function to run when the value changes
* @returns Jotai atom with localStorage persistence and side effects
*/
export function createStorageAtomWithEffect<T>(
key: string,
defaultValue: T,
onWrite: (value: T) => void,
) {
const baseAtom = createStorageAtom(key, defaultValue);
return atom(
(get) => get(baseAtom),
(get, set, newValue: T) => {
set(baseAtom, newValue);
if (typeof window !== 'undefined') {
onWrite(newValue);
}
},
);
}
/**
* Initialize a value from localStorage and optionally apply it
* Useful for applying saved values on app startup (e.g., theme, fontSize)
*
* @param key - localStorage key
* @param defaultValue - default value if no saved value exists
* @param onInit - optional callback to run with the loaded value
* @returns The loaded value (or default if none exists)
*/
export function initializeFromStorage<T>(
key: string,
defaultValue: T,
onInit?: (value: T) => void,
): T {
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
return defaultValue;
}
try {
const savedValue = localStorage.getItem(key);
const value = savedValue ? (JSON.parse(savedValue) as T) : defaultValue;
if (onInit) {
onInit(value);
}
return value;
} catch (error) {
console.error(`Error initializing ${key} from localStorage, using default. Error:`, error);
// Reset corrupted value
try {
localStorage.setItem(key, JSON.stringify(defaultValue));
} catch (setError) {
console.error(`Error resetting corrupted ${key} in localStorage:`, setError);
}
if (onInit) {
onInit(defaultValue);
}
return defaultValue;
}
}

View file

@ -0,0 +1,8 @@
import { createStorageAtom } from './jotai-utils';
const DEFAULT_SHOW_THINKING = false;
/**
* This atom controls whether AI reasoning/thinking content is expanded by default.
*/
export const showThinkingAtom = createStorageAtom<boolean>('showThinking', DEFAULT_SHOW_THINKING);