diff --git a/client/src/components/Artifacts/ArtifactButton.tsx b/client/src/components/Artifacts/ArtifactButton.tsx index bfb9037210..ddd1c86955 100644 --- a/client/src/components/Artifacts/ArtifactButton.tsx +++ b/client/src/components/Artifacts/ArtifactButton.tsx @@ -55,40 +55,52 @@ const ArtifactButton = ({ artifact }: { artifact: Artifact | null }) => { return (
-
- - + + ); + })()}
); diff --git a/client/src/components/Artifacts/ArtifactPreview.tsx b/client/src/components/Artifacts/ArtifactPreview.tsx index ee7d22852f..295437e629 100644 --- a/client/src/components/Artifacts/ArtifactPreview.tsx +++ b/client/src/components/Artifacts/ArtifactPreview.tsx @@ -1,6 +1,10 @@ import React, { memo, useMemo, type MutableRefObject } from 'react'; import { SandpackPreview, SandpackProvider } from '@codesandbox/sandpack-react/unstyled'; -import type { SandpackProviderProps, SandpackPreviewRef, PreviewProps } from '@codesandbox/sandpack-react/unstyled'; +import type { + SandpackProviderProps, + SandpackPreviewRef, + PreviewProps, +} from '@codesandbox/sandpack-react/unstyled'; import type { TStartupConfig } from 'librechat-data-provider'; import type { ArtifactFiles } from '~/common'; import { sharedFiles, sharedOptions } from '~/utils/artifacts'; diff --git a/client/src/components/Artifacts/ArtifactTabs.tsx b/client/src/components/Artifacts/ArtifactTabs.tsx index 67342448c0..7245885064 100644 --- a/client/src/components/Artifacts/ArtifactTabs.tsx +++ b/client/src/components/Artifacts/ArtifactTabs.tsx @@ -39,12 +39,16 @@ export default function ArtifactTabs({ const { files, fileKey, template, sharedProps } = useArtifactProps({ artifact }); return ( - <> +
- + + - +
); } diff --git a/client/src/components/Artifacts/ArtifactVersion.tsx b/client/src/components/Artifacts/ArtifactVersion.tsx index a344c28f1a..1998ff02d1 100644 --- a/client/src/components/Artifacts/ArtifactVersion.tsx +++ b/client/src/components/Artifacts/ArtifactVersion.tsx @@ -50,6 +50,7 @@ export default function ArtifactVersion({ return ( (); const previewRef = useRef(); const [isVisible, setIsVisible] = useState(false); + const [isClosing, setIsClosing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); + const [isMounted, setIsMounted] = useState(false); + const [height, setHeight] = useState(90); // Height as percentage of viewport + const [isDragging, setIsDragging] = useState(false); + const [blurAmount, setBlurAmount] = useState(0); // Dynamic blur amount + const dragStartY = useRef(0); + const dragStartHeight = useRef(90); const setArtifactsVisible = useSetRecoilState(store.artifactsVisibility); + const resetCurrentArtifactId = useResetRecoilState(store.currentArtifactId); useEffect(() => { - setIsVisible(true); - }, []); + setIsMounted(true); + const delay = isMobile ? 50 : 30; + const timer = setTimeout(() => setIsVisible(true), delay); + return () => { + clearTimeout(timer); + setIsMounted(false); + }; + }, [isMobile]); + + // Dynamic blur based on height - more blur when taking up more screen + useEffect(() => { + if (!isMobile) { + setBlurAmount(0); + return; + } + + // Calculate blur amount based on how much screen is covered + // 50% height = no blur, 100% height = full blur + const minHeightForBlur = 50; + const maxHeightForBlur = 100; + + if (height <= minHeightForBlur) { + setBlurAmount(0); + } else if (height >= maxHeightForBlur) { + setBlurAmount(32); // Increased from 16 to 32 for stronger blur + } else { + // Linear interpolation between 0 and 32px blur + const progress = (height - minHeightForBlur) / (maxHeightForBlur - minHeightForBlur); + setBlurAmount(Math.round(progress * 32)); // Changed from 16 to 32 + } + }, [height, isMobile]); const { activeTab, @@ -37,7 +74,47 @@ export default function Artifacts() { setCurrentArtifactId, } = useArtifacts(); - if (!currentArtifact) { + const handleDragStart = (e: React.PointerEvent) => { + setIsDragging(true); + dragStartY.current = e.clientY; + dragStartHeight.current = height; + (e.target as HTMLElement).setPointerCapture(e.pointerId); + }; + + const handleDragMove = (e: React.PointerEvent) => { + if (!isDragging) return; + + const deltaY = dragStartY.current - e.clientY; + const viewportHeight = window.innerHeight; + const deltaPercentage = (deltaY / viewportHeight) * 100; + const newHeight = Math.max(10, Math.min(100, dragStartHeight.current + deltaPercentage)); + + setHeight(newHeight); + }; + + const handleDragEnd = (e: React.PointerEvent) => { + if (!isDragging) return; + + setIsDragging(false); + (e.target as HTMLElement).releasePointerCapture(e.pointerId); + + // Snap to positions based on final height + if (height < 30) { + // Close if dragged down significantly + closeArtifacts(); + } else if (height > 95) { + // Snap to full height if dragged near top + setHeight(100); + } else if (height < 60) { + // Snap to minimum if in lower range + setHeight(50); + } else { + // Snap to default + setHeight(90); + } + }; + + if (!currentArtifact || !isMounted) { return null; } @@ -51,57 +128,97 @@ export default function Artifacts() { }; const closeArtifacts = () => { - setIsVisible(false); - setTimeout(() => setArtifactsVisible(false), isMobile ? 400 : 500); + if (isMobile) { + setIsClosing(true); + setIsVisible(false); + setTimeout(() => { + setArtifactsVisible(false); + setIsClosing(false); + setHeight(90); // Reset height + }, 250); + } else { + resetCurrentArtifactId(); + setArtifactsVisible(false); + } }; + // Calculate backdrop opacity based on blur amount + const backdropOpacity = blurAmount > 0 ? Math.min(0.3, blurAmount / 53.33) : 0; + return (
- {/* Mobile backdrop */} + {/* Mobile backdrop with dynamic blur */} {isMobile && (
= 8 ? closeArtifacts : undefined} aria-hidden="true" /> )}
- {/* Mobile drag indicator */} {isMobile && ( -
-
+
+
)} {/* Header */}
{!isMobile && ( -
+
- - {localize('com_ui_code')} + + {localize('com_ui_code')} - - {localize('com_ui_preview')} + + {localize('com_ui_preview')}
)} -
+
{activeTab === 'preview' && ( )} {activeTab !== 'preview' && isMutating && ( @@ -161,35 +288,37 @@ export default function Artifacts() { variant="ghost" onClick={closeArtifacts} aria-label={localize('com_ui_close')} - className="h-8 w-8 transition-transform duration-150 ease-out hover:scale-105 active:scale-95" + className="h-8 w-8 transition-all duration-150 hover:scale-105 active:scale-90" >
- {/* Content Area - This is the key fix */} -
- } - previewRef={previewRef as React.MutableRefObject} - /> + {/* Content Area - Fixed positioning to prevent layout shifts */} +
+
+ } + previewRef={previewRef as React.MutableRefObject} + /> +
- {/* Mobile Tab Switcher */} + {/* Mobile Tab Switcher with iOS-style animation */} {isMobile && (
{localize('com_ui_code')} @@ -197,7 +326,7 @@ export default function Artifacts() { {localize('com_ui_preview')} diff --git a/client/src/components/Artifacts/Thinking.tsx b/client/src/components/Artifacts/Thinking.tsx index 08e241c6e8..5890398466 100644 --- a/client/src/components/Artifacts/Thinking.tsx +++ b/client/src/components/Artifacts/Thinking.tsx @@ -7,8 +7,8 @@ import { cn } from '~/utils'; import store from '~/store'; const BUTTON_STYLES = { - base: 'group mt-3 flex w-fit items-center justify-center rounded-xl bg-surface-tertiary px-3 py-2 text-xs leading-[18px] animate-thinking-appear', - icon: 'icon-sm ml-1.5 transform-gpu text-text-primary transition-transform duration-200', + base: '-sring group mt-3 flex w-fit items-center justify-center rounded-xl bg-surface-tertiary px-3 py-2 text-xs leading-[18px] transition-all duration-300 hover:bg-surface-secondary active:scale-95 animate-thinking-appear', + icon: 'icon-sm ml-1.5 transform-gpu text-text-primary transition-transform duration-300', } as const; const CONTENT_STYLES = { @@ -69,7 +69,7 @@ const Thinking: React.ElementType = memo(({ children }: { children: React.ReactN
(null); + const artifactsPanelRef = useRef(null); const [minSize, setMinSize] = useState(defaultMinSize); const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse); const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize); + const [shouldRenderArtifacts, setShouldRenderArtifacts] = useState(artifacts != null); + const artifactsTimeoutRef = useRef(null); const isSmallScreen = useMediaQuery('(max-width: 767px)'); const hideSidePanel = useRecoilValue(store.hideSidePanel); + useEffect(() => { + if (artifacts != null) { + if (artifactsTimeoutRef.current) { + clearTimeout(artifactsTimeoutRef.current); + artifactsTimeoutRef.current = null; + } + setShouldRenderArtifacts(true); + requestAnimationFrame(() => { + requestAnimationFrame(() => { + artifactsPanelRef.current?.expand(); + }); + }); + } else if (shouldRenderArtifacts) { + artifactsPanelRef.current?.collapse(); + artifactsTimeoutRef.current = setTimeout(() => { + setShouldRenderArtifacts(false); + }, ANIMATION_DURATION); + } + + return () => { + if (artifactsTimeoutRef.current) { + clearTimeout(artifactsTimeoutRef.current); + } + }; + }, [artifacts, shouldRenderArtifacts]); + const calculateLayout = useCallback(() => { if (artifacts == null) { const navSize = defaultLayout.length === 2 ? defaultLayout[1] : defaultLayout[2]; @@ -120,20 +151,25 @@ const SidePanelGroup = memo( > {children} - {artifacts != null && !isSmallScreen && ( + {shouldRenderArtifacts && !isSmallScreen && ( <> - + {artifacts != null && ( + + )} - {artifacts} +
{artifacts}
)} diff --git a/client/src/locales/ar/translation.json b/client/src/locales/ar/translation.json index 8aba0b49c1..ffb9fd8e10 100644 --- a/client/src/locales/ar/translation.json +++ b/client/src/locales/ar/translation.json @@ -427,7 +427,7 @@ "com_ui_all_proper": "الكل", "com_ui_archive": "أرشفة", "com_ui_archive_error": "فشل في أرشفة المحادثة", - "com_ui_artifact_click": "انقر للفتح", + "com_ui_click_to_open": "انقر للفتح", "com_ui_artifacts": "المخرجات", "com_ui_artifacts_toggle": "تبديل واجهة العناصر", "com_ui_ascending": "تصاعدي", diff --git a/client/src/locales/ca/translation.json b/client/src/locales/ca/translation.json index 2ede5df176..51da367cb8 100644 --- a/client/src/locales/ca/translation.json +++ b/client/src/locales/ca/translation.json @@ -508,7 +508,7 @@ "com_ui_archive": "Arxiva", "com_ui_archive_delete_error": "No s'ha pogut eliminar la conversa arxivada", "com_ui_archive_error": "No s'ha pogut arxivar la conversa", - "com_ui_artifact_click": "Fes clic per obrir", + "com_ui_click_to_open": "Fes clic per obrir", "com_ui_artifacts": "Artifacts", "com_ui_artifacts_toggle": "Activa/desactiva la UI d'artifacts", "com_ui_artifacts_toggle_agent": "Habilita artifacts", diff --git a/client/src/locales/cs/translation.json b/client/src/locales/cs/translation.json index 027027f156..7abdbbfead 100644 --- a/client/src/locales/cs/translation.json +++ b/client/src/locales/cs/translation.json @@ -385,7 +385,7 @@ "com_ui_api_key": "API klíč", "com_ui_archive": "Archivovat", "com_ui_archive_error": "Nepodařilo se archivovat konverzaci", - "com_ui_artifact_click": "Klikněte pro otevření", + "com_ui_click_to_open": "Klikněte pro otevření", "com_ui_artifacts": "Artefakty", "com_ui_artifacts_toggle": "Přepnout uživatelské rozhraní artefaktů", "com_ui_artifacts_toggle_agent": "Povolit artefakty", diff --git a/client/src/locales/da/translation.json b/client/src/locales/da/translation.json index 9879e3c618..4a71c28cba 100644 --- a/client/src/locales/da/translation.json +++ b/client/src/locales/da/translation.json @@ -531,7 +531,7 @@ "com_ui_archive": "Arkiv", "com_ui_archive_delete_error": "Kunne ikke slette arkiveret samtale", "com_ui_archive_error": "Kunne ikke arkivere samtale", - "com_ui_artifact_click": "Klik for at åbne", + "com_ui_click_to_open": "Klik for at åbne", "com_ui_artifacts": "Artefakter", "com_ui_artifacts_toggle": "Skift artefakter UI", "com_ui_artifacts_toggle_agent": "Aktiver artefakter", diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index 58e89591b6..ad268daae4 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -685,7 +685,7 @@ "com_ui_archive": "Archivieren", "com_ui_archive_delete_error": "Archivierter Chat konnte nicht gelöscht werden.", "com_ui_archive_error": "Konversation konnte nicht archiviert werden", - "com_ui_artifact_click": "Zum Öffnen klicken", + "com_ui_click_to_open": "Zum Öffnen klicken", "com_ui_artifacts": "Artefakte", "com_ui_artifacts_options": "Artefakt Optionen", "com_ui_artifacts_toggle": "Artefakte-Funktion einschalten", diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 9e8a228457..9ca3117018 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -688,7 +688,8 @@ "com_ui_archive": "Archive", "com_ui_archive_delete_error": "Failed to delete archived conversation", "com_ui_archive_error": "Failed to archive conversation", - "com_ui_artifact_click": "Click to open", + "com_ui_click_to_open": "Click to open", + "com_ui_click_to_close": "Click to close", "com_ui_artifacts": "Artifacts", "com_ui_artifacts_options": "Artifacts Options", "com_ui_artifacts_toggle": "Toggle Artifacts UI", diff --git a/client/src/locales/es/translation.json b/client/src/locales/es/translation.json index bc914e50b6..388c9da69f 100644 --- a/client/src/locales/es/translation.json +++ b/client/src/locales/es/translation.json @@ -510,7 +510,7 @@ "com_ui_archive": "Archivar", "com_ui_archive_delete_error": "Error al borrar conversación archivada", "com_ui_archive_error": "Error al archivar la conversación", - "com_ui_artifact_click": "Haga clic para abrir", + "com_ui_click_to_open": "Haga clic para abrir", "com_ui_artifacts": "Artefactos", "com_ui_artifacts_options": "Opciones de artefactos", "com_ui_artifacts_toggle": "Alternar Interfaz de Artefactos", diff --git a/client/src/locales/et/translation.json b/client/src/locales/et/translation.json index f7907d27d3..31bdcf79e6 100644 --- a/client/src/locales/et/translation.json +++ b/client/src/locales/et/translation.json @@ -532,7 +532,7 @@ "com_ui_archive": "Arhiveeri", "com_ui_archive_delete_error": "Arhiveeritud vestluse kustutamine ebaõnnestus", "com_ui_archive_error": "Vestluse arhiveerimine ebaõnnestus", - "com_ui_artifact_click": "Klõpsa avamiseks", + "com_ui_click_to_open": "Klõpsa avamiseks", "com_ui_artifacts": "Artefaktid", "com_ui_artifacts_toggle": "Lülita artefaktide kasutajaliides sisse/välja", "com_ui_artifacts_toggle_agent": "Luba artefaktid", diff --git a/client/src/locales/fa/translation.json b/client/src/locales/fa/translation.json index f194209d2c..6a2827ed5a 100644 --- a/client/src/locales/fa/translation.json +++ b/client/src/locales/fa/translation.json @@ -479,7 +479,7 @@ "com_ui_api_key": "کلید API", "com_ui_archive": "آرشیو", "com_ui_archive_error": "مکالمه بایگانی نشد", - "com_ui_artifact_click": "برای باز کردن کلیک کنید", + "com_ui_click_to_open": "برای باز کردن کلیک کنید", "com_ui_artifacts": "مصنوعات", "com_ui_artifacts_toggle": "تغییر رابط کاربری Artifacts", "com_ui_artifacts_toggle_agent": "Artifacts را فعال کنید", diff --git a/client/src/locales/fr/translation.json b/client/src/locales/fr/translation.json index 2321acfdd1..4edcce209a 100644 --- a/client/src/locales/fr/translation.json +++ b/client/src/locales/fr/translation.json @@ -635,7 +635,7 @@ "com_ui_archive": "Archiver", "com_ui_archive_delete_error": "Suppression de la conversation archivée échouée", "com_ui_archive_error": "échec de l'archivage de la conversation", - "com_ui_artifact_click": "Cliquer pour ouvrir", + "com_ui_click_to_open": "Cliquer pour ouvrir", "com_ui_artifacts": "Artefacts", "com_ui_artifacts_options": "Options des Artefacts", "com_ui_artifacts_toggle": "Afficher/Masquer l'interface des artefacts", diff --git a/client/src/locales/he/translation.json b/client/src/locales/he/translation.json index 6425f444ad..a361efce9e 100644 --- a/client/src/locales/he/translation.json +++ b/client/src/locales/he/translation.json @@ -676,7 +676,7 @@ "com_ui_archive": "לארכיון", "com_ui_archive_delete_error": "מחיקת השיחה מהארכיון נכשלה", "com_ui_archive_error": "אירעה שגיאה באירכוב השיחה", - "com_ui_artifact_click": "לחץ לפתיחה", + "com_ui_click_to_open": "לחץ לפתיחה", "com_ui_artifacts": "רכיבי תצוגה", "com_ui_artifacts_options": "אפשרויות ארטיפקטים", "com_ui_artifacts_toggle": "הפעל/כבה רכיבי תצוגה", diff --git a/client/src/locales/hu/translation.json b/client/src/locales/hu/translation.json index 2293e657a2..fb144499b7 100644 --- a/client/src/locales/hu/translation.json +++ b/client/src/locales/hu/translation.json @@ -479,7 +479,7 @@ "com_ui_api_key": "API kulcs", "com_ui_archive": "Archiválás", "com_ui_archive_error": "Nem sikerült archiválni a beszélgetést", - "com_ui_artifact_click": "Kattintson a megnyitáshoz", + "com_ui_click_to_open": "Kattintson a megnyitáshoz", "com_ui_artifacts": "Műtermékek", "com_ui_artifacts_toggle": "Műtermék kezelőfelület váltása", "com_ui_artifacts_toggle_agent": "Műtermékek engedélyezése", diff --git a/client/src/locales/it/translation.json b/client/src/locales/it/translation.json index 5f4181a07c..af9be2a52a 100644 --- a/client/src/locales/it/translation.json +++ b/client/src/locales/it/translation.json @@ -541,7 +541,7 @@ "com_ui_api_key": "Chiave API", "com_ui_archive": "Archivia", "com_ui_archive_error": "Errore durante l'archiviazione della conversazione", - "com_ui_artifact_click": "Clicca per aprire", + "com_ui_click_to_open": "Clicca per aprire", "com_ui_artifacts": "Artefatti", "com_ui_artifacts_toggle": "Mostra/Nascondi Interfaccia Artefatti", "com_ui_artifacts_toggle_agent": "Abilita artefatti", diff --git a/client/src/locales/ja/translation.json b/client/src/locales/ja/translation.json index 33f0d7d768..e939bb9b5c 100644 --- a/client/src/locales/ja/translation.json +++ b/client/src/locales/ja/translation.json @@ -576,7 +576,7 @@ "com_ui_archive": "アーカイブ", "com_ui_archive_delete_error": "アーカイブされた会話の削除に失敗しました", "com_ui_archive_error": "アーカイブに失敗しました。", - "com_ui_artifact_click": "クリックして開く", + "com_ui_click_to_open": "クリックして開く", "com_ui_artifacts": "アーティファクト", "com_ui_artifacts_options": "アーティファクト・オプション", "com_ui_artifacts_toggle": "アーティファクト UI の切替", diff --git a/client/src/locales/ko/translation.json b/client/src/locales/ko/translation.json index d99a4d09f6..043393bd3f 100644 --- a/client/src/locales/ko/translation.json +++ b/client/src/locales/ko/translation.json @@ -567,7 +567,7 @@ "com_ui_archive": "아카이브", "com_ui_archive_delete_error": "저장된 대화 삭제 실패", "com_ui_archive_error": "대화 아카이브 실패", - "com_ui_artifact_click": "클릭하여 열기", + "com_ui_click_to_open": "클릭하여 열기", "com_ui_artifacts": "아티팩트", "com_ui_artifacts_options": "아티팩트 옵션", "com_ui_artifacts_toggle": "아티팩트 UI 표시/숨기기", diff --git a/client/src/locales/lv/translation.json b/client/src/locales/lv/translation.json index ac3fe25876..25a39dda10 100644 --- a/client/src/locales/lv/translation.json +++ b/client/src/locales/lv/translation.json @@ -688,7 +688,7 @@ "com_ui_archive": "Arhīvs", "com_ui_archive_delete_error": "Neizdevās izdzēst arhivēto sarunu.", "com_ui_archive_error": "Neizdevās arhivēt sarunu.", - "com_ui_artifact_click": "Noklikšķiniet, lai atvērtu", + "com_ui_click_to_open": "Noklikšķiniet, lai atvērtu", "com_ui_artifacts": "Artefakti", "com_ui_artifacts_options": "Artefaktu opcijas", "com_ui_artifacts_toggle": "Pārslēgt artefaktu lietotāja saskarni", diff --git a/client/src/locales/nb/translation.json b/client/src/locales/nb/translation.json index c34347a2df..9e638b9d15 100644 --- a/client/src/locales/nb/translation.json +++ b/client/src/locales/nb/translation.json @@ -683,7 +683,7 @@ "com_ui_archive": "Arkiver", "com_ui_archive_delete_error": "Sletting av arkivert samtale mislyktes.", "com_ui_archive_error": "Arkivering av samtale mislyktes.", - "com_ui_artifact_click": "Klikk for å åpne", + "com_ui_click_to_open": "Klikk for å åpne", "com_ui_artifacts": "Artefakter", "com_ui_artifacts_options": "Alternativer for artefakter", "com_ui_artifacts_toggle": "Veksle artefakt-UI", diff --git a/client/src/locales/pl/translation.json b/client/src/locales/pl/translation.json index 8c69ea9cab..8893a975d4 100644 --- a/client/src/locales/pl/translation.json +++ b/client/src/locales/pl/translation.json @@ -429,7 +429,7 @@ "com_ui_all_proper": "Wszystkie", "com_ui_archive": "Archiwum", "com_ui_archive_error": "Nie udało się archiwizować rozmowy", - "com_ui_artifact_click": "Kliknij, aby otworzyć", + "com_ui_click_to_open": "Kliknij, aby otworzyć", "com_ui_artifacts": "Artefakty", "com_ui_artifacts_toggle": "Przełącz interfejs artefaktów", "com_ui_ascending": "Rosnąco", diff --git a/client/src/locales/pt-BR/translation.json b/client/src/locales/pt-BR/translation.json index e7968fd719..cc88c3a75c 100644 --- a/client/src/locales/pt-BR/translation.json +++ b/client/src/locales/pt-BR/translation.json @@ -582,7 +582,7 @@ "com_ui_api_key": "Chave API", "com_ui_archive": "Arquivar", "com_ui_archive_error": "Falha ao arquivar conversa", - "com_ui_artifact_click": "Clique para abrir", + "com_ui_click_to_open": "Clique para abrir", "com_ui_artifacts": "Artefatos", "com_ui_artifacts_toggle": "Alternar UI de Artefatos", "com_ui_artifacts_toggle_agent": "Habilitar artefatos", diff --git a/client/src/locales/pt-PT/translation.json b/client/src/locales/pt-PT/translation.json index eebebdd270..98da87a056 100644 --- a/client/src/locales/pt-PT/translation.json +++ b/client/src/locales/pt-PT/translation.json @@ -656,7 +656,7 @@ "com_ui_archive": "Arquivar", "com_ui_archive_delete_error": "Falha ao eliminar conversa arquivada", "com_ui_archive_error": "Falha ao arquivar conversa", - "com_ui_artifact_click": "Clique para abrir", + "com_ui_click_to_open": "Clique para abrir", "com_ui_artifacts": "Artefatos", "com_ui_artifacts_options": "Opções de Artefactos", "com_ui_artifacts_toggle": "Alternar UI de Artefatos", diff --git a/client/src/locales/ru/translation.json b/client/src/locales/ru/translation.json index 4025d4a95c..afb93da119 100644 --- a/client/src/locales/ru/translation.json +++ b/client/src/locales/ru/translation.json @@ -682,7 +682,7 @@ "com_ui_archive": "Архивировать", "com_ui_archive_delete_error": "Не удалось удалить архивированный чат", "com_ui_archive_error": "Не удалось заархивировать чат", - "com_ui_artifact_click": "Нажмите, чтобы открыть", + "com_ui_click_to_open": "Нажмите, чтобы открыть", "com_ui_artifacts": "Артефакты", "com_ui_artifacts_options": "Параметры артефактов", "com_ui_artifacts_toggle": "Показать/скрыть артефакты", diff --git a/client/src/locales/th/translation.json b/client/src/locales/th/translation.json index a5afdb1283..cf596a4fb1 100644 --- a/client/src/locales/th/translation.json +++ b/client/src/locales/th/translation.json @@ -477,7 +477,7 @@ "com_ui_api_key": "คีย์ API", "com_ui_archive": "เก็บถาวร", "com_ui_archive_error": "ไม่สามารถเก็บถาวรการสนทนา", - "com_ui_artifact_click": "คลิกเพื่อเปิด", + "com_ui_click_to_open": "คลิกเพื่อเปิด", "com_ui_artifacts": "สิ่งประดิษฐ์", "com_ui_artifacts_toggle": "สลับ UI สิ่งประดิษฐ์", "com_ui_artifacts_toggle_agent": "เปิดใช้งานสิ่งประดิษฐ์", diff --git a/client/src/locales/tr/translation.json b/client/src/locales/tr/translation.json index 8cdf3c6d0b..016e00edb6 100644 --- a/client/src/locales/tr/translation.json +++ b/client/src/locales/tr/translation.json @@ -430,7 +430,7 @@ "com_ui_all_proper": "Tümü", "com_ui_archive": "Arşivle", "com_ui_archive_error": "Konuşmayı arşivleyemedi", - "com_ui_artifact_click": "Açmak için tıklayın", + "com_ui_click_to_open": "Açmak için tıklayın", "com_ui_artifacts": "Yapıtlar", "com_ui_artifacts_toggle": "Yapıtlar Arayüzünü Aç/Kapat", "com_ui_ascending": "Artan", diff --git a/client/src/locales/uk/translation.json b/client/src/locales/uk/translation.json index c786ebc7ef..f7001ec611 100644 --- a/client/src/locales/uk/translation.json +++ b/client/src/locales/uk/translation.json @@ -683,7 +683,7 @@ "com_ui_archive": "Архівувати", "com_ui_archive_delete_error": "Не вдалося видалити заархівований чат", "com_ui_archive_error": "Не вдалося заархівувати чат", - "com_ui_artifact_click": "Натисніть, щоб відкрити", + "com_ui_click_to_open": "Натисніть, щоб відкрити", "com_ui_artifacts": "Артефакти", "com_ui_artifacts_options": "Параметри артефактів", "com_ui_artifacts_toggle": "Показати/приховати артефакти", diff --git a/client/src/locales/zh-Hans/translation.json b/client/src/locales/zh-Hans/translation.json index 736caa7dba..29790eecb8 100644 --- a/client/src/locales/zh-Hans/translation.json +++ b/client/src/locales/zh-Hans/translation.json @@ -686,7 +686,7 @@ "com_ui_archive": "归档", "com_ui_archive_delete_error": "删除已归档对话失败", "com_ui_archive_error": "归档对话失败", - "com_ui_artifact_click": "点击以打开", + "com_ui_click_to_open": "点击以打开", "com_ui_artifacts": "Artifacts", "com_ui_artifacts_options": "Artifacts 选项", "com_ui_artifacts_toggle": "切换 Artifacts UI", diff --git a/client/src/locales/zh-Hant/translation.json b/client/src/locales/zh-Hant/translation.json index 2de11c381e..0cf7c954cc 100644 --- a/client/src/locales/zh-Hant/translation.json +++ b/client/src/locales/zh-Hant/translation.json @@ -548,7 +548,7 @@ "com_ui_api_key": "API 金鑰", "com_ui_archive": "封存", "com_ui_archive_error": "封存對話時發生錯誤", - "com_ui_artifact_click": "點擊開啟", + "com_ui_click_to_open": "點擊開啟", "com_ui_artifacts": "成品", "com_ui_artifacts_toggle": "切換成品介面", "com_ui_ascending": "遞增", diff --git a/client/src/style.css b/client/src/style.css index 1a3747c80c..37d0477eb5 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -2715,6 +2715,8 @@ html { .animate-pulse-slow { animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite; } + +/* iOS-inspired smooth animations */ @keyframes fadeIn { from { opacity: 0; @@ -2730,10 +2732,45 @@ html { animation: fadeIn 0.5s ease-out forwards; } +/* Enhanced smooth scaling for interactions */ .scale-98 { transform: scale(0.98); } +/* Add hardware acceleration for smoother animations */ +.will-change-transform { + will-change: transform; +} + +.will-change-opacity { + will-change: opacity; +} + +/* Prevent content flash and layout shifts in artifacts */ +[data-radix-scroll-area-viewport] { + /* Prevent scrollbar from causing layout shifts */ + scrollbar-gutter: stable; +} + +/* Ensure smooth tab content transitions */ +[role="tabpanel"] { + /* Use GPU acceleration for tab content */ + transform: translateZ(0); + backface-visibility: hidden; + -webkit-font-smoothing: subpixel-antialiased; +} + +/* Prevent flash of content during mounting */ +[role="tabpanel"][data-state="inactive"] { + visibility: hidden; + pointer-events: none; +} + +[role="tabpanel"][data-state="active"] { + visibility: visible; + pointer-events: auto; +} + /* Chat Badges Animation */ @keyframes ios-wiggle { diff --git a/client/tailwind.config.cjs b/client/tailwind.config.cjs index c30d2ca703..ef15ba5d2e 100644 --- a/client/tailwind.config.cjs +++ b/client/tailwind.config.cjs @@ -47,6 +47,69 @@ module.exports = { '0%': { transform: 'translateX(0)' }, '100%': { transform: 'translateX(100%)' }, }, + // iOS-inspired smooth animations + 'artifact-slide-up': { + '0%': { + transform: 'translateY(100%) scale(0.95)', + opacity: '0', + }, + '100%': { + transform: 'translateY(0) scale(1)', + opacity: '1', + }, + }, + 'artifact-slide-down': { + '0%': { + transform: 'translateY(0) scale(1)', + opacity: '1', + }, + '100%': { + transform: 'translateY(100%) scale(0.95)', + opacity: '0', + }, + }, + 'artifact-slide-in-desktop': { + '0%': { + transform: 'translateX(20px)', + opacity: '0', + }, + '100%': { + transform: 'translateX(0)', + opacity: '1', + }, + }, + 'artifact-slide-out-desktop': { + '0%': { + transform: 'translateX(0)', + opacity: '1', + }, + '100%': { + transform: 'translateX(20px)', + opacity: '0', + }, + }, + 'backdrop-fade-in': { + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, + }, + 'backdrop-fade-out': { + '0%': { opacity: '1' }, + '100%': { opacity: '0' }, + }, + 'tab-slide': { + '0%': { transform: 'translateX(var(--tab-slide-from))' }, + '100%': { transform: 'translateX(var(--tab-slide-to))' }, + }, + 'thinking-appear': { + '0%': { + opacity: '0', + transform: 'scale(0.9) translateY(4px)', + }, + '100%': { + opacity: '1', + transform: 'scale(1) translateY(0)', + }, + }, }, animation: { 'fade-in': 'fadeIn 0.5s ease-out forwards', @@ -56,6 +119,22 @@ module.exports = { 'slide-in-left': 'slide-in-left 300ms cubic-bezier(0.25, 0.1, 0.25, 1)', 'slide-out-left': 'slide-out-left 300ms cubic-bezier(0.25, 0.1, 0.25, 1)', 'slide-out-right': 'slide-out-right 300ms cubic-bezier(0.25, 0.1, 0.25, 1)', + // iOS-inspired smooth animations + 'artifact-slide-up': 'artifact-slide-up 0.45s cubic-bezier(0.32, 0.72, 0, 1)', + 'artifact-slide-down': 'artifact-slide-down 0.35s cubic-bezier(0.32, 0.72, 0, 1)', + 'artifact-slide-in-desktop': + 'artifact-slide-in-desktop 0.5s cubic-bezier(0.32, 0.72, 0, 1)', + 'artifact-slide-out-desktop': + 'artifact-slide-out-desktop 0.35s cubic-bezier(0.32, 0.72, 0, 1)', + 'backdrop-fade-in': 'backdrop-fade-in 0.3s cubic-bezier(0.32, 0.72, 0, 1)', + 'backdrop-fade-out': 'backdrop-fade-out 0.25s cubic-bezier(0.32, 0.72, 0, 1)', + 'tab-slide': 'tab-slide 0.35s cubic-bezier(0.32, 0.72, 0, 1)', + 'thinking-appear': 'thinking-appear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)', + }, + transitionTimingFunction: { + ios: 'cubic-bezier(0.32, 0.72, 0, 1)', + 'ios-spring': 'cubic-bezier(0.34, 1.56, 0.64, 1)', + 'ios-decelerate': 'cubic-bezier(0, 0, 0.2, 1)', }, colors: { gray: {