@lobehub/chat 1.28.6 → 1.29.0

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/locales/ar/chat.json +1 -0
  3. package/locales/ar/setting.json +7 -2
  4. package/locales/bg-BG/chat.json +1 -0
  5. package/locales/bg-BG/setting.json +7 -2
  6. package/locales/de-DE/chat.json +1 -0
  7. package/locales/de-DE/setting.json +7 -2
  8. package/locales/en-US/chat.json +1 -0
  9. package/locales/en-US/setting.json +7 -2
  10. package/locales/es-ES/chat.json +1 -0
  11. package/locales/es-ES/setting.json +7 -2
  12. package/locales/fa-IR/chat.json +1 -0
  13. package/locales/fa-IR/setting.json +7 -2
  14. package/locales/fr-FR/chat.json +1 -0
  15. package/locales/fr-FR/setting.json +7 -2
  16. package/locales/it-IT/chat.json +1 -0
  17. package/locales/it-IT/setting.json +7 -2
  18. package/locales/ja-JP/chat.json +1 -0
  19. package/locales/ja-JP/setting.json +7 -2
  20. package/locales/ko-KR/chat.json +1 -0
  21. package/locales/ko-KR/setting.json +7 -2
  22. package/locales/nl-NL/chat.json +1 -0
  23. package/locales/nl-NL/setting.json +7 -2
  24. package/locales/pl-PL/chat.json +1 -0
  25. package/locales/pl-PL/setting.json +7 -2
  26. package/locales/pt-BR/chat.json +1 -0
  27. package/locales/pt-BR/setting.json +7 -2
  28. package/locales/ru-RU/chat.json +1 -0
  29. package/locales/ru-RU/setting.json +7 -2
  30. package/locales/tr-TR/chat.json +1 -0
  31. package/locales/tr-TR/setting.json +7 -2
  32. package/locales/vi-VN/chat.json +1 -0
  33. package/locales/vi-VN/setting.json +7 -2
  34. package/locales/zh-CN/chat.json +1 -0
  35. package/locales/zh-CN/setting.json +7 -2
  36. package/locales/zh-TW/chat.json +1 -0
  37. package/locales/zh-TW/setting.json +7 -2
  38. package/package.json +1 -1
  39. package/src/app/(main)/settings/system-agent/index.tsx +1 -0
  40. package/src/chains/__tests__/__snapshots__/summaryHistory.test.ts.snap +21 -0
  41. package/src/chains/__tests__/summaryHistory.test.ts +24 -0
  42. package/src/chains/summaryHistory.ts +19 -0
  43. package/src/const/settings/agent.ts +3 -1
  44. package/src/const/settings/systemAgent.ts +1 -0
  45. package/src/database/client/models/__tests__/session.test.ts +0 -1
  46. package/src/database/server/migrations/0011_add_topic_history_summary.sql +2 -0
  47. package/src/database/server/migrations/meta/0011_snapshot.json +3196 -0
  48. package/src/database/server/migrations/meta/_journal.json +7 -0
  49. package/src/database/server/models/__tests__/topic.test.ts +4 -0
  50. package/src/database/server/models/topic.ts +16 -0
  51. package/src/database/server/schemas/lobechat/topic.ts +3 -2
  52. package/src/features/AgentSetting/AgentChat/index.tsx +4 -18
  53. package/src/features/ChatInput/ActionBar/History.tsx +24 -21
  54. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +22 -7
  55. package/src/features/Conversation/Actions/index.ts +2 -2
  56. package/src/features/Conversation/components/ChatItem/index.tsx +6 -6
  57. package/src/features/Conversation/components/History/index.tsx +71 -0
  58. package/src/features/Conversation/types/index.tsx +1 -1
  59. package/src/locales/default/chat.ts +1 -0
  60. package/src/locales/default/setting.ts +7 -2
  61. package/src/prompts/chatMessages/index.test.ts +94 -0
  62. package/src/prompts/chatMessages/index.ts +11 -0
  63. package/src/prompts/systemRole/index.ts +22 -0
  64. package/src/server/routers/lambda/topic.ts +7 -0
  65. package/src/services/__tests__/chat.test.ts +13 -61
  66. package/src/services/chat.ts +45 -11
  67. package/src/store/agent/slices/chat/__snapshots__/selectors.test.ts.snap +3 -1
  68. package/src/store/chat/helpers.ts +6 -2
  69. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +21 -8
  70. package/src/store/chat/slices/aiChat/actions/helpers.ts +9 -0
  71. package/src/store/chat/slices/aiChat/actions/index.ts +3 -1
  72. package/src/store/chat/slices/aiChat/actions/memory.ts +52 -0
  73. package/src/store/chat/slices/message/selectors.ts +1 -3
  74. package/src/store/chat/slices/topic/selectors.ts +24 -12
  75. package/src/store/chat/slices/{enchance → translate}/action.test.ts +0 -13
  76. package/src/store/chat/slices/{enchance → translate}/action.ts +5 -24
  77. package/src/store/chat/slices/tts/action.test.ts +63 -0
  78. package/src/store/chat/slices/tts/action.ts +35 -0
  79. package/src/store/chat/store.ts +6 -3
  80. package/src/store/file/reducers/uploadFileList.test.ts +197 -0
  81. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +3 -1
  82. package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
  83. package/src/types/agent/index.ts +2 -4
  84. package/src/types/topic.ts +13 -0
  85. package/src/types/user/settings/systemAgent.ts +1 -0
  86. package/src/utils/tokenizer/client.ts +1 -1
  87. /package/src/features/Conversation/components/{ChatItem → History}/HistoryDivider.tsx +0 -0
