@lobehub/lobehub 2.0.0-next.277 → 2.0.0-next.278

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 (73) hide show
  1. package/.cursor/rules/db-migrations.mdc +1 -1
  2. package/.cursor/rules/debug-usage.mdc +7 -5
  3. package/.cursor/rules/desktop-controller-tests.mdc +2 -1
  4. package/.cursor/rules/desktop-feature-implementation.mdc +9 -5
  5. package/.cursor/rules/desktop-local-tools-implement.mdc +67 -66
  6. package/.cursor/rules/desktop-menu-configuration.mdc +21 -9
  7. package/.cursor/rules/desktop-window-management.mdc +17 -2
  8. package/.cursor/rules/drizzle-schema-style-guide.mdc +6 -6
  9. package/.cursor/rules/hotkey.mdc +1 -0
  10. package/.cursor/rules/i18n.mdc +1 -0
  11. package/.cursor/rules/project-structure.mdc +16 -3
  12. package/.cursor/rules/react.mdc +17 -5
  13. package/.cursor/rules/recent-data-usage.mdc +2 -1
  14. package/.cursor/rules/testing-guide/testing-guide.mdc +262 -238
  15. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +1 -1
  16. package/.cursor/rules/zustand-action-patterns.mdc +1 -1
  17. package/.cursor/rules/zustand-slice-organization.mdc +4 -4
  18. package/CHANGELOG.md +25 -0
  19. package/CLAUDE.md +1 -1
  20. package/GEMINI.md +1 -1
  21. package/changelog/v1.json +5 -0
  22. package/docs/development/database-schema.dbml +16 -0
  23. package/locales/en-US/chat.json +24 -0
  24. package/locales/zh-CN/chat.json +24 -0
  25. package/package.json +1 -1
  26. package/packages/business/const/src/index.ts +3 -0
  27. package/packages/database/migrations/0069_add_topic_shares_table.sql +22 -0
  28. package/packages/database/migrations/meta/0069_snapshot.json +9704 -0
  29. package/packages/database/migrations/meta/_journal.json +7 -0
  30. package/packages/database/src/models/__tests__/topicShare.test.ts +318 -0
  31. package/packages/database/src/models/topicShare.ts +177 -0
  32. package/packages/database/src/schemas/topic.ts +44 -2
  33. package/packages/types/src/conversation.ts +5 -0
  34. package/packages/types/src/topic/topic.ts +46 -0
  35. package/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx +24 -9
  36. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +26 -9
  37. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +1 -2
  38. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ImageNum.tsx +54 -173
  39. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx +22 -67
  40. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +18 -0
  41. package/src/app/[variants]/router/desktopRouter.config.tsx +18 -0
  42. package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +54 -0
  43. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +170 -0
  44. package/src/app/[variants]/share/t/[id]/features/Portal/index.tsx +66 -0
  45. package/src/app/[variants]/share/t/[id]/index.tsx +112 -0
  46. package/src/app/robots.tsx +1 -1
  47. package/src/business/client/BusinessMobileRoutes.tsx +1 -1
  48. package/src/features/Conversation/ChatList/index.tsx +12 -5
  49. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/index.tsx +8 -4
  50. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +15 -10
  51. package/src/features/Conversation/Messages/AssistantGroup/Tools.tsx +3 -1
  52. package/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx +3 -2
  53. package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +2 -2
  54. package/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +25 -26
  55. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +4 -2
  56. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +16 -12
  57. package/src/features/Conversation/Messages/Tool/index.tsx +20 -11
  58. package/src/features/Conversation/Messages/index.tsx +1 -1
  59. package/src/features/Conversation/store/slices/data/action.ts +2 -1
  60. package/src/features/SharePopover/index.tsx +215 -0
  61. package/src/features/SharePopover/style.ts +10 -0
  62. package/src/libs/next/proxy/define-config.ts +4 -1
  63. package/src/locales/default/chat.ts +26 -0
  64. package/src/proxy.ts +1 -0
  65. package/src/server/routers/lambda/__tests__/message.test.ts +152 -0
  66. package/src/server/routers/lambda/__tests__/share.test.ts +227 -0
  67. package/src/server/routers/lambda/__tests__/topic.test.ts +174 -0
  68. package/src/server/routers/lambda/index.ts +2 -0
  69. package/src/server/routers/lambda/message.ts +37 -4
  70. package/src/server/routers/lambda/share.ts +55 -0
  71. package/src/server/routers/lambda/topic.ts +45 -0
  72. package/src/services/message/index.ts +1 -0
  73. package/src/services/topic/index.ts +16 -0
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { ENABLE_TOPIC_LINK_SHARE } from '@lobechat/business-const';
3
4
  import { ActionIcon } from '@lobehub/ui';
