add reasoning page

This commit is contained in:
Nate Kelley 2025-09-03 22:05:59 -06:00
parent 2dbe578f3a
commit 5d99659307
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 71 additions and 18 deletions

View File

@ -1,6 +1,7 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import { ClientOnly } from '@tanstack/react-router';
import isEmpty from 'lodash/isEmpty';
import type React from 'react';
import { useEffect, useMemo, useRef } from 'react';
@ -11,6 +12,7 @@ import { FileIndeterminateLoader } from '@/components/features/loaders/FileIndet
import { ScrollArea } from '@/components/ui/scroll-area';
import { useGetBlackBoxMessage } from '@/context/BlackBox/blackbox-store';
import { useAutoScroll } from '@/hooks/useAutoScroll';
import { cn } from '@/lib/utils';
import { ReasoningMessageSelector } from './ReasoningMessages';
import { BlackBoxMessage } from './ReasoningMessages/ReasoningBlackBoxMessage';
@ -41,10 +43,11 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
const viewportRef = useRef<HTMLDivElement | null>(null);
const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(viewportRef, {
observeSubTree: true,
enabled: !!viewportRef.current,
});
const { isAutoScrollEnabled, isMountedAutoScrollObserver, scrollToBottom, enableAutoScroll } =
useAutoScroll(viewportRef, {
observeSubTree: true,
enabled: !isStreamFinished,
});
useEffect(() => {
if (hasChat && reasoningMessageIds) {
@ -57,7 +60,12 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
return (
<>
<ScrollArea viewportRef={viewportRef} className="h-full">
<div className="h-full flex-col space-y-0.5 overflow-y-auto p-5">
<div
className={cn(
'h-full flex-col space-y-0.5 overflow-y-auto p-5',
!isMountedAutoScrollObserver && 'invisible'
)}
>
{reasoningMessageIds?.map((reasoningMessageId, messageIndex) => (
<ReasoningMessageSelector
key={reasoningMessageId}
@ -77,10 +85,12 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
</div>
</ScrollArea>
<ScrollToBottomButton
isAutoScrollEnabled={isAutoScrollEnabled}
scrollToBottom={scrollToBottom}
/>
{viewportRef.current && (
<ScrollToBottomButton
isAutoScrollEnabled={isAutoScrollEnabled}
scrollToBottom={scrollToBottom}
/>
)}
</>
);
};

View File

@ -44,6 +44,8 @@ interface UseAutoScrollReturn {
enableAutoScroll: () => void;
/** Disable autoscroll */
disableAutoScroll: () => void;
/** Whether the auto-scroll observer is mounted */
isMountedAutoScrollObserver: boolean;
}
/**
@ -79,7 +81,7 @@ export const useAutoScroll = (
observeAttributes = false,
animationCooldown = 500,
} = options;
const [isMountedAutoScrollObserver, setMountedAutoScrollObserver] = useState(enabled);
const [isAutoScrollEnabled, setIsAutoScrollEnabled] = useState(enabled);
const observerRef = useRef<MutationObserver | null>(null);
const rAFIdRef = useRef<number | null>(null);
@ -294,6 +296,7 @@ export const useAutoScroll = (
const enableAutoScroll = useCallback(() => {
setIsAutoScrollEnabled(true);
setMountedAutoScrollObserver(true);
}, []);
const disableAutoScroll = useCallback(() => {
@ -302,6 +305,7 @@ export const useAutoScroll = (
return {
isAutoScrollEnabled,
isMountedAutoScrollObserver,
scrollToBottom,
scrollToTop,
scrollToNode,

View File

@ -14,12 +14,12 @@ const autoClass = 'mx-auto max-w-[600px] w-full';
export const ChatContent: React.FC<{ chatId: string | undefined }> = React.memo(({ chatId }) => {
const chatMessageIds = useGetChatMessageIds(chatId);
const containerRef = useRef<HTMLElement>(null);
const [isMounted, setIsMounted] = useState(false);
const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(containerRef, {
observeSubTree: true,
enabled: false,
});
const { isAutoScrollEnabled, isMountedAutoScrollObserver, scrollToBottom, enableAutoScroll } =
useAutoScroll(containerRef, {
observeSubTree: true,
enabled: false,
});
useMount(() => {
const container = document
@ -28,14 +28,19 @@ export const ChatContent: React.FC<{ chatId: string | undefined }> = React.memo(
if (!container) return;
containerRef.current = container;
enableAutoScroll();
setIsMounted(true);
console.log('mounted chat content', isMountedAutoScrollObserver);
});
const showScrollToBottomButton = isMounted && containerRef.current;
const showScrollToBottomButton = isMountedAutoScrollObserver && containerRef.current;
return (
<>
<div className={cn('mb-48 flex h-full w-full flex-col', !isMounted && 'invisible')}>
<div
className={cn(
'mb-48 flex h-full w-full flex-col',
!isMountedAutoScrollObserver && 'invisible'
)}
>
<ClientOnly>
{chatMessageIds?.map((messageId, index) => (
<div key={messageId} className={autoClass}>

View File

@ -58,6 +58,7 @@ import { Route as AppAppAssetReportsReportIdLayoutContentRouteImport } from './r
import { Route as AppAppAssetMetricsMetricIdLayoutSqlRouteImport } from './routes/app/_app/_asset/metrics.$metricId/_layout/sql'
import { Route as AppAppAssetMetricsMetricIdLayoutResultsRouteImport } from './routes/app/_app/_asset/metrics.$metricId/_layout/results'
import { Route as AppAppAssetMetricsMetricIdLayoutChartRouteImport } from './routes/app/_app/_asset/metrics.$metricId/_layout/chart'
import { Route as AppAppAssetChatsChatIdReasoningMessageIdIndexRouteImport } from './routes/app/_app/_asset/chats.$chatId/reasoning.$messageId/index'
import { Route as AppAppAssetReportsReportIdMetricsMetricIdContentRouteImport } from './routes/app/_app/_asset/reports.$reportId/metrics.$metricId/_content'
import { Route as AppAppAssetDashboardsDashboardIdMetricsMetricIdContentRouteImport } from './routes/app/_app/_asset/dashboards.$dashboardId/metrics.$metricId/_content'
import { Route as AppAppAssetChatsChatIdReportsReportIdLayoutRouteImport } from './routes/app/_app/_asset/chats.$chatId/reports.$reportId/_layout'
@ -422,6 +423,12 @@ const AppAppAssetMetricsMetricIdLayoutChartRoute =
path: '/chart',
getParentRoute: () => AppAppAssetMetricsMetricIdLayoutRoute,
} as any)
const AppAppAssetChatsChatIdReasoningMessageIdIndexRoute =
AppAppAssetChatsChatIdReasoningMessageIdIndexRouteImport.update({
id: '/reasoning/$messageId/',
path: '/reasoning/$messageId/',
getParentRoute: () => AppAppAssetChatsChatIdRoute,
} as any)
const AppAppAssetReportsReportIdMetricsMetricIdContentRoute =
AppAppAssetReportsReportIdMetricsMetricIdContentRouteImport.update({
id: '/_content',
@ -703,6 +710,7 @@ export interface FileRoutesByFullPath {
'/app/chats/$chatId/reports/$reportId': typeof AppAppAssetChatsChatIdReportsReportIdLayoutRouteWithChildren
'/app/dashboards/$dashboardId/metrics/$metricId': typeof AppAppAssetDashboardsDashboardIdMetricsMetricIdContentRouteWithChildren
'/app/reports/$reportId/metrics/$metricId': typeof AppAppAssetReportsReportIdMetricsMetricIdContentRouteWithChildren
'/app/chats/$chatId/reasoning/$messageId': typeof AppAppAssetChatsChatIdReasoningMessageIdIndexRoute
'/app/chats/$chatId/metrics/$metricId/chart': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutChartRoute
'/app/chats/$chatId/metrics/$metricId/results': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutResultsRoute
'/app/chats/$chatId/metrics/$metricId/sql': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutSqlRoute
@ -773,6 +781,7 @@ export interface FileRoutesByTo {
'/app/chats/$chatId/reports/$reportId': typeof AppAppAssetChatsChatIdReportsReportIdLayoutIndexRoute
'/app/dashboards/$dashboardId/metrics/$metricId': typeof AppAppAssetDashboardsDashboardIdMetricsMetricIdContentIndexRoute
'/app/reports/$reportId/metrics/$metricId': typeof AppAppAssetReportsReportIdMetricsMetricIdContentIndexRoute
'/app/chats/$chatId/reasoning/$messageId': typeof AppAppAssetChatsChatIdReasoningMessageIdIndexRoute
'/app/chats/$chatId/metrics/$metricId/chart': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutChartRoute
'/app/chats/$chatId/metrics/$metricId/results': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutResultsRoute
'/app/chats/$chatId/metrics/$metricId/sql': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutSqlRoute
@ -853,6 +862,7 @@ export interface FileRoutesById {
'/app/_app/_asset/dashboards/$dashboardId/metrics/$metricId/_content': typeof AppAppAssetDashboardsDashboardIdMetricsMetricIdContentRouteWithChildren
'/app/_app/_asset/reports/$reportId/metrics/$metricId': typeof AppAppAssetReportsReportIdMetricsMetricIdRouteWithChildren
'/app/_app/_asset/reports/$reportId/metrics/$metricId/_content': typeof AppAppAssetReportsReportIdMetricsMetricIdContentRouteWithChildren
'/app/_app/_asset/chats/$chatId/reasoning/$messageId/': typeof AppAppAssetChatsChatIdReasoningMessageIdIndexRoute
'/app/_app/_asset/chats/$chatId/metrics/$metricId/_layout/chart': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutChartRoute
'/app/_app/_asset/chats/$chatId/metrics/$metricId/_layout/results': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutResultsRoute
'/app/_app/_asset/chats/$chatId/metrics/$metricId/_layout/sql': typeof AppAppAssetChatsChatIdMetricsMetricIdLayoutSqlRoute
@ -932,6 +942,7 @@ export interface FileRouteTypes {
| '/app/chats/$chatId/reports/$reportId'
| '/app/dashboards/$dashboardId/metrics/$metricId'
| '/app/reports/$reportId/metrics/$metricId'
| '/app/chats/$chatId/reasoning/$messageId'
| '/app/chats/$chatId/metrics/$metricId/chart'
| '/app/chats/$chatId/metrics/$metricId/results'
| '/app/chats/$chatId/metrics/$metricId/sql'
@ -1002,6 +1013,7 @@ export interface FileRouteTypes {
| '/app/chats/$chatId/reports/$reportId'
| '/app/dashboards/$dashboardId/metrics/$metricId'
| '/app/reports/$reportId/metrics/$metricId'
| '/app/chats/$chatId/reasoning/$messageId'
| '/app/chats/$chatId/metrics/$metricId/chart'
| '/app/chats/$chatId/metrics/$metricId/results'
| '/app/chats/$chatId/metrics/$metricId/sql'
@ -1081,6 +1093,7 @@ export interface FileRouteTypes {
| '/app/_app/_asset/dashboards/$dashboardId/metrics/$metricId/_content'
| '/app/_app/_asset/reports/$reportId/metrics/$metricId'
| '/app/_app/_asset/reports/$reportId/metrics/$metricId/_content'
| '/app/_app/_asset/chats/$chatId/reasoning/$messageId/'
| '/app/_app/_asset/chats/$chatId/metrics/$metricId/_layout/chart'
| '/app/_app/_asset/chats/$chatId/metrics/$metricId/_layout/results'
| '/app/_app/_asset/chats/$chatId/metrics/$metricId/_layout/sql'
@ -1520,6 +1533,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppAppAssetMetricsMetricIdLayoutChartRouteImport
parentRoute: typeof AppAppAssetMetricsMetricIdLayoutRoute
}
'/app/_app/_asset/chats/$chatId/reasoning/$messageId/': {
id: '/app/_app/_asset/chats/$chatId/reasoning/$messageId/'
path: '/reasoning/$messageId'
fullPath: '/app/chats/$chatId/reasoning/$messageId'
preLoaderRoute: typeof AppAppAssetChatsChatIdReasoningMessageIdIndexRouteImport
parentRoute: typeof AppAppAssetChatsChatIdRoute
}
'/app/_app/_asset/reports/$reportId/metrics/$metricId/_content': {
id: '/app/_app/_asset/reports/$reportId/metrics/$metricId/_content'
path: '/metrics/$metricId'
@ -1949,6 +1969,7 @@ interface AppAppAssetChatsChatIdRouteChildren {
AppAppAssetChatsChatIdDashboardsDashboardIdRoute: typeof AppAppAssetChatsChatIdDashboardsDashboardIdRouteWithChildren
AppAppAssetChatsChatIdMetricsMetricIdRoute: typeof AppAppAssetChatsChatIdMetricsMetricIdRouteWithChildren
AppAppAssetChatsChatIdReportsReportIdRoute: typeof AppAppAssetChatsChatIdReportsReportIdRouteWithChildren
AppAppAssetChatsChatIdReasoningMessageIdIndexRoute: typeof AppAppAssetChatsChatIdReasoningMessageIdIndexRoute
}
const AppAppAssetChatsChatIdRouteChildren: AppAppAssetChatsChatIdRouteChildren =
@ -1960,6 +1981,8 @@ const AppAppAssetChatsChatIdRouteChildren: AppAppAssetChatsChatIdRouteChildren =
AppAppAssetChatsChatIdMetricsMetricIdRouteWithChildren,
AppAppAssetChatsChatIdReportsReportIdRoute:
AppAppAssetChatsChatIdReportsReportIdRouteWithChildren,
AppAppAssetChatsChatIdReasoningMessageIdIndexRoute:
AppAppAssetChatsChatIdReasoningMessageIdIndexRoute,
}
const AppAppAssetChatsChatIdRouteWithChildren =

View File

@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router';
import { ReasoningController } from '@/controllers/ReasoningController/ReasoningController';
export const Route = createFileRoute('/app/_app/_asset/chats/$chatId/reasoning/$messageId/')({
component: RouteComponent,
});
function RouteComponent() {
const { chatId, messageId } = Route.useParams();
return <ReasoningController chatId={chatId} messageId={messageId} />;
}