h^v**X=^;%^?>=ASm&U}6Cx2Q8r8~mm4C#IS2imon{4JIT|6(K`XbN2#X01Z2crrgJXOZF)R={xDJq
zgQVhrE#KK(x@YFYY*T2w2lj%OlN=}kdb44%Qq
zXa*;c5{dUf-Ow8*jwCTK&)vjyN};GqrPnYMfo2=DN!_m2T7ZIcj%X0TOGlm!oKxeR
z-{%0bcNq#nd}s{RRC_E=ryQ0+x|Wu3hyM8ZYEx_?DKj#v9Vb+7;0d$*Kj;7YwZwvUFC0Rn;UrS6sQthGQ{9Tp_f?7uC%1||J$iNP
z(y3XCR&CmKXj=RSuckq%kR?IX06DG|;&l%C_>l7u4(5aO01H5RfrYS9EP_phJZy1L
z0BHt_AT2-%q!m~Un?5W7X$O{qbO6ghntU*Nv};
zXZ5Jlnv_1fI%HPV#9gZm!Z{^X5^^fLt(Km^42oGK@g_8lz<{JJqdO4J{1M!{b_que
zdfRlngT%TUY{LN%)x9Z6E%e&8N9?}Y7?Sr!GkziJmJ(Cwa|3)PxuLd-(p6|`7P?D3
z;}$+*p}!e4Co+~NRq~O9K)~WgvCM?zY}qn{Slqnu2Y4U;fa1?A?NaJ`m2|lwX}nHV
zKFF#z35{SlRC5t#itQJTpbEA*AJGkb4OBvjDF|E0T_oaHfcGj`4udt8C01TRa_62ZeVz&
z=FE45Vze&>CY!r57poF_`pUJL64f*%-I+gg<>xGEIp`V92k50M{@lED_>hkd8_5-H
zC-q{Mc&ekc!j%EVkHeQf-D4(2?OA!-jB43HQ)pV!&qRgnLZ2c
zDAJ;>cqM71Rw2nEA3AAY(+JC97pQ`OO)s~0E{?pRp?~)3@v4g#f_lUpriP%F`(4S|
zIBXGahwZ0=AQ~Nwv@=mW)^szR5WFyGR=xAZbj7x$Oxm~Wsg+Jh&U4)h%THnHlnZxW
z)Z_fC;rFBxpsf<$DRqOP0h>lUs-Qwam4f79T;OyTm*lt*lCRVt4orKbce;a
z&Kak_M-SfMSNDwjuOZ;U8j|GeGKRc;s5qu-CRc3nLKG#E-c9pGX{l_+c9eyAWHnX4
z=d-!kKTJGO)U~-DW%ae_BZNaxy47yR1*!624#_f|kOx%n>Z+L!UniVZGnM2Iq~~R%
zpJgE(NcGl}&hcu(@lt$Fz~IcJYm3_PR=3nBC05t2DXMnTvfZT6HeA0>Q=TK~$IAq*
zeODl8@i}xLN70MKv#9&Cbt>nw~}9qej(@
zFofW`*2Bc`yL2oy!jSl5ykjlOI1LR+i{|xfbpbYn=Isr%C>Ct37
za{rGZ(x-S?8F%QSXINeb{VoDeloNG+%w_wzK4-G&K~YlhF7#_Jq-d+t6~3{@m(y?G
zT=Tk#r&NqsLUK}1#O}b9{=H$D-(Fij0gB4ev5E`A>u6UK{Xml;vkvZ3O7JTy0q7NF
zIl1U~8(ZQ~t%o5?tJ*8St=S_hwiC9eG#PlYjNo(TV-;I7YUL32P~Os*P?|*UO2>h|(aCmyk7gl{^dA
zX+=s$RH(+0n#+{B5d~3IkIv_cC(9^>_P<68U60vWxHv%3)NSB*-GA{IElYr;!TO5fsvCZe>JD*p54pL*e&7J
zvHMNOEgx4KMVNf~6Btg{(zP#hZq>hf_?n>_&_3C{Nd`xCt@MCF(L(*rW>XR=A}K6^
z*bbveAY1S4A-%-P!+wsd;$gLv{-D#-z0agO*fS7td)wC(DAhIH*7a?w5hUon_J9{Z
zS@r6%HcD;vgr)UOU{fUtKcSw$tMKFXI%uG|c_0LvVAvWsq^lptZ}{GLTpEg0cOgrQ
zODh6>wis`_kG9vgfp!aSd6g`z?g~eaNrCt#?CxT;b}P{0-4?GmWkFOXT4)^j^zqHq
zQR)-4N#oUgkbf4X+5ekT6s
z!Ube`Aj`;wu|Ki{Ho$ZR+l);niOs||9&M}`-LiS{4f4>$=4AG){3R`4Y7I0RP!J?s
z`FqVGSfEndNUm&aBV^p%o&O}#@%NKc_y!SU&|vU06n;s7V?ZIby}6KKY6j(VV_zA$m`opl46_s^zFk1X6ARvE&^tQlj2DN+W8aKA3S24S8E?@=
zki!8*VI!kf!$Q&K0fen(va~Gy!_J)D^AYwVfng17rXz$zoi1rna~H&A;)PsmUdy@j
zpT$S#*NBbhitjI95CK2O`b)a;=F0W!E6wVvJYP&O^G=Je-vLNhaQ+#_HD*VTnf
zfR}OcnHi`vuP+CcOv{8-2)FhsQ^8~H?JGdOCZ*WKi$7gEhdHl?suTk#yAAfSG5Uzy_uMNkm0eF+MpU^*E{`vRvFDoCf{IdPU
zp8Ppze4-vVQk9g+U#1h0z+!uXN=
z3Gn?^qlyYsC@@vket5Ayv1!vTE1LAEtGA0pJ+P^M3-upcU(f60Z_7_`k
zZ(56{ZJ}Ot?Gw7)3u!yB3Hf1q+}W7Uqw03vmKlF9TJmZsRckMld~3}8$??K_|2D>o
z>d|T(+yeM}kg6>HOk~*F-1lpJdnu&9_#FL{6c3?gRe#JVEK6uMim3zSNViXl672aR
ztqP5LoHa>ud7(Mur@buu{|$
z8TPA~z-2f3t2LgWCz3=b!!aK(8ub#qkx+o3-i*#OG!!C)BcW9uSCl;%jfO=Wj+fO5
z+Z74`um}%_=xaka0;e^*`niMzdG8$c{(L4LJ|l)WL=-L5i5On-W&H`b*cufqjS4DV
zu^{HK7$2VK((xiZWMo+tK%m|+$)RmU2tk-#wu*=A?6D2TC<_ze7 s.response);
const router = useRouter();
useEffect(() => {
diff --git a/src/app/(main)/chat/[id]/Background.tsx b/src/app/(main)/chat/[id]/Background.tsx
index 6864821..46bac6f 100644
--- a/src/app/(main)/chat/[id]/Background.tsx
+++ b/src/app/(main)/chat/[id]/Background.tsx
@@ -9,7 +9,7 @@ function Background({ imageUrl }: { imageUrl: string }) {
return (
-
+
{imageUrl && (

)}
{
const [isFullIntroduction, setIsFullIntroduction] = useState(false);
const [shouldShowExpandButton, setShouldShowExpandButton] = useState(false);
const textRef = useRef
(null);
@@ -50,7 +50,7 @@ function ChatMessageUserHeader() {
}, [character.description]);
return (
-
+
Content generated by AI
);
-}
+});
-export default React.memo(ChatMessageUserHeader);
+export default ChatMessageUserHeader;
diff --git a/src/app/(main)/chat/[id]/Drawer/MaskCreate.tsx b/src/app/(main)/chat/[id]/Drawer/MaskCreate.tsx
new file mode 100644
index 0000000..6c826be
--- /dev/null
+++ b/src/app/(main)/chat/[id]/Drawer/MaskCreate.tsx
@@ -0,0 +1,12 @@
+'use client';
+
+import { MaskForm } from '@/app/(main)/profile/mask/MaskForm';
+import { ActiveTabType } from '.';
+
+export default function MaskCreate({ onActiveTab }: { onActiveTab: (tab: ActiveTabType) => void }) {
+ return (
+
+ onActiveTab('mask')} />
+
+ );
+}
diff --git a/src/app/(main)/chat/[id]/Drawer/MaskList.tsx b/src/app/(main)/chat/[id]/Drawer/MaskList.tsx
new file mode 100644
index 0000000..6cece20
--- /dev/null
+++ b/src/app/(main)/chat/[id]/Drawer/MaskList.tsx
@@ -0,0 +1,13 @@
+'use client';
+
+import MaskList from '@/app/(main)/profile/mask/MaskList';
+import { ActiveTabType } from '.';
+
+export default function Personal({ onActiveTab }: { onActiveTab: (tab: ActiveTabType) => void }) {
+ // const router = useRouter();
+ return (
+
+ onActiveTab('mask_create')} />
+
+ );
+}
diff --git a/src/app/(main)/chat/[id]/Drawer/Personal.tsx b/src/app/(main)/chat/[id]/Drawer/Personal.tsx
deleted file mode 100644
index 5f4efcb..0000000
--- a/src/app/(main)/chat/[id]/Drawer/Personal.tsx
+++ /dev/null
@@ -1,308 +0,0 @@
-'use client';
-import { useEffect, useState, useCallback } from 'react';
-import { z } from 'zod';
-import dayjs from 'dayjs';
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from '@/components/ui/form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { useForm } from 'react-hook-form';
-import { Gender } from '@/types/user';
-import { Input } from '@/components/ui/input';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
-import { Label } from '@/components/ui/label';
-import { calculateAge, getDaysInMonth } from '@/lib/utils';
-import { Textarea } from '@/components/ui/textarea';
-import { Button } from '@/components/ui/button';
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from '@/components/ui/alert-dialog';
-
-const currentYear = dayjs().year();
-const years = Array.from({ length: currentYear - 1950 + 1 }, (_, i) => `${1950 + i}`);
-const months = Array.from({ length: 12 }, (_, i) => `${i + 1}`.padStart(2, '0'));
-const monthTexts = Array.from({ length: 12 }, (_, i) => dayjs().month(i).format('MMM'));
-
-const characterFormSchema = z
- .object({
- nickname: z
- .string()
- .trim()
- .min(1, 'Please Enter nickname')
- .min(2, 'Nickname must be between 2 and 20 characters')
- .max(20, 'Nickname must be less than 20 characters'),
- sex: z.enum(Gender, { message: 'Please select gender' }),
- year: z.string().min(1, 'Please select year'),
- month: z.string().min(1, 'Please select month'),
- day: z.string().min(1, 'Please select day'),
- profile: z.string().trim().optional(),
- })
- .refine(
- (data) => {
- const age = calculateAge(data.year, data.month, data.day);
- return age >= 18;
- },
- {
- message: 'Character age must be at least 18 years old',
- path: ['year'],
- }
- )
- .refine(
- (data) => {
- if (data.profile) {
- if (data.profile.trim().length > 300) {
- return false;
- }
- return data.profile.trim().length >= 10;
- }
- return true;
- },
- {
- message: 'At least 10 characters',
- path: ['profile'],
- }
- );
-
-export default function Personal() {
- // 静态数据,模拟从接口获取的数据
- const chatSettingData = {
- nickname: 'John',
- sex: Gender.MALE,
- birthday: dayjs('1995-06-15').valueOf(),
- whoAmI: 'A creative and passionate developer',
- };
-
- const birthday = chatSettingData?.birthday ? dayjs(chatSettingData.birthday) : undefined;
-
- const form = useForm>({
- resolver: zodResolver(characterFormSchema),
- defaultValues: {
- nickname: chatSettingData?.nickname || '',
- sex: chatSettingData?.sex,
- year: birthday?.year().toString() || undefined,
- month:
- birthday?.month() !== undefined
- ? (birthday.month() + 1).toString().padStart(2, '0')
- : undefined,
- day: birthday?.date().toString().padStart(2, '0') || undefined,
- profile: chatSettingData?.whoAmI || '',
- },
- });
-
- const selectedYear = form.watch('year');
- const selectedMonth = form.watch('month');
- const days = selectedYear && selectedMonth ? getDaysInMonth(selectedYear, selectedMonth) : [];
-
- const genderTexts = [
- {
- value: Gender.MALE,
- label: 'Male',
- },
- {
- value: Gender.FEMALE,
- label: 'Female',
- },
- {
- value: Gender.OTHER,
- label: 'Other',
- },
- ];
-
- const gender = form.watch('sex');
- const genderText = genderTexts.find((text) => text.value === gender)?.label;
-
- return (
- <>
-
-
-
-
- {/*
-
-
-
*/}
-
-
- {/* 确认放弃修改的对话框 */}
- {/*
-
-
- Unsaved Edits
-
- The edited content will not be saved after exiting. Please confirm whether to continue
- exiting?
-
-
-
- setShowConfirmDialog(false)}>
- Cancel
-
-
- Exit
-
-
-
- */}
- >
- );
-}
diff --git a/src/app/(main)/chat/[id]/Drawer/Profile.tsx b/src/app/(main)/chat/[id]/Drawer/Profile.tsx
index aecfbc4..47b2a59 100644
--- a/src/app/(main)/chat/[id]/Drawer/Profile.tsx
+++ b/src/app/(main)/chat/[id]/Drawer/Profile.tsx
@@ -29,10 +29,10 @@ const ChatProfilePersona = React.memo(({ onActiveTab }: ProfileProps) => {
return (
-
My Chat Persona
+
Masked Identity Mode
onActiveTab('personal')}
+ onClick={() => onActiveTab('mask')}
>
Edit
diff --git a/src/app/(main)/chat/[id]/Drawer/index.tsx b/src/app/(main)/chat/[id]/Drawer/index.tsx
index 5a7888f..a682b05 100644
--- a/src/app/(main)/chat/[id]/Drawer/index.tsx
+++ b/src/app/(main)/chat/[id]/Drawer/index.tsx
@@ -1,6 +1,6 @@
'use client';
import Profile from './Profile';
-import Personal from './Personal';
+import MaskList from './MaskList';
import VoiceActor from './VoiceActor';
import Font from './Font';
import MaxToken from './MaxToken';
@@ -19,6 +19,8 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { useStreamChatStore } from '../stream-chat';
+import IconFont from '@/components/ui/iconFont';
+import MaskCreate from './MaskCreate';
type SettingProps = {
open: boolean;
@@ -26,7 +28,8 @@ type SettingProps = {
};
export type ActiveTabType =
| 'profile'
- | 'personal'
+ | 'mask'
+ | 'mask_create'
| 'history'
| 'voice_actor'
| 'font'
@@ -35,7 +38,8 @@ export type ActiveTabType =
| 'model';
const titleMap = {
- personal: 'Personal',
+ mask: 'Masked Identity Mode',
+ mask_create: 'Create Mask',
history: 'History',
voice_actor: 'Voice Actor',
font: 'Font',
@@ -44,6 +48,17 @@ const titleMap = {
model: 'Chat Model',
};
+const backMap = {
+ mask: 'profile',
+ mask_create: 'mask',
+ history: 'profile',
+ voice_actor: 'profile',
+ font: 'profile',
+ max_token: 'profile',
+ background: 'profile',
+ model: 'profile',
+} as const;
+
export default function SettingDialog({ open, onOpenChange }: SettingProps) {
const [activeTab, setActiveTab] = useState
('profile');
const updateUserChatSetting = useStreamChatStore((store) => store.updateUserChatSetting);
@@ -65,14 +80,19 @@ export default function SettingDialog({ open, onOpenChange }: SettingProps) {
titleMap[activeTab]
)}
{activeTab !== 'profile' && (
- setActiveTab('profile')}>
-
+ setActiveTab(backMap[activeTab])}
+ >
+
)}
{activeTab === 'profile' &&
}
- {activeTab === 'personal' &&
}
+ {activeTab === 'mask' &&
}
+ {activeTab === 'mask_create' &&
}
{activeTab === 'voice_actor' &&
}
{activeTab === 'font' &&
}
{activeTab === 'max_token' &&
}
diff --git a/src/app/(main)/chat/[id]/Input.tsx b/src/app/(main)/chat/[id]/Input.tsx
index 8ac2efd..0c00fa8 100644
--- a/src/app/(main)/chat/[id]/Input.tsx
+++ b/src/app/(main)/chat/[id]/Input.tsx
@@ -5,6 +5,7 @@ import { useAsyncFn } from '@/hooks/tools';
import { cn } from '@/lib/utils';
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
import { useState, useRef, useEffect } from 'react';
+import IconFont from '@/components/ui/iconFont';
const AuthHeightTextarea = (
props: React.ComponentProps<'textarea'> & {
@@ -90,7 +91,9 @@ export default function Input() {
{/* 打电话按钮 */}
-
{}} iconfont="icon-gift-border" />
+ {}}>
+
+
{/* 语音录制按钮 */}
();
@@ -34,11 +35,11 @@ export default function ChatPage() {
setSettingOpen(!settingOpen)}
- className="absolute top-2 right-2"
+ className="absolute z-10 top-2 right-2"
variant="ghost"
size="small"
>
-
+
diff --git a/src/app/(main)/home/components/Character/index.tsx b/src/app/(main)/home/components/Character/index.tsx
index 3c57a18..b167fdd 100644
--- a/src/app/(main)/home/components/Character/index.tsx
+++ b/src/app/(main)/home/components/Character/index.tsx
@@ -23,6 +23,8 @@ const Character = () => {
items={dataSource}
+ enableLazyRender
+ lazyRenderMargin="500px"
columns={(width) => {
const cardWidth = width > 1200 ? 256 : width > 588 ? 200 : width > 375 ? 170 : 150;
return Math.floor(width / cardWidth);
diff --git a/src/app/(main)/home/components/Filter.tsx b/src/app/(main)/home/components/Filter.tsx
index 9d6accf..ff8555d 100644
--- a/src/app/(main)/home/components/Filter.tsx
+++ b/src/app/(main)/home/components/Filter.tsx
@@ -7,6 +7,7 @@ import { useHomeStore } from '../store';
import { useQuery } from '@tanstack/react-query';
import { fetchCharacterTags } from '@/services/editor';
import { useRef } from 'react';
+import { useTranslations } from 'next-intl';
const Filter = () => {
const tab = useHomeStore((state) => state.tab);
@@ -14,6 +15,7 @@ const Filter = () => {
const ref = useRef(null);
const selectedTags = useHomeStore((state) => state.selectedTags);
const setSelectedTags = useHomeStore((state) => state.setSelectedTags);
+ const t = useTranslations('home');
// useEffect(() => {
// const mainContent = document.getElementById('main-content');
@@ -48,13 +50,13 @@ const Filter = () => {
const tabs = [
{
- label: 'Story',
+ label: t('story'),
value: 'story',
icon: 'icon-story',
activeIcon: 'icon-story-active',
},
{
- label: 'Character',
+ label: t('character'),
value: 'character',
icon: 'icon-character',
activeIcon: 'icon-character-active',
diff --git a/src/app/(main)/home/components/Header.tsx b/src/app/(main)/home/components/Header.tsx
index 6e28607..4638aab 100644
--- a/src/app/(main)/home/components/Header.tsx
+++ b/src/app/(main)/home/components/Header.tsx
@@ -4,10 +4,10 @@ import Image from 'next/image';
import { IconButton } from '@/components/ui/button';
import Link from 'next/link';
import React from 'react';
-import { useMedia } from '@/hooks/tools';
+import { useLayoutStore } from '@/stores';
const Header = React.memo(() => {
- const response = useMedia();
+ const response = useLayoutStore((s) => s.response);
return (
//
diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx
index 530c3b3..c935d73 100644
--- a/src/app/(main)/home/page.tsx
+++ b/src/app/(main)/home/page.tsx
@@ -6,11 +6,11 @@ import Story from './components/Story';
import Character from './components/Character';
import Filter from './components/Filter';
import { useHomeStore } from './store';
-import { useMedia } from '@/hooks/tools';
+import { useLayoutStore } from '@/stores';
const HomePage = () => {
const tab = useHomeStore((state) => state.tab);
- const response = useMedia();
+ const response = useLayoutStore((s) => s.response);
return (
<>
diff --git a/src/app/(main)/profile/account/account-page.tsx b/src/app/(main)/profile/account/account-page.tsx
index e1a5315..022842b 100644
--- a/src/app/(main)/profile/account/account-page.tsx
+++ b/src/app/(main)/profile/account/account-page.tsx
@@ -14,6 +14,7 @@ import {
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { IconButton, Button } from '@/components/ui/button';
import { useCurrentUser, useDeleteUser } from '@/hooks/auth';
+import ProfileLayout from '@/layout/ProfileLayout';
import { ThirdType } from '@/services/auth';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
@@ -25,10 +26,6 @@ const AccountPage = () => {
const [isDisabling, setIsDisabling] = useState(false);
const { mutateAsync: deleteUser } = useDeleteUser();
- const handleBack = () => {
- router.back();
- };
-
const handleDisableAccount = async () => {
setIsDisabling(true);
try {
@@ -56,15 +53,7 @@ const AccountPage = () => {
};
return (
-
- {/* 标题栏 */}
-
-
-
-
-
Account
-
-
+
{/* 账户信息容器 */}
@@ -114,7 +103,7 @@ const AccountPage = () => {
-
+
);
};
diff --git a/src/app/(main)/profile/components/CharacterCard.tsx b/src/app/(main)/profile/components/CharacterCard.tsx
deleted file mode 100644
index b51ad5c..0000000
--- a/src/app/(main)/profile/components/CharacterCard.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { Tag } from '@/components/ui/tag'
-import { cn, formatNumberToKMB } from '@/lib/utils'
-import { AIPermission, AiUserBaseListOutput } from '@/services/create'
-import Image from 'next/image'
-import Link from 'next/link'
-
-const CharacterCard = ({
- character,
- isHovered,
- onHover,
-}: {
- character: AiUserBaseListOutput
- isHovered: boolean
- onHover: (hovered: boolean) => void
-}) => {
- // 根据权限判断是否私密
- const isPrivate = character.permission === AIPermission.Private
-
- return (
-
-
onHover(true)}
- onMouseLeave={() => onHover(false)}
- >
- {/* 角色图片 */}
-
-
-
- {/* 私密标识 */}
-
-
-
-
- {/* 底部遮罩层 */}
-
- {/* 描述文字 - hover时显示,带过渡动效 */}
-
- {character.introduction || ''}
-
-
- {/* 点赞数 - 暂时用固定值,实际应该从API获取 */}
-
- {/*

*/}
-
-
- {formatNumberToKMB(character.likedNum ?? 0)}
-
-
-
-
-
- {/* 角色信息 */}
-
- {/* 角色名称 */}
-
{character.nickname}
-
- {/* 标签 */}
-
- {/* 性格标签 */}
- {character.characterName && {character.characterName}}
-
- {/* 标签 */}
- {character.tagName && {character.tagName}}
-
-
-
-
- )
-}
-
-export default CharacterCard
diff --git a/src/app/(main)/profile/components/CharacterCardAdd.tsx b/src/app/(main)/profile/components/CharacterCardAdd.tsx
deleted file mode 100644
index 40f018d..0000000
--- a/src/app/(main)/profile/components/CharacterCardAdd.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { useCurrentUser } from '@/hooks/auth'
-import { useGetAICharacterList } from '@/hooks/create'
-import useCreatorNavigation from '@/hooks/useCreatorNavigation'
-import Image from 'next/image'
-import { useSetAtom } from 'jotai'
-import { isVipDrawerOpenAtom } from '@/atoms/im'
-import { VipType } from '@/services/wallet'
-
-const CharacterCardAdd = () => {
- const { routerToCreate } = useCreatorNavigation()
- const { data: user } = useCurrentUser()
- const { isMember, canCreateAiCount } = user || {}
- const { data: characters = [] } = useGetAICharacterList()
- const isFull = characters.length >= (canCreateAiCount || 0)
- const setIsVipDrawerOpen = useSetAtom(isVipDrawerOpenAtom)
-
- const handleVip = () => {
- setIsVipDrawerOpen({ open: true, vipType: VipType.ADD_CREATE_AI })
- }
-
- if (isMember && isFull) {
- return null
- }
-
- if (!isMember && isFull) {
- return (
-
-
-
-
-
- Add More Characters
-
-
-
-
- )
- }
-
- return (
-
-
-
-
- Create a Character
-
-
-
- )
-}
-
-export default CharacterCardAdd
diff --git a/src/app/(main)/profile/components/CharacterCardVipAdd.tsx b/src/app/(main)/profile/components/CharacterCardVipAdd.tsx
deleted file mode 100644
index 9468d9a..0000000
--- a/src/app/(main)/profile/components/CharacterCardVipAdd.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-const CharacterCardVipAdd = () => {
- return (
-
-
-
-
- Add More Character
-
-
-
- )
-}
-
-export default CharacterCardVipAdd
diff --git a/src/app/(main)/profile/components/CharacterList.tsx b/src/app/(main)/profile/components/CharacterList.tsx
deleted file mode 100644
index 2616da2..0000000
--- a/src/app/(main)/profile/components/CharacterList.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { useGetAICharacterList } from '@/hooks/create'
-import { useState } from 'react'
-import CharacterCard from './CharacterCard'
-import CharacterCardAdd from './CharacterCardAdd'
-import { useCurrentUser } from '@/hooks/auth'
-import { isVipDrawerOpenAtom } from '@/atoms/im'
-import { useSetAtom } from 'jotai'
-import { VipType } from '@/services/wallet'
-
-const AlbumSkeleton = () => (
-
-)
-
-const CharacterList = () => {
- const { data: characters = [], isPending } = useGetAICharacterList()
- const { data: user } = useCurrentUser()
- const { isMember, canCreateAiCount } = user || {}
- const setIsVipDrawerOpen = useSetAtom(isVipDrawerOpenAtom)
-
- const [hoveredCard, setHoveredCard] = useState
(null)
-
- const handleUnlockMore = () => {
- setIsVipDrawerOpen({ open: true, vipType: VipType.ADD_CREATE_AI })
- }
-
- const renderList = () => {
- if (isPending) {
- return (
-
- {Array.from({ length: 3 }).map((_, index) => (
-
- ))}
-
- )
- }
-
- return (
-
- {characters.map((character) => (
- setHoveredCard(hovered ? (character.aiId ?? null) : null)}
- />
- ))}
-
-
- )
- }
-
- return (
-
- {/* 标题和按钮 */}
-
-
-
Characters
-
- {characters.length}/{canCreateAiCount}
-
-
- {!isMember && (
-
-
-
-

-
- Unlock More
-
-
-
-
- )}
-
-
- {renderList()}
-
- )
-}
-
-export default CharacterList
diff --git a/src/app/(main)/profile/components/ProfileDropdown.tsx b/src/app/(main)/profile/components/ProfileDropdown.tsx
index 8f0b5a4..6e3da29 100644
--- a/src/app/(main)/profile/components/ProfileDropdown.tsx
+++ b/src/app/(main)/profile/components/ProfileDropdown.tsx
@@ -14,13 +14,14 @@ import { useState } from 'react';
import { useLayoutStore } from '@/stores';
import { useStreamChatStore } from '../../chat/[id]/stream-chat';
import { useAsyncFn } from '@/hooks/tools';
+import IconFont from '@/components/ui/iconFont';
const ProfileDropdownItem = ({
icon,
children,
onClick,
}: {
- icon: string;
+ icon: string | React.ReactNode;
children: React.ReactNode;
onClick?: () => void;
}) => {
@@ -30,7 +31,7 @@ const ProfileDropdownItem = ({
className="flex w-full items-center justify-between gap-3 cursor-pointer px-5 py-4 hover:bg-white/5 transition-colors"
>
-
+ {typeof icon === 'string' ?
: icon}
{children}
@@ -61,7 +62,7 @@ const ProfileDropdown = () => {
| {
type: 'item';
label: string;
- icon: string;
+ icon: string | React.ReactNode;
href?: string;
target?: string;
onClick?: () => void;
@@ -73,6 +74,12 @@ const ProfileDropdown = () => {
icon: 'icon-icon_order_remark',
href: '/profile/edit',
},
+ {
+ type: 'item',
+ label: 'Masked Identity Mode',
+ icon: ,
+ href: '/profile/mask',
+ },
{
type: 'item',
label: 'Account',
diff --git a/src/app/(main)/profile/edit/page.tsx b/src/app/(main)/profile/edit/page.tsx
index 73ff8d1..5062144 100644
--- a/src/app/(main)/profile/edit/page.tsx
+++ b/src/app/(main)/profile/edit/page.tsx
@@ -27,6 +27,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { calculateAge } from '@/lib/utils';
import dayjs from 'dayjs';
+import ProfileLayout from '@/layout/ProfileLayout';
const schema = z
.object({
@@ -196,15 +197,7 @@ const EditPage = () => {
}, [selectedYear, selectedMonth, selectedDay, form]);
return (
-
- {/* 标题栏 */}
-
-
-
-
-
Edit Profile
-
-
+
{/* 表单容器 */}
-
+
);
};
diff --git a/src/app/(main)/profile/mask/MaskForm.tsx b/src/app/(main)/profile/mask/MaskForm.tsx
new file mode 100644
index 0000000..97ca3cc
--- /dev/null
+++ b/src/app/(main)/profile/mask/MaskForm.tsx
@@ -0,0 +1,155 @@
+'use client';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import * as z from 'zod';
+import { Gender } from '@/types/user';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useCurrentUser } from '@/hooks/auth';
+import { useAsyncFn } from '@/hooks/tools';
+import { Input } from '@/components/ui/input';
+import GenderInput from '@/components/features/genderInput';
+import { Button } from '@/components/ui/button';
+import { InputNumber } from '@/components/ui/inputnumber';
+import { Textarea } from '@/components/ui/textarea';
+
+const schema = z.object({
+ nickname: z
+ .string()
+ .trim()
+ .min(1, 'Nickname is required')
+ .min(2, 'Nickname must be between 2 and 20 characters'),
+ gender: z.enum(Gender, { message: 'Please select a gender' }),
+ age: z.number().min(1, 'Age is required'),
+ whoAmI: z.string(),
+});
+
+type MaskFormProps = {
+ onSubmitSuccess?: (data: any) => void;
+};
+
+export const MaskForm = ({ onSubmitSuccess }: MaskFormProps) => {
+ const { data: user } = useCurrentUser();
+ const form = useForm>({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ nickname: user?.nickname || '',
+ gender: user?.sex,
+ age: 18,
+ whoAmI: '',
+ },
+ mode: 'onChange',
+ });
+
+ const { run: onSubmitFn, loading } = useAsyncFn(async (data: any) => {
+ onSubmitSuccess?.(data);
+ });
+
+ const {
+ formState: { isValid, isDirty },
+ } = form;
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/app/(main)/profile/mask/MaskList.tsx b/src/app/(main)/profile/mask/MaskList.tsx
new file mode 100644
index 0000000..32a555f
--- /dev/null
+++ b/src/app/(main)/profile/mask/MaskList.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import { Gender } from '@/types/user';
+import { useCurrentUser } from '@/hooks/auth';
+import { useRouter } from 'next/navigation';
+import { Checkbox } from '@/components/ui/checkbox';
+import { Button } from '@/components/ui/button';
+type MaskListProps = {
+ selectAble?: boolean;
+ value?: null;
+ onChange?: (mask: any) => void;
+ onAdd?: () => void;
+};
+
+export default function MaskList(props: MaskListProps) {
+ const { selectAble, value, onChange, onAdd } = props;
+ const { data: user } = useCurrentUser();
+ const router = useRouter();
+
+ const masks = [
+ {
+ nickname: 'NickName',
+ gender: 0,
+ age: 18,
+ whoAmI: ' whoAmI whoAmI whoAmI whoAmI whoAmI whoAmI whoAmI whoAmI',
+ },
+ ];
+
+ const handleSelect = (mask: any) => {
+ if (selectAble) {
+ onChange?.(mask);
+ } else {
+ router.push(`/profile/mask/${mask.nickname}`);
+ }
+ };
+
+ const iconRender = (mask: any) => {
+ if (selectAble) {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+ return (
+
+ {masks.map((mask) => (
+
+
handleSelect(mask)}
+ className="flex bg-white/10 px-4 py-3 items-center justify-between"
+ >
+
+ {mask.nickname}、{mask.gender === Gender.MALE ? 'Male' : 'Female'}、{mask.age}
+
+ {iconRender(mask)}
+
+
{mask.whoAmI}
+
+ ))}
+ {onAdd && (
+
+
+
+ )}
+
+ );
+}
diff --git a/src/app/(main)/profile/mask/[id]/page.tsx b/src/app/(main)/profile/mask/[id]/page.tsx
new file mode 100644
index 0000000..421ac66
--- /dev/null
+++ b/src/app/(main)/profile/mask/[id]/page.tsx
@@ -0,0 +1,19 @@
+'use client';
+import { useCurrentUser } from '@/hooks/auth';
+import { MaskForm } from '../MaskForm';
+import { IconButton } from '@/components/ui/button';
+import { useRouter } from 'next/navigation';
+import ProfileLayout from '@/layout/ProfileLayout';
+
+export default function MaskPage() {
+ const { data: user } = useCurrentUser();
+ const router = useRouter();
+
+ return (
+
+
+ router.push(`/profile/mask`)} />
+
+
+ );
+}
diff --git a/src/app/(main)/profile/mask/page.tsx b/src/app/(main)/profile/mask/page.tsx
new file mode 100644
index 0000000..757dfd0
--- /dev/null
+++ b/src/app/(main)/profile/mask/page.tsx
@@ -0,0 +1,41 @@
+'use client';
+import { Button, IconButton } from '@/components/ui/button';
+import { useRouter } from 'next/navigation';
+import ProfileLayout from '@/layout/ProfileLayout';
+import Link from 'next/link';
+import IconFont from '@/components/ui/iconFont';
+import { useLayoutStore } from '@/stores';
+import MaskList from './MaskList';
+
+export default function MaskPage() {
+ const response = useLayoutStore((s) => s.response);
+
+ const router = useRouter();
+
+ return (
+
+
+
+ }
+ rightDom={
+ response?.isPC && (
+ router.push('/profile/mask/new')}
+ size={32}
+ className="text-white cursor-pointer"
+ type="icon-tianjia"
+ />
+ )
+ }
+ title="Masked Identity Mode"
+ >
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/profile/page.tsx b/src/app/(main)/profile/page.tsx
index 546e01f..3c39208 100644
--- a/src/app/(main)/profile/page.tsx
+++ b/src/app/(main)/profile/page.tsx
@@ -82,8 +82,6 @@ export default function ProfilePage() {
- {/* */}
-
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 7f7edd4..e74e4b2 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -4,9 +4,9 @@ import localFont from 'next/font/local';
import '../css/iconfont.css';
import '../css/iconfont-v2.css';
import './globals.css';
-import { Providers } from '@/lib/providers';
-import { DeviceIdProvider } from '@/components/device-id-provider';
-import ProgressBar from '@/context/progress';
+import { Providers } from '@/layout/Providers';
+import ProgressBar from '@/layout/Providers/ProgressBar';
+import Script from 'next/script';
const poppins = Poppins({
variable: '--font-poppins',
@@ -50,11 +50,12 @@ export default async function RootLayout({
-
-
- {children}
-
-
+
+ {children}