Merge branch 'master' into feat-model-based-on-key

This commit is contained in:
Danny Avila 2023-03-16 13:39:41 -04:00 committed by GitHub
commit 1fe9e29187
27 changed files with 602 additions and 157 deletions

View file

@ -5,34 +5,67 @@ import TextChat from './components/Main/TextChat';
import Nav from './components/Nav';
import MobileNav from './components/Nav/MobileNav';
import useDocumentTitle from '~/hooks/useDocumentTitle';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import { setUser } from './store/userReducer';
import axios from 'axios';
const App = () => {
const dispatch = useDispatch();
const { messages, messageTree } = useSelector((state) => state.messages);
const { user } = useSelector((state) => state.user);
const { title } = useSelector((state) => state.convo);
const { conversationId } = useSelector((state) => state.convo);
const [ navVisible, setNavVisible ]= useState(false)
useDocumentTitle(title);
return (
<div className="flex h-screen">
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white dark:bg-gray-800">
<MobileNav setNavVisible={setNavVisible} />
{messages.length === 0 ? (
<Landing title={title} />
) : (
<Messages
messages={messages}
messageTree={messageTree}
/>
)}
<TextChat messages={messages} />
useEffect(() => {
axios.get('/api/me', {
timeout: 1000,
withCredentials: true
}).then((res) => {
return res.data
}).then((user) => {
if (user)
dispatch(setUser(user))
else {
console.log('Not login!')
window.location.href = "/auth/login";
}
}).catch((error) => {
console.error(error)
console.log('Not login!')
window.location.href = "/auth/login";
})
// setUser
}, [])
if (user)
return (
<div className="flex h-screen">
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white dark:bg-gray-800">
<MobileNav setNavVisible={setNavVisible} />
{messages.length === 0 ? (
<Landing title={title} />
) : (
<Messages
messages={messages}
messageTree={messageTree}
/>
)}
<TextChat messages={messages} />
</div>
</div>
</div>
</div>
);
);
else
return (
<div className="flex h-screen">
</div>
)
};
export default App;

View file

