@lobehub/chat 1.91.2 → 1.92.0
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/.eslintrc.js +2 -0
- package/CHANGELOG.md +74 -0
- package/changelog/v1.json +27 -0
- package/locales/ar/setting.json +1 -1
- package/locales/bg-BG/setting.json +1 -1
- package/locales/de-DE/setting.json +1 -1
- package/locales/en-US/setting.json +1 -1
- package/locales/es-ES/setting.json +1 -1
- package/locales/fa-IR/setting.json +1 -1
- package/locales/fr-FR/setting.json +1 -1
- package/locales/it-IT/setting.json +1 -1
- package/locales/ja-JP/setting.json +1 -1
- package/locales/ko-KR/setting.json +1 -1
- package/locales/nl-NL/setting.json +1 -1
- package/locales/pl-PL/setting.json +1 -1
- package/locales/pt-BR/setting.json +1 -1
- package/locales/ru-RU/setting.json +1 -1
- package/locales/tr-TR/setting.json +1 -1
- package/locales/vi-VN/setting.json +1 -1
- package/locales/zh-CN/setting.json +1 -1
- package/locales/zh-TW/setting.json +1 -1
- package/package.json +1 -1
- package/src/app/[variants]/(main)/profile/features/ClerkProfile.tsx +1 -4
- package/src/config/aiModels/modelscope.ts +4 -1
- package/src/config/aiModels/novita.ts +2 -0
- package/src/config/aiModels/openrouter.ts +2 -0
- package/src/config/aiModels/siliconcloud.ts +1 -0
- package/src/config/modelProviders/anthropic.ts +30 -11
- package/src/config/modelProviders/openai.ts +14 -0
- package/src/features/AgentSetting/AgentModal/index.tsx +3 -2
- package/src/features/ChatInput/ActionBar/Search/Controls.tsx +6 -2
- package/src/layout/AuthProvider/Clerk/useAppearance.ts +1 -4
- package/src/libs/model-runtime/utils/streams/vertex-ai.ts +12 -0
- package/src/locales/default/setting.ts +1 -1
- package/src/services/chat.ts +17 -9
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +23 -31
- package/src/store/user/slices/auth/selectors.test.ts +18 -0
- package/src/store/user/slices/auth/selectors.ts +1 -0
- package/src/utils/client/parserPlaceholder.test.ts +326 -0
- package/src/utils/client/parserPlaceholder.ts +190 -0
- package/src/app/[variants]/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/index.tsx +0 -0
package/src/services/chat.ts
CHANGED
@@ -41,6 +41,7 @@ import { createErrorResponse } from '@/utils/errorResponse';
|
|
41
41
|
import { FetchSSEOptions, fetchSSE, getMessageError } from '@/utils/fetch';
|
42
42
|
import { genToolCallingName } from '@/utils/toolCall';
|
43
43
|
import { createTraceHeader, getTraceId } from '@/utils/trace';
|
44
|
+
import { parsePlaceholderVariablesMessages } from '@/utils/client/parserPlaceholder';
|
44
45
|
|
45
46
|
import { createHeaderWithAuth, createPayloadWithKeyVaults } from './_auth';
|
46
47
|
import { API_ENDPOINTS } from './_url';
|
@@ -172,14 +173,18 @@ class ChatService {
|
|
172
173
|
|
173
174
|
// =================== 0. process search =================== //
|
174
175
|
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
175
|
-
|
176
|
+
const aiInfraStoreState = getAiInfraStoreState();
|
176
177
|
const enabledSearch = chatConfig.searchMode !== 'off';
|
178
|
+
const isProviderHasBuiltinSearch = aiProviderSelectors.isProviderHasBuiltinSearch(
|
179
|
+
payload.provider!,
|
180
|
+
)(aiInfraStoreState);
|
177
181
|
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
178
182
|
payload.model,
|
179
183
|
payload.provider!,
|
180
|
-
)(
|
184
|
+
)(aiInfraStoreState);
|
181
185
|
|
182
|
-
const useModelSearch =
|
186
|
+
const useModelSearch =
|
187
|
+
(isProviderHasBuiltinSearch || isModelHasBuiltinSearch) && chatConfig.useModelBuiltinSearch;
|
183
188
|
|
184
189
|
const useApplicationBuiltinSearchTool = enabledSearch && !useModelSearch;
|
185
190
|
|
@@ -189,11 +194,14 @@ class ChatService {
|
|
189
194
|
pluginIds.push(WebBrowsingManifest.identifier);
|
190
195
|
}
|
191
196
|
|
192
|
-
// ============ 1. preprocess
|
197
|
+
// ============ 1. preprocess placeholder variables ============ //
|
198
|
+
const parsedMessages = parsePlaceholderVariablesMessages(messages);
|
199
|
+
|
200
|
+
// ============ 2. preprocess messages ============ //
|
193
201
|
|
194
202
|
const oaiMessages = this.processMessages(
|
195
203
|
{
|
196
|
-
messages,
|
204
|
+
messages: parsedMessages,
|
197
205
|
model: payload.model,
|
198
206
|
provider: payload.provider!,
|
199
207
|
tools: pluginIds,
|
@@ -201,28 +209,28 @@ class ChatService {
|
|
201
209
|
options,
|
202
210
|
);
|
203
211
|
|
204
|
-
// ============
|
212
|
+
// ============ 3. preprocess tools ============ //
|
205
213
|
|
206
214
|
const tools = this.prepareTools(pluginIds, {
|
207
215
|
model: payload.model,
|
208
216
|
provider: payload.provider!,
|
209
217
|
});
|
210
218
|
|
211
|
-
// ============
|
219
|
+
// ============ 4. process extend params ============ //
|
212
220
|
|
213
221
|
let extendParams: Record<string, any> = {};
|
214
222
|
|
215
223
|
const isModelHasExtendParams = aiModelSelectors.isModelHasExtendParams(
|
216
224
|
payload.model,
|
217
225
|
payload.provider!,
|
218
|
-
)(
|
226
|
+
)(aiInfraStoreState);
|
219
227
|
|
220
228
|
// model
|
221
229
|
if (isModelHasExtendParams) {
|
222
230
|
const modelExtendParams = aiModelSelectors.modelExtendParams(
|
223
231
|
payload.model,
|
224
232
|
payload.provider!,
|
225
|
-
)(
|
233
|
+
)(aiInfraStoreState);
|
226
234
|
// if model has extended params, then we need to check if the model can use reasoning
|
227
235
|
|
228
236
|
if (modelExtendParams!.includes('enableReasoning')) {
|
@@ -1,7 +1,6 @@
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
2
2
|
// Disable the auto sort key eslint rule to make the code more logic and readable
|
3
3
|
import { produce } from 'immer';
|
4
|
-
import { template } from 'lodash-es';
|
5
4
|
import { StateCreator } from 'zustand/vanilla';
|
6
5
|
|
7
6
|
import { LOADING_FLAT, MESSAGE_CANCEL_FLAT } from '@/const/message';
|
@@ -13,7 +12,7 @@ import { messageService } from '@/services/message';
|
|
13
12
|
import { useAgentStore } from '@/store/agent';
|
14
13
|
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
15
14
|
import { getAgentStoreState } from '@/store/agent/store';
|
16
|
-
import { aiModelSelectors } from '@/store/aiInfra';
|
15
|
+
import { aiModelSelectors, aiProviderSelectors } from '@/store/aiInfra';
|
17
16
|
import { getAiInfraStoreState } from '@/store/aiInfra/store';
|
18
17
|
import { chatHelpers } from '@/store/chat/helpers';
|
19
18
|
import { ChatStore } from '@/store/chat/store';
|
@@ -299,7 +298,8 @@ export const generateAIChat: StateCreator<
|
|
299
298
|
// create a new array to avoid the original messages array change
|
300
299
|
const messages = [...originalMessages];
|
301
300
|
|
302
|
-
const
|
301
|
+
const agentStoreState = getAgentStoreState();
|
302
|
+
const { model, provider, chatConfig } = agentSelectors.currentAgentConfig(agentStoreState);
|
303
303
|
|
304
304
|
let fileChunks: MessageSemanticSearchChunk[] | undefined;
|
305
305
|
let ragQueryId;
|
@@ -323,7 +323,7 @@ export const generateAIChat: StateCreator<
|
|
323
323
|
chunks,
|
324
324
|
userQuery: lastMsg.content,
|
325
325
|
rewriteQuery,
|
326
|
-
knowledge: agentSelectors.currentEnabledKnowledge(
|
326
|
+
knowledge: agentSelectors.currentEnabledKnowledge(agentStoreState),
|
327
327
|
});
|
328
328
|
|
329
329
|
// 3. add the retrieve context messages to the messages history
|
@@ -355,14 +355,25 @@ export const generateAIChat: StateCreator<
|
|
355
355
|
if (!assistantId) return;
|
356
356
|
|
357
357
|
// 3. place a search with the search working model if this model is not support tool use
|
358
|
+
const aiInfraStoreState = getAiInfraStoreState();
|
358
359
|
const isModelSupportToolUse = aiModelSelectors.isModelSupportToolUse(
|
359
360
|
model,
|
360
361
|
provider!,
|
361
|
-
)(
|
362
|
-
const
|
362
|
+
)(aiInfraStoreState);
|
363
|
+
const isProviderHasBuiltinSearch = aiProviderSelectors.isProviderHasBuiltinSearch(provider!)(
|
364
|
+
aiInfraStoreState,
|
365
|
+
);
|
366
|
+
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
367
|
+
model,
|
368
|
+
provider!,
|
369
|
+
)(aiInfraStoreState);
|
370
|
+
const useModelBuiltinSearch = agentChatConfigSelectors.useModelBuiltinSearch(agentStoreState);
|
371
|
+
const useModelSearch =
|
372
|
+
(isProviderHasBuiltinSearch || isModelHasBuiltinSearch) && useModelBuiltinSearch;
|
373
|
+
const isAgentEnableSearch = agentChatConfigSelectors.isAgentEnableSearch(agentStoreState);
|
363
374
|
|
364
|
-
if (isAgentEnableSearch && !isModelSupportToolUse) {
|
365
|
-
const { model, provider } = agentChatConfigSelectors.searchFCModel(
|
375
|
+
if (isAgentEnableSearch && !useModelSearch && !isModelSupportToolUse) {
|
376
|
+
const { model, provider } = agentChatConfigSelectors.searchFCModel(agentStoreState);
|
366
377
|
|
367
378
|
let isToolsCalling = false;
|
368
379
|
let isError = false;
|
@@ -460,10 +471,10 @@ export const generateAIChat: StateCreator<
|
|
460
471
|
}
|
461
472
|
|
462
473
|
// 6. summary history if context messages is larger than historyCount
|
463
|
-
const historyCount = agentChatConfigSelectors.historyCount(
|
474
|
+
const historyCount = agentChatConfigSelectors.historyCount(agentStoreState);
|
464
475
|
|
465
476
|
if (
|
466
|
-
agentChatConfigSelectors.enableHistoryCount(
|
477
|
+
agentChatConfigSelectors.enableHistoryCount(agentStoreState) &&
|
467
478
|
chatConfig.enableCompressHistory &&
|
468
479
|
originalMessages.length > historyCount
|
469
480
|
) {
|
@@ -495,8 +506,6 @@ export const generateAIChat: StateCreator<
|
|
495
506
|
const agentConfig = agentSelectors.currentAgentConfig(getAgentStoreState());
|
496
507
|
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
497
508
|
|
498
|
-
const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\S\s]+?)}}/g });
|
499
|
-
|
500
509
|
// ================================== //
|
501
510
|
// messages uniformly preprocess //
|
502
511
|
// ================================== //
|
@@ -511,29 +520,12 @@ export const generateAIChat: StateCreator<
|
|
511
520
|
historyCount,
|
512
521
|
});
|
513
522
|
|
514
|
-
// 2.
|
515
|
-
preprocessMsgs = !chatConfig.inputTemplate
|
516
|
-
? preprocessMsgs
|
517
|
-
: preprocessMsgs.map((m) => {
|
518
|
-
if (m.role === 'user') {
|
519
|
-
try {
|
520
|
-
return { ...m, content: compiler({ text: m.content }) };
|
521
|
-
} catch (error) {
|
522
|
-
console.error(error);
|
523
|
-
|
524
|
-
return m;
|
525
|
-
}
|
526
|
-
}
|
527
|
-
|
528
|
-
return m;
|
529
|
-
});
|
530
|
-
|
531
|
-
// 3. add systemRole
|
523
|
+
// 2. add systemRole
|
532
524
|
if (agentConfig.systemRole) {
|
533
525
|
preprocessMsgs.unshift({ content: agentConfig.systemRole, role: 'system' } as ChatMessage);
|
534
526
|
}
|
535
527
|
|
536
|
-
//
|
528
|
+
// 3. handle max_tokens
|
537
529
|
agentConfig.params.max_tokens = chatConfig.enableMaxTokens
|
538
530
|
? agentConfig.params.max_tokens
|
539
531
|
: undefined;
|
@@ -33,6 +33,24 @@ afterEach(() => {
|
|
33
33
|
});
|
34
34
|
|
35
35
|
describe('userProfileSelectors', () => {
|
36
|
+
describe('fullName', () => {
|
37
|
+
it('should return user fullName if exist', () => {
|
38
|
+
const store: UserStore = {
|
39
|
+
user: { fullName: 'John Doe' },
|
40
|
+
} as UserStore;
|
41
|
+
|
42
|
+
expect(userProfileSelectors.fullName(store)).toBe('John Doe');
|
43
|
+
});
|
44
|
+
|
45
|
+
it('should return empty string if not exist', () => {
|
46
|
+
const store: UserStore = {
|
47
|
+
user: { fullName: undefined },
|
48
|
+
} as UserStore;
|
49
|
+
|
50
|
+
expect(userProfileSelectors.fullName(store)).toBe('');
|
51
|
+
});
|
52
|
+
});
|
53
|
+
|
36
54
|
describe('nickName', () => {
|
37
55
|
it('should return default nickname when auth is disabled and not desktop', () => {
|
38
56
|
enableAuth = false;
|
@@ -36,6 +36,7 @@ const username = (s: UserStore) => {
|
|
36
36
|
};
|
37
37
|
|
38
38
|
export const userProfileSelectors = {
|
39
|
+
fullName: (s: UserStore): string => s.user?.fullName || '',
|
39
40
|
nickName,
|
40
41
|
userAvatar: (s: UserStore): string => s.user?.avatar || '',
|
41
42
|
userId: (s: UserStore) => s.user?.id,
|
@@ -0,0 +1,326 @@
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
2
|
+
import { parsePlaceholderVariablesMessages, VARIABLE_GENERATORS } from './parserPlaceholder';
|
3
|
+
|
4
|
+
// Mock dependencies
|
5
|
+
vi.mock('@/utils/uuid', () => ({
|
6
|
+
uuid: () => 'mocked-uuid-12345'
|
7
|
+
}));
|
8
|
+
|
9
|
+
vi.mock('@/store/user', () => ({
|
10
|
+
useUserStore: {
|
11
|
+
getState: () => ({})
|
12
|
+
}
|
13
|
+
}));
|
14
|
+
|
15
|
+
vi.mock('@/store/user/selectors', () => ({
|
16
|
+
userProfileSelectors: {
|
17
|
+
nickName: () => 'Test User',
|
18
|
+
username: () => 'testuser',
|
19
|
+
fullName: () => 'Test Full Name'
|
20
|
+
}
|
21
|
+
}));
|
22
|
+
|
23
|
+
vi.mock('@/store/agent/store', () => ({
|
24
|
+
getAgentStoreState: () => ({})
|
25
|
+
}));
|
26
|
+
|
27
|
+
vi.mock('@/store/agent/selectors', () => ({
|
28
|
+
agentChatConfigSelectors: {
|
29
|
+
currentChatConfig: () => ({
|
30
|
+
inputTemplate: 'Hello {{username}}!'
|
31
|
+
})
|
32
|
+
}
|
33
|
+
}));
|
34
|
+
|
35
|
+
describe('parsePlaceholderVariablesMessages', () => {
|
36
|
+
beforeEach(() => {
|
37
|
+
// Mock Date for consistent testing
|
38
|
+
vi.useFakeTimers();
|
39
|
+
vi.setSystemTime(new Date('2025-06-06T06:06:06.666Z'));
|
40
|
+
|
41
|
+
// Mock Math.random for consistent random values
|
42
|
+
vi.spyOn(Math, 'random').mockReturnValue(0.5);
|
43
|
+
});
|
44
|
+
|
45
|
+
afterEach(() => {
|
46
|
+
vi.useRealTimers();
|
47
|
+
vi.restoreAllMocks();
|
48
|
+
});
|
49
|
+
|
50
|
+
describe('string content messages', () => {
|
51
|
+
it('should replace template variables in string content', () => {
|
52
|
+
const messages = [
|
53
|
+
{
|
54
|
+
id: '1',
|
55
|
+
content: 'Hello {{username}}, today is {{date}}'
|
56
|
+
}
|
57
|
+
];
|
58
|
+
|
59
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
60
|
+
|
61
|
+
expect(result[0].content).toContain('testuser');
|
62
|
+
expect(result[0].content).toContain(new Date().toLocaleDateString());
|
63
|
+
});
|
64
|
+
|
65
|
+
it('should handle multiple variables in one message', () => {
|
66
|
+
const messages = [
|
67
|
+
{
|
68
|
+
id: '1',
|
69
|
+
content: 'Time: {{time}}, Date: {{date}}, User: {{nickname}}'
|
70
|
+
}
|
71
|
+
];
|
72
|
+
|
73
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
74
|
+
|
75
|
+
expect(result[0].content).toContain('Test User');
|
76
|
+
expect(result[0].content).toMatch(/Time: .+, Date: .+, User: Test User/);
|
77
|
+
});
|
78
|
+
|
79
|
+
it('should preserve message structure when replacing variables', () => {
|
80
|
+
const messages = [
|
81
|
+
{
|
82
|
+
id: '1',
|
83
|
+
role: 'user',
|
84
|
+
content: 'Hello {{username}}'
|
85
|
+
}
|
86
|
+
];
|
87
|
+
|
88
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
89
|
+
|
90
|
+
expect(result[0]).toEqual({
|
91
|
+
id: '1',
|
92
|
+
role: 'user',
|
93
|
+
content: 'Hello testuser'
|
94
|
+
});
|
95
|
+
});
|
96
|
+
});
|
97
|
+
|
98
|
+
describe('array content messages', () => {
|
99
|
+
it('should replace variables in text type array elements', () => {
|
100
|
+
const messages = [
|
101
|
+
{
|
102
|
+
id: '1',
|
103
|
+
content: [
|
104
|
+
{
|
105
|
+
type: 'text',
|
106
|
+
text: 'Hello {{username}}'
|
107
|
+
},
|
108
|
+
{
|
109
|
+
type: 'image_url',
|
110
|
+
image_url: 'image.jpg'
|
111
|
+
}
|
112
|
+
]
|
113
|
+
}
|
114
|
+
];
|
115
|
+
|
116
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
117
|
+
|
118
|
+
expect(result[0].content[0].text).toBe('Hello testuser');
|
119
|
+
expect(result[0].content[1]).toEqual({
|
120
|
+
type: 'image_url',
|
121
|
+
image_url: 'image.jpg'
|
122
|
+
});
|
123
|
+
});
|
124
|
+
|
125
|
+
it('should handle multiple text elements with variables', () => {
|
126
|
+
const messages = [
|
127
|
+
{
|
128
|
+
id: '1',
|
129
|
+
content: [
|
130
|
+
{
|
131
|
+
type: 'text',
|
132
|
+
text: 'Date: {{date}}'
|
133
|
+
},
|
134
|
+
{
|
135
|
+
type: 'text',
|
136
|
+
text: 'Time: {{time}}'
|
137
|
+
},
|
138
|
+
{
|
139
|
+
type: 'image_url',
|
140
|
+
image_url: 'test.jpg'
|
141
|
+
}
|
142
|
+
]
|
143
|
+
}
|
144
|
+
];
|
145
|
+
|
146
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
147
|
+
|
148
|
+
expect(result[0].content[0].text).toContain(new Date().toLocaleDateString());
|
149
|
+
expect(result[0].content[1].text).toContain(new Date().toLocaleTimeString());
|
150
|
+
expect(result[0].content[2]).toEqual({
|
151
|
+
type: 'image_url',
|
152
|
+
image_url: 'test.jpg'
|
153
|
+
});
|
154
|
+
});
|
155
|
+
|
156
|
+
it('should preserve non-text array elements unchanged', () => {
|
157
|
+
const messages = [
|
158
|
+
{
|
159
|
+
id: '1',
|
160
|
+
content: [
|
161
|
+
{
|
162
|
+
type: 'image_url',
|
163
|
+
image_url: 'image.jpg',
|
164
|
+
},
|
165
|
+
{
|
166
|
+
type: 'image_url',
|
167
|
+
name: 'image2.jpg'
|
168
|
+
}
|
169
|
+
]
|
170
|
+
}
|
171
|
+
];
|
172
|
+
|
173
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
174
|
+
|
175
|
+
expect(result[0].content).toEqual([
|
176
|
+
{
|
177
|
+
type: 'image_url',
|
178
|
+
image_url: 'image.jpg'
|
179
|
+
},
|
180
|
+
{
|
181
|
+
type: 'image_url',
|
182
|
+
name: 'image2.jpg'
|
183
|
+
}
|
184
|
+
]);
|
185
|
+
});
|
186
|
+
});
|
187
|
+
|
188
|
+
describe('edge cases', () => {
|
189
|
+
it('should handle empty messages array', () => {
|
190
|
+
const result = parsePlaceholderVariablesMessages([]);
|
191
|
+
expect(result).toEqual([]);
|
192
|
+
});
|
193
|
+
|
194
|
+
it('should handle messages without content', () => {
|
195
|
+
const messages = [
|
196
|
+
{ id: '1' },
|
197
|
+
{ id: '2', content: null },
|
198
|
+
{ id: '3', content: undefined }
|
199
|
+
];
|
200
|
+
|
201
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
202
|
+
|
203
|
+
expect(result).toEqual([
|
204
|
+
{ id: '1' },
|
205
|
+
{ id: '2', content: null },
|
206
|
+
{ id: '3', content: undefined }
|
207
|
+
]);
|
208
|
+
});
|
209
|
+
|
210
|
+
it('should handle empty string content', () => {
|
211
|
+
const messages = [
|
212
|
+
{ id: '1', content: '' }
|
213
|
+
];
|
214
|
+
|
215
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
216
|
+
|
217
|
+
expect(result[0].content).toBe('');
|
218
|
+
});
|
219
|
+
|
220
|
+
it('should handle content without variables', () => {
|
221
|
+
const messages = [
|
222
|
+
{ id: '1', content: 'Hello world!' },
|
223
|
+
{
|
224
|
+
id: '2',
|
225
|
+
content: [
|
226
|
+
{ type: 'text', text: 'No variables here' },
|
227
|
+
{ type: 'image_url', image_url: 'test.jpg' }
|
228
|
+
]
|
229
|
+
}
|
230
|
+
];
|
231
|
+
|
232
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
233
|
+
|
234
|
+
expect(result[0].content).toBe('Hello world!');
|
235
|
+
expect(result[1].content[0].text).toBe('No variables here');
|
236
|
+
});
|
237
|
+
|
238
|
+
it('should handle unknown variable types', () => {
|
239
|
+
const messages = [
|
240
|
+
{ id: '1', content: 'Hello {{unknown_variable}}!' }
|
241
|
+
];
|
242
|
+
|
243
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
244
|
+
|
245
|
+
// Unknown variables should remain unchanged
|
246
|
+
expect(result[0].content).toBe('Hello {{unknown_variable}}!');
|
247
|
+
});
|
248
|
+
|
249
|
+
it('should handle nested variables (input_template)', () => {
|
250
|
+
const messages = [
|
251
|
+
{ id: '1', content: 'Template: {{input_template}}' }
|
252
|
+
];
|
253
|
+
|
254
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
255
|
+
|
256
|
+
// Should resolve nested variables in input_template
|
257
|
+
expect(result[0].content).toBe('Template: Hello testuser!');
|
258
|
+
});
|
259
|
+
});
|
260
|
+
|
261
|
+
describe('specific variable types', () => {
|
262
|
+
it('should handle time variables', () => {
|
263
|
+
const messages = [
|
264
|
+
{
|
265
|
+
id: '1',
|
266
|
+
content: 'Year: {{year}}, Month: {{month}}, Day: {{day}}'
|
267
|
+
}
|
268
|
+
];
|
269
|
+
|
270
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
271
|
+
|
272
|
+
expect(result[0].content).toContain('Year: 2025');
|
273
|
+
expect(result[0].content).toContain('Month: 06');
|
274
|
+
expect(result[0].content).toContain('Day: 06');
|
275
|
+
});
|
276
|
+
|
277
|
+
it('should handle random variables', () => {
|
278
|
+
const messages = [
|
279
|
+
{
|
280
|
+
id: '1',
|
281
|
+
content: 'Random: {{random}}, Bool: {{random_bool}}, UUID: {{uuid}}'
|
282
|
+
}
|
283
|
+
];
|
284
|
+
|
285
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
286
|
+
|
287
|
+
expect(result[0].content).toContain('Random: 500001'); // Math.random() * 1000000 + 1 with 0.5
|
288
|
+
expect(result[0].content).toContain('Bool: false'); // Math.random() > 0.5 with 0.5
|
289
|
+
expect(result[0].content).toContain('UUID: mocked-uuid-12345');
|
290
|
+
});
|
291
|
+
|
292
|
+
it('should handle user variables', () => {
|
293
|
+
const messages = [
|
294
|
+
{
|
295
|
+
id: '1',
|
296
|
+
content: 'User: {{username}}, Nickname: {{nickname}}'
|
297
|
+
}
|
298
|
+
];
|
299
|
+
|
300
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
301
|
+
|
302
|
+
expect(result[0].content).toBe('User: testuser, Nickname: Test User');
|
303
|
+
});
|
304
|
+
});
|
305
|
+
|
306
|
+
describe('multiple messages', () => {
|
307
|
+
it('should process multiple messages correctly', () => {
|
308
|
+
const messages = [
|
309
|
+
{ id: '1', content: 'Hello {{username}}' },
|
310
|
+
{
|
311
|
+
id: '2',
|
312
|
+
content: [
|
313
|
+
{ type: 'text', text: 'Today is {{date}}' }
|
314
|
+
]
|
315
|
+
},
|
316
|
+
{ id: '3', content: 'Time: {{time}}' }
|
317
|
+
];
|
318
|
+
|
319
|
+
const result = parsePlaceholderVariablesMessages(messages);
|
320
|
+
|
321
|
+
expect(result[0].content).toBe('Hello testuser');
|
322
|
+
expect(result[1].content[0].text).toContain(new Date().toLocaleDateString());
|
323
|
+
expect(result[2].content).toContain(new Date().toLocaleTimeString());
|
324
|
+
});
|
325
|
+
});
|
326
|
+
});
|