@@ -77,6 +77,13 @@
77
77
  "when": 1730900133049,
78
78
  "tag": "0010_add_accessed_at_and_clean_tables",
79
79
  "breakpoints": true
80
+ },
81
+ {
82
+ "idx": 11,
83
+ "version": "7",
84
+ "when": 1731138670427,
85
+ "tag": "0011_add_topic_history_summary",
86
+ "breakpoints": true
80
87
  }
81
88
  ],
82
89
  "version": "6"
@@ -427,6 +427,8 @@ describe('TopicModel', () => {
427
427
  favorite: true,
428
428
  sessionId,
429
429
  userId,
430
+ historySummary: null,
431
+ metadata: null,
430
432
  clientId: null,
431
433
  createdAt: expect.any(Date),
432
434
  updatedAt: expect.any(Date),
@@ -473,6 +475,8 @@ describe('TopicModel', () => {
473
475
  title: 'New Topic',
474
476
  favorite: false,
475
477
  clientId: null,
478
+ historySummary: null,
479
+ metadata: null,
476
480
  sessionId,
477
481
  userId,
478
482
  createdAt: expect.any(Date),
@@ -36,6 +36,8 @@ export class TopicModel {
36
36
  createdAt: topics.createdAt,
37
37
  favorite: topics.favorite,
38
38
  id: topics.id,
39
+ metadata: topics.metadata,
40
+ summary: topics.historySummary,
39
41
  title: topics.title,
40
42
  updatedAt: topics.updatedAt,
41
43
  })
@@ -47,6 +49,20 @@ export class TopicModel {
47
49
  .limit(pageSize)
48
50
  .offset(offset)
49
51
  );
52
+
53
+ // return result.map(({ summary, metadata, ...item }) => {
54
+ // const meta = metadata as ChatTopicMetadata;
55
+ // return {
56
+ // ...item,
57
+ // summary: !!summary
58
+ // ? ({
59
+ // content: summary,
60
+ // model: meta?.model,
61
+ // provider: meta?.provider,
62
+ // } as ChatTopicSummary)
63
+ // : undefined,
64
+ // };
65
+ // });
50
66
  }
51
67
 
52
68
  async findById(id: string) {
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
- import { boolean, pgTable, text, unique } from 'drizzle-orm/pg-core';
2
+ import { boolean, jsonb, pgTable, text, unique } from 'drizzle-orm/pg-core';
3
3
 
4
4
  import { idGenerator } from '../../utils/idGenerator';
5
5
  import { timestamps } from './_helpers';
@@ -19,7 +19,8 @@ export const topics = pgTable(
19
19
  .references(() => users.id, { onDelete: 'cascade' })
20
20
  .notNull(),
21
21
  clientId: text('client_id'),
22
-
22
+ historySummary: text('history_summary'),
23
+ metadata: jsonb('metadata'),
23
24
  ...timestamps,
24
25
  },
25
26
  (t) => ({
@@ -18,20 +18,13 @@ const AgentChat = memo(() => {
18
18
  const { t } = useTranslation('setting');
19
19
  const [form] = Form.useForm();
20
20
  const { isDarkMode } = useThemeMode();
21
- const [
22
- displayMode,
23
- enableAutoCreateTopic,
24
- enableHistoryCount,
25
- enableCompressThreshold,
26
- updateConfig,
27
- ] = useStore((s) => {
21
+ const [displayMode, enableAutoCreateTopic, enableHistoryCount, updateConfig] = useStore((s) => {
28
22
  const config = selectors.chatConfig(s);
29
23
 
30
24
  return [
31
25
  config.displayMode,
32
26
  config.enableAutoCreateTopic,
33
27
  config.enableHistoryCount,
34
- config.enableCompressThreshold,
35
28
  s.setChatConfig,
36
29
  ];
37
30
  });
@@ -110,19 +103,12 @@ const AgentChat = memo(() => {
110
103
  },
111
104
  {
112
105
  children: <Switch />,
113
- label: t('settingChat.enableCompressThreshold.title'),
106
+ hidden: !enableHistoryCount,
107
+ label: t('settingChat.enableCompressHistory.title'),
114
108
  minWidth: undefined,
115
- name: 'enableCompressThreshold',
109
+ name: 'enableCompressHistory',
116
110
  valuePropName: 'checked',
117
111
  },
118
- {
119
- children: <SliderWithInput max={32} min={0} />,
120
- desc: t('settingChat.compressThreshold.desc'),
121
- divider: false,
122
- hidden: !enableCompressThreshold,
123
- label: t('settingChat.compressThreshold.title'),
124
- name: 'compressThreshold',
125
- },
126
112
  ],
127
113
  title: t('settingChat.title'),
128
114
  };
@@ -5,6 +5,7 @@ import { memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
 
8
+ import { useIsMobile } from '@/hooks/useIsMobile';
8
9
  import { useAgentStore } from '@/store/agent';
9
10
  import { agentSelectors } from '@/store/agent/selectors';
10
11
 
@@ -12,17 +13,18 @@ const History = memo(() => {
12
13
  const { t } = useTranslation('setting');
13
14
  const [popoverOpen, setPopoverOpen] = useState(false);
14
15
 
15
- const [historyCount, unlimited, updateAgentConfig] = useAgentStore((s) => {
16
+ const [historyCount, enableHistoryCount, updateAgentConfig] = useAgentStore((s) => {
16
17
  const config = agentSelectors.currentAgentChatConfig(s);
17
- return [config.historyCount, !config.enableHistoryCount, s.updateAgentChatConfig];
18
+ return [config.historyCount, config.enableHistoryCount, s.updateAgentChatConfig];
18
19
  });
19
20
 
20
21
  const title = t(
21
- unlimited
22
- ? 'settingChat.enableHistoryCount.unlimited'
23
- : 'settingChat.enableHistoryCount.limited',
22
+ enableHistoryCount
23
+ ? 'settingChat.enableHistoryCount.limited'
24
+ : 'settingChat.enableHistoryCount.unlimited',
24
25
  { number: historyCount || 0 },
25
26
  );
27
+ const mobile = useIsMobile();
26
28
 
27
29
  return (
28
30
  <Popover
@@ -30,36 +32,37 @@ const History = memo(() => {
30
32
  content={
31
33
  <Flexbox align={'center'} gap={16} horizontal>
32
34
  <SliderWithInput
33
- disabled={unlimited}
34
- max={30}
35
- min={1}
35
+ disabled={!enableHistoryCount}
36
+ max={20}
37
+ min={0}
36
38
  onChange={(v) => {
37
39
  updateAgentConfig({ historyCount: v });
38
40
  }}
39
41
  step={1}
40
- style={{ width: 160 }}
42
+ style={{ width: mobile ? 160 : 300 }}
41
43
  value={historyCount}
42
44
  />
43
- <Flexbox align={'center'} gap={4} horizontal>
44
- <Switch
45
- checked={unlimited}
46
- onChange={(checked) => {
47
- updateAgentConfig({ enableHistoryCount: !checked });
48
- }}
49
- size={'small'}
50
- />
51
- {t('settingChat.enableHistoryCount.alias')}
52
- </Flexbox>
53
45
  </Flexbox>
54
46
  }
55
47
  onOpenChange={setPopoverOpen}
56
48
  open={popoverOpen}
57
49
  placement={'top'}
58
- title={t('settingChat.enableHistoryCount.setlimited')}
50
+ title={
51
+ <Flexbox align={'center'} gap={4} horizontal>
52
+ <Switch
53
+ checked={enableHistoryCount}
54
+ onChange={(enableHistoryCount) => {
55
+ updateAgentConfig({ enableHistoryCount });
56
+ }}
57
+ size={'small'}
58
+ />
59
+ {t('settingChat.enableHistoryCount.title')}
60
+ </Flexbox>
61
+ }
59
62
  trigger={'click'}
60
63
  >
61
64
  <ActionIcon
62
- icon={unlimited ? TimerOff : Timer}
65
+ icon={enableHistoryCount ? Timer : TimerOff}
63
66
  placement={'bottom'}
64
67
  title={popoverOpen ? undefined : title}
65
68
  />
@@ -10,7 +10,7 @@ import { useTokenCount } from '@/hooks/useTokenCount';
10
10
  import { useAgentStore } from '@/store/agent';
11
11
  import { agentSelectors } from '@/store/agent/selectors';
12
12
  import { useChatStore } from '@/store/chat';
13
- import { chatSelectors } from '@/store/chat/selectors';
13
+ import { chatSelectors, topicSelectors } from '@/store/chat/selectors';
14
14
  import { useToolStore } from '@/store/tool';
15
15
  import { toolSelectors } from '@/store/tool/selectors';
16
16
  import { useUserStore } from '@/store/user';
@@ -22,15 +22,23 @@ const Token = memo(() => {
22
22
  const { t } = useTranslation(['chat', 'components']);
23
23
  const theme = useTheme();
24
24
 
25
- const [input, messageString] = useChatStore((s) => [
25
+ const [input, messageString, historySummary] = useChatStore((s) => [
26
26
  s.inputMessage,
27
27
  chatSelectors.chatsMessageString(s),
28
+ topicSelectors.currentActiveTopicSummary(s)?.content || '',
28
29
  ]);
29
30
 
30
- const [systemRole, model] = useAgentStore((s) => [
31
- agentSelectors.currentAgentSystemRole(s),
32
- agentSelectors.currentAgentModel(s) as string,
33
- ]);
31
+ const [systemRole, model] = useAgentStore((s) => {
32
+ const config = agentSelectors.currentAgentChatConfig(s);
33
+
34
+ return [
35
+ agentSelectors.currentAgentSystemRole(s),
36
+ agentSelectors.currentAgentModel(s) as string,
37
+ // add these two params to enable the component to re-render
38
+ config.historyCount,
39
+ config.enableHistoryCount,
40
+ ];
41
+ });
34
42
 
35
43
  const maxTokens = useUserStore(modelProviderSelectors.modelMaxToken(model));
36
44
 
@@ -55,9 +63,10 @@ const Token = memo(() => {
55
63
 
56
64
  // SystemRole token
57
65
  const systemRoleToken = useTokenCount(systemRole);
66
+ const historySummaryToken = useTokenCount(historySummary);
58
67
 
59
68
  // Total token
60
- const totalToken = systemRoleToken + toolsToken + chatsToken;
69
+ const totalToken = systemRoleToken + historySummaryToken + toolsToken + chatsToken;
61
70
 
62
71
  const content = (
63
72
  <Flexbox gap={12} style={{ minWidth: 200 }}>
@@ -99,6 +108,12 @@ const Token = memo(() => {
99
108
  title: t('tokenDetails.tools'),
100
109
  value: toolsToken,
101
110
  },
111
+ {
112
+ color: theme.orange,
113
+ id: 'historySummary',
114
+ title: t('tokenDetails.historySummary'),
115
+ value: historySummaryToken,
116
+ },
102
117
  {
103
118
  color: theme.gold,
104
119
  id: 'chats',
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
 
5
5
  import { useChatStore } from '@/store/chat';
6
- import { LLMRoleType } from '@/types/llm';
6
+ import { MessageRoleType } from '@/types/message';
7
7
 
8
8
  import { OnActionsClick, RenderAction } from '../types';
9
9
  import { AssistantActionsBar } from './Assistant';
@@ -11,7 +11,7 @@ import { DefaultActionsBar } from './Fallback';
11
11
  import { ToolActionsBar } from './Tool';
12
12
  import { UserActionsBar } from './User';
13
13
 
14
- export const renderActions: Record<LLMRoleType, RenderAction> = {
14
+ export const renderActions: Record<MessageRoleType, RenderAction> = {
15
15
  assistant: AssistantActionsBar,
16
16
  system: DefaultActionsBar,
17
17
  tool: ToolActionsBar,
@@ -22,9 +22,9 @@ import {
22
22
  renderMessages,
23
23
  useAvatarsClick,
24
24
  } from '../../Messages';
25
+ import History from '../History';
25
26
  import { markdownElements } from '../MarkdownElements';
26
27
  import ActionsBar from './ActionsBar';
27
- import HistoryDivider from './HistoryDivider';
28
28
  import { processWithArtifact } from './utils';
29
29
 
30
30
  const rehypePlugins = markdownElements.map((element) => element.rehypePlugin);
@@ -61,11 +61,9 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
61
61
 
62
62
  if (index >= chats.length) return;
63
63
 
64
- return chatSelectors.currentChatsWithGuideMessage(meta)(s)[index];
64
+ return chats[index];
65
65
  }, isEqual);
66
66
 
67
- const historyLength = useChatStore((s) => chatSelectors.currentChats(s).length);
68
-
69
67
  const [
70
68
  isMessageLoading,
71
69
  generating,
@@ -138,12 +136,14 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
138
136
 
139
137
  const error = useErrorContent(item?.error);
140
138
 
139
+ const historyLength = useChatStore((s) => chatSelectors.currentChats(s).length);
140
+
141
141
  const enableHistoryDivider = useAgentStore((s) => {
142
142
  const config = agentSelectors.currentAgentChatConfig(s);
143
143
  return (
144
144
  config.enableHistoryCount &&
145
145
  historyLength > (config.historyCount ?? 0) &&
146
- config.historyCount === historyLength - index + 1
146
+ config.historyCount === historyLength - index
147
147
  );
148
148
  });
149
149
 
@@ -166,7 +166,7 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
166
166
  return (
167
167
  item && (
168
168
  <>
169
- <HistoryDivider enable={enableHistoryDivider} />
169
+ {enableHistoryDivider && <History />}
170
170
  <ChatItem
171
171
  actions={
172
172
  <ActionsBar
@@ -0,0 +1,71 @@
1
+ import { ModelTag } from '@lobehub/icons';
2
+ import { Icon, Markdown } from '@lobehub/ui';
3
+ import { Typography } from 'antd';
4
+ import { createStyles } from 'antd-style';
5
+ import { ScrollText } from 'lucide-react';
6
+ import { memo } from 'react';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
+
9
+ import { useChatStore } from '@/store/chat';
10
+ import { topicSelectors } from '@/store/chat/selectors';
11
+
12
+ import HistoryDivider from './HistoryDivider';
13
+
14
+ const useStyles = createStyles(({ css, token }) => ({
15
+ container: css`
16
+ padding-inline: 12px;
17
+ border-radius: 12px;
18
+ `,
19
+ content: css`
20
+ color: ${token.colorTextDescription};
21
+ `,
22
+ line: css`
23
+ width: 3px;
24
+ height: 100%;
25
+ background: ${token.colorBorder};
26
+ `,
27
+ }));
28
+
29
+ const History = memo(() => {
30
+ const { styles, theme } = useStyles();
31
+ const [content, model] = useChatStore((s) => {
32
+ const history = topicSelectors.currentActiveTopicSummary(s);
33
+ return [history?.content, history?.model];
34
+ });
35
+
36
+ return (
37
+ <Flexbox paddingInline={16} style={{ paddingBottom: 8 }}>
38
+ <HistoryDivider enable />
39
+ {!!content && (
40
+ <Flexbox className={styles.container} gap={8}>
41
+ <Flexbox align={'flex-start'} gap={8} horizontal>
42
+ <Center height={20} width={20}>
43
+ <Icon
44
+ icon={ScrollText}
45
+ size={{ fontSize: 16 }}
46
+ style={{ color: theme.colorTextDescription }}
47
+ />
48
+ </Center>
49
+ <Typography.Text type={'secondary'}>历史消息总结</Typography.Text>
50
+
51
+ {model && (
52
+ <div>
53
+ <ModelTag model={model} />
54
+ </div>
55
+ )}
56
+ </Flexbox>
57
+ <Flexbox align={'flex-start'} gap={8} horizontal>
58
+ <Flexbox align={'center'} padding={8} width={20}>
59
+ <div className={styles.line}></div>
60
+ </Flexbox>
61
+ <Markdown className={styles.content} variant={'chat'}>
62
+ {content}
63
+ </Markdown>
64
+ </Flexbox>
65
+ </Flexbox>
66
+ )}
67
+ </Flexbox>
68
+ );
69
+ });
70
+
71
+ export default History;
@@ -9,7 +9,7 @@ import { type ActionsBarProps } from '../components/ChatItem/ActionsBar';
9
9
 
10
10
  export type OnActionsClick = (action: ActionEvent, message: ChatMessage) => void;
11
11
  export type OnAvatarsClick = (role: RenderRole) => ChatItemProps['onAvatarClick'];
12
- export type RenderRole = LLMRoleType | 'default' | string;
12
+ export type RenderRole = LLMRoleType | 'default' | 'history' | string;
13
13
  export type RenderMessage = FC<ChatMessage & { editableContent: ReactNode }>;
14
14
  export type RenderBelowMessage = FC<ChatMessage>;
15
15
  export type RenderMessageExtra = FC<ChatMessage>;
@@ -125,6 +125,7 @@ export default {
125
125
  },
126
126
  tokenDetails: {
127
127
  chats: '会话消息',
128
+ historySummary: '历史总结',
128
129
  rest: '剩余可用',
129
130
  systemRole: '角色设定',
130
131
  title: '上下文明细',
@@ -177,8 +177,8 @@ export default {
177
177
  desc: '会话过程中是否自动创建话题,仅在临时话题中生效',
178
178
  title: '自动创建话题',
179
179
  },
180
- enableCompressThreshold: {
181
- title: '是否开启历史消息长度压缩阈值',
180
+ enableCompressHistory: {
181
+ title: '开启历史消息自动总结',
182
182
  },
183
183
  enableHistoryCount: {
184
184
  alias: '不限制',
@@ -380,6 +380,11 @@ export default {
380
380
  placeholder: '请输入自定义提示词',
381
381
  title: '自定义提示词',
382
382
  },
383
+ historyCompress: {
384
+ label: '会话历史模型',
385
+ modelDesc: '指定用于压缩会话历史的模型',
386
+ title: '自动总结会话历史',
387
+ },
383
388
  queryRewrite: {
384
389
  label: '提问重写模型',
385
390
  modelDesc: '指定用于优化用户提问的模型',
@@ -0,0 +1,94 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { ChatMessage } from '@/types/message';
4
+
5
+ import { chatHistoryPrompts } from './index';
6
+
7
+ describe('chatHistoryPrompts', () => {
8
+ // Test with empty messages array
9
+ it('should return empty chat history with empty messages', () => {
10
+ const messages: ChatMessage[] = [];
11
+ const result = chatHistoryPrompts(messages);
12
+
13
+ expect(result).toBe(`<chat_history>
14
+
15
+ </chat_history>`);
16
+ });
17
+
18
+ // Test with single message
19
+ it('should format single message correctly', () => {
20
+ const messages = [
21
+ {
22
+ role: 'user',
23
+ content: 'Hello',
24
+ },
25
+ ] as ChatMessage[];
26
+ const result = chatHistoryPrompts(messages);
27
+
28
+ expect(result).toBe(`<chat_history>
29
+ <user>Hello</user>
30
+ </chat_history>`);
31
+ });
32
+
33
+ // Test with multiple messages
34
+ it('should format multiple messages correctly', () => {
35
+ const messages = [
36
+ {
37
+ role: 'user',
38
+ content: 'Hello',
39
+ },
40
+ {
41
+ role: 'assistant',
42
+ content: 'Hi there!',
43
+ },
44
+ {
45
+ role: 'user',
46
+ content: 'How are you?',
47
+ },
48
+ ] as ChatMessage[];
49
+ const result = chatHistoryPrompts(messages);
50
+
51
+ expect(result).toBe(`<chat_history>
52
+ <user>Hello</user>
53
+ <assistant>Hi there!</assistant>
54
+ <user>How are you?</user>
55
+ </chat_history>`);
56
+ });
57
+
58
+ // Test with messages containing special characters
59
+ it('should handle messages with special characters', () => {
60
+ const messages = [
61
+ {
62
+ role: 'user',
63
+ content: 'Hello & goodbye',
64
+ },
65
+ {
66
+ role: 'assistant',
67
+ content: '<test> & </test>',
68
+ },
69
+ ] as ChatMessage[];
70
+
71
+ const result = chatHistoryPrompts(messages);
72
+
73
+ expect(result).toBe(`<chat_history>
74
+ <user>Hello & goodbye</user>
75
+ <assistant><test> & </test></assistant>
76
+ </chat_history>`);
77
+ });
78
+
79
+ // Test with messages containing multiple lines
80
+ it('should handle multi-line messages correctly', () => {
81
+ const messages = [
82
+ {
83
+ role: 'user',
84
+ content: 'Line 1\nLine 2',
85
+ },
86
+ ] as ChatMessage[];
87
+
88
+ const result = chatHistoryPrompts(messages);
89
+
90
+ expect(result).toBe(`<chat_history>
91
+ <user>Line 1\nLine 2</user>
92
+ </chat_history>`);
93
+ });
94
+ });
@@ -0,0 +1,11 @@
1
+ import { ChatMessage } from '@/types/message';
2
+
3
+ const chatMessage = (message: ChatMessage) => {
4
+ return `<${message.role}>${message.content}</${message.role}>`;
5
+ };
6
+
7
+ export const chatHistoryPrompts = (messages: ChatMessage[]) => {
8
+ return `<chat_history>
9
+ ${messages.map((m) => chatMessage(m)).join('\n')}
10
+ </chat_history>`;
11
+ };
@@ -0,0 +1,22 @@
1
+ const historySummaryPrompt = (historySummary: string) => `<chat_history_summary>
2
+ <docstring>Users may have lots of chat messages,here is the summary of the hisotry:</docstring>
3
+ <summary>${historySummary}</summary>
4
+ </chat_history_summary>
5
+ `;
6
+
7
+ /**
8
+ * Lobe Chat will inject some system instructions here
9
+ */
10
+ export const BuiltinSystemRolePrompts = ({
11
+ welcome,
12
+ plugins,
13
+ historySummary,
14
+ }: {
15
+ historySummary?: string;
16
+ plugins?: string;
17
+ welcome?: string;
18
+ }) => {
19
+ return [welcome, plugins, historySummary ? historySummaryPrompt(historySummary) : '']
20
+ .filter(Boolean)
21
+ .join('\n\n');
22
+ };
@@ -121,7 +121,14 @@ export const topicRouter = router({
121
121
  value: z.object({
122
122
  favorite: z.boolean().optional(),
123
123
  messages: z.array(z.string()).optional(),
124
+ metadata: z
125
+ .object({
126
+ model: z.string().optional(),
127
+ provider: z.string().optional(),
128
+ })
129
+ .optional(),
124
130
  sessionId: z.string().optional(),
131
+ summary: z.string().optional(),
125
132
  title: z.string().optional(),
126
133
  }),
127
134
  }),