Deeplink error fix, updated chat ux

This commit is contained in:
Alois 2026-04-10 22:07:51 +02:00
commit 60635d07c2
4 changed files with 63 additions and 7 deletions

View file

@ -6,6 +6,7 @@ import {
type ReactNode, type ReactNode,
} from "react"; } from "react";
import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"; import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link";
import { isTauri } from "@tauri-apps/api/core";
type DeeplinkContextValue = { type DeeplinkContextValue = {
deeplinks: readonly string[]; deeplinks: readonly string[];
@ -30,8 +31,11 @@ export default function DeeplinkProvider({
children: ReactNode; children: ReactNode;
}) { }) {
const [deeplinks, setDeeplinks] = useState<string[]>([]); const [deeplinks, setDeeplinks] = useState<string[]>([]);
const isTauriEnv = isTauri();
useEffect(() => { useEffect(() => {
if (!isTauriEnv) return;
let mounted = true; let mounted = true;
let unlisten: (() => void) | undefined; let unlisten: (() => void) | undefined;
@ -53,7 +57,7 @@ export default function DeeplinkProvider({
mounted = false; mounted = false;
unlisten?.(); unlisten?.();
}; };
}, []); }, [isTauriEnv]);
useEffect(() => { useEffect(() => {
console.log(deeplinks); console.log(deeplinks);

View file

@ -115,7 +115,7 @@ export default function InputComponent({
<CardHeader className="relative p-0 flex flex-col"> <CardHeader className="relative p-0 flex flex-col">
<div <div
aria-hidden="true" aria-hidden="true"
className="pointer-events-none absolute left-0 top-0 w-full opacity-0" className="pointer-events-none absolute left-0 top-0 w-full px-2.5 opacity-0"
style={{ visibility: "hidden" }} style={{ visibility: "hidden" }}
> >
<Message <Message

View file

@ -28,7 +28,7 @@ function generateFixedLoadingSize(size: number, height: number) {
* @param props Parameter props. * @param props Parameter props.
* @returns unknown. * @returns unknown.
*/ */
export default function Message(props: { function MessageComponent(props: {
message: RawMessage & { message: RawMessage & {
failed?: boolean; failed?: boolean;
}; };
@ -143,3 +143,16 @@ export default function Message(props: {
</div> </div>
); );
} }
export default React.memo(MessageComponent, (prev, next) => {
return (
prev.notEncrypted === next.notEncrypted &&
prev.measureRef === next.measureRef &&
prev.message.timestamp === next.message.timestamp &&
prev.message.content === next.message.content &&
prev.message.height === next.message.height &&
prev.message.sent_by_self === next.message.sent_by_self &&
prev.message.message_state === next.message.message_state &&
prev.message.failed === next.message.failed
);
});

View file

@ -8,6 +8,11 @@ import Message from "./components/message";
import { PAGE_SIZE } from "./values"; import { PAGE_SIZE } from "./values";
import { useLayoutEffect } from "react"; import { useLayoutEffect } from "react";
import { useIsMobile } from "@tensamin/ui/utils";
function getDistanceFromBottom(element: HTMLDivElement) {
return element.scrollHeight - (element.scrollTop + element.clientHeight);
}
/** /**
* Renders the chat screen with virtualized history and live message updates. * Renders the chat screen with virtualized history and live message updates.
@ -18,6 +23,8 @@ export default function Screen() {
const inputBoxRef = React.useRef<HTMLDivElement | null>(null); const inputBoxRef = React.useRef<HTMLDivElement | null>(null);
const scrollRef = React.useRef<HTMLDivElement | null>(null); const scrollRef = React.useRef<HTMLDivElement | null>(null);
const isMobile = useIsMobile();
const stickyBottomThreshold = isMobile ? 220 : 140;
const [hasScrolledToBottomInitially, setHasScrolledToBottomInitially] = const [hasScrolledToBottomInitially, setHasScrolledToBottomInitially] =
React.useState(false); React.useState(false);
@ -72,11 +79,22 @@ export default function Screen() {
return [...historicalMessages, ...liveWithoutDuplicates]; return [...historicalMessages, ...liveWithoutDuplicates];
}, [historicalMessages, liveMessagesSnapshot]); }, [historicalMessages, liveMessagesSnapshot]);
const messagesRef = React.useRef(messages);
React.useEffect(() => {
messagesRef.current = messages;
}, [messages]);
const getItemKey = React.useCallback((index: number) => {
return messagesRef.current[index]?.timestamp ?? index;
}, []);
const virtualizer = useVirtualizer({ const virtualizer = useVirtualizer({
count: messages.length, count: messages.length,
getScrollElement: () => scrollRef.current, getScrollElement: () => scrollRef.current,
getItemKey,
estimateSize: (index) => messages[index].height, estimateSize: (index) => messages[index].height,
overscan: 6, overscan: isMobile ? 10 : 6,
}); });
/** /**
@ -125,11 +143,16 @@ export default function Screen() {
} }
if (count > lastLiveMessageCount) { if (count > lastLiveMessageCount) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight; const shouldStickToBottom =
getDistanceFromBottom(scrollRef.current) <= stickyBottomThreshold;
if (shouldStickToBottom) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
} }
setLastLiveMessageCount(count); setLastLiveMessageCount(count);
}, [lastLiveMessageCount, liveMessagesSnapshot.length]); }, [lastLiveMessageCount, liveMessagesSnapshot.length, stickyBottomThreshold]);
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
if ( if (
@ -163,6 +186,9 @@ export default function Screen() {
const host = scrollRef.current; const host = scrollRef.current;
if (!host || !el || typeof window === "undefined") return; if (!host || !el || typeof window === "undefined") return;
const shouldStickToBottom =
getDistanceFromBottom(host) <= stickyBottomThreshold;
const viewportHeight = Math.max( const viewportHeight = Math.max(
window.innerHeight, window.innerHeight,
window.visualViewport?.height ?? 0, window.visualViewport?.height ?? 0,
@ -170,7 +196,17 @@ export default function Screen() {
const inputHeight = el.scrollHeight + 56; const inputHeight = el.scrollHeight + 56;
host.style.maxHeight = `calc(${viewportHeight}px - ${inputHeight - 62}px - env(safe-area-inset-bottom) - env(safe-area-inset-top))`; host.style.maxHeight = `calc(${viewportHeight}px - ${inputHeight - 62}px - env(safe-area-inset-bottom) - env(safe-area-inset-top))`;
}, []);
if (shouldStickToBottom) {
requestAnimationFrame(() => {
if (!scrollRef.current) {
return;
}
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
});
}
}, [stickyBottomThreshold]);
useLayoutEffect(() => { useLayoutEffect(() => {
updateMaxHeight(); updateMaxHeight();
@ -221,6 +257,9 @@ export default function Screen() {
ref={scrollRef} ref={scrollRef}
id="chat_container" id="chat_container"
className="flex-1 overflow-y-auto px-2.5" className="flex-1 overflow-y-auto px-2.5"
style={{
overflowAnchor: "none",
}}
onScroll={handleContainerScroll} onScroll={handleContainerScroll}
> >
<div <div