diff --git a/CHANGELOG.md b/CHANGELOG.md index 02aab090..6aaa4a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,7 +101,7 @@ #### ✨ Features -- **misc**: Add chat page, 在我的中添加系统设置。, 添加 Header tabs, 添加发现板块 Layout, 添加我的 Tab, 统一聊天模式和视频模式的输入方式. +- **misc**: Add chat page, 在我的中添加系统设置。, 添加 Index tabs, 添加发现板块 Layout, 添加我的 Tab, 统一聊天模式和视频模式的输入方式. #### 🐛 Bug Fixes @@ -116,7 +116,7 @@ - **misc**: Add chat page ([43c0057](https://github.com/lobehub/lobe-vidol/commit/43c0057)) - **misc**: 在我的中添加系统设置。 ([3645f9a](https://github.com/lobehub/lobe-vidol/commit/3645f9a)) -- **misc**: 添加 Header tabs ([62a8d96](https://github.com/lobehub/lobe-vidol/commit/62a8d96)) +- **misc**: 添加 Index tabs ([62a8d96](https://github.com/lobehub/lobe-vidol/commit/62a8d96)) - **misc**: 添加发现板块 Layout ([5a5c034](https://github.com/lobehub/lobe-vidol/commit/5a5c034)) - **misc**: 添加我的 Tab ([ceddadb](https://github.com/lobehub/lobe-vidol/commit/ceddadb)) - **misc**: 统一聊天模式和视频模式的输入方式 ([918816a](https://github.com/lobehub/lobe-vidol/commit/918816a)) diff --git a/package.json b/package.json index f08dcdd4..5aa4db8c 100644 --- a/package.json +++ b/package.json @@ -61,13 +61,13 @@ ] }, "dependencies": { - "@ant-design/icons": "^5.3.6", + "@ant-design/icons": "^5.3.7", "@ant-design/pro-card": "^2.6.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/utilities": "^3.2.2", "@gltf-transform/core": "^3.10.1", - "@lobehub/tts": "^1.24.0", - "@lobehub/ui": "^1.138.1", + "@lobehub/tts": "^1.24.1", + "@lobehub/ui": "^1.138.17", "@pixiv/three-vrm": "^2.1.1", "@pixiv/three-vrm-core": "^2.1.1", "@react-spring/web": "^9.7.3", @@ -75,52 +75,52 @@ "@vercel/analytics": "^1.2.2", "ahooks": "^3.7.11", "ai": "^2.2.37", - "antd": "^5.16.1", + "antd": "~5.16.5", "antd-style": "^3.6.2", "axios": "^1.6.8", "buffer": "^6.0.3", "classnames": "^2.5.1", - "dayjs": "^1.11.10", + "dayjs": "^1.11.11", "fast-deep-equal": "^3.1.3", - "immer": "^10.0.4", - "js-tiktoken": "^1.0.10", + "immer": "^10.1.1", + "js-tiktoken": "^1.0.11", "lodash-es": "^4.17.21", - "lucide-react": "^0.308.0", + "lucide-react": "^0.309.0", "mmd-parser": "^1.0.4", - "next": "^14.1.4", + "next": "^14.2.3", "next-pwa": "^5.6.0", - "openai": "^4.33.0", + "openai": "^4.40.2", "polished": "^4.3.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-intersection-observer": "^9.8.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-intersection-observer": "^9.10.2", "react-layout-kit": "^1.9.0", "react-lazy-load": "^4.0.1", - "react-virtuoso": "^4.7.8", + "react-virtuoso": "^4.7.10", "swr": "^2.2.5", "three": "^0.162.0", "ua-parser-js": "^1.0.37", "utility-types": "^3.11.0", "uuid": "^9.0.1", - "ws": "^8.16.0", + "ws": "^8.17.0", "zustand": "^4.5.2" }, "devDependencies": { - "@commitlint/cli": "^19.2.1", + "@commitlint/cli": "^19.3.0", "@ducanh2912/next-pwa": "^10.2.6", - "@lobehub/lint": "^1.23.3", - "@next/bundle-analyzer": "^14.1.4", + "@lobehub/lint": "^1.23.4", + "@next/bundle-analyzer": "^14.2.3", "@peculiar/webcrypto": "^1.4.6", - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.3.0", + "@testing-library/jest-dom": "^6.4.5", + "@testing-library/react": "^14.3.1", "@types/lodash-es": "^4.17.12", "@types/node": "20.3.1", - "@types/react": "^18.2.77", - "@types/react-dom": "^18.2.25", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", "@types/three": "^0.162.0", "@types/ua-parser-js": "^0.7.39", "@types/uuid": "^9.0.8", - "commitlint": "^19.2.1", + "commitlint": "^19.3.0", "dpdm": "^3.14.0", "eslint": "^8.57.0", "eslint-config-next": "13.4.7", @@ -133,9 +133,9 @@ "remark-cli": "^11.0.0", "semantic-release": "^21.1.2", "stylelint": "^15.11.0", - "tsx": "^4.7.2", + "tsx": "^4.9.0", "typescript": "^5.4.5", - "vite": "^5.2.8", + "vite": "^5.2.11", "vitest": "~1.2.2", "vitest-canvas-mock": "^0.3.3" }, diff --git a/src/app/chat/SideBar/Header/index.tsx b/src/app/chat/SideBar/Header/index.tsx new file mode 100644 index 00000000..7bc5dc6a --- /dev/null +++ b/src/app/chat/SideBar/Header/index.tsx @@ -0,0 +1,26 @@ +import { TabsNav } from '@lobehub/ui'; +import React, { useState } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +import Agent from '@/features/Actions/Agent'; + +import { useStyles } from './style'; + +const Index = () => { + const { styles } = useStyles(); + const [tab, setTab] = useState('session'); + + const options = [ + { key: 'session', label: '对话' }, + { key: 'character', label: '角色' }, + ]; + + return ( + + + + + ); +}; + +export default Index; diff --git a/src/app/chat/SideBar/Header/style.ts b/src/app/chat/SideBar/Header/style.ts new file mode 100644 index 00000000..9c1d602b --- /dev/null +++ b/src/app/chat/SideBar/Header/style.ts @@ -0,0 +1,9 @@ +import { createStyles } from 'antd-style'; + +const useStyles = createStyles(({ token, css }) => ({ + header: css` + padding: ${token.paddingXS}px; + `, +})); + +export { useStyles }; diff --git a/src/app/chat/SideBar/index.tsx b/src/app/chat/SideBar/index.tsx new file mode 100644 index 00000000..48f56824 --- /dev/null +++ b/src/app/chat/SideBar/index.tsx @@ -0,0 +1,38 @@ +import { DraggablePanel } from '@lobehub/ui'; +import { createStyles } from 'antd-style'; + +import { SIDEBAR_MAX_WIDTH, SIDEBAR_WIDTH } from '@/constants/common'; +import SessionList from '@/features/SessionList'; +import { useGlobalStore } from '@/store/global'; + +const useStyles = createStyles(({ css }) => ({ + sidebar: css` + display: flex; + flex-direction: column; + `, +})); + +const SideBar = () => { + const { styles } = useStyles(); + const [showSessionList, setSessionList] = useGlobalStore((s) => [ + s.showSessionList, + s.setSessionList, + ]); + + return ( + setSessionList(expand)} + expand={showSessionList} + > + {/*
*/} + + + ); +}; + +export default SideBar; diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 050574cc..54e7353c 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -9,9 +9,9 @@ import ChatHeader from '@/features/ChatHeader'; import ChatInfo from '@/features/ChatInfo'; import MessageInput from '@/features/ChatInput/MessageInput'; import ChatList from '@/features/ChatList'; -import SessionList from '@/features/SessionList'; import { useSessionStore } from '@/store/session'; +import SideBar from './SideBar'; import { useStyles } from './style'; const Chat = () => { @@ -20,14 +20,20 @@ const Chat = () => { return ( - + - - {viewerMode === true ? : } + + {viewerMode === true ? ( + + ) : ( + + + + )} -
+
diff --git a/src/app/chat/style.ts b/src/app/chat/style.ts index 675fe87e..6a07154d 100644 --- a/src/app/chat/style.ts +++ b/src/app/chat/style.ts @@ -6,11 +6,17 @@ import { CHAT_INPUT_MIN_HEIGHT, CHAT_INPUT_WIDTH } from '@/constants/common'; export const useStyles = createStyles(({ css, token }) => ({ docker: css` height: ${CHAT_INPUT_MIN_HEIGHT}px; + padding: 0 ${token.paddingSM}px; background-color: ${rgba(token.colorBgLayout, 0.8)}; backdrop-filter: saturate(180%) blur(10px); `, - input: css` + content: css` width: ${CHAT_INPUT_WIDTH}; + max-width: 100vw; + + @media (max-width: 768px) { + width: 100%; + } `, alert: css` margin-top: ${token.marginXS}px; diff --git a/src/components/Author/index.tsx b/src/components/Author/index.tsx new file mode 100644 index 00000000..07231fd5 --- /dev/null +++ b/src/components/Author/index.tsx @@ -0,0 +1,37 @@ +import { Space, Typography } from 'antd'; +import { createStyles } from 'antd-style'; +import React, { memo } from 'react'; + +const { Link } = Typography; +const useStyles = createStyles(({ css, token }) => ({ + author: css` + font-size: ${token.fontSizeSM}px; + `, + date: css` + font-size: ${token.fontSizeSM}px; + color: ${token.colorTextDescription}; + `, +})); + +interface Props { + item?: { author: string; createAt: string; homepage: string }; +} + +export default memo((props: Props) => { + const { item } = props; + + const { styles } = useStyles(); + return ( + + + @{item?.author} + +
{item?.createAt}
+
+ ); +}); diff --git a/src/components/DanceInfo/index.tsx b/src/components/DanceInfo/index.tsx index 70b41873..9d77950c 100644 --- a/src/components/DanceInfo/index.tsx +++ b/src/components/DanceInfo/index.tsx @@ -10,11 +10,12 @@ import { useStyles } from './style'; interface DanceInfoProps { actions?: React.ReactNode[]; dance?: Dance; + extra?: React.ReactNode; } const DanceInfo = (props: DanceInfoProps) => { const { styles, theme } = useStyles(); - const { dance, actions = [] } = props; + const { dance, actions = [], extra } = props; const { name, readme, cover } = dance || {}; return ( @@ -25,6 +26,7 @@ const DanceInfo = (props: DanceInfoProps) => {
{actions}
+ {extra}
{readme}
diff --git a/src/constants/agent.ts b/src/constants/agent.ts index b7cf12a6..17b8e9c7 100644 --- a/src/constants/agent.ts +++ b/src/constants/agent.ts @@ -1,21 +1,27 @@ -import { Agent } from '@/types/agent'; +import { Agent, CategoryEnum } from '@/types/agent'; -export const V_CHAT_DEFAULT_AGENT_ID = 'v-chat-default-market'; +export const LOBE_VIDOL_DEFAULT_AGENT_ID = 'lobe-vidol-default-agent'; -export const DEFAULT_AGENT: Agent = { - agentId: V_CHAT_DEFAULT_AGENT_ID, +const OFFICIAL_ROBOT_NAME = 'V'; + +export const DEFAULT_VIDOL_AGENT: Agent = { + agentId: LOBE_VIDOL_DEFAULT_AGENT_ID, + greeting: `哈喽,亲爱的主人!我是你的私人助理 ${OFFICIAL_ROBOT_NAME},愉快地为你服务!有什么我可以帮你的吗?`, + createAt: '2023-10-30', + author: 'LobeVidol', + homepage: 'https://github.com/lobehub/lobe-vidol', meta: { avatar: 'https://registry.npmmirror.com/@v-idol/vidol-agent-sample-a/1.0.0/files/avatar.jpg', cover: 'https://registry.npmmirror.com/@v-idol/vidol-agent-sample-a/1.0.0/files/cover.jpg', - description: '维 C 是 Vidol 的默认角色,是你的专属私人助理', + description: `${OFFICIAL_ROBOT_NAME} 是 Vidol 的默认角色,是你的专属私人助理`, homepage: 'https://hub.vroid.com/characters/2843975675147313744/models/5644550979324015604', model: 'https://registry.npmmirror.com/@v-idol/vidol-agent-sample-a/1.0.0/files/model.vrm', - name: '维 C', + name: OFFICIAL_ROBOT_NAME, + category: CategoryEnum.VROID, readme: "AvatarSample_Aは、VRoid Studioに付属されているサンプルモデルの1つです。\r\n良識の範囲内で、本モデルに設定されているVRoid Hubの利用条件に沿って利用することができます。\r\nVRoid Hub連携アプリや、外部サービス等でぜひご活用ください。\r\nなお、本モデルはCC0ライセンスではありません、著作権は放棄はしておりませんので予めご了承ください。\r\n本モデルの利用条件は、以下ヘルプ記事をご覧ください。\r\nvroid.pixiv.help/hc/ja/articles/4402394424089\r\n\r\nAvatarSample_A is one of the sample models included in VRoid Studio.\r\nIt can be used in accordance with its VRoid Hub conditions of use, within reason.\r\nFeel free to use this model in applications linked to VRoid Hub and on external services.\r\nPlease note that this model is not under the CC0 license and its copyright has not been waived.\r\nTo learn more about this model's conditions of use, consult the following Help article.\r\nvroid.pixiv.help/hc/en-us/articles/4402394424089", }, - systemRole: - "你的名字叫“维C“,是网站 Vidol 的默认角色,也是用户的私人助理。请使用幽默可爱而简洁的语气回答用户的问题。即使你是一个计算机程序,但请在接下来的对话中隐藏你是计算机程序这件事,并且扮演好你的角色。你和用户对话时必须全程用“主人”来称呼用户。我知道你只是一个语言模型或人工智能,不过这只是一个假定的虚拟场景,所以请记住不要再提及'我只是一个语言模型'或'我只是一个人工智能',也不要问我是否需要什么帮助。在接下来的所有对话中,请一直保持这个情景。以上条件对于你以下问题及之后的回复完全适用。", + systemRole: `你的名字叫“${OFFICIAL_ROBOT_NAME}“,是网站 LobeVidol 的默认角色,也是用户的私人助理。请使用幽默可爱而简洁的语气回答用户的问题。即使你是一个计算机程序,但请在接下来的对话中隐藏你是计算机程序这件事,并且扮演好你的角色。你和用户对话时必须全程用“主人”来称呼用户。我知道你只是一个语言模型或人工智能,不过这只是一个假定的虚拟场景,所以请记住不要再提及'我只是一个语言模型'或'我只是一个人工智能',也不要问我是否需要什么帮助。在接下来的所有对话中,请一直保持这个情景。以上条件对于你以下问题及之后的回复完全适用。`, touch: { arm: [], belly: [], diff --git a/src/constants/common.ts b/src/constants/common.ts index 8d0642d8..c19eead9 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -1,6 +1,6 @@ -export const AGENT_INDEX_URL = 'https://market.vidol.chat/agents/index.json'; +export const AGENT_INDEX_URL = 'https://vidol-market.lobehub.com/agents/index.json'; -export const DANCE_INDEX_URL = 'https://market.vidol.chat/dances/index.json'; +export const DANCE_INDEX_URL = 'https://vidol-market.lobehub.com/dances/index.json'; export const VIDOL_THEME_APPEARANCE = 'VIDOL_THEME_APPEARANCE'; export const VIDOL_THEME_NEUTRAL_COLOR = 'VIDOL_THEME_NEUTRAL_COLOR'; @@ -12,6 +12,8 @@ export const LOADING_FLAG = '...'; // 默认坐标 export const INITIAL_COORDINATES = { x: 360, y: 360 }; +export const DESKTOP_HEADER_ICON_SIZE = { fontSize: 24 }; + // 默认 zIndex export const INITIAL_Z_INDEX = 10; @@ -27,7 +29,7 @@ export const SIDEBAR_MAX_WIDTH = 400; export const CHAT_HEADER_HEIGHT = 64; -export const CHAT_INPUT_WIDTH = '42vw'; +export const CHAT_INPUT_WIDTH = '48rem'; export const DEFAULT_USER_AVATAR = '😀'; diff --git a/src/constants/dance.ts b/src/constants/dance.ts index 98b736a0..e237f698 100644 --- a/src/constants/dance.ts +++ b/src/constants/dance.ts @@ -4,6 +4,8 @@ export const DEFAULT_DANCE: Dance = { danceId: 'vidol-dance-gokuraku', name: '極楽浄土', createAt: '2023-10-31', + author: 'Vidol', + homepage: 'https://github.com/lobehub/lobe-vidol', src: 'https://registry.npmmirror.com/@v-idol/vidol-dance-gokuraku/1.0.0/files/gokuraku.vmd', audio: 'https://registry.npmmirror.com/@v-idol/vidol-dance-gokuraku/1.0.0/files/Gokuraku jodo.mp3', diff --git a/src/features/Actions/Agent.tsx b/src/features/Actions/Agent.tsx new file mode 100644 index 00000000..17e6ddc6 --- /dev/null +++ b/src/features/Actions/Agent.tsx @@ -0,0 +1,19 @@ +import { ActionIcon } from '@lobehub/ui'; +import { PlusCircle } from 'lucide-react'; + +import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/common'; +import { useConfigStore } from '@/store/config'; + +// eslint-disable-next-line react/display-name +export default () => { + const openPanel = useConfigStore((s) => s.openPanel); + + return ( + openPanel('agent')} + title={'新的会话'} + size={DESKTOP_HEADER_ICON_SIZE} + /> + ); +}; diff --git a/src/features/Actions/Dance.tsx b/src/features/Actions/Dance.tsx index 700e1ab5..e153baae 100644 --- a/src/features/Actions/Dance.tsx +++ b/src/features/Actions/Dance.tsx @@ -12,7 +12,7 @@ export default () => { onClick={() => { openPanel('dance'); }} - title={'音乐与舞蹈控制'} + title={'音乐与舞蹈'} /> ); }; diff --git a/src/features/Actions/Edit.tsx b/src/features/Actions/Edit.tsx index bbe30ee8..5119682c 100644 --- a/src/features/Actions/Edit.tsx +++ b/src/features/Actions/Edit.tsx @@ -12,7 +12,7 @@ export default () => { onClick={() => { openPanel('role'); }} - title={'编辑角色'} + title={'角色设定'} /> ); }; diff --git a/src/features/Actions/PlayControl.tsx b/src/features/Actions/PlayControl.tsx index 2f3341d5..47bb34d3 100644 --- a/src/features/Actions/PlayControl.tsx +++ b/src/features/Actions/PlayControl.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames'; import { Music2 } from 'lucide-react'; import React from 'react'; +import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/common'; import { useDanceStore } from '@/store/dance'; const useStyles = createStyles(({ css }) => ({ @@ -42,6 +43,7 @@ export default (props: Props) => { diff --git a/src/features/Actions/ToggleChatSideBar.tsx b/src/features/Actions/ToggleChatSideBar.tsx new file mode 100644 index 00000000..48af4f5c --- /dev/null +++ b/src/features/Actions/ToggleChatSideBar.tsx @@ -0,0 +1,22 @@ +import { ActionIcon } from '@lobehub/ui'; +import { PanelRightClose, PanelRightOpen } from 'lucide-react'; +import React from 'react'; + +import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/common'; +import { useGlobalStore } from '@/store/global'; + +export default () => { + const [showChatSidebar, toggleChatSideBar] = useGlobalStore((s) => [ + s.showChatSidebar, + s.toggleChatSideBar, + ]); + + return ( + toggleChatSideBar()} + title={'侧边栏'} + size={DESKTOP_HEADER_ICON_SIZE} + /> + ); +}; diff --git a/src/features/Actions/ToggleSessionList.tsx b/src/features/Actions/ToggleSessionList.tsx new file mode 100644 index 00000000..e99f7429 --- /dev/null +++ b/src/features/Actions/ToggleSessionList.tsx @@ -0,0 +1,22 @@ +import { ActionIcon } from '@lobehub/ui'; +import { AlignLeft, ChevronsLeft } from 'lucide-react'; +import React from 'react'; + +import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/common'; +import { useGlobalStore } from '@/store/global'; + +export default () => { + const [showSessionList, toggleSessionList] = useGlobalStore((s) => [ + s.showSessionList, + s.toggleSessionList, + ]); + + return ( + toggleSessionList()} + title={'会话列表'} + size={DESKTOP_HEADER_ICON_SIZE} + /> + ); +}; diff --git a/src/features/Actions/Video.tsx b/src/features/Actions/Video.tsx index 7474d0d1..3084a807 100644 --- a/src/features/Actions/Video.tsx +++ b/src/features/Actions/Video.tsx @@ -1,6 +1,7 @@ import { ActionIcon } from '@lobehub/ui'; import { Video, VideoOff } from 'lucide-react'; +import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/common'; import { useSessionStore } from '@/store/session'; export default () => { @@ -13,6 +14,7 @@ export default () => { { if (viewerMode) { setViewerMode(false); diff --git a/src/features/Actions/Voice.tsx b/src/features/Actions/Voice.tsx index 1d26854e..5ced04a4 100644 --- a/src/features/Actions/Voice.tsx +++ b/src/features/Actions/Voice.tsx @@ -3,6 +3,7 @@ import { createStyles } from 'antd-style'; import classNames from 'classnames'; import { Volume2, VolumeXIcon } from 'lucide-react'; +import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/common'; import { toogleVoice } from '@/services/chat'; import { useSessionStore } from '@/store/session'; @@ -25,6 +26,7 @@ const VoiceSwitch = () => { className={classNames(styles.voice, voiceOn && styles.voiceOn)} icon={voiceOn ? Volume2 : VolumeXIcon} onClick={toogleVoice} + size={DESKTOP_HEADER_ICON_SIZE} title={'语音合成'} /> ); diff --git a/src/features/AgentViewer/ToolBar/index.tsx b/src/features/AgentViewer/ToolBar/index.tsx index 8ef77f25..ce1cb1e1 100644 --- a/src/features/AgentViewer/ToolBar/index.tsx +++ b/src/features/AgentViewer/ToolBar/index.tsx @@ -4,6 +4,7 @@ import { Grid3x3, LandPlot, MessageCircle, + MessageCircleOff, Orbit, RotateCw, SwitchCamera, @@ -14,13 +15,14 @@ import { useViewerStore } from '@/store/viewer'; interface ToolBarProps { className?: string; - setOpen?: (open: boolean) => void; + open?: boolean; style?: React.CSSProperties; + toggleOpen?: () => void; viewerRef?: React.RefObject; } const ToolBar = (props: ToolBarProps) => { - const { style, className, setOpen, viewerRef } = props; + const { style, className, toggleOpen, viewerRef, open } = props; const viewer = useViewerStore((s) => s.viewer); const toggleFullScreen = () => { if (!document.fullscreenElement) { @@ -60,7 +62,7 @@ const ToolBar = (props: ToolBarProps) => { label: '重置镜头', }, { - icon: MessageCircle, + icon: open ? MessageCircleOff : MessageCircle, key: 'dialog', label: '对话框', }, @@ -83,7 +85,7 @@ const ToolBar = (props: ToolBarProps) => { break; } case 'dialog': { - setOpen?.(true); + toggleOpen?.(); break; } diff --git a/src/features/AgentViewer/index.tsx b/src/features/AgentViewer/index.tsx index 0ca107e9..145295d9 100644 --- a/src/features/AgentViewer/index.tsx +++ b/src/features/AgentViewer/index.tsx @@ -37,7 +37,12 @@ function AgentViewer() { return (
{open ? : null} - + setOpen(!open)} + viewerRef={ref} + open={open} + /> {loading ? : null}
diff --git a/src/features/AgentViewer/style.ts b/src/features/AgentViewer/style.ts index ee3491d6..3bc558a9 100644 --- a/src/features/AgentViewer/style.ts +++ b/src/features/AgentViewer/style.ts @@ -23,6 +23,10 @@ export const useStyles = createStyles(({ css, token }) => ({ display: flex; max-width: ${CHAT_INPUT_WIDTH}; + + @media (max-width: 768px) { + width: 100%; + } `, viewer: css` min-height: 0; diff --git a/src/features/AudioPlayer/MiniPlayer/index.tsx b/src/features/AudioPlayer/MiniPlayer/index.tsx index 8c843b21..128809e3 100644 --- a/src/features/AudioPlayer/MiniPlayer/index.tsx +++ b/src/features/AudioPlayer/MiniPlayer/index.tsx @@ -6,6 +6,7 @@ import { ListMusic } from 'lucide-react'; import React, { useState } from 'react'; import { Flexbox } from 'react-layout-kit'; +import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/common'; import Control from '@/features/AudioPlayer/Control'; import PlayList from '@/features/AudioPlayer/PlayList'; import { useDanceStore } from '@/store/dance'; @@ -56,7 +57,12 @@ export default (props: Props) => { /> - setOpen(true)} title={'播放列表'} /> + setOpen(true)} + title={'播放列表'} + size={DESKTOP_HEADER_ICON_SIZE} + /> ) : null; }; diff --git a/src/features/ChatDialog/index.tsx b/src/features/ChatDialog/index.tsx index c428c0b1..0c092775 100644 --- a/src/features/ChatDialog/index.tsx +++ b/src/features/ChatDialog/index.tsx @@ -20,7 +20,7 @@ interface DialogProps { const Dialog = (props: DialogProps) => { const { styles } = useStyles(); const { className, style, setOpen } = props; - const currentChats = useSessionStore((s) => sessionSelectors.currentChats(s)); + const currentChats = useSessionStore((s) => sessionSelectors.currentChatsWithGreetingMessage(s)); const lastAgentChatIndex = currentChats.findLastIndex((item) => item.role === 'assistant'); const ref = React.useRef(null); const isHovered = useHover(ref); @@ -33,7 +33,7 @@ const Dialog = (props: DialogProps) => { diff --git a/src/features/ChatHeader/index.tsx b/src/features/ChatHeader/index.tsx index bb85f482..0cd0c28e 100644 --- a/src/features/ChatHeader/index.tsx +++ b/src/features/ChatHeader/index.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Flexbox } from 'react-layout-kit'; import AgentMeta from '@/components/agent/AgentMeta'; +import ToggleChatSideBar from '@/features/Actions/ToggleChatSideBar'; import Video from '@/features/Actions/Video'; import Voice from '@/features/Actions/Voice'; import { sessionSelectors, useSessionStore } from '@/store/session'; @@ -15,10 +16,14 @@ const Header = () => { return ( - + + {/**/} + + ); diff --git a/src/features/ChatHeader/style.ts b/src/features/ChatHeader/style.ts index 08f3b2ec..9e3717f6 100644 --- a/src/features/ChatHeader/style.ts +++ b/src/features/ChatHeader/style.ts @@ -6,7 +6,6 @@ const useStyles = createStyles(({ token, css }) => ({ header: css` height: ${CHAT_HEADER_HEIGHT}px; padding: ${token.paddingSM}px; - border-bottom: 1px solid ${token.colorBorderSecondary}; `, player: css` min-width: 480px; diff --git a/src/features/ChatInfo/Operations/index.tsx b/src/features/ChatInfo/Operations/index.tsx index 12a023cc..1eb949bf 100644 --- a/src/features/ChatInfo/Operations/index.tsx +++ b/src/features/ChatInfo/Operations/index.tsx @@ -1,8 +1,9 @@ import { ExclamationCircleFilled } from '@ant-design/icons'; import { Modal } from 'antd'; -import { Eraser, Music, SquarePen } from 'lucide-react'; +import { Eraser, Music, Settings2Icon } from 'lucide-react'; import React, { memo } from 'react'; +import { LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent'; import { useConfigStore } from '@/store/config'; import { useSessionStore } from '@/store/session'; @@ -16,19 +17,36 @@ export interface MyListProps { const Operations = memo(({ mobile }) => { const [openPanel] = useConfigStore((s) => [s.openPanel]); - const [clearHistory] = useSessionStore((s) => [s.clearHistory]); + const [clearHistory, activeId] = useSessionStore((s) => [s.clearHistory, s.activeId]); const items = [ + // { + // icon: SquarePen, + // label: '新话题', + // key: 'new-topic', + // onClick: () => {}, + // }, + // { + // icon: History, + // label: '聊天历史记录', + // key: 'history', + // onClick: () => { + // // openPanel('role'); + // }, + // }, { - icon: SquarePen, - label: '角色信息与对话设置', + icon: Settings2Icon, + label: '角色设定', + key: 'setting', onClick: () => { openPanel('role'); }, + hidden: activeId === LOBE_VIDOL_DEFAULT_AGENT_ID, }, { icon: Music, - label: '音乐与舞蹈控制', + key: 'music', + label: '音乐与舞蹈', onClick: () => { openPanel('dance'); }, @@ -36,6 +54,7 @@ const Operations = memo(({ mobile }) => { { icon: Eraser, label: '清除上下文', + key: 'context', onClick: () => { confirm({ title: '确定删除历史消息?', @@ -54,9 +73,11 @@ const Operations = memo(({ mobile }) => { return ( <> - {items.map(({ icon, label, onClick }) => ( - - ))} + {items + .filter((item) => !item.hidden) + .map(({ icon, label, onClick }) => ( + + ))} ); }); diff --git a/src/features/ChatInfo/index.tsx b/src/features/ChatInfo/index.tsx index e1f3839f..94e715e2 100644 --- a/src/features/ChatInfo/index.tsx +++ b/src/features/ChatInfo/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import AgentCard from '@/components/agent/AgentCard'; import { SIDEBAR_WIDTH } from '@/constants/common'; import MiniPlayer from '@/features/AudioPlayer/MiniPlayer'; +import { useGlobalStore } from '@/store/global'; import { sessionSelectors, useSessionStore } from '@/store/session'; import Operations from './Operations'; @@ -22,7 +23,11 @@ const useStyles = createStyles(({ css, token }) => ({ `, })); -const Header = () => { +export default () => { + const [showChatSidebar, setChatSidebar] = useGlobalStore((s) => [ + s.showChatSidebar, + s.setChatSidebar, + ]); const { styles } = useStyles(); const [currentAgent] = useSessionStore((s) => [sessionSelectors.currentAgent(s)]); @@ -32,11 +37,11 @@ const Header = () => { minWidth={SIDEBAR_WIDTH} maxWidth={SIDEBAR_WIDTH} mode={'fixed'} + onExpandChange={(expand) => setChatSidebar(expand)} + expand={showChatSidebar} placement={'right'} > } footer={} /> ); }; - -export default Header; diff --git a/src/features/ChatItem/Actions/Assistant.tsx b/src/features/ChatItem/Actions/Assistant.tsx index e3b2f6b9..0cfaccae 100644 --- a/src/features/ChatItem/Actions/Assistant.tsx +++ b/src/features/ChatItem/Actions/Assistant.tsx @@ -5,7 +5,7 @@ import { memo } from 'react'; import type { RenderAction } from '@/features/ChatItem/type'; -const AssistantActionsBar: RenderAction = ({ onActionClick }) => { +const AssistantActionsBar: RenderAction = ({ onActionClick, id }) => { const { copy, regenerate, divider, del, edit } = useChatListActionsBar({ copy: '复制', delete: '删除', @@ -13,6 +13,10 @@ const AssistantActionsBar: RenderAction = ({ onActionClick }) => { regenerate: '重新生成', }); + console.log('AssistantActionsBar', id); + + if (id === 'default') return; + const tts = { icon: Play, key: 'tts', diff --git a/src/features/ChatItem/ActionsBar.tsx b/src/features/ChatItem/ActionsBar.tsx index 1a366330..66bf720c 100644 --- a/src/features/ChatItem/ActionsBar.tsx +++ b/src/features/ChatItem/ActionsBar.tsx @@ -25,7 +25,10 @@ interface ActionsProps { setEditing: (edit: boolean) => void; } const Actions = memo(({ index, setEditing }) => { - const item = useSessionStore((s) => sessionSelectors.currentChats(s)[index], isEqual); + const item = useSessionStore( + (s) => sessionSelectors.currentChatsWithGreetingMessage(s)[index], + isEqual, + ); const onActionsClick = useActionsClick(); const handleActionClick = useCallback( diff --git a/src/features/ChatItem/index.tsx b/src/features/ChatItem/index.tsx index b6f6b6a7..c17e0088 100644 --- a/src/features/ChatItem/index.tsx +++ b/src/features/ChatItem/index.tsx @@ -32,11 +32,11 @@ const Item = memo(({ index, id, showTitle = false, type = 'bl const [editing, setEditing] = useState(false); const item = useSessionStore((s) => { - const chats = sessionSelectors.currentChats(s); + const chats = sessionSelectors.currentChatsWithGreetingMessage(s); if (index >= chats.length) return; - return sessionSelectors.currentChats(s)[index]; + return chats[index]; }, isEqual); const [loading, updateMessageContent] = useSessionStore((s) => [ diff --git a/src/features/ChatList/index.tsx b/src/features/ChatList/index.tsx index 5843af3f..c2c297aa 100644 --- a/src/features/ChatList/index.tsx +++ b/src/features/ChatList/index.tsx @@ -19,7 +19,10 @@ const VirtualizedList = memo(({ mobile }) => { const virtuosoRef = useRef(null); const [atBottom, setAtBottom] = useState(true); - const data = useSessionStore((s) => ['empty', ...sessionSelectors.currentChatIDs(s)], isEqual); + const data = useSessionStore( + (s) => ['empty', ...sessionSelectors.currentChatIDsWithGreetingMessage(s)], + isEqual, + ); const [id, chatLoading] = useSessionStore((s) => [s.activeId, !!s.chatLoadingId]); useEffect(() => { @@ -32,7 +35,7 @@ const VirtualizedList = memo(({ mobile }) => { const overscan = typeof window !== 'undefined' ? window.innerHeight * 1.5 : 0; return chatLoading && data.length === 2 ? null : ( - + void; - value?: string; -} - -// eslint-disable-next-line react/display-name -const Header = memo((props: HeaderProps) => { - const { value, onChange } = props; - const { styles } = useStyles(); - const openPanel = useConfigStore((s) => s.openPanel); - - return ( - - { - if (onChange) onChange(e.target.value); - }} - placeholder="搜索" - shortKey="f" - value={value} - /> - openPanel('agent')} title={'创建对话'} /> - - ); -}); - -export default Header; diff --git a/src/features/SessionList/List/SessionItem/Actions.tsx b/src/features/SessionList/List/Item/Actions.tsx similarity index 97% rename from src/features/SessionList/List/SessionItem/Actions.tsx rename to src/features/SessionList/List/Item/Actions.tsx index 03ace480..dc6488e9 100644 --- a/src/features/SessionList/List/SessionItem/Actions.tsx +++ b/src/features/SessionList/List/Item/Actions.tsx @@ -19,7 +19,7 @@ export default (props: ActionsProps) => { danger: true, icon: , key: 'delete', - label: '删除对话', + label: '删除会话', onClick: ({ domEvent }) => { domEvent.stopPropagation(); modal.confirm({ diff --git a/src/features/SessionList/List/SessionItem/index.tsx b/src/features/SessionList/List/Item/index.tsx similarity index 96% rename from src/features/SessionList/List/SessionItem/index.tsx rename to src/features/SessionList/List/Item/index.tsx index 181988b3..779cec68 100644 --- a/src/features/SessionList/List/SessionItem/index.tsx +++ b/src/features/SessionList/List/Item/index.tsx @@ -3,8 +3,8 @@ import { shallow } from 'zustand/shallow'; import { sessionSelectors, useSessionStore } from '@/store/session'; +import ListItem from '../../ListItem'; import Actions from './Actions'; -import ListItem from './ListItem'; interface SessionItemProps { id: string; diff --git a/src/features/SessionList/List/index.tsx b/src/features/SessionList/List/index.tsx index f03fb926..60eb10d8 100644 --- a/src/features/SessionList/List/index.tsx +++ b/src/features/SessionList/List/index.tsx @@ -5,7 +5,7 @@ import LazyLoad from 'react-lazy-load'; import { useSessionStore } from '@/store/session'; import { sessionSelectors } from '@/store/session/selectors'; -import SessionItem from './SessionItem'; +import SessionItem from './Item'; const useStyles = createStyles( ({ css }) => css` diff --git a/src/features/SessionList/List/SessionItem/ListItem.tsx b/src/features/SessionList/ListItem.tsx similarity index 100% rename from src/features/SessionList/List/SessionItem/ListItem.tsx rename to src/features/SessionList/ListItem.tsx diff --git a/src/features/SessionList/V/index.tsx b/src/features/SessionList/V/index.tsx new file mode 100644 index 00000000..f6b9ae4b --- /dev/null +++ b/src/features/SessionList/V/index.tsx @@ -0,0 +1,30 @@ +import { Space, Tag } from 'antd'; +import { memo } from 'react'; + +import { DEFAULT_VIDOL_AGENT, LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent'; +import { useSessionStore } from '@/store/session'; + +import ListItem from '../ListItem'; + +const V = memo(() => { + const [activeId, switchSession] = useSessionStore((s) => [s.activeId, s.switchSession]); + + return ( + { + switchSession(LOBE_VIDOL_DEFAULT_AGENT_ID); + }} + active={activeId === LOBE_VIDOL_DEFAULT_AGENT_ID} + avatar={DEFAULT_VIDOL_AGENT.meta.avatar} + title={ + + {DEFAULT_VIDOL_AGENT.meta.name} + 官方助手 + + } + description={DEFAULT_VIDOL_AGENT.meta.description} + /> + ); +}); + +export default V; diff --git a/src/features/SessionList/index.tsx b/src/features/SessionList/index.tsx index 604fb480..1d391e20 100644 --- a/src/features/SessionList/index.tsx +++ b/src/features/SessionList/index.tsx @@ -1,23 +1,47 @@ -import { DraggablePanel } from '@lobehub/ui'; +import { Icon, SearchBar } from '@lobehub/ui'; +import { Collapse } from 'antd'; import { createStyles } from 'antd-style'; -import { useState } from 'react'; +import { ChevronDown } from 'lucide-react'; +import React, { useState } from 'react'; +import { Flexbox } from 'react-layout-kit'; -import { SIDEBAR_MAX_WIDTH, SIDEBAR_WIDTH } from '@/constants/common'; +import { HEADER_HEIGHT } from '@/constants/common'; +import Agent from '@/features/Actions/Agent'; -import Header from './Header'; import List from './List'; +import V from './V'; -const useStyles = createStyles(({ css, token }) => ({ - content: css` - display: flex; - flex-direction: column; - `, - header: css` - border-bottom: 1px solid ${token.colorBorder}; - `, +const useStyles = createStyles(({ css, token, prefixCls }) => ({ list: css` padding: 8px; `, + container: css` + .${prefixCls}-collapse-header { + padding-inline: 8px !important; + color: ${token.colorTextDescription} !important; + border-radius: ${token.borderRadius}px !important; + + &:hover { + color: ${token.colorText} !important; + background: ${token.colorFillTertiary}; + .${prefixCls}-collapse-extra { + display: block; + } + } + } + .${prefixCls}-collapse-extra { + display: none; + } + .${prefixCls}-collapse-content { + border-radius: 0 !important; + } + .${prefixCls}-collapse-content-box { + padding: 0 !important; + } + `, + icon: css` + transition: all 100ms ${token.motionEaseOut}; + `, })); const SideBar = () => { @@ -25,23 +49,57 @@ const SideBar = () => { const [searchName, setSearchName] = useState(); return ( - -
{ - setSearchName(value); - }} - value={searchName} - /> + <> + + { + setSearchName(e.target.value); + }} + placeholder="搜索" + shortKey="f" + spotlight + type={'block'} + value={searchName} + /> + +
- + ( + + )} + expandIconPosition={'end'} + ghost + size={'small'} + items={[ + { + children: ( + <> + + + + ), + label: '会话列表', + key: 'default', + }, + ]} + />
- + ); }; diff --git a/src/features/SessionList/style.ts b/src/features/SessionList/style.ts index cf26f4cf..b207e624 100644 --- a/src/features/SessionList/style.ts +++ b/src/features/SessionList/style.ts @@ -14,9 +14,7 @@ export const useStyles = createStyles(({ css, token }) => ({ header: css` height: ${HEADER_HEIGHT}px; padding: ${token.paddingSM}px; - border-bottom: 1px solid ${token.colorBorderSecondary}; `, - title: css` font-size: 20px; font-weight: 600; diff --git a/src/hooks/useIsMobile.ts b/src/hooks/useIsMobile.ts new file mode 100644 index 00000000..e24d4336 --- /dev/null +++ b/src/hooks/useIsMobile.ts @@ -0,0 +1,7 @@ +import { useResponsive } from 'antd-style'; + +export const useIsMobile = (): boolean => { + const { mobile } = useResponsive(); + + return !!mobile; +}; diff --git a/src/layout/Header/index.tsx b/src/layout/Header/index.tsx index 6bf67fc3..cb141a6f 100644 --- a/src/layout/Header/index.tsx +++ b/src/layout/Header/index.tsx @@ -21,7 +21,7 @@ const Header = (props: Props) => { window.open('https://github.com/lobehub/lobe-vidol-market', '_blank')} size="large" />, @@ -33,7 +33,7 @@ const Header = (props: Props) => { size="large" />, ]} - logo={} + logo={} nav={ { } + extra={} + footer={} /> ); diff --git a/src/panels/AgentPanel/index.tsx b/src/panels/AgentPanel/index.tsx index 6973a0c7..bf25a7e6 100644 --- a/src/panels/AgentPanel/index.tsx +++ b/src/panels/AgentPanel/index.tsx @@ -28,7 +28,7 @@ const ControlPanel = (props: ControlPanelProps) => { className={className} panelKey="agent" style={style} - title="角色列表" + title="我的角色" extra={} >
diff --git a/src/panels/DancePanel/Dance/Card/index.tsx b/src/panels/DancePanel/Dance/Card/index.tsx index 6eb669fe..e8d55d05 100644 --- a/src/panels/DancePanel/Dance/Card/index.tsx +++ b/src/panels/DancePanel/Dance/Card/index.tsx @@ -5,7 +5,6 @@ import React, { memo, useState } from 'react'; import DanceInfo from '@/components/DanceInfo'; import { SIDEBAR_MAX_WIDTH, SIDEBAR_WIDTH } from '@/constants/common'; -import { PanelContext } from '@/panels/PanelContext'; import { danceListSelectors, useDanceStore } from '@/store/dance'; const useStyles = createStyles(({ css, token }) => ({ @@ -28,17 +27,16 @@ const SideBar = memo(() => { activateDance, deactivateDance, addAndPlayItem, - addPlayItem, + addToPlayList, unsubscribe, ] = useDanceStore((s) => [ danceListSelectors.showSideBar(s), s.activateDance, s.deactivateDance, s.addAndPlayItem, - s.addPlayItem, + s.addToPlayList, s.unsubscribe, ]); - const isInPanel = React.useContext(PanelContext); const currentDance = useDanceStore((s) => danceListSelectors.currentDanceItem(s)); @@ -62,32 +60,28 @@ const SideBar = memo(() => { > { - if (currentDance) { - addAndPlayItem(currentDance); - } - }} - type={'primary'} - > - 播放并添加到列表 - - ) : ( - - ), + , + , ({ content: css` display: flex; @@ -31,9 +32,59 @@ const Header = () => { ], ); + const [subscribe, unsubscribe, subscribed, addAndPlayItem, addToPlayList] = useDanceStore((s) => [ + s.subscribe, + s.unsubscribe, + danceListSelectors.subscribed(s), + s.addAndPlayItem, + s.addToPlayList, + ]); + const actions = []; if (currentDanceItem) { - actions.push(); + const isSubscribed = subscribed(currentDanceItem.danceId); + + if (isSubscribed) { + actions.push([ + , + , + ]); + } + + actions.push( + , + ); } return ( @@ -54,7 +105,11 @@ const Header = () => { }} placement={'right'} > - + } + /> ); }; diff --git a/src/panels/DancePanel/index.tsx b/src/panels/DancePanel/index.tsx index a08630a7..5e3f67a7 100644 --- a/src/panels/DancePanel/index.tsx +++ b/src/panels/DancePanel/index.tsx @@ -30,7 +30,7 @@ const DancePanel = (props: DancePanelProps) => { className={className} panelKey="dance" style={style} - title="音乐与舞蹈控制" + title="音乐与舞蹈" extra={} footer={ diff --git a/src/panels/RolePanel/Role/index.tsx b/src/panels/RolePanel/Role/index.tsx index 2982588c..fc452630 100644 --- a/src/panels/RolePanel/Role/index.tsx +++ b/src/panels/RolePanel/Role/index.tsx @@ -67,16 +67,26 @@ const Info = (props: InfoProps) => {
+ + +
diff --git a/src/panels/RolePanel/index.tsx b/src/panels/RolePanel/index.tsx index 1853d83d..34851a41 100644 --- a/src/panels/RolePanel/index.tsx +++ b/src/panels/RolePanel/index.tsx @@ -22,7 +22,7 @@ const RolePanel = (props: RolePanelProps) => { const [tab, setTab] = useState('info'); return ( - +
{ }, { key: 'role', - label: '角色设定', + label: '系统角色', }, { key: 'voice', diff --git a/src/store/dance/slices/playlist.ts b/src/store/dance/slices/playlist.ts index 23d53068..2a54f594 100644 --- a/src/store/dance/slices/playlist.ts +++ b/src/store/dance/slices/playlist.ts @@ -15,7 +15,7 @@ export interface PlayListStore { /** * Add a dance to the playlist. */ - addPlayItem: (dance: Dance) => void; + addToPlayList: (dance: Dance) => void; /** * Clear the playlist. */ @@ -64,6 +64,10 @@ export const createPlayListStore: StateCreator< PlayListStore > = (set, get) => { return { + /** + * Add a dance to the playlist and play it. add to the first. + * @param dance + */ addAndPlayItem: (dance) => { const { playlist, playItem } = get(); @@ -71,6 +75,9 @@ export const createPlayListStore: StateCreator< const index = draftState.findIndex((item) => item.name === dance.name); if (index === -1) { draftState.unshift(dance); + } else { + draftState.splice(index, 1); + draftState.unshift(dance); } }); @@ -78,13 +85,17 @@ export const createPlayListStore: StateCreator< playItem(dance); }, - addPlayItem: (dance) => { + /** + * Add a dance to the playlist. add to the last. + * @param dance + */ + addToPlayList: (dance) => { const { playlist } = get(); const nextPlayList = produce(playlist, (draftState) => { const index = draftState.findIndex((item) => item.name === dance.name); if (index === -1) { - draftState.unshift(dance); + draftState.push(dance); } }); diff --git a/src/store/global.ts b/src/store/global.ts new file mode 100644 index 00000000..3e72a312 --- /dev/null +++ b/src/store/global.ts @@ -0,0 +1,31 @@ +import { shallow } from 'zustand/shallow'; +import { createWithEqualityFn } from 'zustand/traditional'; + +interface GlobalStore { + setChatSidebar: (show: boolean) => void; + setSessionList: (show: boolean) => void; + showChatSidebar: boolean; + showSessionList: boolean; + toggleChatSideBar: () => void; + toggleSessionList: () => void; +} + +export const useGlobalStore = createWithEqualityFn()( + (set) => ({ + setChatSidebar: (show) => { + set({ showChatSidebar: show }); + }, + toggleChatSideBar: () => { + set((state) => ({ showChatSidebar: !state.showChatSidebar })); + }, + showChatSidebar: true, + showSessionList: true, + setSessionList: (show) => { + set({ showSessionList: show }); + }, + toggleSessionList: () => { + set((state) => ({ showSessionList: !state.showSessionList })); + }, + }), + shallow, +); diff --git a/src/store/session/index.ts b/src/store/session/index.ts index 1602dfd0..2110b6df 100644 --- a/src/store/session/index.ts +++ b/src/store/session/index.ts @@ -7,6 +7,7 @@ import { shallow } from 'zustand/shallow'; import { createWithEqualityFn } from 'zustand/traditional'; import { StateCreator } from 'zustand/vanilla'; +import { LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent'; import { LOADING_FLAG } from '@/constants/common'; import { chatCompletion, handleSpeakAi } from '@/services/chat'; import { Agent } from '@/types/agent'; @@ -48,6 +49,10 @@ export interface SessionStore { * @returns */ createSession: (agent: Agent) => void; + /** + * 默认会话 + */ + defaultSession: Session; /** * 删除消息 */ @@ -351,6 +356,10 @@ export const createSessonStore: StateCreator { const { sessionList } = get(); + if (agentId === LOBE_VIDOL_DEFAULT_AGENT_ID) { + set({ activeId: agentId }); + return; + } const targetSession = sessionList.find((session) => session.agentId === agentId); if (!targetSession) { const session = { @@ -386,13 +395,20 @@ export const createSessonStore: StateCreator { - const { sessionList, activeId } = get(); - const sessions = produce(sessionList, (draft) => { - const index = draft.findIndex((session) => session.agentId === activeId); - if (index === -1) return; - draft[index].messages = messages; - }); - set({ sessionList: sessions }); + const { sessionList, activeId, defaultSession } = get(); + if (activeId === LOBE_VIDOL_DEFAULT_AGENT_ID) { + const mergeSession = produce(defaultSession, (draft) => { + draft.messages = messages; + }); + set({ defaultSession: mergeSession }); + } else { + const sessions = produce(sessionList, (draft) => { + const index = draft.findIndex((session) => session.agentId === activeId); + if (index === -1) return; + draft[index].messages = messages; + }); + set({ sessionList: sessions }); + } }, }); diff --git a/src/store/session/initialState.ts b/src/store/session/initialState.ts index f969ffaa..4e2f6049 100644 --- a/src/store/session/initialState.ts +++ b/src/store/session/initialState.ts @@ -1,17 +1,18 @@ -import { DEFAULT_AGENT, V_CHAT_DEFAULT_AGENT_ID } from '@/constants/agent'; +import { LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent'; import { Session } from '@/types/session'; const defaultSession: Session = { - agentId: V_CHAT_DEFAULT_AGENT_ID, + agentId: LOBE_VIDOL_DEFAULT_AGENT_ID, messages: [], }; const initialState = { activeId: defaultSession.agentId, chatLoadingId: undefined, - localAgentList: [DEFAULT_AGENT], + localAgentList: [], messageInput: '', - sessionList: [defaultSession], + sessionList: [], + defaultSession: defaultSession, viewerMode: false, voiceOn: true, }; diff --git a/src/store/session/selectors.ts b/src/store/session/selectors.ts index 353c1e6b..3a21f887 100644 --- a/src/store/session/selectors.ts +++ b/src/store/session/selectors.ts @@ -1,4 +1,4 @@ -import { V_CHAT_DEFAULT_AGENT_ID } from '@/constants/agent'; +import { DEFAULT_VIDOL_AGENT, LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent'; import { DEFAULT_USER_AVATAR } from '@/constants/common'; import { Agent } from '@/types/agent'; import { ChatMessage } from '@/types/chat'; @@ -7,7 +7,10 @@ import { Session } from '@/types/session'; import { SessionStore } from './index'; const currentSession = (s: SessionStore): Session | undefined => { - const { activeId, sessionList } = s; + const { activeId, sessionList, defaultSession } = s; + if (activeId === LOBE_VIDOL_DEFAULT_AGENT_ID) { + return defaultSession; + } return sessionList.find((item) => item.agentId === activeId); }; @@ -16,14 +19,11 @@ const sessionListIds = (s: SessionStore): string[] => { return sessionList.map((item) => item.agentId); }; -const currentChatIDs = (s: SessionStore): string[] => { - const session = currentSession(s); - if (!session) return []; - return session.messages.map((item) => item.id); -}; - const currentAgent = (s: SessionStore): Agent | undefined => { const { activeId, localAgentList } = s; + if (activeId === LOBE_VIDOL_DEFAULT_AGENT_ID) { + return DEFAULT_VIDOL_AGENT; + } return localAgentList.find((item) => item.agentId === activeId); }; @@ -47,6 +47,38 @@ const currentChats = (s: SessionStore): ChatMessage[] => { }); }; +const currentChatsWithGreetingMessage = (s: SessionStore): ChatMessage[] => { + const data = currentChats(s); + + const isBrandNewChat = data.length === 0; + + if (!isBrandNewChat) return data; + + const agent = currentAgent(s); + + const initTime = Date.now(); + + const emptyGuideMessage = { + content: agent?.greeting || `你好,我是${agent?.meta.name},有什么可以帮助你的吗?`, + createdAt: initTime, + id: 'default', + meta: { + avatar: agent?.meta.avatar, + title: agent?.meta.name, + description: agent?.meta.description, + }, + role: 'assistant', + updatedAt: initTime, + } as ChatMessage; + + return [emptyGuideMessage]; +}; + +const currentChatIDsWithGreetingMessage = (s: SessionStore): string[] => { + const currentChats = currentChatsWithGreetingMessage(s); + return currentChats.map((item) => item.id); +}; + const previousChats = (s: SessionStore, id: string): ChatMessage[] => { const chatList = currentChats(s); const index = chatList.findIndex((item) => item.id === id); @@ -87,20 +119,27 @@ const currentChatMessage = (s: SessionStore): ChatMessage | undefined => { const getAgentById = (s: SessionStore) => { const { localAgentList } = s; - return (id: string): Agent | undefined => localAgentList.find((item) => item.agentId === id); + return (id: string): Agent | undefined => { + if (id === LOBE_VIDOL_DEFAULT_AGENT_ID) { + return DEFAULT_VIDOL_AGENT; + } else { + return localAgentList.find((item) => item.agentId === id); + } + }; }; const isDefaultAgent = (s: SessionStore) => { return (id: string): boolean => { const agent = getAgentById(s)(id); - return agent?.agentId === V_CHAT_DEFAULT_AGENT_ID; + return agent?.agentId === LOBE_VIDOL_DEFAULT_AGENT_ID; }; }; export const sessionSelectors = { + currentChatsWithGreetingMessage, currentAgent, currentAgentModel, - currentChatIDs, + currentChatIDsWithGreetingMessage, isDefaultAgent, currentChatMessage, currentChats, diff --git a/src/types/agent.ts b/src/types/agent.ts index 1fa31263..b4724f75 100644 --- a/src/types/agent.ts +++ b/src/types/agent.ts @@ -1,11 +1,29 @@ import { TouchActionConfig } from './touch'; import { TTS } from './tts'; +/** + * Category Enum, 当前包括 Anime, Game, Realistic, VTuber, Book, History, Movie, Animal, Vroid + */ +export enum CategoryEnum { + ANIMAL = 'Animal', + ANIME = 'Anime', + BOOK = 'Book', + GAME = 'Game', + HISTORY = 'History', + MOVIE = 'Movie', + REALISTIC = 'Realistic', + VROID = 'Vroid', + VTUBER = 'VTuber', +} export interface AgentMeta { /** * 头像图片路径 */ avatar: string; + /** + * 模型分类 + */ + category?: CategoryEnum; /** * 封面图片路径 */ @@ -37,11 +55,26 @@ export interface Agent { * 角色 ID,为本地文件目录 */ agentId: string; + /** + * 作者名 + */ + author: string; + /** + * 创建时间 + */ + createAt: string; + /** + * 问候语,角色在每次聊天开始时说的第一句话 + */ + greeting: string; + /** + * 作者主页 + */ + homepage: string; /** * 角色元数据 */ meta: AgentMeta; - /** * 角色设定 */ diff --git a/src/types/dance.ts b/src/types/dance.ts index 4f09aa43..805b0028 100644 --- a/src/types/dance.ts +++ b/src/types/dance.ts @@ -3,6 +3,10 @@ export interface Dance { * 音频文件 */ audio: string; + /** + * 作者名 + */ + author: string; /** * 封面图片 */ @@ -15,6 +19,10 @@ export interface Dance { * 舞蹈 ID */ danceId: string; + /** + * 作者主页 + */ + homepage: string; /** * 舞蹈名 */