default file passthrough

This commit is contained in:
Nate Kelley 2025-01-24 16:59:38 -07:00
parent de0d46bb2e
commit b3af7267ef
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
14 changed files with 66 additions and 92 deletions

View File

@ -1,36 +1,27 @@
import React, { useMemo, useRef } from 'react'; import React, { useMemo, useRef } from 'react';
import type { ChatSplitterProps } from '../ChatLayout'; import type { ChatSplitterProps } from '../ChatLayout';
import { ChatHeader } from './ChatHeader'; import { ChatHeader } from './ChatHeader';
import { SelectedFile } from '../interfaces';
import { ChatContent } from './ChatContent'; import { ChatContent } from './ChatContent';
import { useScroll } from 'ahooks'; import { useScroll } from 'ahooks';
interface ChatContainerProps { interface ChatContainerProps {}
chatContent: ChatSplitterProps['chatContent'];
selectedFile: SelectedFile | undefined;
isPureFile: boolean;
}
export const ChatContainer: React.FC<ChatContainerProps> = React.memo( export const ChatContainer: React.FC<ChatContainerProps> = React.memo(({}) => {
({ chatContent, selectedFile, isPureFile }) => { const chatContentRef = useRef<HTMLDivElement>(null);
const chatContentRef = useRef<HTMLDivElement>(null); const scroll = useScroll(chatContentRef);
const scroll = useScroll(chatContentRef);
const showScrollOverflow = useMemo(() => { const showScrollOverflow = useMemo(() => {
if (!chatContentRef.current || !scroll) return false; if (!chatContentRef.current || !scroll) return false;
const trigger = 50; const trigger = 50;
return scroll.top > trigger; return scroll.top > trigger;
}, [chatContentRef, scroll?.top]); }, [chatContentRef, scroll?.top]);
if (isPureFile) return null; return (
<div className="flex h-full w-full flex-col">
return ( <ChatHeader showScrollOverflow={showScrollOverflow} />
<div className="flex h-full w-full flex-col"> <ChatContent chatContentRef={chatContentRef} />
<ChatHeader selectedFile={selectedFile} showScrollOverflow={showScrollOverflow} /> </div>
<ChatContent chatContentRef={chatContentRef} /> );
</div> });
);
}
);
ChatContainer.displayName = 'ChatContainer'; ChatContainer.displayName = 'ChatContainer';

View File

