diff --git a/src/app/chat/Background/index.tsx b/src/app/chat/Background/index.tsx
deleted file mode 100644
index f25e098a..00000000
--- a/src/app/chat/Background/index.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useGlobalStore } from '@/store/global';
-import { useSettingStore } from '@/store/setting';
-
-import { useStyles } from './style';
-
-const Background = () => {
- const { styles } = useStyles();
- const backgroundUrl = useGlobalStore((s) => s.backgroundUrl);
- const backgroundEffect = useSettingStore((s) => s.config.backgroundEffect);
-
- if (backgroundUrl) {
- return (
-
- );
- }
-
- return backgroundEffect === 'glow' ? : null;
-};
-
-export default Background;
diff --git a/src/app/chat/CameraMode/Background/index.tsx b/src/app/chat/CameraMode/Background/index.tsx
new file mode 100644
index 00000000..e327a524
--- /dev/null
+++ b/src/app/chat/CameraMode/Background/index.tsx
@@ -0,0 +1,24 @@
+import { useGlobalStore } from '@/store/global';
+
+const Background = () => {
+ const backgroundUrl = useGlobalStore((s) => s.backgroundUrl);
+
+ return backgroundUrl ? (
+
+ ) : null;
+};
+
+export default Background;
diff --git a/src/app/chat/ViewerMode/ChatDialog/index.tsx b/src/app/chat/CameraMode/ChatDialog/index.tsx
similarity index 100%
rename from src/app/chat/ViewerMode/ChatDialog/index.tsx
rename to src/app/chat/CameraMode/ChatDialog/index.tsx
diff --git a/src/app/chat/ViewerMode/ChatDialog/style.ts b/src/app/chat/CameraMode/ChatDialog/style.ts
similarity index 100%
rename from src/app/chat/ViewerMode/ChatDialog/style.ts
rename to src/app/chat/CameraMode/ChatDialog/style.ts
diff --git a/src/app/chat/CameraMode/Operation/actions/CallOff.tsx b/src/app/chat/CameraMode/Operation/actions/CallOff.tsx
new file mode 100644
index 00000000..4112faf7
--- /dev/null
+++ b/src/app/chat/CameraMode/Operation/actions/CallOff.tsx
@@ -0,0 +1,31 @@
+import { ActionIcon } from '@lobehub/ui';
+import { useTheme } from 'antd-style';
+import { Phone } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+
+import { DESKTOP_OPERATION_ICON_SIZE } from '@/constants/token';
+import { useSessionStore } from '@/store/session';
+
+const CallOff = () => {
+ const [setChatMode] = useSessionStore((s) => [s.setChatMode]);
+ const setVoiceOn = useSessionStore((s) => s.setVoiceOn);
+ const theme = useTheme();
+ const { t } = useTranslation('chat');
+ return (
+ {
+ setVoiceOn(false);
+ setChatMode('chat');
+ }}
+ title={t('callOff')}
+ size={DESKTOP_OPERATION_ICON_SIZE}
+ style={{
+ backgroundColor: theme.colorError,
+ color: theme.colorWhite,
+ }}
+ />
+ );
+};
+
+export default CallOff;
diff --git a/src/app/chat/ViewerMode/MessageInput/actions/Record.tsx b/src/app/chat/CameraMode/Operation/actions/Record.tsx
similarity index 68%
rename from src/app/chat/ViewerMode/MessageInput/actions/Record.tsx
rename to src/app/chat/CameraMode/Operation/actions/Record.tsx
index fd964983..f6c66378 100644
--- a/src/app/chat/ViewerMode/MessageInput/actions/Record.tsx
+++ b/src/app/chat/CameraMode/Operation/actions/Record.tsx
@@ -1,14 +1,17 @@
import { ActionIcon } from '@lobehub/ui';
+import { useTheme } from 'antd-style';
import { Mic } from 'lucide-react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+import { DESKTOP_OPERATION_ICON_SIZE_LARGE } from '@/constants/token';
import { useSpeechRecognition } from '@/hooks/useSpeechRecognition';
import { useSessionStore } from '@/store/session';
const Record = () => {
const [sendMessage, setMessageInput] = useSessionStore((s) => [s.sendMessage, s.setMessageInput]);
const { t } = useTranslation('chat');
+ const theme = useTheme();
const handleMessageInput = useCallback(
(result: string, isFinal: boolean) => {
setMessageInput(result);
@@ -25,7 +28,16 @@ const Record = () => {
});
return (
-
+
);
};
diff --git a/src/app/chat/CameraMode/Operation/actions/Setting.tsx b/src/app/chat/CameraMode/Operation/actions/Setting.tsx
new file mode 100644
index 00000000..f7cc3cd3
--- /dev/null
+++ b/src/app/chat/CameraMode/Operation/actions/Setting.tsx
@@ -0,0 +1,25 @@
+import { ActionIcon } from '@lobehub/ui';
+import { useTheme } from 'antd-style';
+import { Box } from 'lucide-react';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { DESKTOP_OPERATION_ICON_SIZE } from '@/constants/token';
+import { useGlobalStore } from '@/store/global';
+
+export default () => {
+ const [toggleChatSideBar] = useGlobalStore((s) => [s.toggleChatSideBar]);
+ const theme = useTheme();
+ const { t } = useTranslation('dance');
+ return (
+ toggleChatSideBar()}
+ title={t('menu')}
+ size={DESKTOP_OPERATION_ICON_SIZE}
+ style={{
+ backgroundColor: theme.colorBgElevated,
+ }}
+ />
+ );
+};
diff --git a/src/app/chat/CameraMode/Operation/index.tsx b/src/app/chat/CameraMode/Operation/index.tsx
new file mode 100644
index 00000000..684ee458
--- /dev/null
+++ b/src/app/chat/CameraMode/Operation/index.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import { memo } from 'react';
+import { Flexbox } from 'react-layout-kit';
+
+import CallOff from './actions/CallOff';
+import Record from './actions/Record';
+import Setting from './actions/Setting';
+
+const VoiceOperation = memo(() => {
+ return (
+
+
+
+
+
+ );
+});
+
+export default VoiceOperation;
diff --git a/src/app/chat/ChatInfo/BackGroundList/index.tsx b/src/app/chat/CameraMode/Settings/BackGroundList/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/BackGroundList/index.tsx
rename to src/app/chat/CameraMode/Settings/BackGroundList/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/Card/SubscribeButton.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/Card/SubscribeButton.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/Card/SubscribeButton.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/Card/SubscribeButton.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/Card/UnSubscribeButton.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/Card/UnSubscribeButton.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/Card/UnSubscribeButton.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/Card/UnSubscribeButton.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/Card/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/Card/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/Card/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/Card/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/AudioUpload.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/AudioUpload.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/AudioUpload.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/AudioUpload.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/CameraUpload.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/CameraUpload.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/CameraUpload.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/CameraUpload.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/CoverImageUpload/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/CoverImageUpload/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/CoverImageUpload/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/CoverImageUpload/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceIdInput/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceIdInput/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceIdInput/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceIdInput/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceName.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceName.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceName.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/DanceName.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/ReadMe/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/ReadMe/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/ReadMe/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/ReadMe/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/SrcUpload.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/SrcUpload.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/SrcUpload.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/SrcUpload.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/CreateDanceModal/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/CreateDanceModal/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/List/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/List/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/List/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/List/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/style.ts b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/style.ts
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/Market/style.ts
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/Market/style.ts
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/DanceMarketModal/style.ts b/src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/style.ts
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/DanceMarketModal/style.ts
rename to src/app/chat/CameraMode/Settings/DanceList/DanceMarketModal/style.ts
diff --git a/src/app/chat/ChatInfo/DanceList/Item/Actions.tsx b/src/app/chat/CameraMode/Settings/DanceList/Item/Actions.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/Item/Actions.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/Item/Actions.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/Item/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/Item/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/Item/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/Item/index.tsx
diff --git a/src/app/chat/ChatInfo/DanceList/Item/style.ts b/src/app/chat/CameraMode/Settings/DanceList/Item/style.ts
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/Item/style.ts
rename to src/app/chat/CameraMode/Settings/DanceList/Item/style.ts
diff --git a/src/app/chat/ChatInfo/DanceList/index.tsx b/src/app/chat/CameraMode/Settings/DanceList/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/DanceList/index.tsx
rename to src/app/chat/CameraMode/Settings/DanceList/index.tsx
diff --git a/src/app/chat/ChatInfo/MotionList/ActionList/index.tsx b/src/app/chat/CameraMode/Settings/MotionList/ActionList/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/MotionList/ActionList/index.tsx
rename to src/app/chat/CameraMode/Settings/MotionList/ActionList/index.tsx
diff --git a/src/app/chat/ChatInfo/MotionList/SideBar/index.tsx b/src/app/chat/CameraMode/Settings/MotionList/SideBar/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/MotionList/SideBar/index.tsx
rename to src/app/chat/CameraMode/Settings/MotionList/SideBar/index.tsx
diff --git a/src/app/chat/ChatInfo/MotionList/index.tsx b/src/app/chat/CameraMode/Settings/MotionList/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/MotionList/index.tsx
rename to src/app/chat/CameraMode/Settings/MotionList/index.tsx
diff --git a/src/app/chat/ChatInfo/PostureList/ActionList/index.tsx b/src/app/chat/CameraMode/Settings/PostureList/ActionList/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/PostureList/ActionList/index.tsx
rename to src/app/chat/CameraMode/Settings/PostureList/ActionList/index.tsx
diff --git a/src/app/chat/ChatInfo/PostureList/SideBar/filters/CategoryFilter.tsx b/src/app/chat/CameraMode/Settings/PostureList/SideBar/filters/CategoryFilter.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/PostureList/SideBar/filters/CategoryFilter.tsx
rename to src/app/chat/CameraMode/Settings/PostureList/SideBar/filters/CategoryFilter.tsx
diff --git a/src/app/chat/ChatInfo/PostureList/SideBar/filters/GenderFilter.tsx b/src/app/chat/CameraMode/Settings/PostureList/SideBar/filters/GenderFilter.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/PostureList/SideBar/filters/GenderFilter.tsx
rename to src/app/chat/CameraMode/Settings/PostureList/SideBar/filters/GenderFilter.tsx
diff --git a/src/app/chat/ChatInfo/PostureList/SideBar/index.tsx b/src/app/chat/CameraMode/Settings/PostureList/SideBar/index.tsx
similarity index 89%
rename from src/app/chat/ChatInfo/PostureList/SideBar/index.tsx
rename to src/app/chat/CameraMode/Settings/PostureList/SideBar/index.tsx
index e80dcd59..6cba985e 100644
--- a/src/app/chat/ChatInfo/PostureList/SideBar/index.tsx
+++ b/src/app/chat/CameraMode/Settings/PostureList/SideBar/index.tsx
@@ -3,12 +3,13 @@ import { ReactNode, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
-import CategoryFilter from '@/app/chat/ChatInfo/PostureList/SideBar/filters/CategoryFilter';
-import GenderFilter from '@/app/chat/ChatInfo/PostureList/SideBar/filters/GenderFilter';
import Header from '@/components/Header';
import { GenderEnum } from '@/types/agent';
import { PostureCategoryEnum } from '@/types/touch';
+import CategoryFilter from './filters/CategoryFilter';
+import GenderFilter from './filters/GenderFilter';
+
interface IndexProps {
categoryOptions: { icon: ReactNode; label: string; value: PostureCategoryEnum | undefined }[];
currentCategory: PostureCategoryEnum | undefined;
diff --git a/src/app/chat/ChatInfo/PostureList/index.tsx b/src/app/chat/CameraMode/Settings/PostureList/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/PostureList/index.tsx
rename to src/app/chat/CameraMode/Settings/PostureList/index.tsx
diff --git a/src/app/chat/ChatInfo/PostureList/style.ts b/src/app/chat/CameraMode/Settings/PostureList/style.ts
similarity index 100%
rename from src/app/chat/ChatInfo/PostureList/style.ts
rename to src/app/chat/CameraMode/Settings/PostureList/style.ts
diff --git a/src/app/chat/ChatInfo/StageList/index.tsx b/src/app/chat/CameraMode/Settings/StageList/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/StageList/index.tsx
rename to src/app/chat/CameraMode/Settings/StageList/index.tsx
diff --git a/src/app/chat/ChatInfo/index.tsx b/src/app/chat/CameraMode/Settings/index.tsx
similarity index 79%
rename from src/app/chat/ChatInfo/index.tsx
rename to src/app/chat/CameraMode/Settings/index.tsx
index 6d37796c..2bd0b4cd 100644
--- a/src/app/chat/ChatInfo/index.tsx
+++ b/src/app/chat/CameraMode/Settings/index.tsx
@@ -1,7 +1,8 @@
'use client';
-import { DraggablePanel, TabsNav } from '@lobehub/ui';
+import { ActionIcon, DraggablePanel, TabsNav } from '@lobehub/ui';
import { createStyles } from 'antd-style';
+import { X } from 'lucide-react';
import dynamic from 'next/dynamic';
import { rgba } from 'polished';
import React, { useState } from 'react';
@@ -20,11 +21,6 @@ const Loading = () => (
);
-const ChatList = dynamic(() => import('./ChatList'), {
- ssr: false,
- loading: Loading,
-});
-
const BackGround = dynamic(() => import('./BackGroundList'), {
ssr: false,
loading: Loading,
@@ -56,7 +52,7 @@ const useStyles = createStyles(({ css, token }) => ({
display: flex;
flex-direction: column;
- background-color: ${rgba(token.colorBgLayout, 0.2)};
+ background-color: ${rgba(token.colorBgLayout, 0.8)};
backdrop-filter: saturate(180%) blur(8px);
`,
header: css`
@@ -66,6 +62,15 @@ const useStyles = createStyles(({ css, token }) => ({
player: css`
min-width: 480px;
`,
+ closeIcon: css`
+ cursor: pointer;
+ margin-right: 12px;
+ color: ${token.colorTextSecondary};
+
+ &:hover {
+ color: ${token.colorText};
+ }
+ `,
}));
export default () => {
@@ -74,7 +79,7 @@ export default () => {
s.setChatSidebar,
]);
- const [tab, setTab] = useState(Tab.ChatList);
+ const [tab, setTab] = useState(Tab.DanceList);
const { t } = useTranslation('chat');
const { styles } = useStyles();
@@ -84,7 +89,7 @@ export default () => {
classNames={{ content: styles.content }}
minWidth={CHAT_INFO_WIDTH}
maxWidth={CHAT_INFO_MAX_WIDTH}
- mode={'fixed'}
+ mode={'float'}
onExpandChange={(expand) => {
setChatSidebar(expand);
}}
@@ -93,12 +98,20 @@ export default () => {
>
{
+ setChatSidebar(false);
+ }}
+ style={{ marginLeft: 12 }}
+ />
+ ),
+ }}
items={[
- {
- label: t('info.chat'),
- key: Tab.ChatList,
- },
{
label: t('info.dance'),
key: Tab.DanceList,
@@ -125,8 +138,7 @@ export default () => {
}}
/>
-
- {tab === Tab.ChatList && }
+
{tab === Tab.DanceList && }
{tab === Tab.Motions && }
{tab === Tab.Posture && }
diff --git a/src/app/chat/ChatInfo/type.ts b/src/app/chat/CameraMode/Settings/type.ts
similarity index 86%
rename from src/app/chat/ChatInfo/type.ts
rename to src/app/chat/CameraMode/Settings/type.ts
index f21b04d6..109388ba 100644
--- a/src/app/chat/ChatInfo/type.ts
+++ b/src/app/chat/CameraMode/Settings/type.ts
@@ -1,6 +1,5 @@
export enum Tab {
Background = 'background',
- ChatList = 'chats',
DanceList = 'dances',
Motions = 'motions',
Posture = 'posture',
diff --git a/src/app/chat/CameraMode/index.tsx b/src/app/chat/CameraMode/index.tsx
new file mode 100644
index 00000000..4ff52cfb
--- /dev/null
+++ b/src/app/chat/CameraMode/index.tsx
@@ -0,0 +1,47 @@
+'use client';
+
+import classNames from 'classnames';
+import { isEqual } from 'lodash-es';
+import React, { memo } from 'react';
+import { Flexbox } from 'react-layout-kit';
+
+import ChatDialog from '@/app/chat/CameraMode/ChatDialog';
+import { HEADER_HEIGHT } from '@/constants/token';
+import AgentViewer from '@/features/AgentViewer';
+import { sessionSelectors, useSessionStore } from '@/store/session';
+
+import Background from './Background';
+import Operation from './Operation';
+import Settings from './Settings';
+import { useStyles } from './style';
+
+export default memo(() => {
+ const { styles } = useStyles();
+ const [currentAgent, interactive] = useSessionStore(
+ (s) => [sessionSelectors.currentAgent(s), s.interactive],
+ isEqual,
+ );
+
+ return (
+
+
+ {currentAgent ? (
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+ );
+});
diff --git a/src/app/chat/ViewerMode/style.ts b/src/app/chat/CameraMode/style.ts
similarity index 60%
rename from src/app/chat/ViewerMode/style.ts
rename to src/app/chat/CameraMode/style.ts
index f59afe7b..c0fe47c6 100644
--- a/src/app/chat/ViewerMode/style.ts
+++ b/src/app/chat/CameraMode/style.ts
@@ -1,8 +1,6 @@
import { createStyles } from 'antd-style';
-import { CHAT_HEADER_HEIGHT, CHAT_INPUT_WIDTH } from '@/constants/token';
-
-export const useStyles = createStyles(({ css, token }) => ({
+export const useStyles = createStyles(({ css, token, responsive }) => ({
viewer: css`
position: absolute;
top: 0;
@@ -16,21 +14,19 @@ export const useStyles = createStyles(({ css, token }) => ({
max-width: 100vw;
padding: 0 12px;
- @media (max-width: 768px) {
+ ${responsive.mobile} {
width: 100%;
}
`,
dialog: css`
z-index: 2;
- margin: ${CHAT_HEADER_HEIGHT}px auto 0;
+ margin: ${token.marginLG}px auto 0;
`,
docker: css`
z-index: 2;
width: 100%;
+ margin-bottom: ${token.marginLG}px;
padding: ${token.paddingSM}px;
`,
- input: css`
- width: ${CHAT_INPUT_WIDTH};
- `,
mask: css``,
}));
diff --git a/src/app/chat/ChatHeader/actions/ToggleChatSideBar.tsx b/src/app/chat/ChatHeader/actions/ToggleChatSideBar.tsx
deleted file mode 100644
index 97d4462e..00000000
--- a/src/app/chat/ChatHeader/actions/ToggleChatSideBar.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ActionIcon } from '@lobehub/ui';
-import { PanelRightClose, PanelRightOpen } from 'lucide-react';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/token';
-import { useGlobalStore } from '@/store/global';
-
-export default () => {
- const [showChatSidebar, toggleChatSideBar] = useGlobalStore((s) => [
- s.showChatSidebar,
- s.toggleChatSideBar,
- ]);
- const { t } = useTranslation('common');
- return (
- toggleChatSideBar()}
- title={t('sideBar')}
- size={DESKTOP_HEADER_ICON_SIZE}
- />
- );
-};
diff --git a/src/app/chat/ChatHeader/actions/Voice.tsx b/src/app/chat/ChatHeader/actions/Voice.tsx
deleted file mode 100644
index 8858f5ee..00000000
--- a/src/app/chat/ChatHeader/actions/Voice.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ActionIcon } from '@lobehub/ui';
-import { createStyles } from 'antd-style';
-import classNames from 'classnames';
-import { Volume2, VolumeXIcon } from 'lucide-react';
-import { useTranslation } from 'react-i18next';
-
-import { DESKTOP_HEADER_ICON_SIZE } from '@/constants/token';
-import { toggleVoice } from '@/services/chat';
-import { useSessionStore } from '@/store/session';
-
-const useStyles = createStyles(({ token, css }) => ({
- voice: css`
- cursor: pointer;
- transition: color 0.3s;
- `,
- voiceOn: css`
- color: ${token.colorLinkActive};
- `,
-}));
-
-const VoiceSwitch = () => {
- const { styles } = useStyles();
- const [voiceOn] = useSessionStore((s) => [s.voiceOn]);
- const { t } = useTranslation('chat');
- return (
-
- );
-};
-
-export default VoiceSwitch;
diff --git a/src/app/chat/ChatInfo/ChatList/SkeletonList.tsx b/src/app/chat/ChatInfo/ChatList/SkeletonList.tsx
deleted file mode 100644
index d3ec5e86..00000000
--- a/src/app/chat/ChatInfo/ChatList/SkeletonList.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-'use client';
-
-import { Skeleton } from 'antd';
-import { createStyles } from 'antd-style';
-import { memo } from 'react';
-import { Flexbox } from 'react-layout-kit';
-
-const useStyles = createStyles(({ css, prefixCls }) => ({
- message: css`
- display: flex;
- gap: 12px;
- .${prefixCls}-skeleton-header {
- padding: 0;
- }
- `,
- user: css`
- flex-direction: row-reverse;
-
- .${prefixCls}-skeleton-paragraph {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- }
- `,
-}));
-interface SkeletonListProps {
- mobile?: boolean;
-}
-const SkeletonList = memo(({ mobile }) => {
- const { cx, styles } = useStyles();
-
- return (
-
-
-
-
- );
-});
-export default SkeletonList;
diff --git a/src/app/chat/ChatHeader/AgentMeta/index.tsx b/src/app/chat/ChatMode/ChatHeader/AgentMeta/index.tsx
similarity index 92%
rename from src/app/chat/ChatHeader/AgentMeta/index.tsx
rename to src/app/chat/ChatMode/ChatHeader/AgentMeta/index.tsx
index 7253324a..f3fe08b3 100644
--- a/src/app/chat/ChatHeader/AgentMeta/index.tsx
+++ b/src/app/chat/ChatMode/ChatHeader/AgentMeta/index.tsx
@@ -4,7 +4,6 @@ import React from 'react';
import useSessionContext from '@/hooks/useSessionContext';
-import ModelSwitchPanel from './ModelSwitchPanel';
import { useStyles } from './style';
interface AgentMetaProps {
@@ -28,7 +27,6 @@ export default (props: AgentMetaProps) => {
{name}
-
diff --git a/src/app/chat/ChatHeader/AgentMeta/style.ts b/src/app/chat/ChatMode/ChatHeader/AgentMeta/style.ts
similarity index 95%
rename from src/app/chat/ChatHeader/AgentMeta/style.ts
rename to src/app/chat/ChatMode/ChatHeader/AgentMeta/style.ts
index 0d67b0f7..01659d40 100644
--- a/src/app/chat/ChatHeader/AgentMeta/style.ts
+++ b/src/app/chat/ChatMode/ChatHeader/AgentMeta/style.ts
@@ -10,7 +10,7 @@ export const useStyles = createStyles(({ css, token }) => ({
line-height: 1;
`,
desc: css`
- width: 480px;
+ max-width: 320px;
margin-top: ${token.marginXXS}px;
font-size: ${token.fontSizeSM}px;
diff --git a/src/app/chat/ChatHeader/actions/Interactive.tsx b/src/app/chat/ChatMode/ChatHeader/actions/Interactive.tsx
similarity index 100%
rename from src/app/chat/ChatHeader/actions/Interactive.tsx
rename to src/app/chat/ChatMode/ChatHeader/actions/Interactive.tsx
diff --git a/src/app/chat/ChatHeader/actions/ShareButton/ChatList.tsx b/src/app/chat/ChatMode/ChatHeader/actions/ShareButton/ChatList.tsx
similarity index 100%
rename from src/app/chat/ChatHeader/actions/ShareButton/ChatList.tsx
rename to src/app/chat/ChatMode/ChatHeader/actions/ShareButton/ChatList.tsx
diff --git a/src/app/chat/ChatHeader/actions/ShareButton/Preview.tsx b/src/app/chat/ChatMode/ChatHeader/actions/ShareButton/Preview.tsx
similarity index 94%
rename from src/app/chat/ChatHeader/actions/ShareButton/Preview.tsx
rename to src/app/chat/ChatMode/ChatHeader/actions/ShareButton/Preview.tsx
index d9b72472..797c2b6e 100644
--- a/src/app/chat/ChatHeader/actions/ShareButton/Preview.tsx
+++ b/src/app/chat/ChatMode/ChatHeader/actions/ShareButton/Preview.tsx
@@ -5,8 +5,8 @@ import { Flexbox } from 'react-layout-kit';
import { DEFAULT_CHAT_MODEL } from '@/constants/agent';
import useSessionContext from '@/hooks/useSessionContext';
+import { siteUrl } from '@/server/utils/url';
-import pkg from '../../../../../../package.json';
import ChatList from './ChatList';
import { useStyles } from './style';
import { FieldType } from './type';
@@ -41,7 +41,7 @@ const Preview = memo(
{withFooter ? (
- {pkg.homepage}
+ {siteUrl}
) : (
diff --git a/src/app/chat/ChatHeader/actions/ShareButton/ShareModal.tsx b/src/app/chat/ChatMode/ChatHeader/actions/ShareButton/ShareModal.tsx
similarity index 100%
rename from src/app/chat/ChatHeader/actions/ShareButton/ShareModal.tsx
rename to src/app/chat/ChatMode/ChatHeader/actions/ShareButton/ShareModal.tsx
diff --git a/src/app/chat/ChatHeader/actions/ShareButton/index.tsx b/src/app/chat/ChatMode/ChatHeader/actions/ShareButton/index.tsx
similarity index 100%
rename from src/app/chat/ChatHeader/actions/ShareButton/index.tsx
rename to src/app/chat/ChatMode/ChatHeader/actions/ShareButton/index.tsx
diff --git a/src/app/chat/ChatHeader/actions/ShareButton/style.ts b/src/app/chat/ChatMode/ChatHeader/actions/ShareButton/style.ts
similarity index 100%
rename from src/app/chat/ChatHeader/actions/ShareButton/style.ts
rename to src/app/chat/ChatMode/ChatHeader/actions/ShareButton/style.ts
diff --git a/src/app/chat/ChatHeader/actions/ShareButton/type.ts b/src/app/chat/ChatMode/ChatHeader/actions/ShareButton/type.ts
similarity index 100%
rename from src/app/chat/ChatHeader/actions/ShareButton/type.ts
rename to src/app/chat/ChatMode/ChatHeader/actions/ShareButton/type.ts
diff --git a/src/app/chat/ChatHeader/actions/ToggleSessionList.tsx b/src/app/chat/ChatMode/ChatHeader/actions/ToggleSessionList.tsx
similarity index 100%
rename from src/app/chat/ChatHeader/actions/ToggleSessionList.tsx
rename to src/app/chat/ChatMode/ChatHeader/actions/ToggleSessionList.tsx
diff --git a/src/app/chat/ChatHeader/index.tsx b/src/app/chat/ChatMode/ChatHeader/index.tsx
similarity index 88%
rename from src/app/chat/ChatHeader/index.tsx
rename to src/app/chat/ChatMode/ChatHeader/index.tsx
index ca63266f..90e5e1fc 100644
--- a/src/app/chat/ChatHeader/index.tsx
+++ b/src/app/chat/ChatMode/ChatHeader/index.tsx
@@ -7,9 +7,7 @@ import React from 'react';
import { Flexbox } from 'react-layout-kit';
import ShareButton from './actions/ShareButton';
-import ToggleChatSideBar from './actions/ToggleChatSideBar';
import ToggleSessionList from './actions/ToggleSessionList';
-import Voice from './actions/Voice';
import { useStyles } from './style';
const AgentMeta = dynamic(() => import('./AgentMeta'), {
@@ -48,9 +46,7 @@ export default (props: Props) => {
-
-
);
diff --git a/src/app/chat/ChatHeader/style.ts b/src/app/chat/ChatMode/ChatHeader/style.ts
similarity index 100%
rename from src/app/chat/ChatHeader/style.ts
rename to src/app/chat/ChatMode/ChatHeader/style.ts
diff --git a/src/app/chat/ChatInfo/ChatList/AutoScroll.tsx b/src/app/chat/ChatMode/ChatList/AutoScroll.tsx
similarity index 78%
rename from src/app/chat/ChatInfo/ChatList/AutoScroll.tsx
rename to src/app/chat/ChatMode/ChatList/AutoScroll.tsx
index f95064f4..69242b87 100644
--- a/src/app/chat/ChatInfo/ChatList/AutoScroll.tsx
+++ b/src/app/chat/ChatMode/ChatList/AutoScroll.tsx
@@ -6,14 +6,15 @@ import BackBottom from './BackBottom';
interface AutoScrollProps {
atBottom: boolean;
+ isScrolling: boolean;
onScrollToBottom: (type: 'auto' | 'click') => void;
}
-const AutoScroll = memo(({ atBottom, onScrollToBottom }) => {
+const AutoScroll = memo(({ atBottom, isScrolling, onScrollToBottom }) => {
const trackVisibility = useSessionStore((s) => !!s.chatLoadingId);
const str = useSessionStore(sessionSelectors.currentChatsString);
useEffect(() => {
- if (atBottom && trackVisibility) {
+ if (atBottom && trackVisibility && !isScrolling) {
onScrollToBottom?.('auto');
}
}, [atBottom, trackVisibility, str]);
diff --git a/src/app/chat/ChatInfo/ChatList/BackBottom/index.tsx b/src/app/chat/ChatMode/ChatList/BackBottom/index.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/ChatList/BackBottom/index.tsx
rename to src/app/chat/ChatMode/ChatList/BackBottom/index.tsx
diff --git a/src/app/chat/ChatInfo/ChatList/BackBottom/style.ts b/src/app/chat/ChatMode/ChatList/BackBottom/style.ts
similarity index 100%
rename from src/app/chat/ChatInfo/ChatList/BackBottom/style.ts
rename to src/app/chat/ChatMode/ChatList/BackBottom/style.ts
diff --git a/src/app/chat/ChatMode/ChatList/SkeletonList.tsx b/src/app/chat/ChatMode/ChatList/SkeletonList.tsx
new file mode 100644
index 00000000..0cfc04a0
--- /dev/null
+++ b/src/app/chat/ChatMode/ChatList/SkeletonList.tsx
@@ -0,0 +1,74 @@
+'use client';
+
+import { Skeleton } from 'antd';
+import { createStyles } from 'antd-style';
+import { memo } from 'react';
+import { Flexbox } from 'react-layout-kit';
+
+import { CHAT_INPUT_WIDTH } from '@/constants/token';
+
+const useStyles = createStyles(({ css, prefixCls, responsive }) => ({
+ message: css`
+ display: flex;
+ gap: 12px;
+
+ width: ${CHAT_INPUT_WIDTH};
+ min-width: 480px;
+ max-width: 100vw;
+
+ .${prefixCls}-skeleton-header {
+ padding: 0;
+ }
+
+ ${responsive.mobile} {
+ width: 100%;
+ }
+ `,
+ user: css`
+ flex-direction: row-reverse;
+ width: ${CHAT_INPUT_WIDTH};
+ min-width: 480px;
+ max-width: 100vw;
+
+ .${prefixCls}-skeleton-paragraph {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ }
+
+ ${responsive.mobile} {
+ width: 100%;
+ }
+ `,
+}));
+interface SkeletonListProps {
+ count?: number;
+ mobile?: boolean;
+}
+const SkeletonList = memo(({ count = 3, mobile }) => {
+ const { cx, styles } = useStyles();
+
+ return (
+
+ {Array.from({ length: count }).map((_, index) => (
+
+
+
+
+ ))}
+
+ );
+});
+export default SkeletonList;
diff --git a/src/app/chat/ChatInfo/ChatList/VirtualizedList.tsx b/src/app/chat/ChatMode/ChatList/VirtualizedList.tsx
similarity index 68%
rename from src/app/chat/ChatInfo/ChatList/VirtualizedList.tsx
rename to src/app/chat/ChatMode/ChatList/VirtualizedList.tsx
index 8757262c..aeb889eb 100644
--- a/src/app/chat/ChatInfo/ChatList/VirtualizedList.tsx
+++ b/src/app/chat/ChatMode/ChatList/VirtualizedList.tsx
@@ -1,28 +1,15 @@
-import { Space } from 'antd';
import classNames from 'classnames';
import isEqual from 'fast-deep-equal';
-import React, { memo, useEffect, useRef, useState } from 'react';
-import { useTranslation } from 'react-i18next';
+import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
import { Flexbox } from 'react-layout-kit';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
-import Header from '@/components/Header';
import Item from '@/features/ChatItem';
import { sessionSelectors, useSessionStore } from '@/store/session';
-import History from './Actions/History';
-import TokenMini from './Actions/TokenMini';
import AutoScroll from './AutoScroll';
import { useStyles } from './style';
-const itemContent = (index: number, id: string) => {
- return index === 0 ? (
-
- ) : (
-
- );
-};
-
interface VirtualizedListProps {
className?: string;
mobile?: boolean;
@@ -31,8 +18,8 @@ interface VirtualizedListProps {
const VirtualizedList = memo(({ mobile, className, style }) => {
const virtuosoRef = useRef(null);
const [atBottom, setAtBottom] = useState(true);
+ const [isScrolling, setIsScrolling] = useState(false);
const { styles } = useStyles();
- const { t } = useTranslation('chat');
const data = useSessionStore(
(s) => ['empty', ...sessionSelectors.currentChatIDsWithGreetingMessage(s)],
@@ -40,34 +27,47 @@ const VirtualizedList = memo(({ mobile, className, style }
);
const [id, chatLoading] = useSessionStore((s) => [s.activeId, !!s.chatLoadingId]);
+ const prevDataLengthRef = useRef(data.length);
+
+ const getFollowOutput = useCallback(() => {
+ const newFollowOutput = data.length > prevDataLengthRef.current ? 'auto' : false;
+ prevDataLengthRef.current = data.length;
+ return newFollowOutput;
+ }, [data.length]);
+
useEffect(() => {
if (virtuosoRef.current) {
virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
}
}, [id]);
- // overscan should be 1.5 times the height of the window
- const overscan = typeof window !== 'undefined' ? window.innerHeight * 1.5 : 0;
+ const itemContent = useCallback(
+ (index: number, id: string) => {
+ // if (id === WELCOME_GUIDE_CHAT_ID) return ;
+
+ return index === 0 ? (
+
+ ) : (
+
+ );
+ },
+ [mobile],
+ );
+
+ // overscan should be 3 times the height of the window
+ const overscan = typeof window !== 'undefined' ? window.innerHeight * 3 : 0;
// @ts-ignore
return chatLoading && data.length === 2 ? null : (
-
-
-
-
- }
- />
item}
data={data}
- followOutput={'auto'}
+ followOutput={getFollowOutput}
+ increaseViewportBy={overscan}
+ isScrolling={setIsScrolling}
initialTopMostItemIndex={data?.length - 1}
itemContent={itemContent}
overscan={overscan}
@@ -75,6 +75,7 @@ const VirtualizedList = memo(({ mobile, className, style }
/>
{
const virtuoso = virtuosoRef.current;
switch (type) {
diff --git a/src/app/chat/ChatInfo/ChatList/index.tsx b/src/app/chat/ChatMode/ChatList/index.tsx
similarity index 57%
rename from src/app/chat/ChatInfo/ChatList/index.tsx
rename to src/app/chat/ChatMode/ChatList/index.tsx
index 656cc090..64a3918a 100644
--- a/src/app/chat/ChatInfo/ChatList/index.tsx
+++ b/src/app/chat/ChatMode/ChatList/index.tsx
@@ -1,12 +1,24 @@
+import dynamic from 'next/dynamic';
import { Flexbox } from 'react-layout-kit';
-import ChatList from './VirtualizedList';
+import SkeletonList from './SkeletonList';
interface ConversationProps {
mobile?: boolean;
}
const Conversation = ({ mobile }: ConversationProps) => {
+ const Loading = () => (
+
+
+
+ );
+
+ const ChatList = dynamic(() => import('./VirtualizedList'), {
+ ssr: false,
+ loading: Loading,
+ });
+
return (
{
const isChineseInput = useRef(false);
const onSend = useChatInput();
const { t } = useTranslation('chat');
-
const [loading, messageInput, setMessageInput] = useSessionStore((s) => [
!!s.chatLoadingId,
s.messageInput,
@@ -26,6 +25,7 @@ const InputArea = memo(() => {
onBlur={(e) => {
setMessageInput?.(e.target.value);
}}
+ // className={styles.input}
onChange={(e) => {
setMessageInput?.(e.target.value);
}}
@@ -48,7 +48,8 @@ const InputArea = memo(() => {
}}
placeholder={t('input.placeholder')}
ref={ref}
- autoSize={true}
+ rows={1}
+ autoSize={{ minRows: 1, maxRows: 10 }}
type={'block'}
value={messageInput}
/>
diff --git a/src/app/chat/ChatMode/MessageInput/actions/Camera.tsx b/src/app/chat/ChatMode/MessageInput/actions/Camera.tsx
new file mode 100644
index 00000000..84550f9e
--- /dev/null
+++ b/src/app/chat/ChatMode/MessageInput/actions/Camera.tsx
@@ -0,0 +1,26 @@
+import { ActionIcon } from '@lobehub/ui';
+import { message } from 'antd';
+import { Video } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+
+import { useSessionStore } from '@/store/session';
+
+const Camera = () => {
+ const [setChatMode] = useSessionStore((s) => [s.setChatMode]);
+ const setVoiceOn = useSessionStore((s) => s.setVoiceOn);
+ const { t } = useTranslation('chat');
+
+ return (
+ {
+ message.info(t('voiceOn'));
+ setVoiceOn(true);
+ setChatMode('camera');
+ }}
+ title={t('camera')}
+ />
+ );
+};
+
+export default Camera;
diff --git a/src/app/chat/ChatInfo/ChatList/Actions/History.tsx b/src/app/chat/ChatMode/MessageInput/actions/History.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/ChatList/Actions/History.tsx
rename to src/app/chat/ChatMode/MessageInput/actions/History.tsx
diff --git a/src/app/chat/ChatHeader/AgentMeta/ModelSwitchPanel.tsx b/src/app/chat/ChatMode/MessageInput/actions/ModelSwitchPanel.tsx
similarity index 100%
rename from src/app/chat/ChatHeader/AgentMeta/ModelSwitchPanel.tsx
rename to src/app/chat/ChatMode/MessageInput/actions/ModelSwitchPanel.tsx
diff --git a/src/app/chat/ChatInfo/ChatList/Actions/TokenMini.tsx b/src/app/chat/ChatMode/MessageInput/actions/TokenMini.tsx
similarity index 100%
rename from src/app/chat/ChatInfo/ChatList/Actions/TokenMini.tsx
rename to src/app/chat/ChatMode/MessageInput/actions/TokenMini.tsx
diff --git a/src/app/chat/ViewerMode/MessageInput/index.tsx b/src/app/chat/ChatMode/MessageInput/index.tsx
similarity index 81%
rename from src/app/chat/ViewerMode/MessageInput/index.tsx
rename to src/app/chat/ChatMode/MessageInput/index.tsx
index c2ef7070..29d372c3 100644
--- a/src/app/chat/ViewerMode/MessageInput/index.tsx
+++ b/src/app/chat/ChatMode/MessageInput/index.tsx
@@ -1,6 +1,6 @@
import { SendOutlined } from '@ant-design/icons';
import { Icon } from '@lobehub/ui';
-import { Button } from 'antd';
+import { Button, Space } from 'antd';
import { useTheme } from 'antd-style';
import { ChevronUp, CornerDownLeft, LucideCommand } from 'lucide-react';
import React, { memo } from 'react';
@@ -13,7 +13,10 @@ import { useSessionStore } from '@/store/session';
import { isMacOS } from '@/utils/platform';
import TextArea from './TextArea';
-import Record from './actions/Record';
+import Camera from './actions/Camera';
+import History from './actions/History';
+import ModelSwitchPanel from './actions/ModelSwitchPanel';
+import TokenMini from './actions/TokenMini';
import { useStyles } from './style';
interface InputAreaProps {
@@ -61,7 +64,14 @@ const InputArea = memo((props: InputAreaProps) => {
return (
-
+
+
+
+
+
+
+
+
{t('input.alert')}
diff --git a/src/app/chat/ViewerMode/MessageInput/style.ts b/src/app/chat/ChatMode/MessageInput/style.ts
similarity index 100%
rename from src/app/chat/ViewerMode/MessageInput/style.ts
rename to src/app/chat/ChatMode/MessageInput/style.ts
diff --git a/src/app/chat/SideBar/SessionList/Elsa/index.tsx b/src/app/chat/ChatMode/SideBar/SessionList/Elsa/index.tsx
similarity index 100%
rename from src/app/chat/SideBar/SessionList/Elsa/index.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/Elsa/index.tsx
diff --git a/src/app/chat/SideBar/SessionList/List/Item/Actions.tsx b/src/app/chat/ChatMode/SideBar/SessionList/List/Item/Actions.tsx
similarity index 100%
rename from src/app/chat/SideBar/SessionList/List/Item/Actions.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/List/Item/Actions.tsx
diff --git a/src/app/chat/SideBar/SessionList/List/Item/index.tsx b/src/app/chat/ChatMode/SideBar/SessionList/List/Item/index.tsx
similarity index 100%
rename from src/app/chat/SideBar/SessionList/List/Item/index.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/List/Item/index.tsx
diff --git a/src/app/chat/SideBar/SessionList/List/SkeletonList.tsx b/src/app/chat/ChatMode/SideBar/SessionList/List/SkeletonList.tsx
similarity index 100%
rename from src/app/chat/SideBar/SessionList/List/SkeletonList.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/List/SkeletonList.tsx
diff --git a/src/app/chat/SideBar/SessionList/List/index.tsx b/src/app/chat/ChatMode/SideBar/SessionList/List/index.tsx
similarity index 63%
rename from src/app/chat/SideBar/SessionList/List/index.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/List/index.tsx
index 9dfdf574..83725ca9 100644
--- a/src/app/chat/SideBar/SessionList/List/index.tsx
+++ b/src/app/chat/ChatMode/SideBar/SessionList/List/index.tsx
@@ -1,6 +1,8 @@
+import { Empty } from 'antd';
import { createStyles } from 'antd-style';
import { isEqual } from 'lodash-es';
import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
import LazyLoad from 'react-lazy-load';
import useSessionContext from '@/hooks/useSessionContext';
@@ -25,14 +27,21 @@ const SessionList = memo(({ filter }) => {
isEqual,
);
const { styles } = useStyles();
-
+ const { t } = useTranslation('chat');
const { switchSession } = useSessionContext();
- return sessionListIds.map((id) => (
-
- switchSession(id)} />
-
- ));
+ return (
+ <>
+ {sessionListIds.map((id) => (
+
+ switchSession(id)} />
+
+ ))}
+ {sessionListIds.length === 0 && (
+
+ )}
+ >
+ );
});
export default SessionList;
diff --git a/src/app/chat/SideBar/SessionList/ListItem.tsx b/src/app/chat/ChatMode/SideBar/SessionList/ListItem.tsx
similarity index 100%
rename from src/app/chat/SideBar/SessionList/ListItem.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/ListItem.tsx
diff --git a/src/app/chat/SideBar/SessionList/SkeletonList.tsx b/src/app/chat/ChatMode/SideBar/SessionList/SkeletonList.tsx
similarity index 100%
rename from src/app/chat/SideBar/SessionList/SkeletonList.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/SkeletonList.tsx
diff --git a/src/app/chat/SideBar/SessionList/index.tsx b/src/app/chat/ChatMode/SideBar/SessionList/index.tsx
similarity index 100%
rename from src/app/chat/SideBar/SessionList/index.tsx
rename to src/app/chat/ChatMode/SideBar/SessionList/index.tsx
diff --git a/src/app/chat/SideBar/index.tsx b/src/app/chat/ChatMode/SideBar/index.tsx
similarity index 100%
rename from src/app/chat/SideBar/index.tsx
rename to src/app/chat/ChatMode/SideBar/index.tsx
diff --git a/src/app/chat/ChatMode/index.tsx b/src/app/chat/ChatMode/index.tsx
new file mode 100644
index 00000000..6e6b3470
--- /dev/null
+++ b/src/app/chat/ChatMode/index.tsx
@@ -0,0 +1,33 @@
+'use client';
+
+import classNames from 'classnames';
+import React, { memo } from 'react';
+import { Flexbox } from 'react-layout-kit';
+
+import ChatHeader from './ChatHeader';
+import ChatList from './ChatList';
+import MessageInput from './MessageInput';
+import SideBar from './SideBar';
+import { useStyles } from './style';
+
+const Chat = () => {
+ const { styles } = useStyles();
+ const ref = React.useRef(null);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default memo(Chat);
diff --git a/src/app/chat/ChatMode/style.ts b/src/app/chat/ChatMode/style.ts
new file mode 100644
index 00000000..a26a9a2d
--- /dev/null
+++ b/src/app/chat/ChatMode/style.ts
@@ -0,0 +1,24 @@
+import { createStyles } from 'antd-style';
+
+import { CHAT_HEADER_HEIGHT, CHAT_INPUT_WIDTH } from '@/constants/token';
+
+export const useStyles = createStyles(({ css, token, responsive }) => ({
+ chat: css``,
+ list: css`
+ margin-top: ${CHAT_HEADER_HEIGHT}px;
+ `,
+ input: css`
+ width: ${CHAT_INPUT_WIDTH};
+ min-width: 480px;
+ max-width: 100vw;
+
+ ${responsive.mobile} {
+ width: 100%;
+ }
+ `,
+
+ docker: css`
+ width: 100%;
+ padding: ${token.paddingSM}px;
+ `,
+}));
diff --git a/src/app/chat/Effect/index.tsx b/src/app/chat/Effect/index.tsx
new file mode 100644
index 00000000..ee5acd13
--- /dev/null
+++ b/src/app/chat/Effect/index.tsx
@@ -0,0 +1,12 @@
+import { useSettingStore } from '@/store/setting';
+
+import { useStyles } from './style';
+
+const Background = () => {
+ const { styles } = useStyles();
+ const backgroundEffect = useSettingStore((s) => s.config.backgroundEffect);
+
+ return backgroundEffect === 'glow' ? : null;
+};
+
+export default Background;
diff --git a/src/app/chat/Background/style.ts b/src/app/chat/Effect/style.ts
similarity index 100%
rename from src/app/chat/Background/style.ts
rename to src/app/chat/Effect/style.ts
diff --git a/src/app/chat/ViewerMode/index.tsx b/src/app/chat/ViewerMode/index.tsx
deleted file mode 100644
index 166b3c8d..00000000
--- a/src/app/chat/ViewerMode/index.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-'use client';
-
-import classNames from 'classnames';
-import { isEqual } from 'lodash-es';
-import React, { memo } from 'react';
-import { Flexbox } from 'react-layout-kit';
-
-import ChatDialog from '@/app/chat/ViewerMode/ChatDialog';
-import { HEADER_HEIGHT } from '@/constants/token';
-import AgentViewer from '@/features/AgentViewer';
-import { sessionSelectors, useSessionStore } from '@/store/session';
-
-import MessageInput from './MessageInput';
-import { useStyles } from './style';
-
-export default memo(() => {
- const { styles } = useStyles();
- const [currentAgent, interactive] = useSessionStore(
- (s) => [sessionSelectors.currentAgent(s), s.interactive],
- isEqual,
- );
-
- return (
-
- {currentAgent ? (
-
- ) : null}
-
-
-
-
-
-
-
-
- );
-});
diff --git a/src/app/chat/layout.tsx b/src/app/chat/layout.tsx
index e5c605b0..a79bec8c 100644
--- a/src/app/chat/layout.tsx
+++ b/src/app/chat/layout.tsx
@@ -5,7 +5,7 @@ import { ReactNode, memo } from 'react';
import AppLayout from '@/layout/AppLayout';
import { HeaderNavKey } from '@/layout/type';
-import Background from './Background';
+import Effect from './Effect';
export interface LayoutProps {
children?: ReactNode;
@@ -16,7 +16,8 @@ const LayoutDesktop = (props: LayoutProps) => {
return (
- {children}
+ {children}
+
);
};
diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx
index 6bcbf86e..94dfe14d 100644
--- a/src/app/chat/page.tsx
+++ b/src/app/chat/page.tsx
@@ -1,13 +1,17 @@
+'use client';
+
import { Spin } from 'antd';
import dynamic from 'next/dynamic';
+import { useSearchParams } from 'next/navigation';
import React, { memo } from 'react';
import { Center, Flexbox } from 'react-layout-kit';
-import ChatHeader from './ChatHeader';
-import ChatInfo from './ChatInfo';
-import SideBar from './SideBar';
+import DebugUI from '@/features/DebugUI';
+import { useSessionStore } from '@/store/session';
+
+import ChatMode from './ChatMode';
-const ViewerMode = dynamic(() => import('./ViewerMode'), {
+const CameraMode = dynamic(() => import('./CameraMode'), {
ssr: false,
loading: () => (
@@ -17,21 +21,15 @@ const ViewerMode = dynamic(() => import('./ViewerMode'), {
});
const Chat = () => {
+ const chatMode = useSessionStore((s) => s.chatMode);
+
+ const searchParams = useSearchParams();
+ const showDebug = process.env.NODE_ENV === 'development' && searchParams.get('debug') === 'true';
return (
-
-
-
-
-
-
+ {chatMode === 'camera' && }
+ {chatMode === 'chat' && }
+ {showDebug && }
);
};
diff --git a/src/app/discover/page.tsx b/src/app/discover/page.tsx
index 7d0cb4e9..3e8ba56d 100644
--- a/src/app/discover/page.tsx
+++ b/src/app/discover/page.tsx
@@ -15,7 +15,7 @@ import MarketInfo from './MarketInfo';
const FETCH_AGENT_INDEX_KEY = 'agentIndex';
-const useStyles = createStyles(({ css }) => ({
+const useStyles = createStyles(({ css, responsive }) => ({
container: css`
overflow-y: auto;
@@ -24,7 +24,7 @@ const useStyles = createStyles(({ css }) => ({
min-height: 500px;
padding: 0 32px 16px;
- @media screen and (max-width: 768px) {
+ ${responsive.mobile} {
padding: 0 16px;
}
`,
diff --git a/src/app/role/RoleEdit/Touch/ActionList/Actions/Play.tsx b/src/app/role/RoleEdit/Touch/ActionList/Actions/Play.tsx
index 5fdcb123..dafd46a7 100644
--- a/src/app/role/RoleEdit/Touch/ActionList/Actions/Play.tsx
+++ b/src/app/role/RoleEdit/Touch/ActionList/Actions/Play.tsx
@@ -1,4 +1,5 @@
import { ActionIcon } from '@lobehub/ui';
+import { message } from 'antd';
import { isEqual } from 'lodash-es';
import { Loader2, PlayIcon } from 'lucide-react';
import { memo, useState } from 'react';
@@ -33,10 +34,6 @@ export default memo((props: Props) => {
title={t('play', { ns: 'common' })}
key="play"
onClick={() => {
- setLoading(true);
-
- console.log('touchAction', touchAction);
-
speakCharacter(
{
expression: touchAction.expression,
@@ -47,9 +44,16 @@ export default memo((props: Props) => {
motion: touchAction.motion,
},
viewer,
- () => {},
- () => {
- setLoading(false);
+ {
+ onStart: () => {
+ setLoading(true);
+ },
+ onComplete: () => {
+ setLoading(false);
+ },
+ onError: () => {
+ message.error(t('ttsTransformFailed', { ns: 'error' }));
+ },
},
);
}}
diff --git a/src/components/ChatItem/components/Avatar.tsx b/src/components/ChatItem/components/Avatar.tsx
index a7b3732e..a7b6e80f 100644
--- a/src/components/ChatItem/components/Avatar.tsx
+++ b/src/components/ChatItem/components/Avatar.tsx
@@ -36,7 +36,7 @@ const Avatar = memo(
if (!addon) return avatarContent;
return (
-
+
{avatarContent}
{addon}
diff --git a/src/components/ChatItem/index.tsx b/src/components/ChatItem/index.tsx
index 04888823..45553f3d 100644
--- a/src/components/ChatItem/index.tsx
+++ b/src/components/ChatItem/index.tsx
@@ -1,6 +1,7 @@
'use client';
import { useResponsive } from 'antd-style';
+import React from 'react';
import { Flexbox } from 'react-layout-kit';
import Actions from './components/Actions';
@@ -14,101 +15,103 @@ import { ChatItemProps } from './type';
const MOBILE_AVATAR_SIZE = 32;
-const ChatItem = ({
- avatarAddon,
- onAvatarClick,
- avatarProps,
- actions,
- className,
- primary,
- loading,
- message,
- placement = 'left',
- type = 'block',
- avatar,
- error,
- showTitle,
- time,
- editing,
- onChange,
- onEditingChange,
- messageExtra,
- renderMessage,
- text,
- errorMessage,
- onDoubleClick,
- fontSize,
- ...rest
-}: ChatItemProps) => {
- const { mobile } = useResponsive();
- const { cx, styles } = useStyles({
- editing,
- placement,
+const ChatItem = React.forwardRef(
+ ({
+ avatarAddon,
+ onAvatarClick,
+ avatarProps,
+ actions,
+ className,
primary,
+ loading,
+ message,
+ placement = 'left',
+ type = 'block',
+ avatar,
+ error,
showTitle,
time,
- title: avatar.title,
- type,
- });
+ editing,
+ onChange,
+ onEditingChange,
+ messageExtra,
+ renderMessage,
+ text,
+ errorMessage,
+ onDoubleClick,
+ fontSize,
+ ...rest
+ }: ChatItemProps) => {
+ const { mobile } = useResponsive();
+ const { cx, styles } = useStyles({
+ editing,
+ placement,
+ primary,
+ showTitle,
+ time,
+ title: avatar.title,
+ type,
+ });
- return (
-
-
+ return (
-
+
- {error ? (
-
- ) : (
-
- )}
-
+
+
+ {error ? (
+
+ ) : (
+
+ )}
+
+
+ {mobile && type === 'block' && }
- {mobile && type === 'block' && }
-
- );
-};
+ );
+ },
+);
export default ChatItem;
diff --git a/src/constants/token.ts b/src/constants/token.ts
index 864bdebf..6e3980e0 100644
--- a/src/constants/token.ts
+++ b/src/constants/token.ts
@@ -3,6 +3,8 @@ import type { FormProps } from '@lobehub/ui';
export const INITIAL_COORDINATES = { x: 360, y: 360 };
export const DESKTOP_HEADER_ICON_SIZE = { fontSize: 24 };
+export const DESKTOP_OPERATION_ICON_SIZE = { fontSize: 24, borderRadius: 24, blockSize: 48 };
+export const DESKTOP_OPERATION_ICON_SIZE_LARGE = { fontSize: 24, borderRadius: 32, blockSize: 64 };
export const MAX_WIDTH = 1024;
export const FORM_STYLE: FormProps = {
diff --git a/src/constants/url.ts b/src/constants/url.ts
index 029230a0..7720e8d8 100644
--- a/src/constants/url.ts
+++ b/src/constants/url.ts
@@ -2,6 +2,8 @@ import urlJoin from 'url-join';
export const UTM_SOURCE = 'lobevidol';
+export const GITHUB_REPO = 'https://github.com/lobehub/lobe-vidol';
+
export const AGENTS_INDEX_GITHUB = 'https://github.com/lobehub/lobe-vidol-market';
export const AGENTS_INDEX_GITHUB_ISSUE = urlJoin(AGENTS_INDEX_GITHUB, 'issues/new');
export const imageUrl = (filename: string) => `/images/${filename}`;
diff --git a/src/features/AgentViewer/ToolBar/index.tsx b/src/features/AgentViewer/ToolBar/index.tsx
index 1864be35..e0a7995e 100644
--- a/src/features/AgentViewer/ToolBar/index.tsx
+++ b/src/features/AgentViewer/ToolBar/index.tsx
@@ -8,7 +8,7 @@ import {
Orbit,
Pointer,
PointerOff,
- Power,
+ RotateCcw,
SwitchCamera,
} from 'lucide-react';
import React from 'react';
@@ -63,8 +63,8 @@ const ToolBar = (props: ToolBarProps) => {
dropdownMenu={dropdownMenu}
items={[
{
- icon: Power,
- key: 'power',
+ icon: RotateCcw,
+ key: 'reset',
label: t('toolBar.resetToIdle'),
},
{
@@ -96,7 +96,7 @@ const ToolBar = (props: ToolBarProps) => {
break;
}
- case 'power': {
+ case 'reset': {
viewer.resetToIdle();
break;
}
diff --git a/src/features/AgentViewer/index.tsx b/src/features/AgentViewer/index.tsx
index 24101ae3..cedfc4ff 100644
--- a/src/features/AgentViewer/index.tsx
+++ b/src/features/AgentViewer/index.tsx
@@ -78,10 +78,14 @@ function AgentViewer(props: Props) {
motion: touchAction.motion,
},
viewer,
- () => {},
- () => {
- viewer.model?.loadIdleAnimation();
- playingRef.current = false;
+ {
+ onComplete: () => {
+ viewer.model?.loadIdleAnimation();
+ playingRef.current = false;
+ },
+ onError: () => {
+ message.error(t('ttsTransformFailed', { ns: 'error' }));
+ },
},
);
}
@@ -177,11 +181,16 @@ function AgentViewer(props: Props) {
motion: MotionPresetName.FemaleGreeting,
},
viewer,
- () => {},
- () => {
- //
- viewer.resetToIdle();
- playingRef.current = false;
+ {
+ onComplete: () => {
+ viewer.resetToIdle();
+ playingRef.current = false;
+ },
+ onError: () => {
+ viewer.resetToIdle();
+ message.error(t('ttsTransformFailed', { ns: 'error' }));
+ playingRef.current = false;
+ },
},
);
}
diff --git a/src/features/ChatItem/Actions/Assistant.tsx b/src/features/ChatItem/Actions/Assistant.tsx
index 921f5d1f..e859727d 100644
--- a/src/features/ChatItem/Actions/Assistant.tsx
+++ b/src/features/ChatItem/Actions/Assistant.tsx
@@ -1,28 +1,18 @@
import { ActionIconGroup } from '@lobehub/ui';
-import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
-import { Play } from 'lucide-react';
import { memo } from 'react';
-import { useTranslation } from 'react-i18next';
import type { RenderAction } from '@/features/ChatItem/type';
import { useChatListActionsBar } from '@/hooks/useChatListActionsBar';
const AssistantActionsBar: RenderAction = ({ onActionClick, id }) => {
- const { copy, regenerate, divider, del, delAndRegenerate, edit } = useChatListActionsBar();
- const { t } = useTranslation('chat');
+ const { copy, regenerate, divider, del, delAndRegenerate, edit, tts } = useChatListActionsBar();
if (id === 'default') return;
- const tts = {
- icon: Play,
- key: 'tts',
- label: t('tts.combine'),
- } as ActionIconGroupItems;
-
return (
diff --git a/src/features/ChatItem/Actions/index.tsx b/src/features/ChatItem/Actions/index.tsx
index 1b017bb5..96795379 100644
--- a/src/features/ChatItem/Actions/index.tsx
+++ b/src/features/ChatItem/Actions/index.tsx
@@ -4,7 +4,6 @@ import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { OnActionsClick, RenderAction } from '@/features/ChatItem/type';
-import { handleSpeakAi } from '@/services/chat';
import { useSessionStore } from '@/store/session';
import { LLMRoleType } from '@/types/llm';
@@ -19,9 +18,10 @@ export const renderActions: Record = {
};
export const useActionsClick = (): OnActionsClick => {
- const [deleteMessage, regenerateMessage] = useSessionStore((s) => [
+ const [deleteMessage, regenerateMessage, ttsMessage] = useSessionStore((s) => [
s.deleteMessage,
s.regenerateMessage,
+ s.ttsMessage,
]);
const { message } = App.useApp();
const { t } = useTranslation('chat');
@@ -53,7 +53,7 @@ export const useActionsClick = (): OnActionsClick => {
}
case 'tts': {
- handleSpeakAi(content);
+ ttsMessage(id, content);
break;
}
}
diff --git a/src/features/ChatItem/AvatarAddon/Assistant.tsx b/src/features/ChatItem/AvatarAddon/Assistant.tsx
new file mode 100644
index 00000000..c9653110
--- /dev/null
+++ b/src/features/ChatItem/AvatarAddon/Assistant.tsx
@@ -0,0 +1,14 @@
+import { memo } from 'react';
+
+import { useSessionStore } from '@/store/session';
+import { sessionSelectors } from '@/store/session/selectors';
+import { ChatMessage } from '@/types/message';
+
+import { RenderAvatarAddon } from '../type';
+import TTS from './TTS';
+
+export const AssistantAvatarAddon: RenderAvatarAddon = memo(({ content, id }) => {
+ const loading = useSessionStore((s) => sessionSelectors.ttsLoading(s)(id));
+
+ return ;
+});
diff --git a/src/features/ChatItem/AvatarAddon/TTS.tsx b/src/features/ChatItem/AvatarAddon/TTS.tsx
new file mode 100644
index 00000000..e29e82d3
--- /dev/null
+++ b/src/features/ChatItem/AvatarAddon/TTS.tsx
@@ -0,0 +1,112 @@
+import { createStyles, useTheme } from 'antd-style';
+import { Play } from 'lucide-react';
+import React from 'react';
+
+import { useSessionStore } from '@/store/session';
+
+const useStyles = createStyles(({ css, token }) => ({
+ container: css`
+ cursor: pointer;
+
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ width: 24px;
+ height: 24px;
+
+ border-radius: 50%;
+
+ transition: all 0.2s ease-in-out;
+
+ &:hover {
+ background: ${token.colorFillSecondary};
+ }
+ `,
+ audioWaves: css`
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+
+ display: flex;
+ gap: 2px;
+ align-items: center;
+
+ height: 16px;
+ padding: 4px;
+
+ border-radius: 50%;
+
+ transition: all 0.2s ease-in-out;
+ `,
+ wave: css`
+ width: 2px;
+ height: 8px;
+
+ background-color: ${token.colorPrimary};
+ border-radius: 2px;
+
+ animation: wave 1s ease-in-out infinite;
+
+ &:nth-child(2) {
+ animation-delay: 0.2s;
+ }
+
+ &:nth-child(3) {
+ animation-delay: 0.4s;
+ }
+
+ &:nth-child(4) {
+ animation-delay: 0.6s;
+ }
+
+ @keyframes wave {
+ 0%,
+ 100% {
+ height: 8px;
+ opacity: 0.5;
+ }
+
+ 50% {
+ height: 16px;
+ opacity: 1;
+ }
+ }
+ `,
+ playIcon: css`
+ width: 16px;
+ height: 16px;
+ color: ${token.colorPrimary};
+ `,
+}));
+
+export default ({ content, id, loading }: { content: string; id: string; loading: boolean }) => {
+ const { styles } = useStyles();
+ const stopTtsMessage = useSessionStore((s) => s.stopTtsMessage);
+ const ttsMessage = useSessionStore((s) => s.ttsMessage);
+ const theme = useTheme();
+
+ return (
+
+ {loading ? (
+
stopTtsMessage()}>
+
+
+
+
+
+ ) : (
+
ttsMessage(id, content)}
+ />
+ )}
+
+ );
+};
diff --git a/src/features/ChatItem/AvatarAddon/index.tsx b/src/features/ChatItem/AvatarAddon/index.tsx
new file mode 100644
index 00000000..7b52d4c1
--- /dev/null
+++ b/src/features/ChatItem/AvatarAddon/index.tsx
@@ -0,0 +1,7 @@
+import { RenderAvatarAddon } from '../type';
+import { AssistantAvatarAddon } from './Assistant';
+
+export const renderAvatarAddon: Record = {
+ assistant: AssistantAvatarAddon,
+ user: () => null,
+};
diff --git a/src/features/ChatItem/index.tsx b/src/features/ChatItem/index.tsx
index c44fbe5e..b875eb1a 100644
--- a/src/features/ChatItem/index.tsx
+++ b/src/features/ChatItem/index.tsx
@@ -11,10 +11,11 @@ import { sessionSelectors } from '@/store/session/selectors';
import { ChatMessage } from '@/types/chat';
import ActionsBar from './ActionsBar';
+import { renderAvatarAddon } from './AvatarAddon';
import ErrorMessageExtra, { useErrorContent } from './Error';
import { renderMessages } from './Messages';
-const useStyles = createStyles(({ css, prefixCls }) => ({
+const useStyles = createStyles(({ css, prefixCls, responsive }) => ({
message: css`
width: 100%;
min-width: 480px;
@@ -25,7 +26,7 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
max-height: 900px;
}
- @media (max-width: 1024px) {
+ ${responsive.mobile} {
width: 100%;
}
`,
@@ -70,6 +71,18 @@ const Item = memo(
[item?.role],
);
+ const AvatarAddon = useCallback(
+ ({ data }: { data: ChatMessage }) => {
+ if (!item?.role) return;
+ let RenderFunction;
+ if (renderAvatarAddon?.[item.role]) RenderFunction = renderAvatarAddon[item.role];
+
+ if (!RenderFunction) return;
+ return ;
+ },
+ [item?.role],
+ );
+
const error = useErrorContent(item?.error);
return (
@@ -77,6 +90,7 @@ const Item = memo(
}
avatar={item.meta}
+ avatarAddon={}
className={classNames(styles.message, className)}
editing={editing}
error={error}
diff --git a/src/features/ChatItem/type.ts b/src/features/ChatItem/type.ts
index c77d0c3b..44001e4f 100644
--- a/src/features/ChatItem/type.ts
+++ b/src/features/ChatItem/type.ts
@@ -8,3 +8,6 @@ export type ActionsBarProps = ActionIconGroupProps;
export type OnActionsClick = (action: ActionEvent, message: ChatMessage) => void;
export type RenderMessage = FC;
export type RenderAction = FC;
+export type RenderMessageExtra = FC;
+export type RenderAvatarAddon = FC;
+export type RenderTitleExtra = FC;
diff --git a/src/features/DebugUI/data.ts b/src/features/DebugUI/data.ts
index 3ef5996a..1d839521 100644
--- a/src/features/DebugUI/data.ts
+++ b/src/features/DebugUI/data.ts
@@ -447,7 +447,6 @@ const sessions = {
},
],
},
- viewerMode: false,
voiceOn: false,
localAgentList: [
{
diff --git a/src/features/UserPanel/LocaleSwitch.tsx b/src/features/UserPanel/LocaleSwitch.tsx
index 9dbc623f..33482944 100644
--- a/src/features/UserPanel/LocaleSwitch.tsx
+++ b/src/features/UserPanel/LocaleSwitch.tsx
@@ -13,7 +13,7 @@ import { useSettingStore } from '@/store/setting';
import { LocaleMode } from '@/types/locale';
const LocaleSwitch = memo(() => {
- const { t } = useTranslation('common');
+ const { t } = useTranslation(['common', 'settings']);
const theme = useTheme();
const [switchLocale] = useSettingStore((s) => [s.switchLocale], isEqual);
const router = useRouter();
diff --git a/src/hooks/useChatListActionsBar.tsx b/src/hooks/useChatListActionsBar.tsx
index be8b1786..816a29d6 100644
--- a/src/hooks/useChatListActionsBar.tsx
+++ b/src/hooks/useChatListActionsBar.tsx
@@ -1,5 +1,5 @@
import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
-import { Copy, Edit, ListRestart, RotateCcw, Trash } from 'lucide-react';
+import { Copy, Edit, ListRestart, Play, RotateCcw, Trash } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -10,6 +10,7 @@ interface ChatListActionsBar {
divider: { type: 'divider' };
edit: ActionIconGroupItems;
regenerate: ActionIconGroupItems;
+ tts: ActionIconGroupItems;
}
export const useChatListActionsBar = (): ChatListActionsBar => {
@@ -40,6 +41,11 @@ export const useChatListActionsBar = (): ChatListActionsBar => {
key: 'edit',
label: t('actions.edit'),
},
+ tts: {
+ icon: Play,
+ key: 'tts',
+ label: t('actions.tts'),
+ },
regenerate: {
icon: RotateCcw,
key: 'regenerate',
diff --git a/src/hooks/useScreenshot.ts b/src/hooks/useScreenshot.ts
index 31e3226d..77ca1966 100644
--- a/src/hooks/useScreenshot.ts
+++ b/src/hooks/useScreenshot.ts
@@ -2,7 +2,7 @@ import dayjs from 'dayjs';
import { domToJpeg, domToPng, domToSvg, domToWebp } from 'modern-screenshot';
import { useCallback, useState } from 'react';
-import { ImageType } from '@/app/chat/ChatHeader/actions/ShareButton/type';
+import { ImageType } from '@/app/chat/ChatMode/ChatHeader/actions/ShareButton/type';
import { useSessionStore } from '@/store/session';
import { sessionSelectors } from '@/store/session/selectors';
diff --git a/src/layout/AppLayout.tsx b/src/layout/AppLayout.tsx
index 93725afe..df12776a 100644
--- a/src/layout/AppLayout.tsx
+++ b/src/layout/AppLayout.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import { ReactNode, memo } from 'react';
import { Flexbox } from 'react-layout-kit';
diff --git a/src/layout/index.tsx b/src/layout/index.tsx
index 21f89351..93a26ea9 100644
--- a/src/layout/index.tsx
+++ b/src/layout/index.tsx
@@ -1,8 +1,7 @@
import { PrimaryColors } from '@lobehub/ui';
import { ThemeAppearance } from 'antd-style';
-import dynamic from 'next/dynamic';
import { cookies, headers } from 'next/headers';
-import { FC, ReactNode } from 'react';
+import { ReactNode } from 'react';
import { resolveAcceptLanguage } from 'resolve-accept-language';
import { LOBE_LOCALE_COOKIE } from '@/constants/locale';
@@ -19,12 +18,6 @@ import { getAntdLocale } from '@/utils/locale';
import Locale from './Locale';
-let DebugUI: FC = () => null;
-
-if (process.env.NODE_ENV === 'development') {
- DebugUI = dynamic(() => import('@/features/DebugUI'), { ssr: false }) as FC;
-}
-
export interface LayoutProps {
children?: ReactNode;
defaultAppearance?: ThemeAppearance;
@@ -65,8 +58,6 @@ const Layout = async (props: LayoutProps) => {
defaultNeutralColor={neutralColor?.value as any}
defaultPrimaryColor={primaryColor?.value as any}
>
-
-
{children}
diff --git a/src/libs/agent-runtime/minimax/index.ts b/src/libs/agent-runtime/minimax/index.ts
index 1e7b51e0..78abbb87 100644
--- a/src/libs/agent-runtime/minimax/index.ts
+++ b/src/libs/agent-runtime/minimax/index.ts
@@ -102,7 +102,6 @@ export class LobeMinimaxAI implements LobeRuntimeAI {
return StreamingResponse(MinimaxStream(prod), { headers: options?.headers });
} catch (error) {
- console.log('error', error);
const err = error as Error | ChatCompletionErrorPayload;
if ('provider' in err) {
throw error;
diff --git a/src/libs/agent-runtime/utils/streams/bedrock/common.ts b/src/libs/agent-runtime/utils/streams/bedrock/common.ts
index dbc93b76..3fe015eb 100644
--- a/src/libs/agent-runtime/utils/streams/bedrock/common.ts
+++ b/src/libs/agent-runtime/utils/streams/bedrock/common.ts
@@ -15,8 +15,6 @@ const chatStreamable = async function* (stream: AsyncIterable) {
yield chunk;
} catch (e) {
- console.log('bedrock stream parser error:', e);
-
yield value;
}
} else {
diff --git a/src/libs/audio/AudioPlayer.ts b/src/libs/audio/AudioPlayer.ts
new file mode 100644
index 00000000..90dceef2
--- /dev/null
+++ b/src/libs/audio/AudioPlayer.ts
@@ -0,0 +1,63 @@
+export class AudioPlayer {
+ private static instance: AudioPlayer;
+ private audioContext?: AudioContext;
+ private audioSource?: AudioBufferSourceNode;
+ private isPlaying: boolean = false;
+
+ private constructor() {}
+
+ public static getInstance(): AudioPlayer {
+ if (!AudioPlayer.instance) {
+ AudioPlayer.instance = new AudioPlayer();
+ }
+ return AudioPlayer.instance;
+ }
+
+ public async play(audioBuffer: ArrayBuffer, onEnded?: () => void): Promise {
+ // 如果正在播放,先停止当前播放
+ this.stop();
+
+ this.audioContext = new AudioContext();
+ this.audioSource = this.audioContext.createBufferSource();
+
+ return new Promise((resolve) => {
+ this.audioContext!.decodeAudioData(audioBuffer, (buffer) => {
+ if (this.audioSource && this.audioContext) {
+ this.audioSource.buffer = buffer;
+ this.audioSource.connect(this.audioContext.destination);
+ this.audioSource.start(0);
+ this.isPlaying = true;
+
+ // 播放结束时清理资源
+ this.audioSource.onended = () => {
+ this.stop();
+ onEnded?.();
+ resolve();
+ };
+ }
+ });
+ });
+ }
+
+ public stop(): void {
+ if (this.audioSource && this.isPlaying) {
+ try {
+ this.audioSource.stop();
+ } catch (e) {
+ // 忽略已经停止的错误
+ }
+ }
+
+ if (this.audioContext) {
+ this.audioContext.close();
+ }
+
+ this.audioSource = undefined;
+ this.audioContext = undefined;
+ this.isPlaying = false;
+ }
+
+ public isCurrentlyPlaying(): boolean {
+ return this.isPlaying;
+ }
+}
diff --git a/src/libs/audioPlayer/audioPlayer.ts b/src/libs/audioPlayer/audioPlayer.ts
deleted file mode 100644
index 73a7b029..00000000
--- a/src/libs/audioPlayer/audioPlayer.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-export class AudioPlayer {
- public readonly audio: AudioContext;
- public bufferSource: AudioBufferSourceNode | undefined;
-
- public constructor(audio: AudioContext) {
- this.audio = audio;
- this.bufferSource = undefined;
- }
-
- public async playFromArrayBuffer(buffer: ArrayBuffer, onEnded?: () => void) {
- this.stopPlay();
- const audioBuffer = await this.audio.decodeAudioData(buffer);
-
- this.bufferSource = this.audio.createBufferSource();
- this.bufferSource.buffer = audioBuffer;
-
- this.bufferSource.connect(this.audio.destination);
- this.bufferSource.start();
- if (onEnded) {
- this.bufferSource.addEventListener('ended', onEnded);
- }
- }
-
- public stopPlay() {
- if (this.bufferSource) {
- this.bufferSource.stop();
- this.bufferSource = undefined;
- }
- }
-
- public async playFromURL(url: string, onEnded?: () => void) {
- const res = await fetch(url);
- const buffer = await res.arrayBuffer();
- this.playFromArrayBuffer(buffer, onEnded);
- }
-}
diff --git a/src/libs/messages/speakCharacter.ts b/src/libs/messages/speakCharacter.ts
index 2270ff7b..b01b9a25 100644
--- a/src/libs/messages/speakCharacter.ts
+++ b/src/libs/messages/speakCharacter.ts
@@ -1,22 +1,20 @@
import { message } from 'antd';
+import { AudioPlayer } from '@/libs/audio/AudioPlayer';
import { Viewer } from '@/libs/vrmViewer/viewer';
import { speechApi } from '@/services/tts';
import { Screenplay } from '@/types/touch';
import { getPreloadedVoice } from '@/utils/voice';
import { wait } from '@/utils/wait';
+import { SpeakAudioOptions } from './type';
+
const createSpeakCharacter = () => {
let lastTime = 0;
let prevFetchPromise: Promise = Promise.resolve();
let prevSpeakPromise: Promise = Promise.resolve();
- return (
- screenplay: Screenplay,
- viewer: Viewer,
- onStart?: () => void,
- onComplete?: () => void,
- ) => {
+ return (screenplay: Screenplay, viewer: Viewer, options?: SpeakAudioOptions) => {
const fetchPromise = prevFetchPromise.then(async () => {
const now = Date.now();
if (now - lastTime < 1000) {
@@ -26,22 +24,23 @@ const createSpeakCharacter = () => {
const buffer =
(await getPreloadedVoice(screenplay.tts)) ||
(await speechApi(screenplay.tts).catch((err) => {
- message.error((err as Error).message);
+ options?.onError?.(err as Error);
}));
lastTime = Date.now();
return buffer;
});
prevFetchPromise = fetchPromise;
- prevSpeakPromise = Promise.all([fetchPromise, prevSpeakPromise]).then(([audioBuffer]) => {
- onStart?.();
+ prevSpeakPromise = Promise.all([fetchPromise, prevSpeakPromise]).then(async ([audioBuffer]) => {
if (!audioBuffer) {
+ options?.onError?.(new Error('No audio buffer'));
return;
}
- return viewer.model?.speak(audioBuffer, screenplay);
+ options?.onStart?.();
+ await viewer.model?.speak(audioBuffer, screenplay);
});
prevSpeakPromise.then(() => {
- onComplete?.();
+ options?.onComplete?.();
});
};
};
diff --git a/src/libs/messages/speakChatItem.ts b/src/libs/messages/speakChatItem.ts
new file mode 100644
index 00000000..35448696
--- /dev/null
+++ b/src/libs/messages/speakChatItem.ts
@@ -0,0 +1,46 @@
+import { AudioPlayer } from '@/libs/audio/AudioPlayer';
+import { speechApi } from '@/services/tts';
+import { TTS } from '@/types/tts';
+import { wait } from '@/utils/wait';
+
+import { SpeakAudioOptions } from './type';
+
+const audioPlayer = AudioPlayer.getInstance();
+
+const createSpeakChatItem = () => {
+ let lastTime = 0;
+ let prevFetchPromise: Promise = Promise.resolve();
+ let prevSpeakPromise: Promise = Promise.resolve();
+
+ return (tts: TTS, options?: SpeakAudioOptions) => {
+ const fetchPromise = prevFetchPromise.then(async () => {
+ const now = Date.now();
+ if (now - lastTime < 1000) {
+ await wait(1000 - (now - lastTime));
+ }
+
+ const buffer = await speechApi(tts).catch((err) => {
+ options?.onError?.(err as Error);
+ });
+ lastTime = Date.now();
+ return buffer;
+ });
+
+ prevFetchPromise = fetchPromise;
+ prevSpeakPromise = Promise.all([fetchPromise, prevSpeakPromise]).then(async ([audioBuffer]) => {
+ if (!audioBuffer) {
+ options?.onError?.(new Error('No audio buffer'));
+ return;
+ }
+ options?.onStart?.();
+ await audioPlayer.play(audioBuffer, () => {
+ options?.onComplete?.();
+ });
+ });
+ prevSpeakPromise.then(() => {
+ options?.onComplete?.();
+ });
+ };
+};
+
+export const speakChatItem = createSpeakChatItem();
diff --git a/src/libs/messages/type.ts b/src/libs/messages/type.ts
new file mode 100644
index 00000000..67613c9b
--- /dev/null
+++ b/src/libs/messages/type.ts
@@ -0,0 +1,5 @@
+export interface SpeakAudioOptions {
+ onError?: (err: Error) => void;
+ onStart?: () => void;
+ onComplete?: () => void;
+}
diff --git a/src/libs/vrmViewer/viewer.ts b/src/libs/vrmViewer/viewer.ts
index 62e99abd..19412b42 100644
--- a/src/libs/vrmViewer/viewer.ts
+++ b/src/libs/vrmViewer/viewer.ts
@@ -124,8 +124,6 @@ export class Viewer {
if (cameraUrl) this.playCameraAnimation();
// 将 canvas 全屏加载
this.requestFullScreen();
- // 如果没有加载舞台,那么开启网格
- if (!this._currentStage) this.enableGrid();
}
public resetToIdle() {
@@ -199,6 +197,9 @@ export class Viewer {
if (this._canvas) {
this._canvas.addEventListener('click', this._boundHandleClick, false);
}
+
+ // 如果没有加载舞台,那么开启网格
+ if (!this._currentStage) this.enableGrid();
}
public unloadVRM(): void {
diff --git a/src/locales/default/chat.ts b/src/locales/default/chat.ts
index f0cc676a..bbb0ac65 100644
--- a/src/locales/default/chat.ts
+++ b/src/locales/default/chat.ts
@@ -12,6 +12,7 @@ export default {
delAndRegenerate: '删除并重新生成',
copySuccess: '复制成功',
edit: '编辑',
+ tts: '语音',
del: '删除',
save: '保存',
share: '分享',
@@ -49,6 +50,9 @@ export default {
combine: '语音合成',
},
interactive: '可交互',
+ voiceOn: '开启语音',
+ camera: '视频通话',
+ callOff: '挂断',
sessionList: '会话列表',
selectModel: '请选择模型',
share: {
@@ -99,6 +103,7 @@ export default {
delSession: '删除会话',
delSessionAlert: '确认删除对话吗?删除后无法恢复, 请谨慎操作!',
chat: '聊天',
+ noSession: '暂无会话, 可以通过 + 创建自定义角色, 也可通过发现页添加角色',
sessionCreate: '创建聊天',
ModelSelect: {
featureTag: {
diff --git a/src/locales/default/dance.ts b/src/locales/default/dance.ts
index 30523f9b..d8f2f95d 100644
--- a/src/locales/default/dance.ts
+++ b/src/locales/default/dance.ts
@@ -5,6 +5,7 @@ export default {
play: '播放',
pause: '暂停',
},
+ menu: '菜单',
play: '播放',
addPlay: '添加到列表',
addPlaySuccess: '已添加到播放列表',
diff --git a/src/services/chat.ts b/src/services/chat.ts
index b87ea01a..4ab8215f 100644
--- a/src/services/chat.ts
+++ b/src/services/chat.ts
@@ -1,7 +1,10 @@
import { DEFAULT_MODEL_PROVIDER_LIST } from '@/config/modelProviders';
import { DEFAULT_CHAT_MODEL, DEFAULT_CHAT_PROVIDER } from '@/constants/agent';
import { AgentRuntime, ChatCompletionErrorPayload, ModelProvider } from '@/libs/agent-runtime';
+import { AudioPlayer } from '@/libs/audio/AudioPlayer';
import { speakCharacter } from '@/libs/messages/speakCharacter';
+import { speakChatItem } from '@/libs/messages/speakChatItem';
+import { SpeakAudioOptions } from '@/libs/messages/type';
import { useGlobalStore } from '@/store/global';
import { sessionSelectors, useSessionStore } from '@/store/session';
import { useSettingStore } from '@/store/setting';
@@ -237,27 +240,35 @@ export const chatCompletion = async (params: ChatCompletionPayload, options?: Fe
});
};
-export const handleSpeakAi = async (message: string) => {
+export const handleSpeakAi = async (message: string, options?: SpeakAudioOptions) => {
const viewer = useGlobalStore.getState().viewer;
+ const chatMode = useSessionStore.getState().chatMode;
+
const currentAgent = sessionSelectors.currentAgent(useSessionStore.getState());
+ const tts = { ...currentAgent?.tts, message };
- speakCharacter(
- {
- expression: 'aa',
- tts: {
- ...currentAgent?.tts,
- message: message,
+ if (chatMode === 'camera') {
+ await speakCharacter(
+ {
+ expression: 'aa',
+ tts,
},
- },
- viewer,
- );
+ viewer,
+ options,
+ );
+ } else {
+ await speakChatItem(tts, options);
+ }
};
-export const toggleVoice = async () => {
- const { toggleVoice, voiceOn } = useSessionStore.getState();
- if (voiceOn) {
- const viewer = useGlobalStore.getState().viewer;
+export const handleStopSpeak = async () => {
+ const viewer = useGlobalStore.getState().viewer;
+ const chatMode = useSessionStore.getState().chatMode;
+
+ if (chatMode === 'camera') {
viewer.model?.stopSpeak();
+ } else {
+ const audioPlayer = AudioPlayer.getInstance();
+ audioPlayer.stop();
}
- toggleVoice();
};
diff --git a/src/store/session/index.ts b/src/store/session/index.ts
index 6e54feaf..df1e7e1f 100644
--- a/src/store/session/index.ts
+++ b/src/store/session/index.ts
@@ -8,11 +8,11 @@ import { StateCreator } from 'zustand/vanilla';
import { DEFAULT_LLM_CONFIG, LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent';
import { DEFAULT_USER_AVATAR_URL, LOADING_FLAG } from '@/constants/common';
-import { chatCompletion, handleSpeakAi } from '@/services/chat';
+import { chatCompletion, handleSpeakAi, handleStopSpeak } from '@/services/chat';
import { shareService } from '@/services/share';
import { Agent } from '@/types/agent';
import { ChatMessage } from '@/types/chat';
-import { Session } from '@/types/session';
+import { ChatMode, Session } from '@/types/session';
import { ShareGPTConversation } from '@/types/share';
import { vidolStorage } from '@/utils/storage';
@@ -23,11 +23,6 @@ import { sessionSelectors } from './selectors';
export const SESSION_STORAGE_KEY = 'vidol-chat-session-storage';
-export enum ViewerModeEnum {
- Img = 'Img',
- Normal = 'Normal',
-}
-
interface ShareMessage {
from: 'human' | 'gpt';
value: string;
@@ -50,6 +45,11 @@ export interface SessionStore {
* 聊天加载中的消息 ID
*/
chatLoadingId: string | undefined;
+ /**
+ * 当前模式
+ */
+ chatMode: ChatMode;
+
/**
* 清空历史消息
*/
@@ -111,39 +111,56 @@ export interface SessionStore {
* @returns
*/
sendMessage: (message: string) => void;
+
/**
* 会话列表
*/
sessionList: Session[];
+
+ /**
+ * 设置当前聊天模式
+ */
+ setChatMode: (chatMode: ChatMode) => void;
+
/**
* 设置消息输入
* @param messageInput
*/
setMessageInput: (messageInput: string) => void;
/**
- * 触发 3D 渲染开关
+ * 触发语音开关
*/
- setViewerMode: (mode: boolean) => void;
+ setVoiceOn: (voiceOn: boolean) => void;
shareLoading: boolean;
shareToShareGPT: (props: { withSystemRole?: boolean }) => Promise;
/**
* 停止生成消息
*/
stopGenerateMessage: () => void;
+ /**
+ * 停止语音消息
+ */
+ stopTtsMessage: () => void;
/**
* 切换会话
* @param agent
* @returns
*/
switchSession: (agentId: string) => void;
+
/**
* 触摸响应开关
*/
toggleInteractive: () => void;
/**
- * 触发语音开关
+ * 语音加载中的消息 ID
*/
- toggleVoice: () => void;
+ ttsLoadingId: string | undefined;
+ /**
+ * 语音消息
+ */
+ ttsMessage: (id: string, content: string) => void;
+
/**
* 更新消息
* @returns
@@ -154,10 +171,6 @@ export interface SessionStore {
* @param messages
*/
updateSessionMessages: (messages: ChatMessage[]) => void;
- /**
- * 角色渲染模式
- */
- viewerMode: boolean;
/**
* 语音开关
*/
@@ -274,9 +287,10 @@ export const createSessionStore: StateCreator {
switch (chunk.type) {
case 'text': {
- const { voiceOn } = get();
+ const { voiceOn, chatMode } = get();
- if (voiceOn) {
+ // 只有视频模式下才需要连续语音合成
+ if (voiceOn && chatMode === 'camera') {
// 语音合成
receivedMessage += chunk.text;
// 文本切割
@@ -329,6 +343,27 @@ export const createSessionStore: StateCreator {
+ set({ chatMode });
+ },
+ /**
+ * 语音消息
+ */
+ ttsMessage: async (id: string, content: string) => {
+ set({ ttsLoadingId: id });
+ await handleSpeakAi(content, {
+ onComplete: () => {
+ set({ ttsLoadingId: undefined });
+ },
+ onError: () => {
+ set({ ttsLoadingId: undefined });
+ },
+ });
+ },
+ stopTtsMessage: () => {
+ set({ ttsLoadingId: undefined });
+ handleStopSpeak();
+ },
shareToShareGPT: async ({ withSystemRole }) => {
const messages = sessionSelectors.currentChats(get());
const agent = sessionSelectors.currentAgent(get());
@@ -477,9 +512,6 @@ export const createSessionStore: StateCreator {
set({ messageInput }, false, 'session/setMessageInput');
},
- setViewerMode: (mode) => {
- set({ viewerMode: mode });
- },
switchSession: (agentId) => {
const { sessionList } = get();
if (agentId === LOBE_VIDOL_DEFAULT_AGENT_ID) {
@@ -498,9 +530,8 @@ export const createSessionStore: StateCreator {
- const { voiceOn } = get();
- set({ voiceOn: !voiceOn });
+ setVoiceOn: (voiceOn) => {
+ set({ voiceOn });
},
toggleInteractive: () => {
const { interactive: touchOn } = get();
diff --git a/src/store/session/initialState.ts b/src/store/session/initialState.ts
index cddc12ba..a28326c1 100644
--- a/src/store/session/initialState.ts
+++ b/src/store/session/initialState.ts
@@ -1,5 +1,5 @@
import { LOBE_VIDOL_DEFAULT_AGENT_ID } from '@/constants/agent';
-import { Session } from '@/types/session';
+import { ChatMode, Session } from '@/types/session';
const defaultSession: Session = {
agentId: LOBE_VIDOL_DEFAULT_AGENT_ID,
@@ -7,14 +7,15 @@ const defaultSession: Session = {
};
const initialState = {
+ chatMode: 'chat' as ChatMode,
activeId: defaultSession.agentId,
chatLoadingId: undefined,
+ ttsLoadingId: undefined,
shareLoading: false,
messageInput: '',
sessionList: [],
defaultSession: defaultSession,
- viewerMode: false,
- voiceOn: true,
+ voiceOn: false,
interactive: true,
};
diff --git a/src/store/session/selectors.ts b/src/store/session/selectors.ts
index 962e2b42..da9f4711 100644
--- a/src/store/session/selectors.ts
+++ b/src/store/session/selectors.ts
@@ -132,6 +132,11 @@ const currentChatMessage = (s: SessionStore): ChatMessage | undefined => {
return currentChats(s).find((item) => item.id === chatLoadingId);
};
+const ttsLoading = (s: SessionStore) => {
+ const { ttsLoadingId } = s;
+ return (id: string) => ttsLoadingId === id;
+};
+
const getLastMessageByAgentId = (s: SessionStore) => {
return (id: string): ChatMessage | undefined => {
const { sessionList, defaultSession } = s;
@@ -157,4 +162,5 @@ export const sessionSelectors = {
currentSession,
currentSystemRole,
sessionListIds,
+ ttsLoading,
};
diff --git a/src/store/setting/index.ts b/src/store/setting/index.ts
index 140df4e8..46d91c22 100644
--- a/src/store/setting/index.ts
+++ b/src/store/setting/index.ts
@@ -143,6 +143,8 @@ const persistOptions: PersistOptions = {
baseURL: state.config.languageModel['openAI']!.endpoint,
},
};
+ //@ts-ignore
+ delete state.config.languageModel['openAI'];
}
return state;
}
diff --git a/src/types/session.ts b/src/types/session.ts
index 43a1f5a6..84d01000 100644
--- a/src/types/session.ts
+++ b/src/types/session.ts
@@ -1,5 +1,7 @@
import { ChatMessage } from './chat';
+export type ChatMode = 'camera' | 'chat' | 'call';
+
export interface Session {
/**
* 会话对应的 Agent ID