web: transcript improvements
This commit is contained in:
@@ -98,12 +98,25 @@ const EMPTY_MODEL_PREFERENCES: ProviderModelPreferences = {
|
||||
xai: null,
|
||||
};
|
||||
|
||||
const TRANSCRIPT_BOTTOM_GAP = 20;
|
||||
const REPLY_SCROLL_BUFFER_MIN = 288;
|
||||
const REPLY_SCROLL_BUFFER_MAX = 576;
|
||||
const REPLY_SCROLL_BUFFER_VIEWPORT_RATIO = 0.52;
|
||||
|
||||
function getModelOptions(catalog: ModelCatalogResponse["providers"], provider: Provider) {
|
||||
const providerModels = catalog[provider]?.models ?? [];
|
||||
if (providerModels.length) return providerModels;
|
||||
return PROVIDER_FALLBACK_MODELS[provider];
|
||||
}
|
||||
|
||||
function getReplyScrollBufferHeight() {
|
||||
if (typeof window === "undefined") return REPLY_SCROLL_BUFFER_MIN;
|
||||
return Math.min(
|
||||
REPLY_SCROLL_BUFFER_MAX,
|
||||
Math.max(REPLY_SCROLL_BUFFER_MIN, Math.round(window.innerHeight * REPLY_SCROLL_BUFFER_VIEWPORT_RATIO))
|
||||
);
|
||||
}
|
||||
|
||||
function loadStoredModelPreferences() {
|
||||
if (typeof window === "undefined") return EMPTY_MODEL_PREFERENCES;
|
||||
try {
|
||||
@@ -443,6 +456,7 @@ export default function App() {
|
||||
return stored.openai ?? PROVIDER_FALLBACK_MODELS.openai[0];
|
||||
});
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [transcriptTailSpacerHeight, setTranscriptTailSpacerHeight] = useState(TRANSCRIPT_BOTTOM_GAP);
|
||||
const transcriptContainerRef = useRef<HTMLDivElement>(null);
|
||||
const transcriptEndRef = useRef<HTMLDivElement>(null);
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null);
|
||||
@@ -453,12 +467,41 @@ export default function App() {
|
||||
const shouldAutoScrollRef = useRef(true);
|
||||
const wasSendingRef = useRef(false);
|
||||
const pendingReplyScrollRef = useRef(false);
|
||||
const transcriptTailSpacerHeightRef = useRef(TRANSCRIPT_BOTTOM_GAP);
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||||
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
|
||||
const [sidebarQuery, setSidebarQuery] = useState("");
|
||||
const initialRouteSelectionRef = useRef<SidebarSelection | null>(readSidebarSelectionFromUrl());
|
||||
const hasSyncedSelectionHistoryRef = useRef(false);
|
||||
|
||||
const setTranscriptTailSpacer = (height: number) => {
|
||||
const nextHeight = Math.max(TRANSCRIPT_BOTTOM_GAP, Math.ceil(height));
|
||||
transcriptTailSpacerHeightRef.current = nextHeight;
|
||||
setTranscriptTailSpacerHeight(nextHeight);
|
||||
};
|
||||
|
||||
const expandTranscriptTailSpacer = (height: number) => {
|
||||
const targetHeight = Math.max(TRANSCRIPT_BOTTOM_GAP, Math.ceil(height));
|
||||
setTranscriptTailSpacerHeight((currentHeight) => {
|
||||
const nextHeight = Math.max(currentHeight, targetHeight);
|
||||
transcriptTailSpacerHeightRef.current = nextHeight;
|
||||
return nextHeight;
|
||||
});
|
||||
};
|
||||
|
||||
const settleTranscriptTailSpacer = () => {
|
||||
const container = transcriptContainerRef.current;
|
||||
const currentSpacerHeight = transcriptTailSpacerHeightRef.current;
|
||||
if (!container) {
|
||||
setTranscriptTailSpacer(TRANSCRIPT_BOTTOM_GAP);
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollHeightWithoutSpacer = container.scrollHeight - currentSpacerHeight;
|
||||
const requiredSpacerHeight = container.scrollTop + container.clientHeight - scrollHeightWithoutSpacer;
|
||||
setTranscriptTailSpacer(requiredSpacerHeight);
|
||||
};
|
||||
|
||||
const focusComposer = () => {
|
||||
if (typeof window === "undefined") return;
|
||||
window.requestAnimationFrame(() => {
|
||||
@@ -653,6 +696,9 @@ export default function App() {
|
||||
|
||||
useEffect(() => {
|
||||
shouldAutoScrollRef.current = true;
|
||||
if (!isSending || !isChatReplyStreamingInView) {
|
||||
setTranscriptTailSpacer(TRANSCRIPT_BOTTOM_GAP);
|
||||
}
|
||||
}, [draftKind, selectedItem?.kind, selectedKey]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -855,6 +901,7 @@ export default function App() {
|
||||
|
||||
const handleSendChat = async (content: string) => {
|
||||
pendingReplyScrollRef.current = true;
|
||||
expandTranscriptTailSpacer(getReplyScrollBufferHeight());
|
||||
|
||||
const optimisticUserMessage: Message = {
|
||||
id: `temp-user-${Date.now()}`,
|
||||
@@ -1043,6 +1090,7 @@ export default function App() {
|
||||
if (currentSelection?.kind === "chat" && currentSelection.id === chatId) {
|
||||
await refreshChat(chatId);
|
||||
}
|
||||
settleTranscriptTailSpacer();
|
||||
setPendingChatState(null);
|
||||
};
|
||||
|
||||
@@ -1462,9 +1510,11 @@ export default function App() {
|
||||
onStartChat={selectedSearch ? handleStartChatFromSearch : undefined}
|
||||
/>
|
||||
)}
|
||||
{isChatReplyStreamingInView ? (
|
||||
<div className="mx-auto mt-6 h-[52vh] min-h-72 max-h-[36rem] max-w-4xl" aria-hidden="true" />
|
||||
) : null}
|
||||
<div
|
||||
className="mx-auto max-w-4xl"
|
||||
style={{ height: `${transcriptTailSpacerHeight}px` }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div ref={transcriptEndRef} />
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user