121 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			121 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								import styled from '@emotion/styled';
							 | 
						||
| 
								 | 
							
								import Helmet from 'react-helmet';
							 | 
						||
| 
								 | 
							
								import { useSpotlight } from '@mantine/spotlight';
							 | 
						||
| 
								 | 
							
								import { Button, ButtonProps, TextInput } from '@mantine/core';
							 | 
						||
| 
								 | 
							
								import { useCallback, useState } from 'react';
							 | 
						||
| 
								 | 
							
								import { useNavigate } from 'react-router-dom';
							 | 
						||
| 
								 | 
							
								import { APP_NAME } from '../values';
							 | 
						||
| 
								 | 
							
								import { useAppContext } from '../context';
							 | 
						||
| 
								 | 
							
								import { backend } from '../backend';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const Container = styled.div`
							 | 
						||
| 
								 | 
							
								    display: flex;
							 | 
						||
| 
								 | 
							
								    align-items: center;
							 | 
						||
| 
								 | 
							
								    gap: 0.5rem;
							 | 
						||
| 
								 | 
							
								    padding: 0.5rem 1rem;
							 | 
						||
| 
								 | 
							
								    min-height: 2.618rem;
							 | 
						||
| 
								 | 
							
								    background: rgba(0, 0, 0, 0.2);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    h1 {
							 | 
						||
| 
								 | 
							
								        @media (max-width: 40em) {
							 | 
						||
| 
								 | 
							
								            width: 100%;
							 | 
						||
| 
								 | 
							
								            order: -1;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        font-family: "Work Sans", sans-serif;
							 | 
						||
| 
								 | 
							
								        font-size: 1rem;
							 | 
						||
| 
								 | 
							
								        line-height: 1.3;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        animation: fadein 0.5s;
							 | 
						||
| 
								 | 
							
								        animation-fill-mode: forwards;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        strong {
							 | 
						||
| 
								 | 
							
								            font-weight: bold;
							 | 
						||
| 
								 | 
							
								            white-space: nowrap;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        span {
							 | 
						||
| 
								 | 
							
								            display: block;
							 | 
						||
| 
								 | 
							
								            font-size: 70%;
							 | 
						||
| 
								 | 
							
								            white-space: nowrap;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        @keyframes fadein {
							 | 
						||
| 
								 | 
							
								            from { opacity: 0; }
							 | 
						||
| 
								 | 
							
								            to   { opacity: 1; }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .spacer {
							 | 
						||
| 
								 | 
							
								        @media (min-width: 40em) {
							 | 
						||
| 
								 | 
							
								            flex-grow: 1;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    i {
							 | 
						||
| 
								 | 
							
								        font-size: 90%;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    i + span {
							 | 
						||
| 
								 | 
							
								        @media (max-width: 40em) {
							 | 
						||
| 
								 | 
							
								            position: absolute;
							 | 
						||
| 
								 | 
							
								            left: -9999px;
							 | 
						||
| 
								 | 
							
								            top: -9999px;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								`;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function HeaderButton(props: ButtonProps & { icon?: string, onClick?: any, children?: any }) {
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								        <Button size='xs'
							 | 
						||
| 
								 | 
							
								                variant={props.variant || 'subtle'}
							 | 
						||
| 
								 | 
							
								                onClick={props.onClick}>
							 | 
						||
| 
								 | 
							
								            {props.icon && <i className={'fa fa-' + props.icon} />}
							 | 
						||
| 
								 | 
							
								            {props.children && <span>
							 | 
						||
| 
								 | 
							
								                {props.children}
							 | 
						||
| 
								 | 
							
								            </span>}
							 | 
						||
| 
								 | 
							
								        </Button>
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export default function Header(props: { title?: any, onShare?: () => void, share?: boolean, canShare?: boolean }) {
							 | 
						||
| 
								 | 
							
								    const context = useAppContext();
							 | 
						||
| 
								 | 
							
								    const navigate = useNavigate();
							 | 
						||
| 
								 | 
							
								    const spotlight = useSpotlight();
							 | 
						||
| 
								 | 
							
								    const [loading, setLoading] = useState(false);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const onNewChat = useCallback(async () => {
							 | 
						||
| 
								 | 
							
								        setLoading(true);
							 | 
						||
| 
								 | 
							
								        navigate(`/`);
							 | 
						||
| 
								 | 
							
								        setLoading(false);
							 | 
						||
| 
								 | 
							
								    }, [navigate]);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const openSettings = useCallback(() => {
							 | 
						||
| 
								 | 
							
								        context.settings.open(context.apiKeys.openai ? 'options' : 'user');
							 | 
						||
| 
								 | 
							
								    }, [context, context.apiKeys.openai]);
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    return <Container>
							 | 
						||
| 
								 | 
							
								        <Helmet>
							 | 
						||
| 
								 | 
							
								            <title>{props.title ? `${props.title} - ` : ''}{APP_NAME} - Unofficial ChatGPT app</title>
							 | 
						||
| 
								 | 
							
								        </Helmet>
							 | 
						||
| 
								 | 
							
								        {props.title && <h1>{props.title}</h1>}
							 | 
						||
| 
								 | 
							
								        {!props.title && (<h1>
							 | 
						||
| 
								 | 
							
								            <div>
							 | 
						||
| 
								 | 
							
								                <strong>{APP_NAME}</strong><br />
							 | 
						||
| 
								 | 
							
								                <span>An unofficial ChatGPT app</span>
							 | 
						||
| 
								 | 
							
								            </div>
							 | 
						||
| 
								 | 
							
								        </h1>)}
							 | 
						||
| 
								 | 
							
								        <div className="spacer" />
							 | 
						||
| 
								 | 
							
								        <HeaderButton icon="search" onClick={spotlight.openSpotlight} />
							 | 
						||
| 
								 | 
							
								        <HeaderButton icon="gear" onClick={openSettings} />
							 | 
						||
| 
								 | 
							
								        {backend && !props.share && props.canShare && typeof navigator.share !== 'undefined' && <HeaderButton icon="share" onClick={props.onShare}>
							 | 
						||
| 
								 | 
							
								            Share
							 | 
						||
| 
								 | 
							
								        </HeaderButton>}
							 | 
						||
| 
								 | 
							
								        {backend && !context.authenticated && (
							 | 
						||
| 
								 | 
							
								            <HeaderButton onClick={() => backend?.signIn()}>Sign in to sync</HeaderButton>
							 | 
						||
| 
								 | 
							
								        )}
							 | 
						||
| 
								 | 
							
								        <HeaderButton icon="plus" onClick={onNewChat} loading={loading} variant="light">
							 | 
						||
| 
								 | 
							
								            New Chat
							 | 
						||
| 
								 | 
							
								        </HeaderButton>
							 | 
						||
| 
								 | 
							
								    </Container>;
							 | 
						||
| 
								 | 
							
								}
							 |