4
5
  import { Share2 } from 'lucide-react';
5
6
  import dynamic from 'next/dynamic';
@@ -8,8 +9,12 @@ import { useTranslation } from 'react-i18next';
8
9
 
9
10
  import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
10
11
  import { useWorkspaceModal } from '@/hooks/useWorkspaceModal';
12
+ import { useChatStore } from '@/store/chat';
13
+
14
+ console.log('ENABLE_TOPIC_LINK_SHARE', ENABLE_TOPIC_LINK_SHARE);
11
15
 
12
16
  const ShareModal = dynamic(() => import('@/features/ShareModal'));
17
+ const SharePopover = dynamic(() => import('@/features/SharePopover'));
13
18
 
14
19
  interface ShareButtonProps {
15
20
  mobile?: boolean;
@@ -20,18 +25,30 @@ interface ShareButtonProps {
20
25
  const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
21
26
  const [isModalOpen, setIsModalOpen] = useWorkspaceModal(open, setOpen);
22
27
  const { t } = useTranslation('common');
28
+ const activeTopicId = useChatStore((s) => s.activeTopicId);
29
+
30
+ // Hide share button when no topic exists (no messages sent yet)
31
+ if (!activeTopicId) return null;
32
+
33
+ const iconButton = (
34
+ <ActionIcon
35
+ icon={Share2}
36
+ onClick={ENABLE_TOPIC_LINK_SHARE ? undefined : () => setIsModalOpen(true)}
37
+ size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
38
+ title={t('share')}
39
+ tooltipProps={{
40
+ placement: 'bottom',
41
+ }}
42
+ />
43
+ );
23
44
 
24
45
  return (
25
46
  <>
26
- <ActionIcon
27
- icon={Share2}
28
- onClick={() => setIsModalOpen(true)}
29
- size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
30
- title={t('share')}
31
- tooltipProps={{
32
- placement: 'bottom',
33
- }}
34
- />
47
+ {ENABLE_TOPIC_LINK_SHARE ? (
48
+ <SharePopover onOpenModal={() => setIsModalOpen(true)}>{iconButton}</SharePopover>
49
+ ) : (
50
+ iconButton
51
+ )}
35
52
  <ShareModal onCancel={() => setIsModalOpen(false)} open={isModalOpen} />
36
53
  </>
37
54
  );
@@ -29,8 +29,7 @@ const AspectRatioSelect = memo<AspectRatioSelectProps>(
29
29
  {options?.map((item) => {
30
30
  const [width, height] = item.value.split(':').map(Number);
31
31
  const isWidthGreater = width > height;
32
- const isEqual = width === height;
33
- const isActive = isEqual ? item.value === '1:1' : active === item.value;
32
+ const isActive = active === item.value;
34
33
  return (
35
34
  <Block
36
35
  align={'center'}
@@ -1,116 +1,15 @@
1
1
  'use client';
2
2
 
3
3
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
4
- import { ActionIcon, Flexbox, InputNumber } from '@lobehub/ui';
5
- import { createStaticStyles, cx } from 'antd-style';
4
+ import { ActionIcon, Flexbox, InputNumber, Segmented } from '@lobehub/ui';
6
5
  import { Check, Plus, X } from 'lucide-react';
7
- import { memo, useCallback, useEffect, useRef, useState } from 'react';
6
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
7
 
9
8
  import { useImageStore } from '@/store/image';
10
9
  import { imageGenerationConfigSelectors } from '@/store/image/selectors';
11
10
 
12
11
  const DEFAULT_IMAGE_NUM_MAX = ENABLE_BUSINESS_FEATURES ? 8 : 50;
13
-
14
- const styles = createStaticStyles(({ css, cssVar }) => ({
15
- actionButton: css`
16
- flex-shrink: 0;
17
- `,
18
-
19
- button: css`
20
- cursor: pointer;
21
-
22
- display: flex;
23
- align-items: center;
24
- justify-content: center;
25
-
26
- min-width: 40px;
27
- height: 32px;
28
- padding-block: 0;
29
- padding-inline: 12px;
30
- border: 1px solid ${cssVar.colorBorder};
31
- border-radius: ${cssVar.borderRadius}px;
32
-
33
- font-size: 14px;
34
- font-weight: 500;
35
- color: ${cssVar.colorText};
36
-
37
- background: ${cssVar.colorBgContainer};
38
-
39
- transition: all 0.2s ease;
40
-
41
- &:hover {
42
- border-color: ${cssVar.colorPrimary};
43
- background: ${cssVar.colorBgTextHover};
44
- }
45
-
46
- &:disabled {
47
- cursor: not-allowed;
48
- opacity: 0.5;
49
-
50
- &:hover {
51
- border-color: ${cssVar.colorBorder};
52
- background: ${cssVar.colorBgContainer};
53
- }
54
- }
55
- `,
56
-
57
- cancelButton: css`
58
- border-color: ${cssVar.colorBorder};
59
- color: ${cssVar.colorTextTertiary};
60
-
61
- &:hover {
62
- border-color: ${cssVar.colorBorderSecondary};
63
- color: ${cssVar.colorText};
64
- background: ${cssVar.colorBgTextHover};
65
- }
66
- `,
67
-
68
- confirmButton: css`
69
- border-color: ${cssVar.colorSuccess};
70
- color: ${cssVar.colorSuccess};
71
-
72
- &:hover {
73
- border-color: ${cssVar.colorSuccessHover};
74
- color: ${cssVar.colorSuccessHover};
75
- background: ${cssVar.colorSuccessBg};
76
- }
77
- `,
78
-
79
- container: css`
80
- display: flex;
81
- gap: 8px;
82
- align-items: center;
83
- `,
84
-
85
- editContainer: css`
86
- display: flex;
87
- gap: 8px;
88
- align-items: center;
89
- width: 100%;
90
- `,
91
-
92
- input: css`
93
- flex: 1;
94
- min-width: 80px;
95
-
96
- .ant-input {
97
- font-weight: 500;
98
- text-align: center;
99
- }
100
- `,
101
-
102
- selectedButton: css`
103
- border-color: ${cssVar.colorPrimary};
104
- color: ${cssVar.colorPrimary};
105
- background: ${cssVar.colorPrimaryBg};
106
-
107
- &:hover {
108
- border-color: ${cssVar.colorPrimary};
109
- color: ${cssVar.colorPrimary};
110
- background: ${cssVar.colorPrimaryBgHover};
111
- }
112
- `,
113
- }));
12
+ const CUSTOM_VALUE = '__custom__';
114
13
 
115
14
  interface ImageNumSelectorProps {
116
15
  disabled?: boolean;
@@ -130,34 +29,52 @@ const ImageNum = memo<ImageNumSelectorProps>(
130
29
 
131
30
  const isCustomValue = !presetCounts.includes(imageNum);
132
31
 
133
- // 处理预设按钮点击
134
- const handlePresetClick = useCallback(
135
- (count: number) => {
32
+ const options = useMemo(() => {
33
+ const items = presetCounts.map((count) => ({
34
+ label: String(count),
35
+ value: count,
36
+ }));
37
+
38
+ // Add custom option or show current custom value
39
+ if (isCustomValue) {
40
+ items.push({
41
+ label: String(imageNum),
42
+ value: imageNum,
43
+ });
44
+ } else {
45
+ items.push({
46
+ label: <Plus size={16} style={{ verticalAlign: 'middle' }} />,
47
+ value: CUSTOM_VALUE,
48
+ } as any);
49
+ }
50
+
51
+ return items;
52
+ }, [presetCounts, isCustomValue, imageNum]);
53
+
54
+ const handleChange = useCallback(
55
+ (value: number | string) => {
136
56
  if (disabled) return;
137
- setImageNum(count);
57
+
58
+ if (value === CUSTOM_VALUE || (isCustomValue && value === imageNum)) {
59
+ // Enter edit mode
60
+ setCustomCount(imageNum);
61
+ customCountRef.current = imageNum;
62
+ setIsEditing(true);
63
+ } else {
64
+ setImageNum(value as number);
65
+ }
138
66
  },
139
- [disabled, setImageNum],
67
+ [disabled, isCustomValue, imageNum, setImageNum],
140
68
  );
141
69
 
142
- // 进入编辑模式
143
- const handleEditStart = useCallback(() => {
144
- if (disabled) return;
145
- setCustomCount(imageNum);
146
- customCountRef.current = imageNum;
147
- setIsEditing(true);
148
- }, [disabled, imageNum]);
149
-
150
- // 确认自定义输入
151
70
  const handleCustomConfirm = useCallback(() => {
152
71
  let count = customCountRef.current;
153
72
 
154
- // 如果解析失败或输入为空,使用当前值
155
73
  if (count === null) {
156
74
  setIsEditing(false);
157
75
  return;
158
76
  }
159
77
 
160
- // 智能处理超出范围的值 (作为二次保险)
161
78
  if (count > max) {
162
79
  count = max;
163
80
  } else if (count < min) {
@@ -174,10 +91,7 @@ const ImageNum = memo<ImageNumSelectorProps>(
174
91
  setCustomCount(null);
175
92
  }, []);
176
93
 
177
- // 处理输入变化
178
94
  const handleInputChange = useCallback((value: number | string | null) => {
179
- console.log('handleInputChange', value);
180
-
181
95
  if (value === null) {
182
96
  setCustomCount(null);
183
97
  customCountRef.current = null;
@@ -192,10 +106,8 @@ const ImageNum = memo<ImageNumSelectorProps>(
192
106
  }
193
107
  }, []);
194
108
 
195
- // 自动聚焦和选择输入框内容
196
109
  useEffect(() => {
197
110
  if (isEditing) {
198
- // 延迟聚焦以确保 input 已渲染
199
111
  setTimeout(() => {
200
112
  if (inputRef.current) {
201
113
  inputRef.current.focus();
@@ -205,16 +117,12 @@ const ImageNum = memo<ImageNumSelectorProps>(
205
117
  }
206
118
  }, [isEditing]);
207
119
 
208
- // 验证输入是否有效
209
- const isValidInput = useCallback(() => {
210
- return customCount !== null;
211
- }, [customCount]);
120
+ const isValidInput = customCount !== null;
212
121
 
213
122
  if (isEditing) {
214
123
  return (
215
- <div className={styles.editContainer}>
124
+ <Flexbox gap={8} horizontal style={{ width: '100%' }}>
216
125
  <InputNumber
217
- className={styles.input}
218
126
  max={max}
219
127
  min={min}
220
128
  onChange={handleInputChange}
@@ -228,59 +136,32 @@ const ImageNum = memo<ImageNumSelectorProps>(
228
136
  placeholder={`${min}-${max}`}
229
137
  ref={inputRef}
230
138
  size="small"
139
+ style={{ flex: 1 }}
231
140
  value={customCount}
232
141
  />
233
142
  <ActionIcon
234
- className={cx(styles.actionButton, styles.confirmButton)}
235
- disabled={!isValidInput()}
143
+ color="success"
144
+ disabled={!isValidInput}
236
145
  icon={Check}
237
146
  onClick={handleCustomConfirm}
238
147
  size="small"
148
+ variant="filled"
239
149
  />
240
- <ActionIcon
241
- className={cx(styles.actionButton, styles.cancelButton)}
242
- icon={X}
243
- onClick={handleCustomCancel}
244
- size="small"
245
- />
246
- </div>
150
+ <ActionIcon icon={X} onClick={handleCustomCancel} size="small" variant="filled" />
151
+ </Flexbox>
247
152
  );
248
153
  }
249
154
 
250
155
  return (
251
- <Flexbox className={styles.container} horizontal>
252
- {presetCounts.map((count) => (
253
- <button
254
- className={cx(styles.button, imageNum === count && styles.selectedButton)}
255
- disabled={disabled}
256
- key={count}
257
- onClick={() => handlePresetClick(count)}
258
- type="button"
259
- >
260
- {count}
261
- </button>
262
- ))}
263
-
264
- {isCustomValue ? (
265
- <button
266
- className={cx(styles.button, styles.selectedButton)}
267
- disabled={disabled}
268
- onClick={handleEditStart}
269
- type="button"
270
- >
271
- {imageNum}
272
- </button>
273
- ) : (
274
- <button
275
- className={styles.button}
276
- disabled={disabled}
277
- onClick={handleEditStart}
278
- type="button"
279
- >
280
- <Plus size={16} />
281
- </button>
282
- )}
283
- </Flexbox>
156
+ <Segmented
157
+ block
158
+ disabled={disabled}
159
+ onChange={handleChange}
160
+ options={options}
161
+ style={{ width: '100%' }}
162
+ value={isCustomValue ? imageNum : imageNum}
163
+ variant="filled"
164
+ />
284
165
  );
285
166
  },
286
167
  );
@@ -1,86 +1,41 @@
1
- import { Flexbox } from '@lobehub/ui';
2
- import { createStaticStyles, cx } from 'antd-style';
3
- import { memo, useCallback } from 'react';
1
+ import { Segmented } from '@lobehub/ui';
2
+ import { memo, useCallback, useMemo } from 'react';
4
3
  import { useTranslation } from 'react-i18next';
5
4
 
6
5
  import { useGenerationConfigParam } from '@/store/image/slices/generationConfig/hooks';
7
6
 
8
- const styles = createStaticStyles(({ css, cssVar }) => ({
9
- button: css`
10
- cursor: pointer;
11
-
12
- display: flex;
13
- align-items: center;
14
- justify-content: center;
15
-
16
- min-width: 60px;
17
- height: 32px;
18
- padding-block: 0;
19
- padding-inline: 16px;
20
- border: 1px solid ${cssVar.colorBorder};
21
- border-radius: ${cssVar.borderRadius}px;
22
-
23
- font-size: 14px;
24
- font-weight: 500;
25
- color: ${cssVar.colorText};
26
-
27
- background: ${cssVar.colorBgContainer};
28
-
29
- transition: all 0.2s ease;
30
-
31
- &:hover {
32
- border-color: ${cssVar.colorPrimary};
33
- background: ${cssVar.colorBgTextHover};
34
- }
35
- `,
36
-
37
- container: css`
38
- display: flex;
39
- gap: 8px;
40
- align-items: center;
41
- `,
42
-
43
- selectedButton: css`
44
- border-color: ${cssVar.colorPrimary};
45
- color: ${cssVar.colorPrimary};
46
- background: ${cssVar.colorPrimaryBg};
47
-
48
- &:hover {
49
- border-color: ${cssVar.colorPrimary};
50
- color: ${cssVar.colorPrimary};
51
- background: ${cssVar.colorPrimaryBgHover};
52
- }
53
- `,
54
- }));
55
-
56
7
  const ResolutionSelect = memo(() => {
57
8
  const { t } = useTranslation('image');
58
9
  const { value, setValue, enumValues } = useGenerationConfigParam('resolution');
59
10
 
60
- const handleClick = useCallback(
61
- (resolution: string) => {
62
- setValue(resolution);
11
+ const handleChange = useCallback(
12
+ (resolution: string | number) => {
13
+ setValue(String(resolution));
63
14
  },
64
15
  [setValue],
65
16
  );
66
17
 
67
- if (!enumValues || enumValues.length === 0) {
18
+ const options = useMemo(() => {
19
+ if (!enumValues || enumValues.length === 0) return [];
20
+ return enumValues.map((resolution) => ({
21
+ label: t(`config.resolution.options.${resolution}`, { defaultValue: resolution }),
22
+ value: resolution,
23
+ }));
24
+ }, [enumValues, t]);
25
+
26
+ if (options.length === 0) {
68
27
  return null;
69
28
  }
70
29
 
71
30
  return (
72
- <Flexbox className={styles.container} horizontal>
73
- {enumValues.map((resolution) => (
74
- <button
75
- className={cx(styles.button, value === resolution && styles.selectedButton)}
76
- key={resolution}
77
- onClick={() => handleClick(resolution)}
78
- type="button"
79
- >
80
- {t(`config.resolution.options.${resolution}`, { defaultValue: resolution })}
81
- </button>
82
- ))}
83
- </Flexbox>
31
+ <Segmented
32
+ block
33
+ onChange={handleChange}
34
+ options={options}
35
+ style={{ width: '100%' }}
36
+ value={value}
37
+ variant="filled"
38
+ />
84
39
  );
85
40
  });
86
41
 
@@ -254,4 +254,22 @@ export const mobileRoutes: RouteConfig[] = [
254
254
  path: '/onboarding',
255
255
  },
256
256
  ...BusinessMobileRoutesWithoutMainLayout,
257
+
258
+ // Share topic route (outside main layout)
259
+ {
260
+ children: [
261
+ {
262
+ element: dynamicElement(
263
+ () => import('../../share/t/[id]'),
264
+ 'Mobile > Share > Topic',
265
+ ),
266
+ path: ':id',
267
+ },
268
+ ],
269
+ element: dynamicElement(
270
+ () => import('../../share/t/[id]/_layout'),
271
+ 'Mobile > Share > Topic > Layout',
272
+ ),
273
+ path: '/share/t',
274
+ },
257
275
  ];
@@ -398,6 +398,24 @@ export const desktopRoutes: RouteConfig[] = [
398
398
  // Onboarding route (outside main layout)
399
399
 
400
400
  ...BusinessDesktopRoutesWithoutMainLayout,
401
+
402
+ // Share topic route (outside main layout)
403
+ {
404
+ children: [
405
+ {
406
+ element: dynamicElement(
407
+ () => import('../share/t/[id]'),
408
+ 'Desktop > Share > Topic',
409
+ ),
410
+ path: ':id',
411
+ },
412
+ ],
413
+ element: dynamicElement(
414
+ () => import('../share/t/[id]/_layout'),
415
+ 'Desktop > Share > Topic > Layout',
416
+ ),
417
+ path: '/share/t',
418
+ },
401
419
  ];
402
420
 
403
421
  // Desktop onboarding route (SPA-only)
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { Flexbox } from '@lobehub/ui';
4
+ import { memo, useCallback, useMemo } from 'react';
5
+
6
+ import { ChatList, ConversationProvider, MessageItem } from '@/features/Conversation';
7
+ import { useChatStore } from '@/store/chat';
8
+ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
9
+
10
+ interface SharedMessageListProps {
11
+ agentId: string | null;
12
+ groupId: string | null;
13
+ shareId: string;
14
+ topicId: string;
15
+ }
16
+
17
+ const SharedMessageList = memo<SharedMessageListProps>(({ agentId, groupId, shareId, topicId }) => {
18
+ const context = useMemo(
19
+ () => ({
20
+ agentId: agentId ?? '',
21
+ groupId: groupId ?? undefined,
22
+ topicId,
23
+ topicShareId: shareId,
24
+ }),
25
+ [agentId, groupId, shareId, topicId],
26
+ );
27
+
28
+ // Sync messages to chatStore for artifact selectors to work
29
+ const chatKey = useMemo(() => messageMapKey(context), [context]);
30
+ const replaceMessages = useChatStore((s) => s.replaceMessages);
31
+ const messages = useChatStore((s) => s.dbMessagesMap[chatKey]);
32
+
33
+ const itemContent = useCallback(
34
+ (index: number, id: string) => <MessageItem disableEditing id={id} index={index} key={id} />,
35
+ [],
36
+ );
37
+
38
+ return (
39
+ <ConversationProvider
40
+ context={context}
41
+ hasInitMessages={!!messages}
42
+ messages={messages}
43
+ onMessagesChange={(messages) => {
44
+ replaceMessages(messages, { context });
45
+ }}
46
+ >
47
+ <Flexbox flex={1}>
48
+ <ChatList disableActionsBar itemContent={itemContent} />
49
+ </Flexbox>
50
+ </ConversationProvider>
51
+ );
52
+ });
53
+
54
+ export default SharedMessageList;