@ -21,12 +21,10 @@ const colors = [
export const ChatContent: React.FC<{ chatContentRef: React.RefObject<HTMLDivElement> }> = ({ export const ChatContent: React.FC<{ chatContentRef: React.RefObject<HTMLDivElement> }> = ({
chatContentRef chatContentRef
}) => { }) => {
const hasFile = useChatSplitterContextSelector((state) => state.hasFile);
return ( return (
<div ref={chatContentRef} className="h-full w-full overflow-y-auto"> <div ref={chatContentRef} className="h-full w-full overflow-y-auto">
<div className="mx-auto max-w-[600px]"> <div className="mx-auto max-w-[600px]">
{Array.from({ length: 350 }).map((_, index) => ( {Array.from({ length: 150 }).map((_, index) => (
<ChatContentItem key={index} index={index} /> <ChatContentItem key={index} index={index} />
))} ))}
</div> </div>

View File

@ -7,9 +7,8 @@ import { ChatHeaderOptions } from './ChatHeaderOptions';
import { ChatHeaderTitle } from './ChatHeaderTitle'; import { ChatHeaderTitle } from './ChatHeaderTitle';
export const ChatHeader: React.FC<{ export const ChatHeader: React.FC<{
selectedFile: SelectedFile | undefined;
showScrollOverflow: boolean; showScrollOverflow: boolean;
}> = React.memo(({ selectedFile, showScrollOverflow }) => { }> = React.memo(({ showScrollOverflow }) => {
const { cx, styles } = useStyles(); const { cx, styles } = useStyles();
const hasFile = useChatSplitterContextSelector((state) => state.hasFile); const hasFile = useChatSplitterContextSelector((state) => state.hasFile);

View File

@ -9,7 +9,8 @@ export const ChatContainerHeaderDropdown: React.FC<{
const selectedFileType = useChatSplitterContextSelector((state) => state.selectedFileType); const selectedFileType = useChatSplitterContextSelector((state) => state.selectedFileType);
const menuItem: MenuProps['items'] = useMemo(() => { const menuItem: MenuProps['items'] = useMemo(() => {
if (!selectedFileType) return [] as MenuProps['items']; if (!selectedFileType || !(selectedFileType in HeaderOptionsRecord))
return [] as MenuProps['items'];
return HeaderOptionsRecord[selectedFileType](); return HeaderOptionsRecord[selectedFileType]();
}, [selectedFileType]); }, [selectedFileType]);

View File

@ -30,12 +30,5 @@ export const HeaderOptionsRecord: Record<AppChatMessageFileType, () => MenuProps
key: 'delete', key: 'delete',
icon: <AppMaterialIcons icon="delete" /> icon: <AppMaterialIcons icon="delete" />
} }
],
chat: () => [
{
label: 'Delete',
key: 'delete',
icon: <AppMaterialIcons icon="delete" />
}
] ]
}; };

View File

@ -10,56 +10,52 @@ import { SelectedFile } from './interfaces';
import { useUpdateEffect, useUpdateLayoutEffect } from 'ahooks'; import { useUpdateEffect, useUpdateLayoutEffect } from 'ahooks';
export interface ChatSplitterProps { export interface ChatSplitterProps {
chatContent?: React.ReactNode;
showChatCollapse?: boolean; showChatCollapse?: boolean;
defaultShowLayout?: 'chat' | 'file' | 'both'; selectedLayout?: 'chat' | 'file' | 'both';
defaultSelectedFile?: SelectedFile; selectedFile?: SelectedFile;
} }
export const ChatLayout: React.FC<ChatSplitterProps> = React.memo( export const ChatLayout: React.FC<ChatSplitterProps> = React.memo(
({ defaultSelectedFile, defaultShowLayout = 'chat', chatContent }) => { ({ selectedFile, selectedLayout = 'chat' }) => {
const appSplitterRef = useRef<AppSplitterRef>(null); const appSplitterRef = useRef<AppSplitterRef>(null);
const [isPureFile, setIsPureFile] = useState(defaultShowLayout === 'file'); const [isPureFile, setIsPureFile] = useState(selectedLayout === 'file');
const defaultSplitterLayout = useMemo(() => { const defaultSplitterLayout = useMemo(() => {
if (defaultShowLayout === 'chat') return ['100%', '0%']; if (selectedLayout === 'chat') return ['100%', '0%'];
if (defaultShowLayout === 'file') return ['0%', '100%']; if (selectedLayout === 'file') return ['0%', '100%'];
return ['325px', 'auto']; return ['325px', 'auto'];
}, [defaultShowLayout]); }, [selectedLayout]);
const useChatSplitterProps = useChatLayout({ defaultSelectedFile }); const useChatSplitterProps = useChatLayout({ selectedFile });
const { onSetSelectedFile, selectedFile, hasFile } = useChatSplitterProps; const { onSetSelectedFile, hasFile, selectedFileId } = useChatSplitterProps;
useUpdateEffect(() => { useUpdateEffect(() => {
if (defaultSelectedFile && appSplitterRef.current) { if (appSplitterRef.current) {
if (defaultShowLayout === 'chat') { if (selectedLayout === 'chat') {
appSplitterRef.current?.animateWidth('100%', 'left'); appSplitterRef.current?.animateWidth('100%', 'left');
} else if (defaultShowLayout === 'file') { } else if (selectedLayout === 'file') {
appSplitterRef.current?.animateWidth('100%', 'right'); appSplitterRef.current?.animateWidth('100%', 'right');
} else if (appSplitterRef.current.isRightClosed || appSplitterRef.current.isLeftClosed) { } else if (
selectedLayout === 'both' &&
(appSplitterRef.current.isRightClosed || appSplitterRef.current.isLeftClosed)
) {
appSplitterRef.current?.animateWidth('320px', 'left'); appSplitterRef.current?.animateWidth('320px', 'left');
} }
} }
if (defaultSelectedFile) onSetSelectedFile(defaultSelectedFile); if (selectedFile) onSetSelectedFile(selectedFile);
}, [defaultSelectedFile, defaultShowLayout]); }, [selectedFile, selectedLayout]);
useUpdateLayoutEffect(() => { useUpdateLayoutEffect(() => {
if (isPureFile === true) setIsPureFile(defaultShowLayout === 'file'); if (isPureFile === true) setIsPureFile(selectedLayout === 'file');
}, [defaultShowLayout]); }, [selectedLayout]);
return ( return (
<ChatSplitterContextProvider useChatSplitterProps={useChatSplitterProps}> <ChatSplitterContextProvider useChatSplitterProps={useChatSplitterProps}>
<AppSplitter <AppSplitter
ref={appSplitterRef} ref={appSplitterRef}
leftChildren={ leftChildren={isPureFile ? null : <ChatContainer />}
<ChatContainer rightChildren={!selectedFileId ? null : <FileContainer />}
selectedFile={selectedFile}
chatContent={chatContent}
isPureFile={isPureFile}
/>
}
rightChildren={<FileContainer selectedFile={selectedFile} />}
autoSaveId="chat-splitter" autoSaveId="chat-splitter"
defaultLayout={defaultSplitterLayout} defaultLayout={defaultSplitterLayout}
preserveSide="left" preserveSide="left"

View File

@ -7,31 +7,31 @@ import React, { PropsWithChildren, useMemo, useState } from 'react';
import { SelectedFile } from '../interfaces'; import { SelectedFile } from '../interfaces';
interface UseChatSplitterProps { interface UseChatSplitterProps {
defaultSelectedFile: SelectedFile | undefined; selectedFile: SelectedFile | undefined;
} }
export const useChatLayout = ({ defaultSelectedFile }: UseChatSplitterProps) => { export const useChatLayout = ({ selectedFile: selectedFileProp }: UseChatSplitterProps) => {
const [selectedFile, setSelectedFile] = const [selectedFileId, setSelectedFileId] = useState<string | undefined>(selectedFileProp?.id);
useState<UseChatSplitterProps['defaultSelectedFile']>(defaultSelectedFile);
const hasFile = !!selectedFile; const hasFile = !!selectedFileId;
const selectedFileTitle: string = useMemo(() => { const selectedFileTitle: string = useMemo(() => {
if (!selectedFile) return ''; console.log('selectedFileId', selectedFileId);
return selectedFile.type; if (!selectedFileId) return '';
}, [selectedFile]); return 'test';
}, [selectedFileId]);
const selectedFileType = selectedFile?.type || null; const selectedFileType = selectedFileProp?.type;
const onSetSelectedFile = (file: SelectedFile) => { const onSetSelectedFile = (file: SelectedFile) => {
setSelectedFile(file); // setSelectedFileId(file.id);
}; };
return { return {
selectedFileTitle, selectedFileTitle,
selectedFileType, selectedFileType,
selectedFileId,
hasFile, hasFile,
selectedFile,
onSetSelectedFile onSetSelectedFile
}; };
}; };

View File

@ -2,11 +2,9 @@ import React from 'react';
import type { ChatSplitterProps } from '../ChatLayout'; import type { ChatSplitterProps } from '../ChatLayout';
import { SelectedFile } from '../interfaces'; import { SelectedFile } from '../interfaces';
interface FileContainerProps { interface FileContainerProps {}
selectedFile: SelectedFile | undefined;
}
export const FileContainer: React.FC<FileContainerProps> = React.memo(({ selectedFile }) => { export const FileContainer: React.FC<FileContainerProps> = React.memo(({}) => {
return <div className="h-full w-full bg-green-500">FileContainer</div>; return <div className="h-full w-full bg-green-500">FileContainer</div>;
}); });

View File

@ -6,7 +6,7 @@ import { useParams } from 'next/navigation';
import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer'; import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
import { ChatSplitterProps } from '../ChatLayout'; import { ChatSplitterProps } from '../ChatLayout';
export const useDefaultFile = () => { export const useSelectedFileByParams = () => {
const { metricId, collectionId, datasetId, dashboardId, chatId } = useParams() as { const { metricId, collectionId, datasetId, dashboardId, chatId } = useParams() as {
metricId?: string; metricId?: string;
collectionId?: string; collectionId?: string;
@ -15,15 +15,14 @@ export const useDefaultFile = () => {
chatId?: string; chatId?: string;
}; };
const defaultFile: SelectedFile | undefined = useMemo(() => { const selectedFile: SelectedFile | undefined = useMemo(() => {
if (metricId) return { id: metricId, type: AppChatMessageFileType.Metric }; if (metricId) return { id: metricId, type: AppChatMessageFileType.Metric };
if (collectionId) return { id: collectionId, type: AppChatMessageFileType.Collection }; if (collectionId) return { id: collectionId, type: AppChatMessageFileType.Collection };
if (datasetId) return { id: datasetId, type: AppChatMessageFileType.Dataset }; if (datasetId) return { id: datasetId, type: AppChatMessageFileType.Dataset };
if (dashboardId) return { id: dashboardId, type: AppChatMessageFileType.Dashboard }; if (dashboardId) return { id: dashboardId, type: AppChatMessageFileType.Dashboard };
if (chatId) return { id: chatId, type: AppChatMessageFileType.Chat };
}, [metricId, collectionId, datasetId, dashboardId, chatId]); }, [metricId, collectionId, datasetId, dashboardId, chatId]);
const defaultLayout: ChatSplitterProps['defaultShowLayout'] = useMemo(() => { const selectedLayout: ChatSplitterProps['selectedLayout'] = useMemo(() => {
const hasFileId = metricId || collectionId || datasetId || dashboardId; const hasFileId = metricId || collectionId || datasetId || dashboardId;
if (chatId) { if (chatId) {
@ -36,5 +35,5 @@ export const useDefaultFile = () => {
return 'chat'; return 'chat';
}, [metricId, collectionId, datasetId, dashboardId, chatId]); }, [metricId, collectionId, datasetId, dashboardId, chatId]);
return { defaultFile, defaultLayout }; return { selectedFile, selectedLayout };
}; };

View File

@ -1,5 +1,5 @@
import { ThreadContentController } from '@/app/app/_controllers/ThreadController'; import { ThreadContentController } from '@/app/app/_controllers/ThreadController';
import { AppAssetCheckLayout } from '@/app/app/_layouts'; import { AppAssetCheckLayout } from '../../../../_layouts/AppAssetCheckLayout';
import { getAppSplitterLayout } from '@/components/layout'; import { getAppSplitterLayout } from '@/components/layout';
import React from 'react'; import React from 'react';

View File

@ -1,5 +1,5 @@
import { ThreadContentController } from '@/app/app/_controllers/ThreadController'; import { ThreadContentController } from '@/app/app/_controllers/ThreadController';
import { AppAssetCheckLayout } from '@/app/app/_layouts'; import { AppAssetCheckLayout } from '../../../../_layouts/AppAssetCheckLayout';
import { getAppSplitterLayout } from '@/components/layout'; import { getAppSplitterLayout } from '@/components/layout';
import React from 'react'; import React from 'react';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { DashboardIndividualHeader } from './_DashboardIndividualHeader'; import { DashboardIndividualHeader } from './_DashboardIndividualHeader';
import { DashboardIndividualContent } from './_DashboardIndividualContent'; import { DashboardIndividualContent } from './_DashboardIndividualContent';
import { AppAssetCheckLayout } from '../../_layouts'; import { AppAssetCheckLayout } from '../../_layouts/AppAssetCheckLayout';
export default function DashboardPage({ export default function DashboardPage({
params: { dashboardId } params: { dashboardId }

View File

@ -1,12 +1,12 @@
'use client'; 'use client';
import { ChatLayout, useDefaultFile } from '@chatLayout/index'; import { ChatLayout, useSelectedFileByParams } from '@chatLayout/index';
import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer'; import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
export default function Layout({}: {}) { export default function Layout({}: {}) {
const { defaultFile, defaultLayout } = useDefaultFile(); const { selectedFile, selectedLayout } = useSelectedFileByParams();
const router = useRouter(); const router = useRouter();
useHotkeys('m', () => { useHotkeys('m', () => {
@ -31,7 +31,7 @@ export default function Layout({}: {}) {
return ( return (
<div className="h-screen w-screen"> <div className="h-screen w-screen">
<ChatLayout defaultShowLayout={defaultLayout} defaultSelectedFile={defaultFile} /> <ChatLayout selectedLayout={selectedLayout} selectedFile={selectedFile} />
</div> </div>
); );
} }

View File

@ -34,8 +34,7 @@ export enum AppChatMessageFileType {
Dataset = 'dataset', Dataset = 'dataset',
Collection = 'collection', Collection = 'collection',
Metric = 'metric', Metric = 'metric',
Dashboard = 'dashboard', Dashboard = 'dashboard'
Chat = 'chat'
} }
export type AppChatMessageFile = { export type AppChatMessageFile = {