-
+ } />
+ } />
} />
} />
@@ -124,7 +115,6 @@ function MobileScreen() {
}
export function Home() {
- const isMobileScreen = useMobileScreen();
useSwitchTheme();
if (!useHasHydrated()) {
@@ -133,7 +123,9 @@ export function Home() {
return (
- {isMobileScreen ? : }
+
+
+
);
}
diff --git a/app/components/new-chat.module.scss b/app/components/new-chat.module.scss
new file mode 100644
index 000000000..9cd179609
--- /dev/null
+++ b/app/components/new-chat.module.scss
@@ -0,0 +1,100 @@
+.new-chat {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ padding-top: 80px;
+
+ .mask-cards {
+ display: flex;
+ margin-bottom: 20px;
+
+ .mask-card {
+ padding: 20px 10px;
+ border: var(--border-in-light);
+ box-shadow: var(--card-shadow);
+ border-radius: 14px;
+ background-color: var(--white);
+ transform: scale(1);
+
+ &:first-child {
+ transform: rotate(-15deg) translateY(5px);
+ }
+
+ &:last-child {
+ transform: rotate(15deg) translateY(5px);
+ }
+ }
+ }
+
+ .title {
+ font-size: 32px;
+ font-weight: bolder;
+ animation: slide-in ease 0.3s;
+ }
+
+ .sub-title {
+ animation: slide-in ease 0.3s;
+ }
+
+ .search-bar {
+ margin-top: 20px;
+ }
+
+ .masks {
+ flex-grow: 1;
+ width: 100%;
+ overflow: hidden;
+ align-items: center;
+ padding-top: 20px;
+
+ animation: slide-in ease 0.3s;
+
+ .mask-row {
+ margin-bottom: 10px;
+ display: flex;
+ justify-content: center;
+
+ @for $i from 1 to 10 {
+ &:nth-child(#{$i * 2}) {
+ margin-left: 50px;
+ }
+ }
+
+ .mask {
+ display: flex;
+ align-items: center;
+ padding: 10px 16px;
+ border: var(--border-in-light);
+ box-shadow: var(--card-shadow);
+ background-color: var(--white);
+ border-radius: 10px;
+ margin-right: 10px;
+ width: 100px;
+ transform: scale(1);
+ cursor: pointer;
+ transition: all ease 0.3s;
+
+ &:hover {
+ transform: translateY(-5px) scale(1.1);
+ z-index: 999;
+ border-color: var(--primary);
+ }
+
+ .mask-avatar {
+ display: flex;
+ min-width: 18px;
+ min-height: 18px;
+ background-color: #eee;
+ border-radius: 20px;
+ }
+
+ .mask-name {
+ margin-left: 10px;
+ }
+ }
+ }
+ }
+}
diff --git a/app/components/new-chat.tsx b/app/components/new-chat.tsx
new file mode 100644
index 000000000..e053a7fe5
--- /dev/null
+++ b/app/components/new-chat.tsx
@@ -0,0 +1,92 @@
+import { useEffect, useRef } from "react";
+import { SlotID } from "../constant";
+import { EmojiAvatar } from "./emoji";
+import styles from "./new-chat.module.scss";
+
+function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
+ const xmin = Math.max(aRect.x, bRect.x);
+ const xmax = Math.min(aRect.x + aRect.width, bRect.x + bRect.width);
+ const ymin = Math.max(aRect.y, bRect.y);
+ const ymax = Math.min(aRect.y + aRect.height, bRect.y + bRect.height);
+ const width = xmax - xmin;
+ const height = ymax - ymin;
+ const intersectionArea = width < 0 || height < 0 ? 0 : width * height;
+ return intersectionArea;
+}
+
+function Mask(props: { avatar: string; name: string }) {
+ const domRef = useRef
(null);
+
+ useEffect(() => {
+ const changeOpacity = () => {
+ const dom = domRef.current;
+ const parent = document.getElementById(SlotID.AppBody);
+ if (!parent || !dom) return;
+
+ const domRect = dom.getBoundingClientRect();
+ const parentRect = parent.getBoundingClientRect();
+ const intersectionArea = getIntersectionArea(domRect, parentRect);
+ const domArea = domRect.width * domRect.height;
+ const ratio = intersectionArea / domArea;
+ const opacity = ratio > 0.9 ? 1 : 0.4;
+ dom.style.opacity = opacity.toString();
+ };
+
+ setTimeout(changeOpacity, 30);
+
+ window.addEventListener("resize", changeOpacity);
+
+ return () => window.removeEventListener("resize", changeOpacity);
+ }, [domRef]);
+
+ return (
+
+ );
+}
+
+export function NewChat() {
+ const masks = new Array(20).fill(0).map(() =>
+ new Array(10).fill(0).map((_, i) => ({
+ avatar: "1f" + (Math.round(Math.random() * 50) + 600).toString(),
+ name: ["撩妹达人", "编程高手", "情感大师", "健康医生", "数码通"][
+ Math.floor(Math.random() * 4)
+ ],
+ })),
+ );
+
+ return (
+
+
+
+
挑选一个面具
+
现在开始,与面具背后的思维碰撞
+
+
+
+
+ {masks.map((masks, i) => (
+
+ {masks.map((mask, index) => (
+
+ ))}
+
+ ))}
+
+
+ );
+}
diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx
index 1e35964d3..8b534192b 100644
--- a/app/components/sidebar.tsx
+++ b/app/components/sidebar.tsx
@@ -134,7 +134,7 @@ export function SideBar(props: { className?: string }) {
icon={}
text={shouldNarrow ? undefined : Locale.Home.NewChat}
onClick={() => {
- chatStore.newSession();
+ navigate(Path.NewChat);
}}
shadow
/>
diff --git a/app/constant.ts b/app/constant.ts
index 43ae4cc68..60bb73bdf 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -11,6 +11,11 @@ export enum Path {
Home = "/",
Chat = "/chat",
Settings = "/settings",
+ NewChat = "/new-chat",
+}
+
+export enum SlotID {
+ AppBody = "app-body",
}
export const MAX_SIDEBAR_WIDTH = 500;
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 2e35cb30c..0b8a467b3 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -3,7 +3,8 @@ import { SubmitKey } from "../store/config";
const cn = {
WIP: "该功能仍在开发中……",
Error: {
- Unauthorized: "现在是未授权状态,请点击左下角设置按钮输入访问密码。",
+ Unauthorized:
+ "现在是未授权状态,请点击左下角[设置](/#/settings)按钮输入访问密码。",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 条对话`,
@@ -141,7 +142,7 @@ const cn = {
Model: "模型 (model)",
Temperature: {
Title: "随机性 (temperature)",
- SubTitle: "值越大,回复越随机,大于 1 的值可能会导致乱码",
+ SubTitle: "值越大,回复越随机",
},
MaxTokens: {
Title: "单次回复限制 (max_tokens)",
diff --git a/app/locales/index.ts b/app/locales/index.ts
index 389304f85..2ce59261c 100644
--- a/app/locales/index.ts
+++ b/app/locales/index.ts
@@ -19,7 +19,7 @@ export const AllLangs = [
"jp",
"de",
] as const;
-type Lang = (typeof AllLangs)[number];
+export type Lang = (typeof AllLangs)[number];
const LANG_KEY = "lang";
diff --git a/app/masks.ts b/app/masks.ts
new file mode 100644
index 000000000..213d9a47a
--- /dev/null
+++ b/app/masks.ts
@@ -0,0 +1,3 @@
+import { Mask } from "./store/mask";
+
+export const BUILT_IN_MASKS: Mask[] = [];
diff --git a/app/store/mask.ts b/app/store/mask.ts
new file mode 100644
index 000000000..168761cc7
--- /dev/null
+++ b/app/store/mask.ts
@@ -0,0 +1,81 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+import { getLang, Lang } from "../locales";
+import { Message } from "./chat";
+import { ModelConfig, useAppConfig } from "./config";
+
+export const MASK_KEY = "mask-store";
+
+export type Mask = {
+ id: number;
+ avatar: string;
+ name: string;
+ context: Message[];
+ config: ModelConfig;
+ lang: Lang;
+};
+
+export const DEFAULT_MASK_STATE = {
+ masks: {} as Record,
+ globalMaskId: 0,
+};
+
+export type MaskState = typeof DEFAULT_MASK_STATE;
+type MaskStore = MaskState & {
+ create: (mask: Partial) => Mask;
+ update: (id: number, updater: (mask: Mask) => void) => void;
+ delete: (id: number) => void;
+ search: (text: string) => Mask[];
+ getAll: () => Mask[];
+};
+
+export const useMaskStore = create()(
+ persist(
+ (set, get) => ({
+ ...DEFAULT_MASK_STATE,
+
+ create(mask) {
+ set(() => ({ globalMaskId: get().globalMaskId + 1 }));
+ const id = get().globalMaskId;
+ const masks = get().masks;
+ masks[id] = {
+ id,
+ avatar: "1f916",
+ name: "",
+ config: useAppConfig.getState().modelConfig,
+ context: [],
+ lang: getLang(),
+ ...mask,
+ };
+
+ set(() => ({ masks }));
+
+ return masks[id];
+ },
+ update(id, updater) {
+ const masks = get().masks;
+ const mask = masks[id];
+ if (!mask) return;
+ const updateMask = { ...mask };
+ updater(updateMask);
+ masks[id] = updateMask;
+ set(() => ({ masks }));
+ },
+ delete(id) {
+ const masks = get().masks;
+ delete masks[id];
+ set(() => ({ masks }));
+ },
+ getAll() {
+ return Object.values(get().masks).sort((a, b) => a.id - b.id);
+ },
+ search(text) {
+ return Object.values(get().masks);
+ },
+ }),
+ {
+ name: MASK_KEY,
+ version: 2,
+ },
+ ),
+);
diff --git a/app/styles/globals.scss b/app/styles/globals.scss
index c5ffacbc8..549f254b8 100644
--- a/app/styles/globals.scss
+++ b/app/styles/globals.scss
@@ -336,3 +336,9 @@ pre {
box-shadow: var(--card-shadow);
border-radius: 10px;
}
+
+.one-line {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}