@lobehub/chat 0.29.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js CHANGED
@@ -4,5 +4,8 @@ config.extends.push('plugin:@next/next/recommended');
4
4
 
5
5
  config.rules['unicorn/no-negated-condition'] = 0;
6
6
  config.rules['unicorn/prefer-type-error'] = 0;
7
+ config.rules['unicorn/prefer-logical-operator-over-ternary'] = 0;
8
+ config.rules['unicorn/no-null'] = 0;
9
+ config.rules['unicorn/no-typeof-undefined'] = 0;
7
10
 
8
11
  module.exports = config;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,59 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.30.1](https://github.com/lobehub/lobe-chat/compare/v0.30.0...v0.30.1)
6
+
7
+ <sup>Released on **2023-07-30**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **misc**: 优化搜索引擎插件展示.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: 优化搜索引擎插件展示 ([347e6b0](https://github.com/lobehub/lobe-chat/commit/347e6b0))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 0.30.0](https://github.com/lobehub/lobe-chat/compare/v0.29.0...v0.30.0)
31
+
32
+ <sup>Released on **2023-07-30**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **misc**: 优化保存为话题功能, 实现 Topic 重命名功能, 实现话题删除功能, 支持缓存角色面板的展开折叠状态.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's improved
44
+
45
+ - **misc**: 优化保存为话题功能 ([fdbe084](https://github.com/lobehub/lobe-chat/commit/fdbe084))
46
+ - **misc**: 实现 Topic 重命名功能 ([5ef1685](https://github.com/lobehub/lobe-chat/commit/5ef1685))
47
+ - **misc**: 实现话题删除功能 ([970889d](https://github.com/lobehub/lobe-chat/commit/970889d))
48
+ - **misc**: 支持缓存角色面板的展开折叠状态 ([c241c4b](https://github.com/lobehub/lobe-chat/commit/c241c4b))
49
+
50
+ </details>
51
+
52
+ <div align="right">
53
+
54
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
55
+
56
+ </div>
57
+
5
58
  ## [Version 0.29.0](https://github.com/lobehub/lobe-chat/compare/v0.28.0...v0.29.0)
6
59
 
7
60
  <sup>Released on **2023-07-30**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.29.0",
3
+ "version": "0.30.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -1,3 +1,5 @@
1
+ export const LOADING_FLAT = '...';
2
+
1
3
  // 只要 start with 这个,就可以判断为 function message
2
4
  export const FUNCTION_MESSAGE_FLAG = '{"function';
3
5
 
@@ -52,6 +52,8 @@ export default {
52
52
  temp: '临时',
53
53
  tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
54
54
  topic: {
55
+ confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
56
+ defaultTitle: '默认话题',
55
57
  saveCurrentMessages: '将当前会话保存为话题',
56
58
  searchPlaceholder: '搜索话题...',
57
59
  },
@@ -6,10 +6,10 @@ import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
  import { shallow } from 'zustand/shallow';
8
8
 
9
- import { Topic } from '@/pages/chat/[id]/Config/Topic';
10
9
  import { agentSelectors, useSessionStore } from '@/store/session';
11
10
 
12
11
  import Header from './Header';
12
+ import { Topic } from './Topic';
13
13
 
14
14
  const useStyles = createStyles(({ css, token }) => ({
15
15
  desc: css`
@@ -0,0 +1,29 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Tag, Typography } from 'antd';
3
+ import { useTheme } from 'antd-style';
4
+ import { MessageSquareDashed } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ const { Paragraph } = Typography;
10
+
11
+ const DefaultContent = memo(() => {
12
+ const { t } = useTranslation('common');
13
+
14
+ const theme = useTheme();
15
+
16
+ return (
17
+ <Flexbox align={'center'} gap={8} horizontal>
18
+ <Flexbox align={'center'} height={24} justify={'center'} width={24}>
19
+ <Icon color={theme.colorTextDescription} icon={MessageSquareDashed} />
20
+ </Flexbox>
21
+ <Paragraph ellipsis={{ rows: 1 }} style={{ margin: 0 }}>
22
+ {t('topic.defaultTitle')}
23
+ </Paragraph>
24
+ <Tag>{t('temp')}</Tag>
25
+ </Flexbox>
26
+ );
27
+ });
28
+
29
+ export default DefaultContent;
@@ -0,0 +1,151 @@
1
+ import { ActionIcon, EditableText, Icon } from '@lobehub/ui';
2
+ import { App, Dropdown, type MenuProps, Typography } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { MoreVertical, PencilLine, Star, Trash } from 'lucide-react';
5
+ import { memo, useMemo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { useSessionStore } from '@/store/session';
10
+
11
+ const useStyles = createStyles(({ css }) => ({
12
+ content: css`
13
+ position: relative;
14
+ overflow: hidden;
15
+ flex: 1;
16
+ `,
17
+ title: css`
18
+ flex: 1;
19
+ height: 28px;
20
+ line-height: 28px;
21
+ text-align: start;
22
+ `,
23
+ }));
24
+ const { Paragraph } = Typography;
25
+
26
+ interface TopicContentProps {
27
+ fav?: boolean;
28
+ id: string;
29
+ title: string;
30
+ }
31
+
32
+ const TopicContent = memo<TopicContentProps>(({ id, title, fav }) => {
33
+ const { t } = useTranslation('common');
34
+
35
+ const [editing, dispatchTopic, removeTopic] = useSessionStore(
36
+ (s) => [s.renameTopicId === id, s.dispatchTopic, s.removeTopic],
37
+ shallow,
38
+ );
39
+ const { styles, theme } = useStyles();
40
+
41
+ const toggleEditing = (visible?: boolean) => {
42
+ useSessionStore.setState({ renameTopicId: visible ? id : '' });
43
+ };
44
+
45
+ const { modal } = App.useApp();
46
+
47
+ const items = useMemo<MenuProps['items']>(
48
+ () => [
49
+ {
50
+ icon: <Icon icon={PencilLine} />,
51
+ key: 'rename',
52
+ label: t('rename'),
53
+ onClick: () => {
54
+ toggleEditing(true);
55
+ },
56
+ },
57
+ // {
58
+ // icon: <Icon icon={Share2} />,
59
+ // key: 'share',
60
+ // label: t('share'),
61
+ // },
62
+ {
63
+ danger: true,
64
+ icon: <Icon icon={Trash} />,
65
+ key: 'delete',
66
+ label: t('delete'),
67
+ onClick: () => {
68
+ if (!id) return;
69
+
70
+ modal.confirm({
71
+ centered: true,
72
+ okButtonProps: { danger: true },
73
+ onOk: () => {
74
+ removeTopic(id);
75
+ },
76
+ title: t('topic.confirmRemoveTopic'),
77
+ });
78
+ },
79
+ },
80
+ ],
81
+ [],
82
+ );
83
+
84
+ return (
85
+ <>
86
+ <ActionIcon
87
+ color={fav ? theme.colorWarning : undefined}
88
+ fill={fav ? theme.colorWarning : 'transparent'}
89
+ icon={Star}
90
+ onClick={() => {
91
+ if (!id) return;
92
+ dispatchTopic({ favorite: !fav, id, type: 'favorChatTopic' });
93
+ }}
94
+ size={'small'}
95
+ />
96
+
97
+ {!editing ? (
98
+ <Paragraph
99
+ className={styles.title}
100
+ ellipsis={{ rows: 1, tooltip: title }}
101
+ style={{ margin: 0 }}
102
+ >
103
+ {title}
104
+ </Paragraph>
105
+ ) : (
106
+ <EditableText
107
+ editing={editing}
108
+ onChangeEnd={(v) => {
109
+ if (title !== v) {
110
+ dispatchTopic({ id, key: 'title', type: 'updateChatTopic', value: v });
111
+ }
112
+ toggleEditing(false);
113
+ }}
114
+ onEditingChange={toggleEditing}
115
+ showEditIcon={false}
116
+ size={'small'}
117
+ style={{
118
+ background: theme.colorBgContainer,
119
+ borderRadius: 6,
120
+ height: 28,
121
+ padding: '0 2px',
122
+ }}
123
+ value={title}
124
+ />
125
+ )}
126
+ {!editing && (
127
+ <Dropdown
128
+ arrow={false}
129
+ menu={{
130
+ items: items,
131
+ onClick: ({ domEvent }) => {
132
+ domEvent.stopPropagation();
133
+ },
134
+ }}
135
+ trigger={['click']}
136
+ >
137
+ <ActionIcon
138
+ className="topic-more"
139
+ icon={MoreVertical}
140
+ onClick={(e) => {
141
+ e.stopPropagation();
142
+ }}
143
+ size={'small'}
144
+ />
145
+ </Dropdown>
146
+ )}
147
+ </>
148
+ );
149
+ });
150
+
151
+ export default TopicContent;
@@ -0,0 +1,70 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+ import { shallow } from 'zustand/shallow';
5
+
6
+ import { useSessionStore } from '@/store/session';
7
+
8
+ import DefaultContent from './DefaultContent';
9
+ import TopicContent from './TopicContent';
10
+
11
+ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
12
+ active: css`
13
+ background: ${isDarkMode ? token.colorFillTertiary : token.colorFillSecondary};
14
+ transition: background 200ms ${token.motionEaseOut};
15
+
16
+ &:hover {
17
+ background: ${token.colorFill};
18
+ }
19
+ `,
20
+ container: css`
21
+ cursor: pointer;
22
+ padding: 12px 8px;
23
+ border-radius: ${token.borderRadius}px;
24
+
25
+ .topic-more {
26
+ opacity: 0;
27
+ transition: opacity 400ms ${token.motionEaseOut};
28
+ }
29
+
30
+ &:hover {
31
+ background: ${token.colorFillSecondary};
32
+
33
+ .topic-more {
34
+ opacity: 1;
35
+ }
36
+ }
37
+ `,
38
+ split: css`
39
+ border-bottom: 1px solid ${token.colorSplit};
40
+ `,
41
+ }));
42
+
43
+ export interface ConfigCellProps {
44
+ active?: boolean;
45
+ fav?: boolean;
46
+ id?: string;
47
+ title: string;
48
+ }
49
+
50
+ const TopicItem = memo<ConfigCellProps>(({ title, active, id, fav }) => {
51
+ const { styles, cx } = useStyles();
52
+
53
+ const [toggleTopic] = useSessionStore((s) => [s.toggleTopic], shallow);
54
+
55
+ return (
56
+ <Flexbox
57
+ align={'center'}
58
+ className={cx(styles.container, active && styles.active)}
59
+ distribution={'space-between'}
60
+ horizontal
61
+ onClick={() => {
62
+ toggleTopic(id);
63
+ }}
64
+ >
65
+ {!id ? <DefaultContent /> : <TopicContent fav={fav} id={id} title={title} />}
66
+ </Flexbox>
67
+ );
68
+ });
69
+
70
+ export default TopicItem;
@@ -13,14 +13,7 @@ export const Topic = () => {
13
13
  <TopicItem active={!activeTopicId} fav={false} title={'默认话题'} />
14
14
 
15
15
  {topics.map(({ id, favorite, title }) => (
16
- <TopicItem
17
- active={activeTopicId === id}
18
- fav={favorite}
19
- id={id}
20
- key={id}
21
- showFav
22
- title={title}
23
- />
16
+ <TopicItem active={activeTopicId === id} fav={favorite} id={id} key={id} title={title} />
24
17
  ))}
25
18
  </Flexbox>
26
19
  );
@@ -5,7 +5,7 @@ import { shallow } from 'zustand/shallow';
5
5
 
6
6
  import HeaderSpacing from '@/components/HeaderSpacing';
7
7
  import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
8
- import { useSessionStore } from '@/store/session';
8
+ import { useSettings } from '@/store/settings';
9
9
 
10
10
  import SideBar from './SideBar';
11
11
 
@@ -23,8 +23,8 @@ const useStyles = createStyles(({ cx, css, token, stylish }) => ({
23
23
 
24
24
  const Config = () => {
25
25
  const { styles } = useStyles();
26
- const [showAgentSettings, toggleConfig] = useSessionStore(
27
- (s) => [s.showAgentSettings, s.toggleConfig],
26
+ const [showAgentSettings, toggleConfig] = useSettings(
27
+ (s) => [s.showAgentConfig, s.toggleAgentPanel],
28
28
  shallow,
29
29
  );
30
30
 
@@ -32,11 +32,9 @@ const Config = () => {
32
32
  <DraggablePanel
33
33
  className={styles.drawer}
34
34
  expand={showAgentSettings}
35
- maxWidth={CHAT_SIDEBAR_WIDTH}
36
35
  minWidth={CHAT_SIDEBAR_WIDTH}
37
36
  onExpandChange={toggleConfig}
38
37
  placement={'right'}
39
- resize={false}
40
38
  >
41
39
  <HeaderSpacing />
42
40
  <DraggablePanelContainer style={{ flex: 'none', minWidth: CHAT_SIDEBAR_WIDTH }}>
@@ -1,7 +1,7 @@
1
1
  import { ChatInputArea, DraggablePanel, Icon, Tooltip } from '@lobehub/ui';
2
2
  import { Button } from 'antd';
3
3
  import { LucideGalleryVerticalEnd } from 'lucide-react';
4
- import { memo, useMemo, useState } from 'react';
4
+ import { memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { shallow } from 'zustand/shallow';
7
7
 
@@ -18,18 +18,15 @@ const ChatInput = () => {
18
18
  const [text, setText] = useState('');
19
19
 
20
20
  const [inputHeight] = useSettings((s) => [s.inputHeight], shallow);
21
- const [sendMessage, saveToTopic] = useSessionStore(
22
- (s) => [s.createOrSendMsg, s.saveToTopic],
21
+ const [sendMessage, hasTopic, saveToTopic] = useSessionStore(
22
+ (s) => [s.createOrSendMsg, !!s.activeTopicId, s.saveToTopic],
23
23
  shallow,
24
24
  );
25
25
 
26
- const footer = useMemo(
27
- () => (
28
- <Tooltip title={t('topic.saveCurrentMessages')}>
29
- <Button icon={<Icon icon={LucideGalleryVerticalEnd} />} onClick={saveToTopic} />
30
- </Tooltip>
31
- ),
32
- [],
26
+ const footer = hasTopic ? null : (
27
+ <Tooltip title={t('topic.saveCurrentMessages')}>
28
+ <Button icon={<Icon icon={LucideGalleryVerticalEnd} />} onClick={saveToTopic} />
29
+ </Tooltip>
33
30
  );
34
31
 
35
32
  return (
@@ -8,6 +8,7 @@ import { shallow } from 'zustand/shallow';
8
8
 
9
9
  import HeaderTitle from '@/components/HeaderTitle';
10
10
  import { agentSelectors, useSessionStore } from '@/store/session';
11
+ import { useSettings } from '@/store/settings';
11
12
 
12
13
  const Header = memo(() => {
13
14
  const { t } = useTranslation('common');
@@ -24,8 +25,8 @@ const Header = memo(() => {
24
25
  shallow,
25
26
  );
26
27
 
27
- const [showAgentSettings, toggleConfig] = useSessionStore(
28
- (s) => [s.showAgentSettings, s.toggleConfig],
28
+ const [showAgentSettings, toggleConfig] = useSettings(
29
+ (s) => [s.showAgentConfig, s.toggleAgentPanel],
29
30
  shallow,
30
31
  );
31
32
 
@@ -14,9 +14,8 @@ const useStyles = createStyles(({ css, token, cx }) => {
14
14
  `;
15
15
  return {
16
16
  container: css`
17
- :not(:last-child) {
18
- border-bottom: 1px solid ${token.colorBorder};
19
- }
17
+ overflow: scroll;
18
+ max-height: 370px;
20
19
  `,
21
20
  desc: css`
22
21
  color: ${token.colorTextTertiary};
@@ -27,6 +26,11 @@ const useStyles = createStyles(({ css, token, cx }) => {
27
26
  favicon: css`
28
27
  border-radius: 50%;
29
28
  `,
29
+ item: css`
30
+ :not(:last-child) {
31
+ border-bottom: 1px solid ${token.colorBorder};
32
+ }
33
+ `,
30
34
  link: css`
31
35
  &:hover {
32
36
  .${cx(title)} {
@@ -41,10 +45,10 @@ const useStyles = createStyles(({ css, token, cx }) => {
41
45
  const Render = memo<PluginRenderProps<Result>>(({ content }) => {
42
46
  const { styles } = useStyles();
43
47
  return (
44
- <Flexbox>
48
+ <Flexbox className={styles.container}>
45
49
  {content.map((item, index) => (
46
50
  <Flexbox
47
- className={styles.container}
51
+ className={styles.item}
48
52
  distribution={'space-between'}
49
53
  horizontal
50
54
  key={item.link}
@@ -46,11 +46,6 @@ export interface AgentAction {
46
46
  internalUpdateAgentMeta: (id: string) => any;
47
47
  toggleAgentPlugin: (pluginId: string) => void;
48
48
 
49
- /**
50
- * 切换配置
51
- * @param showPanel - 是否显示面板,默认为 true
52
- */
53
- toggleConfig: (showPanel?: boolean) => void;
54
49
  /**
55
50
  * 更新代理配置
56
51
  * @param config - 部分 LobeAgentConfig 的配置
@@ -214,12 +209,6 @@ export const createAgentSlice: StateCreator<
214
209
  get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
215
210
  },
216
211
 
217
- toggleConfig: (newValue) => {
218
- const showAgentSettings = typeof newValue === 'boolean' ? newValue : !get().showAgentSettings;
219
-
220
- set({ showAgentSettings });
221
- },
222
-
223
212
  updateAgentConfig: (config) => {
224
213
  const { activeId } = get();
225
214
  const session = sessionSelectors.currentSession(get());
@@ -6,8 +6,6 @@ export type SessionLoadingState = Record<Partial<keyof MetaData>, boolean>;
6
6
 
7
7
  export interface AgentConfigState {
8
8
  autocompleteLoading: SessionLoadingState;
9
-
10
- showAgentSettings: boolean;
11
9
  }
12
10
 
13
11
  export const initialLobeAgentConfig: LobeAgentConfig = {
@@ -32,6 +30,4 @@ export const initialAgentConfigState: AgentConfigState = {
32
30
  tag: false,
33
31
  title: false,
34
32
  },
35
-
36
- showAgentSettings: true,
37
33
  };
@@ -1,5 +1,6 @@
1
1
  import { StateCreator } from 'zustand/vanilla';
2
2
 
3
+ import { LOADING_FLAT } from '@/const/message';
3
4
  import { fetchChatModel } from '@/services/chatModel';
4
5
  import { fetchPlugin } from '@/services/plugin';
5
6
  import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
@@ -10,8 +11,6 @@ import { nanoid } from '@/utils/uuid';
10
11
 
11
12
  import { MessageDispatch, messagesReducer } from '../reducers/message';
12
13
 
13
- const LOADING_FLAT = '...';
14
-
15
14
  /**
16
15
  * 聊天操作
17
16
  */
@@ -216,6 +215,7 @@ export const chatMessage: StateCreator<
216
215
 
217
216
  await realFetchAIResponse(histories, latestMsg.id);
218
217
  },
218
+
219
219
  sendMessage: async (message) => {
220
220
  const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta, activeTopicId } =
221
221
  get();
@@ -1,7 +1,8 @@
1
1
  import { StateCreator } from 'zustand/vanilla';
2
2
 
3
+ import { LOADING_FLAT } from '@/const/message';
3
4
  import { promptSummaryTitle } from '@/prompts/chat';
4
- import { SessionStore, chatSelectors, sessionSelectors } from '@/store/session';
5
+ import { SessionStore, chatSelectors, sessionSelectors, topicSelectors } from '@/store/session';
5
6
  import { fetchPresetTaskResult } from '@/utils/fetch';
6
7
  import { nanoid } from '@/utils/uuid';
7
8
 
@@ -13,6 +14,11 @@ export interface ChatTopicAction {
13
14
  * @param payload - 要分发的主题
14
15
  */
15
16
  dispatchTopic: (payload: ChatTopicDispatch) => void;
17
+ /**
18
+ * 移除话题
19
+ * @param id
20
+ */
21
+ removeTopic: (id: string) => void;
16
22
  /**
17
23
  * 将当前消息保存为主题
18
24
  */
@@ -28,6 +34,7 @@ export interface ChatTopicAction {
28
34
  */
29
35
  updateTopicLoading: (id?: string) => void;
30
36
  }
37
+
31
38
  export const chatTopic: StateCreator<
32
39
  SessionStore,
33
40
  [['zustand/devtools', never]],
@@ -43,6 +50,24 @@ export const chatTopic: StateCreator<
43
50
 
44
51
  get().dispatchSession({ id: activeId, topics, type: 'updateSessionTopic' });
45
52
  },
53
+ removeTopic: (id) => {
54
+ const { dispatchTopic, dispatchMessage, toggleTopic } = get();
55
+
56
+ // 移除关联的 message
57
+ const messages = topicSelectors.getTopicMessages(id)(get());
58
+ for (const m of messages) {
59
+ dispatchMessage({
60
+ id: m.id,
61
+ type: 'deleteMessage',
62
+ });
63
+ }
64
+
65
+ // 最后移除 topic
66
+ dispatchTopic({ id, type: 'deleteChatTopic' });
67
+
68
+ // 切换到默认 topic
69
+ toggleTopic();
70
+ },
46
71
  saveToTopic: () => {
47
72
  const session = sessionSelectors.currentSession(get());
48
73
  if (!session) return;
@@ -71,6 +96,8 @@ export const chatTopic: StateCreator<
71
96
  dispatchMessage({ id: m.id, key: 'topicId', type: 'updateMessage', value: topicId });
72
97
  }
73
98
 
99
+ dispatchTopic({ id: topicId, key: 'title', type: 'updateChatTopic', value: LOADING_FLAT });
100
+
74
101
  let output = '';
75
102
 
76
103
  // 自动总结话题标题
@@ -91,7 +118,6 @@ export const chatTopic: StateCreator<
91
118
  toggleTopic: (id) => {
92
119
  set({ activeTopicId: id });
93
120
  },
94
-
95
121
  updateTopicLoading: (id) => {
96
122
  set({ topicLoadingId: id });
97
123
  },
@@ -1,6 +1,7 @@
1
1
  export interface ChatState {
2
2
  activeTopicId?: string;
3
3
  chatLoadingId?: string;
4
+ renameTopicId?: string;
4
5
  topicLoadingId?: string;
5
6
  }
6
7
 
@@ -14,12 +14,22 @@ interface UpdateChatTopicAction {
14
14
  value: any;
15
15
  }
16
16
 
17
+ interface FavorChatTopicAction {
18
+ favorite: boolean;
19
+ id: string;
20
+ type: 'favorChatTopic';
21
+ }
22
+
17
23
  interface DeleteChatTopicAction {
18
24
  id: string;
19
25
  type: 'deleteChatTopic';
20
26
  }
21
27
 
22
- export type ChatTopicDispatch = AddChatTopicAction | UpdateChatTopicAction | DeleteChatTopicAction;
28
+ export type ChatTopicDispatch =
29
+ | AddChatTopicAction
30
+ | UpdateChatTopicAction
31
+ | DeleteChatTopicAction
32
+ | FavorChatTopicAction;
23
33
 
24
34
  export const topicReducer = (state: ChatTopicMap, payload: ChatTopicDispatch): ChatTopicMap => {
25
35
  switch (payload.type) {
@@ -46,6 +56,18 @@ export const topicReducer = (state: ChatTopicMap, payload: ChatTopicDispatch): C
46
56
  });
47
57
  }
48
58
 
59
+ case 'favorChatTopic': {
60
+ return produce(state, (draftState) => {
61
+ const { favorite, id } = payload;
62
+
63
+ if (!draftState[id]) return;
64
+
65
+ const topic = draftState[id];
66
+
67
+ topic.favorite = favorite;
68
+ });
69
+ }
70
+
49
71
  case 'deleteChatTopic': {
50
72
  return produce(state, (draftState) => {
51
73
  delete draftState[payload.id];
@@ -1,6 +1,6 @@
1
1
  import { currentChats } from './chat';
2
2
  import { chatsTokenCount, systemRoleTokenCount, totalTokenCount } from './token';
3
- import { currentTopics } from './topic';
3
+ import { currentTopics, getTopicMessages } from './topic';
4
4
 
5
5
  export const chatSelectors = {
6
6
  chatsTokenCount,
@@ -11,4 +11,5 @@ export const chatSelectors = {
11
11
 
12
12
  export const topicSelectors = {
13
13
  currentTopics,
14
+ getTopicMessages,
14
15
  };
@@ -1,5 +1,6 @@
1
1
  // 展示在聊天框中的消息
2
2
  import { SessionStore, sessionSelectors } from '@/store/session';
3
+ import { organizeChats } from '@/store/session/slices/chat/selectors/utils';
3
4
  import { ChatTopic } from '@/types/topic';
4
5
 
5
6
  export const currentTopics = (s: SessionStore): ChatTopic[] => {
@@ -14,3 +15,10 @@ export const currentTopics = (s: SessionStore): ChatTopic[] => {
14
15
 
15
16
  return [...favTopics, ...defaultTopics];
16
17
  };
18
+
19
+ export const getTopicMessages = (topicId: string) => (s: SessionStore) => {
20
+ const session = sessionSelectors.currentSession(s);
21
+ if (!session) return [];
22
+
23
+ return organizeChats(session, { assistant: '', user: '' }, topicId);
24
+ };
@@ -36,6 +36,7 @@ export interface SettingsAction {
36
36
  * @param key - 选中的侧边栏选项
37
37
  */
38
38
  switchSideBar: (key: SidebarTabKey) => void;
39
+ toggleAgentPanel: (visible?: boolean) => void;
39
40
  }
40
41
 
41
42
  export const createSettings: StateCreator<
@@ -52,7 +53,7 @@ export const createSettings: StateCreator<
52
53
  setSettings({
53
54
  ...settings,
54
55
  // 如果用户存在用户头像,那么不做导入
55
- avatar: get().settings.avatar ?? settings.avatar,
56
+ avatar: !get().settings.avatar ? settings.avatar : get().settings.avatar,
56
57
  });
57
58
  },
58
59
  resetSettings: () => {
@@ -71,4 +72,9 @@ export const createSettings: StateCreator<
71
72
  switchSideBar: (key) => {
72
73
  set({ sidebarKey: key });
73
74
  },
75
+ toggleAgentPanel: (newValue) => {
76
+ const showAgentConfig = typeof newValue === 'boolean' ? newValue : !get().showAgentConfig;
77
+
78
+ set({ showAgentConfig });
79
+ },
74
80
  });
@@ -31,6 +31,7 @@ export interface SettingsState {
31
31
  sessionExpandable?: boolean;
32
32
  sessionsWidth: number;
33
33
  settings: GlobalSettings;
34
+ showAgentConfig?: boolean;
34
35
  sidebarKey: SidebarTabKey;
35
36
  }
36
37
 
@@ -39,5 +40,6 @@ export const initialState: SettingsState = {
39
40
  sessionExpandable: true,
40
41
  sessionsWidth: 320,
41
42
  settings: DEFAULT_SETTINGS,
43
+ showAgentConfig: true,
42
44
  sidebarKey: 'chat',
43
45
  };
@@ -1,136 +0,0 @@
1
- import { ActionIcon, Icon } from '@lobehub/ui';
2
- import { Dropdown, type MenuProps, Tag, Typography } from 'antd';
3
- import { createStyles } from 'antd-style';
4
- import { MessageSquareDashed, MoreVertical, PencilLine, Share2, Star, Trash } from 'lucide-react';
5
- import { memo, useMemo, useState } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
- import { Flexbox } from 'react-layout-kit';
8
- import { shallow } from 'zustand/shallow';
9
-
10
- import { useSessionStore } from '@/store/session';
11
-
12
- const { Paragraph } = Typography;
13
-
14
- const useStyles = createStyles(({ css, token, isDarkMode }) => ({
15
- active: css`
16
- background: ${isDarkMode ? token.colorFillTertiary : token.colorFillSecondary};
17
- transition: background 200ms ${token.motionEaseOut};
18
-
19
- &:hover {
20
- background: ${token.colorFill};
21
- }
22
- `,
23
- container: css`
24
- cursor: pointer;
25
- padding: 12px 8px;
26
- border-radius: ${token.borderRadius}px;
27
-
28
- .topic-more {
29
- opacity: 0;
30
- transition: opacity 400ms ${token.motionEaseOut};
31
- }
32
-
33
- &:hover {
34
- background: ${token.colorFillSecondary};
35
-
36
- .topic-more {
37
- opacity: 1;
38
- }
39
- }
40
- `,
41
- split: css`
42
- border-bottom: 1px solid ${token.colorSplit};
43
- `,
44
- }));
45
-
46
- export interface ConfigCellProps {
47
- active?: boolean;
48
- fav?: boolean;
49
- id?: string;
50
- showFav?: boolean;
51
- title: string;
52
- }
53
-
54
- const TopicItem = memo<ConfigCellProps>(({ title, active, id, showFav, fav }) => {
55
- const [dropdownOpen, setDropdownOpen] = useState(false);
56
- const { styles, theme, cx } = useStyles();
57
- const { t } = useTranslation('common');
58
-
59
- const [dispatchTopic, toggleTopic] = useSessionStore(
60
- (s) => [s.dispatchTopic, s.toggleTopic],
61
- shallow,
62
- );
63
-
64
- // TODO: 动作绑定
65
- const items: MenuProps['items'] = useMemo(
66
- () => [
67
- {
68
- icon: <Icon icon={PencilLine} />,
69
- key: 'rename',
70
- label: t('rename'),
71
- },
72
- {
73
- icon: <Icon icon={Share2} />,
74
- key: 'share',
75
- label: t('share'),
76
- },
77
- {
78
- icon: <Icon icon={Trash} />,
79
- key: 'delete',
80
- label: t('delete'),
81
- },
82
- ],
83
- [],
84
- );
85
-
86
- return (
87
- <Flexbox
88
- align={'center'}
89
- className={cx(styles.container, active && styles.active)}
90
- distribution={'space-between'}
91
- horizontal
92
- onClick={() => {
93
- toggleTopic(id);
94
- }}
95
- >
96
- <Flexbox align={'center'} gap={8} horizontal>
97
- {!showFav ? (
98
- <Flexbox align={'center'} height={24} justify={'center'} width={24}>
99
- <Icon color={theme.colorTextDescription} icon={MessageSquareDashed} />
100
- </Flexbox>
101
- ) : (
102
- <ActionIcon
103
- color={fav ? theme.colorWarning : undefined}
104
- fill={fav ? theme.colorWarning : 'transparent'}
105
- icon={Star}
106
- onClick={() => {
107
- if (!id) return;
108
- dispatchTopic({ id, key: 'favorite', type: 'updateChatTopic', value: !fav });
109
- }}
110
- size={'small'}
111
- />
112
- )}
113
- <Paragraph ellipsis={{ rows: 2 }} style={{ margin: 0 }}>
114
- {title}
115
- </Paragraph>
116
- {id ? '' : <Tag>{t('temp')}</Tag>}
117
- </Flexbox>
118
- <Dropdown
119
- arrow={false}
120
- menu={{ items }}
121
- onOpenChange={setDropdownOpen}
122
- open={dropdownOpen}
123
- trigger={['click']}
124
- >
125
- <ActionIcon
126
- className="topic-more"
127
- icon={MoreVertical}
128
- size={'small'}
129
- style={dropdownOpen ? { opacity: 1 } : {}}
130
- />
131
- </Dropdown>
132
- </Flexbox>
133
- );
134
- });
135
-
136
- export default TopicItem;