UI overhaul: fonts, colors, scrollable message boxes, animated text generation status updates

master
Avraham Sakal 4 weeks ago
parent ebfbb22525
commit 6668361f05

@ -100,7 +100,7 @@ export default function LayoutDefault({
<TRPCProvider trpcClient={trpc} queryClient={queryClient}> <TRPCProvider trpcClient={trpc} queryClient={queryClient}>
<MantineProvider theme={theme}> <MantineProvider theme={theme}>
<AppShell <AppShell
header={{ height: 60 }} header={{ height: 80 }}
navbar={{ navbar={{
width: 300, width: 300,
breakpoint: "sm", breakpoint: "sm",
@ -109,7 +109,7 @@ export default function LayoutDefault({
padding="lg" padding="lg"
> >
<AppShell.Header> <AppShell.Header>
<Group h="100%" px="md"> <Group px="md" wrap="nowrap">
<Burger <Burger
opened={opened} opened={opened}
onClick={toggle} onClick={toggle}
@ -120,7 +120,17 @@ export default function LayoutDefault({
{" "} {" "}
<Image h={50} fit="contain" src={logoUrl} />{" "} <Image h={50} fit="contain" src={logoUrl} />{" "}
</a> </a>
<Title>Token-Efficient Context Engineering</Title> <Title
textWrap="balance"
lineClamp={1}
styles={{
root: {
lineHeight: theme?.lineHeights?.["6xl"],
},
}}
>
Token-Efficient Context Engineering
</Title>
</Group> </Group>
</AppShell.Header> </AppShell.Header>
<AppShell.Navbar p="md"> <AppShell.Navbar p="md">

@ -110,6 +110,18 @@ const theme: MantineThemeOverride = createTheme({
"3xl": "2.25rem", "3xl": "2.25rem",
"4xl": "3rem", "4xl": "3rem",
}, },
lineHeights: {
xs: "1.25rem",
sm: "1.375rem",
md: "1.5rem",
lg: "1.75rem",
xl: "2rem",
"2xl": "2.25rem",
"3xl": "2.625rem",
"4xl": "3rem",
"5xl": "3.75rem",
"6xl": "4.5rem",
},
}); });
export default theme; export default theme;

@ -5,12 +5,16 @@ import {
HoverCard, HoverCard,
JsonInput, JsonInput,
List, List,
ScrollArea,
Stack, Stack,
Tabs, Tabs,
Text,
Textarea, Textarea,
TextInput,
Transition,
useMantineTheme, useMantineTheme,
} from "@mantine/core"; } from "@mantine/core";
import { useEffect, useState } from "react"; import { memo, useEffect, useState } from "react";
import { import {
defaultParameters, defaultParameters,
defaultSystemPrompt, defaultSystemPrompt,
@ -23,6 +27,7 @@ import type {
CommittedMessage, CommittedMessage,
DraftMessage, DraftMessage,
OtherParameters, OtherParameters,
SendMessageStatus,
} from "../../../types"; } from "../../../types";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import { import {
@ -351,6 +356,8 @@ export default function ChatPage() {
const setSendMessageStatus = useStore((state) => state.setSendMessageStatus); const setSendMessageStatus = useStore((state) => state.setSendMessageStatus);
const setIsSendingMessage = useStore((state) => state.setIsSendingMessage); const setIsSendingMessage = useStore((state) => state.setIsSendingMessage);
const theme = useMantineTheme();
// Function to send message using subscription // Function to send message using subscription
const sendSubscriptionMessage = async ({ const sendSubscriptionMessage = async ({
conversationId, conversationId,
@ -489,11 +496,11 @@ export default function ChatPage() {
} }
return ( return (
<> <Box>
<div> <Group justify="flex-start" gap={"sm"}>
<span>Conversation #{conversationId} - </span> <TextInput
<input inputSize="50"
type="text" description={`Conversation #${conversationId}`}
defaultValue={conversationTitle || ""} defaultValue={conversationTitle || ""}
// onChange={(e) => { // onChange={(e) => {
// setConversationTitle(e.target.value); // setConversationTitle(e.target.value);
@ -504,10 +511,27 @@ export default function ChatPage() {
title: e.target.value, title: e.target.value,
}); });
}} }}
variant="unstyled"
styles={{
input: {
// backgroundColor: "transparent",
// border: "none",
// padding: 0,
// margin: 0,
fontFamily: theme.headings.fontFamily,
fontSize: theme.fontSizes.lg,
lineHeight: theme.lineHeights["4xl"],
},
wrapper: {
marginTop: 0,
},
}}
/> />
{isSendingMessage && <IconLoaderQuarter size={16} stroke={1.5} />} {isSendingMessage && <IconLoaderQuarter size={16} stroke={1.5} />}
{sendMessageStatus && <span>{sendMessageStatus.message}</span>} {sendMessageStatus && (
</div> <StatusMessage sendMessageStatus={sendMessageStatus} />
)}
</Group>
<Tabs defaultValue="message"> <Tabs defaultValue="message">
<Tabs.List> <Tabs.List>
<Tabs.Tab value="message">Message</Tabs.Tab> <Tabs.Tab value="message">Message</Tabs.Tab>
@ -690,7 +714,7 @@ export default function ChatPage() {
</List> </List>
</Tabs.Panel> </Tabs.Panel>
</Tabs> </Tabs>
</> </Box>
); );
} }
@ -792,15 +816,16 @@ function Messages() {
position={message.role === "user" ? "left" : "right"} position={message.role === "user" ? "left" : "right"}
> >
<HoverCard.Target> <HoverCard.Target>
<Box <ScrollArea
scrollbars="x"
w="75%" w="75%"
p="md"
bdrs="md"
bg={ bg={
message.role === "user" message.role === "user"
? theme.colors.gray[2] ? theme.colors.gray[2]
: theme.colors.blue[2] : theme.colors.blue[2]
} }
p="md"
bdrs="md"
> >
<Markdown> <Markdown>
{message.parts {message.parts
@ -808,7 +833,7 @@ function Messages() {
.map((p) => p.text) .map((p) => p.text)
.join("\n")} .join("\n")}
</Markdown> </Markdown>
</Box> </ScrollArea>
</HoverCard.Target> </HoverCard.Target>
<HoverCard.Dropdown> <HoverCard.Dropdown>
<ActionIcon.Group> <ActionIcon.Group>
@ -840,3 +865,44 @@ function Messages() {
</Stack> </Stack>
); );
} }
const StatusMessage = memo(
({ sendMessageStatus }: { sendMessageStatus: SendMessageStatus | null }) => {
const [displayMessage, setDisplayMessage] = useState(sendMessageStatus);
const [isVisible, setIsVisible] = useState(sendMessageStatus !== null);
useEffect(() => {
if (sendMessageStatus === null) {
setIsVisible(false);
setTimeout(() => setDisplayMessage(null), 250);
} else if (displayMessage === null) {
setDisplayMessage(sendMessageStatus);
setIsVisible(true);
} else if (displayMessage.message !== sendMessageStatus.message) {
setIsVisible(false);
setTimeout(() => {
setDisplayMessage(sendMessageStatus);
setIsVisible(true);
}, 250);
}
}, [sendMessageStatus, displayMessage]);
return (
<div style={{ position: "relative" }}>
{/* This is a hack to make this component take up the space it would take up once the transition is complete. Useful for when this component is in a flexbox with a particular alignment/justification, which we want to be calculated against its eventual size. */}
<span style={{ visibility: "hidden" }}>{displayMessage?.message}</span>
<Transition
transition="pop"
duration={500}
mounted={isVisible && displayMessage !== null}
>
{(styles) => (
<span style={{ ...styles, position: "absolute", top: 0, left: 0 }}>
{displayMessage?.message}
</span>
)}
</Transition>
</div>
);
}
);

Loading…
Cancel
Save