From debca09f6cffa4fa35b9fa1c82f13be529076d21 Mon Sep 17 00:00:00 2001
From: liuyonghe0111 <1763195287@qq.com>
Date: Thu, 6 Nov 2025 14:15:32 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=89=93=E7=94=B5?=
=?UTF-8?q?=E8=AF=9D=E7=95=8C=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 4 +-
pnpm-lock.yaml | 53 ++++++-------
.../character/[id]/chat/ChatMode/ChatList.tsx | 6 +-
.../[id]/chat/ChatMode/Left/ChatHisory.tsx | 5 +-
.../[id]/chat/ChatMode/Left/Side.tsx | 8 +-
.../[id]/chat/ChatMode/Left/index.tsx | 5 +-
.../[id]/chat/ChatMode/actions/index.tsx | 8 +-
.../character/[id]/chat/ChatMode/index.tsx | 6 +-
.../character/[id]/chat/Context/index.tsx | 15 ----
.../[id]/chat/Main/actions/index.tsx | 75 +++++++++++++++++++
.../[id]/chat/PhoneCallMode/index.tsx | 65 +++++++++++++++-
src/app/(main)/character/[id]/chat/atoms.ts | 16 ----
src/app/(main)/character/[id]/chat/page.tsx | 26 +++----
src/app/(main)/character/[id]/chat/store.ts | 27 +++++++
src/app/layout.tsx | 6 +-
src/layouts/GlobalContainer/index.tsx | 15 ----
.../MainLayout/components/LocaleSelect.tsx | 2 +-
.../IntlProvider.tsx | 27 ++++---
.../QueryProvider.tsx | 0
src/layouts/Providers/index.tsx | 11 +++
20 files changed, 254 insertions(+), 126 deletions(-)
delete mode 100644 src/app/(main)/character/[id]/chat/Context/index.tsx
create mode 100644 src/app/(main)/character/[id]/chat/Main/actions/index.tsx
delete mode 100644 src/app/(main)/character/[id]/chat/atoms.ts
create mode 100644 src/app/(main)/character/[id]/chat/store.ts
delete mode 100644 src/layouts/GlobalContainer/index.tsx
rename src/layouts/{GlobalContainer => Providers}/IntlProvider.tsx (76%)
rename src/layouts/{GlobalContainer => Providers}/QueryProvider.tsx (100%)
create mode 100644 src/layouts/Providers/index.tsx
diff --git a/package.json b/package.json
index eff92d3..f06d12b 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,6 @@
"axios": "^1.12.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "jotai": "^2.15.0",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"next": "15.5.4",
@@ -27,7 +26,8 @@
"react": "19.1.0",
"react-dom": "19.1.0",
"react-virtuoso": "^4.14.1",
- "tailwind-merge": "^3.3.1"
+ "tailwind-merge": "^3.3.1",
+ "zustand": "^5.0.8"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7a5608c..faf4285 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,9 +26,6 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
- jotai:
- specifier: ^2.15.0
- version: 2.15.0(@types/react@19.2.2)(react@19.1.0)
js-cookie:
specifier: ^3.0.5
version: 3.0.5
@@ -59,6 +56,9 @@ importers:
tailwind-merge:
specifier: ^3.3.1
version: 3.3.1
+ zustand:
+ specifier: ^5.0.8
+ version: 5.0.8(@types/react@19.2.2)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0))
devDependencies:
'@eslint/eslintrc':
specifier: ^3
@@ -2110,24 +2110,6 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
- jotai@2.15.0:
- resolution: {integrity: sha512-nbp/6jN2Ftxgw0VwoVnOg0m5qYM1rVcfvij+MZx99Z5IK13eGve9FJoCwGv+17JvVthTjhSmNtT5e1coJnr6aw==}
- engines: {node: '>=12.20.0'}
- peerDependencies:
- '@babel/core': '>=7.0.0'
- '@babel/template': '>=7.0.0'
- '@types/react': '>=17.0.0'
- react: '>=17.0.0'
- peerDependenciesMeta:
- '@babel/core':
- optional: true
- '@babel/template':
- optional: true
- '@types/react':
- optional: true
- react:
- optional: true
-
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
@@ -2883,6 +2865,24 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ zustand@5.0.8:
+ resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -5076,11 +5076,6 @@ snapshots:
jiti@2.6.1: {}
- jotai@2.15.0(@types/react@19.2.2)(react@19.1.0):
- optionalDependencies:
- '@types/react': 19.2.2
- react: 19.1.0
-
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
@@ -5904,3 +5899,9 @@ snapshots:
yallist@5.0.0: {}
yocto-queue@0.1.0: {}
+
+ zustand@5.0.8(@types/react@19.2.2)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)):
+ optionalDependencies:
+ '@types/react': 19.2.2
+ react: 19.1.0
+ use-sync-external-store: 1.6.0(react@19.1.0)
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx b/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx
index 2f08263..100e341 100644
--- a/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx
@@ -1,12 +1,12 @@
'use client';
-import { useAtomValue } from 'jotai';
-import { isPortraitModeAtom } from '../atoms';
+import { useChatStore } from '../store';
+
import ChatMessageList from './components/ChatMessageList';
import PortraitChat from './components/PortraitChat';
export default function ChatList() {
- const isPortraitMode = useAtomValue(isPortraitModeAtom);
+ const isPortraitMode = useChatStore((state) => state.isPortraitMode);
return (
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx
index 54d5ebd..0dc88ae 100644
--- a/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx
@@ -1,13 +1,12 @@
'use client';
import React from 'react';
-import { useSetAtom } from 'jotai';
-import { historyListOpenAtom } from '../../atoms';
+import { useChatStore } from '../../store';
import Image from 'next/image';
import IconFont from '@/components/ui/iconFont';
const ChatHistory = React.memo(() => {
- const setHistoryListOpen = useSetAtom(historyListOpenAtom);
+ const setHistoryListOpen = useChatStore((state) => state.setHistoryListOpen);
return (
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx
index 80d562d..d8b7124 100644
--- a/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx
@@ -1,7 +1,6 @@
'use client';
-import { useAtom, useSetAtom } from 'jotai';
-import { historyListOpenAtom, leftTabActiveKeyAtom } from '../../atoms';
+import { useChatStore } from '../../store';
import { cn } from '@/lib';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
@@ -11,8 +10,9 @@ import ArchiveHistory from './ArchiveHistory';
import Info from './info';
export default function Side() {
- const [activeKey, setActiveKey] = useAtom(leftTabActiveKeyAtom);
- const setHistoryListOpen = useSetAtom(historyListOpenAtom);
+ const activeKey = useChatStore((state) => state.leftTabActiveKey);
+ const setActiveKey = useChatStore((state) => state.setLeftTabActiveKey);
+ const setHistoryListOpen = useChatStore((state) => state.setHistoryListOpen);
const Component = activeKey === 'info' ? Info : ArchiveHistory;
const router = useRouter();
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx
index 0445293..d653061 100644
--- a/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx
@@ -1,14 +1,13 @@
'use client';
import Side from './Side';
-import { useAtomValue } from 'jotai';
-import { historyListOpenAtom } from '../../atoms';
+import { useChatStore } from '../../store';
import { memo } from 'react';
import { Drawer } from '@/components';
import ChatHistory from './ChatHisory';
const Left = memo(() => {
- const historyListOpen = useAtomValue(historyListOpenAtom);
+ const historyListOpen = useChatStore((state) => state.historyListOpen);
return (
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx
index 0b39086..e0031f7 100644
--- a/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx
@@ -4,15 +4,15 @@ import {
PhoneCallIcon,
PortraitModeIcon,
} from '@/assets/chatacter';
-import { useAtom, useSetAtom } from 'jotai';
-import { isPhoneCallModeAtom, isPortraitModeAtom } from '../../atoms';
+import { useChatStore } from '../../store';
import IconFont from '@/components/ui/iconFont';
import { cn } from '@/lib';
import Image from 'next/image';
export default function Actions() {
- const [isPortraitMode, setIsPortraitMode] = useAtom(isPortraitModeAtom);
- const setIsPhoneCallMode = useSetAtom(isPhoneCallModeAtom);
+ const isPortraitMode = useChatStore((state) => state.isPortraitMode);
+ const setIsPortraitMode = useChatStore((state) => state.setIsPortraitMode);
+ const setIsPhoneCallMode = useChatStore((state) => state.setIsPhoneCallMode);
const className = 'text-[#0066FF] cursor-pointer hover:text-[#4269D6]';
const suggestMessages = [
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/index.tsx
index cef34cf..a6e1afc 100644
--- a/src/app/(main)/character/[id]/chat/ChatMode/index.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/index.tsx
@@ -3,8 +3,7 @@
import Input from './input';
import Actions from './actions';
import ChatList from './ChatList';
-import { useAtom } from 'jotai';
-import { settingOpenAtom } from '../atoms';
+import { useChatStore } from '../store';
import SettingForm from './Right';
import { Drawer } from '@/components';
import Left from './Left';
@@ -12,7 +11,8 @@ import { ExitFullScreenIcon, FullScreenIcon } from '@/assets/common';
import { cn } from '@/lib';
export default function Main() {
- const [settingOpen, setSettingOpen] = useAtom(settingOpenAtom);
+ const settingOpen = useChatStore((state) => state.settingOpen);
+ const setSettingOpen = useChatStore((state) => state.setSettingOpen);
return (
<>
diff --git a/src/app/(main)/character/[id]/chat/Context/index.tsx b/src/app/(main)/character/[id]/chat/Context/index.tsx
deleted file mode 100644
index d202d6b..0000000
--- a/src/app/(main)/character/[id]/chat/Context/index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-'use client';
-
-import { createContext } from 'react';
-
-interface ChatContextType {}
-
-export const ChatContext = createContext
({});
-
-export default function ChatContextProvider({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return {children};
-}
diff --git a/src/app/(main)/character/[id]/chat/Main/actions/index.tsx b/src/app/(main)/character/[id]/chat/Main/actions/index.tsx
new file mode 100644
index 0000000..e0031f7
--- /dev/null
+++ b/src/app/(main)/character/[id]/chat/Main/actions/index.tsx
@@ -0,0 +1,75 @@
+'use client';
+import {
+ GenerateInputIcon,
+ PhoneCallIcon,
+ PortraitModeIcon,
+} from '@/assets/chatacter';
+import { useChatStore } from '../../store';
+import IconFont from '@/components/ui/iconFont';
+import { cn } from '@/lib';
+import Image from 'next/image';
+
+export default function Actions() {
+ const isPortraitMode = useChatStore((state) => state.isPortraitMode);
+ const setIsPortraitMode = useChatStore((state) => state.setIsPortraitMode);
+ const setIsPhoneCallMode = useChatStore((state) => state.setIsPhoneCallMode);
+ const className = 'text-[#0066FF] cursor-pointer hover:text-[#4269D6]';
+
+ const suggestMessages = [
+ 'The threads of fate intertwine once more...The threads of fate intert',
+ 'The threads of fate intertwine once more.',
+ 'The threads of fate intertwine once more.',
+ ];
+
+ return (
+
+
+ {suggestMessages.map((message, index) => (
+
+ {message}
+
+ ))}
+
+
+
+
+ {/* action */}
+
+
null} className="flex items-center gap-5">
+
+
+
+
setIsPhoneCallMode(true)}
+ className={cn(className, 'relative')}
+ >
+
+
+
+
+
setIsPortraitMode(!isPortraitMode)}
+ className="hover:cursor-pointer"
+ >
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx b/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx
index 30ca0be..b83fe64 100644
--- a/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx
+++ b/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx
@@ -1,5 +1,68 @@
'use client';
+import IconFont from '@/components/ui/iconFont';
+import { cn } from '@/lib';
+import Image from 'next/image';
+import { useState } from 'react';
+import { useChatStore } from '../store';
+
+const message1 =
+ 'The threads of fate intertwine once more... I have been awaiting your arrival, seeker.The threads of fate intertwine once more... I have been awaiting your arrival, seeker.The threads of fate intertwine once more... I have been awaiting your arrival, seeker.';
+const message2 =
+ 'The threads of fate intertwine once more... I have been awaiting your arrival, seeker.The threads of fate intertwine once more';
+
export default function PhoneCallMode() {
- return PhoneCallMode
;
+ const setIsPhoneCallMode = useChatStore((state) => state.setIsPhoneCallMode);
+ const [isTextVisible, setIsTextVisible] = useState(true);
+
+ return (
+ <>
+
+
+
+
+ {'Character 1 · 18'}
+
+
{'00:01'}
+
+
+ {isTextVisible && (
+ <>
+
{message1}
+
{message2}
+ >
+ )}
+
+
+ Tag to top
+
+
setIsPhoneCallMode(false)}
+ className={cn(
+ 'flex-center mb-25 h-20 w-20 cursor-pointer rounded-full bg-white/10',
+ 'bg-[linear-gradient(180deg,rgba(255,59,48,1)0%,rgba(222,46,36,1)100%)]'
+ )}
+ >
+
+
+
+
+ {/* 显示文本按钮 */}
+ setIsTextVisible(!isTextVisible)}
+ className={cn(
+ 'flex-center absolute top-8 right-10 h-10 w-10 cursor-pointer rounded-full',
+ 'bg-white/10 hover:bg-white/20'
+ )}
+ >
+
+
+ >
+ );
}
diff --git a/src/app/(main)/character/[id]/chat/atoms.ts b/src/app/(main)/character/[id]/chat/atoms.ts
deleted file mode 100644
index f782d6c..0000000
--- a/src/app/(main)/character/[id]/chat/atoms.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { atom } from 'jotai';
-
-// 是否打开两侧的设置
-export const settingOpenAtom = atom(true);
-
-// 是否是立绘模式
-export const isPortraitModeAtom = atom(false);
-
-// 是否是通话模式
-export const isPhoneCallModeAtom = atom(false);
-
-// 左侧 tab active key
-export const leftTabActiveKeyAtom = atom<'info' | 'history'>('info');
-
-// 左侧 角色历史列表
-export const historyListOpenAtom = atom(false);
diff --git a/src/app/(main)/character/[id]/chat/page.tsx b/src/app/(main)/character/[id]/chat/page.tsx
index 061a93d..fa3ec50 100644
--- a/src/app/(main)/character/[id]/chat/page.tsx
+++ b/src/app/(main)/character/[id]/chat/page.tsx
@@ -1,26 +1,22 @@
'use client';
-import { useAtomValue } from 'jotai';
-import { isPhoneCallModeAtom } from './atoms';
+import { useChatStore } from './store';
import ChatMode from './ChatMode';
import './index.css';
import PhoneCallMode from './PhoneCallMode';
-import ChatContextProvider from './Context';
export default function CharacterChat() {
- const isPhoneCallMode = useAtomValue(isPhoneCallModeAtom);
+ const isPhoneCallMode = useChatStore((state) => state.isPhoneCallMode);
return (
-
-
- {isPhoneCallMode ?
:
}
-
-
+
+ {isPhoneCallMode ?
:
}
+
);
}
diff --git a/src/app/(main)/character/[id]/chat/store.ts b/src/app/(main)/character/[id]/chat/store.ts
new file mode 100644
index 0000000..3bb1913
--- /dev/null
+++ b/src/app/(main)/character/[id]/chat/store.ts
@@ -0,0 +1,27 @@
+import { create } from 'zustand';
+
+export const useChatStore = create<{
+ // UI state
+ isPhoneCallMode: boolean;
+ setIsPhoneCallMode: (isPhoneCallMode: boolean) => void;
+ settingOpen: boolean;
+ setSettingOpen: (settingOpen: boolean) => void;
+ isPortraitMode: boolean;
+ setIsPortraitMode: (isPortraitMode: boolean) => void;
+ leftTabActiveKey: 'info' | 'history';
+ setLeftTabActiveKey: (leftTabActiveKey: 'info' | 'history') => void;
+ historyListOpen: boolean;
+ setHistoryListOpen: (historyListOpen: boolean) => void;
+ // data state
+}>((set) => ({
+ isPhoneCallMode: false,
+ setIsPhoneCallMode: (isPhoneCallMode) => set({ isPhoneCallMode }),
+ settingOpen: false,
+ setSettingOpen: (settingOpen) => set({ settingOpen }),
+ isPortraitMode: false,
+ setIsPortraitMode: (isPortraitMode) => set({ isPortraitMode }),
+ leftTabActiveKey: 'info',
+ setLeftTabActiveKey: (leftTabActiveKey) => set({ leftTabActiveKey }),
+ historyListOpen: false,
+ setHistoryListOpen: (historyListOpen) => set({ historyListOpen }),
+}));
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 99c60de..07514b6 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,7 +1,7 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
-import GlobalContainer from '@/layouts/GlobalContainer';
+import Providers from '@/layouts/Providers';
import Script from 'next/script';
const geistSans = Geist({
@@ -30,11 +30,11 @@ export default function RootLayout({
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
- {children}
+ {children}