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