Merge branch 'main' of https://github.com/cogentapps/chat-with-gpt
commit
bf9ab7d97e
|
@ -49,7 +49,7 @@
|
|||
"build": "GENERATE_SOURCEMAP=false craco build",
|
||||
"test": "craco test",
|
||||
"eject": "craco eject",
|
||||
"extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file public/lang/en-us.json --format simple --id-interpolation-pattern '[sha512:contenthash:base64:6]'"
|
||||
"extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file public/lang/en-us.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
|
|
@ -1,44 +1,222 @@
|
|||
{
|
||||
"+G35mR": "Open sidebar",
|
||||
"/OKZrc": "Find your API key here.",
|
||||
"3T9nRn": "Your API key is stored only on this device and never transmitted to anyone except OpenAI.",
|
||||
"47FYwb": "Cancel",
|
||||
"4l6vz1": "Copy",
|
||||
"6PgVSe": "Regenerate",
|
||||
"A4iXFN": "Temperature: {temperature, number, ::.0}",
|
||||
"BdPrnc": "Chat with GPT - Unofficial ChatGPT app",
|
||||
"BwIZY+": "System Prompt",
|
||||
"ECx3EW": "Chat with GPT",
|
||||
"ExZfjk": "Sign in <h>to sync</h>",
|
||||
"HIqSlE": "Preview voice",
|
||||
"HyS0qp": "Close sidebar",
|
||||
"J3ca41": "Play",
|
||||
"KKa5Br": "Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a>Click here to sign up.</a>",
|
||||
"KbaJTs": "Loading audio...",
|
||||
"L5s+z7": "OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription.",
|
||||
"NRJ4IQ": "Note: GPT-4 will only work if your OpenAI account has been granted access to the new model. <a>Request access here.</a>",
|
||||
"O83lC6": "Enter a message here...",
|
||||
"OKhRC6": "Share",
|
||||
"Q97T+z": "Paste your API key here",
|
||||
"UT7Nkj": "New Chat",
|
||||
"Ua8luY": "Hello, how can I help you today?",
|
||||
"VL24Xt": "Search your chats",
|
||||
"X0ha1a": "Save changes",
|
||||
"Xzm66E": "Connect your OpenAI account to get started",
|
||||
"c60o5M": "Your OpenAI API Key",
|
||||
"jkpK/t": "Your ElevenLabs Text-to-Speech API Key (optional)",
|
||||
"jtu3jt": "You can find your API key by clicking your avatar or initials in the top right of the ElevenLabs website, then clicking Profile. Your API key is stored only on this device and never transmitted to anyone except ElevenLabs.",
|
||||
"mhtiX2": "Customize system prompt",
|
||||
"mnJYBQ": "Voice",
|
||||
"p556q3": "Copied",
|
||||
"q/uwLT": "Stop",
|
||||
"rhSI1/": "Model",
|
||||
"role-chatgpt": "ChatGPT",
|
||||
"role-system": "System",
|
||||
"role-user": "You",
|
||||
"role-user-formal": "User",
|
||||
"sPtnbA": "The System Prompt is shown to ChatGPT by the "System" before your first message. The <code>'{{ datetime }}'</code> tag is automatically replaced by the current date and time.",
|
||||
"ss6kle": "Reset to default",
|
||||
"tZdXp/": "The temperature parameter controls the randomness of the AI's responses. Lower values will make the AI more predictable, while higher values will make it more creative.",
|
||||
"wEQDC6": "Edit"
|
||||
}
|
||||
"+G35mR": {
|
||||
"defaultMessage": "Open sidebar"
|
||||
},
|
||||
"+LMWDJ": {
|
||||
"defaultMessage": "Chat History",
|
||||
"description": "Heading for the chat history screen"
|
||||
},
|
||||
"/xTTti": {
|
||||
"defaultMessage": "User settings",
|
||||
"description": "Menu item that opens the user settings screen"
|
||||
},
|
||||
"0vL5u1": {
|
||||
"defaultMessage": "Create an account"
|
||||
},
|
||||
"1j61Mn": {
|
||||
"defaultMessage": "Or create an account",
|
||||
"description": "Label for a button on the Sign In page that lets the user create an account instead"
|
||||
},
|
||||
"1r/ryM": {
|
||||
"defaultMessage": "Cancel",
|
||||
"description": "Label for the button that can be clicked while the AI is generating a response to cancel generation"
|
||||
},
|
||||
"2GFjIN": {
|
||||
"defaultMessage": "Enter your password"
|
||||
},
|
||||
"3T9nRn": {
|
||||
"defaultMessage": "Your API key is stored only on this device and never transmitted to anyone except OpenAI."
|
||||
},
|
||||
"4I+enA": {
|
||||
"defaultMessage": "GPT 4 (requires invite)"
|
||||
},
|
||||
"5sg7KC": {
|
||||
"defaultMessage": "Password"
|
||||
},
|
||||
"6LJByb": {
|
||||
"defaultMessage": "Loading audio...",
|
||||
"description": "Message indicating that text-to-speech audio is buffering"
|
||||
},
|
||||
"74eGxP": {
|
||||
"defaultMessage": "New Chat",
|
||||
"description": "Label for the button used to start a new chat session"
|
||||
},
|
||||
"8CYNFt": {
|
||||
"defaultMessage": "No chats yet.",
|
||||
"description": "Message shown on the Chat History screen for new users who haven't started their first chat session"
|
||||
},
|
||||
"8HJxXG": {
|
||||
"defaultMessage": "Sign up"
|
||||
},
|
||||
"Bi0lS9": {
|
||||
"defaultMessage": "Share",
|
||||
"description": "Label for the button used to create a public share URL for a chat log"
|
||||
},
|
||||
"Bm3EKs": {
|
||||
"defaultMessage": "Save changes",
|
||||
"description": "Label for a button that appears when the user is editing the text of one of their messages, to save the changes"
|
||||
},
|
||||
"CJwO9s": {
|
||||
"defaultMessage": "GPT 3.5 Turbo (default)"
|
||||
},
|
||||
"FEzBCd": {
|
||||
"defaultMessage": "Untitled",
|
||||
"description": "default title for untitled chat sessions"
|
||||
},
|
||||
"Fko0yT": {
|
||||
"defaultMessage": "Stop",
|
||||
"description": "Label for the button that stops text-to-speech playback"
|
||||
},
|
||||
"HyS0qp": {
|
||||
"defaultMessage": "Close sidebar"
|
||||
},
|
||||
"JpZMMj": {
|
||||
"defaultMessage": "Voice",
|
||||
"description": "Heading for the setting that lets users choose an ElevenLabs text-to-speech voice, on the settings screen"
|
||||
},
|
||||
"KKa5Br": {
|
||||
"defaultMessage": "Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a>Click here to sign up.</a>"
|
||||
},
|
||||
"L5s+z7": {
|
||||
"defaultMessage": "OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription."
|
||||
},
|
||||
"MI5gZ+": {
|
||||
"defaultMessage": "Download SVG"
|
||||
},
|
||||
"N2NGTf": {
|
||||
"defaultMessage": "Chat with GPT",
|
||||
"description": "app name"
|
||||
},
|
||||
"NRJ4IQ": {
|
||||
"defaultMessage": "Note: GPT-4 will only work if your OpenAI account has been granted access to the new model. <a>Request access here.</a>"
|
||||
},
|
||||
"NgCT/u": {
|
||||
"defaultMessage": "Enter your email address"
|
||||
},
|
||||
"O83lC6": {
|
||||
"defaultMessage": "Enter a message here..."
|
||||
},
|
||||
"Q97T+z": {
|
||||
"defaultMessage": "Paste your API key here"
|
||||
},
|
||||
"S9BFby": {
|
||||
"defaultMessage": "Play",
|
||||
"description": "Label for the button that starts text-to-speech playback"
|
||||
},
|
||||
"SQJto2": {
|
||||
"defaultMessage": "Sign in"
|
||||
},
|
||||
"SQh9En": {
|
||||
"defaultMessage": "Sign in <h>to sync</h>",
|
||||
"description": "Label for sign in button, indicating the purpose of signing in is to sync your data between devices"
|
||||
},
|
||||
"Tgo3vj": {
|
||||
"defaultMessage": "Edit",
|
||||
"description": "Label for the button the user can click to edit the text of one of their messages"
|
||||
},
|
||||
"VL24Xt": {
|
||||
"defaultMessage": "Search your chats"
|
||||
},
|
||||
"WOuVxP": {
|
||||
"defaultMessage": "Model",
|
||||
"description": "Heading for the setting that lets users choose a model to interact with, on the settings screen"
|
||||
},
|
||||
"Xzm66E": {
|
||||
"defaultMessage": "Connect your OpenAI account to get started"
|
||||
},
|
||||
"Y0tGn6": {
|
||||
"defaultMessage": "Or sign in to an existing account",
|
||||
"description": "Label for a button on the Create Account page that lets the user sign into their existing account instead"
|
||||
},
|
||||
"bIacvz": {
|
||||
"defaultMessage": "Chat with GPT - Unofficial ChatGPT app",
|
||||
"description": "HTML title tag"
|
||||
},
|
||||
"cAtzqn": {
|
||||
"defaultMessage": "System Prompt",
|
||||
"description": "Heading for the setting that lets users customize the System Prompt, on the settings screen"
|
||||
},
|
||||
"cmcjSh": {
|
||||
"defaultMessage": "Preview voice",
|
||||
"description": "Label for the button that plays a preview of the selected ElevenLabs text-to-speech voice"
|
||||
},
|
||||
"f/hGIY": {
|
||||
"defaultMessage": "Hello, how can I help you today?",
|
||||
"description": "A friendly message that appears at the start of new chat sessions"
|
||||
},
|
||||
"gzJlXS": {
|
||||
"defaultMessage": "Share",
|
||||
"description": "Label for a button which shares the text of a chat message using the user device's share functionality"
|
||||
},
|
||||
"hJZwTS": {
|
||||
"defaultMessage": "Email address"
|
||||
},
|
||||
"iqbKb2": {
|
||||
"defaultMessage": "Save and Close",
|
||||
"description": "Label for the button that closes the Settings screen, saving any changes"
|
||||
},
|
||||
"jtu3jt": {
|
||||
"defaultMessage": "You can find your API key by clicking your avatar or initials in the top right of the ElevenLabs website, then clicking Profile. Your API key is stored only on this device and never transmitted to anyone except ElevenLabs."
|
||||
},
|
||||
"ljHOzQ": {
|
||||
"defaultMessage": "Copied",
|
||||
"description": "Label for copy-to-clipboard button after a successful copy"
|
||||
},
|
||||
"pv41j8": {
|
||||
"defaultMessage": "Temperature: {temperature, number, ::.0}",
|
||||
"description": "Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses"
|
||||
},
|
||||
"raQMIg": {
|
||||
"defaultMessage": "Cancel",
|
||||
"description": "Label for a button that appears when the user is editing the text of one of their messages, to cancel without saving changes"
|
||||
},
|
||||
"role-chatgpt": {
|
||||
"defaultMessage": "ChatGPT",
|
||||
"description": "Label that is shown above messages written by the AI (as opposed to the user)"
|
||||
},
|
||||
"role-system": {
|
||||
"defaultMessage": "System",
|
||||
"description": "Label that is shown above messages inserted into the conversation automatically by the system (as opposed to either the user or AI)"
|
||||
},
|
||||
"role-user": {
|
||||
"defaultMessage": "You",
|
||||
"description": "Label that is shown above messages written by the user (as opposed to the AI) in the user's own chat sessions (first person)."
|
||||
},
|
||||
"role-user-formal": {
|
||||
"defaultMessage": "User",
|
||||
"description": "Label that is shown above messages written by the user (as opposed to the AI) for publicly shared conversation (third person, formal)."
|
||||
},
|
||||
"sPtnbA": {
|
||||
"defaultMessage": "The System Prompt is shown to ChatGPT by the "System" before your first message. The <code>'{{ datetime }}'</code> tag is automatically replaced by the current date and time."
|
||||
},
|
||||
"ss6kle": {
|
||||
"defaultMessage": "Reset to default"
|
||||
},
|
||||
"sskUPZ": {
|
||||
"defaultMessage": "Your ElevenLabs Text-to-Speech API Key (optional)",
|
||||
"description": "Heading for the ElevenLabs API key setting on the settings screen"
|
||||
},
|
||||
"tZdXp/": {
|
||||
"defaultMessage": "The temperature parameter controls the randomness of the AI's responses. Lower values will make the AI more predictable, while higher values will make it more creative."
|
||||
},
|
||||
"upBSoW": {
|
||||
"defaultMessage": "Copy",
|
||||
"description": "Label for copy-to-clipboard button"
|
||||
},
|
||||
"w5Dmuu": {
|
||||
"defaultMessage": "Customize system prompt",
|
||||
"description": "Label for the button that opens a modal for customizing the 'system prompt', a message used to customize and influence how the AI responds."
|
||||
},
|
||||
"y1F8Hs": {
|
||||
"defaultMessage": "Your OpenAI API Key",
|
||||
"description": "Heading for the OpenAI API key setting on the settings screen"
|
||||
},
|
||||
"zBmup+": {
|
||||
"defaultMessage": "Regenerate",
|
||||
"description": "Label for the button used to ask the AI to regenerate one of its messages. Since message generations are stochastic, the resulting message will be different."
|
||||
},
|
||||
"zFt1cV": {
|
||||
"defaultMessage": "Find your API key here.",
|
||||
"description": "Label for the link that takes the user to the page on the OpenAI website where they can find their API key."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import styled from "@emotion/styled";
|
||||
import { Button, Modal, PasswordInput, TextInput } from "@mantine/core";
|
||||
import { useCallback, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useAppDispatch, useAppSelector } from "../../store";
|
||||
import { closeModals, openLoginModal, openSignupModal, selectModal } from "../../store/ui";
|
||||
|
||||
|
@ -35,6 +36,7 @@ const Container = styled.form`
|
|||
export function LoginModal(props: any) {
|
||||
const modal = useAppSelector(selectModal);
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const onClose = useCallback(() => dispatch(closeModals()), [dispatch]);
|
||||
const onCreateAccountClick = useCallback(() => dispatch(openSignupModal()), [dispatch]);
|
||||
|
@ -42,24 +44,26 @@ export function LoginModal(props: any) {
|
|||
return <Modal opened={modal === 'login'} onClose={onClose} withCloseButton={false}>
|
||||
<Container action="/chatapi/login" method="post">
|
||||
<h2>
|
||||
Sign in
|
||||
<FormattedMessage defaultMessage={"Sign in"} />
|
||||
</h2>
|
||||
<input type="hidden" name="redirect_url" value={window.location.href} />
|
||||
<TextInput label="Email address"
|
||||
<TextInput
|
||||
label={intl.formatMessage({ defaultMessage: "Email address" })}
|
||||
name="username"
|
||||
placeholder="Enter your email address"
|
||||
placeholder={intl.formatMessage({ defaultMessage: "Enter your email address" })}
|
||||
type="email"
|
||||
required />
|
||||
<PasswordInput label="Password"
|
||||
<PasswordInput
|
||||
label={intl.formatMessage({ defaultMessage: "Password" })}
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
placeholder={intl.formatMessage({ defaultMessage: "Enter your password" })}
|
||||
maxLength={500}
|
||||
required />
|
||||
<Button fullWidth type="submit">
|
||||
Sign in
|
||||
<FormattedMessage defaultMessage={"Sign in"} />
|
||||
</Button>
|
||||
<Button fullWidth variant="subtle" onClick={onCreateAccountClick}>
|
||||
Or create an account
|
||||
<FormattedMessage defaultMessage={"Or create an account"} description="Label for a button on the Sign In page that lets the user create an account instead" />
|
||||
</Button>
|
||||
</Container>
|
||||
</Modal>
|
||||
|
@ -68,6 +72,7 @@ export function LoginModal(props: any) {
|
|||
export function CreateAccountModal(props: any) {
|
||||
const modal = useAppSelector(selectModal);
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const onClose = useCallback(() => dispatch(closeModals()), [dispatch]);
|
||||
const onSignInClick = useCallback(() => dispatch(openLoginModal()), [dispatch]);
|
||||
|
@ -75,25 +80,27 @@ export function CreateAccountModal(props: any) {
|
|||
return <Modal opened={modal === 'signup'} onClose={onClose} withCloseButton={false}>
|
||||
<Container action="/chatapi/register" method="post">
|
||||
<h2>
|
||||
Create an account
|
||||
<FormattedMessage defaultMessage={"Create an account"} />
|
||||
</h2>
|
||||
<input type="hidden" name="redirect_url" value={window.location.href} />
|
||||
<TextInput label="Email address"
|
||||
<TextInput
|
||||
label={intl.formatMessage({ defaultMessage: "Email address" })}
|
||||
name="username"
|
||||
placeholder="Enter your email address"
|
||||
placeholder={intl.formatMessage({ defaultMessage: "Enter your email address" })}
|
||||
type="email"
|
||||
required />
|
||||
<PasswordInput label="Password"
|
||||
<PasswordInput
|
||||
label={intl.formatMessage({ defaultMessage: "Password" })}
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
placeholder={intl.formatMessage({ defaultMessage: "Enter your password" })}
|
||||
minLength={6}
|
||||
maxLength={500}
|
||||
required />
|
||||
<Button fullWidth type="submit">
|
||||
Sign up
|
||||
<FormattedMessage defaultMessage={"Sign up"} />
|
||||
</Button>
|
||||
<Button fullWidth variant="subtle" onClick={onSignInClick}>
|
||||
Or sign in to an existing account
|
||||
<FormattedMessage defaultMessage={"Or sign in to an existing account"} description="Label for a button on the Create Account page that lets the user sign into their existing account instead" />
|
||||
</Button>
|
||||
</Container>
|
||||
</Modal>
|
||||
|
|
|
@ -160,16 +160,16 @@ export default function Header(props: HeaderProps) {
|
|||
<Helmet>
|
||||
<title>
|
||||
{props.title ? `${props.title} - ` : ''}
|
||||
{intl.formatMessage({ defaultMessage: "Chat with GPT - Unofficial ChatGPT app" })}
|
||||
{intl.formatMessage({ defaultMessage: "Chat with GPT - Unofficial ChatGPT app", description: "HTML title tag" })}
|
||||
</title>
|
||||
</Helmet>
|
||||
{!sidebarOpen && <Burger opened={sidebarOpen} onClick={onBurgerClick} aria-label={burgerLabel} transitionDuration={0} />}
|
||||
{context.isHome && <h2>{intl.formatMessage({ defaultMessage: "Chat with GPT" })}</h2>}
|
||||
{context.isHome && <h2>{intl.formatMessage({ defaultMessage: "Chat with GPT", description: "app name" })}</h2>}
|
||||
<div className="spacer" />
|
||||
<HeaderButton icon="search" onClick={spotlight.openSpotlight} />
|
||||
<HeaderButton icon="gear" onClick={openSettings} />
|
||||
{backend.current && !props.share && props.canShare && typeof navigator.share !== 'undefined' && <HeaderButton icon="share" onClick={props.onShare}>
|
||||
<FormattedMessage defaultMessage="Share" />
|
||||
<FormattedMessage defaultMessage="Share" description="Label for the button used to create a public share URL for a chat log" />
|
||||
</HeaderButton>}
|
||||
{backend.current && !context.authenticated && (
|
||||
<HeaderButton onClick={() => {
|
||||
|
@ -180,13 +180,14 @@ export default function Header(props: HeaderProps) {
|
|||
}
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Sign in <h>to sync</h>"
|
||||
description="Label for sign in button, indicating the purpose of signing in is to sync your data between devices"
|
||||
values={{
|
||||
h: (chunks: any) => <span className="hide-on-mobile">{chunks}</span>
|
||||
}} />
|
||||
</HeaderButton>
|
||||
)}
|
||||
<HeaderButton icon="plus" onClick={onNewChat} loading={loading} variant="light">
|
||||
<FormattedMessage defaultMessage="New Chat" />
|
||||
<FormattedMessage defaultMessage="New Chat" description="Label for the button used to start a new chat session" />
|
||||
</HeaderButton>
|
||||
</HeaderContainer>
|
||||
), [sidebarOpen, onBurgerClick, props.title, props.share, props.canShare, props.onShare, openSettings, onNewChat, loading, context.authenticated, context.isHome, context.isShare, spotlight.openSpotlight]);
|
||||
|
|
|
@ -79,7 +79,7 @@ export default function MessageInput(props: MessageInputProps) {
|
|||
<Button variant="subtle" size="xs" compact onClick={() => {
|
||||
context.chat.cancelReply(context.currentChat.leaf!.id);
|
||||
}}>
|
||||
<FormattedMessage defaultMessage={"Cancel"} />
|
||||
<FormattedMessage defaultMessage={"Cancel"} description="Label for the button that can be clicked while the AI is generating a response to cancel generation" />
|
||||
</Button>
|
||||
<Loader size="xs" style={{ padding: '0 0.8rem 0 0.5rem' }} />
|
||||
</>)}
|
||||
|
@ -119,7 +119,7 @@ export default function MessageInput(props: MessageInputProps) {
|
|||
compact
|
||||
onClick={onCustomizeSystemPromptClick}>
|
||||
<span>
|
||||
<FormattedMessage defaultMessage={"Customize system prompt"} />
|
||||
<FormattedMessage defaultMessage={"Customize system prompt"} description="Label for the button that opens a modal for customizing the 'system prompt', a message used to customize and influence how the AI responds." />
|
||||
</span>
|
||||
</Button>
|
||||
<Button variant="subtle"
|
||||
|
@ -129,6 +129,7 @@ export default function MessageInput(props: MessageInputProps) {
|
|||
onClick={onTemperatureClick}>
|
||||
<span>
|
||||
<FormattedMessage defaultMessage="Temperature: {temperature, number, ::.0}"
|
||||
description="Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses"
|
||||
values={{ temperature }} />
|
||||
</span>
|
||||
</Button>
|
||||
|
|
|
@ -97,7 +97,10 @@ export function Markdown(props: MarkdownProps) {
|
|||
{({ copy, copied }) => (
|
||||
<Button variant="subtle" size="sm" compact onClick={copy}>
|
||||
<i className="fa fa-clipboard" />
|
||||
<span>{copied ? <FormattedMessage defaultMessage="Copied" /> : <FormattedMessage defaultMessage="Copy" />}</span>
|
||||
<span>
|
||||
{copied ? <FormattedMessage defaultMessage="Copied" description="Label for copy-to-clipboard button after a successful copy" />
|
||||
: <FormattedMessage defaultMessage="Copy" description="Label for copy-to-clipboard button" />}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</CopyButton>
|
||||
|
|
|
@ -208,15 +208,15 @@ export default function MessageComponent(props: { message: Message, last: boolea
|
|||
switch (role) {
|
||||
case 'user':
|
||||
if (share) {
|
||||
return intl.formatMessage({ id: 'role-user-formal', defaultMessage: 'User' });
|
||||
return intl.formatMessage({ id: 'role-user-formal', defaultMessage: 'User', description: "Label that is shown above messages written by the user (as opposed to the AI) for publicly shared conversation (third person, formal)." });
|
||||
} else {
|
||||
return intl.formatMessage({ id: 'role-user', defaultMessage: 'You' });
|
||||
return intl.formatMessage({ id: 'role-user', defaultMessage: 'You', description: "Label that is shown above messages written by the user (as opposed to the AI) in the user's own chat sessions (first person)." });
|
||||
}
|
||||
break;
|
||||
case 'assistant':
|
||||
return intl.formatMessage({ id: 'role-chatgpt', defaultMessage: 'ChatGPT' });
|
||||
return intl.formatMessage({ id: 'role-chatgpt', defaultMessage: 'ChatGPT', description: "Label that is shown above messages written by the AI (as opposed to the user)" });
|
||||
case 'system':
|
||||
return intl.formatMessage({ id: 'role-system', defaultMessage: 'System' });
|
||||
return intl.formatMessage({ id: 'role-system', defaultMessage: 'System', description: "Label that is shown above messages inserted into the conversation automatically by the system (as opposed to either the user or AI)" });
|
||||
default:
|
||||
return role;
|
||||
}
|
||||
|
@ -243,7 +243,8 @@ export default function MessageComponent(props: { message: Message, last: boolea
|
|||
{({ copy, copied }) => (
|
||||
<Button variant="subtle" size="sm" compact onClick={copy} style={{ marginLeft: '1rem' }}>
|
||||
<i className="fa fa-clipboard" />
|
||||
<span>{copied ? <FormattedMessage defaultMessage="Copied" /> : <FormattedMessage defaultMessage="Copy" />}</span>
|
||||
{copied ? <FormattedMessage defaultMessage="Copied" description="Label for copy-to-clipboard button after a successful copy" />
|
||||
: <FormattedMessage defaultMessage="Copy" description="Label for copy-to-clipboard button" />}
|
||||
</Button>
|
||||
)}
|
||||
</CopyButton>
|
||||
|
@ -251,7 +252,7 @@ export default function MessageComponent(props: { message: Message, last: boolea
|
|||
<Button variant="subtle" size="sm" compact onClick={() => share(props.message.content)}>
|
||||
<i className="fa fa-share" />
|
||||
<span>
|
||||
<FormattedMessage defaultMessage="Share" />
|
||||
<FormattedMessage defaultMessage="Share" description="Label for a button which shares the text of a chat message using the user device's share functionality" />
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
|
@ -261,14 +262,17 @@ export default function MessageComponent(props: { message: Message, last: boolea
|
|||
setEditing(v => !v);
|
||||
}}>
|
||||
<i className="fa fa-edit" />
|
||||
<span>{editing ? <FormattedMessage defaultMessage="Cancel" /> : <FormattedMessage defaultMessage="Edit" />}</span>
|
||||
<span>
|
||||
{editing ? <FormattedMessage defaultMessage="Cancel" description="Label for a button that appears when the user is editing the text of one of their messages, to cancel without saving changes" />
|
||||
: <FormattedMessage defaultMessage="Edit" description="Label for the button the user can click to edit the text of one of their messages" />}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
{!context.isShare && props.message.role === 'assistant' && (
|
||||
<Button variant="subtle" size="sm" compact onClick={() => context.regenerateMessage(props.message)}>
|
||||
<i className="fa fa-refresh" />
|
||||
<span>
|
||||
<FormattedMessage defaultMessage="Regenerate" />
|
||||
<FormattedMessage defaultMessage="Regenerate" description="Label for the button used to ask the AI to regenerate one of its messages. Since message generations are stochastic, the resulting message will be different." />
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
|
@ -279,10 +283,10 @@ export default function MessageComponent(props: { message: Message, last: boolea
|
|||
onChange={e => setContent(e.currentTarget.value)}
|
||||
autosize={true} />
|
||||
<Button variant="light" onClick={() => context.editMessage(props.message, content)}>
|
||||
<FormattedMessage defaultMessage="Save changes" />
|
||||
<FormattedMessage defaultMessage="Save changes" description="Label for a button that appears when the user is editing the text of one of their messages, to save the changes" />
|
||||
</Button>
|
||||
<Button variant="subtle" onClick={() => setEditing(false)}>
|
||||
<FormattedMessage defaultMessage="Cancel" />
|
||||
<FormattedMessage defaultMessage="Cancel" description="Label for a button that appears when the user is editing the text of one of their messages, to cancel without saving changes" />
|
||||
</Button>
|
||||
</Editor>)}
|
||||
</div>
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import styled from "@emotion/styled";
|
||||
import { Markdown } from "../markdown";
|
||||
import { Page } from "../page";
|
||||
|
||||
const title = "Learn about Chat with GPT";
|
||||
|
||||
const content = `
|
||||
# About Chat with GPT
|
||||
|
||||
Chat with GPT is an open-source, unofficial ChatGPT app with extra features and more ways to customize your experience.
|
||||
|
||||
ChatGPT is an AI assistant developed by OpenAI. It's designed to understand natural language and generate human-like responses to a wide range of questions and prompts. ChatGPT has been trained on a massive dataset of text from the internet, which allows it to draw on a vast amount of knowledge and information to answer questions and engage in conversation. ChatGPT is constantly being improved. Feel free to ask it anything!
|
||||
|
||||
[Join the Discord.](https://discord.gg/mS5QvKykvv)
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 **Fast** response times.
|
||||
- 🔎 **Search** through your past chat conversations.
|
||||
- 📄 View and customize the System Prompt - the **secret prompt** the system shows the AI before your messages.
|
||||
- 🌡 Adjust the **creativity and randomness** of responses by setting the Temperature setting. Higher temperature means more creativity.
|
||||
- 💬 Give ChatGPT AI a **realistic human voice** by connecting your ElevenLabs text-to-speech account.
|
||||
- ✉ **Share** your favorite chat sessions online using public share URLs.
|
||||
- 📋 Easily **copy-and-paste** ChatGPT messages.
|
||||
- 🖼 **Full markdown support** including code, tables, and math.
|
||||
- 🫰 Pay for only what you use with the ChatGPT API.
|
||||
|
||||
## Bring your own API keys
|
||||
|
||||
### OpenAI
|
||||
|
||||
To get started with Chat with GPT, you will need to add your OpenAI API key on the settings screen. Click "Connect your OpenAI account to get started" on the home page to begin. Once you have added your API key, you can start chatting with ChatGPT.
|
||||
|
||||
Your API key is stored only on your device and is never transmitted to anyone except OpenAI. Please note that OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription.
|
||||
|
||||
### ElevenLabs
|
||||
|
||||
To use the realistic AI text-to-speech feature, you will need to add your ElevenLabs API key by clicking "Play" next to any message.
|
||||
|
||||
Your API key is stored only on your device and never transmitted to anyone except ElevenLabs.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Edit messages (coming soon)
|
||||
- Regenerate messages (coming soon)
|
||||
- [Suggest feature ideas on the Discord](https://discord.gg/mS5QvKykvv)
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 3rem;
|
||||
|
||||
.inner {
|
||||
max-width: 50rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-weight: "Work Sans", sans-serif;
|
||||
|
||||
* {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
border-bottom: thin solid rgba(255, 255, 255, 0.2);
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function AboutPage(props: any) {
|
||||
return <Page id={'about'} headerProps={{ title }}>
|
||||
<Container>
|
||||
<Markdown content={content} className='inner' />
|
||||
</Container>
|
||||
</Page>;
|
||||
}
|
|
@ -27,7 +27,8 @@ export default function LandingPage(props: any) {
|
|||
return <Page id={'landing'} showSubHeader={true}>
|
||||
<Container>
|
||||
<p>
|
||||
<FormattedMessage defaultMessage={'Hello, how can I help you today?'} />
|
||||
<FormattedMessage defaultMessage={'Hello, how can I help you today?'}
|
||||
description="A friendly message that appears at the start of new chat sessions" />
|
||||
</p>
|
||||
{!openAIApiKey && (
|
||||
<Button size="xs" variant="light" compact onClick={onConnectButtonClick}>
|
||||
|
|
|
@ -7,6 +7,7 @@ import GenerationOptionsTab from './options';
|
|||
import { useAppDispatch, useAppSelector } from '../../store';
|
||||
import { closeSettingsUI, selectSettingsTab, setTab } from '../../store/settings-ui';
|
||||
import SpeechOptionsTab from './speech';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const Container = styled.div`
|
||||
padding: .4rem 1rem 1rem 1rem;
|
||||
|
@ -102,7 +103,9 @@ export default function SettingsDrawer(props: SettingsDrawerProps) {
|
|||
</Tabs>
|
||||
<div id="save">
|
||||
<Button variant="light" fullWidth size="md" onClick={close}>
|
||||
Save and Close
|
||||
<FormattedMessage defaultMessage={"Save and Close"}
|
||||
description="Label for the button that closes the Settings screen, saving any changes"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
|
|
|
@ -30,7 +30,7 @@ export default function GenerationOptionsTab(props: any) {
|
|||
&& (model?.trim() !== defaultModel.trim());
|
||||
|
||||
const systemPromptOption = useMemo(() => (
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: "System Prompt" })}
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: "System Prompt", description: "Heading for the setting that lets users customize the System Prompt, on the settings screen" })}
|
||||
focused={option === 'system-prompt'}>
|
||||
<Textarea
|
||||
value={initialSystemPrompt || defaultSystemPrompt}
|
||||
|
@ -49,13 +49,19 @@ export default function GenerationOptionsTab(props: any) {
|
|||
), [option, initialSystemPrompt, resettableSystemPromopt, onSystemPromptChange, onResetSystemPrompt]);
|
||||
|
||||
const modelOption = useMemo(() => (
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Model" })}
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Model", description: "Heading for the setting that lets users choose a model to interact with, on the settings screen" })}
|
||||
focused={option === 'model'}>
|
||||
<Select
|
||||
value={model || defaultModel}
|
||||
data={[
|
||||
{ label: "GPT 3.5 Turbo (default)", value: "gpt-3.5-turbo" },
|
||||
{ label: "GPT 4 (requires invite)", value: "gpt-4" },
|
||||
{
|
||||
label: intl.formatMessage({ defaultMessage: "GPT 3.5 Turbo (default)" }),
|
||||
value: "gpt-3.5-turbo",
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ defaultMessage: "GPT 4 (requires invite)" }),
|
||||
value: "gpt-4",
|
||||
},
|
||||
]}
|
||||
onChange={onModelChange} />
|
||||
{model === 'gpt-4' && (
|
||||
|
@ -71,7 +77,10 @@ export default function GenerationOptionsTab(props: any) {
|
|||
), [option, model, resettableModel, onModelChange, onResetModel]);
|
||||
|
||||
const temperatureOption = useMemo(() => (
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Temperature: {temperature, number, ::.0}", }, { temperature })}
|
||||
<SettingsOption heading={intl.formatMessage({
|
||||
defaultMessage: "Temperature: {temperature, number, ::.0}",
|
||||
description: "Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses",
|
||||
}, { temperature })}
|
||||
focused={option === 'temperature'}>
|
||||
<Slider value={temperature} onChange={onTemperatureChange} step={0.1} min={0} max={1} precision={3} />
|
||||
<p>
|
||||
|
|
|
@ -33,7 +33,7 @@ export default function SpeechOptionsTab() {
|
|||
}, [elevenLabsApiKey]);
|
||||
|
||||
const apiKeyOption = useMemo(() => (
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Your ElevenLabs Text-to-Speech API Key (optional)' })}
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Your ElevenLabs Text-to-Speech API Key (optional)', description: "Heading for the ElevenLabs API key setting on the settings screen" })}
|
||||
focused={option === 'elevenlabs-api-key'}>
|
||||
<TextInput placeholder={intl.formatMessage({ defaultMessage: "Paste your API key here" })}
|
||||
value={elevenLabsApiKey || ''} onChange={onElevenLabsApiKeyChange} />
|
||||
|
@ -50,7 +50,7 @@ export default function SpeechOptionsTab() {
|
|||
), [option, elevenLabsApiKey, onElevenLabsApiKeyChange]);
|
||||
|
||||
const voiceOption = useMemo(() => (
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Voice' })}
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Voice', description: 'Heading for the setting that lets users choose an ElevenLabs text-to-speech voice, on the settings screen' })}
|
||||
focused={option === 'elevenlabs-voice'}>
|
||||
<Select
|
||||
value={voice}
|
||||
|
@ -64,7 +64,7 @@ export default function SpeechOptionsTab() {
|
|||
<Button onClick={() => (document.getElementById('voice-preview') as HTMLMediaElement)?.play()} variant='light' compact style={{ marginTop: '1rem' }}>
|
||||
<i className='fa fa-headphones' />
|
||||
<span>
|
||||
<FormattedMessage defaultMessage="Preview voice" />
|
||||
<FormattedMessage defaultMessage="Preview voice" description="Label for the button that plays a preview of the selected ElevenLabs text-to-speech voice" />
|
||||
</span>
|
||||
</Button>
|
||||
</SettingsOption>
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function UserOptionsTab(props: any) {
|
|||
|
||||
const elem = useMemo(() => (
|
||||
<SettingsTab name="user">
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Your OpenAI API Key" })}
|
||||
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Your OpenAI API Key", description: "Heading for the OpenAI API key setting on the settings screen" })}
|
||||
focused={option === 'openai-api-key'}>
|
||||
<TextInput
|
||||
placeholder={intl.formatMessage({ defaultMessage: "Paste your API key here" })}
|
||||
|
@ -25,7 +25,7 @@ export default function UserOptionsTab(props: any) {
|
|||
onChange={onOpenAIApiKeyChange} />
|
||||
<p>
|
||||
<a href="https://platform.openai.com/account/api-keys" target="_blank" rel="noreferrer">
|
||||
<FormattedMessage defaultMessage="Find your API key here." />
|
||||
<FormattedMessage defaultMessage="Find your API key here." description="Label for the link that takes the user to the page on the OpenAI website where they can find their API key." />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
|
|
|
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
|||
import { ActionIcon, Avatar, Burger, Button, Menu } from '@mantine/core';
|
||||
import { useElementSize } from '@mantine/hooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { backend } from '../../backend';
|
||||
import { useAppContext } from '../../context';
|
||||
import { useAppDispatch, useAppSelector } from '../../store';
|
||||
|
@ -115,7 +115,7 @@ export default function Sidebar(props: {
|
|||
const elem = useMemo(() => (
|
||||
<Container className={"sidebar " + (sidebarOpen ? 'opened' : 'closed')} ref={ref}>
|
||||
<div className="sidebar-header">
|
||||
<h2>Chat History</h2>
|
||||
<h2><FormattedMessage defaultMessage={"Chat History"} description="Heading for the chat history screen" /></h2>
|
||||
<Burger opened={sidebarOpen} onClick={onBurgerClick} aria-label={burgerLabel} transitionDuration={0} />
|
||||
</div>
|
||||
<div className="sidebar-content">
|
||||
|
@ -141,12 +141,12 @@ export default function Sidebar(props: {
|
|||
<Menu.Item onClick={() => {
|
||||
dispatch(setTab('user'));
|
||||
}} icon={<i className="fas fa-gear" />}>
|
||||
User settings
|
||||
<FormattedMessage defaultMessage={"User settings"} description="Menu item that opens the user settings screen" />
|
||||
</Menu.Item>
|
||||
{/*
|
||||
<Menu.Divider />
|
||||
<Menu.Item color="red" onClick={() => backend.current?.logout()} icon={<i className="fas fa-sign-out-alt" />}>
|
||||
Sign out
|
||||
<FormattedMessage defaultMessage={"Sign out"} />
|
||||
</Menu.Item>
|
||||
*/}
|
||||
</Menu.Dropdown>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import styled from '@emotion/styled';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAppContext } from '../../context';
|
||||
import { useAppDispatch } from '../../store';
|
||||
|
@ -83,12 +84,12 @@ export default function RecentChats(props: any) {
|
|||
onClick={onClick}
|
||||
data-chat-id={c.chatID}
|
||||
className={c.chatID === currentChatID ? 'selected' : ''}>
|
||||
<strong>{c.title || 'Untitled'}</strong>
|
||||
<strong>{c.title || <FormattedMessage defaultMessage={"Untitled"} description="default title for untitled chat sessions" />}</strong>
|
||||
</ChatListItem>
|
||||
))}
|
||||
</ChatList>}
|
||||
{recentChats.length === 0 && <Empty>
|
||||
No chats yet.
|
||||
<FormattedMessage defaultMessage={"No chats yet."} description="Message shown on the Chat History screen for new users who haven't started their first chat session" />
|
||||
</Empty>}
|
||||
</Container>
|
||||
);
|
||||
|
|
|
@ -6,13 +6,12 @@ import { IntlProvider } from 'react-intl';
|
|||
import { Provider } from 'react-redux';
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
||||
import AboutPage from './components/pages/about';
|
||||
import ChatPage from './components/pages/chat';
|
||||
import LandingPage from './components/pages/landing';
|
||||
import { AppContextProvider } from './context';
|
||||
import store, { persistor } from './store';
|
||||
|
||||
import ChatPage from './components/pages/chat';
|
||||
import LandingPage from './components/pages/landing';
|
||||
|
||||
import './backend';
|
||||
import './index.scss';
|
||||
|
||||
|
@ -41,12 +40,6 @@ const router = createBrowserRouter([
|
|||
<ChatPage share={true} />
|
||||
</AppContextProvider>,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
element: <AppContextProvider>
|
||||
<AboutPage />
|
||||
</AppContextProvider>,
|
||||
},
|
||||
]);
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
|
@ -54,11 +47,17 @@ const root = ReactDOM.createRoot(
|
|||
);
|
||||
|
||||
async function loadLocaleData(locale: string) {
|
||||
const messages = await fetch(`/lang/${locale}.json`);
|
||||
if (!messages.ok) {
|
||||
const response = await fetch(`/lang/${locale}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to load locale data");
|
||||
}
|
||||
return messages.json()
|
||||
const messages: any = await response.json();
|
||||
for (const key of Object.keys(messages)) {
|
||||
if (typeof messages[key] !== 'string') {
|
||||
messages[key] = messages[key].defaultMessage;
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
async function bootstrapApplication() {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createChatCompletion, defaultModel } from "./openai";
|
|||
import { OpenAIMessage, Chat } from "./types";
|
||||
|
||||
const systemPrompt = `
|
||||
Please read the following exchange and write a short, concise title describing the topic.
|
||||
Please read the following exchange and write a short, concise title describing the topic (in the user's language).
|
||||
`.trim();
|
||||
|
||||
const userPrompt = (user: string, assistant: string) => `
|
||||
|
|
|
@ -272,13 +272,13 @@ export function ElevenLabsReaderButton(props: { selector: string }) {
|
|||
<Button variant="subtle" size="sm" compact onClickCapture={onClick} loading={status === 'init'}>
|
||||
{status !== 'init' && <i className="fa fa-headphones" />}
|
||||
{status === 'idle' && <span>
|
||||
<FormattedMessage defaultMessage="Play" />
|
||||
<FormattedMessage defaultMessage="Play" description="Label for the button that starts text-to-speech playback" />
|
||||
</span>}
|
||||
{status === 'buffering' && <span>
|
||||
<FormattedMessage defaultMessage="Loading audio..." />
|
||||
<FormattedMessage defaultMessage="Loading audio..." description="Message indicating that text-to-speech audio is buffering" />
|
||||
</span>}
|
||||
{status !== 'idle' && status !== 'buffering' && <span>
|
||||
<FormattedMessage defaultMessage="Stop" />
|
||||
<FormattedMessage defaultMessage="Stop" description="Label for the button that stops text-to-speech playback" />
|
||||
</span>}
|
||||
</Button>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue