fix: rework main page to use Chakra, and a little bit of the browse page (#402)

This commit is contained in:
2024-09-04 03:27:20 +02:00
committed by GitHub
parent 873cbb0a42
commit 42796d7d3f
6 changed files with 271 additions and 175 deletions

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { GetTranscript } from "../../api"; import { GetTranscript } from "../../api";
import Pagination from "./pagination"; import Pagination from "./pagination";
@@ -44,6 +45,7 @@ import { ExpandableText } from "../../lib/expandableText";
export default function TranscriptBrowser() { export default function TranscriptBrowser() {
const [page, setPage] = useState<number>(1); const [page, setPage] = useState<number>(1);
const { loading, response, refetch } = useTranscriptList(page); const { loading, response, refetch } = useTranscriptList(page);
const { data: session } = useSession();
const [deletionLoading, setDeletionLoading] = useState(false); const [deletionLoading, setDeletionLoading] = useState(false);
const api = useApi(); const api = useApi();
const { setError } = useError(); const { setError } = useError();
@@ -124,8 +126,9 @@ export default function TranscriptBrowser() {
flexDir="column" flexDir="column"
margin="auto" margin="auto"
gap={2} gap={2}
overflowY="scroll" overflowY="auto"
maxH="100%" minH="100%"
mt={8}
> >
<Flex <Flex
flexDir="row" flexDir="row"
@@ -133,8 +136,12 @@ export default function TranscriptBrowser() {
align="center" align="center"
flexWrap={"wrap-reverse"} flexWrap={"wrap-reverse"}
> >
{/* <Heading>{user?.fields?.name}'s Meetings</Heading> */} {session?.user?.name ? (
<Heading>Your Meetings</Heading> <Heading size="md">{session?.user?.name}'s Meetings</Heading>
) : (
<Heading size="md">Your meetings</Heading>
)}
{loading || (deletionLoading && <Spinner></Spinner>)} {loading || (deletionLoading && <Spinner></Spinner>)}
<Spacer /> <Spacer />
@@ -149,7 +156,6 @@ export default function TranscriptBrowser() {
New Meeting New Meeting
</Button> </Button>
</Flex> </Flex>
<Grid <Grid
templateColumns={{ templateColumns={{
base: "repeat(1, 1fr)", base: "repeat(1, 1fr)",
@@ -161,7 +167,7 @@ export default function TranscriptBrowser() {
lg: 4, lg: 4,
}} }}
maxH="100%" maxH="100%"
overflowY={"scroll"} overflowY="auto"
mb="4" mb="4"
> >
{response?.items {response?.items
@@ -169,49 +175,7 @@ export default function TranscriptBrowser() {
.map((item: GetTranscript) => ( .map((item: GetTranscript) => (
<Card key={item.id} border="gray.light" variant="outline"> <Card key={item.id} border="gray.light" variant="outline">
<CardBody> <CardBody>
<Flex align={"center"} ml="-6px"> <Flex align={"center"} gap={2}>
{item.status == "ended" && (
<Tooltip label="Processing done">
<span>
<Icon color="green" as={FaCheck} mr="2" />
</span>
</Tooltip>
)}
{item.status == "error" && (
<Tooltip label="Processing error">
<span>
<Icon color="red.primary" as={MdError} mr="2" />
</span>
</Tooltip>
)}
{item.status == "idle" && (
<Tooltip label="New meeting, no recording">
<span>
<Icon color="yellow.500" as={FaStar} mr="2" />
</span>
</Tooltip>
)}
{item.status == "processing" && (
<Tooltip label="Processing in progress">
<span>
<Icon
color="grey.primary"
as={FaGear}
mr="2"
transition={"all 2s ease"}
transform={"rotate(0deg)"}
_hover={{ transform: "rotate(360deg)" }}
/>
</span>
</Tooltip>
)}
{item.status == "recording" && (
<Tooltip label="Recording in progress">
<span>
<Icon color="blue.primary" as={FaMicrophone} mr="2" />
</span>
</Tooltip>
)}
<Heading size="md"> <Heading size="md">
<Link <Link
as={NextLink} as={NextLink}
@@ -279,12 +243,61 @@ export default function TranscriptBrowser() {
</MenuList> </MenuList>
</Menu> </Menu>
</Flex> </Flex>
<Stack mt="6" spacing="3"> <Stack mt="4" spacing="2">
<Text fontSize="small"> <Flex align={"center"} gap={2}>
{new Date(item.created_at).toLocaleString("en-US")} {item.status == "ended" && (
{"\u00A0"}-{"\u00A0"} <Tooltip label="Processing done">
{formatTimeMs(item.duration)} <span>
</Text> <Icon color="green" as={FaCheck} />
</span>
</Tooltip>
)}
{item.status == "error" && (
<Tooltip label="Processing error">
<span>
<Icon color="red.primary" as={MdError} />
</span>
</Tooltip>
)}
{item.status == "idle" && (
<Tooltip label="New meeting, no recording">
<span>
<Icon color="yellow.500" as={FaStar} />
</span>
</Tooltip>
)}
{item.status == "processing" && (
<Tooltip label="Processing in progress">
<span>
<Icon
color="grey.primary"
as={FaGear}
transition={"all 2s ease"}
transform={"rotate(0deg)"}
_hover={{ transform: "rotate(360deg)" }}
/>
</span>
</Tooltip>
)}
{item.status == "recording" && (
<Tooltip label="Recording in progress">
<span>
<Icon color="blue.primary" as={FaMicrophone} />
</span>
</Tooltip>
)}
<Text fontSize="small">
{new Date(item.created_at).toLocaleString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
{"\u00A0"}-{"\u00A0"}
{formatTimeMs(item.duration)}
</Text>
</Flex>
<ExpandableText noOfLines={5}> <ExpandableText noOfLines={5}>
{item.short_summary} {item.short_summary}
</ExpandableText> </ExpandableText>

View File

@@ -29,25 +29,22 @@ export default async function AppLayout({
w="100%" w="100%"
py="2" py="2"
px="0" px="0"
mt="1"
> >
{/* Logo on the left */} {/* Logo on the left */}
<Link <Link as={NextLink} href="/" className="flex">
as={NextLink}
href="/"
className="flex outline-blue-300 md:outline-none focus-visible:underline underline-offset-2 decoration-[.5px] decoration-gray-500"
>
<Image <Image
src="/reach.png" src="/reach.svg"
width={16} width={32}
height={16} height={40}
className="h-10 w-auto" className="h-11 w-auto"
alt="Reflector" alt="Reflector"
/> />
<div className="hidden flex-col ml-2 md:block"> <div className="hidden flex-col ml-3 md:block">
<h1 className="text-[38px] font-bold tracking-wide leading-tight"> <h1 className="text-[28px] font-semibold leading-tight">
Reflector Reflector
</h1> </h1>
<p className="text-gray-500 text-xs tracking-tighter"> <p className="text-gray-500 text-xs tracking-tight -mt-1">
Capture the signal, not the noise Capture the signal, not the noise
</p> </p>
</div> </div>

View File

@@ -12,9 +12,36 @@ import SelectSearch from "react-select-search";
import { supportedLanguages } from "../../../supportedLanguages"; import { supportedLanguages } from "../../../supportedLanguages";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { featureEnabled } from "../../../domainContext"; import { featureEnabled } from "../../../domainContext";
import { Button, Text } from "@chakra-ui/react";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import { Spinner } from "@chakra-ui/react"; import {
Flex,
Box,
Spinner,
Heading,
Button,
Card,
Center,
Link,
CardBody,
Stack,
Text,
Icon,
Grid,
IconButton,
Spacer,
Menu,
MenuButton,
MenuItem,
MenuList,
AlertDialog,
AlertDialogOverlay,
AlertDialogContent,
AlertDialogHeader,
AlertDialogBody,
AlertDialogFooter,
Tooltip,
Input,
} from "@chakra-ui/react";
const TranscriptCreate = () => { const TranscriptCreate = () => {
const router = useRouter(); const router = useRouter();
const { status } = useSession(); const { status } = useSession();
@@ -70,106 +97,132 @@ const TranscriptCreate = () => {
useAudioDevice(); useAudioDevice();
return ( return (
<div className="grid grid-rows-layout-topbar gap-2 lg:gap-4 max-h-full overflow-y-scroll"> <Flex
<div className="lg:grid lg:grid-cols-2 lg:grid-rows-1 lg:gap-4 lg:h-full h-auto flex flex-col"> maxW="container.xl"
<section className="flex flex-col w-full lg:h-full items-center justify-evenly p-4 md:px-6 md:py-8"> flexDir="column"
<div className="flex flex-col max-w-xl items-center justify-center"> margin="auto"
<h1 className="text-2xl font-bold mb-2">Welcome to Reflector</h1> gap={2}
<p> overflowY="auto"
Reflector is a transcription and summarization pipeline that maxH="100%"
transforms audio into knowledge. >
<span className="hidden md:block"> <Flex
The output is meeting minutes and topic summaries enabling flexDir={{ base: "column", md: "row" }}
topic-specific analyses stored in your systems of record. This justify="space-between"
is accomplished on your infrastructure without 3rd parties align="center"
keeping your data private, secure, and organized. gap={4}
</span> >
</p> <Flex
<About buttonText="Learn more" /> flexDir="column"
<p className="mt-6"> h="full"
In order to use Reflector, we kindly request permission to access justify="evenly"
your microphone during meetings and events. flexBasis="1"
</p> flexGrow={1}
{featureEnabled("privacy") && ( >
<Privacy buttonText="Privacy policy" /> <Heading size="lg" textAlign={{ base: "center", md: "left" }}>
)} Welcome to Reflector
</div> </Heading>
</section> <Text mt={6}>
<section className="flex flex-col justify-center items-center w-full h-full"> Reflector is a transcription and summarization pipeline that
{!sessionReady ? ( transforms audio into knowledge.
<Spinner /> <Text className="hidden md:block">
) : requireLogin && !isAuthenticated ? ( The output is meeting minutes and topic summaries enabling
<button topic-specific analyses stored in your systems of record. This is
className="mt-4 bg-blue-400 hover:bg-blue-500 focus-visible:bg-blue-500 text-white font-bold py-2 px-4 rounded" accomplished on your infrastructure without 3rd parties
onClick={() => signIn("authentik")} keeping your data private, secure, and organized.
> </Text>
Log in </Text>
</button> <About buttonText="Learn more" />
) : ( <Text mt={6}>
<div className="rounded-xl md:bg-blue-200 md:w-96 p-4 lg:p-6 flex flex-col mb-4 md:mb-10"> In order to use Reflector, we kindly request permission to access
<h2 className="text-2xl font-bold mt-2 mb-2">Try Reflector</h2> your microphone during meetings and events.
<label className="mb-3"> </Text>
<p>Recording name</p> {featureEnabled("privacy") && <Privacy buttonText="Privacy policy" />}
<div className="select-search-container"> </Flex>
<input <Flex flexDir="column" h="full" flexBasis="1" flexGrow={1}>
className="select-search-input" <Center>
type="text" {!sessionReady ? (
onChange={nameChange} <Spinner />
placeholder="Optional" ) : requireLogin && !isAuthenticated ? (
<Button onClick={() => signIn("authentik")} colorScheme="blue">
Log in
</Button>
) : (
<Flex
rounded="xl"
bg="blue.primary"
color="white"
maxW="96"
p={8}
flexDir="column"
my={4}
>
<Heading size="md" mb={4}>
Try Reflector
</Heading>
<Box mb={4}>
<Text>Recording name</Text>
<div className="select-search-container">
<input
className="select-search-input"
type="text"
onChange={nameChange}
placeholder="Optional"
/>
</div>
</Box>
<Box mb={4}>
<Text>Do you want to enable live translation?</Text>
<SelectSearch
search
options={supportedLanguages}
value={targetLanguage}
onChange={onLanguageChange}
placeholder="Choose your language"
/> />
</div> </Box>
</label> {loading ? (
<label className="mb-3"> <Text className="">Checking permissions...</Text>
<p>Do you want to enable live translation?</p> ) : permissionOk ? (
<SelectSearch <Spacer />
search ) : permissionDenied ? (
options={supportedLanguages} <Text className="">
value={targetLanguage} Permission to use your microphone was denied, please change
onChange={onLanguageChange} the permission setting in your browser and refresh this
placeholder="Choose your language" page.
/> </Text>
</label> ) : (
{loading ? ( <Button
<p className="">Checking permissions...</p> colorScheme="whiteAlpha"
) : permissionOk ? ( onClick={requestPermission}
<p className=""> Microphone permission granted </p> disabled={permissionDenied}
) : permissionDenied ? ( >
<p className=""> Request Microphone Permission
Permission to use your microphone was denied, please change </Button>
the permission setting in your browser and refresh this page. )}
</p>
) : (
<Button <Button
colorScheme="blue" colorScheme="whiteAlpha"
onClick={requestPermission} onClick={send}
disabled={permissionDenied} isDisabled={!permissionOk || loadingRecord || loadingUpload}
mt={2}
> >
Request Microphone Permission {loadingRecord ? "Loading..." : "Record Meeting"}
</Button> </Button>
)} <Text align="center" m="2">
<Button OR
colorScheme="blue" </Text>
onClick={send} <Button
isDisabled={!permissionOk || loadingRecord || loadingUpload} colorScheme="whiteAlpha"
mt={2} onClick={uploadFile}
> isDisabled={loadingRecord || loadingUpload}
{loadingRecord ? "Loading..." : "Record Meeting"} >
</Button> {loadingUpload ? "Loading..." : "Upload File"}
<Text align="center" m="2"> </Button>
OR </Flex>
</Text> )}
<Button </Center>
colorScheme="blue" </Flex>
onClick={uploadFile} </Flex>
isDisabled={loadingRecord || loadingUpload} </Flex>
>
{loadingUpload ? "Loading..." : "Upload File"}
</Button>
</div>
)}
</section>
</div>
</div>
); );
}; };

View File

@@ -1,5 +1,4 @@
import "./styles/globals.scss"; import "./styles/globals.scss";
import { Poppins } from "next/font/google";
import { Metadata, Viewport } from "next"; import { Metadata, Viewport } from "next";
import SessionProvider from "./lib/SessionProvider"; import SessionProvider from "./lib/SessionProvider";
import { ErrorProvider } from "./(errors)/errorContext"; import { ErrorProvider } from "./(errors)/errorContext";
@@ -9,8 +8,6 @@ import { getConfig } from "./lib/edgeConfig";
import { ErrorBoundary } from "@sentry/nextjs"; import { ErrorBoundary } from "@sentry/nextjs";
import { Providers } from "./providers"; import { Providers } from "./providers";
const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] });
export const viewport: Viewport = { export const viewport: Viewport = {
themeColor: "black", themeColor: "black",
width: "device-width", width: "device-width",
@@ -68,11 +65,7 @@ export default async function RootLayout({
return ( return (
<html lang="en"> <html lang="en">
<body <body className={"h-[100svh] w-[100svw] overflow-hidden relative"}>
className={
poppins.className + "h-[100svh] w-[100svw] overflow-hidden relative"
}
>
<SessionProvider> <SessionProvider>
<DomainContextProvider config={config}> <DomainContextProvider config={config}>
<ErrorBoundary fallback={<p>"something went really wrong"</p>}> <ErrorBoundary fallback={<p>"something went really wrong"</p>}>

View File

@@ -1,12 +1,16 @@
// 1. Import `extendTheme`
import { extendTheme } from "@chakra-ui/react"; import { extendTheme } from "@chakra-ui/react";
import { Poppins } from "next/font/google";
import { accordionAnatomy } from "@chakra-ui/anatomy"; import { accordionAnatomy } from "@chakra-ui/anatomy";
import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react"; import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react";
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(accordionAnatomy.keys); createMultiStyleConfigHelpers(accordionAnatomy.keys);
const poppins = Poppins({
subsets: ["latin"],
weight: ["200", "400", "600"],
display: "swap",
});
const custom = definePartsStyle({ const custom = definePartsStyle({
container: { container: {
border: "0", border: "0",
@@ -29,6 +33,14 @@ const accordionTheme = defineMultiStyleConfig({
variants: { custom }, variants: { custom },
}); });
const linkTheme = defineStyle({
baseStyle: {
_hover: {
color: "blue.500",
textDecoration: "none",
},
},
});
export const colors = { export const colors = {
blue: { blue: {
primary: "#3158E2", primary: "#3158E2",
@@ -60,6 +72,11 @@ const theme = extendTheme({
colors, colors,
components: { components: {
Accordion: accordionTheme, Accordion: accordionTheme,
Link: linkTheme,
},
fonts: {
body: poppins.style.fontFamily,
heading: poppins.style.fontFamily,
}, },
}); });

23
www/public/reach.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg width="39" height="47" viewBox="0 0 39 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M39 30.5L20 25.5L4 41.5L26 46.5L39 30.5Z" fill="#4A4A4A"/>
<path d="M39 30.5L20 25.5L4 41.5L26 46.5L39 30.5Z" fill="#4A4A4A"/>
<g filter="url(#filter0_d_101_28)">
<path d="M20 4V25.5L4 41.5V16.5L20 4Z" fill="#B6B6B6"/>
<path d="M20 4V25.5L4 41.5V16.5L20 4Z" fill="#B6B6B6"/>
<path d="M20 4V25.5L4 41.5V16.5L20 4Z" fill="#B6B6B6"/>
<path d="M20 4V25.5L4 41.5V16.5L20 4Z" fill="#B6B6B6"/>
</g>
<defs>
<filter id="filter0_d_101_28" x="0" y="0" width="24" height="45.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_101_28"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_101_28" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB