Compare commits
2 Commits
515527efd9
...
49fffe8cbe
| Author | SHA1 | Date | |
|---|---|---|---|
| 49fffe8cbe | |||
| 329b9c7c95 |
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
.settings {
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
|
||||
.logout-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,7 +231,7 @@
|
||||
.select-with-icon {
|
||||
position: relative;
|
||||
max-width: fit-content;
|
||||
|
||||
|
||||
.select-with-icon-select {
|
||||
height: 100%;
|
||||
border: var(--border-in-light);
|
||||
@@ -227,4 +251,57 @@
|
||||
transform: translateY(-50%);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export enum Path {
|
||||
NewChat = "/new-chat",
|
||||
Masks = "/masks",
|
||||
Auth = "/auth",
|
||||
Logout = "/logout",
|
||||
}
|
||||
|
||||
export enum SlotID {
|
||||
|
||||
+8
-2
@@ -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: "查看全部",
|
||||
},
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chatgpt-pro",
|
||||
"version": "1.2",
|
||||
"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 |
Reference in New Issue
Block a user