@rimori/client 1.4.0 → 1.4.4
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/README.md +77 -71
- package/dist/cli/scripts/init/dev-registration.d.ts +1 -1
- package/dist/cli/scripts/init/dev-registration.js +4 -4
- package/dist/cli/scripts/init/main.js +1 -1
- package/dist/cli/scripts/init/package-setup.d.ts +1 -1
- package/dist/cli/scripts/init/package-setup.js +3 -3
- package/dist/cli/scripts/init/router-transformer.js +19 -12
- package/dist/cli/scripts/init/vite-config.d.ts +2 -2
- package/dist/cli/scripts/init/vite-config.js +2 -2
- package/dist/cli/scripts/release/release-config-upload.js +9 -9
- package/dist/cli/scripts/release/release-db-update.d.ts +1 -1
- package/dist/cli/scripts/release/release-db-update.js +9 -9
- package/dist/cli/scripts/release/release-file-upload.js +1 -1
- package/dist/cli/scripts/release/release.js +2 -2
- package/dist/components/CRUDModal.d.ts +1 -1
- package/dist/components/CRUDModal.js +3 -3
- package/dist/components/MarkdownEditor.js +16 -16
- package/dist/components/Spinner.js +2 -2
- package/dist/components/ai/Assistant.js +7 -8
- package/dist/components/ai/Avatar.d.ts +2 -2
- package/dist/components/ai/Avatar.js +10 -5
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +5 -6
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +1 -1
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -2
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -2
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +4 -2
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +2 -3
- package/dist/components/audio/Playbutton.js +10 -7
- package/dist/components/components/ContextMenu.d.ts +1 -1
- package/dist/components/components/ContextMenu.js +19 -16
- package/dist/components.d.ts +10 -10
- package/dist/components.js +10 -10
- package/dist/core/controller/AIController.d.ts +2 -2
- package/dist/core/controller/AIController.js +12 -12
- package/dist/core/controller/ExerciseController.d.ts +2 -2
- package/dist/core/controller/ExerciseController.js +2 -2
- package/dist/core/controller/ObjectController.js +5 -5
- package/dist/core/controller/SettingsController.d.ts +22 -7
- package/dist/core/controller/SettingsController.js +73 -8
- package/dist/core/controller/SharedContentController.d.ts +3 -3
- package/dist/core/controller/SharedContentController.js +38 -20
- package/dist/core/controller/VoiceController.js +6 -4
- package/dist/core/core.d.ts +15 -15
- package/dist/core/core.js +7 -7
- package/dist/fromRimori/EventBus.js +23 -23
- package/dist/fromRimori/PluginTypes.d.ts +4 -4
- package/dist/hooks/UseChatHook.d.ts +3 -3
- package/dist/hooks/UseChatHook.js +9 -3
- package/dist/index.d.ts +10 -10
- package/dist/index.js +9 -9
- package/dist/plugin/AccomplishmentHandler.d.ts +5 -5
- package/dist/plugin/AccomplishmentHandler.js +31 -27
- package/dist/plugin/AudioController.d.ts +1 -1
- package/dist/plugin/AudioController.js +6 -6
- package/dist/plugin/Logger.js +15 -13
- package/dist/plugin/PluginController.d.ts +7 -1
- package/dist/plugin/PluginController.js +32 -27
- package/dist/plugin/RimoriClient.d.ts +17 -18
- package/dist/plugin/RimoriClient.js +31 -31
- package/dist/plugin/StandaloneClient.d.ts +1 -1
- package/dist/plugin/StandaloneClient.js +35 -16
- package/dist/plugin/ThemeSetter.js +4 -4
- package/dist/providers/PluginProvider.js +44 -14
- package/dist/utils/Language.js +57 -57
- package/dist/utils/PluginUtils.js +3 -3
- package/dist/utils/difficultyConverter.d.ts +1 -1
- package/dist/utils/difficultyConverter.js +1 -1
- package/dist/utils/endpoint.js +2 -2
- package/dist/worker/WorkerSetup.d.ts +1 -1
- package/dist/worker/WorkerSetup.js +6 -6
- package/example/docs/devdocs.md +50 -40
- package/example/docs/overview.md +1 -1
- package/example/docs/userdocs.md +4 -1
- package/example/rimori.config.ts +51 -49
- package/example/worker/vite.config.ts +3 -3
- package/example/worker/worker.ts +2 -2
- package/package.json +14 -8
- package/prettier.config.js +1 -1
- package/src/cli/scripts/init/dev-registration.ts +5 -8
- package/src/cli/scripts/init/env-setup.ts +1 -1
- package/src/cli/scripts/init/file-operations.ts +1 -1
- package/src/cli/scripts/init/html-cleaner.ts +2 -5
- package/src/cli/scripts/init/main.ts +16 -13
- package/src/cli/scripts/init/package-setup.ts +11 -15
- package/src/cli/scripts/init/router-transformer.ts +40 -37
- package/src/cli/scripts/init/tailwind-config.ts +17 -26
- package/src/cli/scripts/init/vite-config.ts +3 -3
- package/src/cli/scripts/release/release-config-upload.ts +11 -11
- package/src/cli/scripts/release/release-db-update.ts +12 -12
- package/src/cli/scripts/release/release-file-upload.ts +2 -2
- package/src/cli/scripts/release/release.ts +4 -4
- package/src/cli/types/DatabaseTypes.ts +2 -10
- package/src/components/CRUDModal.tsx +64 -48
- package/src/components/MarkdownEditor.tsx +58 -27
- package/src/components/Spinner.tsx +24 -17
- package/src/components/ai/Assistant.tsx +70 -70
- package/src/components/ai/Avatar.tsx +17 -14
- package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +63 -54
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +14 -5
- package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +75 -74
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +3 -4
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +109 -94
- package/src/components/ai/utils.ts +4 -4
- package/src/components/audio/Playbutton.tsx +101 -93
- package/src/components/components/ContextMenu.tsx +47 -35
- package/src/components.ts +10 -10
- package/src/core/controller/AIController.ts +29 -19
- package/src/core/controller/ExerciseController.ts +16 -23
- package/src/core/controller/ObjectController.ts +15 -10
- package/src/core/controller/SettingsController.ts +89 -16
- package/src/core/controller/SharedContentController.ts +80 -44
- package/src/core/controller/VoiceController.ts +10 -8
- package/src/core/core.ts +15 -16
- package/src/fromRimori/EventBus.ts +76 -47
- package/src/fromRimori/PluginTypes.ts +26 -17
- package/src/fromRimori/readme.md +2 -2
- package/src/hooks/UseChatHook.ts +25 -15
- package/src/index.ts +10 -10
- package/src/plugin/AccomplishmentHandler.ts +53 -35
- package/src/plugin/AudioController.ts +18 -12
- package/src/plugin/Logger.ts +28 -21
- package/src/plugin/PluginController.ts +60 -44
- package/src/plugin/RimoriClient.ts +102 -72
- package/src/plugin/StandaloneClient.ts +51 -24
- package/src/plugin/ThemeSetter.ts +5 -5
- package/src/providers/PluginProvider.tsx +90 -36
- package/src/style.scss +3 -3
- package/src/utils/Language.ts +58 -58
- package/src/utils/PluginUtils.ts +16 -20
- package/src/utils/difficultyConverter.ts +2 -2
- package/src/utils/endpoint.ts +3 -2
- package/src/worker/WorkerSetup.ts +8 -9
- package/tsconfig.json +2 -4
|
@@ -3,9 +3,9 @@ export type EventPayload = Record<string, any>;
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Interface representing a message sent through the EventBus
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Debug capabilities:
|
|
8
|
-
* - System-wide debugging: Send an event to "global.system.requestDebug"
|
|
8
|
+
* - System-wide debugging: Send an event to "global.system.requestDebug"
|
|
9
9
|
* Example: `EventBus.emit("yourPluginId", "global.system.requestDebug");`
|
|
10
10
|
*/
|
|
11
11
|
export interface EventBusMessage<T = EventPayload> {
|
|
@@ -40,7 +40,7 @@ export class EventBusHandler {
|
|
|
40
40
|
private responseResolvers: Map<number, (value: EventBusMessage<unknown>) => void> = new Map();
|
|
41
41
|
private static instance: EventBusHandler | null = null;
|
|
42
42
|
private debugEnabled: boolean = false;
|
|
43
|
-
private evName: string =
|
|
43
|
+
private evName: string = '';
|
|
44
44
|
|
|
45
45
|
private constructor() {
|
|
46
46
|
//private constructor
|
|
@@ -50,12 +50,14 @@ export class EventBusHandler {
|
|
|
50
50
|
if (!EventBusHandler.instance) {
|
|
51
51
|
EventBusHandler.instance = new EventBusHandler();
|
|
52
52
|
|
|
53
|
-
EventBusHandler.instance.on(
|
|
53
|
+
EventBusHandler.instance.on('global.system.requestDebug', () => {
|
|
54
54
|
EventBusHandler.instance!.debugEnabled = true;
|
|
55
|
-
console.log(
|
|
55
|
+
console.log(
|
|
56
|
+
`[${EventBusHandler.instance!.evName}] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`,
|
|
57
|
+
);
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
|
-
if (name && EventBusHandler.instance.evName ===
|
|
60
|
+
if (name && EventBusHandler.instance.evName === '') {
|
|
59
61
|
EventBusHandler.instance.evName = name;
|
|
60
62
|
}
|
|
61
63
|
return EventBusHandler.instance;
|
|
@@ -80,9 +82,9 @@ export class EventBusHandler {
|
|
|
80
82
|
* @param topic - The topic of the event.
|
|
81
83
|
* @param data - The data of the event.
|
|
82
84
|
* @param eventId - The event id of the event.
|
|
83
|
-
*
|
|
85
|
+
*
|
|
84
86
|
* The topic format is: **pluginId.area.action**
|
|
85
|
-
*
|
|
87
|
+
*
|
|
86
88
|
* Example topics:
|
|
87
89
|
* - pl1234.card.requestHard
|
|
88
90
|
* - pl1234.card.requestNew
|
|
@@ -96,7 +98,13 @@ export class EventBusHandler {
|
|
|
96
98
|
this.emitInternal(sender, topic, data || {}, eventId);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
private emitInternal(
|
|
101
|
+
private emitInternal(
|
|
102
|
+
sender: string,
|
|
103
|
+
topic: string,
|
|
104
|
+
data: EventPayload,
|
|
105
|
+
eventId?: number,
|
|
106
|
+
skipResponseTrigger = false,
|
|
107
|
+
): void {
|
|
100
108
|
if (!this.validateTopic(topic)) {
|
|
101
109
|
this.logAndThrowError(false, `Invalid topic: ` + topic);
|
|
102
110
|
return;
|
|
@@ -105,7 +113,7 @@ export class EventBusHandler {
|
|
|
105
113
|
const event = this.createEvent(sender, topic, data, eventId);
|
|
106
114
|
|
|
107
115
|
const handlers = this.getMatchingHandlers(event.topic);
|
|
108
|
-
handlers.forEach(handler => {
|
|
116
|
+
handlers.forEach((handler) => {
|
|
109
117
|
if (handler.ignoreSender && handler.ignoreSender.includes(sender)) {
|
|
110
118
|
// console.log("ignore event as its in the ignoreSender list", { event, ignoreList: handler.ignoreSender });
|
|
111
119
|
return;
|
|
@@ -132,8 +140,12 @@ export class EventBusHandler {
|
|
|
132
140
|
* @param ignoreSender - The senders to ignore.
|
|
133
141
|
* @returns An EventListener object containing an off() method to unsubscribe the listeners.
|
|
134
142
|
*/
|
|
135
|
-
public on<T = EventPayload>(
|
|
136
|
-
|
|
143
|
+
public on<T = EventPayload>(
|
|
144
|
+
topics: string | string[],
|
|
145
|
+
handler: EventHandler<T>,
|
|
146
|
+
ignoreSender: string[] = [],
|
|
147
|
+
): EventListener {
|
|
148
|
+
const ids = this.toArray(topics).map((topic) => {
|
|
137
149
|
this.logIfDebug(`Subscribing to ` + topic, { ignoreSender });
|
|
138
150
|
if (!this.validateTopic(topic)) {
|
|
139
151
|
this.logAndThrowError(true, `Invalid topic: ` + topic);
|
|
@@ -154,7 +166,7 @@ export class EventBusHandler {
|
|
|
154
166
|
});
|
|
155
167
|
|
|
156
168
|
return {
|
|
157
|
-
off: () => this.off(ids)
|
|
169
|
+
off: () => this.off(ids),
|
|
158
170
|
};
|
|
159
171
|
}
|
|
160
172
|
|
|
@@ -165,33 +177,41 @@ export class EventBusHandler {
|
|
|
165
177
|
* @param handler - The handler to be called when the event is received. The handler returns the data to be emitted. Can be a static object or a function.
|
|
166
178
|
* @returns An EventListener object containing an off() method to unsubscribe the listeners.
|
|
167
179
|
*/
|
|
168
|
-
public respond(
|
|
180
|
+
public respond(
|
|
181
|
+
sender: string,
|
|
182
|
+
topic: string | string[],
|
|
183
|
+
handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>),
|
|
184
|
+
): EventListener {
|
|
169
185
|
const topics = Array.isArray(topic) ? topic : [topic];
|
|
170
|
-
const listeners = topics.map(topic => {
|
|
186
|
+
const listeners = topics.map((topic) => {
|
|
171
187
|
const blackListedEventIds: number[] = [];
|
|
172
188
|
//To allow event communication inside the same plugin the sender needs to be ignored but the events still need to be checked for the same event just reaching the subscriber to prevent infinite loops
|
|
173
|
-
const finalIgnoreSender = !topic.startsWith(
|
|
174
|
-
|
|
175
|
-
const listener = this.on(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
blackListedEventIds.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
const finalIgnoreSender = !topic.startsWith('self.') ? [sender] : [];
|
|
190
|
+
|
|
191
|
+
const listener = this.on(
|
|
192
|
+
topic,
|
|
193
|
+
async (data: EventBusMessage) => {
|
|
194
|
+
if (blackListedEventIds.includes(data.eventId)) {
|
|
195
|
+
// console.log("BLACKLISTED EVENT ID", data.eventId);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
blackListedEventIds.push(data.eventId);
|
|
199
|
+
if (blackListedEventIds.length > 20) {
|
|
200
|
+
blackListedEventIds.shift();
|
|
201
|
+
}
|
|
202
|
+
const response = typeof handler === 'function' ? await handler(data) : handler;
|
|
203
|
+
this.emit(sender, topic, response, data.eventId);
|
|
204
|
+
},
|
|
205
|
+
finalIgnoreSender,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
this.logIfDebug(`Added respond listener ` + sender + ' to topic ' + topic, { listener, sender });
|
|
189
209
|
return {
|
|
190
|
-
off: () => listener.off()
|
|
210
|
+
off: () => listener.off(),
|
|
191
211
|
};
|
|
192
212
|
});
|
|
193
213
|
return {
|
|
194
|
-
off: () => listeners.forEach(listener => listener.off())
|
|
214
|
+
off: () => listeners.forEach((listener) => listener.off()),
|
|
195
215
|
};
|
|
196
216
|
}
|
|
197
217
|
|
|
@@ -221,12 +241,12 @@ export class EventBusHandler {
|
|
|
221
241
|
* @param listenerIds - The ids of the listeners to unsubscribe from.
|
|
222
242
|
*/
|
|
223
243
|
private off(listenerIds: string | string[]): void {
|
|
224
|
-
this.toArray(listenerIds).forEach(fullId => {
|
|
244
|
+
this.toArray(listenerIds).forEach((fullId) => {
|
|
225
245
|
const { topic, id } = JSON.parse(atob(fullId));
|
|
226
246
|
|
|
227
247
|
const listeners = this.listeners.get(topic) || new Set();
|
|
228
248
|
|
|
229
|
-
listeners.forEach(listener => {
|
|
249
|
+
listeners.forEach((listener) => {
|
|
230
250
|
if (listener.id === Number(id)) {
|
|
231
251
|
listeners.delete(listener);
|
|
232
252
|
this.logIfDebug(`Removed listener ` + fullId, { topic, listenerId: id });
|
|
@@ -246,7 +266,11 @@ export class EventBusHandler {
|
|
|
246
266
|
* @param data - The data of the event.
|
|
247
267
|
* @returns A promise that resolves to the event.
|
|
248
268
|
*/
|
|
249
|
-
public async request<T = EventPayload>(
|
|
269
|
+
public async request<T = EventPayload>(
|
|
270
|
+
sender: string,
|
|
271
|
+
topic: string,
|
|
272
|
+
data?: EventPayload,
|
|
273
|
+
): Promise<EventBusMessage<T>> {
|
|
250
274
|
if (!this.validateTopic(topic)) {
|
|
251
275
|
this.logAndThrowError(true, `Invalid topic: ` + topic);
|
|
252
276
|
}
|
|
@@ -255,8 +279,10 @@ export class EventBusHandler {
|
|
|
255
279
|
|
|
256
280
|
this.logIfDebug(`Requesting data from ` + topic, { event });
|
|
257
281
|
|
|
258
|
-
return new Promise<EventBusMessage<T>>(resolve => {
|
|
259
|
-
this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) =>
|
|
282
|
+
return new Promise<EventBusMessage<T>>((resolve) => {
|
|
283
|
+
this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) =>
|
|
284
|
+
resolve(value as EventBusMessage<T>),
|
|
285
|
+
);
|
|
260
286
|
this.emitInternal(sender, topic, data || {}, event.eventId, true);
|
|
261
287
|
});
|
|
262
288
|
}
|
|
@@ -271,7 +297,7 @@ export class EventBusHandler {
|
|
|
271
297
|
|
|
272
298
|
// Find wildcard matches
|
|
273
299
|
const wildcard = [...this.listeners.entries()]
|
|
274
|
-
.filter(([key]) => key.endsWith(
|
|
300
|
+
.filter(([key]) => key.endsWith('*') && topic.startsWith(key.slice(0, -1)))
|
|
275
301
|
.flatMap(([_, handlers]) => [...handlers]);
|
|
276
302
|
return new Set([...exact, ...wildcard]);
|
|
277
303
|
}
|
|
@@ -283,32 +309,35 @@ export class EventBusHandler {
|
|
|
283
309
|
*/
|
|
284
310
|
private validateTopic(topic: string): boolean {
|
|
285
311
|
// Split event type into parts
|
|
286
|
-
const parts = topic.split(
|
|
312
|
+
const parts = topic.split('.');
|
|
287
313
|
const [plugin, area, action] = parts;
|
|
288
314
|
|
|
289
315
|
if (parts.length !== 3) {
|
|
290
|
-
if (parts.length === 1 && plugin ===
|
|
316
|
+
if (parts.length === 1 && plugin === '*') {
|
|
291
317
|
return true;
|
|
292
318
|
}
|
|
293
|
-
if (parts.length === 2 && plugin !==
|
|
319
|
+
if (parts.length === 2 && plugin !== '*' && area === '*') {
|
|
294
320
|
return true;
|
|
295
321
|
}
|
|
296
322
|
this.logAndThrowError(false, `Event type must have 3 parts separated by dots. Received: ` + topic);
|
|
297
323
|
return false;
|
|
298
324
|
}
|
|
299
325
|
|
|
300
|
-
if (action ===
|
|
326
|
+
if (action === '*') {
|
|
301
327
|
return true;
|
|
302
328
|
}
|
|
303
329
|
|
|
304
330
|
// Validate action part
|
|
305
|
-
const validActions = [
|
|
331
|
+
const validActions = ['request', 'create', 'update', 'delete', 'trigger'];
|
|
306
332
|
|
|
307
|
-
if (validActions.some(a => action.startsWith(a))) {
|
|
333
|
+
if (validActions.some((a) => action.startsWith(a))) {
|
|
308
334
|
return true;
|
|
309
335
|
}
|
|
310
336
|
|
|
311
|
-
this.logAndThrowError(
|
|
337
|
+
this.logAndThrowError(
|
|
338
|
+
false,
|
|
339
|
+
`Invalid event topic name. The action: ` + action + '. Must be or start with one of: ' + validActions.join(', '),
|
|
340
|
+
);
|
|
312
341
|
return false;
|
|
313
342
|
}
|
|
314
343
|
|
|
@@ -327,4 +356,4 @@ export class EventBusHandler {
|
|
|
327
356
|
}
|
|
328
357
|
}
|
|
329
358
|
|
|
330
|
-
export const EventBus = EventBusHandler.getInstance();
|
|
359
|
+
export const EventBus = EventBusHandler.getInstance();
|
|
@@ -4,10 +4,10 @@ export type Plugin<T extends {} = {}> = Omit<RimoriPluginConfig<T>, 'context_men
|
|
|
4
4
|
endpoint: string;
|
|
5
5
|
assetEndpoint: string;
|
|
6
6
|
context_menu_actions: MenuEntry[];
|
|
7
|
-
release_channel:
|
|
8
|
-
}
|
|
7
|
+
release_channel: 'alpha' | 'beta' | 'stable';
|
|
8
|
+
};
|
|
9
9
|
|
|
10
|
-
export type ActivePlugin = Plugin<{ active?: boolean }
|
|
10
|
+
export type ActivePlugin = Plugin<{ active?: boolean }>;
|
|
11
11
|
|
|
12
12
|
// browsable page of a plugin
|
|
13
13
|
export interface PluginPage {
|
|
@@ -17,13 +17,22 @@ export interface PluginPage {
|
|
|
17
17
|
// Whether the page should be shown in the navbar
|
|
18
18
|
show: boolean;
|
|
19
19
|
description: string;
|
|
20
|
-
root:
|
|
20
|
+
root:
|
|
21
|
+
| 'vocabulary'
|
|
22
|
+
| 'grammar'
|
|
23
|
+
| 'reading'
|
|
24
|
+
| 'listening'
|
|
25
|
+
| 'watching'
|
|
26
|
+
| 'writing'
|
|
27
|
+
| 'speaking'
|
|
28
|
+
| 'other'
|
|
29
|
+
| 'community';
|
|
21
30
|
// The actions that can be triggered in the plugin
|
|
22
31
|
// The key is the action key. The other entries are additional properties needed when triggering the action
|
|
23
32
|
action?: {
|
|
24
33
|
key: string;
|
|
25
34
|
parameters: ObjectTool;
|
|
26
|
-
}
|
|
35
|
+
};
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
// a sidebar page of a plugin
|
|
@@ -65,7 +74,7 @@ export interface ContextMenuAction {
|
|
|
65
74
|
// id of the plugin that the action belongs to
|
|
66
75
|
plugin_id: string;
|
|
67
76
|
// key of the action. Used to know which action to trigger when clicking on the context menu
|
|
68
|
-
action_key: string
|
|
77
|
+
action_key: string;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
/**
|
|
@@ -86,7 +95,7 @@ export interface RimoriPluginConfig<T extends {} = {}> {
|
|
|
86
95
|
logo: string;
|
|
87
96
|
/** Optional website URL for the plugin's homepage or link to plugins owner for contributions */
|
|
88
97
|
website?: string;
|
|
89
|
-
}
|
|
98
|
+
};
|
|
90
99
|
/**
|
|
91
100
|
* Configuration for different types of pages.
|
|
92
101
|
*/
|
|
@@ -101,11 +110,11 @@ export interface RimoriPluginConfig<T extends {} = {}> {
|
|
|
101
110
|
settings?: string;
|
|
102
111
|
/** Optional array of event topics the plugin pages can listen to for cross-plugin communication */
|
|
103
112
|
topics?: string[];
|
|
104
|
-
}
|
|
113
|
+
};
|
|
105
114
|
/**
|
|
106
115
|
* Context menu actions that the plugin registers to appear in right-click menus throughout the application.
|
|
107
116
|
*/
|
|
108
|
-
context_menu_actions: Omit<MenuEntry,
|
|
117
|
+
context_menu_actions: Omit<MenuEntry, 'plugin_id'>[];
|
|
109
118
|
/**
|
|
110
119
|
* Documentation paths for different types of plugin documentation.
|
|
111
120
|
*/
|
|
@@ -116,7 +125,7 @@ export interface RimoriPluginConfig<T extends {} = {}> {
|
|
|
116
125
|
user_path: string;
|
|
117
126
|
/** Path to developer documentation for plugin development */
|
|
118
127
|
developer_path: string;
|
|
119
|
-
}
|
|
128
|
+
};
|
|
120
129
|
/**
|
|
121
130
|
* Configuration for the plugin's web worker if it uses background processing or exposes actions to other plugins.
|
|
122
131
|
*/
|
|
@@ -136,7 +145,7 @@ export interface Tool {
|
|
|
136
145
|
parameters: {
|
|
137
146
|
name: string;
|
|
138
147
|
description: string;
|
|
139
|
-
type:
|
|
148
|
+
type: 'string' | 'number' | 'boolean';
|
|
140
149
|
}[];
|
|
141
150
|
execute?: (args: Record<string, any>) => Promise<unknown> | unknown | void;
|
|
142
151
|
}
|
|
@@ -145,7 +154,7 @@ export interface Tool {
|
|
|
145
154
|
* The tool definition structure is used for LLM function calling and plugin action parameters.
|
|
146
155
|
* It defines the schema for tools that can be used by Language Learning Models (LLMs)
|
|
147
156
|
* and plugin actions.
|
|
148
|
-
*
|
|
157
|
+
*
|
|
149
158
|
* @example
|
|
150
159
|
* ```typescript
|
|
151
160
|
* const flashcardTool: Tool = {
|
|
@@ -155,13 +164,13 @@ export interface Tool {
|
|
|
155
164
|
* description: 'Number of flashcards to practice'
|
|
156
165
|
* },
|
|
157
166
|
* deck: {
|
|
158
|
-
* type: 'string',
|
|
167
|
+
* type: 'string',
|
|
159
168
|
* enum: ['latest', 'random', 'oldest', 'mix', 'best_known'],
|
|
160
169
|
* description: 'Type of deck to practice'
|
|
161
170
|
* }
|
|
162
171
|
* };
|
|
163
172
|
* ```
|
|
164
|
-
*
|
|
173
|
+
*
|
|
165
174
|
*/
|
|
166
175
|
export type ObjectTool = {
|
|
167
176
|
[key: string]: ToolParameter;
|
|
@@ -188,15 +197,15 @@ interface ToolParameter {
|
|
|
188
197
|
* Supports primitive types, nested objects for complex data structures,
|
|
189
198
|
* and arrays of objects for collections. The tuple notation [{}] indicates
|
|
190
199
|
* arrays of objects with a specific structure.
|
|
191
|
-
*
|
|
200
|
+
*
|
|
192
201
|
* @example Primitive: 'string' | 'number' | 'boolean'
|
|
193
202
|
* @example Nested object: { name: { type: 'string' }, age: { type: 'number' } }
|
|
194
203
|
* @example Array of objects: [{ id: { type: 'string' }, value: { type: 'number' } }]
|
|
195
204
|
*/
|
|
196
205
|
type ToolParameterType =
|
|
197
206
|
| PrimitiveType
|
|
198
|
-
| { [key: string]: ToolParameter }
|
|
199
|
-
| [{ [key: string]: ToolParameter }];
|
|
207
|
+
| { [key: string]: ToolParameter } // for nested objects
|
|
208
|
+
| [{ [key: string]: ToolParameter }]; // for arrays of objects (notice the tuple type)
|
|
200
209
|
|
|
201
210
|
/**
|
|
202
211
|
* Primitive data types supported by the LLM tool system.
|
package/src/fromRimori/readme.md
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Files inside this folder are copied from Rimori and should not be changed.
|
|
2
|
-
If change is needed adapt it in Rimori and copy the file then over.
|
|
1
|
+
Files inside this folder are copied from Rimori and should not be changed.
|
|
2
|
+
If change is needed adapt it in Rimori and copy the file then over.
|
package/src/hooks/UseChatHook.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Tool } from
|
|
3
|
-
import { useRimori } from
|
|
4
|
-
import { Message, ToolInvocation } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tool } from '../fromRimori/PluginTypes';
|
|
3
|
+
import { useRimori } from '../providers/PluginProvider';
|
|
4
|
+
import { Message, ToolInvocation } from '../core/controller/AIController';
|
|
5
5
|
|
|
6
6
|
export function useChat(tools?: Tool[]) {
|
|
7
7
|
const [messages, setMessages] = React.useState<Message[]>([]);
|
|
@@ -11,18 +11,28 @@ export function useChat(tools?: Tool[]) {
|
|
|
11
11
|
const append = (appendMessages: Message[]) => {
|
|
12
12
|
const allMessages = [...messages, ...appendMessages];
|
|
13
13
|
setMessages(allMessages);
|
|
14
|
-
ai.getSteamedText(
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
ai.getSteamedText(
|
|
15
|
+
allMessages,
|
|
16
|
+
(id, message, finished: boolean, toolInvocations?: ToolInvocation[]) => {
|
|
17
|
+
const lastMessage = messages[messages.length - 1];
|
|
18
|
+
setIsLoading(!finished);
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
if (lastMessage?.id === id) {
|
|
21
|
+
lastMessage.content = message;
|
|
22
|
+
setMessages([...messages, lastMessage]);
|
|
23
|
+
} else {
|
|
24
|
+
setMessages([...allMessages, { id, role: 'assistant', content: message, toolCalls: toolInvocations }]);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
tools,
|
|
28
|
+
);
|
|
25
29
|
};
|
|
26
30
|
|
|
27
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
messages,
|
|
33
|
+
append,
|
|
34
|
+
isLoading,
|
|
35
|
+
setMessages,
|
|
36
|
+
lastMessage: messages[messages.length - 1] as Message | undefined,
|
|
37
|
+
};
|
|
28
38
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Re-export everything
|
|
2
2
|
export * from './components';
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export { FirstMessages } from
|
|
12
|
-
export { AudioController } from
|
|
3
|
+
export * from './hooks/UseChatHook';
|
|
4
|
+
export * from './plugin/PluginController';
|
|
5
|
+
export * from './providers/PluginProvider';
|
|
6
|
+
export * from './cli/types/DatabaseTypes';
|
|
7
|
+
export * from './utils/difficultyConverter';
|
|
8
|
+
export * from './utils/PluginUtils';
|
|
9
|
+
export * from './utils/Language';
|
|
10
|
+
export * from './fromRimori/PluginTypes';
|
|
11
|
+
export { FirstMessages } from './components/ai/utils';
|
|
12
|
+
export { AudioController } from './plugin/AudioController';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { EventBus, EventBusMessage } from
|
|
1
|
+
import { EventBus, EventBusMessage } from '../fromRimori/EventBus';
|
|
2
2
|
|
|
3
3
|
export type AccomplishmentMessage = EventBusMessage<MicroAccomplishmentPayload>;
|
|
4
4
|
|
|
5
|
-
export const skillCategories = [
|
|
5
|
+
export const skillCategories = ['reading', 'listening', 'speaking', 'writing', 'learning', 'community'] as const;
|
|
6
6
|
|
|
7
7
|
interface BaseAccomplishmentPayload {
|
|
8
|
-
type:
|
|
8
|
+
type: 'micro' | 'macro';
|
|
9
9
|
skillCategory: (typeof skillCategories)[number];
|
|
10
10
|
/*
|
|
11
11
|
what is the accomplishment? e.g. chapter, flashcard, story, etc.
|
|
@@ -25,11 +25,11 @@ interface BaseAccomplishmentPayload {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface MicroAccomplishmentPayload extends BaseAccomplishmentPayload {
|
|
28
|
-
type:
|
|
28
|
+
type: 'micro';
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface MacroAccomplishmentPayload extends BaseAccomplishmentPayload {
|
|
32
|
-
type:
|
|
32
|
+
type: 'macro';
|
|
33
33
|
errorRatio: number;
|
|
34
34
|
durationMinutes: number;
|
|
35
35
|
}
|
|
@@ -43,14 +43,17 @@ export class AccomplishmentHandler {
|
|
|
43
43
|
this.pluginId = pluginId;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
emitAccomplishment(payload: Omit<AccomplishmentPayload,
|
|
47
|
-
const accomplishmentPayload = {
|
|
46
|
+
emitAccomplishment(payload: Omit<AccomplishmentPayload, 'type'>) {
|
|
47
|
+
const accomplishmentPayload = {
|
|
48
|
+
...payload,
|
|
49
|
+
type: 'durationMinutes' in payload ? 'macro' : 'micro',
|
|
50
|
+
} as AccomplishmentPayload;
|
|
48
51
|
|
|
49
52
|
this.validateAccomplishment(accomplishmentPayload);
|
|
50
53
|
|
|
51
54
|
const sanitizedPayload = this.sanitizeAccomplishment(accomplishmentPayload);
|
|
52
55
|
|
|
53
|
-
const topic =
|
|
56
|
+
const topic = 'global.accomplishment.trigger' + (accomplishmentPayload.type === 'macro' ? 'Macro' : 'Micro');
|
|
54
57
|
|
|
55
58
|
EventBus.emit(this.pluginId, topic, sanitizedPayload);
|
|
56
59
|
}
|
|
@@ -62,53 +65,59 @@ export class AccomplishmentHandler {
|
|
|
62
65
|
|
|
63
66
|
//regex validate accomplishmentKeyword
|
|
64
67
|
if (!/^[a-z_-]+$/.test(payload.accomplishmentKeyword)) {
|
|
65
|
-
throw new Error(
|
|
68
|
+
throw new Error(
|
|
69
|
+
`The accomplishment keyword: ${payload.accomplishmentKeyword} is invalid. Only lowercase letters, minuses and underscores are allowed`,
|
|
70
|
+
);
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
//description is required
|
|
69
74
|
if (payload.description.length < 10) {
|
|
70
|
-
throw new Error(
|
|
75
|
+
throw new Error('Description is too short');
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
//check that the type is valid
|
|
74
|
-
if (![
|
|
75
|
-
throw new Error(
|
|
79
|
+
if (!['micro', 'macro'].includes(payload.type)) {
|
|
80
|
+
throw new Error('Invalid accomplishment type ' + payload.type);
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
//durationMinutes is required
|
|
79
|
-
if (payload.type ===
|
|
80
|
-
throw new Error(
|
|
84
|
+
if (payload.type === 'macro' && payload.durationMinutes < 4) {
|
|
85
|
+
throw new Error('The duration must be at least 4 minutes');
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
//errorRatio is required
|
|
84
|
-
if (payload.type ===
|
|
85
|
-
throw new Error(
|
|
89
|
+
if (payload.type === 'macro' && (payload.errorRatio < 0 || payload.errorRatio > 1)) {
|
|
90
|
+
throw new Error('The error ratio must be between 0 and 1');
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
//regex check meta data key
|
|
89
94
|
if (payload.meta) {
|
|
90
|
-
payload.meta.forEach(meta => {
|
|
95
|
+
payload.meta.forEach((meta) => {
|
|
91
96
|
if (!/^[a-z_]+$/.test(meta.key)) {
|
|
92
|
-
throw new Error(
|
|
97
|
+
throw new Error('Invalid meta data key ' + meta.key + ', only lowercase letters and underscores are allowed');
|
|
93
98
|
}
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
private sanitizeAccomplishment(payload: AccomplishmentPayload) {
|
|
99
|
-
payload.description = payload.description.replace(/[^\x20-\x7E]/g,
|
|
104
|
+
payload.description = payload.description.replace(/[^\x20-\x7E]/g, '');
|
|
100
105
|
|
|
101
106
|
payload.meta?.forEach((meta) => {
|
|
102
|
-
meta.description = meta.description.replace(/[^\x20-\x7E]/g,
|
|
107
|
+
meta.description = meta.description.replace(/[^\x20-\x7E]/g, '');
|
|
103
108
|
});
|
|
104
109
|
|
|
105
110
|
return payload;
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
private getDecoupledTopic(topic: string) {
|
|
109
|
-
const [plugin, skillCategory, accomplishmentKeyword] = topic.split(
|
|
114
|
+
const [plugin, skillCategory, accomplishmentKeyword] = topic.split('.');
|
|
110
115
|
|
|
111
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
plugin: plugin || '*',
|
|
118
|
+
skillCategory: skillCategory || '*',
|
|
119
|
+
accomplishmentKeyword: accomplishmentKeyword || '*',
|
|
120
|
+
};
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
/**
|
|
@@ -116,30 +125,39 @@ export class AccomplishmentHandler {
|
|
|
116
125
|
* @param accomplishmentTopic - The topic of the accomplishment event. The pattern can be any pattern of plugin.skillCategory.accomplishmentKeyword or an * as wildcard for any plugin, skill category or accomplishment keyword
|
|
117
126
|
* @param callback - The callback function to be called when the accomplishment event is triggered
|
|
118
127
|
*/
|
|
119
|
-
subscribe(
|
|
120
|
-
|
|
128
|
+
subscribe(
|
|
129
|
+
accomplishmentTopics = '*' as string | string[],
|
|
130
|
+
callback: (payload: EventBusMessage<AccomplishmentPayload>) => void,
|
|
131
|
+
) {
|
|
132
|
+
if (typeof accomplishmentTopics === 'string') {
|
|
121
133
|
accomplishmentTopics = [accomplishmentTopics];
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
accomplishmentTopics.forEach((accomplishmentTopic) => {
|
|
125
|
-
const topicLength = accomplishmentTopic.split(
|
|
137
|
+
const topicLength = accomplishmentTopic.split('.').length;
|
|
126
138
|
if (topicLength === 1) {
|
|
127
|
-
accomplishmentTopic +=
|
|
139
|
+
accomplishmentTopic += '.*.*';
|
|
128
140
|
} else if (topicLength === 2) {
|
|
129
|
-
accomplishmentTopic +=
|
|
141
|
+
accomplishmentTopic += '.*';
|
|
130
142
|
} else if (topicLength !== 3) {
|
|
131
|
-
throw new Error(
|
|
143
|
+
throw new Error(
|
|
144
|
+
'Invalid accomplishment topic pattern. The pattern must be plugin.skillCategory.accomplishmentKeyword or an * as wildcard for any plugin, skill category or accomplishment keyword',
|
|
145
|
+
);
|
|
132
146
|
}
|
|
133
147
|
|
|
134
|
-
EventBus.on<AccomplishmentPayload>(
|
|
135
|
-
|
|
148
|
+
EventBus.on<AccomplishmentPayload>(
|
|
149
|
+
['global.accomplishment.triggerMicro', 'global.accomplishment.triggerMacro'],
|
|
150
|
+
(event) => {
|
|
151
|
+
const { plugin, skillCategory, accomplishmentKeyword } = this.getDecoupledTopic(accomplishmentTopic);
|
|
136
152
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
if (plugin !== '*' && event.sender !== plugin) return;
|
|
154
|
+
if (skillCategory !== '*' && event.data.skillCategory !== skillCategory) return;
|
|
155
|
+
if (accomplishmentKeyword !== '*' && event.data.accomplishmentKeyword !== accomplishmentKeyword) return;
|
|
140
156
|
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
callback(event);
|
|
158
|
+
},
|
|
159
|
+
[this.pluginId],
|
|
160
|
+
);
|
|
143
161
|
});
|
|
144
162
|
}
|
|
145
163
|
}
|