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

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 (91) 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 +51 -0
  19. package/CLAUDE.md +1 -1
  20. package/GEMINI.md +1 -1
  21. package/changelog/v1.json +14 -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)/agent/features/Conversation/ThreadHydration.tsx +2 -1
  37. package/src/app/[variants]/(main)/agent/features/Portal/_layout/Mobile.tsx +3 -3
  38. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/GroupMember.tsx +3 -2
  39. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +26 -9
  40. package/src/app/[variants]/(main)/group/features/Conversation/ThreadHydration.tsx +2 -1
  41. package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +3 -3
  42. package/src/app/[variants]/(main)/group/profile/features/MemberProfile/AgentTool.tsx +4 -1
  43. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +1 -2
  44. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ImageNum.tsx +54 -173
  45. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx +22 -67
  46. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +18 -0
  47. package/src/app/[variants]/router/desktopRouter.config.tsx +18 -0
  48. package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +54 -0
  49. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +170 -0
  50. package/src/app/[variants]/share/t/[id]/features/Portal/index.tsx +66 -0
  51. package/src/app/[variants]/share/t/[id]/index.tsx +112 -0
  52. package/src/app/robots.tsx +1 -1
  53. package/src/business/client/BusinessMobileRoutes.tsx +1 -1
  54. package/src/features/Conversation/ChatList/index.tsx +17 -6
  55. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/index.tsx +8 -4
  56. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +15 -10
  57. package/src/features/Conversation/Messages/AssistantGroup/Tools.tsx +3 -1
  58. package/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx +3 -2
  59. package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +2 -2
  60. package/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +25 -26
  61. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +4 -2
  62. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +16 -12
  63. package/src/features/Conversation/Messages/Tool/index.tsx +20 -11
  64. package/src/features/Conversation/Messages/index.tsx +1 -1
  65. package/src/features/Conversation/store/slices/data/action.test.ts +42 -0
  66. package/src/features/Conversation/store/slices/data/action.ts +4 -2
  67. package/src/features/Portal/GroupThread/Header/index.tsx +2 -2
  68. package/src/features/Portal/MessageDetail/Body/index.tsx +3 -3
  69. package/src/features/Portal/components/Header.tsx +3 -3
  70. package/src/features/ProfileEditor/AgentTool.tsx +50 -19
  71. package/src/features/SharePopover/index.tsx +215 -0
  72. package/src/features/SharePopover/style.ts +10 -0
  73. package/src/hooks/useNavigateToAgent.ts +3 -3
  74. package/src/libs/next/proxy/define-config.ts +4 -1
  75. package/src/locales/default/chat.ts +26 -0
  76. package/src/proxy.ts +1 -0
  77. package/src/server/routers/lambda/__tests__/message.test.ts +152 -0
  78. package/src/server/routers/lambda/__tests__/share.test.ts +227 -0
  79. package/src/server/routers/lambda/__tests__/topic.test.ts +174 -0
  80. package/src/server/routers/lambda/index.ts +2 -0
  81. package/src/server/routers/lambda/message.ts +37 -4
  82. package/src/server/routers/lambda/share.ts +55 -0
  83. package/src/server/routers/lambda/topic.ts +45 -0
  84. package/src/services/message/index.ts +1 -0
  85. package/src/services/topic/index.ts +16 -0
  86. package/src/store/chat/slices/portal/action.test.ts +0 -41
  87. package/src/store/chat/slices/portal/action.ts +0 -25
  88. package/src/store/chat/slices/thread/action.test.ts +10 -6
  89. package/src/store/chat/slices/thread/action.ts +10 -3
  90. package/src/app/[variants]/(main)/group/features/Portal/features/Portal.tsx +0 -105
  91. package/src/app/[variants]/(main)/group/features/Portal/features/PortalPanel.tsx +0 -23
@@ -268,45 +268,4 @@ describe('chatDockSlice', () => {
268
268
  });
269
269
  });
270
270
 
271
- describe('toggleDock', () => {
272
- it('should toggle dock state when no argument is provided', () => {
273
- const { result } = renderHook(() => useChatStore());
274
-
275
- expect(result.current.showPortal).toBe(false);
276
-
277
- act(() => {
278
- result.current.togglePortal();
279
- });
280
-
281
- expect(result.current.showPortal).toBe(true);
282
-
283
- act(() => {
284
- result.current.togglePortal();
285
- });
286
-
287
- expect(result.current.showPortal).toBe(false);
288
- });
289
-
290
- it('should set dock state to the provided value', () => {
291
- const { result } = renderHook(() => useChatStore());
292
-
293
- act(() => {
294
- result.current.togglePortal(true);
295
- });
296
-
297
- expect(result.current.showPortal).toBe(true);
298
-
299
- act(() => {
300
- result.current.togglePortal(false);
301
- });
302
-
303
- expect(result.current.showPortal).toBe(false);
304
-
305
- act(() => {
306
- result.current.togglePortal(true);
307
- });
308
-
309
- expect(result.current.showPortal).toBe(true);
310
- });
311
- });
312
271
  });
@@ -28,7 +28,6 @@ export interface ChatPortalAction {
28
28
  pushPortalView: (view: PortalViewData) => void;
29
29
  replacePortalView: (view: PortalViewData) => void;
30
30
  toggleNotebook: (open?: boolean) => void;
31
- togglePortal: (open?: boolean) => void;
32
31
  }
33
32
 
34
33
  // Helper to get current view type from stack
@@ -222,28 +221,4 @@ pushPortalView: (view) => {
222
221
  get().closeNotebook();
223
222
  }
224
223
  },
225
-
226
- togglePortal: (open) => {
227
- const nextOpen = open === undefined ? !get().showPortal : open;
228
-
229
- if (!nextOpen) {
230
- // When closing, clear the stack
231
- set({ portalStack: [], showPortal: false }, false, 'togglePortal/close');
232
- } else {
233
- // When opening, if stack is empty, push Home view
234
- const { portalStack } = get();
235
- if (portalStack.length === 0) {
236
- set(
237
- {
238
- portalStack: [{ type: PortalViewType.Home }],
239
- showPortal: true,
240
- },
241
- false,
242
- 'togglePortal/openHome',
243
- );
244
- } else {
245
- set({ showPortal: true }, false, 'togglePortal/open');
246
- }
247
- }
248
- },
249
224
  });
