🧪 fix: Editor Styling, Incomplete Artifact Editing, Optimize Artifact Context (#8953)

* refactor: optimize artifacts context for improved performance

* fix: layout classes for artifacts editor

* chore: linting

* fix: enhance artifact mutation handling in CodeEditor to prevent infinite retries

* fix: handle incomplete artifacts in replaceArtifactContent and add regression tests
This commit is contained in:
Danny Avila 2025-08-08 15:49:58 -04:00 committed by GitHub
parent e7d6100fe4
commit 5d0bc95193
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 311 additions and 45 deletions

View file

@ -1,14 +1,15 @@
import { useMemo, useState, useEffect, useRef } from 'react';
import { Constants } from 'librechat-data-provider';
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';
import { getLatestText, logger } from '~/utils';
import { useChatContext } from '~/Providers';
import { logger } from '~/utils';
import { useArtifactsContext } from '~/Providers';
import { getKey } from '~/utils/artifacts';
import store from '~/store';
export default function useArtifacts() {
const [activeTab, setActiveTab] = useState('preview');
const { isSubmitting, latestMessage, conversation } = useChatContext();
const { isSubmitting, latestMessageId, latestMessageText, conversationId } =
useArtifactsContext();
const artifacts = useRecoilValue(store.artifactsState);
const resetArtifacts = useResetRecoilState(store.artifactsState);
@ -31,26 +32,23 @@ export default function useArtifacts() {
const resetState = () => {
resetArtifacts();
resetCurrentArtifactId();
prevConversationIdRef.current = conversation?.conversationId ?? null;
prevConversationIdRef.current = conversationId;
lastRunMessageIdRef.current = null;
lastContentRef.current = null;
hasEnclosedArtifactRef.current = false;
};
if (
conversation?.conversationId !== prevConversationIdRef.current &&
prevConversationIdRef.current != null
) {
if (conversationId !== prevConversationIdRef.current && prevConversationIdRef.current != null) {
resetState();
} else if (conversation?.conversationId === Constants.NEW_CONVO) {
} else if (conversationId === Constants.NEW_CONVO) {
resetState();
}
prevConversationIdRef.current = conversation?.conversationId ?? null;
prevConversationIdRef.current = conversationId;
/** Resets artifacts when unmounting */
return () => {
logger.log('artifacts_visibility', 'Unmounting artifacts');
resetState();
};
}, [conversation?.conversationId, resetArtifacts, resetCurrentArtifactId]);
}, [conversationId, resetArtifacts, resetCurrentArtifactId]);
useEffect(() => {
if (orderedArtifactIds.length > 0) {
@ -66,7 +64,7 @@ export default function useArtifacts() {
if (orderedArtifactIds.length === 0) {
return;
}
if (latestMessage == null) {
if (latestMessageId == null) {
return;
}
const latestArtifactId = orderedArtifactIds[orderedArtifactIds.length - 1];
@ -78,7 +76,6 @@ export default function useArtifacts() {
setCurrentArtifactId(latestArtifactId);
lastContentRef.current = latestArtifact?.content ?? null;
const latestMessageText = getLatestText(latestMessage);
const hasEnclosedArtifact =
/:::artifact(?:\{[^}]*\})?(?:\s|\n)*(?:```[\s\S]*?```(?:\s|\n)*)?:::/m.test(
latestMessageText.trim(),
@ -95,15 +92,22 @@ export default function useArtifacts() {
hasAutoSwitchedToCodeRef.current = true;
}
}
}, [setCurrentArtifactId, isSubmitting, orderedArtifactIds, artifacts, latestMessage]);
}, [
artifacts,
isSubmitting,
latestMessageId,
latestMessageText,
orderedArtifactIds,
setCurrentArtifactId,
]);
useEffect(() => {
if (latestMessage?.messageId !== lastRunMessageIdRef.current) {
lastRunMessageIdRef.current = latestMessage?.messageId ?? null;
if (latestMessageId !== lastRunMessageIdRef.current) {
lastRunMessageIdRef.current = latestMessageId;
hasEnclosedArtifactRef.current = false;
hasAutoSwitchedToCodeRef.current = false;
}
}, [latestMessage]);
}, [latestMessageId]);
const currentArtifact = currentArtifactId != null ? artifacts?.[currentArtifactId] : null;