v1.5: Add login module and GPT selection feature

This commit is contained in:
2023-07-12 18:04:12 +08:00
parent 329b9c7c95
commit 49fffe8cbe
17 changed files with 375 additions and 31 deletions
+24 -5
View File
@@ -27,6 +27,26 @@
fill: white !important;
}
}
&.danger {
color: rgba($color: red, $alpha: 0.8);
border-color: rgba($color: red, $alpha: 0.5);
background-color: rgba($color: red, $alpha: 0.05);
&:hover {
border-color: red;
background-color: rgba($color: red, $alpha: 0.1);
}
path {
fill: red !important;
}
}
&:hover,
&:focus {
border-color: var(--primary);
}
}
.shadow {
@@ -37,10 +57,6 @@
border: var(--border-in-light);
}
.icon-button:hover {
border-color: var(--primary);
}
.icon-button-icon {
width: 16px;
height: 16px;
@@ -56,9 +72,12 @@
}
.icon-button-text {
margin-left: 5px;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:not(:first-child) {
margin-left: 5px;
}
}
+7 -1
View File
@@ -2,16 +2,20 @@ import * as React from "react";
import styles from "./button.module.scss";
export type ButtonType = "primary" | "danger" | null;
export function IconButton(props: {
onClick?: () => void;
icon?: JSX.Element;
type?: "primary" | "danger";
type?: ButtonType;
text?: string;
bordered?: boolean;
shadow?: boolean;
className?: string;
title?: string;
disabled?: boolean;
tabIndex?: number;
autoFocus?: boolean;
}) {
return (
<button
@@ -25,6 +29,8 @@ export function IconButton(props: {
title={props.title}
disabled={props.disabled}
role="button"
tabIndex={props.tabIndex}
autoFocus={props.autoFocus}
>
{props.icon && (
<div
+30 -1
View File
@@ -9,6 +9,8 @@ import { ModelType } from "../store";
import BotIcon from "../icons/bot.svg";
import BlackBotIcon from "../icons/black-bot.svg";
import Image from 'next/image';
export function getEmojiUrl(unified: string, style: EmojiStyle) {
return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`;
}
@@ -29,6 +31,15 @@ export function AvatarPicker(props: {
}
export function Avatar(props: { model?: ModelType; avatar?: string }) {
const isSvg = (url: string) => {
return url.toLowerCase().endsWith('.svg');
};
const isRegularImage = (url: string) => {
const regex = /\.(jpe?g|png|gif)$/i;
return regex.test(url);
};
if (props.model) {
return (
<div className="no-dark">
@@ -43,7 +54,25 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) {
return (
<div className="user-avatar">
{props.avatar && <EmojiAvatar avatar={props.avatar} />}
{props.avatar && isRegularImage(props.avatar) && (
<Image
src={props.avatar}
alt="User Avatar"
width={30}
height={30}
className="user-avatar"
/>
)}
{props.avatar && isSvg(props.avatar) && (
<img
src={props.avatar}
alt="User Avatar"
className="user-avatar"
/>
)}
{props.avatar && !isSvg(props.avatar) && !isRegularImage(props.avatar) && (
<EmojiAvatar avatar={props.avatar} />
)}
</div>
);
}
+4 -4
View File
@@ -35,9 +35,9 @@ export function Loading(props: { noLogo?: boolean }) {
);
}
// const Settings = dynamic(async () => (await import("./settings")).Settings, {
// loading: () => <Loading noLogo />,
// });
const Settings = dynamic(async () => (await import("./settings")).Settings, {
loading: () => <Loading noLogo />,
});
const Chat = dynamic(async () => (await import("./chat")).Chat, {
loading: () => <Loading noLogo />,
@@ -141,7 +141,7 @@ function Screen() {
<Route path={Path.NewChat} element={<NewChat />} />
<Route path={Path.Masks} element={<MaskPage />} />
<Route path={Path.Chat} element={<Chat />} />
{/* <Route path={Path.Settings} element={<Settings />} /> */}
<Route path={Path.Settings} element={<Settings />} />
</Routes>
</div>
</>
+5
View File
@@ -1,6 +1,11 @@
.settings {
padding: 20px;
overflow: auto;
.logout-button {
display: flex;
justify-content: center;
}
}
.avatar {
+72 -1
View File
@@ -1 +1,72 @@
export {};
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { IconButton } from "./button";
import { showConfirm } from "./ui-lib";
import Locale from "../locales";
import { Path } from "../constant";
import { ErrorBoundary } from "./error";
import styles from "./settings.module.scss";
import CloseIcon from "../icons/close.svg";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons'
export function Settings() {
const navigate = useNavigate();
useEffect(() => {
const keydownEvent = (e: KeyboardEvent) => {
if (e.key === "Escape") {
navigate(Path.Home);
}
};
document.addEventListener("keydown", keydownEvent);
return () => {
document.removeEventListener("keydown", keydownEvent);
};
}, []);
return (
<ErrorBoundary>
<div className="window-header">
<div className="window-header-title">
<div className="window-header-main-title">
{Locale.Settings.Title}
</div>
<div className="window-header-sub-title">
{Locale.Settings.SubTitle}
</div>
</div>
<div className="window-actions">
<div className="window-action-button"></div>
<div className="window-action-button"></div>
<div className="window-action-button">
<IconButton
icon={<CloseIcon />}
onClick={() => navigate(Path.Home)}
bordered
/>
</div>
</div>
</div>
<div className={styles["settings"]}>
<div className={styles["logout-button"]}>
<IconButton
icon={<FontAwesomeIcon icon={faRightFromBracket} />}
text={Locale.Settings.Danger.Logout.Action}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Logout.Confirm)) {
navigate(Path.Logout);
}
}}
type="danger"
/>
</div>
</div>
</ErrorBoundary>
);
}
+7 -7
View File
@@ -3,7 +3,7 @@ import { useEffect, useRef } from "react";
import styles from "./home.module.scss";
import { IconButton } from "./button";
// import SettingsIcon from "../icons/settings.svg";
import SettingsIcon from "../icons/settings.svg";
// import GithubIcon from "../icons/github.svg";
import ChatGptIcon from "../icons/chatgpt.svg";
import AddIcon from "../icons/add.svg";
@@ -168,15 +168,15 @@ export function SideBar(props: { className?: string }) {
/> */}
</div>
<div className={styles["sidebar-action"]}>
{/* <Link to={Path.Settings}>
<Link to={Path.Settings}>
<IconButton icon={<SettingsIcon />} shadow />
</Link> */}
</Link>
</div>
<div className={styles["sidebar-action"]}>
{/* <a href={REPO_URL} target="_blank">
{/* <div className={styles["sidebar-action"]}>
<a href={REPO_URL} target="_blank">
<IconButton icon={<GithubIcon />} shadow />
</a> */}
</div>
</a>
</div> */}
</div>
<div>
<IconButton
+82 -5
View File
@@ -62,6 +62,7 @@
box-shadow: var(--card-shadow);
margin-bottom: 20px;
animation: slide-in ease 0.3s;
background: var(--white);
}
.list .list-item:last-child {
@@ -72,11 +73,26 @@
box-shadow: var(--card-shadow);
background-color: var(--white);
border-radius: 12px;
width: 60vw;
width: 80vw;
max-width: 900px;
min-width: 300px;
animation: slide-in ease 0.3s;
--modal-padding: 20px;
&-max {
width: 95vw;
max-width: unset;
height: 95vh;
display: flex;
flex-direction: column;
.modal-content {
max-height: unset !important;
flex-grow: 1;
}
}
.modal-header {
padding: var(--modal-padding);
display: flex;
@@ -89,11 +105,19 @@
font-size: 16px;
}
.modal-close-btn {
cursor: pointer;
.modal-header-actions {
display: flex;
&:hover {
filter: brightness(1.2);
.modal-header-action {
cursor: pointer;
&:not(:last-child) {
margin-right: 20px;
}
&:hover {
filter: brightness(1.2);
}
}
}
}
@@ -228,3 +252,56 @@
pointer-events: none;
}
}
.modal-input {
height: 100%;
width: 100%;
border-radius: 10px;
border: var(--border-in-light);
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
background-color: var(--white);
color: var(--black);
font-family: inherit;
padding: 10px;
resize: none;
outline: none;
box-sizing: border-box;
&:focus {
border: 1px solid var(--primary);
}
}
.selector {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
&-content {
.list {
max-height: 90vh;
overflow-x: hidden;
overflow-y: auto;
.list-item {
cursor: pointer;
background-color: var(--white);
&:hover {
filter: brightness(0.95);
}
&:active {
filter: brightness(0.9);
}
}
}
}
}
+58
View File
@@ -5,6 +5,11 @@ import EyeIcon from "../icons/eye.svg";
import EyeOffIcon from "../icons/eye-off.svg";
import DownIcon from "../icons/down.svg";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCheck, faBan } from '@fortawesome/free-solid-svg-icons'
import Locale from "../locales";
import { createRoot } from "react-dom/client";
import React, { HTMLProps, useEffect, useState } from "react";
import { IconButton } from "./button";
@@ -262,3 +267,56 @@ export function Select(
</div>
);
}
export function showConfirm(content: any) {
const div = document.createElement("div");
div.className = "modal-mask";
document.body.appendChild(div);
const root = createRoot(div);
const closeModal = () => {
root.unmount();
div.remove();
};
return new Promise<boolean>((resolve) => {
root.render(
<Modal
title={Locale.UI.Confirm}
actions={[
<IconButton
key="cancel"
text={Locale.UI.Cancel}
onClick={() => {
resolve(false);
closeModal();
}}
// icon={<CancelIcon />}
icon={<FontAwesomeIcon icon={faBan} />}
tabIndex={0}
bordered
shadow
></IconButton>,
<IconButton
key="confirm"
text={Locale.UI.Confirm}
type="primary"
onClick={() => {
resolve(true);
closeModal();
}}
// icon={<ConfirmIcon />}
icon={<FontAwesomeIcon icon={faCheck} />}
tabIndex={0}
autoFocus
bordered
shadow
></IconButton>,
]}
onClose={closeModal}
>
{content}
</Modal>,
);
});
}
+1
View File
@@ -7,6 +7,7 @@ export enum Path {
NewChat = "/new-chat",
Masks = "/masks",
Auth = "/auth",
Logout = "/logout",
}
export enum SlotID {
+8 -2
View File
@@ -36,7 +36,7 @@ const cn = {
dark: "深色模式",
},
Prompt: "快捷指令",
Masks: "所有面具",
Masks: "所有角色",
Clear: "清除聊天",
Settings: "对话设置",
AIPaint: "AI绘画",
@@ -198,6 +198,12 @@ const cn = {
Title: "话题新鲜度 (presence_penalty)",
SubTitle: "值越大,越有可能扩展到新话题",
},
Danger: {
Logout: {
Action: "退出登录",
Confirm: "确定要退出登录吗?",
}
},
},
Store: {
DefaultTopic: "新的聊天",
@@ -266,7 +272,7 @@ const cn = {
Skip: "直接开始",
NotShow: "不再展示",
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
Title: "挑选一个面具",
Title: "挑选一个角色",
SubTitle: "现在开始,与面具背后的灵魂思维碰撞",
More: "查看全部",
},
+6
View File
@@ -200,6 +200,12 @@ const en: RequiredLocaleType = {
SubTitle:
"A larger value increases the likelihood to talk about new topics",
},
Danger: {
Logout: {
Action: "Log out",
Confirm: "Are you sure you want to log out?",
}
},
},
Store: {
DefaultTopic: "New Conversation",
+34 -1
View File
@@ -1,6 +1,39 @@
import { BuiltinMask } from "./typing";
export const CN_MASKS: BuiltinMask[] = [];
export const CN_MASKS: BuiltinMask[] = [
{
avatar: "/avatar/chatgpt.png",
name: "gpt3.5",
context: [],
modelConfig: {
model: "gpt-3.5-turbo",
temperature: 0.5,
max_tokens: 2000,
presence_penalty: 0,
sendMemory: true,
historyMessageCount: 64,
compressMessageLengthThreshold: 2000,
},
lang: "cn",
builtin: true,
},
{
avatar: "/avatar/chatgpt4.png",
name: "gpt4",
context: [],
modelConfig: {
model: "gpt-4",
temperature: 0.5,
max_tokens: 2000,
presence_penalty: 0,
sendMemory: true,
historyMessageCount: 32,
compressMessageLengthThreshold: 500,
},
lang: "cn",
builtin: true,
},
]
// export const CN_MASKS: BuiltinMask[] = [
// {
+34 -1
View File
@@ -1,6 +1,39 @@
import { BuiltinMask } from "./typing";
export const EN_MASKS: BuiltinMask[] = [];
export const EN_MASKS: BuiltinMask[] = [
{
avatar: "/avatar/chatgpt.png",
name: "gpt3.5",
context: [],
modelConfig: {
model: "gpt-3.5-turbo",
temperature: 0.5,
max_tokens: 2000,
presence_penalty: 0,
sendMemory: true,
historyMessageCount: 64,
compressMessageLengthThreshold: 2000,
},
lang: "en",
builtin: true,
},
{
avatar: "/avatar/chatgpt4.png",
name: "gpt4",
context: [],
modelConfig: {
model: "gpt-4",
temperature: 0.5,
max_tokens: 2000,
presence_penalty: 0,
sendMemory: true,
historyMessageCount: 32,
compressMessageLengthThreshold: 500,
},
lang: "en",
builtin: true,
},
]
// export const EN_MASKS: BuiltinMask[] = [
// {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "chatgpt-pro",
"version": "1.3",
"version": "1.5",
"private": false,
"license": "CC-BY-NC-SA-4.0",
"scripts": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB