@lobehub/chat 1.28.6 → 1.29.1
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/locales/ar/chat.json +1 -0
- package/locales/ar/setting.json +7 -2
- package/locales/bg-BG/chat.json +1 -0
- package/locales/bg-BG/setting.json +7 -2
- package/locales/de-DE/chat.json +1 -0
- package/locales/de-DE/setting.json +7 -2
- package/locales/en-US/chat.json +1 -0
- package/locales/en-US/setting.json +7 -2
- package/locales/es-ES/chat.json +1 -0
- package/locales/es-ES/setting.json +7 -2
- package/locales/fa-IR/chat.json +1 -0
- package/locales/fa-IR/setting.json +7 -2
- package/locales/fr-FR/chat.json +1 -0
- package/locales/fr-FR/setting.json +7 -2
- package/locales/it-IT/chat.json +1 -0
- package/locales/it-IT/setting.json +7 -2
- package/locales/ja-JP/chat.json +1 -0
- package/locales/ja-JP/setting.json +7 -2
- package/locales/ko-KR/chat.json +1 -0
- package/locales/ko-KR/setting.json +7 -2
- package/locales/nl-NL/chat.json +1 -0
- package/locales/nl-NL/setting.json +7 -2
- package/locales/pl-PL/chat.json +1 -0
- package/locales/pl-PL/setting.json +7 -2
- package/locales/pt-BR/chat.json +1 -0
- package/locales/pt-BR/setting.json +7 -2
- package/locales/ru-RU/chat.json +1 -0
- package/locales/ru-RU/setting.json +7 -2
- package/locales/tr-TR/chat.json +1 -0
- package/locales/tr-TR/setting.json +7 -2
- package/locales/vi-VN/chat.json +1 -0
- package/locales/vi-VN/setting.json +7 -2
- package/locales/zh-CN/chat.json +1 -0
- package/locales/zh-CN/setting.json +7 -2
- package/locales/zh-TW/chat.json +1 -0
- package/locales/zh-TW/setting.json +7 -2
- package/package.json +1 -1
- package/src/app/(main)/settings/system-agent/index.tsx +1 -0
- package/src/chains/__tests__/__snapshots__/summaryHistory.test.ts.snap +21 -0
- package/src/chains/__tests__/summaryHistory.test.ts +24 -0
- package/src/chains/summaryHistory.ts +19 -0
- package/src/const/settings/agent.ts +3 -1
- package/src/const/settings/systemAgent.ts +1 -0
- package/src/database/client/models/__tests__/session.test.ts +0 -1
- package/src/database/server/migrations/0011_add_topic_history_summary.sql +2 -0
- package/src/database/server/migrations/meta/0011_snapshot.json +3196 -0
- package/src/database/server/migrations/meta/_journal.json +7 -0
- package/src/database/server/models/__tests__/topic.test.ts +4 -0
- package/src/database/server/models/topic.ts +2 -0
- package/src/database/server/schemas/lobechat/topic.ts +3 -2
- package/src/features/AgentSetting/AgentChat/index.tsx +4 -18
- package/src/features/ChatInput/ActionBar/History.tsx +24 -21
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +22 -7
- package/src/features/Conversation/Actions/index.ts +2 -2
- package/src/features/Conversation/components/ChatItem/index.tsx +6 -6
- package/src/features/Conversation/components/History/index.tsx +71 -0
- package/src/features/Conversation/types/index.tsx +1 -1
- package/src/features/Portal/Artifacts/Body/Renderer/React/index.tsx +1 -0
- package/src/locales/default/chat.ts +1 -0
- package/src/locales/default/setting.ts +7 -2
- package/src/prompts/chatMessages/index.test.ts +94 -0
- package/src/prompts/chatMessages/index.ts +11 -0
- package/src/prompts/systemRole/index.ts +22 -0
- package/src/server/routers/lambda/topic.ts +7 -0
- package/src/services/__tests__/chat.test.ts +13 -61
- package/src/services/chat.ts +45 -11
- package/src/store/agent/slices/chat/__snapshots__/selectors.test.ts.snap +3 -1
- package/src/store/chat/helpers.ts +6 -2
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +21 -8
- package/src/store/chat/slices/aiChat/actions/helpers.ts +9 -0
- package/src/store/chat/slices/aiChat/actions/index.ts +3 -1
- package/src/store/chat/slices/aiChat/actions/memory.ts +52 -0
- package/src/store/chat/slices/message/selectors.ts +1 -3
- package/src/store/chat/slices/topic/selectors.ts +24 -12
- package/src/store/chat/slices/{enchance → translate}/action.test.ts +0 -13
- package/src/store/chat/slices/{enchance → translate}/action.ts +5 -24
- package/src/store/chat/slices/tts/action.test.ts +63 -0
- package/src/store/chat/slices/tts/action.ts +35 -0
- package/src/store/chat/store.ts +6 -3
- package/src/store/file/reducers/uploadFileList.test.ts +197 -0
- package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +3 -1
- package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
- package/src/types/agent/index.ts +2 -4
- package/src/types/topic.ts +13 -0
- package/src/types/user/settings/systemAgent.ts +1 -0
- package/src/utils/tokenizer/client.ts +1 -1
- /package/src/features/Conversation/components/{ChatItem → History}/HistoryDivider.tsx +0 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
2
|
+
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
|
+
|
4
|
+
import { messageService } from '@/services/message';
|
5
|
+
|
6
|
+
import { useChatStore } from '../../store';
|
7
|
+
|
8
|
+
// Mock messageService 和 chatService
|
9
|
+
vi.mock('@/services/message', () => ({
|
10
|
+
messageService: {
|
11
|
+
updateMessageTTS: vi.fn(),
|
12
|
+
updateMessageTranslate: vi.fn(),
|
13
|
+
updateMessage: vi.fn(),
|
14
|
+
},
|
15
|
+
}));
|
16
|
+
|
17
|
+
vi.mock('@/services/chat', () => ({
|
18
|
+
chatService: {
|
19
|
+
fetchPresetTaskResult: vi.fn(),
|
20
|
+
},
|
21
|
+
}));
|
22
|
+
|
23
|
+
vi.mock('@/chains/langDetect', () => ({
|
24
|
+
chainLangDetect: vi.fn(),
|
25
|
+
}));
|
26
|
+
|
27
|
+
vi.mock('@/chains/translate', () => ({
|
28
|
+
chainTranslate: vi.fn(),
|
29
|
+
}));
|
30
|
+
|
31
|
+
// Mock supportLocales
|
32
|
+
vi.mock('@/locales/options', () => ({
|
33
|
+
supportLocales: ['en-US', 'zh-CN'],
|
34
|
+
}));
|
35
|
+
|
36
|
+
beforeEach(() => {
|
37
|
+
vi.clearAllMocks();
|
38
|
+
useChatStore.setState(
|
39
|
+
{
|
40
|
+
// ... 初始状态
|
41
|
+
},
|
42
|
+
false,
|
43
|
+
);
|
44
|
+
});
|
45
|
+
|
46
|
+
afterEach(() => {
|
47
|
+
vi.restoreAllMocks();
|
48
|
+
});
|
49
|
+
|
50
|
+
describe('ChatEnhanceAction', () => {
|
51
|
+
describe('clearTTS', () => {
|
52
|
+
it('should clear TTS for a message and refresh messages', async () => {
|
53
|
+
const { result } = renderHook(() => useChatStore());
|
54
|
+
const messageId = 'message-id';
|
55
|
+
|
56
|
+
await act(async () => {
|
57
|
+
await result.current.clearTTS(messageId);
|
58
|
+
});
|
59
|
+
|
60
|
+
expect(messageService.updateMessageTTS).toHaveBeenCalledWith(messageId, false);
|
61
|
+
});
|
62
|
+
});
|
63
|
+
});
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import { StateCreator } from 'zustand/vanilla';
|
2
|
+
|
3
|
+
import { messageService } from '@/services/message';
|
4
|
+
import { ChatStore } from '@/store/chat/store';
|
5
|
+
import { ChatTTS } from '@/types/message';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* enhance chat action like translate,tts
|
9
|
+
*/
|
10
|
+
export interface ChatTTSAction {
|
11
|
+
clearTTS: (id: string) => Promise<void>;
|
12
|
+
ttsMessage: (
|
13
|
+
id: string,
|
14
|
+
state?: { contentMd5?: string; file?: string; voice?: string },
|
15
|
+
) => Promise<void>;
|
16
|
+
updateMessageTTS: (id: string, data: Partial<ChatTTS> | false) => Promise<void>;
|
17
|
+
}
|
18
|
+
|
19
|
+
export const chatTTS: StateCreator<ChatStore, [['zustand/devtools', never]], [], ChatTTSAction> = (
|
20
|
+
set,
|
21
|
+
get,
|
22
|
+
) => ({
|
23
|
+
clearTTS: async (id) => {
|
24
|
+
await get().updateMessageTTS(id, false);
|
25
|
+
},
|
26
|
+
|
27
|
+
ttsMessage: async (id, state = {}) => {
|
28
|
+
await get().updateMessageTTS(id, state);
|
29
|
+
},
|
30
|
+
|
31
|
+
updateMessageTTS: async (id, data) => {
|
32
|
+
await messageService.updateMessageTTS(id, data);
|
33
|
+
await get().refreshMessages();
|
34
|
+
},
|
35
|
+
});
|
package/src/store/chat/store.ts
CHANGED
@@ -8,19 +8,21 @@ import { createDevtools } from '../middleware/createDevtools';
|
|
8
8
|
import { ChatStoreState, initialState } from './initialState';
|
9
9
|
import { ChatBuiltinToolAction, chatToolSlice } from './slices/builtinTool/action';
|
10
10
|
import { ChatPortalAction, chatPortalSlice } from './slices/portal/action';
|
11
|
-
import {
|
11
|
+
import { ChatTranslateAction, chatTranslate } from './slices/translate/action';
|
12
12
|
import { ChatMessageAction, chatMessage } from './slices/message/action';
|
13
13
|
import { ChatPluginAction, chatPlugin } from './slices/plugin/action';
|
14
14
|
import { ShareAction, chatShare } from './slices/share/action';
|
15
15
|
import { ChatTopicAction, chatTopic } from './slices/topic/action';
|
16
16
|
import { chatAiChat, ChatAIChatAction } from './slices/aiChat/actions';
|
17
|
+
import { chatTTS, ChatTTSAction } from './slices/tts/action';
|
17
18
|
|
18
19
|
export interface ChatStoreAction
|
19
20
|
extends ChatMessageAction,
|
20
21
|
ChatAIChatAction,
|
21
22
|
ChatTopicAction,
|
22
23
|
ShareAction,
|
23
|
-
|
24
|
+
ChatTranslateAction,
|
25
|
+
ChatTTSAction,
|
24
26
|
ChatPluginAction,
|
25
27
|
ChatBuiltinToolAction,
|
26
28
|
ChatPortalAction {}
|
@@ -36,7 +38,8 @@ const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...
|
|
36
38
|
...chatAiChat(...params),
|
37
39
|
...chatTopic(...params),
|
38
40
|
...chatShare(...params),
|
39
|
-
...
|
41
|
+
...chatTranslate(...params),
|
42
|
+
...chatTTS(...params),
|
40
43
|
...chatToolSlice(...params),
|
41
44
|
...chatPlugin(...params),
|
42
45
|
...chatPortalSlice(...params),
|
@@ -0,0 +1,197 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
|
3
|
+
import { UploadFileItem } from '@/types/files/upload';
|
4
|
+
|
5
|
+
import { uploadFileListReducer } from './uploadFileList';
|
6
|
+
|
7
|
+
// 创建测试用的文件项
|
8
|
+
const createMockFile = (id: string): UploadFileItem => ({
|
9
|
+
id,
|
10
|
+
file: new File(['test'], 'test.txt'),
|
11
|
+
status: 'pending',
|
12
|
+
uploadState: { speed: 2, restTime: 2, progress: 10 },
|
13
|
+
});
|
14
|
+
|
15
|
+
describe('uploadFileListReducer', () => {
|
16
|
+
describe('addFile action', () => {
|
17
|
+
it('should add a file at the end by default', () => {
|
18
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
19
|
+
const newFile = createMockFile('2');
|
20
|
+
|
21
|
+
const result = uploadFileListReducer(initialState, {
|
22
|
+
type: 'addFile',
|
23
|
+
file: newFile,
|
24
|
+
});
|
25
|
+
|
26
|
+
expect(result).toHaveLength(2);
|
27
|
+
expect(result[1]).toEqual(newFile);
|
28
|
+
});
|
29
|
+
|
30
|
+
it('should add a file at the start when atStart is true', () => {
|
31
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
32
|
+
const newFile = createMockFile('2');
|
33
|
+
|
34
|
+
const result = uploadFileListReducer(initialState, {
|
35
|
+
type: 'addFile',
|
36
|
+
file: newFile,
|
37
|
+
atStart: true,
|
38
|
+
});
|
39
|
+
|
40
|
+
expect(result).toHaveLength(2);
|
41
|
+
expect(result[0]).toEqual(newFile);
|
42
|
+
});
|
43
|
+
});
|
44
|
+
|
45
|
+
describe('addFiles action', () => {
|
46
|
+
it('should add multiple files at the end by default', () => {
|
47
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
48
|
+
const newFiles = [createMockFile('2'), createMockFile('3')];
|
49
|
+
|
50
|
+
const result = uploadFileListReducer(initialState, {
|
51
|
+
type: 'addFiles',
|
52
|
+
files: newFiles,
|
53
|
+
});
|
54
|
+
|
55
|
+
expect(result).toHaveLength(3);
|
56
|
+
expect(result.slice(1)).toEqual(newFiles);
|
57
|
+
});
|
58
|
+
|
59
|
+
it('should add multiple files at the start when atStart is true', () => {
|
60
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
61
|
+
const newFiles = [createMockFile('2'), createMockFile('3')];
|
62
|
+
|
63
|
+
const result = uploadFileListReducer(initialState, {
|
64
|
+
type: 'addFiles',
|
65
|
+
files: newFiles,
|
66
|
+
atStart: true,
|
67
|
+
});
|
68
|
+
|
69
|
+
expect(result).toHaveLength(3);
|
70
|
+
expect(result.slice(0, 2)).toEqual(newFiles.reverse());
|
71
|
+
});
|
72
|
+
});
|
73
|
+
|
74
|
+
describe('updateFile action', () => {
|
75
|
+
it('should update file properties', () => {
|
76
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
77
|
+
const updateValue = { name: 'updated.txt' } as File;
|
78
|
+
|
79
|
+
const result = uploadFileListReducer(initialState, {
|
80
|
+
type: 'updateFile',
|
81
|
+
id: '1',
|
82
|
+
value: { file: updateValue },
|
83
|
+
});
|
84
|
+
|
85
|
+
expect(result[0].file.name).toBe('updated.txt');
|
86
|
+
});
|
87
|
+
|
88
|
+
it('should not modify state if file id is not found', () => {
|
89
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
90
|
+
|
91
|
+
const result = uploadFileListReducer(initialState, {
|
92
|
+
type: 'updateFile',
|
93
|
+
id: 'non-existent',
|
94
|
+
value: { file: { name: 'updated.txt' } as File },
|
95
|
+
});
|
96
|
+
|
97
|
+
expect(result).toEqual(initialState);
|
98
|
+
});
|
99
|
+
});
|
100
|
+
|
101
|
+
describe('updateFileStatus action', () => {
|
102
|
+
it('should update file status', () => {
|
103
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
104
|
+
|
105
|
+
const result = uploadFileListReducer(initialState, {
|
106
|
+
type: 'updateFileStatus',
|
107
|
+
id: '1',
|
108
|
+
status: 'success',
|
109
|
+
});
|
110
|
+
|
111
|
+
expect(result[0].status).toBe('success');
|
112
|
+
});
|
113
|
+
});
|
114
|
+
|
115
|
+
describe('updateFileUploadState action', () => {
|
116
|
+
it('should update file upload state', () => {
|
117
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
118
|
+
|
119
|
+
const result = uploadFileListReducer(initialState, {
|
120
|
+
type: 'updateFileUploadState',
|
121
|
+
id: '1',
|
122
|
+
uploadState: { progress: 12, restTime: 12, speed: 12 },
|
123
|
+
});
|
124
|
+
|
125
|
+
expect(result[0].uploadState).toEqual({
|
126
|
+
progress: 12,
|
127
|
+
restTime: 12,
|
128
|
+
speed: 12,
|
129
|
+
});
|
130
|
+
});
|
131
|
+
});
|
132
|
+
|
133
|
+
describe('removeFile action', () => {
|
134
|
+
it('should remove a file by id', () => {
|
135
|
+
const initialState: UploadFileItem[] = [createMockFile('1'), createMockFile('2')];
|
136
|
+
|
137
|
+
const result = uploadFileListReducer(initialState, {
|
138
|
+
type: 'removeFile',
|
139
|
+
id: '1',
|
140
|
+
});
|
141
|
+
|
142
|
+
expect(result).toHaveLength(1);
|
143
|
+
expect(result[0].id).toBe('2');
|
144
|
+
});
|
145
|
+
|
146
|
+
it('should not modify state if file id is not found', () => {
|
147
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
148
|
+
|
149
|
+
const result = uploadFileListReducer(initialState, {
|
150
|
+
type: 'removeFile',
|
151
|
+
id: 'non-existent',
|
152
|
+
});
|
153
|
+
|
154
|
+
expect(result).toEqual(initialState);
|
155
|
+
});
|
156
|
+
});
|
157
|
+
|
158
|
+
describe('removeFiles action', () => {
|
159
|
+
it('should remove multiple files by ids', () => {
|
160
|
+
const initialState: UploadFileItem[] = [
|
161
|
+
createMockFile('1'),
|
162
|
+
createMockFile('2'),
|
163
|
+
createMockFile('3'),
|
164
|
+
];
|
165
|
+
|
166
|
+
const result = uploadFileListReducer(initialState, {
|
167
|
+
type: 'removeFiles',
|
168
|
+
ids: ['1', '2'],
|
169
|
+
});
|
170
|
+
|
171
|
+
expect(result).toHaveLength(1);
|
172
|
+
expect(result[0].id).toBe('3');
|
173
|
+
});
|
174
|
+
|
175
|
+
it('should not modify state if no file ids are found', () => {
|
176
|
+
const initialState: UploadFileItem[] = [createMockFile('1')];
|
177
|
+
|
178
|
+
const result = uploadFileListReducer(initialState, {
|
179
|
+
type: 'removeFiles',
|
180
|
+
ids: ['non-existent'],
|
181
|
+
});
|
182
|
+
|
183
|
+
expect(result).toEqual(initialState);
|
184
|
+
});
|
185
|
+
});
|
186
|
+
|
187
|
+
describe('error handling', () => {
|
188
|
+
it('should throw error for unhandled action type', () => {
|
189
|
+
const initialState: UploadFileItem[] = [];
|
190
|
+
|
191
|
+
// @ts-expect-error Testing invalid action type
|
192
|
+
expect(() => uploadFileListReducer(initialState, { type: 'invalid' })).toThrow(
|
193
|
+
'Unhandled action type',
|
194
|
+
);
|
195
|
+
});
|
196
|
+
});
|
197
|
+
});
|
@@ -73,7 +73,9 @@ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.set
|
|
73
73
|
"autoCreateTopicThreshold": 2,
|
74
74
|
"displayMode": "chat",
|
75
75
|
"enableAutoCreateTopic": true,
|
76
|
-
"
|
76
|
+
"enableCompressHistory": true,
|
77
|
+
"enableHistoryCount": true,
|
78
|
+
"historyCount": 8,
|
77
79
|
},
|
78
80
|
"model": "gpt-3.5-turbo",
|
79
81
|
"params": {
|
@@ -11,9 +11,11 @@ const translation = (s: UserStore) => currentSystemAgent(s).translation;
|
|
11
11
|
const topic = (s: UserStore) => currentSystemAgent(s).topic;
|
12
12
|
const agentMeta = (s: UserStore) => currentSystemAgent(s).agentMeta;
|
13
13
|
const queryRewrite = (s: UserStore) => currentSystemAgent(s).queryRewrite;
|
14
|
+
const historyCompress = (s: UserStore) => currentSystemAgent(s).historyCompress;
|
14
15
|
|
15
16
|
export const systemAgentSelectors = {
|
16
17
|
agentMeta,
|
18
|
+
historyCompress,
|
17
19
|
queryRewrite,
|
18
20
|
topic,
|
19
21
|
translation,
|
package/src/types/agent/index.ts
CHANGED
@@ -56,13 +56,12 @@ export interface LobeAgentConfig {
|
|
56
56
|
|
57
57
|
export interface LobeAgentChatConfig {
|
58
58
|
autoCreateTopicThreshold: number;
|
59
|
-
compressThreshold?: number;
|
60
59
|
displayMode?: 'chat' | 'docs';
|
61
60
|
enableAutoCreateTopic?: boolean;
|
62
61
|
/**
|
63
62
|
* 历史消息长度压缩阈值
|
64
63
|
*/
|
65
|
-
|
64
|
+
enableCompressHistory?: boolean;
|
66
65
|
/**
|
67
66
|
* 开启历史记录条数
|
68
67
|
*/
|
@@ -78,10 +77,9 @@ export interface LobeAgentChatConfig {
|
|
78
77
|
|
79
78
|
export const AgentChatConfigSchema = z.object({
|
80
79
|
autoCreateTopicThreshold: z.number().default(2),
|
81
|
-
compressThreshold: z.number().optional(),
|
82
80
|
displayMode: z.enum(['chat', 'docs']).optional(),
|
83
81
|
enableAutoCreateTopic: z.boolean().optional(),
|
84
|
-
|
82
|
+
enableCompressHistory: z.boolean().optional(),
|
85
83
|
enableHistoryCount: z.boolean().optional(),
|
86
84
|
enableMaxTokens: z.boolean().optional(),
|
87
85
|
historyCount: z.number().optional(),
|
package/src/types/topic.ts
CHANGED
@@ -24,8 +24,21 @@ export interface GroupedTopic {
|
|
24
24
|
title?: string;
|
25
25
|
}
|
26
26
|
|
27
|
+
export interface ChatTopicMetadata {
|
28
|
+
model?: string;
|
29
|
+
provider?: string;
|
30
|
+
}
|
31
|
+
|
32
|
+
export interface ChatTopicSummary {
|
33
|
+
content: string;
|
34
|
+
model: string;
|
35
|
+
provider: string;
|
36
|
+
}
|
37
|
+
|
27
38
|
export interface ChatTopic extends Omit<BaseDataModel, 'meta'> {
|
28
39
|
favorite?: boolean;
|
40
|
+
historySummary?: string;
|
41
|
+
metadata?: ChatTopicMetadata;
|
29
42
|
sessionId?: string;
|
30
43
|
title: string;
|
31
44
|
}
|
@@ -11,6 +11,7 @@ export interface QueryRewriteSystemAgent extends Omit<SystemAgentItem, 'enabled'
|
|
11
11
|
|
12
12
|
export interface UserSystemAgentConfig {
|
13
13
|
agentMeta: SystemAgentItem;
|
14
|
+
historyCompress: SystemAgentItem;
|
14
15
|
queryRewrite: QueryRewriteSystemAgent;
|
15
16
|
topic: SystemAgentItem;
|
16
17
|
translation: SystemAgentItem;
|
File without changes
|