@lobehub/lobehub 2.0.0-next.41 → 2.0.0-next.43
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/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/database/src/models/__tests__/messages/message.create.test.ts +75 -18
- package/packages/database/src/models/__tests__/messages/message.query.test.ts +223 -6
- package/packages/database/src/models/__tests__/messages/message.stats.test.ts +56 -7
- package/packages/database/src/models/__tests__/messages/message.update.test.ts +45 -4
- package/packages/database/src/models/message.ts +3 -5
- package/packages/utils/src/clientIP.ts +6 -6
- package/packages/utils/src/compressImage.ts +3 -3
- package/packages/utils/src/fetch/fetchSSE.ts +15 -15
- package/packages/utils/src/format.ts +2 -2
- package/packages/utils/src/merge.ts +3 -3
- package/packages/utils/src/parseModels.ts +3 -3
- package/packages/utils/src/sanitizeUTF8.ts +4 -4
- package/packages/utils/src/toolManifest.ts +4 -4
- package/packages/utils/src/trace.test.ts +359 -0
- package/packages/utils/src/uriParser.ts +4 -4
- package/src/features/ChatItem/components/Title.tsx +20 -16
- package/src/features/Conversation/Messages/Assistant/index.tsx +3 -2
- package/src/features/Conversation/Messages/Group/index.tsx +10 -3
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +29 -3
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +8 -2
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +1 -4
- package/src/store/chat/slices/message/actions/optimisticUpdate.ts +1 -1
- package/src/store/global/initialState.ts +5 -0
- package/src/store/global/selectors/systemStatus.ts +3 -0
|
@@ -5,20 +5,20 @@ interface UriParserResult {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export const parseDataUri = (dataUri: string): UriParserResult => {
|
|
8
|
-
//
|
|
8
|
+
// Regular expression to match the entire Data URI structure
|
|
9
9
|
const dataUriMatch = dataUri.match(/^data:([^;]+);base64,(.+)$/);
|
|
10
10
|
|
|
11
11
|
if (dataUriMatch) {
|
|
12
|
-
//
|
|
12
|
+
// If it's a valid Data URI
|
|
13
13
|
return { base64: dataUriMatch[2], mimeType: dataUriMatch[1], type: 'base64' };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
17
|
new URL(dataUri);
|
|
18
|
-
//
|
|
18
|
+
// If it's a valid URL
|
|
19
19
|
return { base64: null, mimeType: null, type: 'url' };
|
|
20
20
|
} catch {
|
|
21
|
-
//
|
|
21
|
+
// Neither a Data URI nor a valid URL
|
|
22
22
|
return { base64: null, mimeType: null, type: null };
|
|
23
23
|
}
|
|
24
24
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
|
-
import { memo } from 'react';
|
|
2
|
+
import { CSSProperties, memo } from 'react';
|
|
3
3
|
import { Flexbox } from 'react-layout-kit';
|
|
4
4
|
|
|
5
5
|
import { useStyles } from '../style';
|
|
@@ -10,6 +10,7 @@ export interface TitleProps {
|
|
|
10
10
|
className?: string;
|
|
11
11
|
placement?: ChatItemProps['placement'];
|
|
12
12
|
showTitle?: ChatItemProps['showTitle'];
|
|
13
|
+
style?: CSSProperties;
|
|
13
14
|
time?: ChatItemProps['time'];
|
|
14
15
|
titleAddon?: ChatItemProps['titleAddon'];
|
|
15
16
|
}
|
|
@@ -27,21 +28,24 @@ const formatTime = (time: number): string => {
|
|
|
27
28
|
}
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
const Title = memo<TitleProps>(
|
|
31
|
-
|
|
31
|
+
const Title = memo<TitleProps>(
|
|
32
|
+
({ showTitle, placement, time, avatar, titleAddon, className, style }) => {
|
|
33
|
+
const { styles, cx } = useStyles({ placement, showTitle, time });
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
return (
|
|
36
|
+
<Flexbox
|
|
37
|
+
align={'center'}
|
|
38
|
+
className={cx(styles.name, className)}
|
|
39
|
+
direction={placement === 'left' ? 'horizontal' : 'horizontal-reverse'}
|
|
40
|
+
gap={4}
|
|
41
|
+
style={style}
|
|
42
|
+
>
|
|
43
|
+
{showTitle ? avatar.title || 'untitled' : undefined}
|
|
44
|
+
{showTitle ? titleAddon : undefined}
|
|
45
|
+
{time && <time>{formatTime(time)}</time>}
|
|
46
|
+
</Flexbox>
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
);
|
|
46
50
|
|
|
47
51
|
export default Title;
|
|
@@ -4,6 +4,7 @@ import { LOADING_FLAT } from '@lobechat/const';
|
|
|
4
4
|
import { UIChatMessage } from '@lobechat/types';
|
|
5
5
|
import { Tag } from '@lobehub/ui';
|
|
6
6
|
import { useResponsive } from 'antd-style';
|
|
7
|
+
import isEqual from 'fast-deep-equal';
|
|
7
8
|
import { ReactNode, memo, useCallback, useMemo } from 'react';
|
|
8
9
|
import { useTranslation } from 'react-i18next';
|
|
9
10
|
import { Flexbox } from 'react-layout-kit';
|
|
@@ -213,12 +214,12 @@ const AssistantMessage = memo<AssistantMessageProps>((props) => {
|
|
|
213
214
|
onClick={onAvatarClick}
|
|
214
215
|
placement={placement}
|
|
215
216
|
size={MOBILE_AVATAR_SIZE}
|
|
216
|
-
style={{ marginTop: 6 }}
|
|
217
217
|
/>
|
|
218
218
|
<Title
|
|
219
219
|
avatar={avatar}
|
|
220
220
|
placement={placement}
|
|
221
221
|
showTitle
|
|
222
|
+
style={{ marginBlockEnd: 0 }}
|
|
222
223
|
time={createdAt}
|
|
223
224
|
titleAddon={dmIndicator}
|
|
224
225
|
/>
|
|
@@ -274,6 +275,6 @@ const AssistantMessage = memo<AssistantMessageProps>((props) => {
|
|
|
274
275
|
{mobile && <BorderSpacing borderSpacing={MOBILE_AVATAR_SIZE} />}
|
|
275
276
|
</Flexbox>
|
|
276
277
|
);
|
|
277
|
-
});
|
|
278
|
+
}, isEqual);
|
|
278
279
|
|
|
279
280
|
export default AssistantMessage;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { UIChatMessage } from '@lobechat/types';
|
|
4
4
|
import { useResponsive } from 'antd-style';
|
|
5
|
+
import isEqual from 'fast-deep-equal';
|
|
5
6
|
import { memo, useCallback } from 'react';
|
|
6
7
|
import { Flexbox } from 'react-layout-kit';
|
|
7
8
|
|
|
@@ -49,6 +50,7 @@ const GroupMessage = memo<GroupMessageProps>((props) => {
|
|
|
49
50
|
provider,
|
|
50
51
|
} = props;
|
|
51
52
|
const avatar = meta;
|
|
53
|
+
console.log('render');
|
|
52
54
|
const { mobile } = useResponsive();
|
|
53
55
|
const placement = 'left';
|
|
54
56
|
const type = useAgentStore(agentChatConfigSelectors.displayMode);
|
|
@@ -96,9 +98,14 @@ const GroupMessage = memo<GroupMessageProps>((props) => {
|
|
|
96
98
|
onClick={onAvatarClick}
|
|
97
99
|
placement={placement}
|
|
98
100
|
size={MOBILE_AVATAR_SIZE}
|
|
99
|
-
style={{ marginTop: 6 }}
|
|
100
101
|
/>
|
|
101
|
-
<Title
|
|
102
|
+
<Title
|
|
103
|
+
avatar={avatar}
|
|
104
|
+
placement={placement}
|
|
105
|
+
showTitle
|
|
106
|
+
style={{ marginBlockEnd: 0 }}
|
|
107
|
+
time={createdAt}
|
|
108
|
+
/>
|
|
102
109
|
</Flexbox>
|
|
103
110
|
{isEditing && contentId ? (
|
|
104
111
|
<EditState content={lastAssistantMsg?.content} id={contentId} />
|
|
@@ -141,6 +148,6 @@ const GroupMessage = memo<GroupMessageProps>((props) => {
|
|
|
141
148
|
{mobile && <BorderSpacing borderSpacing={MOBILE_AVATAR_SIZE} />}
|
|
142
149
|
</Flexbox>
|
|
143
150
|
);
|
|
144
|
-
});
|
|
151
|
+
}, isEqual);
|
|
145
152
|
|
|
146
153
|
export default GroupMessage;
|
|
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
8
8
|
import { Center, Flexbox } from 'react-layout-kit';
|
|
9
9
|
|
|
10
10
|
import InfoTooltip from '@/components/InfoTooltip';
|
|
11
|
+
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
11
12
|
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
|
12
13
|
import { useGlobalStore } from '@/store/global';
|
|
13
14
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
@@ -27,6 +28,11 @@ interface TokenDetailProps {
|
|
|
27
28
|
const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
|
|
28
29
|
const { t } = useTranslation('chat');
|
|
29
30
|
const theme = useTheme();
|
|
31
|
+
const isMobile = useIsMobile();
|
|
32
|
+
|
|
33
|
+
// 使用 systemStatus 管理短格式显示状态
|
|
34
|
+
const isShortFormat = useGlobalStore(systemStatusSelectors.tokenDisplayFormatShort);
|
|
35
|
+
const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
|
|
30
36
|
|
|
31
37
|
const modelCard = useAiInfraStore(aiModelSelectors.getModelCard(model, provider));
|
|
32
38
|
const isShowCredit = useGlobalStore(systemStatusSelectors.isShowCredit) && !!modelCard?.pricing;
|
|
@@ -211,12 +217,32 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
|
|
|
211
217
|
</Flexbox>
|
|
212
218
|
}
|
|
213
219
|
placement={'top'}
|
|
214
|
-
trigger={['
|
|
220
|
+
trigger={isMobile ? ['click'] : ['hover']}
|
|
215
221
|
>
|
|
216
|
-
<Center
|
|
222
|
+
<Center
|
|
223
|
+
gap={2}
|
|
224
|
+
horizontal
|
|
225
|
+
onClick={(e) => {
|
|
226
|
+
// 移动端:让 Popover 处理点击事件
|
|
227
|
+
if (isMobile) return;
|
|
228
|
+
|
|
229
|
+
// 桌面端:阻止 Popover 并切换格式
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
e.stopPropagation();
|
|
232
|
+
updateSystemStatus({ tokenDisplayFormatShort: !isShortFormat });
|
|
233
|
+
}}
|
|
234
|
+
style={{ cursor: isMobile ? 'default' : 'pointer' }}
|
|
235
|
+
>
|
|
217
236
|
<Icon icon={isShowCredit ? BadgeCent : CoinsIcon} />
|
|
218
237
|
<AnimatedNumber
|
|
219
|
-
|
|
238
|
+
duration={1500}
|
|
239
|
+
formatter={(value) => {
|
|
240
|
+
const roundedValue = Math.round(value);
|
|
241
|
+
if (isShortFormat) {
|
|
242
|
+
return (formatShortenNumber(roundedValue) as string).toLowerCase?.();
|
|
243
|
+
}
|
|
244
|
+
return new Intl.NumberFormat('en-US').format(roundedValue);
|
|
245
|
+
}}
|
|
220
246
|
// Force remount when switching between token/credit to prevent unwanted animation
|
|
221
247
|
// See: https://github.com/lobehub/lobe-chat/pull/10098
|
|
222
248
|
key={isShowCredit ? 'credit' : 'token'}
|
|
@@ -166,7 +166,11 @@ export const conversationLifecycle: StateCreator<
|
|
|
166
166
|
topicId = data.topicId;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
get().replaceMessages(data.messages, {
|
|
169
|
+
get().replaceMessages(data.messages, {
|
|
170
|
+
sessionId: activeId,
|
|
171
|
+
topicId: topicId,
|
|
172
|
+
action: 'sendMessage/serverResponse',
|
|
173
|
+
});
|
|
170
174
|
|
|
171
175
|
if (data.isCreateNewTopic && data.topicId) {
|
|
172
176
|
await get().switchTopic(data.topicId, true);
|
|
@@ -250,7 +254,9 @@ export const conversationLifecycle: StateCreator<
|
|
|
250
254
|
.map((f) => f?.id)
|
|
251
255
|
.filter(Boolean) as string[];
|
|
252
256
|
|
|
253
|
-
|
|
257
|
+
if (userFiles.length > 0) {
|
|
258
|
+
await getAgentStoreState().addFilesToAgent(userFiles, false);
|
|
259
|
+
}
|
|
254
260
|
} catch (e) {
|
|
255
261
|
console.error(e);
|
|
256
262
|
} finally {
|
|
@@ -521,10 +521,7 @@ export const streamingExecutor: StateCreator<
|
|
|
521
521
|
// Handle completion and error events
|
|
522
522
|
for (const event of result.events) {
|
|
523
523
|
if (event.type === 'done') {
|
|
524
|
-
log('[internal_execAgentRuntime] Received done event
|
|
525
|
-
// Sync final state to database
|
|
526
|
-
const finalMessages = get().messagesMap[messageKey] || [];
|
|
527
|
-
get().replaceMessages(finalMessages);
|
|
524
|
+
log('[internal_execAgentRuntime] Received done event');
|
|
528
525
|
}
|
|
529
526
|
|
|
530
527
|
if (event.type === 'error') {
|
|
@@ -195,7 +195,7 @@ export const messageOptimisticUpdate: StateCreator<
|
|
|
195
195
|
);
|
|
196
196
|
|
|
197
197
|
if (result && result.success && result.messages) {
|
|
198
|
-
replaceMessages(result.messages);
|
|
198
|
+
replaceMessages(result.messages, { action: 'optimisticUpdateMessageContent' });
|
|
199
199
|
} else {
|
|
200
200
|
await refreshMessages();
|
|
201
201
|
}
|
|
@@ -99,6 +99,10 @@ export interface SystemStatus {
|
|
|
99
99
|
* theme mode
|
|
100
100
|
*/
|
|
101
101
|
themeMode?: ThemeMode;
|
|
102
|
+
/**
|
|
103
|
+
* 是否使用短格式显示 token
|
|
104
|
+
*/
|
|
105
|
+
tokenDisplayFormatShort?: boolean;
|
|
102
106
|
zenMode?: boolean;
|
|
103
107
|
}
|
|
104
108
|
|
|
@@ -151,6 +155,7 @@ export const INITIAL_STATUS = {
|
|
|
151
155
|
showSystemRole: false,
|
|
152
156
|
systemRoleExpandedMap: {},
|
|
153
157
|
themeMode: 'auto',
|
|
158
|
+
tokenDisplayFormatShort: true,
|
|
154
159
|
zenMode: false,
|
|
155
160
|
} satisfies SystemStatus;
|
|
156
161
|
|
|
@@ -66,6 +66,8 @@ const getAgentSystemRoleExpanded =
|
|
|
66
66
|
const disabledModelProvidersSortType = (s: GlobalState) =>
|
|
67
67
|
s.status.disabledModelProvidersSortType || 'default';
|
|
68
68
|
const disabledModelsSortType = (s: GlobalState) => s.status.disabledModelsSortType || 'default';
|
|
69
|
+
const tokenDisplayFormatShort = (s: GlobalState) =>
|
|
70
|
+
s.status.tokenDisplayFormatShort !== undefined ? s.status.tokenDisplayFormatShort : true;
|
|
69
71
|
|
|
70
72
|
export const systemStatusSelectors = {
|
|
71
73
|
chatInputHeight,
|
|
@@ -99,5 +101,6 @@ export const systemStatusSelectors = {
|
|
|
99
101
|
showSystemRole,
|
|
100
102
|
systemStatus,
|
|
101
103
|
themeMode,
|
|
104
|
+
tokenDisplayFormatShort,
|
|
102
105
|
wideScreen,
|
|
103
106
|
};
|