@lobehub/lobehub 2.0.0-next.276 → 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.
- package/.cursor/rules/db-migrations.mdc +1 -1
- package/.cursor/rules/debug-usage.mdc +7 -5
- package/.cursor/rules/desktop-controller-tests.mdc +2 -1
- package/.cursor/rules/desktop-feature-implementation.mdc +9 -5
- package/.cursor/rules/desktop-local-tools-implement.mdc +67 -66
- package/.cursor/rules/desktop-menu-configuration.mdc +21 -9
- package/.cursor/rules/desktop-window-management.mdc +17 -2
- package/.cursor/rules/drizzle-schema-style-guide.mdc +6 -6
- package/.cursor/rules/hotkey.mdc +1 -0
- package/.cursor/rules/i18n.mdc +1 -0
- package/.cursor/rules/project-structure.mdc +16 -3
- package/.cursor/rules/react.mdc +17 -5
- package/.cursor/rules/recent-data-usage.mdc +2 -1
- package/.cursor/rules/testing-guide/testing-guide.mdc +262 -238
- package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +1 -1
- package/.cursor/rules/zustand-action-patterns.mdc +1 -1
- package/.cursor/rules/zustand-slice-organization.mdc +4 -4
- package/CHANGELOG.md +51 -0
- package/CLAUDE.md +1 -1
- package/GEMINI.md +1 -1
- package/changelog/v1.json +14 -0
- package/docs/development/database-schema.dbml +16 -0
- package/locales/en-US/chat.json +24 -0
- package/locales/en-US/setting.json +11 -0
- package/locales/zh-CN/chat.json +24 -0
- package/locales/zh-CN/setting.json +11 -0
- package/package.json +1 -1
- package/packages/builtin-tool-group-agent-builder/src/client/Inspector/BatchCreateAgents/index.tsx +2 -2
- package/packages/builtin-tool-group-agent-builder/src/client/Inspector/UpdateGroup/index.tsx +56 -56
- package/packages/builtin-tool-group-agent-builder/src/client/Render/BatchCreateAgents.tsx +3 -2
- package/packages/builtin-tool-group-agent-builder/src/executor.ts +2 -1
- package/packages/business/const/src/index.ts +3 -0
- package/packages/database/migrations/0069_add_topic_shares_table.sql +22 -0
- package/packages/database/migrations/meta/0069_snapshot.json +9704 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/models/__tests__/topicShare.test.ts +318 -0
- package/packages/database/src/models/topicShare.ts +177 -0
- package/packages/database/src/schemas/topic.ts +44 -2
- package/packages/types/src/agentCronJob/index.ts +19 -23
- package/packages/types/src/conversation.ts +5 -0
- package/packages/types/src/serverConfig.ts +1 -0
- package/packages/types/src/topic/topic.ts +46 -0
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/Actions.tsx +31 -0
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +10 -6
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/index.tsx +7 -11
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/useDropdownMenu.tsx +102 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/CronConfig.ts +179 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobContentEditor.tsx +111 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobHeader.tsx +45 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobSaveButton.tsx +31 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +213 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/index.tsx +186 -344
- package/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx +24 -9
- package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/index.tsx +42 -97
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +4 -20
- package/src/app/[variants]/(main)/community/features/UserAvatar/index.tsx +15 -5
- package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/AgentProfilePopup.tsx +1 -6
- package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +26 -9
- package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +1 -2
- package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ImageNum.tsx +54 -173
- package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx +22 -67
- package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +18 -0
- package/src/app/[variants]/router/desktopRouter.config.tsx +18 -0
- package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +54 -0
- package/src/app/[variants]/share/t/[id]/_layout/index.tsx +170 -0
- package/src/app/[variants]/share/t/[id]/features/Portal/index.tsx +66 -0
- package/src/app/[variants]/share/t/[id]/index.tsx +112 -0
- package/src/app/robots.tsx +1 -1
- package/src/business/client/BusinessMobileRoutes.tsx +1 -1
- package/src/features/Conversation/ChatList/index.tsx +12 -5
- package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/index.tsx +8 -4
- package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +15 -10
- package/src/features/Conversation/Messages/AssistantGroup/Tools.tsx +3 -1
- package/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx +3 -2
- package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +2 -2
- package/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +25 -26
- package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +4 -2
- package/src/features/Conversation/Messages/Tool/Tool/index.tsx +16 -12
- package/src/features/Conversation/Messages/Tool/index.tsx +20 -11
- package/src/features/Conversation/Messages/index.tsx +1 -1
- package/src/features/Conversation/store/slices/data/action.ts +2 -1
- package/src/features/SharePopover/index.tsx +215 -0
- package/src/features/SharePopover/style.ts +10 -0
- package/src/libs/next/proxy/define-config.ts +4 -1
- package/src/locales/default/chat.ts +26 -0
- package/src/proxy.ts +1 -0
- package/src/server/globalConfig/index.ts +1 -0
- package/src/server/routers/lambda/__tests__/message.test.ts +152 -0
- package/src/server/routers/lambda/__tests__/share.test.ts +227 -0
- package/src/server/routers/lambda/__tests__/topic.test.ts +174 -0
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/message.ts +37 -4
- package/src/server/routers/lambda/share.ts +55 -0
- package/src/server/routers/lambda/topic.ts +45 -0
- package/src/services/chatGroup/index.ts +1 -4
- package/src/services/message/index.ts +1 -0
- package/src/services/topic/index.ts +16 -0
- package/src/store/serverConfig/selectors.ts +1 -0
|
@@ -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
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Avatar, Flexbox } from '@lobehub/ui';
|
|
4
|
+
import { Typography } from 'antd';
|
|
5
|
+
import { createStyles, cssVar } from 'antd-style';
|
|
6
|
+
import NextLink from 'next/link';
|
|
7
|
+
import { PropsWithChildren, memo, useEffect, useMemo } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
import { Link, Outlet, useParams } from 'react-router-dom';
|
|
10
|
+
import useSWR from 'swr';
|
|
11
|
+
|
|
12
|
+
import { ProductLogo } from '@/components/Branding';
|
|
13
|
+
import { DEFAULT_AVATAR } from '@/const/meta';
|
|
14
|
+
import GroupAvatar from '@/features/GroupAvatar';
|
|
15
|
+
import UserAvatar from '@/features/User/UserAvatar';
|
|
16
|
+
import { lambdaClient } from '@/libs/trpc/client';
|
|
17
|
+
import { useAgentStore } from '@/store/agent';
|
|
18
|
+
import { useUserStore } from '@/store/user';
|
|
19
|
+
import { authSelectors } from '@/store/user/slices/auth/selectors';
|
|
20
|
+
|
|
21
|
+
import SharePortal from '../features/Portal';
|
|
22
|
+
|
|
23
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
24
|
+
container: css`
|
|
25
|
+
width: 100vw;
|
|
26
|
+
min-height: 100vh;
|
|
27
|
+
background: ${token.colorBgLayout};
|
|
28
|
+
`,
|
|
29
|
+
content: css`
|
|
30
|
+
flex: 1;
|
|
31
|
+
width: 100%;
|
|
32
|
+
padding-block: 24px;
|
|
33
|
+
padding-inline: 24px;
|
|
34
|
+
`,
|
|
35
|
+
footer: css`
|
|
36
|
+
padding-block: 16px;
|
|
37
|
+
padding-inline: 24px;
|
|
38
|
+
color: ${token.colorTextTertiary};
|
|
39
|
+
text-align: center;
|
|
40
|
+
`,
|
|
41
|
+
header: css`
|
|
42
|
+
height: 52px;
|
|
43
|
+
padding: 8px;
|
|
44
|
+
`,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const ShareTopicLayout = memo<PropsWithChildren>(({ children }) => {
|
|
48
|
+
const { styles } = useStyles();
|
|
49
|
+
const { t } = useTranslation('chat');
|
|
50
|
+
const { id } = useParams<{ id: string }>();
|
|
51
|
+
const dispatchAgentMap = useAgentStore((s) => s.internal_dispatchAgentMap);
|
|
52
|
+
const isLogin = useUserStore(authSelectors.isLogin);
|
|
53
|
+
|
|
54
|
+
const { data } = useSWR(
|
|
55
|
+
id ? ['shared-topic', id] : null,
|
|
56
|
+
() => lambdaClient.share.getSharedTopic.query({ shareId: id! }),
|
|
57
|
+
{ revalidateOnFocus: false },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Set agent meta to agentStore for avatar display
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (data?.agentId && data.agentMeta) {
|
|
63
|
+
const meta = {
|
|
64
|
+
avatar: data.agentMeta.avatar ?? undefined,
|
|
65
|
+
backgroundColor: data.agentMeta.backgroundColor ?? undefined,
|
|
66
|
+
title: data.agentMeta.title ?? undefined,
|
|
67
|
+
};
|
|
68
|
+
dispatchAgentMap(data.agentId, meta);
|
|
69
|
+
}
|
|
70
|
+
}, [data?.agentId, data?.agentMeta, dispatchAgentMap]);
|
|
71
|
+
|
|
72
|
+
const isGroup = !!data?.groupId;
|
|
73
|
+
const isInboxAgent = !isGroup && data?.agentMeta?.slug === 'inbox';
|
|
74
|
+
const agentOrGroupTitle =
|
|
75
|
+
data?.groupMeta?.title || (isInboxAgent ? 'LobeAI' : data?.agentMeta?.title);
|
|
76
|
+
const agentMarketIdentifier = data?.agentMeta?.marketIdentifier;
|
|
77
|
+
|
|
78
|
+
// Build group avatars for GroupAvatar component
|
|
79
|
+
const groupAvatars = useMemo(() => {
|
|
80
|
+
if (!isGroup || !data?.groupMeta?.members) return [];
|
|
81
|
+
return data.groupMeta.members.map((member) => ({
|
|
82
|
+
avatar: member.avatar || DEFAULT_AVATAR,
|
|
83
|
+
backgroundColor: member.backgroundColor || undefined,
|
|
84
|
+
}));
|
|
85
|
+
}, [isGroup, data?.groupMeta?.members]);
|
|
86
|
+
|
|
87
|
+
const renderAgentOrGroupAvatar = () => {
|
|
88
|
+
// For group: use GroupAvatar with members
|
|
89
|
+
if (isGroup && groupAvatars.length > 0) {
|
|
90
|
+
return <GroupAvatar avatars={groupAvatars} size={24} />;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// For inbox agent: skip avatar as it's the same as product icon
|
|
94
|
+
if (isInboxAgent) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// For agent: use single Avatar
|
|
99
|
+
if (data?.agentMeta?.avatar) {
|
|
100
|
+
return (
|
|
101
|
+
<Avatar
|
|
102
|
+
avatar={data.agentMeta.avatar}
|
|
103
|
+
background={data.agentMeta.backgroundColor || cssVar.colorFillTertiary}
|
|
104
|
+
shape="square"
|
|
105
|
+
size={24}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const renderAgentOrGroupTitle = () => {
|
|
114
|
+
if (!agentOrGroupTitle) return null;
|
|
115
|
+
|
|
116
|
+
// If agent has marketIdentifier, render as link to assistant page
|
|
117
|
+
if (agentMarketIdentifier && !data?.groupMeta?.title) {
|
|
118
|
+
return (
|
|
119
|
+
<a href={`/community/assistant/${agentMarketIdentifier}`} rel="noreferrer" target="_blank">
|
|
120
|
+
<Typography.Text ellipsis strong>
|
|
121
|
+
{agentOrGroupTitle}
|
|
122
|
+
</Typography.Text>
|
|
123
|
+
</a>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<Typography.Text ellipsis strong>
|
|
129
|
+
{agentOrGroupTitle}
|
|
130
|
+
</Typography.Text>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Flexbox className={styles.container}>
|
|
136
|
+
<Flexbox align="center" className={styles.header} gap={12} horizontal justify="space-between">
|
|
137
|
+
<Flexbox align="center" flex={1} gap={12} horizontal>
|
|
138
|
+
{isLogin ? (
|
|
139
|
+
<Link to="/">
|
|
140
|
+
<ProductLogo size={24} />
|
|
141
|
+
</Link>
|
|
142
|
+
) : (
|
|
143
|
+
<NextLink href="/login">
|
|
144
|
+
<ProductLogo size={24} />
|
|
145
|
+
</NextLink>
|
|
146
|
+
)}
|
|
147
|
+
{renderAgentOrGroupAvatar()}
|
|
148
|
+
{renderAgentOrGroupTitle()}
|
|
149
|
+
</Flexbox>
|
|
150
|
+
{data?.title && (
|
|
151
|
+
<Typography.Text ellipsis strong style={{ textAlign: 'center' }}>
|
|
152
|
+
{data.title}
|
|
153
|
+
</Typography.Text>
|
|
154
|
+
)}
|
|
155
|
+
<Flexbox align="center" flex={1} horizontal justify="flex-end">
|
|
156
|
+
{isLogin && <UserAvatar size={24} />}
|
|
157
|
+
</Flexbox>
|
|
158
|
+
</Flexbox>
|
|
159
|
+
<Flexbox className={styles.content} horizontal style={{ overflow: 'hidden' }}>
|
|
160
|
+
<Flexbox flex={1} style={{ overflow: 'hidden' }}>
|
|
161
|
+
{children ?? <Outlet />}
|
|
162
|
+
</Flexbox>
|
|
163
|
+
<SharePortal />
|
|
164
|
+
</Flexbox>
|
|
165
|
+
<Typography.Text className={styles.footer}>{t('sharePageDisclaimer')}</Typography.Text>
|
|
166
|
+
</Flexbox>
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
export default ShareTopicLayout;
|