@ -28,7 +28,7 @@ export default function Landing({ title }) {
return (
<div className="flex h-full flex-col items-center overflow-y-auto text-sm dark:bg-gray-800">
<div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl">
<h1 className="mt-6 ml-auto mr-auto mb-10 flex items-center justify-center gap-2 text-center text-4xl font-semibold sm:mt-[20vh] sm:mb-16">
<h1 className="mt-6 ml-auto mr-auto mb-10 flex items-center justify-center gap-2 text-center text-4xl font-semibold md:mt-[20vh] sm:mb-16">
ChatGPT Clone
</h1>
<div className="items-start gap-3.5 text-center md:flex">

View file

@ -10,9 +10,12 @@ export default function SubmitButton({ submitMessage }) {
if (isSubmitting) {
return (
<button className="absolute bottom-0 h-[50px] w-[30px] right-1 rounded-md p-1 text-gray-500 hover:bg-gray-100disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:right-2" disabled>
<button
className="absolute bottom-0 right-1 h-[100%] w-[30px] rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:right-2"
disabled
>
<div className="text-2xl">
<span >·</span>
<span>·</span>
<span className="blink">·</span>
<span className="blink2">·</span>
</div>
@ -23,28 +26,30 @@ export default function SubmitButton({ submitMessage }) {
<button
onClick={clickHandler}
disabled={disabled}
className="absolute bottom-0 flex justify-center items-center h-[50px] w-[50px] right-0 rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent"
className="group absolute bottom-0 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500"
>
<svg
stroke="currentColor"
fill="none"
strokeWidth="2"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-1 h-4 w-4"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="22"
y1="2"
x2="11"
y2="13"
/>
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
<div className="m-1 rounded-md p-2 pt-[10px] pb-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
<svg
stroke="currentColor"
fill="none"
strokeWidth="2"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-1 h-4 w-4 "
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="22"
y1="2"
x2="11"
y2="13"
/>
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
</div>
</button>
);
}

View file

@ -18,6 +18,7 @@ export default function TextChat({ messages }) {
const inputRef = useRef(null)
const isComposing = useRef(false);
const dispatch = useDispatch();
const { user } = useSelector((state) => state.user);
const convo = useSelector((state) => state.convo);
const { initial } = useSelector((state) => state.models);
const { isSubmitting, stopStream, submission, disabled, model, chatGptLabel, promptPrefix } =
@ -309,7 +310,7 @@ export default function TextChat({ messages }) {
<div
className={`relative flex w-full flex-grow flex-col rounded-md border border-black/10 ${
disabled ? 'bg-gray-100' : 'bg-white'
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
} py-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
>
@ -328,7 +329,7 @@ export default function TextChat({ messages }) {
onCompositionEnd={handleCompositionEnd}
placeholder={disabled ? 'Choose another model or customize GPT again' : ''}
disabled={disabled}
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-9 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-12 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
/>
<SubmitButton submitMessage={submitMessage} />
</div>

View file

@ -28,7 +28,7 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
dispatch(setModels(fetchedModels));
});
const icon = getIconOfModel({ size: 16, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" });
const icon = getIconOfModel({ size: 20, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" });
if (value === 'chatgptCustom') {
return (
@ -143,7 +143,7 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
onKeyDown={handleKeyDown}
/>
) : (
<div className="w-3/4 overflow-hidden">{modelInput}</div>
<div className=" overflow-hidden">{modelInput}</div>
)}
{value === 'chatgpt' && <sup>$</sup>}

View file

@ -18,6 +18,7 @@ import { setText } from '~/store/textSlice';
import GPTIcon from '../svg/GPTIcon';
import BingIcon from '../svg/BingIcon';
import { Button } from '../ui/Button.tsx';
import { getIconOfModel } from '../../utils';
import {
DropdownMenu,
@ -34,7 +35,7 @@ export default function ModelMenu() {
const dispatch = useDispatch();
const [modelSave, setModelSave] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const { model, customModel } = useSelector((state) => state.submit);
const { model, customModel, promptPrefix, chatGptLabel } = useSelector((state) => state.submit);
const { models, modelMap, initial } = useSelector((state) => state.models);
const { data, isLoading, mutate } = swr(`/api/customGpts`, (res) => {
const fetchedModels = res.map((modelItem) => ({
@ -176,7 +177,7 @@ export default function ModelMenu() {
const isBing = model === 'bingai' || model === 'sydney';
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
const icon = isBing ? <BingIcon button={true} /> : <GPTIcon button={true} />;
const icon = getIconOfModel({ size: 32, sender: chatGptLabel || model, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, button: true});
return (
<Dialog onOpenChange={onOpenChange}>
@ -188,9 +189,9 @@ export default function ModelMenu() {
<Button
variant="outline"
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
className={`absolute bottom-0.5 rounded-md border-0 p-1 pl-2 outline-none ${colorProps.join(
className={`absolute bottom-0.5 items-center mb-[1.75px] md:mb-0 rounded-md border-0 p-1 ml-1 md:ml-0 outline-none ${colorProps.join(
' '
)} focus:ring-0 focus:ring-offset-0 disabled:bottom-0.5 dark:data-[state=open]:bg-opacity-50 md:bottom-1 md:left-2 md:pl-1 md:disabled:bottom-1`}
)} focus:ring-0 focus:ring-offset-0 disabled:bottom-0.5 dark:data-[state=open]:bg-opacity-50 md:bottom-1 md:left-1 md:pl-1 md:disabled:bottom-1`}
>
{icon}
</Button>

View file

@ -0,0 +1,23 @@
import React, { useState, useContext } from 'react';
import { useSelector } from 'react-redux';
import LogOutIcon from '../svg/LogOutIcon';
export default function Logout() {
const { user } = useSelector((state) => state.user);
const clickHandler = () => {
window.location.href = "/auth/logout";
};
return (
<a
className="flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"
onClick={clickHandler}
>
<LogOutIcon />
{user?.display || user?.username || 'USER'}
<small>Log out</small>
</a>
);
}

View file

@ -3,16 +3,18 @@ import NavLink from './NavLink';
import LogOutIcon from '../svg/LogOutIcon';
import ClearConvos from './ClearConvos';
import DarkMode from './DarkMode';
import Logout from './Logout';
export default function NavLinks() {
return (
<>
<ClearConvos />
<DarkMode />
<NavLink
<Logout />
{/* <NavLink
svg={LogOutIcon}
text="Log out"
/>
/> */}
</>
);
}

View file

@ -5,6 +5,7 @@ import messageReducer from './messageSlice.js'
import modelReducer from './modelSlice.js'
import submitReducer from './submitSlice.js'
import textReducer from './textSlice.js'
import userReducer from './userReducer.js'
export const store = configureStore({
reducer: {
@ -13,6 +14,7 @@ export const store = configureStore({
models: modelReducer,
text: textReducer,
submit: submitReducer,
user: userReducer,
},
devTools: true,
});

View file

@ -0,0 +1,19 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
user: null,
};
const currentSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser: (state, action) => {
state.user = action.payload;
},
}
});
export const { setUser } = currentSlice.actions;
export default currentSlice.reducer;

View file

@ -3,10 +3,10 @@ import axios from 'axios';
import useSWR from 'swr';
import useSWRMutation from 'swr/mutation';
const fetcher = (url) => fetch(url).then((res) => res.json());
const fetcher = (url) => fetch(url, {credentials: 'include'}).then((res) => res.json());
const postRequest = async (url, { arg }) => {
return await axios.post(url, { arg });
return await axios.post(url, { withCredentials: true, arg });
};
export const swr = (path, successCallback, options) => {

View file

@ -47,20 +47,23 @@ export const wrapperRegex = {
};
export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGptLabel, error, ...props }) => {
const { button } = props;
const bgColors = {
chatgpt: 'rgb(16, 163, 127)',
chatgptBrowser: 'rgb(25, 207, 207)',
chatgpt: `rgb(16, 163, 127${ button ? ', 0.75' : ''})`,
chatgptBrowser: `rgb(25, 207, 207${ button ? ', 0.75' : ''})`,
bingai: 'transparent',
sydney: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)',
chatgptCustom: 'rgb(0, 163, 255)',
chatgptCustom: `rgb(0, 163, 255${ button ? ', 0.75' : ''})`,
};
console.log(sender, isCreatedByUser, model, chatGptLabel, error, )
if (isCreatedByUser)
return (
<div
title='User'
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12 }}
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12, width: size, height: size }}
className={`relative flex items-center justify-center rounded-sm text-white ` + props?.className}
>
User
</div>
@ -75,11 +78,11 @@ export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGp
<div
title={chatGptLabel || model}
style={
{ background } || { background: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)' }
{ background: background || 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)', width: size, height: size }
}
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
className={`relative flex items-center justify-center rounded-sm text-white ` + props?.className}
>
{isBing ? <BingIcon size={size} /> : <GPTIcon size={size} />}
{isBing ? <BingIcon size={size * 0.7} /> : <GPTIcon size={size * 0.7} />}
{error && (
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
!
@ -91,8 +94,8 @@ export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGp
return (
<div
title='User'
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12 }}
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12, width: size, height: size }}
className={`relative flex items-center justify-center rounded-sm p-1 text-white ` + props?.className}
>
{chatGptLabel}
</div>