@lobehub/lobehub 2.0.0-next.40 → 2.0.0-next.42
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/locales/ar/chat.json +1 -0
- package/locales/bg-BG/chat.json +1 -0
- package/locales/de-DE/chat.json +1 -0
- package/locales/en-US/chat.json +1 -0
- package/locales/es-ES/chat.json +1 -0
- package/locales/fa-IR/chat.json +1 -0
- package/locales/fr-FR/chat.json +1 -0
- package/locales/it-IT/chat.json +1 -0
- package/locales/ja-JP/chat.json +1 -0
- package/locales/ko-KR/chat.json +1 -0
- package/locales/nl-NL/chat.json +1 -0
- package/locales/pl-PL/chat.json +1 -0
- package/locales/pt-BR/chat.json +1 -0
- package/locales/ru-RU/chat.json +1 -0
- package/locales/tr-TR/chat.json +1 -0
- package/locales/vi-VN/chat.json +1 -0
- package/locales/zh-CN/chat.json +1 -0
- package/locales/zh-TW/chat.json +1 -0
- package/package.json +1 -1
- package/packages/database/src/models/__tests__/messages/message.create.test.ts +549 -0
- package/packages/database/src/models/__tests__/messages/message.delete.test.ts +481 -0
- package/packages/database/src/models/__tests__/messages/message.query.test.ts +1187 -0
- package/packages/database/src/models/__tests__/messages/message.stats.test.ts +633 -0
- package/packages/database/src/models/__tests__/messages/message.update.test.ts +757 -0
- package/packages/database/src/models/message.ts +5 -55
- 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/server/services/message/index.ts +14 -4
- 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/packages/database/src/models/__tests__/message.test.ts +0 -2632
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { LOBE_CHAT_TRACE_HEADER, LOBE_CHAT_TRACE_ID } from '@lobechat/const';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { createTraceHeader, getTraceId, getTracePayload } from './trace';
|
|
5
|
+
|
|
6
|
+
describe('trace utilities', () => {
|
|
7
|
+
describe('getTracePayload', () => {
|
|
8
|
+
it('should extract and decode trace payload from request headers', () => {
|
|
9
|
+
const payload = {
|
|
10
|
+
traceId: '123-456-789',
|
|
11
|
+
sessionId: 'session-abc',
|
|
12
|
+
timestamp: 1234567890,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
16
|
+
const mockRequest = new Request('http://localhost', {
|
|
17
|
+
headers: {
|
|
18
|
+
[LOBE_CHAT_TRACE_HEADER]: encoded,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const result = getTracePayload(mockRequest);
|
|
23
|
+
|
|
24
|
+
expect(result).toEqual(payload);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return undefined when trace header is not present', () => {
|
|
28
|
+
const mockRequest = new Request('http://localhost');
|
|
29
|
+
|
|
30
|
+
const result = getTracePayload(mockRequest);
|
|
31
|
+
|
|
32
|
+
expect(result).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle empty trace header', () => {
|
|
36
|
+
const mockRequest = new Request('http://localhost', {
|
|
37
|
+
headers: {
|
|
38
|
+
[LOBE_CHAT_TRACE_HEADER]: '',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const result = getTracePayload(mockRequest);
|
|
43
|
+
|
|
44
|
+
expect(result).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should decode complex payload with nested objects', () => {
|
|
48
|
+
const payload = {
|
|
49
|
+
traceId: 'trace-123',
|
|
50
|
+
metadata: {
|
|
51
|
+
user: { id: 'user-1', role: 'admin' },
|
|
52
|
+
context: { env: 'production', region: 'us-west-2' },
|
|
53
|
+
},
|
|
54
|
+
tags: ['important', 'production'],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
58
|
+
const mockRequest = new Request('http://localhost', {
|
|
59
|
+
headers: {
|
|
60
|
+
[LOBE_CHAT_TRACE_HEADER]: encoded,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const result = getTracePayload(mockRequest);
|
|
65
|
+
|
|
66
|
+
expect(result).toEqual(payload);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should handle payload with special characters', () => {
|
|
70
|
+
const payload = {
|
|
71
|
+
traceId: 'trace-with-特殊字符-🔥',
|
|
72
|
+
message: 'Test with émojis 😀',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
76
|
+
const mockRequest = new Request('http://localhost', {
|
|
77
|
+
headers: {
|
|
78
|
+
[LOBE_CHAT_TRACE_HEADER]: encoded,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const result = getTracePayload(mockRequest);
|
|
83
|
+
|
|
84
|
+
expect(result).toEqual(payload);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle payload with null values', () => {
|
|
88
|
+
const payload = {
|
|
89
|
+
traceId: 'trace-123',
|
|
90
|
+
optionalField: null,
|
|
91
|
+
anotherField: undefined,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
95
|
+
const mockRequest = new Request('http://localhost', {
|
|
96
|
+
headers: {
|
|
97
|
+
[LOBE_CHAT_TRACE_HEADER]: encoded,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = getTracePayload(mockRequest);
|
|
102
|
+
|
|
103
|
+
// Note: undefined values are removed during JSON.stringify
|
|
104
|
+
expect(result).toEqual({
|
|
105
|
+
traceId: 'trace-123',
|
|
106
|
+
optionalField: null,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle numeric and boolean values in payload', () => {
|
|
111
|
+
const payload = {
|
|
112
|
+
traceId: 'trace-123',
|
|
113
|
+
count: 42,
|
|
114
|
+
isActive: true,
|
|
115
|
+
ratio: 3.14159,
|
|
116
|
+
isDisabled: false,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
120
|
+
const mockRequest = new Request('http://localhost', {
|
|
121
|
+
headers: {
|
|
122
|
+
[LOBE_CHAT_TRACE_HEADER]: encoded,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = getTracePayload(mockRequest);
|
|
127
|
+
|
|
128
|
+
expect(result).toEqual(payload);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('getTraceId', () => {
|
|
133
|
+
it('should extract trace ID from response headers', () => {
|
|
134
|
+
const traceId = 'trace-xyz-789';
|
|
135
|
+
const mockResponse = new Response(null, {
|
|
136
|
+
headers: {
|
|
137
|
+
[LOBE_CHAT_TRACE_ID]: traceId,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = getTraceId(mockResponse);
|
|
142
|
+
|
|
143
|
+
expect(result).toBe(traceId);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return null when trace ID header is not present', () => {
|
|
147
|
+
const mockResponse = new Response(null);
|
|
148
|
+
|
|
149
|
+
const result = getTraceId(mockResponse);
|
|
150
|
+
|
|
151
|
+
expect(result).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle empty trace ID', () => {
|
|
155
|
+
const mockResponse = new Response(null, {
|
|
156
|
+
headers: {
|
|
157
|
+
[LOBE_CHAT_TRACE_ID]: '',
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const result = getTraceId(mockResponse);
|
|
162
|
+
|
|
163
|
+
expect(result).toBe('');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle trace ID with special characters', () => {
|
|
167
|
+
const traceId = 'trace-123-abc-特殊-🔥';
|
|
168
|
+
const mockResponse = new Response(null, {
|
|
169
|
+
headers: {
|
|
170
|
+
[LOBE_CHAT_TRACE_ID]: traceId,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = getTraceId(mockResponse);
|
|
175
|
+
|
|
176
|
+
expect(result).toBe(traceId);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should handle UUID-formatted trace IDs', () => {
|
|
180
|
+
const traceId = '550e8400-e29b-41d4-a716-446655440000';
|
|
181
|
+
const mockResponse = new Response(null, {
|
|
182
|
+
headers: {
|
|
183
|
+
[LOBE_CHAT_TRACE_ID]: traceId,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const result = getTraceId(mockResponse);
|
|
188
|
+
|
|
189
|
+
expect(result).toBe(traceId);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('createTraceHeader', () => {
|
|
194
|
+
it('should create a base64-encoded trace header from payload', () => {
|
|
195
|
+
const payload = {
|
|
196
|
+
traceId: 'trace-123',
|
|
197
|
+
sessionId: 'session-456',
|
|
198
|
+
timestamp: 1234567890,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const result = createTraceHeader(payload);
|
|
202
|
+
|
|
203
|
+
expect(result).toHaveProperty(LOBE_CHAT_TRACE_HEADER);
|
|
204
|
+
expect(typeof result[LOBE_CHAT_TRACE_HEADER]).toBe('string');
|
|
205
|
+
|
|
206
|
+
// Verify it's valid base64
|
|
207
|
+
const decoded = Buffer.from(result[LOBE_CHAT_TRACE_HEADER], 'base64').toString('utf8');
|
|
208
|
+
expect(JSON.parse(decoded)).toEqual(payload);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should create header for empty payload object', () => {
|
|
212
|
+
const payload = {};
|
|
213
|
+
|
|
214
|
+
const result = createTraceHeader(payload);
|
|
215
|
+
|
|
216
|
+
expect(result).toHaveProperty(LOBE_CHAT_TRACE_HEADER);
|
|
217
|
+
|
|
218
|
+
const decoded = Buffer.from(result[LOBE_CHAT_TRACE_HEADER], 'base64').toString('utf8');
|
|
219
|
+
expect(JSON.parse(decoded)).toEqual({});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should handle payload with nested objects', () => {
|
|
223
|
+
const payload = {
|
|
224
|
+
traceId: 'trace-123',
|
|
225
|
+
metadata: {
|
|
226
|
+
user: { id: 'user-1', name: 'John' },
|
|
227
|
+
context: { env: 'prod' },
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const result = createTraceHeader(payload);
|
|
232
|
+
|
|
233
|
+
const decoded = Buffer.from(result[LOBE_CHAT_TRACE_HEADER], 'base64').toString('utf8');
|
|
234
|
+
expect(JSON.parse(decoded)).toEqual(payload);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should handle payload with arrays', () => {
|
|
238
|
+
const payload = {
|
|
239
|
+
traceId: 'trace-123',
|
|
240
|
+
tags: ['tag1', 'tag2', 'tag3'],
|
|
241
|
+
values: [1, 2, 3, 4, 5],
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const result = createTraceHeader(payload);
|
|
245
|
+
|
|
246
|
+
const decoded = Buffer.from(result[LOBE_CHAT_TRACE_HEADER], 'base64').toString('utf8');
|
|
247
|
+
expect(JSON.parse(decoded)).toEqual(payload);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should handle payload with Unicode characters', () => {
|
|
251
|
+
const payload = {
|
|
252
|
+
traceId: 'trace-特殊-🔥',
|
|
253
|
+
message: 'Hello 世界 😀',
|
|
254
|
+
description: 'Тест тест',
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const result = createTraceHeader(payload);
|
|
258
|
+
|
|
259
|
+
const decoded = Buffer.from(result[LOBE_CHAT_TRACE_HEADER], 'base64').toString('utf8');
|
|
260
|
+
expect(JSON.parse(decoded)).toEqual(payload);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should handle payload with null values', () => {
|
|
264
|
+
const payload = {
|
|
265
|
+
traceId: 'trace-123',
|
|
266
|
+
optionalField: null,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const result = createTraceHeader(payload);
|
|
270
|
+
|
|
271
|
+
const decoded = Buffer.from(result[LOBE_CHAT_TRACE_HEADER], 'base64').toString('utf8');
|
|
272
|
+
expect(JSON.parse(decoded)).toEqual(payload);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should handle payload with boolean and numeric values', () => {
|
|
276
|
+
const payload = {
|
|
277
|
+
traceId: 'trace-123',
|
|
278
|
+
count: 42,
|
|
279
|
+
isActive: true,
|
|
280
|
+
ratio: 3.14,
|
|
281
|
+
isDisabled: false,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const result = createTraceHeader(payload);
|
|
285
|
+
|
|
286
|
+
const decoded = Buffer.from(result[LOBE_CHAT_TRACE_HEADER], 'base64').toString('utf8');
|
|
287
|
+
expect(JSON.parse(decoded)).toEqual(payload);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should create header that works with getTracePayload', () => {
|
|
291
|
+
const payload = {
|
|
292
|
+
traceId: 'trace-round-trip',
|
|
293
|
+
sessionId: 'session-test',
|
|
294
|
+
timestamp: Date.now(),
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const header = createTraceHeader(payload);
|
|
298
|
+
const mockRequest = new Request('http://localhost', {
|
|
299
|
+
headers: header,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const extractedPayload = getTracePayload(mockRequest);
|
|
303
|
+
|
|
304
|
+
expect(extractedPayload).toEqual(payload);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('round-trip encoding and decoding', () => {
|
|
309
|
+
it('should correctly encode and decode simple payload', () => {
|
|
310
|
+
const originalPayload = {
|
|
311
|
+
traceId: 'test-123',
|
|
312
|
+
data: 'test-data',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const header = createTraceHeader(originalPayload);
|
|
316
|
+
const mockRequest = new Request('http://localhost', { headers: header });
|
|
317
|
+
const decodedPayload = getTracePayload(mockRequest);
|
|
318
|
+
|
|
319
|
+
expect(decodedPayload).toEqual(originalPayload);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should correctly encode and decode complex payload', () => {
|
|
323
|
+
const originalPayload = {
|
|
324
|
+
traceId: 'complex-trace',
|
|
325
|
+
metadata: {
|
|
326
|
+
user: { id: 'usr-123', roles: ['admin', 'user'] },
|
|
327
|
+
timestamps: { created: 1234567890, updated: 1234567900 },
|
|
328
|
+
},
|
|
329
|
+
tags: ['production', 'critical'],
|
|
330
|
+
metrics: { duration: 123.45, count: 10 },
|
|
331
|
+
flags: { enabled: true, debug: false },
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const header = createTraceHeader(originalPayload);
|
|
335
|
+
const mockRequest = new Request('http://localhost', { headers: header });
|
|
336
|
+
const decodedPayload = getTracePayload(mockRequest);
|
|
337
|
+
|
|
338
|
+
expect(decodedPayload).toEqual(originalPayload);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should preserve data types through encoding and decoding', () => {
|
|
342
|
+
const originalPayload = {
|
|
343
|
+
traceId: 'trace-types-test',
|
|
344
|
+
sessionId: 'session-123',
|
|
345
|
+
userId: 'user-456',
|
|
346
|
+
topicId: 'topic-789',
|
|
347
|
+
observationId: 'obs-abc',
|
|
348
|
+
enabled: true,
|
|
349
|
+
tags: ['tag1', 'tag2', 'tag3'],
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const header = createTraceHeader(originalPayload);
|
|
353
|
+
const mockRequest = new Request('http://localhost', { headers: header });
|
|
354
|
+
const decodedPayload = getTracePayload(mockRequest);
|
|
355
|
+
|
|
356
|
+
expect(decodedPayload).toEqual(originalPayload);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
@@ -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;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LobeChatDatabase } from '@lobechat/database';
|
|
2
|
-
import { CreateMessageParams, UpdateMessageParams } from '@lobechat/types';
|
|
2
|
+
import { CreateMessageParams, UIChatMessage, UpdateMessageParams } from '@lobechat/types';
|
|
3
3
|
|
|
4
4
|
import { MessageModel } from '@/database/models/message';
|
|
5
5
|
|
|
@@ -51,7 +51,9 @@ export class MessageService {
|
|
|
51
51
|
/**
|
|
52
52
|
* Query messages and return response with success status (used after mutations)
|
|
53
53
|
*/
|
|
54
|
-
private async queryWithSuccess(
|
|
54
|
+
private async queryWithSuccess(
|
|
55
|
+
options?: QueryOptions,
|
|
56
|
+
): Promise<{ messages?: UIChatMessage[], success: boolean; }> {
|
|
55
57
|
if (!options || (options.sessionId === undefined && options.topicId === undefined)) {
|
|
56
58
|
return { success: true };
|
|
57
59
|
}
|
|
@@ -139,7 +141,11 @@ export class MessageService {
|
|
|
139
141
|
* Update plugin state and return message list
|
|
140
142
|
* Pattern: update + conditional query
|
|
141
143
|
*/
|
|
142
|
-
async updatePluginState(
|
|
144
|
+
async updatePluginState(
|
|
145
|
+
id: string,
|
|
146
|
+
value: any,
|
|
147
|
+
options: QueryOptions,
|
|
148
|
+
): Promise<{ messages?: UIChatMessage[], success: boolean; }> {
|
|
143
149
|
await this.messageModel.updatePluginState(id, value);
|
|
144
150
|
return this.queryWithSuccess(options);
|
|
145
151
|
}
|
|
@@ -148,7 +154,11 @@ export class MessageService {
|
|
|
148
154
|
* Update message and return message list
|
|
149
155
|
* Pattern: update + conditional query
|
|
150
156
|
*/
|
|
151
|
-
async updateMessage(
|
|
157
|
+
async updateMessage(
|
|
158
|
+
id: string,
|
|
159
|
+
value: UpdateMessageParams,
|
|
160
|
+
options: QueryOptions,
|
|
161
|
+
): Promise<{ messages?: UIChatMessage[], success: boolean; }> {
|
|
152
162
|
await this.messageModel.update(id, value as any);
|
|
153
163
|
return this.queryWithSuccess(options);
|
|
154
164
|
}
|
|
@@ -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
|
}
|