-
-
-
-
{artifact.title}
-
- {localize('com_ui_artifact_click')}
+ : 'border-border-light bg-surface-tertiary shadow-sm');
+
+ const actionLabel = isSelected
+ ? localize('com_ui_click_to_close')
+ : localize('com_ui_click_to_open');
+
+ 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: {