♻️ refactor: refactor the input area to suit the files upload feature (#442)

* ♻️ refactor: refactor the input area from ui

* ♻️ refactor: refactor ActionBar to a configurable stage
This commit is contained in:
Arvin Xu
2023-11-11 09:40:10 +08:00
committed by GitHub
parent 240c0c3019
commit 57a61fde45
22 changed files with 559 additions and 259 deletions
@@ -1,18 +1,37 @@
import { Icon } from '@lobehub/ui';
import { useTheme } from 'antd-style';
import { ArrowBigUp, CornerDownLeft } from 'lucide-react';
import { Button } from 'antd';
import { createStyles } from 'antd-style';
import { ArrowBigUp, CornerDownLeft, Loader2 } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import SaveTopic from '../../../features/ChatInputContent/Topic';
import SaveTopic from '@/app/chat/features/ChatInput/Topic';
import { useSessionStore } from '@/store/session';
import { useSendMessage } from './useSend';
const useStyles = createStyles(({ css }) => ({
footerBar: css`
display: flex;
flex: none;
gap: 8px;
align-items: center;
justify-content: flex-end;
padding: 0 24px;
`,
}));
const Footer = memo(() => {
const theme = useTheme();
const { t } = useTranslation('chat');
const { styles, theme } = useStyles();
const [loading, onStop] = useSessionStore((s) => [!!s.chatLoadingId, s.stopGenerateMessage]);
const onSend = useSendMessage();
return (
<>
<div className={styles.footerBar}>
<Flexbox
gap={4}
horizontal
@@ -28,7 +47,16 @@ const Footer = memo(() => {
<span>{t('warp')}</span>
</Flexbox>
<SaveTopic />
</>
{loading ? (
<Button icon={loading && <Icon icon={Loader2} spin />} onClick={onStop}>
{t('stop')}
</Button>
) : (
<Button onClick={() => onSend()} type={'primary'}>
{t('send')}
</Button>
)}
</div>
);
});
@@ -0,0 +1,69 @@
import { TextArea } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { memo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useSessionStore } from '@/store/session';
import { useSendMessage } from './useSend';
const useStyles = createStyles(({ css }) => {
return {
textarea: css`
height: 100% !important;
padding: 0 24px;
line-height: 1.5;
`,
textareaContainer: css`
position: relative;
flex: 1;
`,
};
});
const InputArea = memo(() => {
const { t } = useTranslation('common');
const isChineseInput = useRef(false);
const { cx, styles } = useStyles();
const [loading, message, updateInputMessage] = useSessionStore((s) => [
!!s.chatLoadingId,
s.inputMessage,
s.updateInputMessage,
]);
const handleSend = useSendMessage();
return (
<div className={cx(styles.textareaContainer)}>
<TextArea
className={styles.textarea}
onBlur={(e) => {
updateInputMessage(e.target.value);
}}
onChange={(e) => {
updateInputMessage(e.target.value);
}}
onCompositionEnd={() => {
isChineseInput.current = false;
}}
onCompositionStart={() => {
isChineseInput.current = true;
}}
onPressEnter={(e) => {
if (!loading && !e.shiftKey && !isChineseInput.current) {
e.preventDefault();
handleSend();
}
}}
placeholder={t('sendPlaceholder', { ns: 'chat' })}
resize={false}
type="pure"
value={message}
/>
</div>
);
});
export default InputArea;
@@ -1,14 +1,34 @@
import { DraggablePanel } from '@lobehub/ui';
import { ActionIcon, DraggablePanel } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { Maximize2, Minimize2 } from 'lucide-react';
import { memo, useState } from 'react';
import Footer from '@/app/chat/(desktop)/features/ChatInput/Footer';
import ActionBar from '@/app/chat/features/ChatInput/ActionBar';
import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
import { useGlobalStore } from '@/store/global';
import ChatInputContent from '../../../features/ChatInputContent';
import Footer from './Footer';
import InputArea from './InputArea';
const useStyles = createStyles(({ css }) => {
return {
container: css`
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
height: 100%;
padding: 12px 0 16px;
`,
};
});
const ChatInputDesktopLayout = memo(() => {
const { styles } = useStyles();
const [expand, setExpand] = useState<boolean>(false);
const [inputHeight, updatePreference] = useGlobalStore((s) => [
s.preference.inputHeight,
s.updatePreference,
@@ -30,7 +50,20 @@ const ChatInputDesktopLayout = memo(() => {
size={{ height: inputHeight, width: '100%' }}
style={{ zIndex: 10 }}
>
<ChatInputContent expand={expand} footer={<Footer />} onExpandChange={setExpand} />
<section className={styles.container} style={{ minHeight: CHAT_TEXTAREA_HEIGHT }}>
<ActionBar
rightAreaEndRender={
<ActionIcon
icon={expand ? Minimize2 : Maximize2}
onClick={() => {
setExpand(!expand);
}}
/>
}
/>
<InputArea />
<Footer />
</section>
</DraggablePanel>
);
});
@@ -0,0 +1,17 @@
import { useCallback } from 'react';
import { useSessionStore } from '@/store/session';
export const useSendMessage = () => {
const [sendMessage, updateInputMessage] = useSessionStore((s) => [
s.sendMessage,
s.updateInputMessage,
]);
return useCallback(() => {
const store = useSessionStore.getState();
if (!!store.chatLoadingId) return;
sendMessage(store.inputMessage);
updateInputMessage('');
}, []);
};
@@ -0,0 +1,79 @@
import { Icon, Input } from '@lobehub/ui';
import { Button, type InputRef } from 'antd';
import { Loader2, SendHorizonal } from 'lucide-react';
import { forwardRef, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import useControlledState from 'use-merge-value';
import ActionBar from '@/app/chat/features/ChatInput/ActionBar';
import SaveTopic from '@/app/chat/features/ChatInput/Topic';
import { useStyles } from './style.mobile';
export type ChatInputAreaMobile = {
loading?: boolean;
onChange?: (value: string) => void;
onSend?: (value: string) => void;
onStop?: () => void;
value?: string;
};
const ChatInputArea = forwardRef<InputRef, ChatInputAreaMobile>(
({ onSend, loading, onChange, onStop, value }) => {
const { t } = useTranslation('chat');
const [currentValue, setCurrentValue] = useControlledState<string>('', {
onChange: onChange,
value,
});
const { cx, styles } = useStyles();
const isChineseInput = useRef(false);
const handleSend = useCallback(() => {
if (loading) return;
if (onSend) onSend(currentValue);
setCurrentValue('');
}, [currentValue]);
return (
<Flexbox className={cx(styles.container)} gap={12}>
<ActionBar rightAreaStartRender={<SaveTopic />} />
<Flexbox className={styles.inner} gap={8} horizontal>
<Input
className={cx(styles.input)}
onBlur={(e) => {
setCurrentValue(e.target.value);
}}
onChange={(e) => {
setCurrentValue(e.target.value);
}}
onCompositionEnd={() => {
isChineseInput.current = false;
}}
onCompositionStart={() => {
isChineseInput.current = true;
}}
onPressEnter={(e) => {
if (!loading && !e.shiftKey && !isChineseInput.current) {
e.preventDefault();
handleSend();
}
}}
placeholder={t('sendPlaceholder')}
type={'block'}
value={currentValue}
/>
<div>
{loading ? (
<Button icon={loading && <Icon icon={Loader2} spin />} onClick={onStop} />
) : (
<Button icon={<Icon icon={SendHorizonal} />} onClick={handleSend} type={'primary'} />
)}
</div>
</Flexbox>
</Flexbox>
);
},
);
export default ChatInputArea;
@@ -1,10 +1,11 @@
import { createStyles } from 'antd-style';
import { memo } from 'react';
import { memo, useState } from 'react';
import SafeSpacing from '@/components/SafeSpacing';
import { CHAT_TEXTAREA_HEIGHT_MOBILE } from '@/const/layoutTokens';
import { useSessionStore } from '@/store/session';
import ChatInputContent from '../../features/ChatInputContent';
import ChatInputArea from './Mobile';
const useStyles = createStyles(
({ css, token }) => css`
@@ -21,11 +22,26 @@ const useStyles = createStyles(
const ChatInputMobileLayout = memo(() => {
const { styles } = useStyles();
const [message, setMessage] = useState('');
const [isLoading, sendMessage, stopGenerateMessage] = useSessionStore((s) => [
!!s.chatLoadingId,
s.sendMessage,
s.stopGenerateMessage,
]);
return (
<>
<SafeSpacing height={CHAT_TEXTAREA_HEIGHT_MOBILE} mobile position={'bottom'} />
<div className={styles}>
<ChatInputContent mobile />
<ChatInputArea
loading={isLoading}
onChange={setMessage}
onSend={sendMessage}
onStop={stopGenerateMessage}
value={message}
/>
</div>
</>
);
@@ -0,0 +1,19 @@
import { createStyles } from 'antd-style';
import { rgba } from 'polished';
export const useStyles = createStyles(({ css, token }) => {
return {
container: css`
padding: 12px 0;
background: ${token.colorBgLayout};
border-top: 1px solid ${rgba(token.colorBorder, 0.25)};
`,
inner: css`
padding: 0 16px;
`,
input: css`
background: ${token.colorFillSecondary} !important;
border: none !important;
`,
};
});
@@ -0,0 +1,39 @@
import { ActionIcon } from '@lobehub/ui';
import { Popconfirm } from 'antd';
import { Eraser } from 'lucide-react';
import { memo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import HotKeys from '@/components/HotKeys';
import { CLEAN_MESSAGE_KEY, PREFIX_KEY } from '@/const/hotkeys';
import { useSessionStore } from '@/store/session';
const Clear = memo(() => {
const { t } = useTranslation('setting');
const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
const hotkeys = [PREFIX_KEY, CLEAN_MESSAGE_KEY].join('+');
useHotkeys(hotkeys, clearMessage, {
preventDefault: true,
});
return (
<Popconfirm
cancelText={t('cancel', { ns: 'common' })}
okButtonProps={{ danger: true }}
okText={t('ok', { ns: 'common' })}
onConfirm={() => clearMessage()}
placement={'topRight'}
title={t('confirmClearCurrentMessages', { ns: 'chat' })}
>
<ActionIcon
icon={Eraser}
placement={'bottom'}
title={(<HotKeys desc={t('clearCurrentMessages', { ns: 'chat' })} keys={hotkeys} />) as any}
/>
</Popconfirm>
);
});
export default Clear;
@@ -0,0 +1,64 @@
import { ActionIcon, SliderWithInput } from '@lobehub/ui';
import { Popover, Switch } from 'antd';
import { Timer, TimerOff } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { useSessionStore } from '@/store/session';
import { agentSelectors } from '@/store/session/selectors';
const History = memo(() => {
const { t } = useTranslation('setting');
const [historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
const config = agentSelectors.currentAgentConfig(s);
return [config.historyCount, !config.enableHistoryCount, s.updateAgentConfig];
});
return (
<Popover
arrow={false}
content={
<Flexbox align={'center'} gap={16} horizontal>
<SliderWithInput
disabled={unlimited}
max={30}
min={1}
onChange={(v) => {
updateAgentConfig({ historyCount: v });
}}
step={1}
style={{ width: 160 }}
value={historyCount}
/>
<Flexbox align={'center'} gap={4} horizontal>
<Switch
checked={unlimited}
onChange={(checked) => {
updateAgentConfig({ enableHistoryCount: !checked });
}}
size={'small'}
/>
{t('settingChat.enableHistoryCount.alias')}
</Flexbox>
</Flexbox>
}
placement={'top'}
trigger={'click'}
>
<ActionIcon
icon={unlimited ? TimerOff : Timer}
placement={'bottom'}
title={t(
unlimited
? 'settingChat.enableHistoryCount.unlimited'
: 'settingChat.enableHistoryCount.limited',
{ number: historyCount || 0 },
)}
/>
</Popover>
);
});
export default History;
@@ -0,0 +1,42 @@
import { ActionIcon } from '@lobehub/ui';
import { Dropdown } from 'antd';
import { BrainCog } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { settingsSelectors, useGlobalStore } from '@/store/global';
import { useSessionStore } from '@/store/session';
import { agentSelectors } from '@/store/session/selectors';
import { LanguageModel } from '@/types/llm';
const ModelSwitch = memo(() => {
const { t } = useTranslation('setting');
const [model, updateAgentConfig] = useSessionStore((s) => {
const config = agentSelectors.currentAgentConfig(s);
return [config.model, s.updateAgentConfig];
});
const modelList = useGlobalStore(settingsSelectors.modelList);
return (
<Dropdown
menu={{
activeKey: model,
items: modelList.map((i) => ({ key: i, label: i })),
onClick: (e) => {
updateAgentConfig({ model: e.key as LanguageModel });
},
style: {
maxHeight: 400,
overflow: 'scroll',
},
}}
trigger={['click']}
>
<ActionIcon icon={BrainCog} placement={'bottom'} title={t('settingModel.model.title')} />
</Dropdown>
);
});
export default ModelSwitch;
@@ -0,0 +1,47 @@
import { ActionIcon, SliderWithInput } from '@lobehub/ui';
import { Popover } from 'antd';
import { Thermometer } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSessionStore } from '@/store/session';
import { agentSelectors } from '@/store/session/selectors';
const Temperature = memo(() => {
const { t } = useTranslation('setting');
const [temperature, updateAgentConfig] = useSessionStore((s) => {
const config = agentSelectors.currentAgentConfig(s);
return [config.params.temperature, s.updateAgentConfig];
});
return (
<Popover
arrow={false}
content={
<SliderWithInput
controls={false}
max={1}
min={0}
onChange={(v) => {
updateAgentConfig({ params: { temperature: v } });
}}
size={'small'}
step={0.1}
style={{ width: 160 }}
value={temperature}
/>
}
placement={'top'}
trigger={'click'}
>
<ActionIcon
icon={Thermometer}
placement={'bottom'}
title={t('settingModel.temperature.titleWithValue', { value: temperature })}
/>
</Popover>
);
});
export default Temperature;
@@ -8,16 +8,16 @@ import { useSessionStore } from '@/store/session';
import { agentSelectors, chatSelectors } from '@/store/session/selectors';
import { LanguageModel } from '@/types/llm';
const Token = memo<{ input: string }>(({ input }) => {
const Token = memo(() => {
const { t } = useTranslation('chat');
const inputTokenCount = useTokenCount(input);
const [messageString, systemRole, model] = useSessionStore((s) => [
const [input, messageString, systemRole, model] = useSessionStore((s) => [
s.inputMessage,
chatSelectors.chatsMessageString(s),
agentSelectors.currentAgentSystemRole(s),
agentSelectors.currentAgentModel(s) as LanguageModel,
]);
const inputTokenCount = useTokenCount(input);
const systemRoleToken = useTokenCount(systemRole);
const chatsToken = useTokenCount(messageString);
@@ -0,0 +1,21 @@
import dynamic from 'next/dynamic';
import { Suspense, memo } from 'react';
import { useSessionStore } from '@/store/session';
import { agentSelectors } from '@/store/session/selectors';
const LargeTokenContent = dynamic(() => import('./TokenTag'), { ssr: false });
const Token = memo(() => {
const [showTokenTag] = useSessionStore((s) => [agentSelectors.showTokenTag(s)]);
return (
showTokenTag && (
<Suspense>
<LargeTokenContent />
</Suspense>
)
);
});
export default Token;
@@ -0,0 +1,19 @@
import { FC } from 'react';
import Clear from './Clear';
import History from './History';
import ModelSwitch from './ModelSwitch';
import Temperature from './Temperature';
import Token from './Token';
export const actionMap: Record<string, FC> = {
clear: Clear,
history: History,
model: ModelSwitch,
temperature: Temperature,
token: Token,
};
// we can make these action lists configurable in the future
export const leftActionList = ['model', 'temperature', 'history', 'token'];
export const rightActionList = ['clear'];
@@ -0,0 +1,35 @@
import { ReactNode, memo } from 'react';
import { Flexbox } from 'react-layout-kit';
import { actionMap, leftActionList, rightActionList } from './config';
const RenderActionList = ({ dataSource }: { dataSource: string[] }) => (
<>
{dataSource.map((key) => {
const Render = actionMap[key];
return <Render key={key} />;
})}
</>
);
export interface ActionBarProps {
rightAreaEndRender?: ReactNode;
rightAreaStartRender?: ReactNode;
}
const ActionBar = memo<ActionBarProps>(({ rightAreaStartRender, rightAreaEndRender }) => {
return (
<Flexbox align={'center'} flex={'none'} horizontal justify={'space-between'} padding={'0 16px'}>
<Flexbox align={'center'} flex={1} gap={4} horizontal>
<RenderActionList dataSource={leftActionList} />
</Flexbox>
<Flexbox align={'center'} flex={0} gap={4} horizontal justify={'flex-end'}>
{rightAreaStartRender}
<RenderActionList dataSource={rightActionList} />
{rightAreaEndRender}
</Flexbox>
</Flexbox>
);
});
export default ActionBar;
@@ -1,117 +0,0 @@
import { ActionIcon, SliderWithInput } from '@lobehub/ui';
import { Dropdown, Popover, Switch } from 'antd';
import { BrainCog, Thermometer, Timer, TimerOff } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { settingsSelectors, useGlobalStore } from '@/store/global';
import { useSessionStore } from '@/store/session';
import { agentSelectors } from '@/store/session/selectors';
import { LanguageModel } from '@/types/llm';
const ActionLeft = memo(() => {
const { t } = useTranslation('setting');
const [model, temperature, historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
const config = agentSelectors.currentAgentConfig(s);
return [
config.model,
config.params.temperature,
config.historyCount,
!config.enableHistoryCount,
s.updateAgentConfig,
];
});
const modelList = useGlobalStore(settingsSelectors.modelList);
return (
<>
<Dropdown
menu={{
activeKey: model,
items: modelList.map((i) => ({ key: i, label: i })),
onClick: (e) => {
updateAgentConfig({ model: e.key as LanguageModel });
},
style: {
maxHeight: 400,
overflow: 'scroll',
},
}}
trigger={['click']}
>
<ActionIcon icon={BrainCog} placement={'bottom'} title={t('settingModel.model.title')} />
</Dropdown>
<Popover
arrow={false}
content={
<SliderWithInput
controls={false}
max={1}
min={0}
onChange={(v) => {
updateAgentConfig({ params: { temperature: v } });
}}
size={'small'}
step={0.1}
style={{ width: 160 }}
value={temperature}
/>
}
placement={'top'}
trigger={'click'}
>
<ActionIcon
icon={Thermometer}
placement={'bottom'}
title={t('settingModel.temperature.titleWithValue', { value: temperature })}
/>
</Popover>
<Popover
arrow={false}
content={
<Flexbox align={'center'} gap={16} horizontal>
<SliderWithInput
disabled={unlimited}
max={30}
min={1}
onChange={(v) => {
updateAgentConfig({ historyCount: v });
}}
step={1}
style={{ width: 160 }}
value={historyCount}
/>
<Flexbox align={'center'} gap={4} horizontal>
<Switch
checked={unlimited}
onChange={(checked) => {
updateAgentConfig({ enableHistoryCount: !checked });
}}
size={'small'}
/>
{t('settingChat.enableHistoryCount.alias')}
</Flexbox>
</Flexbox>
}
placement={'top'}
trigger={'click'}
>
<ActionIcon
icon={unlimited ? TimerOff : Timer}
placement={'bottom'}
title={t(
unlimited
? 'settingChat.enableHistoryCount.unlimited'
: 'settingChat.enableHistoryCount.limited',
{ number: historyCount || 0 },
)}
/>
</Popover>
</>
);
});
export default ActionLeft;
@@ -1,48 +0,0 @@
import { ActionIcon } from '@lobehub/ui';
import { Popconfirm } from 'antd';
import { useResponsive } from 'antd-style';
import { Eraser } from 'lucide-react';
import { memo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import HotKeys from '@/components/HotKeys';
import { CLEAN_MESSAGE_KEY, PREFIX_KEY } from '@/const/hotkeys';
import { useSessionStore } from '@/store/session';
import SaveTopic from '../Topic';
const ActionsRight = memo(() => {
const { t } = useTranslation('setting');
const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
const { mobile } = useResponsive();
const hotkeys = [PREFIX_KEY, CLEAN_MESSAGE_KEY].join('+');
useHotkeys(hotkeys, clearMessage, {
preventDefault: true,
});
return (
<>
{mobile && <SaveTopic />}
<Popconfirm
cancelText={t('cancel', { ns: 'common' })}
okButtonProps={{ danger: true }}
okText={t('ok', { ns: 'common' })}
onConfirm={() => clearMessage()}
placement={'topRight'}
title={t('confirmClearCurrentMessages', { ns: 'chat' })}
>
<ActionIcon
icon={Eraser}
placement={'bottom'}
title={
(<HotKeys desc={t('clearCurrentMessages', { ns: 'chat' })} keys={hotkeys} />) as any
}
/>
</Popconfirm>
</>
);
});
export default ActionsRight;
@@ -1,71 +0,0 @@
import { ChatInputArea } from '@lobehub/ui';
import { useResponsive } from 'antd-style';
import dynamic from 'next/dynamic';
import { ReactNode, Suspense, memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CHAT_TEXTAREA_HEIGHT } from '@/const/layoutTokens';
import { useSessionStore } from '@/store/session';
import { agentSelectors } from '@/store/session/selectors';
import ActionLeft from './ActionBar/ActionLeft';
import ActionsRight from './ActionBar/ActionRight';
const Token = dynamic(() => import('./ActionBar/Token'), { ssr: false });
interface ChatContentProps {
expand?: boolean;
footer?: ReactNode;
mobile?: boolean;
onExpandChange?: (expand: boolean) => void;
}
const ChatInputContent = memo<ChatContentProps>(
({ expand, onExpandChange, mobile: defaultMobile, footer }) => {
const { t } = useTranslation('common');
const [message, setMessage] = useState('');
const { mobile: runtimeMobile } = useResponsive();
const mobile = runtimeMobile || defaultMobile;
const [isLoading, sendMessage, stopGenerateMessage, showTokenTag] = useSessionStore((s) => [
!!s.chatLoadingId,
s.sendMessage,
s.stopGenerateMessage,
agentSelectors.showTokenTag(s),
]);
return (
<ChatInputArea
actions={
<>
<ActionLeft />
{showTokenTag && (
<Suspense>
<Token input={message} />
</Suspense>
)}
</>
}
actionsRight={<ActionsRight />}
expand={expand}
footer={footer}
loading={isLoading}
minHeight={mobile ? 0 : CHAT_TEXTAREA_HEIGHT}
onExpandChange={onExpandChange}
onInputChange={setMessage}
onSend={sendMessage}
onStop={stopGenerateMessage}
placeholder={t('sendPlaceholder', { ns: 'chat' })}
text={{
send: t('send'),
stop: t('stop'),
}}
value={message}
/>
);
},
);
export default ChatInputContent;
@@ -68,12 +68,13 @@ export interface ChatMessageAction {
*/
sendMessage: (text: string) => Promise<void>;
stopGenerateMessage: () => void;
toggleChatLoading: (
loading: boolean,
id?: string,
action?: string,
) => AbortController | undefined;
updateInputMessage: (message: string) => void;
}
export const chatMessage: StateCreator<
@@ -94,7 +95,6 @@ export const chatMessage: StateCreator<
// after remove topic , go back to default topic
toggleTopic();
},
coreProcessMessage: async (messages, userMessageId) => {
const { dispatchMessage, fetchAIChatMessage, triggerFunctionCall, activeTopicId } = get();
@@ -163,6 +163,7 @@ export const chatMessage: StateCreator<
deleteMessage: (id) => {
get().dispatchMessage({ id, type: 'deleteMessage' });
},
dispatchMessage: (payload) => {
const { activeId } = get();
const session = sessionSelectors.currentSession(get());
@@ -172,7 +173,6 @@ export const chatMessage: StateCreator<
get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
},
fetchAIChatMessage: async (messages, assistantId) => {
const { dispatchMessage, toggleChatLoading } = get();
@@ -341,6 +341,7 @@ export const chatMessage: StateCreator<
toggleChatLoading(false);
},
toggleChatLoading: (loading, id, action) => {
if (loading) {
const abortController = new AbortController();
@@ -350,4 +351,7 @@ export const chatMessage: StateCreator<
set({ abortController: undefined, chatLoadingId: undefined }, false, action);
}
},
updateInputMessage: (message) => {
set({ inputMessage: message }, false, t('updateInputMessage'));
},
});
@@ -2,9 +2,12 @@ export interface ChatState {
abortController?: AbortController;
activeTopicId?: string;
chatLoadingId?: string;
inputMessage: string;
renameTopicId?: string;
shareLoading?: boolean;
topicLoadingId?: string;
}
export const initialChatState: ChatState = {};
export const initialChatState: ChatState = {
inputMessage: '',
};
@@ -41,6 +41,7 @@ export const currentChats = (s: SessionStore): ChatMessage[] => {
return getChatsById(s.activeId)(s);
};
const initTime = Date.now();
// 针对新助手添加初始化时的自定义消息
export const currentChatsWithGuideMessage = (s: SessionStore): ChatMessage[] => {
const data = currentChats(s);
@@ -67,14 +68,14 @@ export const currentChatsWithGuideMessage = (s: SessionStore): ChatMessage[] =>
const emptyInboxGuideMessage = {
content: isInbox ? inboxMsg : !!meta.description ? agentSystemRoleMsg : agentMsg,
createAt: Date.now(),
createAt: initTime,
extra: {},
id: 'default',
meta: meta || {
avatar: DEFAULT_INBOX_AVATAR,
},
role: 'assistant',
updateAt: Date.now(),
updateAt: initTime,
} as ChatMessage;
return [emptyInboxGuideMessage];