@@ -141,7 +141,7 @@ describe('thread action', () => {
141
141
  describe('openThreadCreator', () => {
142
142
  it('should set thread creator state and open portal', () => {
143
143
  const { result } = renderHook(() => useChatStore());
144
- const togglePortalSpy = vi.spyOn(result.current, 'togglePortal');
144
+ const pushPortalViewSpy = vi.spyOn(result.current, 'pushPortalView');
145
145
 
146
146
  act(() => {
147
147
  result.current.openThreadCreator('message-id');
@@ -150,14 +150,14 @@ describe('thread action', () => {
150
150
  expect(result.current.threadStartMessageId).toBe('message-id');
151
151
  expect(result.current.portalThreadId).toBeUndefined();
152
152
  expect(result.current.startToForkThread).toBe(true);
153
- expect(togglePortalSpy).toHaveBeenCalledWith(true);
153
+ expect(pushPortalViewSpy).toHaveBeenCalledWith({ type: 'thread', startMessageId: 'message-id' });
154
154
  });
155
155
  });
156
156
 
157
157
  describe('openThreadInPortal', () => {
158
158
  it('should set portal thread state and open portal', () => {
159
159
  const { result } = renderHook(() => useChatStore());
160
- const togglePortalSpy = vi.spyOn(result.current, 'togglePortal');
160
+ const pushPortalViewSpy = vi.spyOn(result.current, 'pushPortalView');
161
161
 
162
162
  act(() => {
163
163
  result.current.openThreadInPortal('thread-id', 'source-message-id');
@@ -166,7 +166,11 @@ describe('thread action', () => {
166
166
  expect(result.current.portalThreadId).toBe('thread-id');
167
167
  expect(result.current.threadStartMessageId).toBe('source-message-id');
168
168
  expect(result.current.startToForkThread).toBe(false);
169
- expect(togglePortalSpy).toHaveBeenCalledWith(true);
169
+ expect(pushPortalViewSpy).toHaveBeenCalledWith({
170
+ type: 'thread',
171
+ threadId: 'thread-id',
172
+ startMessageId: 'source-message-id',
173
+ });
170
174
  });
171
175
  });
172
176
 
@@ -182,7 +186,7 @@ describe('thread action', () => {
182
186
  });
183
187
  });
184
188
 
185
- const togglePortalSpy = vi.spyOn(result.current, 'togglePortal');
189
+ const clearPortalStackSpy = vi.spyOn(result.current, 'clearPortalStack');
186
190
 
187
191
  act(() => {
188
192
  result.current.closeThreadPortal();
@@ -191,7 +195,7 @@ describe('thread action', () => {
191
195
  expect(result.current.portalThreadId).toBeUndefined();
192
196
  expect(result.current.threadStartMessageId).toBeUndefined();
193
197
  expect(result.current.startToForkThread).toBeUndefined();
194
- expect(togglePortalSpy).toHaveBeenCalledWith(false);
198
+ expect(clearPortalStackSpy).toHaveBeenCalled();
195
199
  });
196
200
  });
197
201
 
@@ -19,6 +19,7 @@ import { merge } from '@/utils/merge';
19
19
  import { setNamespace } from '@/utils/storeDebug';
20
20
 
21
21
  import { displayMessageSelectors } from '../message/selectors';
22
+ import { PortalViewType } from '../portal/initialState';
22
23
  import { type ThreadDispatch, threadReducer } from './reducer';
23
24
  import { genParentMessages } from './selectors';
24
25
 
@@ -88,7 +89,8 @@ export const chatThreadMessage: StateCreator<
88
89
  false,
89
90
  'openThreadCreator',
90
91
  );
91
- get().togglePortal(true);
92
+ // Push Thread view to portal stack instead of togglePortal
93
+ get().pushPortalView({ type: PortalViewType.Thread, startMessageId: messageId });
92
94
  },
93
95
  openThreadInPortal: (threadId, sourceMessageId) => {
94
96
  set(
@@ -96,7 +98,12 @@ export const chatThreadMessage: StateCreator<
96
98
  false,
97
99
  'openThreadInPortal',
98
100
  );
99
- get().togglePortal(true);
101
+ // Push Thread view to portal stack with threadId
102
+ get().pushPortalView({
103
+ type: PortalViewType.Thread,
104
+ threadId,
105
+ startMessageId: sourceMessageId ?? undefined,
106
+ });
100
107
  },
101
108
 
102
109
  closeThreadPortal: () => {
@@ -105,7 +112,7 @@ export const chatThreadMessage: StateCreator<
105
112
  false,
106
113
  'closeThreadPortal',
107
114
  );
108
- get().togglePortal(false);
115
+ get().clearPortalStack();
109
116
  },
110
117
  createThread: async ({ message, sourceMessageId, topicId, type }) => {
111
118
  set({ isCreatingThread: true }, false, n('creatingThread/start'));
@@ -1,105 +0,0 @@
1
- 'use client';
2
-
3
- import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } from '@lobehub/ui';
4
- import { Flexbox } from '@lobehub/ui';
5
- import { createStaticStyles, useResponsive } from 'antd-style';
6
- import isEqual from 'fast-deep-equal';
7
- import { Activity, type PropsWithChildren, memo, useState } from 'react';
8
-
9
- import {
10
- CHAT_PORTAL_MAX_WIDTH,
11
- CHAT_PORTAL_TOOL_UI_WIDTH,
12
- CHAT_PORTAL_WIDTH,
13
- } from '@/const/layoutTokens';
14
- import { useChatStore } from '@/store/chat';
15
- import { chatPortalSelectors, portalThreadSelectors } from '@/store/chat/selectors';
16
- import { useGlobalStore } from '@/store/global';
17
- import { systemStatusSelectors } from '@/store/global/selectors';
18
-
19
- const styles = createStaticStyles(({ css, cssVar }) => ({
20
- content: css`
21
- display: flex;
22
- flex-direction: column;
23
- height: 100% !important;
24
- `,
25
- drawer: css`
26
- z-index: 10;
27
- height: 100%;
28
- background: ${cssVar.colorBgContainer};
29
- `,
30
- panel: css`
31
- overflow: hidden;
32
- height: 100%;
33
- background: ${cssVar.colorBgContainer};
34
- `,
35
- }));
36
-
37
- const PortalPanel = memo(({ children }: PropsWithChildren) => {
38
- const { md = true } = useResponsive();
39
-
40
- const [showPortal, showToolUI, showArtifactUI, showThread] = useChatStore((s) => [
41
- chatPortalSelectors.showPortal(s),
42
- chatPortalSelectors.showPluginUI(s),
43
- chatPortalSelectors.showArtifactUI(s),
44
- portalThreadSelectors.showThread(s),
45
- ]);
46
-
47
- const [portalWidth, updateSystemStatus] = useGlobalStore((s) => [
48
- systemStatusSelectors.portalWidth(s),
49
- s.updateSystemStatus,
50
- ]);
51
-
52
- const [tmpWidth, setWidth] = useState(portalWidth);
53
- if (tmpWidth !== portalWidth) setWidth(portalWidth);
54
-
55
- const handleSizeChange: DraggablePanelProps['onSizeChange'] = (_, size) => {
56
- if (!size) return;
57
- const nextWidth = typeof size.width === 'string' ? Number.parseInt(size.width) : size.width;
58
- if (!nextWidth) return;
59
-
60
- if (isEqual(nextWidth, portalWidth)) return;
61
- setWidth(nextWidth);
62
- updateSystemStatus({ portalWidth: nextWidth });
63
- };
64
-
65
- return (
66
- <DraggablePanel
67
- className={styles.drawer}
68
- classNames={{
69
- content: styles.content,
70
- }}
71
- defaultSize={{ width: tmpWidth }}
72
- expand={showPortal}
73
- maxWidth={CHAT_PORTAL_MAX_WIDTH}
74
- minWidth={
75
- (showArtifactUI || showToolUI || showThread) && md
76
- ? CHAT_PORTAL_TOOL_UI_WIDTH
77
- : CHAT_PORTAL_WIDTH
78
- }
79
- mode={md ? 'fixed' : 'float'}
80
- onSizeChange={handleSizeChange}
81
- placement={'right'}
82
- showHandleWhenCollapsed={false}
83
- showHandleWideArea={false}
84
- size={{ height: '100%', width: portalWidth }}
85
- styles={{
86
- handle: { display: 'none' },
87
- }}
88
- >
89
- <DraggablePanelContainer
90
- style={{
91
- flex: 'none',
92
- height: '100%',
93
- maxHeight: '100vh',
94
- minWidth: CHAT_PORTAL_WIDTH,
95
- }}
96
- >
97
- <Activity mode={showPortal ? 'visible' : 'hidden'} name="GroupPortal">
98
- <Flexbox className={styles.panel}>{children}</Flexbox>
99
- </Activity>
100
- </DraggablePanelContainer>
101
- </DraggablePanel>
102
- );
103
- });
104
-
105
- export default PortalPanel;
@@ -1,23 +0,0 @@
1
- import { Suspense, memo } from 'react';
2
-
3
- import DesktopLayout from '@/app/[variants]/(main)/group/features/Portal/_layout/Desktop';
4
- import MobileLayout from '@/app/[variants]/(main)/group/features/Portal/_layout/Mobile';
5
- import Loading from '@/components/Loading/BrandTextLoading';
6
-
7
- interface PortalPanelProps {
8
- mobile?: boolean;
9
- }
10
-
11
- const PortalPanel = memo<PortalPanelProps>(({ mobile }) => {
12
- const Layout = mobile ? MobileLayout : DesktopLayout;
13
-
14
- return (
15
- <Suspense fallback={<Loading debugId="PortalPanel" />}>
16
- <Layout />
17
- </Suspense>
18
- );
19
- });
20
-
21
- PortalPanel.displayName = 'PortalPanel';
22
-
23
- export default PortalPanel;