Skip to content

Commit

Permalink
Merge pull request #676 from polywrap/cedricwaxwing/details-modal-ux-…
Browse files Browse the repository at this point in the history
…updates

Details modal ux/ui updates
  • Loading branch information
dOrgJelli authored Jan 3, 2024
2 parents b93d586 + 90a2be5 commit b8b15c2
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 57 deletions.
3 changes: 2 additions & 1 deletion apps/browser/components/ChatLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function ChatLogs({
logs,
isRunning,
status,
chatName
chatName,
}: ChatLogsProps) {
const listContainerRef = useRef<HTMLDivElement | null>(null);
const [isAtBottom, setIsAtBottom] = useState(true);
Expand Down Expand Up @@ -189,6 +189,7 @@ export default function ChatLogs({
isOpen={logsDetails.open}
onClose={() => setLogsDetails({ ...logsDetails, open: false })}
logs={sanitizedLogs[logsDetails.index]}
status={status}
/>
</>
);
Expand Down
127 changes: 85 additions & 42 deletions apps/browser/components/modals/ChatDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { useRef, useState } from "react";
import { useLayoutEffect, useRef, useState } from "react";
import Modal from "./ModalBase";
import ReactMarkdown from "react-markdown";
import { CaretUp } from "@phosphor-icons/react";
import { CaretUp, CheckCircle } from "@phosphor-icons/react";
import { MessageSet } from "@/lib/utils/sanitizeLogsDetails";
import clsx from "clsx";
import LoadingCircle from "../LoadingCircle";

interface ChatDetailsProps {
isOpen: boolean;
onClose: () => void;
logs: MessageSet;
status?: string;
}

export default function ChatDetails({
isOpen,
onClose,
logs,
status,
}: ChatDetailsProps) {
const [expandedStep, setExpandedStep] = useState<string | null>(null);
const [initialToggle, setInitialToggle] = useState<boolean>(false);
const contentRefs = useRef<{ [index: number]: HTMLDivElement | null }>({});

const toggleStep = (step: string, index: number) => {
Expand All @@ -38,61 +42,100 @@ export default function ChatDetails({
}
};

useLayoutEffect(() => {
if (!initialToggle && isOpen) {
const logs_index = Object.keys(logs.details).length - 1;
const stepTitle = Object.keys(logs.details)[logs_index];
const stepDetails = Object.values(logs.details)[logs_index];
if (contentRefs.current[logs_index] && stepDetails.length > 0) {
toggleStep(stepTitle, logs_index);
setInitialToggle(true);
}
}
}, [isOpen, logs.details]);

return (
<Modal
isOpen={isOpen}
onClose={() => {
setInitialToggle(false);
setExpandedStep(null);
onClose();
}}
autoScroll
title="Details"
panelStyles={{ maxWidth: "max-w-screen-md" }}
>
<div className="space-y-4">
<div className="space-y-2 md:space-y-2">
{logs &&
Object.entries(logs.details).map(
([stepTitle, stepDetails], index) => (
<div
key={stepTitle}
className={clsx(
"prose-condensed prose prose-zinc prose-invert rounded-md bg-zinc-800 shadow-md transition-colors duration-0 ease-in-out hover:shadow-lg",
{
"cursor-pointer duration-150 hover:bg-zinc-700":
expandedStep !== stepTitle,
}
)}
>
<button
onClick={() => toggleStep(stepTitle, index)}
className="group flex w-full items-center justify-between p-4"
>
<ReactMarkdown>{stepTitle}</ReactMarkdown>
{stepDetails.length > 0 && (
<CaretUp
weight="bold"
size={14}
([stepTitle, stepDetails], index) => {
const isGoal = stepTitle.includes("## Goal");
return (
<div key={stepTitle} className="space-y-2 md:space-y-4">
<div
className={clsx(
"prose-condensed prose prose-zinc prose-invert relative max-w-none rounded-md bg-zinc-800 shadow-md transition-colors duration-0 ease-in-out hover:shadow-lg",
{
"cursor-pointer duration-150 hover:bg-zinc-700":
expandedStep !== stepTitle && stepDetails.length > 0,
}
)}
>
<button
onClick={() => toggleStep(stepTitle, index)}
className={clsx(
"group flex w-full items-center justify-between p-4",
{ "cursor-default": stepDetails.length <= 0 },
{
"rounded-md border border-green-500 bg-green-900 text-green-400":
isGoal,
}
)}
>
<div className="flex items-center space-x-2">
{isGoal && <CheckCircle size={24} weight="bold" />}
<ReactMarkdown className="prose-headings:mt-0 prose-headings:text-lg prose-headings:text-inherit">
{stepTitle}
</ReactMarkdown>
</div>
{stepDetails.length > 0 && (
<CaretUp
weight="bold"
size={14}
className={clsx(
"transform text-white transition-transform duration-500 ease-in-out group-hover:text-cyan-500",
expandedStep !== stepTitle && "rotate-180"
)}
/>
)}
</button>
<div
ref={(el) => {
contentRefs.current[index] = el;
}}
className={clsx(
"transform text-white transition-transform duration-500 ease-in-out group-hover:text-cyan-500",
expandedStep !== stepTitle && "rotate-180"
"step h-0 overflow-hidden transition-[height] duration-500 ease-in-out"
)}
/>
)}
</button>
<div
ref={(el) => {
contentRefs.current[index] = el;
}}
className={clsx(
"step h-0 overflow-hidden transition-[height] duration-500 ease-in-out"
)}
>
{stepDetails.map((detail, detailIndex) => (
<div className="p-4 pt-0" key={detailIndex}>
<ReactMarkdown>{detail}</ReactMarkdown>
>
{stepDetails.map((detail, detailIndex) => (
<div className="px-4 pt-0" key={detailIndex}>
<ReactMarkdown>{detail}</ReactMarkdown>
</div>
))}
</div>
))}
</div>
{status &&
Object.keys(logs.details).length - 1 === index &&
!isGoal && (
<div className="flex items-center space-x-2 text-cyan-500">
<LoadingCircle />
<div>{status}</div>
</div>
)}
</div>
</div>
)
);
}
)}
</div>
</Modal>
Expand Down
4 changes: 2 additions & 2 deletions apps/browser/components/modals/FileModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export default function FileModal(props: FileModalProps) {
title={file?.path ?? "View File"}
onClose={onClose}
contentStyles={{ padding: "p-0" }}
panelStyles={{ maxWidth: "max-w-[700px]" }}
panelStyles={{ maxWidth: "max-w-screen-md" }}
>
{file?.content && (
<div className="prose-file prose prose-invert w-full max-w-none overflow-auto p-8 pr-[1.5rem] font-mono text-xs [scrollbar-gutter:stable]">
<div className="prose prose-invert w-full max-w-none overflow-auto p-8 pr-[1.5rem] font-mono text-xs [scrollbar-gutter:stable]">
{formattedContent}
</div>
)}
Expand Down
71 changes: 63 additions & 8 deletions apps/browser/components/modals/ModalBase.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { PropsWithChildren } from "react";
import {
PropsWithChildren,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { Dialog, Transition } from "@headlessui/react";
import { Fragment } from "react";
import { Ubuntu_FONT } from "@/lib/fonts";
Expand All @@ -10,6 +16,7 @@ interface ModalProps {
title: string;
isOpen: boolean;
onClose: () => void;
autoScroll?: boolean;
panelStyles?: {
maxWidth?: string;
other?: string;
Expand All @@ -24,21 +31,63 @@ interface ModalProps {
}

export default function Modal(props: PropsWithChildren<ModalProps>) {
const { title, isOpen, onClose, panelStyles, contentStyles, children } =
props;
const {
title,
isOpen,
onClose,
autoScroll = false,
panelStyles,
contentStyles,
children,
} = props;
const modalContainerRef = useRef<HTMLDivElement | null>(null);
const [isAtBottom, setIsAtBottom] = useState(true);

const maxWidth = panelStyles?.maxWidth ?? "max-w-[540px]";

const defaultContentStyles = clsx(
"bg-zinc-900 [scrollbar-gutter:stable]",
contentStyles?.padding ? contentStyles?.padding : "p-8 pr-[1.5rem]",
contentStyles?.padding ? contentStyles?.padding : "p-4 pr-3 md:p-8 md:pr-6",
contentStyles?.["max-h"]
? contentStyles?.["max-h"]
: "max-h-[calc(100vh-72px)] md:max-h-[calc(100vh-96px)]",
: "max-h-[calc(100dvh-78px)] md:max-h-[calc(100dvh-96px)]",
contentStyles?.spacing ? contentStyles?.spacing : "space-y-8",
contentStyles?.overflow ? contentStyles?.overflow : "overflow-y-auto",
contentStyles?.other
);

const scrollToBottom = () => {
modalContainerRef.current?.scrollTo({
top: modalContainerRef.current.scrollHeight,
behavior: "smooth",
});
};

const handleScroll = useCallback(() => {
// Detect if the user is at the bottom of the modal
const container = modalContainerRef.current;
if (container) {
const isScrolledToBottom =
container.scrollHeight - container.scrollTop <= container.clientHeight;
setIsAtBottom(isScrolledToBottom);
}
}, []);

useEffect(() => {
// If the user is at the bottom, scroll to the bottom
if (isAtBottom && autoScroll) {
scrollToBottom();
}
}, [children, isAtBottom, autoScroll]);

useEffect(() => {
const container = modalContainerRef.current;
if (container) {
// Add scroll event listener
container.addEventListener("scroll", handleScroll);
}
});

return (
<>
<Transition appear show={isOpen} as={Fragment}>
Expand All @@ -48,7 +97,7 @@ export default function Modal(props: PropsWithChildren<ModalProps>) {
onClose={onClose}
>
<div className="fixed inset-0 overflow-y-auto bg-zinc-400/50 backdrop-blur [scrollbar-gutter:stable] ">
<div className="flex min-h-full items-center justify-center p-1 text-center md:p-4">
<div className="flex min-h-screen -translate-y-0.5 transform items-center justify-center p-1 text-center md:p-4">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
Expand All @@ -60,7 +109,7 @@ export default function Modal(props: PropsWithChildren<ModalProps>) {
>
<Dialog.Panel
className={clsx(
"w-full transform overflow-hidden rounded-2xl rounded-none bg-zinc-800 text-left align-middle text-zinc-50 shadow-xl transition-all",
"w-full transform overflow-hidden rounded-xl bg-zinc-800 text-left align-middle text-zinc-50 shadow-xl transition-all",
maxWidth
)}
onClick={(e) => {
Expand All @@ -84,7 +133,13 @@ export default function Modal(props: PropsWithChildren<ModalProps>) {
/>
</Button>
</div>
<div className={defaultContentStyles}>{children}</div>
<div
className={defaultContentStyles}
onScroll={() => autoScroll && handleScroll()}
ref={modalContainerRef}
>
{children}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
Expand Down
11 changes: 7 additions & 4 deletions apps/browser/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ code {
background-position: 56% 58%;
}

.prose-condensed blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre {
@apply !m-0;
.prose-condensed {
@apply prose-headings:mt-1 prose-headings:mb-0;
}

.prose-file * {
@apply my-0;
.prose {
@apply prose-headings:font-medium;
}

::-webkit-scrollbar {
Expand All @@ -69,6 +69,9 @@ code {
.h-screen {
height: 100dvh; /* Override height for Safari */
}
.min-h-screen {
min-height: 100dvh; /* Override height for Safari */
}
}

/* In smaller screens, font-sizes smaller than 16px cause
Expand Down

0 comments on commit b8b15c2

Please sign in to comment.